113

我有这门课

public class Overloaded
{
    public void ComplexOverloadResolution(params string[] something)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }
}

如果我这样称呼它:

        var blah = new Overloaded();
        blah.ComplexOverloadResolution("Which wins?");

它写入Normal Winner控制台。

但是,如果我添加另一种方法:

    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }

我收到以下错误:

以下方法或属性之间的调用不明确: > ' Overloaded.ComplexOverloadResolution(params string[])' 和 ' Overloaded.ComplexOverloadResolution<string>(string)'

我可以理解添加方法可能会引入调用歧义,但这是已经存在的两种方法之间的歧义,(params string[])并且<string>(string)!显然,歧义中涉及的两个方法都不是新添加的方法,因为第一个是 params,第二个是泛型。

这是一个错误吗?规范的哪一部分说应该是这种情况?

4

5 回答 5

108

这是一个错误吗?

是的。

恭喜,您在重载解析中发现了一个错误。该错误在 C# 4 和 5 中重现;它不会在语义分析器的“Roslyn”版本中重现。我已经通知了 C# 5 测试团队,希望我们能够在最终版本发布之前对此进行调查和解决。(一如既往,没有承诺。)

下面是正确的分析。候选人是:

0: C(params string[]) in its normal form
1: C(params string[]) in its expanded form
2: C<string>(string) 
3: C(string, object) 

候选零显然不适用,因为string不能转换为string[]. 剩下三个。

在这三者中,我们必须确定一个独特的最佳方法。我们通过对剩下的三个候选者进行成对比较来做到这一点。有三对这样的对。一旦我们去掉省略的可选参数,它们都有相同的参数列表,这意味着我们必须进入规范第 7.5.3.2 节中描述的高级决胜回合。

哪个更好,1还是2?相关的决胜局是泛型方法总是比非泛型方法差。2 比 1 差。所以 2 不可能是赢家。

1 还是 3 哪个更好?相关的决胜局是:仅适用于其扩展形式的方法总是比适用于其正常形式的方法差。因此 1 比 3 差。所以 1 不可能是赢家。

2还是3哪个更好?相关的决胜局是泛型方法总是比非泛型方法差。2 比 3 差。所以 2 不可能是赢家。

要从一组多个适用候选人中选出候选人,必须 (1) 不败,(2) 击败至少一个其他候选人,以及 (3) 是具有前两个属性的唯一候选人。候选人三没有被其他候选人击败,并且至少击败了一名其他候选人;它是唯一拥有此属性的候选人。所以候选人三是唯一的最佳人选。它应该赢。

不仅 C# 4 编译器出错了,正如您正确注意到的那样,它报告了一个奇怪的错误消息。编译器错误的重载解析分析有点令人惊讶。它得到错误消息是完全不足为奇的。如果无法确定最佳方法,“模糊方法”错误启发式基本上会从候选集中选择任何两种方法。它不太善于发现“真正的”歧义,如果实际上有的话。

有人可能会合理地问为什么会这样。找到两种“明确模棱两可”的方法是相当棘手的,因为“更好”关系是不及物的。可能会出现候选 1 优于 2、2 优于 3、3 优于 1 的情况。在这种情况下,我们最好选择其中两个作为“模棱两可的”。

我想为 Roslyn 改进这种启发式方法,但它的优先级较低。

(给读者的练习:“设计一个线性时间算法来识别一组 n 个元素中的唯一最佳成员,其中更好的关系是不及物的”是我在为这个团队采访的那天被问到的问题之一。它不是一个非常难的算法;试一试。)

长期以来,我们一直拒绝向 C# 添加可选参数的原因之一是它在重载解析算法中引入了许多复杂的模棱两可的情况。显然我们没有做对。

如果您想输入 Connect 问题来跟踪它,请随意。如果您只是想引起我们的注意,请考虑完成。明年我会继续测试。

感谢您引起我的注意。为错误道歉。

于 2011-12-29T23:45:55.490 回答
5

规范的哪一部分说应该是这种情况?

第 7.5.3 节(重载解析),以及第 7.4 节(成员查找)和第 7.5.2 节(类型推断)。

特别注意第 7.5.3.2 节(更好的函数成员),其中部分说“从参数列表中删除没有相应参数的可选参数”和“如果 M(p) 是非泛型方法,则 M(q) 是一个通用的方法,那么 M(p) 比 M(q) 好。”

但是,我对规范的这些部分了解得不够透彻,无法知道规范的哪些部分控制了这种行为,更不用说判断它是否合规了。

于 2011-12-29T23:15:34.727 回答
3

您可以通过更改某些方法中的第一个参数的名称并指定要分配的参数来避免这种歧义

像这样 :

public class Overloaded
{
    public void ComplexOverloadResolution(params string[] somethings)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }

    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Overloaded a = new Overloaded();
        a.ComplexOverloadResolution(something:"asd");
    }
}
于 2012-01-04T18:39:46.437 回答
1
  1. 如果你写

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");
    

    或者只是写

    var blah = new Overloaded();
    blah.ComplexOverloadResolution();
    

    它将以相同的方法结束,在方法中

    public void ComplexOverloadResolution(params string[] something
    

    这是原因params关键字,它使得它在没有指定参数的情况下也最匹配

  2. 如果您尝试像这样添加您的新方法

    public void ComplexOverloadResolution(string something)
    {
        Console.WriteLine("Added Later");
    }
    

    它将完美地编译和调用此方法,因为它与您的带参数调用完美匹配。string那就强多了 params string[] something

  3. 你像你一样声明第二种方法

    public void ComplexOverloadResolution(string something, object something=null);
    

    编译器,在第一种方法和这个方法之间完全混淆,只是添加了一个。因为它不知道他现在应该在你的电话中使用哪个功能

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");
    

    事实上,如果你从调用中删除字符串参数,就像下面的代码一样,一切都会正确编译并像以前一样工作

    var blah = new Overloaded();
    blah.ComplexOverloadResolution(); // will be ComplexOverloadResolution(params string[] something) function called here, like a best match.
    
于 2011-12-29T23:20:00.260 回答
1

如果您params从第一种方法中删除 ,则不会发生这种情况。你的第一种和第三种方法都有有效的调用ComplexOverloadResolution(string),但如果你的第一种方法是public void ComplexOverloadResolution(string[] something)没有歧义的。

为参数提供值object somethingElse = null使其成为可选参数,因此在调用该重载时不必指定它。

编辑:编译器在这里做了一些疯狂的事情。如果您在第一个方法之后在代码中移动您的第三个方法,它将正确报告。因此,它似乎正在接受前两个重载并报告它们,而不检查正确的重载。

'ConsoleApplication1.Program.ComplexOverloadResolution(params string[])' 和 'ConsoleApplication1.Program.ComplexOverloadResolution(string, object)'

编辑2:新发现。从上述三个中删除任何方法都不会在两者之间产生歧义。因此,无论顺序如何,似乎只有存在三种方法时才会出现冲突。

于 2011-12-29T23:13:06.343 回答