Зачем
О вариантности в C# написано множество хороших статей. Но читая о её проявлениях в разных аспектах языка я столкнулся с тем, что каждый раз её представляют мне несколько иначе, чем в прошлый раз. Поэтому мне не удавалось сформулировать чёткое определение — шаблон, который хорошо соответствовал бы каждому проявлению вариантности, с которым я сталкиваюсь, и позволил бы мне держать в голове лишь одну концепцию, вместо набора различных ситуаций. Эта заметка — моя попытка сформулировать нужный шаблон.
Определение
Вариантность — это свойство преобразования типов операторами.
Оператор — любая сущность языка C#, преобразующая типы данных в производные от них. Например, оператором явялется T[] создающий производный тип массива из типа T, Action<T> создающий производный тип действия с одним параметром из T. Оператор группы методов T MyMethod(U), преобразует сразу два типа: T, создавая производный тип возвращаемое значение и U, создавая производный тип аргумент метода. Те же преобразования осуществляет и оператор создания типа делегата Func<U,T>. Но в последнем случае эти преобразования отличаются от первого своими своиствами — вариантностью.
Чтобы описать все формы, которые может принимать свойство вариантности необходимо определить, как типы могут соотноситься с друг другом. Здесь и далее под типом я понимаю любой ссылочный тип или производный от него. Если экземпляр типа T можно заменить экземпляром типа U, то мы будем считать, что U < T. Если экземпляр U можно заменить экземпляром T, то U > T. Если обе замены возможны, то U = T. Если ни одна не возможна, то типы T и U не сравнимы.
Преобразование, осуществляемое оператором, является:
- Ковариантным, если сохраняет отношение между парой типов после их преобразования в производные
- Контравариантным, если обращает отношение «меньше» на «больше» и сохраняет «равны» и «не сравнимы»
- Бивариантным, если отношения «меньше» и «больше» обращает в «равны» и сохраняет «равны» и «не сравнимы»
- Инвариантным, если сохраняет «равно», а любое другое отношение обращает в «не сравнимы»
Примеры, кратко
Несколько примеров применения такого определения:
- Оператор
T[]ковариантно преобразуетT, поэтому можно заменить экземплярObject[]на экземплярString[] - Оператор
Action<T>контравариантно преобразуетT, поэтому можно заменитьAction<String>наAction<Object> - Оператор
Func<T,U>ковариантно преобразует возвращяемый типUи контравариантно преобразует тип передаваемого аргументаT. Поэтому можно заменитьFunc<String,Object>наFunc<Object, String> - Оператор
U MyMethod(T t)инвариантно преобразует как возвращаемый типT, так и тип передаваемого аргументаU. Поэтому нельзя перегрузить методObject MyMethod(String)методомString MyMethod(Object)
Конец
Надеюсь, что сформулированное определение сможет помочь в изучении вопроса вариантности, на ряду с ресурсами, которые я использовал для создания этой заметки:
- Eric Lippert's Blog, Covariance and Contravariance in C#, Part One
- Eric Lippert's Blog, Covariance and Contravariance in C#, Part Two: Array Covariance
- Eric Lippert's Blog, Covariance and Contravariance in C#, Part Three: Method Group Conversion Variance
- Eric Lippert's Blog, Covariance and Contravariance in C#, Part Four: Real Delegate Variance
- Джон Скит, C# Для Профессионалов
- Wikipedia, Covariance and contravariance (computer science)
- MSDN, Covariance and Contravariance
