Поделиться через


Универсальные шаблоны в среде выполнения (руководство по программированию на 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# создается с помощью параметра типа значения или ссылочного типа, отражение может запрашивать его во время выполнения, и его фактический тип и его параметр типа можно определить.

См. также