为什么添加方法会添加不明确的调用,如果它不会涉及模糊性

时间:2011-12-29 23:03:51

标签: c# overload-resolution

我有这个班级

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");
    }

我收到以下错误:

  

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

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

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

5 个答案:

答案 0 :(得分:105)

  

这是一个错误吗?

是。

恭喜,您发现重载解析存在错误。该错误在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节中描述的高级tiebreaking轮次。

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

哪个更好,1还是3?相关的决胜局是:仅适用于其扩展形式的方法总是比适用于其正常形式的方法更糟糕。因此1比3差。所以1不能成为胜利者。

哪个更好,2个还是3个?相关的决胜局是泛型方法总是比非泛型方法更糟糕。 2比3差。所以2不能成为胜利者。

要从一组多个适用候选者中选择,候选者必须(1)不败,(2)击败至少一个其他候选者,以及(3)是具有前两个属性的唯一候选者。候选人三人被其他候选人击败,至少击败另一名候选人;它是唯一具有此属性的候选人。因此候选人三是 unique best candidate 。它应该赢。

不仅C#4编译器出错了,因为您正确地注意到它报告了一个奇怪的错误消息。编译器错误地重载分辨率分析有点令人惊讶。它错误消息的错误是完全不足为奇的;如果无法确定最佳方法,“模糊方法”错误启发式基本上从候选集中选择任意两种方法。如果事实上存在一个模糊性,那么找到“真正的”歧义并不是很好。

有人可能会合理地问为什么会这样。找到两个两种方法是非常棘手的,因为“更好”关系是不及物。有可能提出候选1优于2,2优于3,3优于1的情况。在这种情况下,我们不能比将其中两个选为“模糊的”更好。

我想改进Roslyn的这种启发式方法,但这是一个低优先级。

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

我们推迟向C#添加可选参数的原因之一是它在重载决策算法中引入的复杂模糊情况的数量;显然我们做得不对。

如果您想输入Connect问题进行跟踪,请随意。如果你只是想让它引起我们的注意,那就考虑一下吧。我会在明年跟进测试。

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

答案 1 :(得分:5)

  

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

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

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

但是,我并不完全了解规范的这些部分,足以知道规范的哪些部分控制了这种行为,更不用说判断它是否合规了。

答案 2 :(得分: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");
    }
}

答案 3 :(得分:1)

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

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

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

  

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

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

答案 4 :(得分: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.