Как ValueTypes происходит от Object (ReferenceType) и по-прежнему остается ValueTypes?

83

C # не позволяет структурам быть производными от классов, но все ValueTypes являются производными от Object. Где проводится это различие?

Как с этим справляется среда CLR?

Джоан Венге
источник
Результат черной магии System.ValueTypeтипа в системе типов CLR.
RBT

Ответы:

108

C # не позволяет структурам быть производными от классов

Ваше утверждение неверно, отсюда и ваше замешательство. C # действительно позволяет структурам быть производными от классов. Все структуры являются производными от одного и того же класса System.ValueType, который является производным от System.Object. И все перечисления являются производными от System.Enum.

ОБНОВЛЕНИЕ: в некоторых (теперь удаленных) комментариях была некоторая путаница, которая требует разъяснения. Я задам еще несколько вопросов:

Являются ли структуры производными от базового типа?

Ясно да. Мы можем убедиться в этом, прочитав первую страницу спецификации:

Все типы C #, включая примитивные типы, такие как int и double, наследуются от одного корневого типа объекта.

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

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

Конечно. Тип структуры может переопределить ToString. Что это замещающее, если не виртуальный метод своего базового типа? Следовательно, он должен иметь базовый тип. Этот базовый тип - это класс.

Могу ли я получить определяемую пользователем структуру из выбранного мной класса?

Точно нет. Это не означает, что структуры не являются производными от класса . Структуры являются производными от класса и, таким образом, наследуют наследуемые члены этого класса. Фактически, структуры должны быть производными от определенного класса: перечисления должны быть производными Enum, а структуры - производными ValueType. Поскольку они необходимы , язык C # запрещает вам указывать производное отношение в коде.

Зачем это запрещать?

Когда отношения требуются , язык дизайнер имеет варианты: (1) требовать от пользователя ввести требуемое колдовство, (2) сделать его необязательным, или (3) запретить его. У каждого есть свои плюсы и минусы, и разработчики языка C # сделали выбор по-разному в зависимости от конкретных деталей каждого из них.

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

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

Точно так же все типы делегатов являются производными MulticastDelegate, но C # требует, чтобы вы этого не говорили.

Итак, теперь мы установили, что все структуры в C # являются производными от класса .

Какая связь между наследованием и производным от класса ?

Многих смущают отношения наследования в C #. Отношения наследования довольно просты: если структура, класс или тип делегата D является производным от типа класса B, то наследуемые члены B также являются членами D. Это так просто.

Что означает наследование, когда мы говорим, что структура является производной от ValueType? Просто все наследуемые члены ValueType также являются членами структуры. Вот как структуры получают свою реализацию ToString, например; он наследуется от базового класса структуры.

Все наследуемые члены? Конечно нет. Передаются ли частные члены по наследству?

Да. Все частные члены базового класса также являются членами производного типа. Конечно, незаконно называть этих участников по имени, если сайт вызова не находится в домене доступности участника. То, что у вас есть участник, не означает, что вы можете им пользоваться!

Теперь продолжим исходный ответ:


Как с этим справляется среда CLR?

Очень хорошо. :-)

Что делает тип значения типом значения, так это то, что его экземпляры копируются по значению . Что делает ссылочный тип ссылочным типом, так это то, что его экземпляры копируются по ссылке . Кажется, у вас есть некоторое убеждение, что отношения наследования между типами значений и ссылочными типами являются чем-то особенным и необычным, но я не понимаю, что это за вера. Наследование не имеет ничего общего с тем, как что-то копируется.

Рассмотрим этот вариант. Предположим, я рассказал вам следующие факты:

  • Есть два вида ящиков: красные и синие.

  • Каждый красный квадрат пуст.

  • Есть три специальных синих прямоугольника, которые называются O, V и E.

  • О нет ни в одной коробке.

  • V находится внутри O.

  • E находится внутри V.

  • Никакого другого синего ящика внутри V.

  • Внутри E. нет синей коробки.

  • Каждый красный квадрат находится либо в V, либо в E.

  • Каждый синий квадрат, кроме O, находится внутри синего квадрата.

