您应该在C#4.0中声明使用重载还是可选参数的方法?

我正在观看Anders关于C#4.0的讨论以及C#5.0的预览 ,这让我想到了C#中可选参数何时可用,推荐的方法是声明不需要指定所有参数的方法。

比如像FileStream这样的类有大约15个不同的构造函数,它们可以被分成逻辑“家族”,比如下面的string, IntPtrSafeFileHandle

 FileStream(string,FileMode); FileStream(string,FileMode,FileAccess); FileStream(string,FileMode,FileAccess,FileShare); FileStream(string,FileMode,FileAccess,FileShare,int); FileStream(string,FileMode,FileAccess,FileShare,int,bool); 

在我看来,这种types的模式可以简化为具有三个构造函数,并使用可选的参数作为可缺省的参数,这将使不同的构造函数族更加清晰[注:我知道这个变化不会是在BCL制造的,我假设这种情况是在说话]。

你怎么看? 从C#4.0开始,将更紧密相关的构造函数和方法组合成一个具有可选参数的单一方法,还是有一个很好的理由来支持传统的多重过载机制?

我会考虑以下几点:

  • 你是否需要使用不支持可选参数的语言来使用你的代码? 如果是这样,考虑包括超载。
  • 你的团队中是否有强烈反对可选参数的成员? (有时用一个你不喜欢的决定来生活,比争辩这个事情更容易。)
  • 你有信心你的默认值不会在你的代码的构build之间改变,或者如果他们可能的话,你的调用者会好吗?

我没有检查默认是如何工作的,但我会假设默认值将被烘焙到调用代码中,非常像const字段的引用一样。 这通常是可以的 – 改变默认值是非常重要的 – 但这些都是要考虑的事情。

当一个方法重载通常用不同数量的参数执行相同的事情时,将使用默认值。

当方法重载根据其参数执行不同的function时,将继续使用重载。

我在VB6的时候使用了可选的function,因此错过了它,这将减less很多C#中的XML注释重复。

我一直在使用delphi,与可选参数,永远。 我已经转而使用重载。

因为当你去创build更多的重载时,你总会用一个可选参数的forms来expression; 然后你必须把它们转换成非可选的。

而且我喜欢这个概念,通常只有一个超级方法,其余的更简单的包装。

