【问题标题】:Why is Func<T> ambiguous with Func<IEnumerable<T>>?为什么 Func<T> 与 Func<IEnumerable<T>> 有歧义?
【发布时间】:2011-01-01 02:34:07
【问题描述】:

这个让我很困惑,所以我想我会在这里问,希望 C# 大师可以向我解释。

为什么这段代码会产生错误?

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&lt;T&gt;Func&lt;IEnumerable&lt;T&gt;&gt;一样。

有人能解释一下吗?

【问题讨论】:

    标签: c# generics


    【解决方案1】:

    好的,这就是交易。

    简短版:

    • 奇怪的是,歧义错误是正确的。
    • C# 4 编译器在正确的歧义错误之后也会产生虚假错误。这似乎是编译器中的一个错误。

    长版:

    我们有一个过载解决问题。重载分辨率非常明确。

    第一步:确定候选集。这很容易。候选人是Foo(Func&lt;IEnumerable&lt;String&gt;&gt;)Foo(Func&lt;String&gt;)

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

    Foo(Func&lt;IEnumerable&lt;String&gt;&gt;) 适用吗?那么,X 可以转换为Func&lt;IEnumerable&lt;String&gt; 吗?

    我们参考了规范的第 6.6 节。这部分规范就是我们语言设计者所说的“真的很奇怪”。基本上,它说可以存在转换,但使用该转换是错误的。 (我们出现这种奇怪的情况是有充分理由的,主要与避免未来的重大变化和避免“鸡和蛋”的情况有关,但在你的情况下,我们会因此而出现一些不幸的行为。)

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

    因此,存在从 XFunc&lt;IEnumerable&lt;String&gt; 的转换,因此该重载是适用的候选对象。

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

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

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

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

    因此两者都不是更好,所以重载解析会报错。

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

    整个情况相当不幸;如果返回类型不匹配,最好说没有方法组到委托的转换。 (或者,产生错误的转换总是比不产生错误的转换更糟糕。)但是,我们现在坚持下去。

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

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

    【讨论】:

    • “如果返回类型不匹配,最好说没有方法组到委托的转换”:对我来说确实更好......我不认为更改该规则将是一项重大更改,对吗?我的意思是,据我所知,没有办法生成使用这种转换的工作代码(也许我没有足够努力,但我总是得到“XXX 的返回类型错误”)
    • @Thomas:将曾经是错误的事情变成成功案例可以将重载解决问题从只有一个解决方案变成有两个模棱两可的解决方案,现在将成功案例变成错误案例!令人惊讶的是,有多少事情在技术上是突破性的变化,但在这种情况下,我认为这可能是值得的。我会和 Mads 讨论的。
    • 引用:“重载解析总是忽略返回类型”和“lambdas 和方法组有不同的转换规则”——很不幸,但我认为这是一个很好的解释!
    【解决方案2】:

    您的代码需要“魔法”发生两次,一次是从命名方法组转换为委托,一次是执行重载决议。

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

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

    您的特殊情况看起来很简单,但一般情况非常困难,因此语言不允许。

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

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

    应该编译得很好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多