为什么Func <t>与Func <ienumerable <t>&gt;不明确?</ienumerable <t> </t>

时间:2011-01-01 02:34:07

标签: c# generics

这个让我感到沮丧,所以我想我会在这里问,希望C#guru可以向我解释。

为什么此代码会产生错误?

class Program
{
    static void Main(string[] args)
    {
        Foo(X); // the error is on this line
    }

    static String X() { return "Test"; }

    static void Foo(Func<IEnumerable<String>> x) { }
    static void Foo(Func<String> x) { }
}

有问题的错误:

Error
    1
    The call is ambiguous between the following methods or properties:
'ConsoleApplication1.Program.Foo(System.Func<System.Collections.Generic.IEnumerable<string>>)' and 'ConsoleApplication1.Program.Foo(System.Func<string>)'
    C:\Users\mabster\AppData\Local\Temporary Projects\ConsoleApplication1\Program.cs
    12
    13
    ConsoleApplication1

我使用的是什么类型无关紧要 - 如果在该代码中用“int”替换“String”声明,您将得到相同类型的错误。这就像编译器无法区分Func<T>Func<IEnumerable<T>>

有人可以对此有所了解吗?

2 个答案:

答案 0 :(得分:26)

好的,这是交易。

简短版本:

  • 模棱两可的错误,很奇怪,是正确的。
  • 在正确的模糊度错误之后,C#4编译器也会产生虚假错误。这似乎是编译器中的一个错误。

长版:

我们遇到了重载解决问题。过载分辨率非常明确。

第一步:确定候选集。这很简单。候选人是Foo(Func<IEnumerable<String>>)Foo(Func<String>)

第二步:确定候选集的哪些成员适用。适用的成员具有可转换为每个参数类型的每个参数。

Foo(Func<IEnumerable<String>>)适用吗?那么,X可转换为Func<IEnumerable<String>吗?

我们参考规范的第6.6节。规范的这一部分是我们语言设计者所说的“非常奇怪”。基本上,它表示转换可以存在,但使用该转换是错误。 (有很好的理由说明为什么我们有这种奇怪的情况,主要是避免未来的变化和避免“鸡和鸡蛋”的情况,但在你的情况下,我们的行为有点不幸。)

基本上,这里的规则是,如果对X()形式的调用的重载解析成功,则从X转换为没有参数的委托类型。显然,这样的调用成功,因此存在转换。实际上使用转换是一个错误,因为返回类型不匹配,但重载解析总是忽略返回类型

因此,存在从XFunc<IEnumerable<String>的转换,因此该重载是适用的候选者。

显然,出于同样的原因,其他超载也是适用的候选人。

第三步:我们现在有两个适用的候选人。哪一个“更好”?

“更好”的是具有更具体类型的那个。如果您有两个适用的候选人M(Animal)M(Giraffe),我们会选择长颈鹿版本,因为长颈鹿比动物更具体。我们知道长颈鹿更具体,因为每只长颈鹿都是动物,但不是每只动物都是长颈鹿。

但在你的情况下,两种类型都没有比另一种更具体。两种Func类型之间没有转换。

因此两者都没有更好,因此重载分辨率报告错误。

然后,C#4编译器出现了一个错误,其错误恢复模式无论如何选择其中一个候选者,并报告另一个错误。我不清楚为什么会这样。基本上它是说错误恢复正在选择IEnumerable重载,然后注意方法组转换产生无法维持的结果;即,该字符串与IEnumerable<String>不兼容。

整个情况相当不幸;如果返回类型不匹配,可能更好的说没有方法组到委托的转换。 (或者,产生错误的转换总是比没有产生错误的转换更差。)但是,我们现在仍然坚持使用它。

一个有趣的事实:lambda 的转换规则考虑了返回类型。如果你说Foo(()=>X())那么我们做对了。 lambdas和方法组具有不同的可兑换规则这一事实是相当不幸的。

总而言之,在这种情况下,编译器实际上是规范的正确实现,而这种特殊情况是一些可能不幸的规范选择的意外结果。

答案 1 :(得分:7)

您的代码需要两次“魔术”,一次从命名方法组转换为委托,一次执行重载解析。

尽管您只有一个名为X的方法,但编译规则是针对有多个的情况而构建的。

此外,由于代理不必完全匹配方法签名,因此复杂性进一步增加。最重要的是,任何给定的方法都可以转换为具有相同签名的无限数量的不同委托类型。

你的具体案例看起来很简单,但一般情况非常困难,所以语言不允许。

如果您手工完成部分工作,您将解决问题。例如

Func<string> d = X;
Foo(d);

应该编译得很好。