我一定会使用4.0的可选参数function。 它摆脱了荒谬的…

 public void M1( string foo, string bar ) { // do that thang } public void M1( string foo ) { M1( foo, "bar default" ); // I have always hated this line of code specifically } 

…并把值正确的地方,来电者可以看到他们…

 public void M1( string foo, string bar = "bar default" ) { // do that thang } 

更简单,更容易出错。 我已经看到这是一个在重载情况下的错误…

 public void M1( string foo ) { M2( foo, "bar default" ); // oops! I meant M1! } 

我还没有和4.0编译器一起玩过,但是我不会感到惊讶,因为编译器只是为你发出重载。

可选参数本质上是一段元数据,它指示正在处理方法调用的编译器在呼叫站点插入适当的默认值。 相比之下,重载提供了一种方法,编译器可以select其中一些方法,其中一些方法可能自己提供默认值。 请注意,如果试图调用一个方法来指定可选参数,而这些参数是用不支持它们的语言编写的,则编译器将要求指定“可选”参数,但是由于调用方法时没有指定可选参数相当于用一个等于缺省值的参数调用它,这样的语言调用这样的方法没有任何障碍。

在调用站点绑定可选参数的一个重要结果是,它们将根据编译器可用的目标代码的版本来分配值。 如果程序集Foo有一个默认值为5的方法Boo(int) ,并且程序集Bar包含对Foo.Boo()的调用,则编译器会将其处理为Foo.Boo(5) 。 如果将默认值更改为6,并重新编译程序集Foo ,则Bar将继续调用Foo.Boo(5)除非或直到与新版本的Foo重新编译为止。 因此,应避免使用可选参数来改变可能发生的事情。

我期待可选的参数,因为它保持默认值更接近的方法。 因此,对于刚刚调用“expanded”方法的重载而言,不要使用数十行代码,只需定义一次方法,即可在方法签名中查看可选参数的默认值。 我宁愿看看:

 public Rectangle (Point start = Point.Zero, int width, int height) { Start = start; Width = width; Height = height; } 

而不是这个:

 public Rectangle (Point start, int width, int height) { Start = start; Width = width; Height = height; } public Rectangle (int width, int height) : this (Point.Zero, width, height) { } 

很明显,这个例子非常简单,但是在OP有5个重载的情况下,事情会变得很快。

可以争论是否应该使用可选论证或超载,但最重要的是,每个都有自己不可替代的领域。

可选参数与命名参数结合使用时,与COM调用的一些长参数列表和所有可选参数结合使用时非常有用。

例如,当方法能够在许多不同的参数types(只是一个例子)上运行时,重载是非常有用的, 你只需要给它提供任何有意义的数据types(一些现有的超载就可以接受)。 不能用可选参数来打败。

我最喜欢的可选参数之一是,如果您不参考方法定义提供它们,您将看到参数会发生什么情况。 当你键入方法名时, Visual Studio将简单地向你显示参数的默认值 。 如果使用重载方法,则无论是读取(如果存在)文档,还是直接导航到方法定义(如果存在)以及重载被包装到哪个方法上,都会停滞不前。

特别是:文档支付可能会因重载的数量而迅速增加,您可能最终将从现有的重载中复制已有的评论。 这是非常烦人的,因为它没有产生任何价值,并打破了DRY原则 )。 另一方面,可选参数只有一个地方 ,所有的参数都被logging下来,你可以在input时看到它们的含义以及它们的默认值

最后但并非最不重要的,如果你是一个API的消费者,你可能不会看到实现的细节(因为你没有源代码),因此没有机会看到哪个超级方法重载的被包裹。 因此,你一直在阅读文档,并希望所有的默认值列在那里,但事实并非总是如此。

当然,这不是一个能够处理所有问题的答案,但是我认为这个答案还没有被覆盖到目前为止。

可选参数,方法超载都有自己的优势或劣势,这取决于你的偏好select它们之间。

可选参数:仅在.Net 4.0中可用。 可选参数减less您的代码大小。 你不能定义和ref参数

重载的方法:你可以定义Out和ref参数。 代码大小会增加,但重载的方法很容易理解。

虽然他们(据说?)有两种概念上相同的方法可供您从头开始对API进行build模,但不幸的是,当您需要考虑运行时对旧客户端的运行时向后兼容性时,它们会有一些细微的差别。 我的同事(感谢布伦特!)指出我这个奇妙的post:版本问题与可选参数 。 有人引用它:

首先将可选参数引入C#4的原因是为了支持COM互操作。 而已。 而现在,我们正在了解这个事实的全部影响。 如果你有一个带有可选参数的方法,那么你绝对不能使用额外的可选参数来添加一个重载,而不用担心导致编译时间的改变。 你永远不能删除现有的重载,因为这一直是一个运行时间的重大变化。 你几乎需要把它当作一个接口来对待。 在这种情况下,唯一的办法就是写一个新名字的新方法。 所以如果你打算在你的API中使用可选的参数,请注意这一点。

在许多情况下可选参数用于切换执行。 例如:

 decimal GetPrice(string productName, decimal discountPercentage = 0) { decimal basePrice = CalculateBasePrice(productName); if (discountPercentage > 0) return basePrice * (1 - discountPercentage / 100); else return basePrice; } 

此处的折扣参数用于提供if-then-else语句。 有没有被识别的多态性,然后它被实现为if-then-else语句。 在这种情况下,将两个控制stream分成两个独立的方法要好得多:

 decimal GetPrice(string productName) { decimal basePrice = CalculateBasePrice(productName); return basePrice; } decimal GetPrice(string productName, decimal discountPercentage) { if (discountPercentage <= 0) throw new ArgumentException(); decimal basePrice = GetPrice(productName); decimal discountedPrice = basePrice * (1 - discountPercentage / 100); return discountedPrice; } 

这样,我们甚至可以保护class级免于接打电话。 这个电话将意味着来电者认为有折扣,但实际上根本没有折扣。 这种误解很容易导致错误。

在这种情况下,我宁愿不使用可选参数,而是强制调用者明确select适合其当前情况的执行场景。

这种情况非常类似于可以为null的参数。 当实现沸腾到像if (x == null)这样的语句时,这也是一个坏主意。

您可以在这些链接中find详细的分析: 避免可选参数和避免空参数