Синие прямоугольники - ссылочные типы, красные прямоугольники - типы значений, O - System.Object, V - System.ValueType, E - System.Enum, а «внутреннее» отношение - «производное от».

Это совершенно последовательный и простой набор правил, который вы могли бы легко реализовать самостоятельно, если бы у вас было много картона и много терпения. Красный или синий ящик не имеет ничего общего с тем, что он внутри; в реальном мире вполне возможно поместить красную коробку в синюю. В среде CLR совершенно законно создать тип значения, наследующий от ссылочного типа, если это либо System.ValueType, либо System.Enum.

Итак, давайте перефразируем ваш вопрос:

Как ValueTypes происходит от Object (ReferenceType) и по-прежнему остается ValueTypes?

в виде

Как это возможно, что каждое красное поле (типы значений) находится внутри (происходит от) поля O (System.Object), которое является синим прямоугольником (ссылочный тип) и по-прежнему является красным прямоугольником (тип значения)?

Когда вы это формулируете так, надеюсь, это очевидно. Ничто не мешает вам поместить красную коробку внутри коробки V, которая находится внутри синей коробки O. Почему это могло быть?


ДОПОЛНИТЕЛЬНОЕ ОБНОВЛЕНИЕ:

Первоначальный вопрос Джоан был о том, как это возможночто тип значения является производным от ссылочного типа. Мой первоначальный ответ на самом деле не объяснял ни один из механизмов, которые CLR использует для учета того факта, что у нас есть производное отношение между двумя вещами, которые имеют совершенно разные представления, а именно, имеют ли упомянутые данные заголовок объекта, a блок синхронизации, владеет ли он собственным хранилищем для сбора мусора и т. д. Эти механизмы сложны, слишком сложны, чтобы объяснить их одним ответом. Правила системы типов CLR несколько сложнее, чем несколько упрощенный вариант, который мы видим в C #, где, например, нет четкого различия между упакованными и неупакованными версиями типа. Введение универсальных шаблонов также привело к значительному усложнению среды CLR.

Эрик Липперт
источник
8
Языковые конструкции должны иметь смысл. Что означало бы иметь произвольный тип значения, производный от произвольного ссылочного типа? Есть ли что-нибудь, что вы могли бы сделать с помощью такой схемы, чего нельзя было бы достичь с помощью пользовательских неявных преобразований?
Эрик Липперт
2
Думаю, нет. Я просто подумал, что у вас могут быть некоторые члены, доступные для многих типов значений, которые вы видите как группа, что вы могли бы сделать, используя абстрактный класс для создания структуры. Я думаю, вы могли бы использовать неявные преобразования, но тогда вы бы заплатили штраф за производительность, верно? Если вы делаете миллионы из них.
Joan Venge
8
Ах я вижу. Вы хотите использовать наследование не как механизм моделирования "своего рода" отношений, а просто как механизм для совместного использования кода между множеством связанных типов. Это кажется разумным сценарием, хотя лично я стараюсь избегать использования наследования исключительно для удобства совместного использования кода.
Эрик Липперт
3
Джоан, чтобы определить поведение один раз, вы можете создать интерфейс, иметь структуры, которые вы хотите поделиться поведением, реализовать интерфейс, а затем создать метод расширения, работающий с интерфейсом. Одна потенциальная проблема с этим подходом заключается в том, что при вызове методов интерфейса структура будет упакована первой, а скопированное упакованное значение будет передано методу расширения. Любое изменение состояния будет происходить с копией объекта, что может быть не интуитивно понятно пользователям API.
Дэвид Сильва Смит
2
@Sipo: Теперь, честно говоря, вопрос включает в себя "как CLR справляется с этим?" и ответ хорошо описывает, как CLR реализует эти правила. Но вот в чем дело: мы должны ожидать, что система, реализующая язык, не имеет тех же правил, что и язык! Системы реализации обязательно бывают нижнего уровня, но давайте не будем путать правила этой системы нижнего уровня с правилами системы верхнего уровня, построенной на ней. Конечно, в системе типов CLR проводится различие между упакованными и неупакованными типами значений, как я отмечал в своем ответе. Но C # этого не делает .
Эрик Липперт
21

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

