У чому суть ковариантности і контраваріантних делегатів stack overflow російською

заданий 24 Квітня '16 о 18:36

Незрозуміло як влаштовані коваріантність і контраваріантних. У прикладі показано, що делегат може отримати посилання на два методу, з різними вхідними і вихідними параметрами. Тут коваріантність це ChangeIt change = IncrB ;, а контраваріантних ChangeIt change = IncrA ;? Загалом, не зрозуміло визначення ковариантности і контраваріантних, надто вже вони заплутані. - NaughtyBrain 24 Квітня '16 о 19:20

Для початку, давайте глянемо, що таке ця сама варіантність.

Нехай у нас є два класи, Car і BMW. Очевидно, що BMW є підклас Car. кожна бєха є машиною.

Зазвичай при цьому говорять так: «всюди, де ви використовуєте Car. можна використовувати і BMW ». Це насправді майже правда, але не зовсім.

Приклад: якщо у вас є список машин, ви не можете замість нього використовувати список BMW. Чому? А ось чому. Нехай вас є List. і ви використовуєте його як список машин. Тоді, раз це список машин. в нього можна додати і Запорожець Lanos, правильно? Ось тут-то і починаються проблеми. Якщо у вас в коді написано:

Якби у нас був список, доступний тільки на читання. то проблем би якраз не було:

Отже, що у нас виходить? Незважаючи на те, що BMW - машина, список BMW вже не обов'язково є списком машин. А ось список BMW, доступний лише на читання, таки є списком машин.

Тепер назад до варіантності. Ми говоримо про ковариантности в загальному сенсі, якщо щось змінюється аналогічним чином. У разі успадкування класів: ми можемо замість Car використовувати BMW. і точно так же ми можемо замість IEnumerable використовувати IEnumerable .

Окей, це було довгий вступ, тепер повернемося до теми: коваріантність делегатів. Нехай у нас є делегат, що залежить від типу Car. Поміняємо в його визначенні Car на BMW. чи можна новий делегат використовувати замість старого?

Давайте міркувати логічно. Якщо у нас є такий делегат:

(Він приймає на вхід Car. І видає інший екземпляр Car), то чи можна замість нього підставити функцію, яка описує делегатів такого виду:

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

А ось якщо наш варіантний тип даних (тобто, Car) знаходиться лише в позиції повертається типу:

то на його місці можна використовувати функцію такого вигляду:

(Якщо підходила будь-яка машина, то BMW теж підійде).

Це і є коваріантність делегатів: там, де від вас в коді потрібно делегат, ви можете замість нього надати коваріантний делегат.

Приклад коду, який використовує це:

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

Це працює по тимі ж причинам, що і коваріантність: якщо тестеру підходить будь-який тип машини, то він зможе працювати і з BMW теж.

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

Параметри-типи можуть бути:

  • Інваріантними. Параметр-тип не може змінюватися.
  • Контраваріантнимі. Параметр-тип може бути перетворений від класу до
    класу, похідного від нього. У мові C # контраваріантний тип
    позначається ключовим словом in. Контраваріантний параметр-тип
    може з'являтися тільки у вхідній позиції, наприклад, в якості
    аргументів методу.
  • Коваріантними. Аргумент-тип може бути перетворений від класу до одного з його базових класів. У мові С # коваріантний тип позначається ключовим словом out. Коваріантний параметр узагальненого типу може з'являтися тільки в вихідний позиції, наприклад, в якості значення, що повертається методу.

Припустимо, що існує наступний тип делегата:

Тут параметр-тип T позначений словом in. що робить його контраваріантним, а параметр-тип TResult позначений словом out. що робить його коваріантним. Нехай оголошена наступна змінна:

Її можна привести до типу MyDelegate з іншими параметрами-типами:

Це говорить про те, що fn1 посилається на функцію, яка отримує Object і повертає ArgumentException. Мінлива fn2 намагається послатися на метод, який отримує String і повертає Exception. Так як ми можемо передати String методу, якому потрібно тип Object (тип String є похідним від Object), а результат методу, який повертає ArgumentException. може інтерпретуватися як Exception (тип ArgumentException є похідним від Exception), представлений тут програмний код відкомпілюйте, а на етапі компіляції буде збережена безпеку типів.

Примітка Варіантність діє тільки в тому випадку, якщо компілятор зможе встановити можливість перетворення посилань між типами. Іншими словами, варіантність непридатна для значущих типів через необхідність упаковки (boxing).

Схожі статті