Определение вариантности в C#

в 23:10, , рубрики: Песочница, метки: , ,

Зачем

О вариантности в 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)

Конец

Надеюсь, что сформулированное определение сможет помочь в изучении вопроса вариантности, на ряду с ресурсами, которые я использовал для создания этой заметки:

Поделиться

* - обязательные к заполнению поля