Зачем нам нужен бокс и распаковка в C#?


зачем нам бокс и распаковка в C#?

Я знаю, что такое бокс и распаковка, но я не могу понять его реального использования. Почему и где я должен его использовать?

short s = 25;

object objshort = s;  //Boxing

short anothershort = (short)objshort;  //Unboxing
10 285

10 ответов:

почему

иметь единую систему типов и позволять типам значений иметь совершенно другое представление своих базовых данных от способа, которым ссылочные типы представляют свои базовые данные (например,int это просто ведро из тридцати двух битов, которое полностью отличается от ссылочного типа).

подумайте об этом. У вас есть переменная o типа object. И теперь у вас есть int и вы хотите поставить его в o. o - это ссылка на что-то где-то, а int это отнюдь не Ссылка на что-то где-то (в конце концов, это просто номер). Итак, что вы делаете это: вы делаете новый object который может хранить int и затем вы назначаете ссылку на этот объект o. Мы называем этот процесс "боксом"."

Итак, если вы не заботитесь о том, чтобы иметь единую систему типов (т. е. ссылочные типы и типы значений имеют очень разные представления, и вы не хочу общий способ "представлять" два), то вам не нужен бокс. Если вы не заботитесь о int представляют их базовое значение (т. е. вместо этого имеют int быть ссылочными типами и просто хранить ссылку на их реальную ценность), то вам не нужен бокс.

где я должен использовать его.

например, старый тип коллекции ArrayList ест только objects. то есть, он хранит только ссылки на то, что живет где-то. Без бокса вы не можете поставить 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 framework существует два вида типов-типы значений и ссылочные типы. Это относительно распространено в языках ОО.

одной из важных особенностей объектно-ориентированных языков является возможность обрабатывать экземпляры агностическим способом. Это называется полиморфизм. Поскольку мы хотим воспользоваться полиморфизмом, но у нас есть два разных вида типов, должен быть какой-то способ объединить их, чтобы мы могли справиться с одним или с другой-точно так же.

теперь, в старые времена (1.0 из Microsoft.NET), не было этого новомодного генералиссимуса hullabaloo. Вы не можете написать метод, который имел бы один аргумент, который мог бы обслуживать тип значения и ссылочный тип. Это нарушение полиморфизма. Таким образом, бокс был принят как средство принуждения типа значения к объекту.

Если бы это было невозможно, структура была бы завалена методами и классами, единственной целью которых было принять другие виды типа. Не только это, но поскольку типы значений действительно не имеют общего предка типа, вам придется иметь другую перегрузку метода для каждого типа значения (бит, байт, int16, int32 и т. д. и т. д.).

бокс предотвратил это. и именно поэтому британцы празднуют День бокса.

лучший способ понять это-посмотреть на языки программирования более низкого уровня, на которых строится C#.

в языках самого низкого уровня, таких как C, все переменные идут в одно место: стек. Каждый раз, когда вы объявляете переменную, она попадает в стек. Они могут быть только примитивными значениями, такими как bool, byte, 32-разрядный int, 32-разрядный uint и т. д. Стек является одновременно простым и быстрым. По мере добавления переменных они просто идут один поверх другого, поэтому первый, который вы объявляете, сидит, скажем, 0x00, следующий-0x01, следующий на 0x02 в оперативной памяти и т. д. Кроме того, переменные часто предварительно адресуются во время компиляции, поэтому их адрес известен еще до запуска программы.

на следующем уровне вверх, как C++, вводится вторая структура памяти, называемая кучей. Вы все еще в основном живете в стеке, но специальные ints называются указатели может быть добавлен в стек, который хранит адрес памяти для первого байта объекта,и этот объект живет в куче. Куча - это своего рода беспорядок и несколько дорого поддерживать, потому что в отличие от переменных стека они не накапливаются линейно вверх, а затем вниз по мере выполнения программы. Они могут приходить и уходить без определенной последовательности, они могут расти и уменьшаться.

работа с указателями сложно. Они являются причиной утечек памяти, переполнения буфера, и разочарования. C# на помощь.

на более высоком уровне, C#, вам не нужно думать об указателях-.Net framework (написанный на C++) думает об этом для вас и представляет их вам как ссылки на объекты, а для производительности позволяет хранить более простые значения, такие как bools, bytes и ints, как типы значений. Под капотом объекты и вещи, которые создают экземпляр класса, отправляются в дорогую управляемую памятью кучу, в то время как типы значений входят в тот же стек, который у вас был в низкоуровневом C-super-fast.

ради сохранения взаимодействия между этими двумя принципиально разными концепциями памяти (и стратегиями хранения) простыми от кодера перспектива, типы значений могут быть упакованы в любое время. бокс приводит к тому, что значение копируется из стека, помещается в объект и помещается в кучу - более дорогое, но, жидкое взаимодействие с эталонным миром. Как указывают другие ответы, это произойдет, когда вы, например, скажете:

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 есть некоторые базовые структуры, которые по-прежнему требуют передачи типов значений в качестве объекта (и поэтому требуют бокса), но по большей части вам никогда не нужно будет боксировать.

