C# 4.0中的方法重载和可选参数:区别与应用

127

哪个更好?乍一看,可选参数似乎更好(代码更少,XML文档也更少等),但为什么大多数MSDN库类使用重载而不是可选参数?

当你选择使用可选参数(或重载)时,有什么特别的事情需要注意吗?


是的,我知道那个问题,但它是在C# 4.0发布并普及之前提出的。而且我还有更多的问题(请参见问题详情)。 - Louis Rhys
1
"为什么大多数MSDN库类使用可选参数而不是重载?" - 你是不是想说反了?我不知道有任何BCL类使用可选参数。" - Joe White
抱歉,我是指反过来。抱歉,已编辑。 - Louis Rhys
11个回答

84

在C# 4.0中,“Optional parameters”与“Named Parameters”结合使用的一个好用例是,它为我们提供了一种优雅的方法重载替代方案,而不是基于参数数量进行方法重载。

例如说你想要一个名为foo的方法可以这样被调用和使用:foo()foo(1)foo(1,2)foo(1,2, "hello")。使用方法重载,你需要实现以下代码:

///Base foo method
public void DoFoo(int a, long b, string c)
{
   //Do something
}  

/// Foo with 2 params only
public void DoFoo(int a, long b)
{
    /// ....
    DoFoo(a, b, "Hello");
}

public void DoFoo(int a)
{
    ///....
    DoFoo(a, 23, "Hello");
}

.....

在C# 4.0中使用可选参数,您可以按照以下方式实现用例:

public void DoFoo(int a = 10, long b = 23, string c = "Hello")

那么您可以像这样使用该方法-请注意使用了命名参数-

DoFoo(c:"Hello There, John Doe")

这个调用需要将参数a的值设置为10,参数b的值设置为23。 此调用的另一种变体 - 注意您不需要按照方法签名中的顺序设置参数值,命名参数使值明确。

DoFoo(c:"hello again", a:100) 

使用命名参数的另一个好处是极大地提高了可选参数方法的可读性,从而增强了代码的可维护性。

请注意,一种方法基本上使得定义3个或更多方法在方法重载中变得多余。我发现这是使用可选参数与命名参数结合使用的一个好用例。


5
准备使用这种方法的所有人请注意:如果您在调用方中使用了命名参数,则无法在之后更改参数名称,除非在每个调用方中进行更改(如果所有引用都在同一个解决方案中,则是简单的重构,但如果您将其用作其他项目中的库,则不是)。 话虽如此,即使没有使用命名参数,更改参数名称始终被认为是一种破坏性变化。 - Shishir Gupta

63

