如果不包含歧义,为什么要添加一个方法会导致模棱两可的调用


112

我有这个课

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)!显然,模棱两可的两种方法都不是新添加的方法,因为第一种是参数,第二种是泛型。

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


2
我不认为这'Overloaded.ComplexOverloadResolution(string)'<string>(string)方法。我认为它是指(string, object)没有提供对象的方法。
phoog 2011年

1
@phoog哦,该数据被StackOverflow截断了,因为它是一个标记,但是错误消息具有模板指示符。我将其重新添加
McKay

你把我抓了!我在回答中引用了规范的相关部分,但我没有花最后半个小时来阅读和理解它们!
phoog 2011年

@phoog,在浏览规范的那些部分时,我看不到将歧义引入除自身和其他方法(而不是其他两个方法)之外的方法中。
麦凯

在我看来,这只是剪刀布:任何两个不同值的集合都有赢家,但三个值的完整集合却没有。
phoog 2012年

Answers:


107

这是一个错误吗?

是。

恭喜,您已找到重载解析中的错误。该错误在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编译器不仅会出错,而且还会报告奇怪的错误消息。编译器错误地进行了过载解析分析,这有点令人惊讶。弄错错误信息是完全不足为奇的。如果无法确定最佳方法,则“模糊方法”错误试探法基本上从候选集中选择任何两种方法。如果确实存在歧义,那不是很好。

人们可能会合理地问为什么。这是相当难找到2个方法是“unambigously暧昧”,因为“betterness”关系是不及物动词。可能会得出候选者1优于2、2优于3、3优于1的情况。在这种情况下,我们不能比选择其中两个作为“模棱两可的”更好。

我想为Roslyn改进这种启发式方法,但这并不是很重要。

(对读者来说,锻炼:“设计线性时间算法以识别n个元素之间的唯一性最佳关系是更好的关系是不传递的”,这是我在为该团队采访的那天提出的问题之一。一个非常困难的算法;试一试。)

我们之所以推迟向C#添加可选参数的原因之一,是它引入了重载解析算法的复杂歧义情况的数量。显然,我们做得不好。

如果您想输入Connect问题来跟踪它,请放心。如果您只希望它引起我们的注意,请考虑已完成。我明年将进行测试。

感谢您引起我的注意。对错误表示歉意。


1
感谢您的回复。您说“ 1比2差”,但是如果我只有方法1和2,它会选择方法1?
麦凯

@McKay:哎呀,你是对的,我说的很倒。我会修复文本。
埃里克·利珀特

1
考虑到还剩下半个星期,阅读“一年中的其余时间”一词感觉很尴尬:)
BoltClock

2
实际上,@ BoltClock声明“将在今年余下时间离开”意味着要休息一天。
phoog 2011年

1
我认同。我读“ 3)是具有前两个属性的唯一候选者”,因为“是(不败并击败至少一个其他候选者的)唯一候选者”。但是您最近的评论使我认为“(是唯一保持不败的候选人)并击败了至少一名其他候选人”。英语确实可以使用分组符号。如果后者是正确的,那么我会再次得到它。
default.kramer 2012年

5

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

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

特别要注意第7.5.3.2节(更好的函数成员),该节的部分内容为“从参数列表中删除了没有相应参数的可选参数”,而“如果M(p)是非泛型方法,则M(q)为如果使用通用方法,则M(p)优于M(q)。”

但是,我还没有完全理解规范的这些部分,以至于无法了解规范的哪些部分控制了这种行为,更不用说判断它是否符合要求了。


但这并不能解释为什么添加成员会导致已经存在的两种方法之间产生歧义。
麦凯

@McKay足够公平(请参阅编辑)。我们只需要等待Eric Lippert告诉我们这是否是正确的行为:->
phoog 2011年

1
这些是规范中正确的部分。问题是他们说不应该这样!
埃里克·利珀特

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

哦,我知道这段代码很糟糕,并且有多种解决方法,问题是“为什么编译器会以这种方式运行?”
McKay 2012年

1

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

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

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

“ ConsoleApplication1.Program.ComplexOverloadResolution(参数字符串[])”和“ ConsoleApplication1.Program.ComplexOverloadResolution(字符串,对象)”

Edit2:新发现。从上述三种方法中删除任何方法都不会在两者之间产生歧义。因此,似乎仅在存在三种方法时才会出现冲突,而与顺序无关。


但这并不能解释为什么添加成员会导致已经存在的两种方法之间产生歧义。
麦凯

您的第一种方法和第三种方法之间存在歧义,但是为什么编译器报告其他两种方法却不在我的范围之内。
Tomislav Markovski

但是,如果删除第二种方法,就不会产生歧义,它会成功调用第三种方法。因此,看起来编译器在第一方法和第三方法之间没有模棱两可的地方。
麦凯

请参阅我的编辑。疯狂的编译器。
Tomislav Markovski

实际上,只有两种方法的任何组合都不会产生歧义。这很奇怪。编辑2。
Tomislav Markovski

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.
    

首先,编写两个相同的调用案例。因此,它当然会采用相同的方法,还是您要写不同的东西?
麦凯

但是同样,如果我正确地理解了其余的答案,那么您就不会读到编译器所说的混淆,即第一和第二种方法之间的混淆,而不是我刚刚添加的新的第三种。
麦凯

嗯,谢谢。但这仍然留下我在对您的帖子的第二条评论中提到的问题。
麦凯

更清楚地说,您声明“编译器,在第一种方法和该方法之间完全混淆,仅添加了一个。” 但事实并非如此。它与其他两种方法混淆不清。params方法和通用方法。
麦凯

@McKay:确切地说,它给定state了3个功能,而不是单个或几个,令人困惑。实际上,对其中任何一个进行注释就足以解决问题。可用功能之间的最佳匹配是,其中一个带有params,第二个是带有generics参数的一个,当我们添加第三个时,它会混淆其中一个set函数。我认为,很可能是编译器产生的不清楚的错误消息。
提格伦
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.