Вам бонус- начислено 1 монета за дневную активность. Сейчас у вас 1 монета

Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

Лекция



Привет, Вы узнаете о том , что такое вариантность, Разберем основные их виды и особенности использования. Еще будет много подробных примеров и описаний. Для того чтобы лучше понимать что такое вариантность, ковариантность, контравариантность, инвариантность , настоятельно рекомендую прочитать все из категории Объектно-ориентированное программирование ООП.

вариантность разбирается безотносительно к какому-либо языку программирования. Примеры в разделе практики написаны на псевдоязыке и поэтому не обязаны компилироваться компилятором.

В документации, технической литературе и других источниках вы могли встречаться с различными названиями для явлений вариантности.

Термины ковариантность и контравариантность были введены Сильвестром в 1853 году для исследований по алгебраической теории инвариантов.

Термины ковариантность и ковариация эквивалентны (по крайней мере в программировании). Более того, термины контравариантность и контравариация также эквивалентны. Так, например, термины ковариантность и контравариантность используется в Википедии и у Троелсена (в переводе).

А термины ковариация и контравариация встречаются, например, на MSDN и у Скита (в переводе).

Вариантность — перенос наследования исходных типов на производные от них типы. Под производными типами понимаются контейнеры, делегаты, обобщения, а не типы, связанные отношениями "предок-потомок". Различными видами вариантности являются ковариантность, контравариантность и инвариантность .

Ковариантность (covariance)— перенос наследования исходных типов на производные от них типы в прямом порядке.
Контравариантность (contravariance)— перенос наследования исходных типов на производные от них типы в обратном порядке.
Инвариантность — ситуация, когда наследование исходных типов не переносится на производные.

Возможно более правильным определением вариантности является предложенное Эриком Липпертом.

Совместимость присваивания, assignment compatibility — это возможность присвоить значение более частного типа совместимой переменной более общего типа.
Вариантность — это сохранение совместимости присваивания исходных типов у производных типов.
Ковариантность — это сохранение совместимости присваивания исходных типов у производных в прямом порядке.
Контравариантность — это сохранение совместимости присваивания исходных типов у производных в обратном порядке.

Ковариа́нтность и контравариа́нтность — используемые в математике (линейной алгебре, дифференциальной геометрии, тензорном анализе) и в физике понятия, характеризующие то, как тензоры (скаляры, векторы, операторы, билинейные формы и т. д.) изменяются при преобразованиях базисов в соответствующих пространствах или многообразиях. Контравариантными называют «обычные» компоненты, которые при смене базиса пространства изменяются с помощью преобразования, обратного преобразованию базиса. Ковариантными — те, которые изменяются так же, как и базис.

Связь между ковариантными и контравариантными координатами тензора возможна только в пространствах, где задан метрический тензор (не следует путать с метрическим пространством).

Термины ковариантность и контравариантность были введены Сильвестром в 1853 году для исследований по алгебраической теории инвариантов.

Определения

Ковариантностью называется сохранение иерархии наследования исходных типов в производных типах в том же порядке. Так, если класс Cat наследуется от класса Animal, то естественно полагать, что перечисление IEnumerable<Cat> будет потомком перечисления IEnumerable<Animal>. Действительно, «список из пяти кошек» — это частный случай «списка из пяти животных». В таком случае говорят, что тип (в данном случае обобщенный интерфейс) IEnumerable<T> ковариантен своему параметру-типу T.

Контравариантностью называется обращение иерархии исходных типов на противоположную в производных типах. Так, если класс String наследуется от класса Object, а делегат Action<T> определен как метод, принимающий объект типа T, то Action<Object> наследуется от делегата Action<String>, а не наоборот. Действительно, если «все строки — объекты», то «всякий метод, оперирующий произвольными объектами, может выполнить операцию над строкой», но не наоборот. В таком случае говорят, что тип (в данном случае обобщенный делегат) Action<T> контравариантен своему параметру-типу T.

Отсутствие наследования между производными типами называется инвариантностью.

Контравариантность позволяет корректно устанавливать тип при создании подтипов (subtyping), то есть, установить множество функций, позволяющее заменить другое множество функций в любом контексте. В свою очередь, ковариантность характеризует специализацию кода, то есть замену старого кода новым в определенных случаях. Таким образом, ковариантность и контравариантность являются независимыми механизмами типобезопасности, не исключающими друг друга, и могут и должны применяться в объектно-ориентированных языках программирования .

Хорошей визуализацией понятий вариантности является следующий рисунок:

Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

