C#可选参数还是方法重载?

26

自从C#添加了可选参数,使用可选参数还是方法重载被认为是更好的实践方式?还是在特定情况下,你会更倾向于使用其中一种?例如,一个有很多参数的函数是否更适合使用可选参数?

6个回答

24

可选参数很好,但应在有意义时使用。可选参数经常会混淆方法的意图--如果有其他替代方案,我会倾向于选择替代方案。

可选参数和命名参数的需求部分是因为COM允许可选和命名参数:

MSDN

一些API,尤其是Office自动化API等COM接口,专门针对命名和可选参数编写。直到现在,从C#调用这些API非常痛苦,必须显式传递多达30个参数,其中大多数具有合理的默认值,可以省略。

来自forums.asp.net的SomeNewKid简明地表达了这一点:

http://forums.asp.net/t/386604.aspx/1

过载方法通常比可选参数更可取。为什么呢?为了保持每个方法的清晰目的。也就是说,每个方法应该做一件事情得当。一旦您引入可选参数,就会稀释该方法的清洁度,并引入分支逻辑,最好将其保留在方法之外。当您开始使用继承时,这种目的的清晰度变得更加重要。如果您覆盖具有一个或多个可选参数的方法,则它们变得更难处理。因此,我建议除了快速和肮脏的类之外,您优先使用重载而不是可选参数。

请记住,可选参数是一种语法糖:

Reflector C#:

public class Class1
{
    // Methods
    public Class1()
    {
        this.Method1("3", "23");
    }

    public void Method1(string one, [Optional, DefaultParameterValue("23")] string two)
    {
    }
}

IL:

.class public auto ansi beforefieldinit Class1
    extends [mscorlib]System.Object
{
    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        .maxstack 8
        L_0000: ldarg.0 
        L_0001: call instance void [mscorlib]System.Object::.ctor()
        L_0006: nop 
        L_0007: nop 
        L_0008: ldarg.0 
        L_0009: ldstr "3"
        L_000e: ldstr "23"
        L_0013: call instance void WebApplication1.Class1::Method1(string, string)
        L_0018: nop 
        L_0019: nop 
        L_001a: ret 
    }

    .method public hidebysig instance void Method1(string one, [opt] string two) cil managed
    {
        .param [2] = string('23')
        .maxstack 8
        L_0000: nop 
        L_0001: ret 
    }

}

7
据我的经验,16个重载会使事情变得非常混乱。相比之下,4个可选参数要清晰得多。这当然是一个极端的例子,但我不明白为什么一般的重载组合能够达到像SomeNewKid所说的理想状态;通常你只会得到n个可选参数的2ⁿ个重载。如果你发现自己在做这件事,请使用可选参数代替。 - Roman Starkov
重载的一个优点是,在使用诸如 ReSharper 或内置于 Visual Studio 的 IDE 工具时,更容易找到实际正在寻找的引用。如果我有可选参数,并且我正在寻找调用 2 个参数与 3 个参数的引用,那么这样做就不那么容易了。 - zumalifeguard
1
VB.Net一直支持可选参数,并且在许多大型VB项目中广泛使用。当您想要扩展函数的参数列表而不必重新访问每个现有调用时,它们特别方便。缺少可选参数在将大规模VB项目迁移到C#时会出现严重问题。我认为这是将它们添加到C#的重要动机之一。 - Herc

13

在Visual Studio和FxCop中的代码分析建议您不要在公共API中使用可选参数 (规则CA1026:不应使用默认参数)。原因是并非所有.NET语言都支持它们。

我认为更好的避免使用它们的原因是,如果您在API 2.0版本中添加更多的重载,它们可能会引入运行时或编译时问题。Phil Haack在这里解释了

我将采用Phil的结论:可选参数是为了支持COM互操作而设计的;如果您不使用COM,请不要使用它们。


