YaChudo

Коваріантність і контраваріантність (програмування)

Переглядів: 0. Оновлено 10.10.2026.

Варіантність — у програмуванні, спосіб перенесення успадкування типів на нові типи, параметризовані попередніми (контейнери, узагальнені типи, делегати).

Терміни виникли від термінів теорії категорій «коваріантний» та «контраваріантний» функтор.

Визначення

В системі типів мови програмування, конструктор типів є:

  • коваріантним — якщо він зберігає порядок типів (від похідних до базового);
  • контраваріантним — якщо він змінює порядок типів на протилежний;
  • біваріантним — якщо прямий і обернений порядок справедливі одночасно.
  • інваріантним чи неваріантним — якщо він не підпадає під попередні варіанти.

Приклади на C#

Наприклад, у 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;
  • якщо тільки записувати чи передавати елементи масиву то безпечно використовувати його контраваріантно, щоб не було можливості в масив 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> буде контраваріантним типу елемента.

При заданні варіантності буде заборонено використовувати методи класу з різною варіантністю.

Примітки

  1. ↑ Func<T, TResult> Delegate [Архівовано 19 Серпня 2017 у Wayback Machine.] — MSDN Documentation

Джерела

Джерело: стаття у Вікіпедії та історія редагувань (автори).