【问题标题】:method overloading vs optional parameter in C# 4.0 [duplicate]C#4.0中的方法重载与可选参数[重复]
【发布时间】:2011-03-19 23:40:58
【问题描述】:

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

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

【问题讨论】:

  • 是的,我知道这个问题,但在 C# 4.0 发布并普及之前就有人问过这个问题。我还有更多问题(请参阅问题详情)
  • “为什么大多数 MSDN 库类使用可选参数而不是重载?” - 你的意思是反过来吗?我不知道任何使用可选参数的 BCL 类。
  • 是的,对不起,我的意思是反过来。抱歉,已编辑

标签: c# .net c#-4.0 overloading optional-parameters


【解决方案1】:

在 C# 4.0 中将“可选参数”与“命名参数”结合使用的一个很好的用例是,它为我们提供了一种优雅的方法重载替代方案,您可以根据参数的数量重载方法。

例如,假设您希望像这样调用/使用方法foofoo()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 个或更多方法时,一种方法几乎是多余的。我发现这是将可选参数与命名参数结合使用的一个很好的用例。

【讨论】:

  • 所有将使用这种方法的人 - 仅供参考,如果您在调用者中使用命名参数,则不能更改参数的名称,除非在每个调用者中更改它(如果所有调用者都更改它,这是简单的重构参考在相同的解决方案中,但如果您将其用作其他项目中的库,则不是)。话虽如此,更改参数名称始终被视为一项重大更改(即使不使用命名参数!)
【解决方案2】:

当您将可选参数公开为 API 时,它们会产生问题。重命名参数可能会导致问题。更改默认值会导致问题(请参阅此处以获取一些信息:Caveats of C# 4.0 optional parameters

此外,可选参数只能用于编译时常量。比较一下:

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的构造函数时的一些额外阅读材料。

【讨论】:

  • CALLING 站点必须注入默认值这一事实对于公共 API 来说绝对是一个问题。如果我们能得到一个可选的参数变体,它实际上是用于创建重载的语法糖(同时仍然保留现有的参数变体,例如 COM 互操作)。
  • 关于重命名参数,无论使用可选参数还是重载方法,这已经是一个问题。消费者可以指定一个命名参数,重命名变量会破坏编译。
  • 链接再次断开...使用非测试版 Wayback Machine 链接,它可以工作。 web.archive.org/web/20140815160502/http://blog.coverity.com/…
【解决方案3】:

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

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) {  
  ...  
}

(我前段时间写过这个here

【讨论】:

  • 为了我的钱,这是最好的答案。你是唯一一个区分方法体做什么的人。如果方法体相同,则使用可选参数,这是完全合理的,但如果方法体会根据参数而变化,则使用重载。
  • 完美的解释。谢谢。
【解决方案4】:

这个几乎不用说,但是:

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

当然,这对大多数商店来说都不是问题。但你可以打赌,这就是微软不在基类库中使用可选参数的原因。

【讨论】:

  • 如果您使用的库带有来自不支持它的语言(例如 C# 2.0)的可选参数,会发生什么情况?
  • C# 2.0 忽略“可选参数”标志,并按要求处理所有参数。因此,每个调用站点都必须为每个参数提供一个值。
【解决方案5】:

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

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”只有一种实现,因此只有一种方法调用。

【讨论】:

  • 这是一个重要的考虑因素吗?
  • 代码越少越好。错误风险更低,可读性更高。
  • 您可以将命名参数与可选参数结合使用,以避免在代码末尾定义可选参数。请参阅下面的帖子。
  • 是的,我同意。我也将其添加到我的帖子中
【解决方案6】:

两者都没有绝对比另一个“更好”。他们在编写好的代码方面都有自己的位置。如果参数可以具有默认值,则应使用可选参数。当签名的差异超出未定义可能具有默认值的参数时(例如行为因传递的参数和保留默认值而异),应使用方法重载。

// 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)
{
    ...
}

【讨论】:

    【解决方案7】:

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

    【讨论】:

      【解决方案8】:

      为了解决您的第一个问题,

      为什么大多数 MSDN 库类都使用 重载而不是可选 参数?

      这是为了向后兼容。

      在 VS2010 中打开 C# 2、3.0 或 3.5 项目时,会自动升级。

      试想一下,如果项目中使用的每个重载都必须转换为匹配相应的可选参数声明,将会带来怎样的不便。

      另外,俗话说“未破何必修?”。不必替换已经用于新实现的重载。

      【讨论】:

        【解决方案9】:

        第三个选项呢:传递一个类的实例,其属性对应于各种“可选参数”。

        这提供了与命名参数和可选参数相同的好处,但我觉得这通常更清楚。它使您有机会在必要时对参数进行逻辑分组(即使用组合)并封装一些基本验证。

        此外,如果您希望使用您的方法的客户端执行任何类型的元编程(例如构建涉及您的方法的 linq 表达式),我认为保持方法签名简单有其优势。

        【讨论】:

        • 有人对你投了反对票,大概是因为没有回答问题。但我认为这是一个有用的观察结果,所以+1。将参数分组到对象中是一种重构,执行得不够频繁。
        【解决方案10】:

        这并不是对原始问题的真正答案,而是对@NileshGule 的answer 的评论,但是:

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

        b) 多行代码在 cmets 中很难阅读

        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
        

        如果您提供空字符串引用,它不会被默认值替换。所以你仍然需要检查输入参数是否为空。

        【讨论】:

          【解决方案11】:

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

          命名参数提供了以任何顺序传递参数值的灵活性。

          【讨论】:

          • 在使用可选参数的时候还是需要检查空值,请看我的回答here
          • 如果需要决策,则在可选参数的情况下将增加保护子句。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-02-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多