Я думаю, что лучший способ ответить на этот вопрос - это ValueTypeособенное. По сути, это базовый класс для всех типов значений в системе типов CLR. Трудно понять, как ответить «как CLR это обрабатывает», потому что это просто правило CLR.

ДжаредПар
источник
+1 за положительный момент о структурах, не производных от чего-либо [кроме неявно производных от System.ValueType].
Рид Копси
2
Вы говорите, что ValueTypeэто особенный ValueTypeтип , но стоит прямо упомянуть, что он на самом деле является ссылочным типом.
LukeH
Если внутренне структуры могут быть производными от класса, почему они не открывают его для всех?
Joan Venge
1
@Joan: На самом деле, нет. Это просто для того, чтобы вы могли преобразовать структуру в объект, а там для полезности. Но технически, по сравнению с реализацией классов, типы значений обрабатываются CLR совершенно иначе.
Рид Копси
2
@JoanVenge Я считаю, что путаница здесь заключается в том, что структуры являются производными от класса ValueType в среде CLR. Я считаю, что правильнее сказать, что в CLR структур на самом деле не существует, реализация «struct» в CLR на самом деле является классом ValueType. Так что это не похоже на то, что структура наследуется от ValueType в CLR.
wired_in 07
20

Это несколько искусственная конструкция, поддерживаемая CLR, чтобы все типы можно было рассматривать как System.Object.

Типы значений являются производными от System.Object через System.ValueType , где происходит специальная обработка (например, CLR обрабатывает упаковку / распаковку и т. Д. Для любого типа, производного от ValueType).

Рид Копси
источник
6

Ваше утверждение неверно, отсюда и ваше замешательство. C # действительно позволяет структурам быть производными от классов. Все структуры являются производными от одного и того же класса System.ValueType.

Итак, давайте попробуем это:

 struct MyStruct :  System.ValueType
 {
 }

Это даже не будет компилироваться. Компилятор напомнит вам: «Введите System.ValueType в списке интерфейсов, это не интерфейс».

При декомпиляции Int32, который является структурой, вы обнаружите:

public struct Int32: IComparable, IFormattable, IConvertible {}, не говоря уже о том, что она является производной от System.ValueType. Но в обозревателе объектов вы обнаруживаете, что Int32 наследуется от System.ValueType.

Итак, все это заставляет меня поверить:

Я думаю, что лучший способ ответить на этот вопрос - это особенность ValueType. По сути, это базовый класс для всех типов значений в системе типов CLR. Трудно понять, как ответить «как CLR справляется с этим», потому что это просто правило CLR.

yi.han
источник
Те же структуры данных используются в .NET для описания содержимого типов значений и ссылочных типов, но когда CLR видит определение типа, которое определяется как производное от ValueType, оно использует это для определения двух типов объектов: типа объекта кучи, который ведет себя как ссылочный тип и как тип места хранения, который фактически находится вне системы наследования типов. Поскольку эти два типа вещей используются во взаимоисключающих контекстах, одни и те же дескрипторы типов могут использоваться для обоих. На уровне CLR структура определяется как класс, родительский System.ValueType
элемент
... запрещает указывать, что структуры наследуются от чего-либо, потому что есть только одна вещь, которую они могут наследовать от ( System.ValueType), и запрещает классам указывать, что они наследуют, System.ValueTypeпотому что любой класс, который был объявлен таким образом, будет вести себя как тип значения.
supercat
3

Тип значения в штучной упаковке фактически является ссылочным типом (он ходит как один и крякает как один, так что фактически это один). Я бы предположил, что ValueType на самом деле не является базовым типом типов значений, а скорее является базовым ссылочным типом, в который типы значений могут быть преобразованы при приведении к типу Object. Сами типы значений без упаковки находятся за пределами иерархии объектов.

суперкар
источник
1
Я думаю, вы имеете в виду: «ValueType на самом деле не является базовым типом типов значений »
wired_in
1
@wired_in: Спасибо. Исправлено.
supercat 09
2

Обоснование

