Зачем
О вариантности в 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