为什么C#允许通过可选参数进行模糊函数调用?

我今天遇到了这个,我很惊讶我之前没有注意到它。 给出一个简单的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#允许函数重载与其他函数不同仅仅通过一个额外的可选项争论?

他认为Eric Lippert可以得到答案的观点引导我进入这个https://meta.stackoverflow.com/a/323382/1880663 ,这听起来好像我的问题只会让他烦恼。 我会尝试对其进行重新设计,以便让我更清楚我在询问语言设计,并且我不是在寻找规范参考

我很感激! 我很高兴谈论语言设计; 令我恼火的是,当提问者非常不清楚实际满足他们的要求时,我会浪费时间。 我认为你的问题清楚明了。


汉斯发表的对你的问题的评论是正确的。 语言设计团队非常了解您提出的问题,这远远不是可选/命名参数创建的唯一潜在歧义。 我们长期考虑很多场景,并尽可能仔细地设计该function以减轻潜在问题。

所有设计过程都是竞争设计原则之间妥协的结果。 显然,有许多论点需要与重要的设计,实现和测试成本以及混淆,错误等forms的用户成本,以及意外构造模糊性(如你指出一个。

我不打算重复几十个小时的辩论; 让我给你高分。

正如Hans所说,该function的主要推动方案是受欢迎的需求,特别是来自使用C#with Office的开发人员。 (并且完全披露,作为在我加入C#团队之前为Word和Excel编写C#编程模型的团队中的一员,我确实是第一个要求它的人;具有讽刺意味的是,我必须实现这个困难的function几年后我并没有丢失。)Office对象模型被设计为从Visual Basic中使用,Visual Basic是一种长期具有可选/命名参数支持的语言。

就明显的function而言,C#4可能看起来像是一个“瘦”的版本。 这是因为该版本中的许多工作都是基础设施,可以与为动态语言设计的对象模型实现更加无缝的互操作性。 动态类型function是显而易见的,但是添加了许多其他小function,它们结合在一起使得动态和遗留COM对象模型的使用变得更加容易。 命名/可选参数只是其中之一。

事实上,我们现有的语言如VB具有这种特定function已有数十年而且世界尚未结束,这进一步certificate该function既可行又有价值。 在设计新版本的function之前,您可以从成功和失败中学习,这是一个很好的例子。

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

而且,坦率地说,如果你编写的代码中你有两个方法命名相同的东西,根据你调用的那个做一些完全不同的东西,你的手上已经有了更大的设计问题! 首先解决这个问题,而不是担心有人会不小心打错方法; 使它成为任何一种方法都是正确的方法。

此行为由Microsoft在MSDN上指定。 查看Named和Optional Arguments(C#编程指南) 。

如果判断两个候选者同样好,则优先选择没有可选参数的候选者,该参数在调用中被省略。 这是对具有较少参数的候选者的重载分辨率的一般偏好的结果。

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

UPDATE

我很惊讶,Jon Skeet也没有真正的解释为什么他们这样做。

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

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

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

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

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

对于这个问题 , Chuck Skeet也有一个很好的答案。