неисчерпывающий список исторических структур C#, которые требуйте бокса, что вы должны избегать:

  • система событий оказывается, есть состояние гонки в наивном использовании его, и он не поддерживает асинхронность. Добавьте в проблему бокса, и ее, вероятно, следует избегать. (Вы можете заменить его, например, асинхронной системой событий, которая использует универсальные шаблоны.)

  • старые модели резьбы и таймера заставили коробку на их параметрах, но были заменены async / await, которые находятся далеко чище и эффективнее.

  • коллекции .Net 1.1 полностью полагались на бокс, потому что они появились до дженериков. Они все еще пинаются в системе.Коллекции. В любом новом коде вы должны использовать коллекции из системы.Коллекции.Общий, который в дополнение к избежанию бокса также предоставить вам более сильный тип безопасности.

вы должны избегать объявления или передачи типов значений в качестве объектов, если только вы должны иметь дело с вышеупомянутыми историческими проблемами, которые заставляют бокс, и вы хотите избежать хита производительности бокса позже, когда вы знаете, что он все равно будет боксировать.

по предложению Микаэля ниже:

Этого

using System.Collections.Generic;

var employeeCount = 5;
var list = new List<int>(10);

Не

using System.Collections;

Int32 employeeCount = 5;
var list = new ArrayList(10);

обновление

этот ответ первоначально предложил Int32, Bool и т. д. вызвать бокс, когда на самом деле они являются простыми псевдонимами для типов значений. То есть .Net имеет такие типы, как Bool, Int32, String и C# псевдонимы их в bool, int, string, без каких-либо функциональных различий.

бокс на самом деле не то, что вы используете - это то, что использует среда выполнения, так что вы можете обрабатывать ссылки и типы значений таким же образом, когда это необходимо. Например, если вы использовали ArrayList для хранения списка целых чисел, целые числа были помещены в коробку, чтобы поместиться в слоты типа объекта в ArrayList.

используя общие коллекции сейчас, это в значительной степени уходит. Если вы создадите List<int>, там нет бокса сделано-то List<int> может содержать целые числа напрямую.

бокс и распаковка специально используются для обработки объектов типа значения как ссылочного типа; перемещение их фактического значения в управляемую кучу и доступ к их значению по ссылке.

без бокса и распаковки вы никогда не сможете передавать типы значений по ссылке; и это означает, что вы не можете передавать типы значений как экземпляры объекта.

последнее место, где я должен был распаковать что-то было при написании кода, который извлек некоторые данные из базы данных (я не использовал 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());
        }
    }

, который работает просто отлично, я не использовал бокс/распаковка. (Если только компилятор не делает это за кулисами?)

в .net каждый экземпляр объекта или любой производный от него тип включает в себя структуру данных, содержащую информацию о его типе. "Реальные" типы значений в .net не содержат такой информации. Чтобы разрешить обработку данных в типах значений с помощью подпрограмм, ожидающих получения типов, производных от объекта, система автоматически определяет для каждого типа значения соответствующий тип класса с теми же членами и полями. Бокс создает новые экземпляры этого типа класса, копируя поля экземпляра типа значения. Распаковка копирует поля из экземпляра типа класса в экземпляр типа значения. Все типы классов, которые создаются из типов значений, являются производными от иронически названного класса ValueType (который, несмотря на свое имя, на самом деле является ссылочным типом).

когда метод принимает только ссылочный тип в качестве параметра (скажем, общий метод, ограниченный классом через new ограничение), вы не сможете передать ему ссылочный тип и должны его упаковать.

Это также верно для любых методов, которые принимают object в качестве параметра - это есть быть ссылочным типом.

В общем, вы обычно хотите, чтобы избежать бокс для типов значений.

однако, есть редкие случаи, когда это полезно. Например, если вам нужно настроить таргетинг на платформу 1.1, у вас не будет доступа к общим коллекциям. Любое использование коллекций в .NET 1.1 потребует обработки вашего типа значения как системы.Объект, который вызывает бокс/распаковка.

есть еще случаи, когда это будет полезно в .NET 2.0+. В любое время вы хотите взять преимущество в том, что все типы, включая типы значений, могут рассматриваться как объект напрямую, вы можете использовать бокс/распаковка. Это может быть удобно время от времени, так как это позволяет сохранить любой тип в коллекции (используя object вместо T в общей коллекции), но в целом, лучше избегать этого, так как вы теряете безопасность типов. Однако один случай, когда бокс часто происходит, - это когда вы используете отражение - многие из вызовов в отражении потребуют бокса/распаковки при работе с типами значений, так как тип не известен заранее.