Если у производных типов наблюдается ковариантность, говорят, что они ковариантны исходному типу. Если у производных типов наблюдается контравариантность, говорят, что они контравариантны исходному типу. Если у производных типов не наблюдается ни того, ни другого, говорят, что они инвариантны.

Рассмотрим конкретные примеры.

Какое назначение этого?

Вся суть вариантности состоит в использовании в производных типах преимуществ наследования. Известно, что если два типа связаны отношением "предок-потомок", то объект потомка может храниться в переменной типа предка. На практике это значит, что мы можем использовать для каких-либо операций объекты потомка вместо объектов предка. Тем самым, можно писать более гибкий и короткий код для выполнения действий поддерживаемых разными потомками с общим предком.

Исходная иерархия и производные типы

Для начала опишем иерархию типов, которой будем оперировать. Вверху иерархии у нас находится Device (устройство), потомками которого являются Mouse (мышь), Keyboard (клавиатура). У Mouse в свою очередь тоже есть потомки — WiredMouse (проводная мышь), WirelessMouse (беспроводная мышь).


Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

Все любят контейнеры. На их примере наиболее просто объяснить, что подразумевается под производными типами. Если говорить о списках как производных типах, то для типа Device производным будет
List (список устройств). Аналогично, для типа Keyboard производным будет List (список клавиатур). Думаю, если и были сомнения, то теперь их нет.

Классическая ковариантность

Ковариантность также легче изучать на примере контейнеров. Для этого выделим часть иерархии (ветвь) — Keyboard : Device (клавиатура является устройством, клавиатура частный случай устройства). Опять возьмем списки и построим ковариантную производную ветвь — List : List (список клавиатур является частным случаем списка устройств). Как видим, наследование передалось в прямом порядке.


Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

Рассмотрим пример кода. Есть функция, которая принимает список устройств List и совершает над ними какие-то манипуляции. Как вы уже догадались, в эту функцию можно передать список клавиатур List:

void DoSmthWithDevices(List devices) { /* действия с элементами списка */ }
...
List keyboards = new List { /* заполнение списка */ };
DoSmthWithDevices(keyboards);

Классическая контравариантность

Каноническим для изучения контравариантности является рассмотрение ее на основе делегатов. Допустим, у нас есть обобщенный делегат:

delegate void Action(T something);

Для исходного типа Device производным будет Action, а для Keyboard — Action. Полученные делегаты могут представлять функции, которые выполняют какие-то действия над устройством или мышью соответственно. Для ветви Keyboard : Device построим производную контравариантную ветвь — Action : Action (действие над устройством является частным случаем действия над клавиатурой — звучит странно, но так и есть). Если можно нажать клавишу на клавиатуре, то это не значит, что и на устройстве можно нажать ее (оно может не иметь понятия о том, что такое клавиша). Но если можно подключить устройство, то можно этим же способом (методом, функцией) подключить и клавиатуру. Как видим, наследование передалось в обратном порядке.


Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

Из выше сказанного логично, что если функция может выполнить, что-то над устройством, то она может выполнить это и над клавиатурой. Это значит, мы можем передать объект делегата Action в функцию, принимающую объект делегата Action. Рассмотрим в коде:

void DoSmthWithKeyboard(Action actionWithKeyboard) { /* выполнение actionWithKeyboard над клавиатурой */ }
...
Action actionWithDevice = device => device.PlugIn();
DoSmthWithKeyboard(actionWithDevice);

Немного инвариантности

Если производные типы инвариантны к исходным типам, то для ветви Keyboard : Device не образуется ни ковариантной (List : List), ни контравариантной (Action : Action) ветви. Это значит, что нет никакой связи между производными типами. Как видим, наследование не переносится.


Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

А что если?

Неочевидная ковариантность

Делегаты типа Action могут быть ковариантны. Это значит, что для ветви Keyboard : Device образуется ковариантная ветвь — Action : Action. Таким образом, в функцию, принимающую объект делегата Action, можно передавать объект делегата Action.

void DoSmthWithDevice(Action actionWithDevice) { /* выполнение actionWithDevice над устройством */ }
...
Action actionWithKeyboard = keyboard => ((Device)keyboard).PlugIn();
DoSmthWithDevice(actionWithKeyboard);

Неочевидная контравариантность

Контейнеры могут быть контравариантны. Это значит, что для ветви Keyboard : Device образуется контравариантная ветвь — List : List. Таким образом, в функцию, принимающую List, можно передавать List:

