C#中lambda表达式的委托逆变性

4
下面的第二个测试方法无法编译(无法将lambda表达式转换为目标类型D1)。这是否意味着(非泛型)委托逆变不能与lambda表达式一起使用?
[TestFixture]
public class MyVarianceTests
{
    private abstract class Animal {}
    private class Tiger : Animal {}

    private delegate Type D1(Tiger tiger);

    private static Type M1(Animal animal)
    {
        return animal.GetType();
    }

    [Test]
    public void ContravariantDelegateWithMethod()
    {
        D1 func = M1;
        Type result = func(new Tiger());
        Assert.AreEqual(result, typeof (Tiger));
    }

    [Test]
    public void ContravariantDelegateWithLambda()
    {
        D1 func = (Animal animal) => animal.GetType();
        Type result = func(new Tiger());
        Assert.AreEqual(result, typeof (Tiger));
    }
}
2个回答

8
您发现了语言上的不一致性。
语言规范中明确提到:
“7.15.1匿名函数签名” "[...] 与方法组转换(§6.6)相反,不支持匿名函数参数类型的逆变性。[...]”
这引出了一个问题:为什么语言设计者没有考虑支持这个特性?
显然,这个特性有一些小的好处。但是,它是否值得以符合规范的编译器实现所需的额外复杂性为代价呢?
当您编写lambda表达式时,您必须已经准确地知道它被转换为哪个委托/表达式树类型(没有通用类型可以“保存”任意lambda)。从C# 5开始,lambda(与方法相比)除了帮助创建一个单独的委托/表达式树实例之外毫无作用。因此,如果期望逆变性支持来自编译器,显然没有优势(除了方便)去显式指定一个比参数所需的更通用的类型。您可以完全省略类型并依赖于类型推断(或者在最坏情况下显式指定所需的确切参数类型),而不会有任何可重用性或表现力的损失。
但是对于方法,这显然不适用。方法除了创建委托/表达式树之外还有其他目的。您可能需要一个特定的函数签名(与委托不同)因为它是最合适的,或者要求它满足接口契约。此外,请考虑在执行方法组转换时,您(作为创建委托/表达式树的程序员)并不一定“拥有”所讨论的方法(它可以是第三方程序集中的方法)。但是在lambda中永远不会出现这种情况。
看起来语言设计者认为实现lambda参数类型的逆变性的好处不如方法组。

1
谢谢,这正是我在寻找的参考资料。而且你对于为什么存在不一致性的猜测也很有道理。 - mtraudt

2

D1需要一个Tiger类型的参数,但是你传递了一个Animal类型的参数。 Animal不是Tiger 但是Tiger是一种Animal。


动物不够具体,因此将一个接受Animal的函数分配给指定Tiger的委托不应该是问题。M1D1不够具体。 - BAF
+1。是的。协变和逆变经常让人感到困惑,并且不会按照你第一次直觉地想象的方式工作。 - Olivier Jacot-Descombes
你的回答为什么不适用于方法组转换 D1 func = M1; - Ani
2
Ani是正确的。这个答案没有考虑到语句D1 func = M1;按预期工作的事实。 - mtraudt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接