Ответ 1
Простите мой многословный пост, но эта тема довольно широкая. Я попытаюсь описать, что испускает компилятор С# и как это интерпретируется компилятором JIT во время выполнения.
ECMA-335 (это действительно хорошо написанный проектный документ, проверьте его), где он за то, что знает, как все, и я имею в виду все, представлено в сборке .NET. В сборке есть несколько связанных таблиц метаданных CLI для общей информации:
- GenericParam - хранит информацию об общем параметре (индекс, флаги, имя, тип/метод).
- GenericParamConstraint - хранит информацию об общем ограничении параметров (владеющем общим параметром, типом ограничения).
- MethodSpec - хранит экземпляры генерируемых стандартных сигнатур (например, Bar.Method <int> для Bar.Method <T> ).
- TypeSpec - хранит экземпляры типичного типа (например, Bar <int> для Bar <T> ).
Поэтому, имея в виду это, пропустите простой пример с помощью этого класса:
class Foo<T>
{
public T SomeProperty { get; set; }
}
Когда компилятор С# компилирует этот пример, он определит Foo в таблице метаданных TypeDef, как и для любого другого типа. В отличие от не общего типа, он также будет иметь запись в таблице GenericParam, которая будет описывать свой общий параметр (index = 0, flags =?, Name = (индекс в кучу String, "T" ), owner = type "Foo" ).
Одним из столбцов данных в таблице TypeDef является начальный индекс в таблице MethodDef, который является непрерывным списком методов, определенных для этого типа. Для Foo мы определили три метода: getter и setter для SomeProperty и конструктор по умолчанию, предоставленный компилятором. В результате таблица MethodDef будет содержать строку для каждого из этих методов. Одним из важных столбцов в таблице MethodDef является столбец "Подпись". В этом столбце хранится ссылка на байк байтов, который описывает точную подпись метода. ECMA-335 подробно рассказывает об этих блоках подписи метаданных, поэтому я не буду срывать эту информацию здесь.
Ключ-подпись функции содержит информацию о параметрах, а также возвращаемое значение. В нашем примере сеттер принимает T, а getter возвращает T. Ну, что же такое T? В блоке подписи это будет специальное значение, которое означает "параметр типового типа с индексом 0". Это означает, что строка в таблице GenericParams имеет индекс = 0 с owner = type "Foo", который является нашим "T".
То же самое касается поля хранилища авто-свойств. Запись Foo в таблице TypeDef будет содержать начальный индекс в таблице Field, а в таблице Field будет столбец "Подпись". Подпись поля будет означать, что тип поля - это "параметр типового типа с индексом 0".
Это хорошо и хорошо, но где генерируется генерация кода, когда T - разные типы? На самом деле ответственность компилятора JIT заключается в генерации кода для генерических экземпляров, а не в компиляторе С#.
Посмотрим на пример:
Foo<int> f1 = new Foo<int>();
f1.SomeProperty = 10;
Foo<string> f2 = new Foo<string>();
f2.SomeProperty = "hello";
Это скомпилирует что-то вроде этого CIL:
newobj <MemberRefToken1> // new Foo<int>()
stloc.0 // Store in local "f1"
ldloc.0 // Load local "f1"
ldc.i4.s 10 // Load a constant 32-bit integer with value 10
callvirt <MemberRefToken2> // Call f1.set_SomeProperty(10)
newobj <MemberRefToken3> // new Foo<string>()
stloc.1 // Store in local "f2"
ldloc.1 // Load local "f2"
ldstr <StringToken> // Load "hello" (which is in the user string heap)
callvirt <MemberRefToken4> // Call f2.set_SomeProperty("hello")
Итак, что это за бизнес MemberRefToken? MemberRefToken - это токен метаданных (токены - это четыре байтовых значения с самым значимым байтом, являющимся идентификатором таблицы метаданных, а остальные три байта - номером строки, 1), который ссылается на строку в таблице метаданных MemberRef. В этой таблице содержится ссылка на метод или поле. Перед generics это таблица, в которой будет храниться информация о методах/полях, которые вы используете, из типов, определенных в ссылочных сборках. Тем не менее, он также может использоваться для ссылки на элемент в общем экземпляре. Поэтому скажем, что MemberRefToken1 относится к первой строке в таблице MemberRef. Он может содержать эти данные: class= TypeSpecToken1, name = ".ctor", blob = < ссылка на ожидаемый заголовок подписи .ctor > .
TypeSpecToken1 будет ссылаться на первую строку в таблице TypeSpec. Сверху мы знаем, что в этой таблице хранятся экземпляры родовых типов. В этом случае эта строка будет содержать ссылку на подпись blob для "Foo <int> ". Таким образом, этот MemberRefToken1 действительно говорит, что мы ссылаемся на "Foo <int> .ctor()".
MemberRefToken1 и MemberRefToken2 будет использовать одно и то же значение класса, то есть TypeSpecToken1, Однако они будут отличаться от имени и подписи blob ( MethodRefToken2 для "set_SomeProperty" ). Аналогично, MemberRefToken3 и MemberRefToken4 будет делиться TypeSpecToken2, создание "Foo <string> ", но отличается от имени и blob в том же путь.
Когда компилятор JIT компилирует вышеупомянутый CIL, он замечает, что он видит общий экземпляр, который он не видел раньше (т.е. Foo <int> или Foo <string> ). То, что происходит дальше, довольно хорошо освещено ответом Шива Кумара, поэтому я не буду повторять его подробно здесь. Проще говоря, когда компилятор JIT сталкивается с новым типизированным типичным типом, он может испускать совершенно новый тип в свою систему типов с полевой компоновкой, используя фактические типы в экземпляре вместо общих параметров. У них также были бы свои собственные таблицы методов, а компиляция JIT каждого метода включала бы замену ссылок на общие параметры на фактические типы из экземпляра. Это также ответственность компилятора JIT для обеспечения правильности и проверяемости CIL.
Итак, чтобы подвести итог: компилятор С# испускает метаданные, описывающие, что общего и как генерируются типичные типы/методы. Компилятор JIT использует эту информацию для извлечения новых типов (при условии, что он несовместим с существующим экземпляром) во время выполнения для экземпляров генерируемых типов, и каждый тип будет иметь свою собственную копию кода, который был JIT, скомпилированный на основе используемых фактических типов в экземпляре.
Надеюсь, это имело смысл некоторым образом.