为什么 C# 允许通过可选参数进行不明确的函数调用?

Why does C# allow ambiguous function calls through optional arguments?

我今天遇到了这个,我很惊讶我以前没有注意到它。给定一个类似于以下内容的简单 C# 程序:

public class Program
{
    public static void Main(string[] args)
    {
        Method(); // Called the method with no arguments.
        Method("a string"); // Called the method with a string.

        Console.ReadLine();
    }

    public static void Method()
    {
        Console.WriteLine("Called the method with no arguments.");
    }

    public static void Method(string aString = "a string")
    {
        Console.WriteLine("Called the method with a string.");
    }
}

您将获得每个方法调用的注释中显示的输出。

我理解为什么编译器会选择它所做的重载,但为什么首先允许这样做?我不是在问重载决议规则是什么,我理解这些规则,但我在问是否有技术原因导致编译器允许本质上具有相同签名的两个重载?

据我所知,一个函数重载的签名与另一个重载的不同之处仅在于具有一个额外的可选参数,与仅需要该参数(以及所有前面的参数)时所提供的一样。

它做的一件事是让程序员(他们可能没有足够注意)认为他们正在调用与实际不同的重载。

我想这是一个相当罕见的情况,为什么允许这样做的答案可能只是因为它根本不值得禁止它的复杂性,但是 C# 允许函数重载与其他函数重载完全不同的另一个原因通过增加一个可选参数?

此行为由 Mi​​crosoft 在 MSDN 上指定。看看 Named and Optional Arguments (C# Programming Guide).

If two candidates are judged to be equally good, preference goes to a candidate that does not have optional parameters for which arguments were omitted in the call. This is a consequence of a general preference in overload resolution for candidates that have fewer parameters.

他们决定以这种方式实现它的一个原因可能是你想在之后重载一个方法。因此,您不必更改所有已编写的方法调用。

更新

我很惊讶,Jon Skeet 也有 no real explantation 为什么他们这样做。

我认为这个问题基本上归结为中间语言如何表示这些签名。请注意,两个重载的签名 不等于 !第二种方法有这样的签名:

.method public hidebysig static void Method([opt] string aString) cil managed
{
    .param [1] = string('a string')
    // ...
}

在 IL 中,方法的签名不同。它需要一个字符串,该字符串被标记为可选。这会更改参数获取方式的初始化行为,但不会更改此参数的存在。

编译器无法决定您调用的是哪种方法,因此它会根据您提供的参数使用最适合的方法。由于您没有为第一次调用提供任何参数,因此它假定您正在调用不带任何参数的重载。

归根结底是一个关于好的代码设计的问题。根据经验,我要么使用可选参数,要么使用重载,具体取决于我想做什么:可选参数很好,如果方法中的逻辑不依赖于提供的参数,而重载则很好,可以提供不同的实现对于不同的参数集。如果您发现自己检查参数是否等于默认值以决定要做什么,您可能应该进行重载。另一方面,如果您发现自己在许多重载中重复大块代码,您应该尝试提取可选参数。

Chuck Skeet to this question也有很好的回答。

His point that Eric Lippert could have an answer lead me to this https://meta.whosebug.com/a/323382/1880663, which makes it sounds like my question will only annoy him. I'll try to rephrase it to make it clearer that I'm asking about the language design, and that I'm not looking for a spec reference

非常感谢!我很高兴谈论语言设计;令我烦恼的是,当提问者非常不清楚什么能真正满足他们的要求时,我却浪费时间这样做。我认为你的问题措辞清楚。


Hans 对您的问题发表的评论是正确的。语言设计团队非常清楚您提出的问题,这远非可选/命名参数造成的唯一潜在歧义。我们考虑了很长时间的许多场景,并尽可能仔细地设计了该功能以缓解潜在问题。

所有设计过程都是相互竞争的设计原则之间妥协的结果。显然,对于必须与重要的设计、实施和测试成本以及由于意外构造歧义(例如你指出的一个。

我不会重复几十个小时的辩论;让我给你重点。

正如 Hans 指出的那样,该功能的主要激励场景是大众需求,尤其是来自将 C# 与 Office 结合使用的开发人员的需求。 (并且完全公开,在我加入 C# 团队之前,作为为 Word 和 Excel 编写 C# 编程模型的团队成员,我实际上是第一个提出要求的人;具有讽刺意味的是,我随后不得不 实施 几年后这个困难的特性并没有让我迷失。)Office 对象模型被设计为从 Visual Basic 中使用,Visual Basic 是一种长期以来一直支持可选/命名参数的语言。

C# 4 在明显的特性方面可能看起来有点像 "thin" 版本。这是因为在该版本中完成的很多工作都是基础设施,以允许与为动态语言设计的对象模型实现更无缝的互操作性。动态类型功能是显而易见的,但还添加了许多其他小功能,这些小功能结合在一起使使用动态和遗留 COM 对象模型变得更加容易。命名/可选参数只是其中之一。

几十年来,我们拥有像 VB 这样具有此特定功能的现有语言,而且世界还没有结束,这一事实进一步证明了该功能既可行又有价值。在设计新版本的功能之前,有一个示例非常好,您可以从中吸取成功和失败的教训。

至于你提到的具体情况:我们考虑过做一些事情,比如检测何时可能存在歧义并发出警告,但这又打开了另一个蠕虫罐头。警告必须针对常见、合理且几乎肯定是错误的代码,并且应该有明确的方法来解决导致警告消失的问题。编写歧义检测器需要大量工作;相信我,在重载解决方案中编写歧义检测比编写处理成功案例的代码花费的时间要长得多。我们不想花很多时间为难以检测的罕见情况添加警告,并且可能没有关于如何消除警告的明确建议。

此外,坦率地说,如果您编写的代码中有两个名称相同的方法,根据您调用的方法执行完全不同的操作,那么您手上已经有更大的设计问题了!先解决那个问题,而不是担心有人会不小心调用错误的方法;使任何一种方法都适合调用。