假设我有这些类:
class Animal { }
class Cat : Animal { }
然后我像这样声明一个变量:
Action<Cat> c;
现在,Action<T>
是T
的逆变,所以我可以这样做:
void Foo(Animal a) { }
c = Foo;
有道理。但当我这样做时,我得到编译器错误CS16611:
c = (Animal a) => { };
我甚至可以这样做而不会出错:
Action<Animal> b = (Animal a) => { };
c = b;
我在这里错过了什么?
超级短版本:你可以告诉编译器你想用哪种委托类型来使用你的表达式:
c = (Action<Animal>)(a => { });
对了,那一边…
正如madreflection在我写这篇文章时指出的那样(谢谢mad),问题是纯委托不是逆变的。逆变仅对相同泛型类型的泛型委托有效。
考虑这个项目列表:
// Let's start with defining some delegate types:
delegate void AnimalAction(Animal parm);
delegate void MyAction<in T>(T parm);
// Now here are my variables, all working fine:
Action<Animal> actAnimal = (Animal a) => { };
MyAction<Animal> myAnimal = (Animal a) => { };
AnimalAction animalAction = (Animal a) => { };
它们看起来基本相同,而且实际上都是功能相同的。遗憾的是,无论它们看起来多么相似,无论void DoNothing(Animal parm) { }
方法可以被赋值给它们,编译器都会告诉你它们是不一样的。
那么当我们有一个像(Animal a) => { }
这样的lambda表达式时会发生什么呢?以上这些都可以接受,因为编译器足够聪明,知道你在做什么。
现在让我们改变一下:
// New delegate for cats
delegate void CatAction(Cat parm);
// New variables, but these all fail:
Action<Cat> actCat = (Animal a) => { };
MyAction<Cat> myCat = (Animal a) => { };
CatAction catAction = (Animal a) => { };
编译器对象在所有这些情况下,因为它的lambda表达式的过渡类型(delegate _expression_type(Animal a)
)是不可引用转换到各种目标委托类型。
你可以用几种方法来解决这个问题。您可以使用new Action<Cat>(some_animal_action)
将一个委托包装成另一个兼容类型的委托。或者您可以简单地告诉编译器从一开始就生成正确的类型,就像我在顶部写的那样。
这些都可以
c = (Action<Animal>)(a => { });
c = new Action<Animal>(a => { });
c = new Action<Animal>((Animal a) => { });
(但不要做new
版本,它可能会创建一个嵌套的委托。)
奇怪的是,您还可以使用var
在自动类型化变量中捕获输出。这是有效的,因为编译器知道使用Action<...>
和Func<...>
的表达式委托,但只有当委托的类型没有事先完全指定。
var temp = (Animal a) => { };
// 'temp' is of type Actions<Animal> so this works:
c = temp;