заданий 24 Квітня '16 о 18:36
Незрозуміло як влаштовані коваріантність і контраваріантних. У прикладі показано, що делегат може отримати посилання на два методу, з різними вхідними і вихідними параметрами. Тут коваріантність це ChangeIt change = IncrB ;, а контраваріантних ChangeIt change = IncrA ;? Загалом, не зрозуміло визначення ковариантности і контраваріантних, надто вже вони заплутані. - NaughtyBrain 24 Квітня '16 о 19:20
Для початку, давайте глянемо, що таке ця сама варіантність.
Нехай у нас є два класи, Car і BMW. Очевидно, що BMW є підклас Car. кожна бєха є машиною.
Зазвичай при цьому говорять так: «всюди, де ви використовуєте Car. можна використовувати і BMW ». Це насправді майже правда, але не зовсім.
Приклад: якщо у вас є список машин, ви не можете замість нього використовувати список BMW. Чому? А ось чому. Нехай вас є List
Якби у нас був список, доступний тільки на читання. то проблем би якраз не було:
Отже, що у нас виходить? Незважаючи на те, що BMW - машина, список BMW вже не обов'язково є списком машин. А ось список BMW, доступний лише на читання, таки є списком машин.
Тепер назад до варіантності. Ми говоримо про ковариантности в загальному сенсі, якщо щось змінюється аналогічним чином. У разі успадкування класів: ми можемо замість Car використовувати BMW. і точно так же ми можемо замість 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).