Почему следующий пример, использующий ковариацию в делегатах, не компилируется?

Я определил следующие типы делегатов. Один возвращает строку и один объект:

delegate object delobject();
delegate string delstring();

Теперь рассмотрим следующий код:

delstring a = () => "foo";
delobject b = a; //Does not compile!

Почему назначение недействительно?

Я не понимаю. Метод, который возвращает строку, должен быть смело рассмотрен как метод, который возвращает объект (поскольку строка является объектом).


В С# 4.0 работает следующий пример. Вместо использования делегатов я использую Func<TResult> общий тип:

Func<string> a = () => "foo";
Func<object> b = a; //Perfectly legal, thanks to covariance in generics

Также: если я переписал его таким образом, он работает:

delobject b = () => a();

Но это не то же самое, что я хотел изначально. Теперь я создал новый метод, который вызывает другой.

Это не просто назначение, как показано в этом примере:

delint a = () => 5;
delobject b = a; //Does not work, but this is OK, since "int" is a value type.
delobject b = () => a(); //This will box the integer to an object.

Ответ 1

Типы делегатов по-прежнему являются конкретными типами объектов. Когда вы пишете

delegate object delobject();
delegate string delstring();

тогда нет отношения "есть" между двумя типами делегатов. Вы просто создали два разных типа.

Это просто как

class A { public int i; };
class B { public int i; };

Нет никакого неявного или явного преобразования между A и B, хотя нет никакого возможного A, который не будет иметь равного смысла как B, или наоборот.

Ко- и контравариантность в Func означает, что авторы этого конкретного типа делегата решили, что Func<string> можно рассматривать как Func<object>, но что-то, что автор типа делегата может решить, точно так же автор моего класса B, который может решить, может ли смысл иметь простое выражение из A.

Что-то, что вы можете сделать, это добавить еще один уровень косвенности без создания дополнительного метода:

delstring a = () => "foo";
delobject b = new delobject(a); // or equivalently, a.Invoke

Ответ 2

Аргументы типа для Func отмечены in и out, поэтому вы можете назначать эти типы друг другу, как вы. Это то, что означает дисперсия в дженериках.

Ваши delobject и delstring являются разными типами, и они не являются вариантами, поэтому вы не можете назначать друг другу. Но для delobject вы можете назначить обработчик, который возвращает производный тип, это то, что означает дисперсия в делегатах.

Ответ 3

Делегаты (MSDN):

В контексте перегрузки метода подпись метода не включает возвращаемое значение. Но в контексте делегатов подпись содержит возвращаемое значение. Другими словами, метод должен иметь тот же тип возврата, что и делегат.

Так как тип возвращаемого значения не соответствует точно (т.е. object != string), назначение недействительно.

Итак, у вас может быть

delstring a = () => "foo"; // compiles
delobject b = a;           // does not compile
delobject c = () => "foo"; // compiles

Как вы указали, Func работает covariantly с помощью out, тогда как делегаты этого не делают.