在将匿名方法分配给委托时,协方差和逆变不起作用

J. *_*Doe 2 c# lambda anonymous-methods covariance contravariance

我有以下代码,取自本MSDN:

public class First { }  
public class Second : First { }  
public delegate First SampleDelegate(Second a);

// Matching signature.  
public static First ASecondRFirst(Second first)  
{ return new First(); }  

// The return type is more derived.  
public static Second ASecondRSecond(Second second)  
{ return new Second(); }  

// The argument type is less derived.  
public static First AFirstRFirst(First first)  
{ return new First(); }  

// The return type is more derived   
// and the argument type is less derived.  
public static Second AFirstRSecond(First first)  
{ return new Second(); }

SampleDelegate test;
test = ASecondRFirst;
test = ASecondRSecond;
test = AFirstRFirst;
test = AFirstRSecond;
Run Code Online (Sandbox Code Playgroud)

这一切都编译得很好,但我想测试将委托分配给一个匿名的lambda表达式:

test = (First x) => { return new Second(); };
Run Code Online (Sandbox Code Playgroud)

但是在那行我得到错误:

Cannot convert lambda expression to type 'SampleDelegate' because the parameter types do not match the delegate parameter types

即:( Parameter 1 is declared as type 'ConsoleApp1.First' but should be ConsoleApp1.Second'"ConsoleApp1"是项目的名称).

我无法理解我的lambda有什么问题.

显然协方差和逆变和反作用以及其他什么工作正常,这似乎只是我的lambda的一个问题.

can*_*on7 9

成为一个无聊的人给出了答案"因为这就是规范所说的"(TL; DR最后用我的想法)......

基本上,这是因为方法组转换(例如将方法分配给委托)和匿名函数转换(例如将lambda分配给委托)遵循不同的规则,只有前者受益于方差.

(注意,a Method Group表示同一方法的一组或多组重载 - 因此您的单个方法仍被视为单个方法组)

C#语言规范的 6.5节讨论了匿名函数转换:

匿名方法表达式或lambda表达式被归类为匿名函数(第7.15节).表达式没有类型,但可以隐式转换为兼容的委托类型或表达式树类型.具体来说,匿名函数F与提供的委托类型D兼容:

  • ...
  • 如果F具有明确类型化的参数列表,则D中的每个参数与F中的相应参数具有相同的类型和修饰符.

然而,第6.6节讨论了方法组转换:

从方法组(第7.1节)到兼容的委托类型存在隐式转换(第6.1节).给定委托类型D和被分类为方法组的表达式E,如果E包含至少一个以其正常形式(第7.5.3.1节)适用于构造的参数列表的方法,则存在从E到D的隐式转换通过使用D的参数类型和修饰符,如下所述.

下面描述从方法组E到委托类型D的转换的编译时应用程序.请注意,从E到D的隐式转换的存在并不能保证转换的编译时应用程序在没有错误的情况下成功.

  • 选择单个方法M,对应于E(A)形式的方法调用(第7.6.5.1节),并进行以下修改:
    • 参数列表A是表达式列表,每个表达式被分类为变量,并且具有D的形式参数列表中相应参数的类型和修饰符(ref或out).
    • 所考虑的候选方法只是那些适用于其正常形式的方法(第7.5.3.1节),而不仅仅适用于其扩展形式的方法.

因此,方法组 - >委托转换使用或多或少相同的规则,就像您尝试使用相应的参数类型调用方法一样.我们的目的是第7.6.5.1节,它将我们引导到第7.5.3.1节.这变得很复杂,所以我不打算在这里逐字粘贴它.

有趣的是,我找不到关于委托协方差的部分,只能找到界面协方差(尽管第6.6节说明在一个例子中提到它).


TL; DR,当你写:

SampleDelegate test = SomeMethodGroup;
Run Code Online (Sandbox Code Playgroud)

编译器通过整个算法来选择与委托类型兼容的方法组的合适成员,如果您正在调用该方法,则遵循与重载解析相同的规则.

当你写:

SampleDelegate test = (First first) => new Second();
Run Code Online (Sandbox Code Playgroud)

编译器遵循一个更简单的规则:"lambda的签名是否与委托签名匹配".

我认为这是有道理的.大部分时间你都会写作:

SampleDelegate test = first => new Second();
Run Code Online (Sandbox Code Playgroud)

并且由编译器根据委托签名确定参数类型.如果您自己添加显式类型,则不会完全更改所使用的算法:编译器使用相同的算法,但如果它出现的类型与您的显式类型冲突,则会出现错误.


请注意,几乎所有时间这都无关紧要.将类型放在lambda的参数上是很少见的,所以你通常只写这个:

SampleDelegate test = x => new Second();
Run Code Online (Sandbox Code Playgroud)

编译器推断x实际上是a Second,这很好:如果你已经编写了一个lambda,如果x是a First则可以工作,如果x是a Second(尽管有LSP),它也应该工作.请注意,Second即使SampleDelegate返回第一个,你也可以放弃返回a:编译器并不介意.

  • 非常感谢你!我为自己的粗鲁道歉,似乎**我一直都是错的! (2认同)