简体   繁体   English

为什么我不能将具有较少派生参数类型的 lambda 表达式传递给 Action 类型的变量<t>鉴于后者在 T 上是逆变的?</t>

[英]Why can't I pass a lambda expression with a less-derived parameter type to a variable of type Action<T> given that the latter is contravariant on T?

Let's say I have these classes:假设我有这些课程:

class Animal { }
class Cat : Animal { }

And then I declare a variable like this:然后我像这样声明一个变量:

Action<Cat> c;

Now, Action<T> is contravariant on T , so I can do this:现在, Action<T>T上是逆变的,所以我可以这样做:

void Foo(Animal a) { }
c = Foo;

That makes sense.这就说得通了。 But when I do this, I get compiler error CS16611 :但是当我这样做时,我得到编译器错误 CS16611

c = (Animal a) => { };

I can even do this with no error:我什至可以毫无错误地做到这一点:

Action<Animal> b = (Animal a) => { };
c = b;

What am I missing here?我在这里错过了什么?

Super short version: you can tell the compiler which delegate type you want it to use for your expression using casting:超短版本:您可以使用强制转换告诉编译器您希望它用于表达式的委托类型:

c = (Action<Animal>)(a => { });

Right, that aside..对了,那一边..

As madreflection pointed out while I was writing this (thanks mad), the problem is that pure delegates are not contravariant.正如 madreflection 在我写这篇文章时指出的那样(感谢 mad),问题在于纯委托不是逆变的。 Contravariance works only for generic delegates of the same generic type.逆变仅适用于相同泛型类型的泛型委托。

Consider this list of items:考虑这个项目列表:

// 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) => { };

They all look basically the same, and indeed are all functionally identical.它们看起来基本相同,实际上在功能上也完全相同。 Sadly no matter how similar they look, and no matter the fact that the method void DoNothing(Animal parm) { } can be assigned to all of them, the compiler will tell you that they're not the same.遗憾的是,无论它们看起来多么相似,也无论可以将方法void DoNothing(Animal parm) { }分配给所有这些,编译器都会告诉您它们并不相同。

So what happens when we have a lambda expression like (Animal a) => { } ?那么当我们有一个像(Animal a) => { }这样的 lambda 表达式时会发生什么呢? Well all of the above will accept it because the compiler is bright enough to figure out what you're doing.好吧,以上所有内容都会接受它,因为编译器足够聪明,可以弄清楚您在做什么。

Now let's change it up:现在让我们改变它:

// 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) => { };

The compiler objects in all of these cases because it the transitional type of the lambda expression ( delegate _expression_type(Animal a) ) is not reference-convertable to the various target delegate types.在所有这些情况下,编译器都反对,因为它是 lambda 表达式的过渡类型 ( delegate _expression_type(Animal a) ) 不可引用转换为各种目标委托类型。

You can get around this in a few ways however.但是,您可以通过几种方式解决这个问题。 You can wrap one delegate in another of compatible type using new Action<Cat>(some_animal_action) .您可以使用new Action<Cat>(some_animal_action)将一个委托包装在另一个兼容类型中。 Or you can simply tell the compiler to make the right type from the beginning, like I put at the top.或者你可以简单地告诉编译器从一开始就创建正确的类型,就像我放在顶部的那样。

So these all work:所以这些都有效:

c = (Action<Animal>)(a => { });
c = new Action<Animal>(a => { });
c = new Action<Animal>((Animal a) => { });

(But don't do the new versions, it will probably create a nested delegate.) (但不要做new版本,它可能会创建一个嵌套委托。)

Oddly, you can also capture the output in an auto-typed variable using var .奇怪的是,您还可以使用var在自动类型变量中捕获 output。 This works because the compiler knows to use Action<...> and Func<...> for expression delegates, but only when the delegate's type is not fully specified beforehand.这是有效的,因为编译器知道将Action<...>Func<...>用于表达式委托,但前提是委托的类型没有事先完全指定。 So this works too:所以这也有效:

var temp = (Animal a) => { };

// 'temp' is of type Actions<Animal> so this works:
c = temp;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么 ReSharper 建议我将类型参数 T 设为逆变? - Why does ReSharper suggest that I make type parameter T contravariant? 为什么我不能将逆变接口作为接口上的方法的参数? - Why can't I take a contravariant interface as a parameter to a method on the interface? (可选)将类型T的参数传递给Action - Optionally pass a parameter of type T to an Action 为什么我不能在 Visual Studio 2019 的即时 Window 中将 lambda 表达式作为参数传递? - Why can't I pass a lambda expression as a parameter in the Immediate Window in Visual Studio 2019? 无法使用类型参数将派生类型强制转换为基类抽象类 - Can't cast derived type to base abstract class with type parameter 如何传递一个动作 <in T> 使用派生类作为Action参数? - how to pass an Action<in T> with a derived class as the Action parameter? 为什么我不能将&#39;as&#39;用于限制为接口的泛型类型参数? - Why can't I use 'as' with generic type parameter that is constrained to be an interface? 为什么我不能在Lamba表达式中使用泛型? - Why i can't use generic type with lamba expression? 我可以通过变量类类型来传递<T> - Can I pass through a variable class type to <T> 为什么泛型约束不能被衍生出来? - Why Is It That Generics Constraint Can't Be Casted to Its Derived Type?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM