死灵术。
带有可选参数的事情是,它们是BAD,因为它们不直观 - 这意味着它们不会按照您的预期行事。
原因如下:
他们打破了ABI 兼容性!
(严格来说,它们在构造函数中使用时也会破坏API-兼容性)
例如:
你有一个 DLL,其中有这样的代码
public void Foo(string a = "dog", string b = "cat", string c = "mouse")
{
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
}
现在发生的情况是,您希望编译器在幕后生成这段代码:
public void Foo(string a, string b, string c)
{
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
}
public void Foo(string a, string b)
{
Foo(a, b, "mouse");
}
public void Foo(string a)
{
Foo(a, "cat", "mouse");
}
public void Foo()
{
Foo("dog", "cat", "mouse");
}
或者更现实地说,你会期望它传递 NULL 并这样做
public void Foo(string a, string b, string c)
{
if(a == null) a = "dog";
if(b == null) b = "cat";
if(c == null) c = "mouse";
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
}
因此您可以在一处更改默认参数。
但这不是 C# 编译器所做的,因为你不能这样做:
Foo(a:"dog", c:"dogfood");
因此,C# 编译器会这样做:
你写作的任何地方,例如
Foo(a:"dog", c:"mouse");
or Foo(a:"dog");
or Foo(a:"dog", b:"bla");
用
代替
Foo(your_value_for_a_or_default, your_value_for_b_or_default, your_value_for_c_or_default);
这意味着如果您添加另一个默认值、更改默认值、删除一个值,您不会破坏 API 兼容性,但会破坏 ABI 兼容性。
这意味着,如果您只是从构成应用程序的所有文件中替换 DLL,您将破坏所有使用您的 DLL 的应用程序。那是相当糟糕的。因为如果您的 DLL 包含错误的错误,并且我必须替换它,我必须使用您最新的 DLL 重新编译我的整个应用程序。这可能包含很多变化,所以我不能很快做到。我也可能手边没有旧的源代码,并且应用程序可能正在进行重大修改,不知道旧版本的应用程序是在什么提交上编译的。所以我现在可能无法重新编译。这很糟糕。
至于仅在 PUBLIC 方法中使用它,而不是私有的、受保护的或内部的。
是的,不错的尝试,但仍然可以使用带反射的私有、受保护或内部方法。不是因为一个人想要,而是因为它有时是必要的,因为没有其他办法。 (Example)。
vcsjones 已经提到了接口。
问题在于代码重复(允许不同的默认值 - 或忽略默认值)。
但真正令人沮丧的是,除此之外,您现在还可以在构造函数中引入 API 中断更改...
示例:
public class SomeClass
{
public SomeClass(bool aTinyLittleBitOfSomethingNew = true)
{
}
}
现在,无论您在哪里使用
System.Activator.CreateInstance<SomeClass>();
您现在将得到一个 RUNTIME 异常,因为现在没有无参数构造函数...
编译器将无法在编译时捕获此异常。
如果您的代码中碰巧有很多 Activator.CreateInstances,晚安。
你会被搞砸的,而且会被搞砸。
如果您必须维护的某些代码使用反射来创建类实例,或使用反射来访问私有/受保护/内部方法...
不要使用可选参数!
尤其是在类构造函数中。
(免责声明:有时,根本没有其他方法 - 例如,属性上的属性自动将属性名称作为构造函数参数 - 但尝试将其限制在这几种情况下,特别是如果您可以通过重载来实现)
我猜理论上它们可以用于快速原型制作,但仅限于此。
但是由于原型具有很强的生产力(至少在我目前工作的公司中),因此也不要使用它。