Вариантность дженериков
Вопрос
Объясните ковариантность и контравариантность в C# на примерах.
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
Ковариантность (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 ошибок.