OOP Course
Сьогодні

Підрозділ 6.4

Коваріантність та контраваріантність делегатів

Пояснює коваріантність і контраваріантність делегатів, їх роботу з похідними й базовими типами та використання in/out в узагальнених делегатах.

6.4. Коваріантність та контраваріантність делегатів

Коваріантність і контраваріантність описують, як делегати взаємодіють зі спадкуванням типів. Вони дозволяють присвоювати делегату метод, сигнатура якого не збігається з делегатом точно, але є сумісною через ієрархію класів. Це підвищує гнучкість коду: замість того щоб оголошувати окремий делегат для кожного типу ієрархії, можна використати один делегат для роботи з базовим або похідним типом.

  • Коваріантність стосується типу, що повертається: метод може повертати більш похідний (конкретніший) тип, ніж оголошено в делегаті.
  • Контраваріантність стосується параметрів: метод може приймати більш загальний (базовий) тип, ніж оголошено в делегаті.

Розглянемо ці поняття на прикладі ієрархії класів медичних сповіщень:

Клас Notification — базовий для всіх типів сповіщень. EmailNotification і SmsNotification — похідні класи, кожен з яких перевизначає метод Print.

Коваріантність

Коваріантність дозволяє передати делегату метод, тип якого, що повертається, є похідним від типу, що повертається делегатом. Якщо делегат оголошує повернення Notification, то метод може повертати EmailNotification:

Компілятор дозволяє це, бо будь-який об'єкт EmailNotification є водночас об'єктом Notification — відносини «є» (is-a). Якщо делегат очікує Notification, метод, що повертає EmailNotification, завжди задовольняє цю вимогу.

Контраваріантність

Контраваріантність дозволяє передати делегату метод, тип параметра якого є базовим по відношенню до типу параметра делегата. Якщо делегат оголошує параметр EmailNotification, то метод може приймати Notification:

На перший погляд це може здатися суперечливим: делегат оголошує EmailNotification, а метод приймає Notification. Але логіка правильна: при виклику receiver(...) ми завжди передаємо EmailNotification. Будь-який EmailNotification є Notification, тому метод ProcessNotification(Notification n) коректно обробить його. Метод з ширшим типом параметра — більш гнучкий, і все, що він може зробити з Notification, він зможе зробити і з EmailNotification.

Коваріантність і контраваріантність делегатів

Коваріантність та контраваріантність в узагальнених делегатах

Узагальнені делегати також підтримують коваріантність і контраваріантність — через ключові слова out і in у параметрах типу.

Коваріантний узагальнений делегат (out)

Ключове слово out у параметрі типу означає, що цей тип використовується лише як тип, що повертається. Завдяки цьому делегат із більш конкретним типом можна присвоїти змінній делегата з більш загальним типом:

Без out компілятор заборонив би таке присвоєння, навіть попри те, що EmailNotification є Notification.

Контраваріантний узагальнений делегат (in)

Ключове слово in означає, що параметр типу використовується лише як тип параметра делегата. Завдяки цьому делегат із більш загальним типом можна присвоїти змінній делегата з більш конкретним типом:

Як і у випадку з узагальненими інтерфейсами: параметр коваріантного типу (out) застосовується лише до типу, що повертається, а параметр контраваріантного типу (in) — лише до параметрів делегата.

Поєднання коваріантності та контраваріантності

Один узагальнений делегат може одночасно використовувати обидва оператори — in для параметра і out для типу, що повертається:

Тут делегат converter очікує: взяти SmsNotification і повернути Notification. Ми присвоїли йому toEmail, який бере будь-який Notification і повертає EmailNotification. Контраваріантність по M означає: SmsNotificationNotification (параметр стає ширшим). Коваріантність по E означає: EmailNotificationNotification (тип повернення стає ширшим). Обидві заміни безпечні, і компілятор це перевіряє статично.

Якщо узагальнити: коваріантність — від більш похідного до більш загального типу (EmailNotification → Notification), контраваріантність — від більш загального до більш похідного (Notification → EmailNotification).

Коваріантність та контраваріантність: напрямки заміни типів

Розроблено Tomka Yurii · © 2026 ·