可选参数在将其作为API公开时会产生问题。重命名参数可能会导致问题。更改默认值也会导致问题(例如,有关一些信息,请参见此处:C# 4.0可选参数的注意事项)。

另外,可选参数只能用于编译时常量。比较以下两者:

public static void Foo(IEnumerable<string> items = new List<string>()) {}
// Default parameter value for 'items' must be a compile-time constant

变成这样

public static void Foo() { Foo(new List<string>());}
public static void Foo(IEnumerable<string> items) {}
//all good

更新

当一个带有默认参数的构造函数与反射不兼容时,这里提供了一些附加阅读材料:does not play nicely with Reflection


6
为公共API注入默认值显然是个问题,如果我们能获得一种可选参数的变体,该变体实际上是为创建重载提供语法糖的话,那就太好了(同时仍保留现有的内容,例如用于COM互操作)。 - Thomas S. Trias
2
关于重命名参数,无论使用可选参数或重载方法都是一个问题。消费者可以指定一个命名参数,如果重命名变量将会破坏编译。 - mrmillsy
链接又坏了...使用非测试版的Wayback Machine链接就可以了。https://web.archive.org/web/20140815160502/http://blog.coverity.com/2013/09/11/c-bug/#.WqvD6-jwaUk - Crob

53

我认为它们有不同的用途。可选参数用于当您可以为参数使用默认值时,底层代码将相同:

public CreditScore CheckCredit( 
  bool useHistoricalData = false,  
  bool useStrongHeuristics = true) { 
  // ... 
}

方法重载是用于具有互斥(子集)参数的情况。这通常意味着您需要预处理一些参数,或者对于不同的方法“版本”,您需要完全不同的代码(请注意,即使在这种情况下,某些参数也可以共享,这就是我上面提到“子集”的原因):

public void SendSurvey(IList<Customer> customers, int surveyKey) {  
  // will loop and call the other one 
} 
public void SendSurvey(Customer customer, int surveyKey) {  
  ...  
}

(我之前写过一篇文章,关于这个问题,在这里


17
对我来说,这是最好的答案。你是唯一一个区分方法体作用的人。如果方法体相同,则使用可选参数非常合理,但如果方法体基于参数而有所不同,则使用重载。 - Chris Pratt
1
完美的解释。谢谢。 - Saurabh Rana

31

这个几乎是不言自明的,但是:

并非所有语言都支持可选参数。如果您希望您的库对这些语言友好,您需要使用重载。

诚然,这对于大多数公司来说甚至都不是问题。但你可以打赌这就是微软在基类库中没有使用可选参数的原因。


2
如果你在不支持可选参数的语言(例如C# 2.0)中使用带有可选参数的库,会发生什么? - Louis Rhys
10
C# 2.0忽略了“可选参数”标志,并将所有参数视为必需。因此,每个调用站点都必须为每个参数提供一个值。 - Joe White

11

两者并没有明确的“更好”之分,它们在编写良好代码方面都有自己的用处。如果参数可以具有默认值,则应使用可选参数。当签名的差异超出未定义可能具有默认值的参数时(例如行为因传递哪些参数以及将哪些参数留给默认值而不同),应该使用方法重载。

// this is a good candidate for optional parameters
public void DoSomething(int requiredThing, int nextThing = 12, int lastThing = 0)

// this is not, because it should be one or the other, but not both
public void DoSomething(Stream streamData = null, string stringData = null)

// these are good candidates for overloading
public void DoSomething(Stream data)
public void DoSomething(string data)

// these are no longer good candidates for overloading
public void DoSomething(int firstThing)
{
    DoSomething(firstThing, 12);
}
public void DoSomething(int firstThing, int nextThing)
{
    DoSomething(firstThing, nextThing, 0);
}
public void DoSomething(int firstThing, int nextThing, int lastThing)
{
    ...
}

9

可选参数必须放在最后。因此,除非该参数也是可选的,否则您不能向该方法添加额外的参数。例如:

void MyMethod(int value, int otherValue = 0);

如果您想向此方法添加新参数而无需重载它,则必须将其设置为可选参数。像这样:
void MyMethod(int value, int otherValue = 0, int newParam = 0);

如果不能使用可选值,那么您需要使用重载并移除“otherValue”的可选值。像这样:

如果无法选择,则必须使用重载并删除“otherValue”的可选值。如下所示:

void MyMethod(int value, int otherValue = 0);
void MyMethod(int value, int otherValue, int newParam);

我猜你想保持参数的顺序不变。
因此,使用可选参数可以减少类中方法的数量,但是限制在于它们需要放在最后。 更新 当调用带有可选参数的方法时,可以像这样使用命名参数:
void MyMethod(int value, int otherValue = 0, int newValue = 0);

MyMethod(10, newValue: 10); // Here I omitted the otherValue parameter that defaults to 0

因此,可选参数为调用者提供了更多的可能性。

最后一件事。如果您使用一个实现的方法重载,就像这样:

void MyMethod(int value, int otherValue)
{
   // Do the work
}

void MyMethod(int value)
{
   MyMethod(value, 0); // Do the defaulting by method overloading
}

然后当像这样调用'MyMethod':

MyMethod(100); 

将会导致2个方法调用。但是,如果您使用可选参数,则只有一个'MyMethod'的实现,因此只有一个方法调用。


这是一个重要的考虑吗? - Louis Rhys
越少的代码越好。代码越少,错误风险越小,可读性越高。 - Martin Ingvar Kofoed Jensen
你可以在使用可选参数时结合命名参数,以避免不得不在代码末尾定义可选参数。请参阅我的下面的帖子。 - Bikal Lem
是的,我同意。我也已经在我的帖子中添加了这个。 - Martin Ingvar Kofoed Jensen

4
有第三种选择:传递一个实例化的类,该类的属性对应于各种“可选参数”。
这提供了与命名参数和可选参数相同的好处,但我认为这通常更清晰。如果需要(即通过组合),它可以使您有机会逻辑地分组参数并封装一些基本验证。
此外,如果您希望使用您的方法的客户端进行任何形式的元编程(例如构建涉及您的方法的LINQ表达式),我认为保持方法签名简单具有优势。

有人给你踩了,可能是因为你没有回答问题。但我认为这个观察仍然很有用,所以点个赞。将参数分组到对象中是一种重构,但并不经常执行。 - Michael Parker

4
一种适合使用可选参数的好地方是 WCF ,因为它不支持方法重载。

3

这并不是对原问题的回答,而是对 @NileshGule 的 回答 的评论:

a) 我没有足够的声望来发表评论

b) 在评论中阅读多行代码很困难

Nilesh Gule 写道:

使用可选参数的好处之一是,如果其中一个输入参数是字符串,您无需在方法中进行条件检查,例如字符串是否为 null 或为空。由于可选参数将分配默认值,因此防御性编程将大大减少。

实际上,这是不正确的,您仍然需要检查空值:

void DoSomething(string value = "") // Unfortunately string.Empty is not a compile-time constant and cannot be used as default value
{
  if(value == null)
    throw new ArgumentNullException();
}

DoSomething(); // OK, will use default value of ""
DoSomething(null); // Will throw

如果您提供了一个空的字符串引用,它将不会被默认值替换。因此,您仍然需要检查输入参数是否为空。

2
为了回答你的第一个问题:为什么大多数MSDN库类使用重载,而不是可选参数?这是为了向后兼容。当你在VS2010中打开C#2、3.0或3.5项目时,它会自动升级。想象一下,如果每个在项目中使用的重载都必须转换为匹配相应可选参数声明,那会带来多么不便。此外,正如俗话说的那样,“如果没坏,为什么要修理呢?”没有必要用新实现替换已经工作的重载。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接