Speak.Me Учить иностранные слова

C#: обобщения (Generics)

В C# существует два механизма повторного использования кода в разных типах: наследование и обобщения (generics). Наследование делает возможным повторное использование благодаря применению базового класса, а обобщения — благодаря использованию шаблонов, содержащих плейсхолдеры (placeholder) типов.

Обобщенные типы (Generic Types)

Обобщенный тип объявляет параметры типа (type parameters) — плейсхолдеры типов, которые будут заполнены в дальнейшем при использовании обобщенного типа, путем передачи в него аргументов типа (type arguments).

В примере объявляется обобщенный тип Stack<T>, который будет хранить экземпляры типа TStack<T> объявляет единственный параметр типа — T. Использовать Stack<T> можно так:

Stack<int> заполняет параметр типа T аргументом типа int, автоматически создавая тип на лету (во время выполнения). Примечательно, что в последних двух строках не требуется приведение к типу производного класса (downcast). Это позволяет избежать ошибок при исполнении и уменьшить затраты ресурсов на приведение к объектному типу (boxing) и восстановление значения (unboxing). Все это делает использование обобщенного типа более удобным в использовании.

Фактически Stack<int> имеет следующее определение:

Технически, Stack<T> является открытым типом (open type), а Stack<int>закрытым типом (closed type). При выполнении все экземпляры обобщенных типов закрываются — плейсхолдеры типов заполняются конкретными типами.

Обобщенные методы (Generic Methods)

Параметры типа могут задаваться не только для типа в целом, но и для отдельного метода. Обобщенный метод объявляет параметры типа в сигнатуре метода.

Данный метод меняет значения двух переменных местами. Применить его можно так:

Обычно нет необходимости передавать в обобщенный метод аргументы типа — компилятор сам определит тип. В случае неоднозначности, обобщенный метод может быть вызван с аргументами типа:

В пределах обобщенного типа методы по умолчанию не являются обобщенными. Чтобы они стали таковыми им нужно явно задать параметр типа (в треугольных скобках).

Методы и типы — единственные конструкции, которые могут быть обобщенными, т.е. вводить параметры типа. Свойства, индексаторы, события, поля, конструкторы, операторы и т.д. не могут объявлять параметры типа, однако они могут использовать любые параметры типа уже объявленные в их родительском типе. Например, обобщенный тип Stack<T> может быть дополнен индексатором:

Объявление параметров типа (Declaring Type Parameters)

Параметры типа могут вводиться при объявлении классов, структур, интерфейсов, делегатов и методов. Обобщенный тип или метод может иметь несколько параметров типа:

Обобщенные типы и методы могут быть перегружены, для этого им нужно задавать разное количество параметров типа:

По соглашению, у обобщенных методов и типов с единственным параметром типа этот параметр называется просто T, а если параметров много, каждый из них должен иметь более содержательное название с префиксом T.

Оператор typeof и свободные обобщенные типы

Открытые обобщенные типы не существуют во время выполнения: они закрываются при компиляции. Однако свободный (unbound) обобщенный тип может существовать во время выполнения, но исключительно как объект Type. Чтобы объявить свободный обобщенный тип нужно использовать оператор typeof:

Оператор typeof можно использовать и для объявления закрытого типа:

А также открытого:

Обобщенное значение по умолчанию (The default Generic Value)

С помощью ключевого слова default можно получить значение по умолчанию для параметра типа. Значением по умолчанию для ссылочных типов будет null, а для значимых типов — результат побитового обнуления полей типа.

Ограничения обобщений (Generic Constraints)

По умолчанию, параметр типа может быть замещен абсолютно любым типом. Но к параметру типа можно применить ограничения (constraints), которые будут требовать более конкретных аргументов типов. Существует 6 видов ограничений:

  • where T : base-class — ограничение базовым классом: параметр типа может быть либо указанным классом, либо его производным классом (допустимо если экземпляр параметра типа может быть автоматически приведен к базовому классу)
  • where T : interface — ограничение интерфейсом: параметр типа может быть только реализацией указанного интерфейса (либо может быть автоматически приведен к этому интерфейсу)
  • where T : class — ограничение ссылочным типом: параметр типа может быть ссылочным типом
  • where T : struct — ограничение значимым типом: параметр типа может быть не нулевым значимым типом
  • where T : new() — ограничение конструктором без параметров: параметр типа должен иметь публичный (public) конструктор без параметров и позволять вызвать new()
  • where U : T — ограничение конкретным типом: параметр типа должен совпадать с типом другого параметра, или быть его производным (третий параметр в примере ниже)

Ограничения могут применяться как к обобщенным методам, так и к обобщенным типам.

Наследование обобщенных типов (Subclassing Generic Types)

Обобщенный класс может наследоваться от другого обобщенного класса. При этом производный класс может оставлять параметр типа базового класса открытым, или закрывать его конкретным типом, а также добавлять свои параметры типа:

Ссылка на самого себя

В качестве аргумента типа при закрытии обобщенного типа может использоваться сам обобщенный тип:

Статические поля

Статические поля всегда уникальны для конкретного закрытого типа:

Ковариантность (Covariance)

Ковариантностью называется сохранение иерархии наследования типов, передаваемых в качестве аргументов типа, в обобщенных типах в том же порядке. Так, если класс Cat наследует от класса Animal, то естественно полагать, что перечисление (обощенный тип) IEnumerable<Cat> будет потомком перечисления IEnumerable<Animal>. Действительно, «список из пяти кошек» — это частный случай «списка из пяти животных». В таком случае говорят, что тип (в данном случае обобщённый интерфейс) IEnumerable<T> ковариантен своему параметру типа T.

В C# начиная с 4 версии ковариантность возможна для обобщенных интерфейсов, принимающих параметры типа с модификатором out. Например, предположим, что класс Stack<T>, описанный в начале данного раздела, реализует такой интерфейс:

Модификатор out помечает интерфейс как ковариант, поэтому можно сделать, например, следующее:

Обобщенные интерфейсы IEnumerator<T> и IEnumerable<T> являются ковариантами, поэтому можно привести IEnumerable<string> к типу IEnumerable<object>, например.

Ковариантность возможна только для приведения ссылочных типов, но не применима для приведения к объектному типу значимых типов. К примеру, если метод принимает параметр типа IPoppable<object>, мы можем передать ему при вызове IPoppable<string>, но не можем передать IPoppable<int>.

Ковариантный обобщенный метод  (и делегат) не может принимать в качестве параметра экземпляр своего ковариантного типа — это приведет к ошибке компиляции:

Контравариантность (Contravariance)

Контравариантностью называется обращение иерархии наследования типов, передаваемых в качестве аргументов типа, на противоположную в обобщенных типах. Так, если класс String наследует от класса Object, а делегат Action<T> определён как метод, принимающий объект типа T, то Action<Object> наследует от делегата Action<String>, а не наоборот. Действительно, если «все строки — объекты», то «всякий метод, оперирующий произвольными объектами, может выполнить операцию над строкой», но не наоборот. В таком случае говорят, что тип (в данном случае обобщённый делегат) Action<T> контравариантен своему параметру типу T.

Контравариантность в С# возможна только для интерфейсов и делегатов, в которые параметр типа передается с модификатором in, т.е. на входящей позиции. Возвращаясь к классу Stack<T>, предположим, что теперь он реализует такой интерфейс:

Тогда мы можем сделать следующее:

В противоположность ковариантности, компилятор выдаст ошибку если попытаться использовать контравариантный параметр типа на позиции out, например, в качестве возвращаемого значения.

Отсутствие наследования между производными типами называется инвариантностью.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *