2009-12-30 3 views
16

В C#, любой определенный пользователем 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, не может компилятор вверх бросьте объект типа вместо бокса?

ответ

35

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

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

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

+4

+1 к вам, потому что вы объясняете это намного лучше меня .. –

+1

Спасибо Мехридаду! Это очистило мое сомнение! :) –

+5

Хороший ответ. Пара второстепенных проблем с ним. Во-первых, стек против кучи не имеет отношения к боксу; типы значений не обязательно должны находиться в стеке, и они помещаются в коробку, даже если они находятся в куче. Во-вторых, виртуальные методы не имеют значения; бокс никогда не нужен для отправки виртуального метода в структуру! Поскольку все структуры запечатаны, джиттер имеет достаточную информацию, чтобы точно определить, какой метод вызывается в jit time. –

0

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

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

3

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

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

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

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

+1

Строго говоря, не все происходит от объекта. На стороне типа типы указателей не являются ни конвертируемыми, ни производными от объекта. Типы типов параметров и типы интерфейсов не выводятся из объекта, а всегда конвертируются в объект. Это * значения * типов без указателей, которые всегда выводятся из объекта. За исключением нулевого значения ссылочных типов, которое происходит из ничего, не являясь объектом. Ссылка, которая ссылается на ничего, не относится к объекту; такая ссылка конвертируется в объект, но не выводится из объекта. –

+0

@ Эрик Липперт: Изменен ответ, чтобы отразить ваши проблемы. – casperOne

+0

В соответствии со спецификацией C# все типы значений производятся из ссылочных типов ('System.ValueType' и' System.Enum'), но не являются ссылочными типами. Это глупость и заставляет меня подозревать, что спецификация C# неточна. @EricLippert: Мне было бы интересно ваше мнение о [моем ответе] (http://stackoverflow.com/a/15031902/240733). – stakx

15

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

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

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

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

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

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

+0

Эрик, как всегда, очень приятное объяснение! Хотя вы могли бы немного рассказать о том, что вы имеете в виду, когда говорите, что «бокс необходим, если вы хотите иметь унифицированную систему типов». Я не понимаю, как «бокс» объединяет систему типов. Благодаря! – SolutionYogi

+0

Думая об этом немного больше, вы предполагаете, что C# предпочитает систему, в которой разработчик может «обрабатывать» типы значений, подобные ссылочному типу, без необходимости понимать, как они реально реализованы .NET CLR? И для достижения этого «бокс» становится необходимым злом? Как бы это выглядело, если вы решили избежать «бокса», как бы разработчик взаимодействовал со типами значений/ссылочными типами? – SolutionYogi

+1

Позвольте мне перефразировать. Три желаемые вещи: (1) типы значений содержат только свои данные и, следовательно, имеют другое представление, чем ссылочные типы, (2) все значения могут быть преобразованы в общий унифицированный тип, объект и (3) типы значений никогда не должны быть " в штучной упаковке». Эти три желательные вещи взаимно невозможны; вы можете иметь не более двух из них. Мы решили иметь (1) и (2); не имея (3) - это цена, которую вы платите. –

0

После того, как вопрос был дан ответ я представлю немного «трюк», связанные с этой темой:

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

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

Нет, просто потому, что в соответствии с определением языка С #, «вверх-отливки» в данном случае является бокс.

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

  1. Есть неявное преобразование типов от типа S к его супер-типу T, но они определены только для шаблона «от типа класса S к опорному типу T». Поскольку ваш struct A не является типом класса, эти преобразования не могут применяться в вашем примере.

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

  2. только существующие преобразования, который соответствует шаблону «от типа значения A к его опорной супер-типа object» классифицируется как преобразование бокса. Таким образом, каждое преобразование из struct в object составляет по определению рассматривается как бокс.

Смежные вопросы