void FillListWithKeyboards(List keyboards) { /* заполнение списка клавиатур  */ }
...
List devices = new List();
FillListWithKeyboards(devices);

Рассмотренные выше экзотические виды вариантности имеют, разве что, академическую ценность. Сложно придумать реальную задачу, которая легче решается при наличии такого рода возможностей. Стоит запомнить, что ковариантность и контравариантность могут вызывать ошибки времени выполнения. Для их устранения требуется вводить определенные ограничения. Компиляторы, как правило, такие ограничения не вводят.

Безопасность для контейнеров

Если производный тип ковариантен, то для обеспечения безопасности контейнер должен быть read only. В противном случае, остается возможность записать в List объект неверного типа (Device, Mouse и другие) через приведение к List:

List devices = new List();
devices.Add(new Device()); // ошибка времени выполнения

Если производный тип контравариантен, то для обеспечения безопасности контейнер должен быть write only. В противном случае, остается возможность считывания из List объекта неверного типа (Keyboard, Mouse и других) через приведение к соответствующему списку (List, List и другим):

List keyboards = new List();
keyboards.Add(new Keyboard());
keyboards .PressSpace(); // ошибка времени выполнения

Двойные стандарты для делегатов

Разумным для делегатов является ковариантность для выходного значения и контравариантность для входных параметров (исключая передачу по ссылке). В случае соблюдения данных условий ошибок времени выполнения не возникает.

Дебрифинг

Представленных примеров достаточно для понимания принципов работы вариантности. Данные о ее поддержке разными типами вашего любимого языка ищите в соответствующей спецификации.

Использование

Массивы и другие контейнеры

В контейнерах, допускающих запись объектов, ковариантность считается нежелательной, поскольку она позволяет обходить контроль типов. В самом деле, рассмотрим ковариантные массивы. Пусть классы Cat и Dog наследуют от класса Animal (в частности, переменной типа Animal можно присвоить переменную типа Cat или Dog). Об этом говорит сайт https://intellect.icu . Создадим массив Cat[]. Благодаря контролю типов в этот массив можно записывать лишь объекты типа Cat и его потомков. Затем присвоим ссылку на этот массив переменной типа Animal[] (ковариантность массивов это позволяет). Теперь в этот массив, известный уже как Animal[], запишем переменную типа Dog. Таким образом, в массив Cat[] мы записали Dog, обойдя контроль типов. Поэтому контейнеры, разрешающие запись, желательно делать инвариантными. Также, контейнеры, разрешающие запись, могут реализовывать два независимых интерфейса, ковариантный Producer<T> и контравариантный Consumer<T>, в этом случае вышеописанный обход контроля типов сделать не удастся.

Поскольку контроль типов может нарушаться лишь при записи элемента в контейнер, то для неизменяемых коллекций и итераторов ковариантность безопасна и даже полезна. Например, с ее помощью в языке C# любому методу, принимающему аргумент типа IEnumerable<Object>, можно передавать любую коллекцию любого типа, например IEnumerable<String> или даже List<String>.

Если же в данном контексте контейнер используется, наоборот, только для записи в него, а чтение отсутствует, то он может быть контравариантным. Так, если есть гипотетический тип WriteOnlyList<T>, наследующий от List<T> и запрещающий в нем операции чтения, и функция с параметром WriteOnlyList<Cat>, куда она записывает объекты типа Cat, то передавать ей List<Animal> или List<Object> безопасно — туда она ничего, кроме объектов класса-наследника, не запишет, а пытаться читать другие объекты не будет.

Функциональные типы

В языках с функциями первого класса существуют обобщенные функциональные типы и переменные-делегаты. Для обобщенных функциональных типов полезна ковариантность по возвращаемым типам и контравариантность по аргументам. Так, если делегат задан как «функция, принимающая String и возвращающая Object», то в него можно записать и функцию, принимающую Object и возвращающую String: если функция способна принимать любой объект, она может принимать и строку; а из того, что результатом функции является строка, следует, что функция возвращает объект.

Наследование в объектно-ориентированных языках

Когда подкласс переопределяет метод в суперклассе, компилятор должен проверить, что метод переопределения имеет правильный тип. В то время как некоторые языки требуют, чтобы тип точно соответствовал типу в суперклассе (инвариантность ), также типобезопасно разрешить заменяющему методу иметь «лучший» тип. По обычному правилу выделения подтипов для типов функций это означает, что метод переопределения должен возвращать более конкретный тип (ковариация типа возвращаемого значения) и принимать более общий аргумент (контравариантность типа параметра). В нотации UML возможности следующие:

