C # не понимает ковариацию и контравариантность делегатов

В течение долгого времени я пытался понять полезность входных и исходящих параметров в связи с дженериками в C #, и я просто не могу вбить это в голову (я знаю, как часто этот вопрос задают в StackOverflow). Я обычно понимаю, что такое ковариация и контравариантность, но не понимаю, почему должны использоваться параметры in и out.

Следующий пример:

public class MainClass {

delegate TOut MyDelegate<TIn, TOut>(TIn input);

public static void Main()
{
    // Func Delegate is using "in T, out TResult"
    Func<Dog, Mammal> funcDelegate = TestMethod;

    // not using "in" or "out" parameters
    MyDelegate<Dog, Mammal> myDelegate = TestMethod;
}

static Dog TestMethod(Mammal m) { return new Dog(); }

class Mammal { }
class Dog : Mammal { } }//end of class

Почему делегат Func использует in и out, если мой собственный делегат без in и out также может ссылаться на метод, который является ковариантным и контравариантным?


person Coco07    schedule 18.07.2020    source источник
comment
Ваш код, о котором идет речь, не компилируется для меня.   -  person Guru Stron    schedule 18.07.2020
comment
TIn in недопустимая декальтива, in здесь неверный идентификатор   -  person Pavel Anikhouski    schedule 18.07.2020
comment
Первый абзац Дисперсия в делегатах ответит на ваш вопрос, NET Framework 3.5 представила поддержку вариативности для сопоставления сигнатур методов с типами делегатов во всех делегатах на C #. Итак, начиная с .NET 3.5, ваш код в полном порядке   -  person Pavel Anikhouski    schedule 18.07.2020
comment
@GuruStron Извините, на самом деле in должно означать ввод. Это было ошибкой при копировании из Visual Studio. Я исправил это   -  person Coco07    schedule 18.07.2020
comment
@PavelAnikhouski Значит, вы могли бы удалять и удалять из всех определений делегатов сегодня, если бы не было проекта ‹3.5?   -  person Coco07    schedule 18.07.2020
comment
@ Coco07 прокрутите вниз до Разница в параметрах универсального типа и часть Чтобы включить неявное преобразование, вы должны явно объявить общие параметры в делегате как ковариантные или контравариантные с помощью in или out ключевое слово sentense. In и out по-прежнему необходимы для неявного преобразования, но не для назначения метода делегатам   -  person Pavel Anikhouski    schedule 18.07.2020
comment
@PavelAnikhouski В этом есть смысл. Я определенно искал 200 похожих записей в Google, но никогда не получал эту информацию от MS. Может мне стоит пройти курс Google, спасибо вам обоим! :)   -  person Coco07    schedule 19.07.2020
comment
@ Coco07 Я добавил ответ, суммирующий приведенные выше комментарии с небольшими пояснениями.   -  person Pavel Anikhouski    schedule 19.07.2020


Ответы (2)


После комментариев под вопросом Разница в делегатах объясняет, что

.NET Framework 3.5 представила поддержку вариативности для сопоставления сигнатур методов с типами делегатов во всех делегатах в C #. Это означает, что вы можете назначать делегатам не только методы с совпадающими сигнатурами, но и методы, которые возвращают больше производных типов (ковариация) или принимают параметры с меньшим количеством производных типов (контравариантность), чем указано в типе делегата.

Итак, ваше присвоение MyDelegate<Dog, Mammal> myDelegate = TestMethod; в порядке, несмотря на разные подписи делегата и TestMethod (обратный входной параметр и тип возвращаемого значения).

Но параметры in и out по-прежнему необходимы, когда у вас есть неявное преобразование между делегатами, согласно Раздел" Разница в параметрах универсального типа "

Чтобы включить неявное преобразование, вы должны явно объявить универсальные параметры в делегате как ковариантные или контравариантные с помощью ключевого слова in или out.

Например. следующий код не будет компилироваться

MyDelegate<Dog, Mammal> myDelegate = TestMethod;
MyDelegate<Dog, Dog> anotherDelegate = TestMethod;
myDelegate = anotherDelegate; //error CS0029: Cannot implicitly convert type...

Пока вы не объявите MyDelegate с контравариантным параметром и ковариантным типом возврата

delegate TOut MyDelegate<in TIn, out TOut>(TIn input);

После этого последняя строка будет скомпилирована

person Pavel Anikhouski    schedule 19.07.2020

Помимо комментария @Pavel Anikhouski (ссылка на this article), когда вы попытаетесь сделать далее, например:

Func<Mammal, Mammal> someFunc = TestMethod;
Func<Dog, Mammal> funcDelegate = someFunc;
MyDelegate<Mammal, Mammal> someDelegate = TestMethod;
MyDelegate<Dog, Mammal> myDelegate = someDelegate; // will not compile unless you will declare MyDelegate<in TIn, TOut>
person Guru Stron    schedule 18.07.2020
comment
Большое тебе спасибо! Думаю, теперь я понял это или, по крайней мере, могу продолжить чтение в правильном направлении :) - person Coco07; 19.07.2020
comment
Честно говоря, этот ответ не добавляет ничего нового к пунктам, уже изложенным в упомянутой статье и комментариях под вопросом. Вроде охота только на очки репутации - person Pavel Anikhouski; 19.07.2020
comment
@PavelAnikhouski Я бы пометил ваш комментарий как принятый, если бы он был написан как ответ - к счастью, все же - person Coco07; 19.07.2020
comment
@PavelAnikhouski Я начал писать это до того, как увидел ваш комментарий, и TBH не читал статью после цитаты, которую вы добавили в первый комментарий. Также согласен с тем, что ваши комментарии хороши в качестве ответа, и вы должны были добавить их изначально. - person Guru Stron; 19.07.2020