为什么添加一个方法会增加一个不明确的调用,如果它不会涉及歧义

本文关键字:一个 如果 歧义 增加 方法 添加 为什么 不明确 调用 | 更新日期: 2023-09-27 18:19:07

我有这个类

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)已经存在的两个方法之间的歧义!显然,歧义中涉及的两种方法都不是新添加的方法,因为第一种方法是参数,第二种是泛型。

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

为什么添加一个方法会增加一个不明确的调用,如果它不会涉及歧义

这是一个错误吗?

是的。

恭喜,您在重载解析中发现了一个错误。该错误在 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# 添加可选参数的原因之一是它在重载解析算法中引入了许多复杂的模糊情况;显然我们没有做对。

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

感谢您提请我注意这一点。对错误表示歉意。

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

第 7.5.3 节(

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

特别要注意第 7.5.3.2 节(更好的函数成员(,其中部分说"没有相应参数的可选参数将从参数列表中删除"和"如果 M(p( 是非泛型方法 amd M(q( 是泛型方法,则 M(p( 优于 M(q(。

但是,我对规范的

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

您可以通过

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

喜欢这个:

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

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

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

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

'ConsoleApplication1.Program.ComplexOverloadResolution(params string[](' 和 '控制台应用程序1.程序.复杂过载分辨率(字符串,对象(">

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

  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.
    
相关文章: