Контравариантность и ковариантность 🧠



В одном из профильных чатов видел сообщение о том, что упомянутая в заголовке тема даётся чуть ли не сложнее всего, что входит в основы 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.