3
如果您正在构建公共API,这些是很好的观点。不过,对于内部方法,规则显然会有所不同,尽管一些人甚至会选择遵循API设计准则来处理内部方法。这也是可以接受的。 - hemp

10

我不确定这个问题是否有标准答案 - 这是主观的,需要根据具体情况而定。然而,在我的观点中,可选参数创造了更加明确的API,因此我通常比起方法重载更喜欢可选参数。

具体地说,当使用Intellisense时,我更喜欢看到这样的形式:

默认可选参数

而不是这样:

重载1

重载2

重载3

如果我不指定参数1和参数2的值,我可能需要猜测(或查找文档)。


8
使用可选参数会造成歧义。你最终得到一个方法来完成许多任务。这个方法将根据输入的不同而有不同的行为。一个方法应该只做一件事情。 - Chuck Conway
8
@Chuck 我完全不同意你的看法,这也是我一开始就说这个问题具有主观性质的原因。此外,“一个方法只应该做一件事”的论点已经陈词滥调且被滥用。更糟糕的是,我看到最常见的重载方法实现模式是其中一个重载调用另一个重载,并传入默认值。 - hemp
3
由于歧义问题是纯主观的,因此无法解决。然而,使用重载方法来设置默认值违反了您反对在方法内部使用分支逻辑的立场,因为在该情景下最终被调用者的行为会根据传入的值而不同。 - hemp
3
虽然我同意“只有一件事”方法是计算机科学课程中的一个内容,但提取多个重载方法确实是有意义的。每个重载应该被视为具有独特参数验证和可能甚至逻辑的用例。正如上面提到的,它在很大程度上是建议性的,但我发现对于可选参数具有简洁的用例比作为一种补救措施和提供有趣的错误开发嵌套要更容易。在很多情况下,测试一系列的重载比想出所有可能的可选参数排列方式要容易得多。 - Joseph Ferris
3
@joseph:感谢您的深思熟虑的评论。我完全同意创建具有许多可选参数排列组合的方法是一个问题,但我认为对于重载方法的排列组合也同样如此。例如,如果您有12个同名方法,那么这个名称可能如何充分描述这12个方法的行为呢?这是一种“代码异味”,无论是通过重载还是可选参数来实现,都会带来测试上的挑战。 - hemp
显示剩余5条评论

4

可选参数旨在促进COM对象的交互,因为COM对象使用大量可选参数。因此,如果您正在进行P/Invoke或COM对象处理,请优先考虑使用可选参数。否则,方法重载是正确的选择,因为它可以节省很多混淆。


4

与其使用过载或命名可选参数,我更喜欢对象初始化器。在类中,你只需要一个无参构造函数和想要在初始化时设置的公共属性。

假设类Bar有公共字符串属性"Name"和"Pet",你可以像这样构造一个新对象。

var foo = new Bar { Name = "Fred", Pet = "Dino" };

优点在于您不需要为每个要初始化的值组合创建单独的重载。

3
这是一个很好的特性,但它需要您的对象可变。在可能的情况下,我倾向于使用不可变类型。 - hemp
2
不可变和可变都各自有其用处,在 C#中,强制不可变性可能需要比只读属性和私有 setter 更多的工作。任何“更改”都需要构造一个新对象,并将其返回原始对象的位置。因此,在大多数情况下,“这取决于情况”。 - Cylon Cat

1

可选参数需要一个默认值(我只是假设),因此在某些情况下可能很难提供。为什么呢?因为有些类需要运行时信息才能初始化,而这些信息可能在编译器中不可用。

当涉及到原始类型时,答案可能与是否可以假定默认值有关,或者如果缺少参数可能意味着方法的不同行为。


如果无法提供默认值,则使用可选参数是没有意义的。在这种情况下,请将其作为必需参数。如果只能在运行时确定默认值,则空值或特殊情况默认值可能足以触发查找正确的值。但我认为这表明存在代码异味,可能需要更好的设计,例如使用类工厂或其他适当的模式。 - hemp

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