Зачем
Иметь унифицированную систему типов и позволить типам значений иметь совершенно другое представление своих базовых данных от способа, которым ссылочные типы представляют свои базовые данные (например, это int
просто набор из тридцати двух битов, который полностью отличается от эталонного тип).
Думайте об этом так. У вас есть переменная o
типа object
. И теперь у вас есть, int
и вы хотите положить его в o
. o
это ссылка на что-то где-то, и int
это категорически не ссылка на что-то где-то (в конце концов, это просто число). Итак, вы делаете следующее: вы создаете новое, object
которое может хранить, int
а затем назначаете ссылку на этот объект o
. Мы называем этот процесс «боксом».
Таким образом, если вам не нужна единая система типов (т. Е. Ссылочные типы и типы значений имеют очень разные представления и вам не нужен общий способ «представлять» их), вам не нужен бокс. Если вам не важно int
представлять их базовое значение (т. Е. Вместо этого иметь int
также ссылочные типы и просто хранить ссылку на их базовое значение), тогда вам не нужен бокс.
где я должен использовать это.
Например, старый тип коллекции ArrayList
ест только object
s. То есть он хранит только ссылки на то, что где-то живет. Без бокса вы не можете положить int
в такую коллекцию. Но с боксом вы можете.
Теперь, во времена дженериков, вам это действительно не нужно, и вы, как правило, можете развлекаться, не задумываясь о проблеме. Но есть несколько предостережений, о которых следует знать:
Это верно:
double e = 2.718281828459045;
int ee = (int)e;
Это не:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)o; // runtime exception
Вместо этого вы должны сделать это:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)(double)o;
Сначала мы должны явно распаковать double
( (double)o
), а затем привести его к int
.
Что является результатом следующего:
double e = 2.718281828459045;
double d = e;
object o1 = d;
object o2 = e;
Console.WriteLine(d == e);
Console.WriteLine(o1 == o2);
Подумайте об этом на секунду, прежде чем перейти к следующему предложению.
Если бы вы сказали True
и False
здорово! Чего ждать? Это потому, что ==
в ссылочных типах используется равенство ссылок, которое проверяет, равны ли ссылки, а не равны ли базовые значения. Это опасно легкая ошибка. Возможно, даже более тонкий
double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);
также будет печатать False
!
Лучше сказать:
Console.WriteLine(o1.Equals(o2));
который затем, к счастью, напечатает True
.
Еще одна тонкость:
[struct|class] Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point p = new Point(1, 1);
object o = p;
p.x = 2;
Console.WriteLine(((Point)o).x);
Какой выход? Это зависит! Если Point
есть, struct
то выходной, 1
но если Point
есть, class
то выходной 2
! Конверсионное преобразование создает копию упакованного значения, объясняющего разницу в поведении.
boxing
иunboxing
?В платформе .NET существует два вида типов - типы значений и ссылочные типы. Это относительно распространено в ОО языках.
Одной из важных особенностей объектно-ориентированных языков является способность обрабатывать экземпляры независимо от типа. Это называется полиморфизмом . Поскольку мы хотим использовать преимущества полиморфизма, но у нас есть два разных вида типов, должен быть какой-то способ их объединения, чтобы мы могли обрабатывать один или другой таким же образом.
Теперь, в былые времена (1.0 из Microsoft.NET), не было этого новомодного дженерика хуллабалу. Вы не могли написать метод с одним аргументом, который мог бы обслуживать тип значения и ссылочный тип. Это нарушение полиморфизма. Таким образом, бокс был принят как средство для приведения типа значения в объект.
Если бы это было невозможно, рамки были бы завалены методами и классами, единственной целью которых было принятие других видов типа. И не только это, но так как типы значений не имеют общего предка общего типа, вам придется иметь разную перегрузку метода для каждого типа значения (бит, байт, int16, int32 и т. Д. И т. Д. И т. Д.).
Бокс предотвратил это. И именно поэтому британцы отмечают День подарков.
источник
List<string>.Enumerator
кIEnumerator<string>
приводит к объекту, который в основном ведет себя как тип класса, но с неработающимEquals
методом. Лучший способ броска ,List<string>.Enumerator
чтобыIEnumerator<string>
бы вызвать оператор пользовательского преобразования, но существование подразумеваемого предотвращает конверсионных что.Лучший способ понять это - взглянуть на языки программирования более низкого уровня, на которых строится C #.
В языках самого низкого уровня, таких как C, все переменные находятся в одном месте: стек. Каждый раз, когда вы объявляете переменную, она попадает в стек. Они могут быть только примитивными значениями, такими как bool, byte, 32-bit int, 32-bit uint и т. Д. Стек является простым и быстрым. Когда переменные добавляются, они просто идут одна поверх другой, поэтому первое, что вы объявляете, находится, скажем, в 0x00, следующее в 0x01, следующее в 0x02 в ОЗУ и т. Д. Кроме того, переменные часто предварительно адресуются при компиляции. время, поэтому их адрес известен еще до того, как вы запустите программу.
На следующем уровне вверх, как в C ++, вводится вторая структура памяти, которая называется Heap. Вы по-прежнему в основном живете в стеке, но в стек можно добавить специальные целые, называемые указателями , которые хранят адрес памяти для первого байта объекта, и этот объект живет в куче. Куча - это беспорядок и несколько дорогой в обслуживании, потому что в отличие от переменных стека они не накапливаются линейно вверх, а затем вниз при выполнении программы. Они могут приходить и уходить в определенной последовательности, и они могут расти и уменьшаться.
Работать с указателями сложно. Они являются причиной утечек памяти, переполнения буфера и разочарования. C # на помощь.
На более высоком уровне, C #, вам не нужно думать об указателях. Платформа .Net (написанная на C ++) думает об этом для вас и представляет их вам как ссылки на объекты, а для производительности позволяет хранить более простые значения как bools, bytes и int как Типы Значения. Под капотом объекты и вещи, которые создают экземпляр класса, идут в дорогую, управляемую памятью кучу, в то время как типы значений идут в том же стеке, который вы имели в низкоуровневом C - сверхбыстрый.
Для упрощения взаимодействия между этими 2 принципиально различными концепциями памяти (и стратегий хранения) с точки зрения кодировщика, типы значений могут быть упакованы в любое время. Бокс заставляет копировать значение из стека, помещать в объект и помещать в кучу - более дорогое, но плавное взаимодействие с эталонным миром. Как указывают другие ответы, это произойдет, когда вы, например, скажете:
Яркой иллюстрацией преимущества бокса является проверка на ноль:
Наш объект o является технически адресом в стеке, который указывает на копию нашего bool b, который был скопирован в кучу. Мы можем проверить на ноль, потому что бул был в штучной упаковке и положил туда.
В общем, вы должны избегать бокса, если вам это не нужно, например, передавать int / bool / what в качестве объекта аргументу. В .Net есть некоторые базовые структуры, которые все еще требуют передачи Типов Значений как объекта (и поэтому требуют Бокса), но по большей части вам никогда не понадобится Box.
Неисчерпывающий список исторических структур C #, требующих бокса, которых следует избегать:
Система событий, как оказалось, имеет условие гонки в наивном использовании, и она не поддерживает асинхронность. Добавьте в проблему бокса, и этого, вероятно, следует избегать. (Вы можете заменить его, например, на асинхронную систему событий, которая использует Generics.)
Старые модели Threading и Timer принудительно устанавливали Box по своим параметрам, но были заменены на async / await, которые намного чище и эффективнее.
Коллекции .Net 1.1 полностью полагались на бокс, потому что они были раньше, чем Generics. Они все еще работают в System.Collections. В любом новом коде вы должны использовать Коллекции из System.Collections.Generic, которые, помимо того, что избегают Бокса, также обеспечат вам большую безопасность типов .
Вы должны избегать объявления или передачи своих Типов значений как объектов, если только вам не приходится иметь дело с вышеупомянутыми историческими проблемами, которые вызывают бокс, и вы хотите избежать снижения производительности бокса позже, когда вы знаете, что он все равно будет помещен в бокс.
Предложение Микаэля ниже:
Сделай это
Не этот
Обновить
Этот ответ первоначально предполагал, что Int32, Bool и т. Д. Вызывают бокс, хотя на самом деле это простые псевдонимы для типов значений. То есть .Net имеет типы, такие как Bool, Int32, String и C #, их псевдонимы - bool, int, string без какой-либо функциональной разницы.
источник
Бокс на самом деле не то, что вы используете - это то, что использует среда выполнения, так что вы можете обрабатывать ссылочные типы и типы значений одинаково, когда это необходимо. Например, если вы использовали ArrayList для хранения списка целых чисел, целые числа были упакованы в ячейки для размещения в слотах типа объекта в ArrayList.
Используя общие коллекции сейчас, это в значительной степени исчезает. Если вы создаете
List<int>
, бокс не выполняется - онList<int>
может содержать целые числа напрямую.источник
Упаковка и распаковка специально используются для обработки объектов типа значения как ссылочного типа; перемещение их фактического значения в управляемую кучу и доступ к их значению по ссылке.
Без упаковки и распаковки вы никогда не сможете передавать типы значений по ссылке; и это означает, что вы не можете передавать типы значений как экземпляры Object.
источник
Последнее, что мне пришлось распаковать, - это когда я писал код, который извлекал некоторые данные из базы данных (я не использовал LINQ to SQL , просто старый ADO.NET ):
По сути, если вы работаете с более старыми API, прежде чем генерики, вы столкнетесь с боксом. Кроме этого, это не так часто.
источник
Упаковка необходима, когда у нас есть функция, которой нужен объект в качестве параметра, но у нас есть разные типы значений, которые необходимо передать, в этом случае нам нужно сначала преобразовать типы значений в типы данных объекта, прежде чем передавать его в функцию.
Я не думаю, что это правда, попробуйте вместо этого:
Это работает очень хорошо, я не использовал бокс / распаковку. (Разве компилятор делает это за кадром?)
источник
В .net каждый экземпляр Object или любой производный от него тип включает структуру данных, которая содержит информацию о его типе. Типы «реальных» значений в .net не содержат никакой такой информации. Чтобы позволить данным в типах значений манипулировать подпрограммами, которые ожидают получения типов, полученных из объекта, система автоматически определяет для каждого типа значения соответствующий тип класса с теми же членами и полями. Бокс создает новые экземпляры этого типа класса, копируя поля из экземпляра типа значения. Распаковка копирует поля из экземпляра типа класса в экземпляр типа значения. Все типы классов, которые создаются из типов значений, являются производными от иронически названного класса ValueType (который, несмотря на свое имя, на самом деле является ссылочным типом).
источник
Когда метод принимает в качестве параметра только ссылочный тип (скажем, универсальный метод, ограниченный как класс через
new
ограничение), вы не сможете передать ему ссылочный тип и поставить его в рамку.Это также верно для любых методов , которые принимают в
object
качестве параметра - это будет иметь , чтобы быть ссылочного типа.источник
В общем, вы, как правило, хотите избегать упаковки типов значений.
Тем не менее, есть редкие случаи, где это полезно. Например, если вам нужно ориентироваться на платформу 1.1, у вас не будет доступа к универсальным коллекциям. Любое использование коллекций в .NET 1.1 потребует обработки вашего типа значения как System.Object, что приведет к упаковке / распаковке.
Есть еще случаи, когда это может быть полезно в .NET 2.0+. В любое время, когда вы хотите воспользоваться тем фактом, что все типы, включая типы значений, могут рассматриваться как объект напрямую, вам может понадобиться использовать упаковку / распаковку. Иногда это может быть удобно, поскольку позволяет сохранять любой тип в коллекции (используя объект вместо T в универсальной коллекции), но в целом этого лучше избегать, поскольку вы теряете безопасность типов. Тем не менее, один из случаев, когда часто происходит боксирование, - это использование Reflection. Многие вызовы в отражении требуют бокса / распаковки при работе с типами значений, так как тип заранее неизвестен.
источник
Упаковка - это преобразование значения в ссылочный тип с данными с некоторым смещением в объекте в куче.
Что касается того, что на самом деле делает бокс. вот несколько примеров
Mono C ++
Распаковка в Mono - это процесс приведения указателя со смещением в 2 gpointers в объекте (например, 16 байтов). А
gpointer
этоvoid*
. Это имеет смысл, если смотреть на определение,MonoObject
поскольку это явно просто заголовок для данных.C ++
Чтобы поместить значение в C ++, вы можете сделать что-то вроде:
источник