Как ValueType.GetType() может определить тип структуры?

Для ссылочного типа макет памяти объекта

| Type Object pointer|
|    Sync Block      |
|  Instance fields...|

Для типа значения макет объекта выглядит

|  Instance fields...|

Для ссылочного типа GetType означает поиск объекта из указателя "Тип объекта". Все объекты данного объекта ссылочного типа указывают на объект того же типа (который также имеет таблицу методов).

Для типа значения этот указатель недоступен. Как работает GetType()?

Я проверил с Google, и я нашел этот фрагмент. Это немного туманно. Может кто-нибудь уточнить?

Решение состоит в том, что местоположение в который сохраняет значение, может хранить только значения определенного типа. Это гарантированный верификатором. Источник

Ответ 1

Вызов GetType() в поле типа значения, тип значения. Перемещая тип значения в кучу, теперь у вас есть ссылочный тип, который теперь имеет указатель на тип этого объекта.

Если вы хотите избежать бокса, вы можете вызвать GetTypeCode, который возвращает перечисление, которое указывает тип типа значения без его бокса.

Вот пример, показывающий бокс, который имеет место:

С#

class Program
{
    static void Main()
    {
        34.GetType();
    }
}

IL для Main():

.method private hidebysig static void Main() cil managed
{
        .entrypoint
        .maxstack 8
        L_0000: ldc.i4.s 0x22
        L_0002: box int32
        L_0007: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
        L_000c: pop 
        L_000d: ret 
}

Изменить: Чтобы показать, что делает компилятор, измените тип литерала следующим образом:

class Program
{
    static void Main()
    {
        34L.GetType();
    }
}

Добавив "L" после литерала, я сообщаю компилятору, что я хочу, чтобы этот литерал был преобразован в System.Int64. Компилятор видит это, и когда он испускает команду box, она выглядит так:

.method private hidebysig static void Main() cil managed
{
        .entrypoint
        .maxstack 8
        L_0000: ldc.i4.s 0x22
        L_0002: conv.i8 
        L_0003: box int64
        L_0008: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
        L_000d: pop 
        L_000e: ret 
}

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

Ответ 2

Может быть, Эндрю Х. воспринял это как очевидное и постарался заставить меня понять +1. мой светло-ламповый момент произошел от Джона Скита... снова (на этот раз через его книгу, которую мне довелось читать... и вокруг точной области, где лежали ответы.

  • С# - статически типизированный. Каждая переменная имеет тип и известна во время компиляции.
  • Типы значений не могут быть унаследованы. В результате объектам VT не нужно переносить дополнительную информацию о типе (в отличие от объектов типа Ref, каждая из которых имеет заголовок типа объекта, так как тип переменной и тип значения/объекта может.)

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

BaseRefType r = new DerivedRefType(); 
ValueType v = new ValueType();

Мой недостающий кусок был пулей №1.
<Snipped after J.Skeet comment since it seems to be wrong>. Кажется, что есть какая-то магия, позволяющая компилятору/времени выполнения знать "тип переменной" при любой произвольной переменной. Таким образом, среда выполнения каким-то образом знает, что ob имеет тип MyStruct, хотя сам объект VT не имеет информации о типе.

MyStruct ob = new MyStruct();
ob.WhoAmI();                          // no box ; defined in MyStruct
Console.WriteLine(ob.GetHashCode());  // no box ; overridden in ValueType
Console.WriteLine( ob.GetType() );    // box ; implemented in Object

В связи с этим я могу вызвать методы, определенные в MyStruct (и ValueType по какой-либо причине) без бокса в RefType.