我正在编写和浏览我正在使用的项目中的许多方法,并且尽可能多地使用我认为overloads有用的方法,我认为optional parameter使用带有默认值的简单方法可以解决问题,帮助编写更具可读性的问题,我认为有效的代码.
现在我听说在方法中使用这些参数可能会带来讨厌的副作用。
这些副作用是什么,是否值得冒险使用这些参数来保持代码干净???
我正在编写和浏览我正在使用的项目中的许多方法,并且尽可能多地使用我认为overloads有用的方法,我认为optional parameter使用带有默认值的简单方法可以解决问题,帮助编写更具可读性的问题,我认为有效的代码.
现在我听说在方法中使用这些参数可能会带来讨厌的副作用。
这些副作用是什么,是否值得冒险使用这些参数来保持代码干净???
我将首先说Any language feature can be well or it can be used well or it can be used bad 来作为我的答案的序言。可选参数有一些缺点,就像声明局部变量var或泛型一样。
这些副作用是什么
想到了两个。
首先是可选参数的默认值是嵌入在方法使用者中的编译时常量。假设我在 AssemblyA 中有这个类:
public class Foo
{
public void Bar(string baz = "cat")
{
//Omitted
}
}
这在 AssemblyB 中:
public void CallBar()
{
new Foo().Bar();
}
真正最终产生的是这个,在汇编B中:
public void CallBar()
{
new Foo().Bar("cat");
}
因此,如果您要更改默认值 on Bar,则assemblyA 和 assemblyB 都需要重新编译。因此,如果方法使用可选参数,我倾向于不将它们声明为公共的,而是内部的或私有的。如果我需要将其声明为公共,我会使用重载。
第二个问题是它们如何与接口和多态性交互。拿这个界面:
public interface IBar
{
void Foo(string baz = "cat");
}
和这个类:
public class Bar : IBar
{
public void Foo(string baz = "dog")
{
Console.WriteLine(baz);
}
}
这些行将打印不同的内容:
IBar bar1 = new Bar();
bar1.Foo(); //Prints "cat"
var bar2 = new Bar();
bar2.Foo(); //Prints "dog"
这是我想到的两个负面因素。然而,也有积极的一面。考虑这种方法:
void Foo(string bar = "bar", string baz = "baz", string yat = "yat")
{
}
创建默认提供所有可能排列的方法将需要几行代码,甚至几十行代码。
结论:可选参数是好的,也可能是坏的。就像其他任何事情一样。
死灵术。
带有可选参数的事情是,它们很糟糕,因为它们不直观——这意味着它们的行为方式与您期望的不同。
原因如下:
它们破坏了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 方法中使用它,而不是私有的、受保护的或内部的。
是的,不错的尝试,但仍然可以使用带反射的私有、受保护或内部方法。不是因为一个人想要,而是因为它有时是必要的,因为没有其他办法。(示例)。
vcsjones 已经提到了接口。
问题在于代码重复(允许不同的默认值 - 或忽略默认值)。
但真正令人沮丧的是,除此之外,您现在还可以在构造函数中引入 API 破坏性更改......
示例:
public class SomeClass
{
public SomeClass(bool aTinyLittleBitOfSomethingNew = true)
{
}
}
现在,在您使用的任何地方
System.Activator.CreateInstance<SomeClass>();
你现在会得到一个 RUNTIME 异常,因为现在没有无参数构造函数......
编译器将无法在编译时捕获它。如果您的代码中碰巧有很多 s,
晚安。
你会被搞砸的,而且会被搞砸。
如果您必须维护的某些代码使用反射来创建类实例,或使用反射来访问私有/受保护/内部方法...Activator.CreateInstance
我想说这取决于包含或省略该参数时方法的不同程度。
如果一个方法的行为和内部功能在没有参数的情况下非常不同,那么让它成为一个重载。如果您使用可选参数来更改行为,请不要。与其让一个方法用一个参数做一件事,当你传入第二个参数时做一些不同的事情,不如让一个方法做一件事,另一种方法做另一件事。如果它们的行为差异很大,那么它们可能应该是完全独立的,而不是具有相同名称的重载。
如果您需要知道参数是用户指定的还是留空的,请考虑将其设为重载。有时你可以使用可为空的值,如果它们被传入的地方不允许空值,但通常你不能排除用户传递的可能性null,所以如果你需要知道值的来源以及至于值是什么,不要使用可选参数。
最重要的是,请记住,可选参数应该(根据定义)用于对方法的结果有很小、微不足道或其他不重要影响的事情。如果您更改默认值,任何调用该方法而不指定值的地方仍然应该对结果感到满意。如果您更改默认值,然后发现调用该方法且可选参数留空的其他一些代码现在无法正常工作,那么它可能不应该是可选参数。
使用可选参数可能是个好主意的地方是:
var foo = bar;在开始时有某个地方时,制作两种方法是没有意义的。workFactoror传递值blockSize,但他们可能不知道这些的“正常”值是什么。评论和文档在这里会有所帮助,但可选参数也会有所帮助 - 编码人员将在签名中看到[workFactor = 24], [blockSize = 256]这有助于他们判断哪种值是合理的。(当然,这不是不正确评论和记录您的代码的借口。)您没有编写更具可读性和效率的代码。
首先,您的方法签名将无缘无故地更长。
其次,重载并不只是为了使用默认值——快速浏览一下 Convert 类应该会告诉你这一点。很多时候重载的方法有不同的执行路径,在你的单个非重载方法中会变成意大利面条代码。
第三,有时您需要知道某个值是否被用作输入。如果他碰巧使用与您使用的默认值相同的值,那么您如何知道用户是否传递了这些值?
我经常在 C# 中看到可选参数,例如IMyInterface parameter = null. 尤其是当我在构造函数中看到它时,我什至会说这是代码异味。我知道这是一个艰难的判断——但在这种情况下,它会掩盖你的依赖关系,这很糟糕。
就像 vcsjones 所说,您可以正确使用这些语言功能,但我相信可选参数仅应在某些极端情况下使用。
我的意见。