Контравариантность и ковариантность 🧠
В одном из профильных чатов видел сообщение о том, что упомянутая в заголовке тема даётся чуть ли не сложнее всего, что входит в основы C#.
Иногда, кажется проще запомнить наглядно, на примерах.
Также, стоит упомянуть, что всё нижеописанное актуально именно в дженериках.
Контравариантность ⬆️
Возможность подставить более базовый тип на место формального типового параметра, которая используется для объявления аргументов функций:
Пример из стандартной библиотеки -
Ковариантность ⬇️
Возможность подставить более производный тип на место формального типового параметра, которая используется для объявления возврата значений из функций:
Пример из стандартной библиотеки -
Дополнительно 🤓
Во-первых, первое и второе можно совмещать в рамках одного типа:
Во-вторых, это очень мощный инструмент на стыке параметрического полиморфизма и полиморфизма подтипов, который позволяет строить мощные расширяемые и универсальные системы типов.
В-третьих, самый смак этой фичи в том, что кастинг, который был показан выше, в силу своей неявности по перфомансу равен отсутствию кастинга, поскольку в результирующем IL отсутствуют инструкции
В одном из профильных чатов видел сообщение о том, что упомянутая в заголовке тема даётся чуть ли не сложнее всего, что входит в основы C#.
Иногда, кажется проще запомнить наглядно, на примерах.
Также, стоит упомянуть, что всё нижеописанное актуально именно в дженериках.
Контравариантность ⬆️
Возможность подставить более базовый тип на место формального типового параметра, которая используется для объявления аргументов функций:
interface IFooIn<in TInputType>
{
void Bar(TInputType input);
}
Пример из стандартной библиотеки -
IComparable<>
, где благодаря контравариантности можно сделать так:IComparable<IEnumerable<char>> charEnumerableComparable = // ...;
// ...
IComparable<string> stringComparable = charEnumerableComparable;
Ковариантность ⬇️
Возможность подставить более производный тип на место формального типового параметра, которая используется для объявления возврата значений из функций:
interface IFooOut<out TOutputType>
{
TOutputType Baz();
}
Пример из стандартной библиотеки -
IEnumerable<>
, где благодаря ковариантности можно сделать так:IEnumerable<Task<object>> tasksWithResults = // ...;
// ...
IEnumerable<Task> tasks = tasksWithResults;
Дополнительно 🤓
Во-первых, первое и второе можно совмещать в рамках одного типа:
interface IFooInOut<in TInputType, out TOutputType>
{
TOutputType Foo(TInputType input);
}
Во-вторых, это очень мощный инструмент на стыке параметрического полиморфизма и полиморфизма подтипов, который позволяет строить мощные расширяемые и универсальные системы типов.
В-третьих, самый смак этой фичи в том, что кастинг, который был показан выше, в силу своей неявности по перфомансу равен отсутствию кастинга, поскольку в результирующем IL отсутствуют инструкции
castclass
.