Это вопрос с точки зрения внутренних возможностей компилятора.
Меня интересуют обобщения, а не шаблоны (C ++), поэтому я пометил вопрос с помощью C #. Не Java, потому что AFAIK дженерики в обоих языках отличаются в реализации.
Когда я смотрю на языки без обобщений, это довольно просто, вы можете проверить определение класса, добавить его в иерархию и все.
Но что делать с универсальным классом и, что более важно, как обрабатывать ссылки на него? Как убедиться, что статические поля являются единичными для каждого экземпляра (т.е. каждый раз, когда общие параметры разрешаются).
Допустим, я вижу звонок:
var x = new Foo<Bar>();
Добавить новый Foo_Bar
класс в иерархию?
Обновление: пока я нашел только 2 соответствующих сообщения, однако даже они не вдавались в подробности в смысле «как сделать это самостоятельно»:
Ответы:
У каждого общего экземпляра есть своя собственная копия (с непонятным названием) MethodTable, в которой хранятся статические поля.
Я не уверен, что полезно рассматривать иерархию классов как некую структуру, которая действительно существует во время выполнения, это скорее логическая конструкция.
Но если вы рассматриваете MethodTables, каждый из которых имеет косвенный указатель на свой базовый класс, для формирования этой иерархии, то да, это добавляет новый класс в иерархию.
источник
Foo<string>
и они не будут создавать два экземпляра статического поляFoo
.Я вижу два реальных конкретных вопроса. Возможно, вы захотите задать дополнительные связанные вопросы (как отдельный вопрос со ссылкой на этот вопрос), чтобы получить полное понимание.
Как статическим полям назначаются отдельные экземпляры для общего экземпляра?
Что ж, для статических членов, которые не связаны с параметрами универсального типа, это довольно легко (используйте словарь, сопоставленный с универсальными параметрами и значением).
Члены (статические или нет), которые связаны с параметрами типа, могут обрабатываться с помощью стирания типа. Просто используйте самое сильное ограничение (часто
System.Object
). Поскольку информация о типе стирается после проверок типа компилятора, это означает, что проверки типов во время выполнения не понадобятся (хотя преобразования интерфейса все еще могут существовать во время выполнения).Каждый общий экземпляр появляется отдельно в иерархии типов?
Не в обобщениях .NET. Было принято решение исключить наследование из параметров типа, поэтому оказывается, что все экземпляры универсального объекта занимают одно и то же место в иерархии типов.
Вероятно, это было хорошее решение, потому что невозможность поиска имен из базового класса была бы невероятно удивительной.
источник
Foo<int>
иFoo<string>
будут попадать в те же данныеFoo
без ограничений.List<string>
иList<Form>
, то, посколькуList<T>
внутренне имеет член типаT[]
и нет никаких ограниченийT
, то на самом деле вы получите машинный код, который манипулируетobject[]
. Однако, посколькуT
в массив помещаются только экземпляры, все, что выходит, может быть возвращено какT
без дополнительной проверки типа. С другой стороны, если бы вы имелиControlCollection<T> where T : Control
, то внутренний массивT[]
стал быControl[]
.Общий способ во внешней части компилятора состоит в том, чтобы иметь два вида экземпляров типа: универсальный тип (
List<T>
) и связанный универсальный тип (List<Foo>
). Универсальный тип определяет, какие функции существуют, какие поля, и имеет ссылки на универсальный тип, где бы он ниT
использовался. Связанный универсальный тип содержит ссылку на универсальный тип и набор аргументов типа. Это имеет достаточно информации для вас, чтобы затем сгенерировать конкретный тип, заменив ссылки на универсальный типFoo
или каковы бы ни были аргументы типа. Такое различие важно, когда вы делаете вывод типа и хотите сделать выводList<T>
противList<Foo>
.Вместо того, чтобы думать об обобщениях, таких как шаблоны (которые непосредственно создают различные реализации), может быть полезно думать о них как о конструкторах типов функционального языка (где обобщенные аргументы подобны аргументам в функции, которая дает вам тип).
Что касается серверной части, я действительно не знаю. Вся моя работа с дженериками была ориентирована на CIL в качестве бэкэнда, поэтому я мог скомпилировать их в поддерживаемые дженерики.
источник
List<T>
содержит действительный тип (его определение), в то время какList<Foo>
(спасибо за часть терминологии) с моим подходом содержат объявленияList<T>
(конечно, теперь связанные сFoo
вместоT
).