Универсальные шаблоны в среде выполнения (руководство по программированию на C#)
Когда универсальный тип или метод компилируется на общий промежуточный язык (CIL), он содержит метаданные, которые идентифицируют его как параметры типа. Использование CIL для универсального типа зависит от того, является ли указанный параметр типом значения или ссылочным типом.
Когда универсальный тип сначала создается с типом значения в качестве параметра, среда выполнения создает специализированный универсальный тип с заданным параметром или параметрами, замененными в соответствующих расположениях в CIL. Специальные универсальные типы создаются один раз для каждого уникального типа значения, используемого в качестве параметра.
Например, предположим, что код программы объявил стек, созданный на основе целых чисел:
Stack<int>? stack;
В этой точке среда выполнения создает специальную версию класса Stack<T>, параметр которого будет соответствующим образом заменяться целым числом. После этого каждый раз, когда в коде программы используется стек целых чисел, среда выполнения обращается к созданному специальному классу Stack<T>. В следующем примере создаются два экземпляра стека целых чисел, которые совместно используют один экземпляр кода Stack<int>
:
Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();
Допустим, в другой точке кода создается другой класс Stack<T>, в качестве параметра которого используется другой тип значения, например long
, или определяемая пользователем структура. В результате среда выполнения создает другую версию универсального типа и заменяет ее long
в соответствующих расположениях в CIL. Преобразования больше не требуются, поскольку каждый специальный универсальный класс содержит собственный тип значения.
Для ссылочных типов универсальные шаблоны применяются несколько иным образом. При первом создании универсального типа с любым ссылочным типом среда выполнения создает специализированный универсальный тип со ссылками на объекты, заменяемые параметрами в CIL. После этого каждый раз при создании экземпляра такого сконструированного типа с использованием в качестве параметра ссылочного типа (независимо от того, какой это тип) среда выполнения использует ранее созданную специальную версию универсального типа. Это возможно, поскольку все ссылки имеют одинаковый размер.
Допустим, у вас есть два ссылочных типа (классы Customer
и Order
) и вы создаете стек типов Customer
:
class Customer { }
class Order { }
Stack<Customer> customers;
В этой точке среда выполнения создает специальную версию класса Stack<T>, содержащую ссылки на объекты, которые не хранят данные и будут заполнены позднее. Допустим, в следующей строке кода создается стек другого ссылочного типа с именем Order
:
Stack<Order> orders = new Stack<Order>();
В отличие от типов значений, в этом случае не создается другая специальная версия класса Stack<T> для типа Order
. Вместо этого создается экземпляр специальной версии класса Stack<T> и задается переменная orders
, ссылающаяся на него. Предположим, далее встречается строка кода, в которой создается стек типа Customer
:
customers = new Stack<Customer>();
Как и в случае предшествующего использования класса Stack<T>, созданного с помощью типа Order
, создается другой специальный экземпляр класса Stack<T>. Содержащиеся в нем указатели будут ссылаться на область памяти с размером типа Customer
. Поскольку число ссылок в разных программах может значительно различаться, реализация C# на основе универсальных шаблонов позволяет значительно сократить объем кода за счет того, что компилятор создает для универсальных классов ссылочных типов только один специальный класс.
Кроме того, при создании экземпляра универсального класса C# с использованием параметра типа значения или ссылочного типа копия может выполнять запросы к нему во время выполнения, и при этом могут быть установлены его фактический тип и параметр типа.