Коваріантність і контраваріантність (програмування)
Варіантність — у програмуванні, спосіб перенесення наслідування типів на нові типи параметризовані попередніми (контейнери, узагальнені типи, делегати).
Терміни виникли від термінів теорії категорій «коваріантний» та «контраваріантний» функтор.
В системі типів мови програмування, конструктор типів є:
- коваріантним — якщо він зберігає порядок типів (від похідних до базового);
- контраваріантним — якщо він змінює порядок типів на протилежний;
- біваріантним — якщо прямий і обернений порядок є справедливими одночасно.
- інваріантним чи неваріантним — якщо він не підпадає під попередні варіанти.
Наприклад, в C#, якщо Cat
це підтип Animal
, тоді:
IEnumerable<Cat>
це підтипIEnumerable<Animal>
. Підтипність збережена, боIEnumerable<T>
коваріантний поT
.Action<Animal>
підтипAction<Cat>
. Підтипність обернута, боAction<T>
контраваріантний поT
.- Ані
IList<Cat>
, аніIList<Animal>
не є підтипом іншого, боIList<T>
інваріантний поT
.
Варіантність узагальнених інтерфейсів C# визначається через використання атрибута out
(коваріантний) або in
(контраваріантний) для (нуля чи більше) його параметрів типів. Для кожного промаркованого таким чином параметра, компілятор перевіряє, не дозволячи жодного порушення, що використання глобально цілісне. Згадані інтерфейси означені як IEnumerable<out T>
, Action<in T>
і IList<T>
. Типи з більш ніж одним параметром типом можуть вказувати різні варіантності для різних параметрів типів. Наприклад, тип делегат Func<in T, out TResult>
представляє функцію з контраваріантним входовим параметром типом T
і коваріантним параметром типу результату TResult
.[1]
Якщо типи Cat
та Dog
наслідують тип Animal
, конструктором масивів з типу Animal
утворимо тип Animal[]
("масив звірів"). Ми можемо використовувати його:
- коваріантно: вважаючи що
Cat[]
є представникомAnimal[]
- контраваріантно: вважаючи що
Animal[]
є представникомCat[]
- інваріантно: вважаючи що
Animal[]
не є представникомCat[]
а такожCat[]
не є представникомAnimal[]
.
Залежно від типу операцій, що будуть проводитись над масивом ми можемо використовувати різну варіантність:
- якщо тільки читати елементи масиву, то безпечно використовувати його коваріантно, оскільки
Cat
є представникомAnimal
; - якщо тільки записувати чи передавати елементи масиву то безпечно використовувати його контраваріантно, щоб не було можливості в масив
Cat[]
записати елемент типуDog
; - якщо ж потрібно читати та записувати елементи масиву, то безпечно використовувати його тільки інваріантно. Також можна реалізувати два незалежні інтерфейси коваріантний Producer<T> для читання та контраваріантний Consumer<T> для запису елементів.
Масиви є коваріантними в Java, C#, та інваріантними в C++.
Функційні типи є коваріантними по типу результата та контраваріантними по типах аргументів.
Тобто функції типу Cat->Cat
та Animal->Animal
наслідують тип Cat->Animal
та можуть безпечно використовуватись там де очікувався базовий тип.
C++ починаючи зі стандарту 1998 року підтримує коваріантність при заміщенні віртуальних методів:
class X {};
class Y : public X {};
class A
{
public:
virtual X* f() { return new X; }
};
class B : public A
{
public:
virtual Y* f() { return new Y; } // заміщуючи метод, можна повернути уточнений тип результату
};
В наступній таблиці зібрана варіантність при заміщенні методів для різних мов програмування.
тип аргумента | тип результату | |
---|---|---|
C# | інваріантний | інваріантний |
C++ (з 1998), Java (з J2SE 5.0), Scala, D | інваріантний | коваріантний |
Sather | контраваріантний | коваріантний |
Eiffel | коваріантний | коваріантний |
В деяких мовах програмування, що підтримують параметричний поліморфізм, можливо вказати варіантність шаблонного типу по параметру. Це можливо:
- при декларації шаблонного типу (як в C#) чи
- при створенні (використанні) конкретного типу на основі шаблонного (як в Java).
- в C# це можливо тільки для інтерфейсів (ключовими кловами
out
таin
), - в Scala та OCaml (ключовими кловами
+
та-
).
Якщо варіантність не задана, то тип буде вважатись інваріантним.
- Приклад
interface IEnumerator<out T>
{
T Current { get; }
bool MoveNext();
}
IEnumerator<Cat>
буде вважатись успадкованим від IEnumerator<Animal>
.
Задання варіантності при створенні кожного конкретного типу є більш гнучким. Воно дозволяє автору класа не декларувати декілька інтерфейсів з різною варіантністю. В Java варіантність задається разом із заданням обмежуючого типу:
List<? extends Animal>
буде коваріантним типу елемента;List<? super Animal>
буде контраваріантним типу елемента.
При заданні варіантності буде заборонено використовувати методи класу з відмінною варіантністю.
- http://msdn.microsoft.com/ru-ru/library/dd799517.aspx [Архівовано 24 Грудня 2015 у Wayback Machine.] Коваріантність і контраваріантність в шаблонах.
- http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx [Архівовано 28 Червня 2013 у WebCite] Covariance and Contravariance in C#, Part Two. Eric Lippert.
- ↑ Func<T, TResult> Delegate [Архівовано 19 Серпня 2017 у Wayback Machine.] - MSDN Documentation