Ответ вслух Senior Дженерики обновлено

Вариантность дженериков

Вопрос

Объясните ковариантность и контравариантность в C# на примерах.

Попробуйте сами

Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.

Показать ответ и разбор

Коротко

Вариантность отвечает на вопрос, можно ли подставить обобщённый тип с другим аргументом. Ковариантность, ключевое слово out, разрешает «расширять» тип на выходе: IEnumerable<string можно использовать как IEnumerable<object, потому что из него только читают, и любая строка является объектом.

Что должно прозвучать в ответе

  • Ковариантность (out): производный тип вместо базового на выходе, IEnumerable<string> как IEnumerable<object>

  • Контравариантность (in): базовый тип вместо производного на входе, Action<object> как Action<string>

  • Только интерфейсы и делегаты, только ссылочные типы

  • Почему List<T> и IList<T> инвариантны: запись нарушила бы типобезопасность

  • Ковариантность массивов и ArrayTypeMismatchException

Что могут спросить следом

  • Почему IEnumerable<int> нельзя присвоить IEnumerable<object>?
  • Где в BCL встречается контравариантность?

Разбор

Вариантность отвечает на вопрос, можно ли подставить обобщённый тип с другим аргументом. Ковариантность, ключевое слово out, разрешает «расширять» тип на выходе: IEnumerable<string> можно использовать как IEnumerable<object>, потому что из него только читают, и любая строка является объектом.

Контравариантность, in, работает в обратную сторону для входа: Action<object> можно передать туда, где ждут Action<string>, потому что метод, умеющий обработать любой объект, справится и со строкой. В BCL это IComparer<in T>, IEqualityComparer<in T>, Action<in T>.

IList<T> инвариантен, потому что он и читает, и пишет: если бы список строк можно было считать списком объектов, через него записали бы число. Массивы в C# ковариантны исторически, и такая запись проверяется в рантайме с ArrayTypeMismatchException. Вариантность поддерживается только для интерфейсов и делегатов и только для ссылочных типов: int в object требует упаковки, а не просто смены представления ссылки.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.