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

Covariance and contravariance not working when assigning anonymous method to delegate

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

成为给出答案的无聊人"because that's what the spec says"(TL;最后是我的想法)...

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

(请注意,Method Group 表示同一方法的一组 1 个或多个重载 - 因此您的单个方法仍算作单独的方法组)

C# Language Specification 的第 6.5 节讨论匿名函数转换:

An anonymous-method-expression or lambda-expression is classified as an anonymous function (§7.15). The expression does not have a type but can be implicitly converted to a compatible delegate type or expression tree type. Specifically, an anonymous function F is compatible with a delegate type D provided:

  • ...
  • If F has an explicitly typed parameter list, each parameter in D has the same type and modifiers as the corresponding parameter in F.

但是第 6.6 节讨论了方法组转换:

An implicit conversion (§6.1) exists from a method group (§7.1) to a compatible delegate type. Given a delegate type D and an expression E that is classified as a method group, an implicit conversion exists from E to D if E contains at least one method that is applicable in its normal form (§7.5.3.1) to an argument list constructed by use of the parameter types and modifiers of D, as described in the following.

The compile-time application of a conversion from a method group E to a delegate type D is described in the following. Note that the existence of an implicit conversion from E to D does not guarantee that the compile-time application of the conversion will succeed without error.

  • A single method M is selected corresponding to a method invocation (§7.6.5.1) of the form E(A), with the following modifications:
    • The argument list A is a list of expressions, each classified as a variable and with the type and modifier (ref or out) of the corresponding parameter in the formal-parameter-list of D.
    • The candidate methods considered are only those methods that are applicable in their normal form (§7.5.3.1), not those applicable only in their expanded form.

所以方法组 -> 委托转换使用或多或少相同的规则,就像您尝试调用具有相应参数类型的方法一样。我们被引导至第 7.6.5.1 节,这将我们引导至第 7.5.3.1 节。这很复杂,所以我不打算在此处逐字粘贴。

有趣的是,我找不到关于委托协变的部分,只有接口协变(尽管第 6.6 节说在示例中提到它)。


TL;DR,当你写的时候:

SampleDelegate test = SomeMethodGroup;

编译器通过整个算法从方法组中挑选一个与委托类型兼容的合适成员,如果您正在调用该方法,则遵循与重载决策大致相同的规则。

当你写:

SampleDelegate test = (First first) => new Second();

编译器遵循更简单的规则 "does the lambda's signature match the delegate signature"。

我想这是有道理的。大多数时候你会写:

SampleDelegate test = first => new Second();

并且由编译器从委托签名中找出参数类型。如果您自己添加显式类型,并不会完全改变所使用的算法:编译器使用相同的算法,但如果它产生的类型与您的显式类型冲突,则会出现错误。


请注意,几乎所有时候 都不重要 。很少将类型放在 lambda 的参数上,因此您通常只需这样写:

SampleDelegate test = x => new Second();

编译器推断 x 实际上是一个 Second,这很好:如果你写了一个 lambda,它可以在 xFirst 的情况下工作,如果 xSecond(尽管有 LSP),它也应该有效。请注意,即使首先返回 SampleDelegate returns,您也可以返回 Second:编译器不介意。