Я теперь не согласен с ответом от SeanVDH. Он говорит: «С лингвистической точки зрения это кажется логичным в смысле параметров по сравнению с возвратом, поэтому направление вперед или назад зависит от входа или выхода из функции».
Вместо этого я думаю, что это ответ, который приходит от здесь :
Ковариация сохраняет совместимость назначений , а контрвариантность меняет ее. Ковариация - это расширяющаяся конверсия, а контравариантность - сужающаяся конверсия.
Когда вы создаете экземпляр делегата, вы
может назначить ему метод, который имеет больше
производный тип возврата, чем это
указано в делегате
(Ковариация). Вы также можете назначить
метод с типами параметров меньше
получены, чем те, в делегате
(Контрвариантность). Акцент добавлен
Пример: * * тысяча двадцать-пять
static object GetObject() { return null; }
static void SetObject(object obj) { }
static string GetString() { return ""; }
static void SetString(string str) { }
static void Main()
{
// Covariance. A delegate specifies a return type as object,
// but I can assign a method that returns a string.
Func<object> del = GetString;
// Contravariance. A delegate specifies a parameter type as string,
// but I can assign a method that takes an object.
Action<string> del2 = SetObject;
// But implicit conversion between generic delegates is not supported until C# 4.0.
Func<string> del3 = GetString;
Func<object> del4 = del3; // Compiler error here until C# 4.0.
}
Это осознание было результатом чтения статьи Эрика Липперта, раздела Ковариантность и контравариантность Быстрых делегатов главы в книге Джона Скита C # In Depth , а также ссылка выше, откуда была взята цитата.