将匿名方法分配给委托时,协方差和对数不起作用

时间:2019-02-21 12:02:24

标签: c# lambda covariance contravariance anonymous-methods

我有以下代码摘自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问题。

1 个答案:

答案 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在xFirst的情况下可以工作,则应该如果xSecond(尽管使用LSP),也可以使用。请注意,即使Second返回第一个SampleDelegate也可以逃脱返回{{1}}的麻烦:编译器不在乎。