哪一个更好?乍一看,可选参数似乎更好(更少的代码、更少的 XML 文档等),但为什么大多数 MSDN 库类使用重载而不是可选参数?
当您选择使用可选参数(或重载)时,有什么特别需要注意的吗?
哪一个更好?乍一看,可选参数似乎更好(更少的代码、更少的 XML 文档等),但为什么大多数 MSDN 库类使用重载而不是可选参数?
当您选择使用可选参数(或重载)时,有什么特别需要注意的吗?
在 C# 4.0 中将“可选参数”与“命名参数”结合使用的一个很好的用例是,它为我们提供了一种优雅的方法重载替代方案,您可以根据参数的数量重载方法。
例如,假设您希望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 个或更多方法几乎是多余的。我发现这是将可选参数与命名参数结合使用的一个很好的用例。
当您将可选参数公开为 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
当具有默认参数的构造函数不能很好地与 Reflection配合使用时,这里有一些额外的阅读材料。
我相信它们有不同的用途。可选参数用于何时可以使用参数的默认值,并且底层代码将相同:
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) {
...
}
(我前段时间在这里写过这个)
这几乎是不言而喻的,但是:
并非所有语言都支持可选参数。如果您希望您的库对这些语言友好,则必须使用重载。
当然,对于大多数商店来说,这甚至都不是问题。但是你可以打赌这就是为什么微软不在基类库中使用可选参数的原因。
两者都不是绝对“更好”的。他们在编写好的代码方面都有自己的位置。如果参数可以具有默认值,则应使用可选参数。当签名的差异超出未定义可能具有默认值的参数时(例如行为不同取决于传递的参数以及保留默认值的参数),应使用方法重载。
// 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)
{
...
}
可选参数必须在最后。因此,除非它也是可选的,否则您不能向该方法添加额外的参数。前任:
void MyMethod(int value, int otherValue = 0);
如果你想在不重载的情况下向这个方法添加一个新参数,它必须是可选的。像这样
void MyMethod(int value, int otherValue = 0, int newParam = 0);
如果它不能是可选的,那么您必须使用重载并删除“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”只有一种实现,因此只有一种方法调用。
使用可选参数的好地方是WCF,因为它不支持方法重载。
第三个选项怎么样:传递一个类的实例,其属性对应于各种“可选参数”。
这提供了与命名参数和可选参数相同的好处,但我觉得这通常更清楚。它使您有机会在必要时对参数进行逻辑分组(即组合)并封装一些基本验证。
此外,如果您希望使用您的方法的客户端执行任何类型的元编程(例如构建涉及您的方法的 linq 表达式),我认为保持方法签名简单有其优势。
这并不是对原始问题的真正答案,而是对@NileshGule答案的评论,但是:
a) 我没有足够的声望点来发表评论
b) 多行代码在注释中很难阅读
Nilesh Gule 写道:
使用可选参数的一个好处是,如果输入参数之一是字符串,则不必在方法中进行条件检查,例如字符串是否为空或为空。由于可选参数会有一个默认值,因此防御性编码将大大减少。
这实际上是不正确的,您仍然必须检查空值:
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
如果您提供空字符串引用,它不会被默认值替换。所以你仍然需要检查输入参数的空值。
为了解决您的第一个问题,
为什么大多数 MSDN 库类使用重载而不是可选参数?
这是为了向后兼容。
当您在 VS2010 中打开 C# 2、3.0 或 3.5 项目时,它会自动升级。
试想一下,如果项目中使用的每个重载都必须转换为匹配相应的可选参数声明,将会带来怎样的不便。
此外,俗话说“未破何必修”。没有必要替换已经与新实现一起使用的重载。
使用可选参数的一个好处是,如果输入参数之一是字符串,则不必在方法中进行条件检查,例如字符串是否为空或为空。由于可选参数会有一个默认值,因此防御性编码将大大减少。
命名参数提供了以任何顺序传递参数值的灵活性。