Почему нужно создавать коробки?

В С# любой пользовательский struct автоматически является подклассом System.Struct System.ValueType и System.Struct System.ValueType является подклассом System.Object.

Но когда мы назначаем некоторую ссылку на объект-тип, он получает коробку. Например:

struct A
{
    public int i;
}

A a;
object obj = a;  // boxing takes place here

Итак, мой вопрос: если A является потомком System.Object, не может ли компилятор применить его к типу объекта вместо бокса?

Ответ 1

Структура - это тип значения. System.Object является ссылочным типом. Типы значений и ссылочные типы хранятся и обрабатываются по-разному во время выполнения. Для типа значения, который следует рассматривать как ссылочный тип, необходимо, чтобы он был помещен в бокс. С точки зрения низкого уровня, это включает в себя копирование значения из стека, где он изначально живет, в недавно выделенную память в куче, которая также содержит заголовок объекта. Дополнительные типы заголовков необходимы для ссылочных типов для разрешения их vtables, чтобы разрешить отправку виртуальных методов и других связанных с ним типов ссылочного типа (помните, что структура в стеке является просто значением и имеет нулевую информацию о типе, она не содержит ничего подобного vtables и может 't непосредственно используется для разрешения динамически отправленных методов). Кроме того, чтобы рассматривать что-то как ссылочный тип, вы должны иметь ссылку (указатель) на него, а не его исходное значение.

Итак, мой вопрос: если A является потомком System.Object, не может компилятор поднять его до типа объекта вместо бокса?

На более низком уровне значение ничего не наследует. На самом деле, как я уже говорил, это не предмет. Тот факт, что A вытекает из System.ValueType, который, в свою очередь, вытекает из System.Object, является чем-то определенным на уровне абстракции вашего языка программирования (С#), а С# действительно скрывает операцию бокса от вас довольно хорошо. Вы не указываете ничего явно, чтобы помещать значение, чтобы вы могли просто подумать, что компилятор "ускорил" структуру для вас. Это делает иллюзию наследования и полиморфизма для значений, а ни один из инструментов, необходимых для полиморфного поведения, напрямую не предоставляется ими.

Ответ 2

Вот как я предпочитаю думать об этом. Рассмотрим реализацию переменной, содержащей 32-битное целое число. При обращении как тип значения все значение вписывается в 32 бита хранилища. Это то, что тип значения: в хранилище содержатся только биты, составляющие значение, не более, не что иное.

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

Ясно, что эти две вещи совершенно разные.

Теперь предположим, что у вас есть переменная типа объекта, и вы хотите скопировать содержимое переменной типа int в нее. Как ты делаешь это? 32 бита, которые составляют целое число, не являются одной из этих "ссылочных" вещей, это всего лишь ведро, содержащее 32 бита. Ссылки могут быть 64-битными указателями в управляемой куче или 32-битными дескрипторами в структуру данных сборщика мусора или любой другой реализацией, о которой вы можете думать, но 32-битное целое число может быть только 32-битным целым числом.

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

Бокс необходим только в том случае, если вы хотите (1) иметь систему унифицированного типа и (2) обеспечить, чтобы 32-битное целое потребляло 32 бит памяти. Если вы готовы отказаться от любого из них, вам не нужен бокс; мы не желаем их отвергать, и поэтому бокс - это то, с чем мы вынуждены жить.

Ответ 3

В то время как разработчикам .NET определенно не нужно было включать в себя раздел 4.3 бокса С# Language Specification, объясняет намерение позади этого довольно ну, ИМО:

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

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

Это отличается от, скажем, С++, где система типов не унифицирована, для всех типов не существует общего базового типа.

Ответ 4

struct - это тип значения по дизайну, поэтому он должен быть помещен в коробку, когда он превращается в ссылочный тип. struct происходит от System.ValueType, который в терминах происходит от System.Object.

Сам факт, что struct является потомком объекта, не имеет большого значения... поскольку CLR имеет дело с structs по-разному во время выполнения, чем ссылочный тип.

Ответ 5

После ответа на вопрос я представлю небольшой "трюк", связанный с этой темой:

struct может реализовывать интерфейсы. Если вы передадите тип значения функции, которая ожидает, что интерфейс, который этот тип значения реализует, обычно получает коробку. Используя дженерики, вы можете избежать бокса:

interface IFoo {...}
struct Bar : IFoo {...}

void boxing(IFoo x) { ... }
void byValue<T>(T x) : where T : IFoo { ... }

var bar = new Bar();
boxing(bar);
byValue(bar);

Ответ 6

"Если struct A является потомком System.Object, не может компилятор поднять его вместо бокса?"

Нет, просто потому, что в соответствии с определением языка С# "подкастинг" в этом случае бокс.

Спецификация языка для С# содержит (в главе 13) каталог всех возможных преобразований типов. Все эти преобразования классифицируются определенным образом (например, числовые преобразования, ссылки и т.д.).

  • Существуют неявные преобразования типов из типа S в его супертип T, но они определены только для шаблона "от типа класса S до ссылочного типа T", Поскольку ваш struct A не является типом класса, эти преобразования не могут применяться в вашем примере.

    То есть тот факт, что A (косвенно), полученный из object (хотя и правильный), здесь просто неактуальен. Важно то, что A - тип значения структуры.

  • Единственное существующее преобразование, которое соответствует шаблону "от типа значения A до его ссылочного супертипа object", классифицируется как преобразование бокса. Таким образом, каждое преобразование от struct до object считается по определению рассмотренным боксом.