Вариант и переопределение метода: обзор

  • Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

    Подтип параметра / типа возвращаемого значения метода.

  • Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

    Инвариантность . Сигнатура замещающего метода не изменилась.

  • Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

    Ковариантный возвращаемый тип . Отношение подтипов находится в том же направлении, что и отношение между ClassA и ClassB.

  • Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

    Контравариантный тип параметра . Отношение подтипов противоположно отношению между ClassA и ClassB.

  • Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

    Ковариантный тип параметра . Не безопасен по типу.

В качестве конкретного примера предположим, что мы пишем класс для моделирования приюта для животных . Мы предполагаем, что Catэто подкласс Animal, и что у нас есть базовый класс (с использованием синтаксиса Java)

Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании
 Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

Теперь возникает вопрос: если мы создаем подкласс AnimalShelter, какие типы нам разрешено отдавать в getAnimalForAdoptionи putAnimal?

Тип возвращаемого значения ковариантного метода

В языке, который допускает ковариантные возвращаемые типы , производный класс может переопределить getAnimalForAdoptionметод для возврата более конкретного типа:

Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании
 Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

Среди основных объектно-ориентированных языков Java , C ++ и C # (начиная с версии 9.0 ) поддерживают ковариантные возвращаемые типы. Добавление ковариантного возвращаемого типа было одной из первых модификаций языка C ++, одобренной комитетом по стандартам в 1998 году. Scala и D также поддерживают ковариантные возвращаемые типы.

Тип параметра контравариантного метода

Точно так же безопасно по типу разрешить переопределяющему методу принимать более общий аргумент, чем метод в базовом классе:

Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании
 Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

Только несколько объектно-ориентированных языков действительно позволяют это (например, Python при проверке типов с помощью mypy). C ++, Java и большинство других языков, поддерживающих перегрузку и / или затенение , интерпретируют это как метод с перегруженным или затененным именем.

Однако Сатер поддерживал как ковариацию, так и контравариантность. Вызов конвенция о перекрытых методах ковариантна из параметров и возвращаемых значений, и контрвариантных с нормальными параметрами (с режимом в ).

Тип параметра ковариантного метода [ править ]

Пара основных языков, Eiffel и Dart , позволяет параметрам замещающего метода иметь более конкретный тип, чем метод в суперклассе (ковариация типа параметра). Таким образом, следующий код Dart будет проверять типы с putAnimalпереопределением метода в базовом классе:

Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании
class  CatShelter  extends  AnimalShelter  {

    void  putAnimal ( ковариантный  Кот-  животное )  { 
        // ... 
    } 
}

Это небезопасно. Повышающей Кастинг CatShelterАнь AnimalShelter, можно попытаться поместить собаку в кошки приюта. Это не соответствует CatShelterограничениям параметров и приведет к ошибке выполнения. Отсутствие безопасности типов (известное как «проблема перехвата» в сообществе Eiffel, где «кошка» или «CAT» - это измененная доступность или тип) было давней проблемой. За прошедшие годы для исправления этой проблемы были предложены различные комбинации глобального статического анализа, локального статического анализа и новых языковых функций , и они были реализованы в некоторых компиляторах Eiffel.

Несмотря на проблему безопасности типов, разработчики Eiffel считают ковариантные типы параметров решающими для моделирования требований реального мира. Приют для кошек иллюстрирует общий феномен: это своего рода приют для животных, но с дополнительными ограничениями , и кажется разумным использовать наследование и типы ограниченных параметров для моделирования этого. Предлагая такое использование наследования, разработчики Eiffel отвергают принцип подстановки Лискова , который гласит, что объекты подклассов всегда должны быть менее ограничены, чем объекты их суперкласса.

Еще один пример основного языка, допускающего ковариацию в параметрах метода, - это PHP в отношении конструкторов классов. В следующем примере принимается метод __construct (), несмотря на то, что параметр метода ковариантен параметру родительского метода. Если бы этот метод был любым, кроме __construct (), произошла бы ошибка:

 Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

Другой пример, в котором ковариантные параметры кажутся полезными, - это так называемые бинарные методы, то есть методы, в которых параметр должен иметь тот же тип, что и объект, для которого вызывается метод. Примером является compareToметод: проверяет, идет ли он до или после в некотором порядке, но способ сравнения, скажем, двух рациональных чисел будет отличаться от способа сравнения двух строк. Другие распространенные примеры бинарных методов включают проверку на равенство, арифметические операции и операции над множеством, такие как подмножество и объединение. a.compareTo(b)ab