Из всех ответов ответ @supercat ближе всего к фактическому ответу. Поскольку другие ответы на самом деле не отвечают на вопрос и прямо делают неверные утверждения (например, что типы значений наследуются от чего-либо), я решил ответить на вопрос.

 

Пролог

Этот ответ основан на моем собственном реверс-инжиниринге и спецификации CLI.

structи classявляются ключевыми словами C #. Что касается CLI, все типы (классы, интерфейсы, структуры и т. Д.) Определяются определениями классов.

Например, тип объекта (известный в C # как class) определяется следующим образом:

.class MyClass
{
}

 

Интерфейс определяется определением класса с interfaceсемантическим атрибутом:

.class interface MyInterface
{
}

 

А как насчет типов значений?

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

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

Если представить себе следующую структуру C #:

namespace MyNamespace
{
    struct MyValueType : ICloneable
    {
        public int A;
        public int B;
        public int C;

        public object Clone()
        {
            // body omitted
        }
    }
}

Ниже приводится определение этой структуры в классе IL:

.class MyNamespace.MyValueType extends [mscorlib]System.ValueType implements [mscorlib]System.ICloneable
{
    .field public int32 A;
    .field public int32 B;
    .field public int32 C;

    .method public final hidebysig newslot virtual instance object Clone() cil managed
    {
        // body omitted
    }
}

Так что здесь происходит? Он явно расширяется System.ValueType, что является типом объекта / ссылки, и реализует System.ICloneable.

Объяснение заключается в том, что когда определение класса расширяется, System.ValueTypeоно фактически определяет 2 вещи: тип значения и соответствующий тип значения в штучной упаковке. Члены определения класса определяют представление как для типа значения, так и для соответствующего упакованного типа. Расширяется и реализуется не тип значения, а соответствующий тип в штучной упаковке. В extendsи implementsключевых словах относятся только к коробочному типу.

Чтобы уточнить, определение класса выше выполняет 2 вещи:

  1. Определяет тип значения с 3 полями (и одним методом). Он не наследуется ни от чего и не реализует никаких интерфейсов (типы значений не могут делать ни то, ни другое).
  2. Определяет тип объекта (упакованный в коробку) с 3 полями (и реализует один метод интерфейса), наследующий от интерфейса System.ValueTypeи реализующий его System.ICloneable.

Также обратите внимание, что любое расширение определения класса System.ValueTypeтакже внутренне запечатано, независимо от того, указано sealedключевое слово или нет.

Поскольку типы значений представляют собой простые структуры, не наследуются, не реализуются и не поддерживают полиморфизм, они не могут использоваться с остальной системой типов. Чтобы обойти это, поверх типа значения CLR также определяет соответствующий ссылочный тип с теми же полями, известный как упакованный тип. Таким образом, хотя тип значения не может быть передан методам, принимающим object, его соответствующий тип в штучной упаковке может .

 

Теперь, если бы вы определили метод на C #, например

public static void BlaBla(MyNamespace.MyValueType x),

вы знаете, что метод примет тип значения MyNamespace.MyValueType.

Выше мы узнали, что определение класса, structявляющееся результатом ключевого слова в C #, на самом деле определяет как тип значения, так и тип объекта. Однако мы можем ссылаться только на определенный тип значения. Несмотря на то, что спецификация CLI заявляет, что ключевое слово ограничения boxedможет использоваться для ссылки на упакованную версию типа, этого ключевого слова не существует (см. ECMA-335, II.13.1 Ссылки на типы значений). Но давайте на мгновение представим, что это так.

При обращении к типам в IL поддерживается несколько ограничений, среди которых classи valuetype. Если мы используем, valuetype MyNamespace.MyTypeмы указываем определение класса типа значения с именем MyNamespace.MyType. Точно так же мы можем использовать class MyNamespace.MyTypeопределение класса объекта типа MyNamespace.MyType. Это означает, что в IL вы можете иметь тип значения (структуру) и тип объекта (класс) с одним и тем же именем и при этом различать их. Теперь, если бы boxedключевое слово, указанное в спецификации CLI, было действительно реализовано, мы могли бы использовать boxed MyNamespace.MyTypeдля указания упакованного типа определения класса типа значения под названием MyNamespace.MyType.

Итак, .method static void Print(valuetype MyNamespace.MyType test) cil managedпринимает тип значения, определенный определением класса типа значения с именем MyNamespace.MyType,

while .method static void Print(class MyNamespace.MyType test) cil managedпринимает тип объекта, определенный определением класса типа объекта с именем MyNamespace.MyType.

аналогично, если бы boxedбыло ключевое слово, .method static void Print(boxed MyNamespace.MyType test) cil managedпримет упакованный тип типа значения, определенного определением класса с именем MyNamespace.MyType.

Вы бы тогда быть в состоянии создать экземпляр упакованного типа , как и любой другой тип объекта и передавать его любой способ , который принимает System.ValueType, objectили в boxed MyNamespace.MyValueTypeкачестве аргумента, и это было бы, для всех намерений и целей, работа , как и любого другого ссылочного типа. Это НЕ тип значения, а соответствующий тип значения в рамке.

 

Резюме

Итак, подведем итог и ответим на вопрос:

Типы значений не являются ссылочными типами и не наследуются от System.ValueTypeлюбого другого типа, и они не могут реализовывать интерфейсы. Соответствующие упакованные типы, которые также определены , наследуются от интерфейсов System.ValueTypeи могут реализовывать их.

.classОпределение определяет различные действия в зависимости от обстоятельств.

  • Если interfaceсемантический атрибут указан, определение класса определяет интерфейс.
  • Если interfaceсемантический атрибут не указан и определение не расширяется System.ValueType, определение класса определяет тип объекта (класс).
  • Если interfaceсемантический атрибут не указан, а определение действительно распространяется System.ValueType, определение класса определяет тип значения и его соответствующий коробочный тип (структура).

Схема памяти

В этом разделе предполагается 32-разрядный процесс

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

public struct MyStruct
{
    public int A;
    public short B;
    public int C;
}

Если мы представим, что экземпляр MyStruct был размещен по адресу 0x1000, то это макет памяти:

0x1000: int A;
0x1004: short B;
0x1006: 2 byte padding
0x1008: int C;

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

 

Если мы определим класс точно так же, как:

public class MyClass
{
    public int A;
    public short B;
    public int C;
}

Представляя тот же адрес, структура памяти выглядит следующим образом:

0x1000: Pointer to object header
0x1004: int A;
0x1008: int C;
0x100C: short B;
0x100E: 2 byte padding
0x1010: 4 bytes extra

По умолчанию для классов используется автоматическая компоновка, и JIT-компилятор упорядочит их в наиболее оптимальном порядке. Поля выравниваются по границам своего размера. Для этого добавлены отступы. Не знаю почему, но у каждого класса всегда есть дополнительные 4 байта в конце.

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

Таким образом, типы значений не поддерживают наследование, интерфейсы и полиморфизм.

Методы

Типы значений не имеют таблиц виртуальных методов и, следовательно, не поддерживают полиморфизм. Однако их соответствующий тип в штучной упаковке делает .

Когда у вас есть экземпляр структуры и вы пытаетесь вызвать виртуальный метод, подобный ToString()определенному System.Object, среда выполнения должна поместить эту структуру в коробку.

MyStruct myStruct = new MyStruct();
Console.WriteLine(myStruct.ToString()); // ToString() call causes boxing of MyStruct.

Однако, если структура переопределяет, ToString()вызов будет статически привязан, и среда выполнения будет вызывать MyStruct.ToString()без упаковки и без просмотра каких-либо таблиц виртуальных методов (у структур их нет). По этой причине он также может встроить ToString()вызов.

Если структура переопределяет ToString()и помещена в рамку, то вызов будет разрешен с использованием таблицы виртуальных методов.

System.ValueType myStruct = new MyStruct(); // Creates a new instance of the boxed type of MyStruct.
Console.WriteLine(myStruct.ToString()); // ToString() is now called through the virtual method table.

Однако помните, что ToString()это определено в структуре и, следовательно, работает со значением структуры, поэтому ожидает тип значения. Упакованный тип, как и любой другой класс, имеет заголовок объекта. Если ToString()метод, определенный в структуре, был вызван непосредственно с упакованным типом в thisуказателе, при попытке доступа к полю Aв MyStructон получил бы доступ к смещению 0, которое в упакованном типе было бы указателем заголовка объекта. Таким образом, у упакованного типа есть скрытый метод, который фактически переопределяет ToString(). Этот скрытый метод распаковывает (только вычисление адреса, как и unboxинструкция IL) упакованный тип, а затем статически вызывает ToString()определенный в структуре.

Точно так же упакованный тип имеет скрытый метод для каждого реализованного метода интерфейса, который выполняет ту же распаковку, а затем статически вызывает метод, определенный в структуре.

 

Спецификация CLI

Заниматься боксом

I.8.2.4 Для каждого типа значения CTS определяет соответствующий ссылочный тип, называемый упакованным типом. Обратное неверно: как правило, ссылочные типы не имеют соответствующего типа значения. Представление значения в штучной упаковке (значение в штучной упаковке) - это место, где может быть сохранено значение типа значения. Упакованный тип - это тип объекта, а упакованное значение - это объект.

Определение типов значений

I.8.9.7 Не все типы, определенные определением класса, являются объектными (см. §I.8.2.3); в частности, типы значений не являются объектными типами, но они определяются с использованием определения класса. Определение класса для типа значения определяет как (распакованный) тип значения, так и связанный с ним упакованный тип (см. §I.8.2.4). Члены определения класса определяют представление обоих.

II.10.1.3 Семантические атрибуты типа определяют, должен ли быть определен тип интерфейса, класса или значения. Атрибут interface определяет интерфейс. Если этот атрибут отсутствует и определение расширяет (прямо или косвенно) System.ValueType, а определение не для System.Enum, должен быть определен тип значения (§II.13). В противном случае должен быть определен класс (§II.11).

Типы значений не наследуются

I.8.9.10 В распакованной форме типы значений не наследуются ни от одного типа. Типы значений в штучной упаковке должны наследовать непосредственно от System.ValueType, если они не являются перечислениями; в этом случае они должны наследоваться от System.Enum. Типы значений в штучной упаковке должны быть запечатаны.

II.13 Распакованные типы значений не считаются подтипами другого типа, и недопустимо использовать инструкцию isinst (см. Раздел III) для распакованных типов значений. Однако инструкция isinst может использоваться для упакованных типов значений.

I.8.9.10 Тип значения не наследуется; скорее базовый тип, указанный в определении класса, определяет базовый тип упакованного типа.

Типы значений не реализуют интерфейсы

I.8.9.7 Типы значений не поддерживают контракты интерфейса, но связанные с ними упакованные типы поддерживают.

II.13 Типы значений должны реализовывать ноль или более интерфейсов, но это имеет значение только в их коробочной форме (§II.13.3).

I.8.2.4 Интерфейсы и наследование определены только для ссылочных типов. Таким образом, хотя определение типа значения (§I.8.9.7) может определять оба интерфейса, которые должны быть реализованы типом значения и классом (System.ValueType или System.Enum), от которого оно наследуется, они применяются только к упакованным значениям. .

Несуществующее ключевое слово в рамке

II.13.1 На распакованную форму типа значения следует ссылаться с помощью ключевого слова valuetype, за которым следует ссылка на тип. Для ссылки на упакованную форму типа значения следует использовать ключевое слово в рамке, за которым следует ссылка на тип.

Примечание: спецификация здесь неверна, boxedключевого слова нет .

Эпилог

Я думаю, что отчасти путаница в том, как типы значений наследуются, проистекает из того факта, что C # использует синтаксис приведения для выполнения упаковки и распаковки, из-за чего кажется, что вы выполняете приведение, что на самом деле не CLR выдаст исключение InvalidCastException при попытке распаковать неправильный тип). (object)myStructв C # создает новый экземпляр упакованного типа типа значения; он не выполняет никаких приведений. Точно так же (MyStruct)objв C # распаковывает упакованный тип, копируя часть значения; он не выполняет никаких приведений.

MulleDK19
источник
1
Наконец, ответ, который четко описывает, как это работает! Этот заслуживает принятого ответа. Молодец!
Just Shadow