我有以下代码摘自this 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;
这一切都可以编译,但是我想测试将委托分配给一个匿名lambda表达式:
test = (First x) => { return new Second(); };
但是在那行我得到了错误:
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问题。
答案 0 :(得分:9)
成为一个无聊的人,给出答案,“因为这就是规范所说的话”(TL; DR带着我的想法结尾)...
基本上是因为方法组转换(例如,将方法分配给委托)和匿名函数转换(例如,将lambda分配给委托)遵循不同的规则,只有前者受益于方差。
(请注意,Method Group
表示同一方法的一组1个或多个重载-因此您的单个方法仍然算作单个方法组)
C# Language Specification的6.5节讨论了匿名函数转换:
anonymous-method-expression或lambda-expression被归类为匿名函数(第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的隐式转换的存在并不能保证转换的编译时应用将成功且不会出错。
- 根据形式E(A)的方法调用(第7.6.5.1节)选择单个方法M,并进行以下修改:
- 参数列表A是一个表达式列表,每个表达式都被分类为变量,并具有D的形式参数列表中相应参数的类型和修饰符(ref或out)。
- 考虑的候选方法仅是那些以其正常形式(第7.5.3.1节)适用的方法,而不是那些仅以其扩展形式适用的方法。
因此,方法组->委托转换使用或多或少相同的规则,就像您尝试使用相应的参数类型调用该方法一样。我们针对第7.6.5.1节,该节将我们引向7.5.3.1节。这变得很复杂,因此我不会在这里逐字粘贴。
有趣的是,我找不到有关委托协方差的部分,只有接口协方差(尽管第6.6节说在一个示例中提到)。
TL; DR,当您写:
SampleDelegate test = SomeMethodGroup;
编译器会通过整个算法来选择与委托类型兼容的方法组的合适成员,并遵循与调用重载解析相同的规则。
写时:
SampleDelegate test = (First first) => new Second();
编译器遵循一个更简单的规则“ lambda的签名与委托的签名匹配”。
我认为这是有道理的。大多数情况下,您将要编写:
SampleDelegate test = first => new Second();
,由编译器根据委托签名确定参数类型。如果您自己添加显式类型,那不会完全改变所使用的算法:编译器使用相同的算法,但是如果所涉及的类型与您的显式类型冲突,则会出现错误。
请注意,几乎所有时间 都不重要 。很少将类型放在lambda的参数上,因此通常只需编写以下代码:
SampleDelegate test = x => new Second();
编译器推断x
实际上是Second
,这很好:如果您编写的lambda在x
是First
的情况下可以工作,则应该如果x
是Second
(尽管使用LSP),也可以使用。请注意,即使Second
返回第一个SampleDelegate
也可以逃脱返回{{1}}的麻烦:编译器不在乎。