为啥 C# 允许通过可选参数进行模棱两可的函数调用?

Posted

技术标签:

【中文标题】为啥 C# 允许通过可选参数进行模棱两可的函数调用?【英文标题】:Why does C# allow ambiguous function calls through optional arguments?为什么 C# 允许通过可选参数进行模棱两可的函数调用? 【发布时间】:2016-10-20 01:12:09 【问题描述】:

我今天遇到了这个,我很惊讶我以前没有注意到它。给定一个类似于以下的简单 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.");
    

您会获得每个方法调用的 cmets 中显示的输出。

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

据我所知,具有签名的函数重载与另一个重载的不同之处仅在于具有一个额外的可选参数>

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

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

【问题讨论】:

C# 团队尽可能地拖延了这个时间。但是他们屈服于第 4 版以满足大众的需求,尤其是 Office 编程如果没有它就太讨厌了。只需设置您自己的编码标准即可避免该功能。 Conflicting overloaded methods with optional parameters的可能重复 【参考方案1】:

他认为 Eric Lippert 可以得到答案的观点将我引向了这个 https://meta.***.com/a/323382/1880663,这听起来像是我的问题只会惹恼他。我将尝试改写它以更清楚地表明我在询问语言设计,并且我不是在寻找规范参考

我很感激!我很高兴谈论语言设计;让我烦恼的是,当提问者不清楚什么会真正满足他们的要求时,我浪费时间这样做。我认为你的问题表述得很清楚。


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

所有设计过程都是相互竞争的设计原则之间妥协的结果。显然,该功能有许多论据,必须与重大的设计、实施和测试成本以及用户因混淆、错误等形式的成本相平衡你指出的。

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

正如 Hans 指出的那样,该功能的主要动机是受欢迎的需求,尤其是来自将 C# 与 Office 结合使用的开发人员。 (并且完全披露,作为在我加入 C# 团队之前为 Word 和 Excel 编写 C# 编程模型的团队中的一个人,我确实是第一个要求它的人;具有讽刺意味的是,我随后必须实施 几年后,我并没有忘记这个困难的功能。)Office 对象模型被设计为在 Visual Basic 中使用,Visual Basic 是一种长期以来支持可选/命名参数的语言。

就明显的特性而言,C# 4 可能看起来有点“瘦”的版本。这是因为在该版本中完成的许多工作都是基础架构,以允许与为动态语言设计的对象模型实现更无缝的互操作性。动态类型功能是显而易见的,但还添加了许多其他小功能,它们结合在一起可以更轻松地处理动态和遗留 COM 对象模型。命名/可选参数只是其中之一。

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

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

另外,坦率地说,如果您编写代码时有两个命名相同的方法,它们会根据您调用的方法执行完全不同的操作,那么您已经面临更大的设计问题!先解决这个问题,而不是担心有人会不小心调用错误的方法;使任何一种方法都是正确的调用方法。

【讨论】:

那肯定回答了,谢谢。至于你的最后一段,在我偶然发现这个之后,我很快发现使用默认参数对我来说并不是最好的——尽管这不是因为这两个函数有完全不同的实现。我有两个函数做 95% 相同的事情,但参数不同,所以我想私下组合实现并保留两个单独的公共签名。我会为私有方法使用默认参数,但由于重载决议规则,这将迫使其中一个公共方法递归调用自身。 我最终制作了私有方法所需的所有参数,为未使用的参数传递了 null。它仍然感觉不对,但我会睡在上面......【参考方案2】:

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

如果两个候选者被判断为同样好,则优先考虑没有可选参数的候选者,在调用中省略了这些参数。这是由于参数较少的候选者普遍偏好重载解决方案的结果。

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

更新

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

【讨论】:

我看过那个页面,就像我说的我理解为什么以它们的方式选择重载,但我仍然想知道为什么围绕可选参数的规则允许在第一名。也许这对 Stack Overflow 来说不是一个好问题,因为我本质上是在问为什么要做出语言设计决定。 您对 John Skeet 评论的更新很有趣,在问我的问题之前我没有发现这一点,但它确实直接解决了我的问题。他认为 Eric Lippert 可以得到答案的观点将我引向了这个meta.***.com/a/323382/1880663,这听起来我的问题只会惹恼他。我将尝试改写它以更清楚地表明我在询问语言设计,并且我不是在寻找规范参考。【参考方案3】:

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

.method public hidebysig static void Method([opt] string aString) cil managed

    .param [1] = string('a string')
    // ...

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

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

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

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

【讨论】:

我认为您对它如何编译为 CIL 的解释是一个很好的观点。如果带有可选参数的方法被编译成两个方法(一个有参数,一个没有),那么我肯定会出现编译器错误,因为你最终会得到两个具有相同签名的重载。但是正如您所解释的那样,这不是发生的事情,因此尽管您实际上不能使用可选参数调用该方法没有提供该可选参数(除非通过反射),但在CIL,所以我想没有根本的理由不允许我的例子。

以上是关于为啥 C# 允许通过可选参数进行模棱两可的函数调用?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这些重载的函数调用模棱两可?

C#基础 可选参数调用params无参静态构造函数

C#4.0中的更改——命名参数和可选参数

为啥python不允许通过引用传递变量

C# void函数传入集合参数排序后没效果为啥

通过反射调用带有可选参数的方法