Почему следующий пример, использующий ковариацию в делегатах, не компилируется?
Я определил следующие типы делегатов. Один возвращает строку и один объект:
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
, тогда как делегаты этого не делают.