В более старых версиях Java метод сравнения был указан как интерфейс Comparable:

interface  Comparable  {

    int  compareTo ( Объект  o ); 
}

Недостатком этого является то, что метод определен как принимающий аргумент типа Object. В типичной реализации этот аргумент сначала будет понижен (выдается ошибка, если он не соответствует ожидаемому типу):

  Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

В языке с ковариантными параметрами аргументу compareToможно напрямую указать желаемый тип RationalNumber, скрывая приведение типов. (Конечно, это все равно приведет к ошибке выполнения, если compareToзатем будет вызвано, например, a String.)

Отсутствие необходимости в ковариантных типах параметров [ править ]

Другие языковые функции могут обеспечить очевидные преимущества ковариантных параметров при сохранении заменяемости Лискова.

На языке с универсальными шаблонами (также известным как параметрический полиморфизм ) и ограниченной квантификацией предыдущие примеры могут быть написаны безопасным для типов способом. [10] Вместо определения AnimalShelterмы определяем параметризованный класс . (Одним из недостатков этого является то, что разработчик базового класса должен предвидеть, какие типы нужно будет специализировать в подклассах.) Shelter<T>

  Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

Точно так же в последних версиях Java Comparableинтерфейс был параметризован, что позволяет опускать приведение вниз безопасным для типов способом:

  Вариантность(Ковариантность и Контравариантность и Инвариантность) в программировании

Еще одна языковая функция, которая может помочь, - это множественная отправка . Одна из причин, по которой двоичные методы неудобно писать, заключается в том, что в таких вызовах, как выбор правильной реализации, действительно зависит от типа среды выполнения обоих и , но в обычном объектно-ориентированном языке учитывается только тип среды выполнения . На языке с множественной диспетчеризацией в стиле Common Lisp Object System (CLOS) метод сравнения может быть записан как универсальная функция, в которой оба аргумента используются для выбора метода. a.compareTo(b)compareToaba

Джузеппе Кастанья [11]заметил, что в типизированном языке с множественной отправкой универсальная функция может иметь некоторые параметры, которые управляют отправкой, и некоторые «оставшиеся» параметры, которые этого не делают. Поскольку правило выбора метода выбирает наиболее конкретный применимый метод, если метод переопределяет другой метод, тогда у метода переопределения будут более конкретные типы для управляющих параметров. С другой стороны, для обеспечения безопасности типов язык по-прежнему должен требовать, чтобы оставшиеся параметры были как минимум такими же общими. Используя предыдущую терминологию, типы, используемые для выбора метода во время выполнения, являются ковариантными, в то время как типы, не используемые для выбора метода во время выполнения, являются контравариантными. Традиционные языки с единой диспетчеризацией, такие как Java, также подчиняются этому правилу: для выбора метода используется только один аргумент (объект-получатель,this), и действительно, тип thisболее специализирован внутри методов переопределения, чем в суперклассе.

Кастанья предлагает, чтобы примеры, в которых ковариантные типы параметров превосходят (в частности, бинарные методы), должны обрабатываться с использованием множественной диспетчеризации; что естественно ковариантно. Однако большинство языков программирования не поддерживают множественную отправку.

Резюме дисперсии и наследования

В следующей таблице приведены правила переопределения методов на языках, обсужденных выше.

Тип параметра Тип возврата
C ++ (с 1998 г.), Java (с J2SE 5.0 ), D Инвариантный Ковариантный
C # Инвариантный Ковариантный (начиная с C # 9 - до инвариантного)
Скала , Сатер Контравариантный Ковариантный
Эйфелева Ковариантный Ковариантный

Реализация в языках программирования

C++

C++ начиная со стандарта 1998 года поддерживает ковариантные типы возврата в перекрытых виртуальных функциях:

class X {};

class A
{
public:
    virtual X* f() { return new X; }
};

class Y : public X {};

class B : public A
{
public:
    virtual Y* f() { return new Y; } // ковариантность позволяет задать в перекрытом методе уточненный тип возврата
};

Указатели в C++ ковариантны: например, указателю на базовый класс можно присвоить указатель на дочерний класс.

Шаблоны C++, вообще говоря, инвариантны, отношения наследования классов-параметров на шаблоны не переносится. Например, ковариантный контейнер vector<T> позволял бы нарушать контроль типов. Однако при помощи параметризованных конструкторов копирования и операторов присваивания можно создать умный указатель, ковариантный своему параметру-типу .

