Почему структуры должны быть упакованы?

В C#, любой пользовательский struct автоматически подкласс Системы. Структура System.ValueType и Система. Структура System.ValueType подкласс System.Object.

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

struct A
{
    public int i;
}

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

Таким образом, мой вопрос: если A потомок System.Object, не может компилятор, восходящий это к типу объекта вместо упаковки?

17
задан stakx supports GoFundMonica 22 February 2013 в 17:52
поделиться

5 ответов

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

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

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

.
37
ответ дан 30 November 2019 в 10:21
поделиться

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

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

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

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

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

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

.
17
ответ дан 30 November 2019 в 10:21
поделиться

Хотя разработчикам .NET определенно не нужно было включать раздел 4.3 C# Language Specification, IMO:

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

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

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

.
4
ответ дан 30 November 2019 в 10:21
поделиться

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

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

.
0
ответ дан 30 November 2019 в 10:21
поделиться

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

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

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);
0
ответ дан 30 November 2019 в 10:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: