Зачем нам нужен бокс и распаковка в С#?
Я знаю, что такое бокс и unboxing, но я не могу понять его использование. Почему и где его использовать?
short s=25;
object objshort=s; //Boxing
short anothershort=(short)objshort; //Unboxing
Зачем нам нужен бокс и распаковка в С#?
Я знаю, что такое бокс и unboxing, но я не могу понять его использование. Почему и где его использовать?
short s=25;
object objshort=s; //Boxing
short anothershort=(short)objshort; //Unboxing
Почему
Чтобы иметь унифицированную систему типов и разрешать типы значений иметь совершенно иное представление своих базовых данных от того, как ссылочные типы представляют их базовые данные (например, 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
! Преобразование бокса делает копию значения в коробке, объясняя разницу в поведении.
В среде .NET существуют два вида типов - типы значений и ссылочные типы. Это относительно распространено в языках OO.
Одной из важных особенностей объектно-ориентированных языков является способность обрабатывать экземпляры с помощью агностического типа. Это называется polymorphism. Поскольку мы хотим использовать полиморфизм, но у нас есть два разных вида типов, должен быть какой-то способ объединить их, чтобы мы могли справиться с тем или иным способом.
Теперь, в прежние времена (1.0 из Microsoft.NET), не было этих новомодных генериков hullabaloo. Вы не могли написать метод, который имел бы один аргумент, который мог бы обслуживать тип значения и ссылочный тип. Это нарушение полиморфизма. Поэтому бокс был принят как средство принуждения типа значения к объекту.
Если бы это было невозможно, структура была бы усеяна методами и классами, единственной целью которых было принятие других видов типа. Не только это, но поскольку типы значений действительно не имеют общего предка общего типа, вам придется иметь другую перегрузку метода для каждого типа значений (бит, байт, int16, int32 и т.д. И т.д. И т.д.).
Бокс предотвратил это. И вот почему британцы празднуют День подарков.
Бокс - это не то, что вы используете - это то, что используется во время выполнения, так что вы можете обрабатывать ссылочные и типы значений таким же образом, когда это необходимо. Например, если вы использовали ArrayList для хранения списка целых чисел, целые числа были вставлены в бокс для размещения в слоях типа объекта в ArrayList.
Используя общие коллекции сейчас, это в значительной степени уходит. Если вы создаете List<int>
, бокс не выполняется - List<int>
может содержать целые числа напрямую.
Лучший способ понять это - посмотреть на языки программирования более низкого уровня, на которых строится С#.
В языках самого низкого уровня, таких как C, все переменные находятся в одном месте: Stack. Каждый раз, когда вы объявляете переменную, она переходит в стек. Они могут быть только примитивными значениями, такими как bool, байт, 32-битный int, 32-разрядный uint и т.д. Stack является простым и быстрым. Когда переменные добавляются, они просто переходят один поверх другого, поэтому первый вы объявляете сидит на say, 0x00, следующий на 0x01, следующий на 0x02 в ОЗУ и т.д. Кроме того, переменные часто предварительно обрабатываются при компиляции, времени, поэтому их адрес известен до того, как вы даже запустите программу.
На следующем уровне вверх, например С++, вводится вторая структура памяти, называемая кучей. Вы по-прежнему в основном используете Stack, но специальные стеки, называемые Указатели, могут быть добавлены в Stack, которые хранят адрес памяти для первого байта объекта, и этот объект живет в куче. Куча - это беспорядок и несколько дорогой в обслуживании, потому что в отличие от переменных Stack они не складываются линейно, а затем вниз по мере выполнения программы. Они могут приходить и уходить без определенной последовательности, и они могут расти и сокращаться.
Работа с указателями сложна. Они являются причиной утечек памяти, переполнения буфера и разочарования. С# для спасения.
На более высоком уровне, С#, вам не нужно думать о указателях. Структура .Net(написанная на С++) думает об этом для вас и представляет их вам как "Ссылки на объекты" и для производительности, позволяет вам хранить более простые значения, такие как bools, bytes и ints как типы значений. Под капотом объекты и вещи, которые создают экземпляр класса, идут на дорогостоящую память, управляемую памятью, в то время как типы значений идут в том же стеке, что и на низкоуровневом С - сверхбыстро.
Чтобы поддерживать взаимодействие между этими двумя принципиально разными концепциями памяти (и стратегиями хранения) простым с точки зрения кодера, типы значений могут быть в штучной упаковке в любое время. Бокс заставляет копировать значение из стека, класть объект и размещать на куче - более дорогое, но, жидкое взаимодействие с Справочный мир. Как указывают другие ответы, это произойдет, когда вы, например, скажете:
bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!
Сильной иллюстрацией преимущества бокса является проверка на null:
if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false
Наш объект o является технически адресом в стеке, который указывает на копию нашего bool b, который был скопирован в кучу. Мы можем проверить o на null, потому что bool был помещен в коробку и помещен туда.
В общем, вам следует избегать бокса, если вам это не нужно, например, передать int/bool/whatever как объект для аргумента. В .Net есть некоторые базовые структуры, которые по-прежнему требуют передачи типов значений в качестве объекта (и поэтому требуют бокса), но по большей части вам не нужно будет вставлять ящик.
Неисчерпывающий список исторических структур С#, требующих бокса, что вам следует избегать:
Система событий имеет наименьшее использование в режиме Race Condition и не поддерживает async. Добавьте в проблему бокса, и этого, вероятно, следует избегать. (Вы можете заменить его, например, системой событий async, которая использует Generics.)
Старые модели Threading и Timer заставляли Box использовать их параметры, но были заменены async/await, которые намного чище и эффективнее.
Коллекции .Net 1.1 полностью основывались на Боксе, потому что они подошли к Generics. Они все еще ногами в System.Collections. В любом новом коде вы должны использовать Коллекции из System.Collections.Generic, которые в дополнение к тому, чтобы избежать бокса, также обеспечить более сильную безопасность типов.
Вам следует избегать объявления ваших типов значений как Bool
вместо Bool
, Int
вместо Int
и т.д., потому что вы сообщаете .Net Framework, чтобы вставить эту переменную, когда вы, вероятно, не используете ее для этого бокса. Единственное исключение было бы, когда вам придется иметь дело с вышеупомянутыми историческими проблемами, которые заставляют Бокс, и вы хотите избежать хита производительности бокса позже, когда вы знаете, что он будет в коробке в любом случае.
Предложение по Микаэлю ниже:
using System.Collections.Generic;
var employeeCount = 5;
var list = new List<int>(10);
using System.Collections;
Int employeeCount = 5;
var list = new ArrayList(10);
Бокс и Unboxing специально используются для обработки объектов типа значения в качестве ссылочного типа; перемещая их фактическое значение в управляемую кучу и получая доступ к их значению по ссылке.
Без бокса и распаковки вы никогда не сможете передавать типы значений по ссылке; и это означает, что вы не можете передавать типы значений в качестве экземпляров объекта.
Последнее место, которое я должен был удалить, было при написании кода, который извлекал некоторые данные из базы данных (я не использовал LINQ to SQL, просто старый ADO.NET):
int myIntValue = (int)reader["MyIntValue"];
В принципе, если вы работаете со старыми API до дженериков, вы столкнетесь с боксом. Кроме этого, это не так часто.
Бокс необходим, когда у нас есть функция, которая нуждается в объекте в качестве параметра, но у нас есть разные типы значений, которые необходимо передать, в этом случае нам нужно сначала преобразовать типы значений в типы данных объектов, прежде чем передавать их в функция.
Я не думаю, что это правда, попробуйте это вместо:
class Program
{
static void Main(string[] args)
{
int x = 4;
test(x);
}
static void test(object o)
{
Console.WriteLine(o.ToString());
}
}
Это работает отлично, я не использовал бокс /unboxing. (Если компилятор делает это за кулисами?)
В .net, каждый экземпляр объекта или любой полученный из него тип включает структуру данных, которая содержит информацию о ее типе. "Реальные" типы значений в .net не содержат никакой такой информации. Чтобы позволить манипулировать данными в типах значений подпрограммами, которые ожидают получать типы, полученные от объекта, система автоматически определяет для каждого типа значений соответствующий тип класса с теми же членами и полями. Бокс создает новые экземпляры этого типа класса, копируя поля из экземпляра типа значения. Unboxing копирует поля из экземпляра типа класса в экземпляр типа значения. Все типы классов, созданные из типов значений, производятся из иронически названного класса ValueType (который, несмотря на его имя, на самом деле является ссылочным типом).
Если метод использует только ссылочный тип в качестве параметра (скажем, общий метод, ограниченный классом с помощью ограничения new
), вы не сможете передать ему ссылочный тип и его нужно поместить.
Это также верно для любых методов, которые принимают object
в качестве параметра - это должен быть ссылочный тип.
В общем, вы, как правило, не захотите боксировать свои типы значений.
Однако есть редкие случаи, когда это полезно. Например, если вам нужно настроить таргетинг на инфраструктуру 1.1, у вас не будет доступа к общим коллекциям. Любое использование коллекций в .NET 1.1 требует обработки вашего типа значений как объекта System.Object, который вызывает бокс/распаковку.
Есть еще примеры, которые могут быть полезны в .NET 2.0+. Каждый раз, когда вы хотите воспользоваться тем, что все типы, включая типы значений, могут рассматриваться как объекты напрямую, вам может потребоваться использовать бокс/распаковку. Это может быть полезно иногда, поскольку оно позволяет сохранять любой тип в коллекции (используя объект вместо T в общей коллекции), но в целом лучше избегать этого, поскольку вы теряете безопасность типов. Один случай, когда часто возникает бокс, - это когда вы используете Reflection - многие из вызовов в отражении потребуют бокса/распаковки при работе со типами значений, так как тип неизвестен заранее.