Java

Ковариантность типов возврата методов реализована в Java начиная с J2SE 5.0. В параметрах методов ковариантности нет: для перекрытия виртуального метода типы его параметров должны совпадать с определением в родительском классе, иначе вместо перекрытия будет определен новый перегруженный метод с этими параметрами.

Массивы в Java ковариантны с самой первой версии, когда в языке еще не было обобщенных типов. (Если бы этого не было, то для использования, например, библиотечного метода, принимающего массив объектов Object[], для работы с массивом строк String[], требовалось бы его сначала скопировать в новый массив Object[].) Поскольку, как было сказано выше, при записи элемента в такой массив можно обойти контроль типов, в JVM существует дополнительный контроль во время выполнения, генерирующий исключение при записи некорректного элемента.

Обобщенные типы в Java инвариантны, поскольку вместо создания универсального метода, работающего с Object’ами, можно его параметризовать, превратив в обобщенный метод и сохранив контроль типов.

Вместе с тем в Java можно реализовать своего рода ко- и контравариантность обобщенных типов, используя символ-джокер и уточняющие спецификаторы: List<? extends Animal> будет ковариантен подставляемому типу, а List<? super Animal> — контравариантен.

C#

В языке C#, начиная с первой его версии, массивы ковариантны. Это было сделано для совместимости с языком Java . При попытке записать в массив элемент неверного типа выбрасывается исключение во время выполнения.

Обобщенные классы и интерфейсы, появившиеся в C# 2.0, стали, как и в Java, инвариантными по типу-параметру.

С введением обобщенных делегатов (параметризированных по типам аргументов и возвращаемым типам), язык позволил автоматическое преобразование обычных методов к обобщенным делегатам с ковариантностью по возвращаемым типам и контравариантностью по типам аргументов. Поэтому в C# 2.0 стал возможен код следующего вида:

void ProcessString(String s) { /* ... */}
void ProcessAnyObject(Object o) { /* ... */ }
String GetString() { /* ... */ }
Object GetAnyObject() { /* ... */ }
//...
Action<String> process = ProcessAnyObject;
process(myString); // легальное действие

Func<Object> getter = GetString;
Object obj = getter(); // легальное действие

однако код Action<Object> process = ProcessString; некорректен и дает ошибку компиляции, иначе этот делегат можно было бы потом вызвать как process(5), передавая Int32 в ProcessString.

В C# 2.0 и 3.0 этот механизм позволял лишь записывать простые методы в обобщенные делегаты и не мог делать автоматическое преобразование одних обобщенных делегатов в другие. Иначе говоря, код

Func<String> f1 = GetString;
Func<Object> f2 = f1;

в этих версиях языка не компилировался. Таким образом, обобщенные делегаты в C# 2.0 и 3.0 все еще были инвариантными.

В C# 4.0 это ограничение было снято, и начиная с этой версии код f2 = f1 в примере выше стал работать.

Кроме того, в 4.0 стало возможным задавать вариантность параметров обобщенных интерфейсов и делегатов явным образом. Для этого используются ключевые слова out и in соответственно. Поскольку в обобщенном типе реальное использование типа-параметра известно лишь его автору, к тому же оно может меняться в процессе разработки, это решение обеспечивает наибольшую гибкость без ущерба для надежности контроля типов.

Некоторые библиотечные интерфейсы и делегаты были переопределены в C# 4.0 с использованием этих возможностей. Например, интерфейс IEnumerable<T> отныне стал определяться как IEnumerable<out T>, интерфейс IComparable<T> — как IComparable<in T>, делегат Action<T> — как Action<in T>, и т. п.

Вау!! 😲 Ты еще не читал? Это зря!

  • Общековариантность
  • Лоренц-ковариантность
  • Бра и кет алгебраический формализм , предназначенный для описания квантовых состояний.
  • Ковариантная производная
  • Метрический тензор
  • Ковариантность и контравариантность ( программирование )

Исследование, описанное в статье про вариантность, подчеркивает ее значимость в современном мире. Надеюсь, что теперь ты понял что такое вариантность, ковариантность, контравариантность, инвариантность и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Объектно-ориентированное программирование ООП

создано: 2020-04-23
обновлено: 2021-11-20
132265



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:


Поделиться:

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей



Комментарии


Оставить комментарий
Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Объектно-ориентированное программирование ООП

Термины: Объектно-ориентированное программирование ООП