【问题标题】:c# method with default parameter value does not generate overload without parameter?具有默认参数值的c#方法在没有参数的情况下不会生成重载?
【发布时间】:2015-03-26 21:22:45
【问题描述】:

最近,我想在扩展方法中添加一个可选参数。原来的方法是这样的:

public static class Extensions {
    public static bool Foo(this IFoo target) {
        target.DoIt(true);
    }
}

这显然是一个简化版本,但让我们从那个开始。

我所做的是:

public static class Extensions {
    public static bool Foo(this IFoo target, bool flag = true) {
        target.DoIt(flag);
    }
}

我所做的只是引入一个带有默认值的可选参数,对吧?我期待的是编译器生成重载方法,没有标志。 这部分发生了。我重新编译的任何代码都能够毫无问题地编译和执行,即使没有这样的参数:

...
IFoo foo = new FooBar();
foo.Foo();
...

但是,针对以前版本的 Foo() 构建的任何代码都不起作用,引发以下异常:

Unhandled Exception: System.MissingMethodException: Method not found: 'Boolean Models.Class1.Foo()'.
at DefaultParamsTests.Program.Main(String[] args)

这对我们来说显然是个问题,因为我们确实有一个供客户使用的公共 API,这将是一个重大变化。

解决办法是显式创建一个重载:

public static class Extensions {
    public static bool Foo(this IFoo target) {
        target.DoIt(true);
    }

    public static bool Foo(this IFoo target, bool ) {
        target.DoIt(true);
    }
}

不过,Resharper 确实建议我可以为方法 foo 引入一个可选参数。

如果我确实遵循了重构,它基本上就是我上面展示的。但是,这不适用于现有代码。

我已经使用 Reflector 和 dotPeek 查看了生成的 IL。两者都没有显示过载的产生。

我错过了什么?

【问题讨论】:

  • 是什么让您相信编译器会为单个声明生成两个方法重载?
  • 我想你已经回答了你的问题。你可以只添加方法而忽略Resharper的引入可选参数的建议吗?
  • @gknicker,显然,在某处创建了过载。否则,默认参数将不起作用。正如回答问题的两个人所指出的那样,我假设它是在错误的地方生成的。默认值由编译器在调用站点而不是声明站点注入。
  • @maxwellb,是的,这就是我最终“修复”它的方式。我只是好奇而已。你和安德鲁在回答这个问题上做得很好。我什至最终用 dotPeek 确认了这一点。是的,默认值是在调用站点注入的。
  • 首先,这个问题写得真好。很清楚,而且有很多好的代码sn-ps。 SO现在需要更多这些。我应该提一下,我关闭了很多 resharper 的建议。有些损害可读性,有些只适用于 1/2 的时间。这是一个我很确定我关掉了

标签: c#


【解决方案1】:

在默认参数的情况下,实际调用站点被重写为使用默认值。这意味着根本没有产生重载,你的调用代码被修改了!

public void fun(int x = 3) { }

// in another file
fun();   // compiler re-writes to fun(3);
fun(7);  // fun(7) as expected

// actual code generated by the c# compiler
fun(3);
fun(7);

如果您在 C# 中使用可选参数,则需要在 function 更改时重新编译所有调用方。出于这个原因,强烈不鼓励将这些方法放在公共接口上(例如,由其他人的代码调用)。在您自己的链接项目中使用它们很好,因为它们都应该同时编译

【讨论】:

  • 另一个问题是你不能可靠地“更新默认值”。默认值为“foo”时编译的任何代码将继续使用“foo”调用该方法。将默认值更新为“bar”只会在使用该定义构建的新代码中调用。
  • @maxwellb 这是同样的问题。编译器为您重新编写了调用,因此您必须重新编译。如果它是内部代码,仅在您的项目中使用(并且具有适当的依赖项),那么在某些情况下它们是一个很好的节省时间的方法。除了internal 类,我总是使用重载,以防有人滥用我写的内容:D
  • 完全正确。我的意思是用另一个例子来补充你的回答,说明为什么可选参数有“陷阱”并且应该谨慎使用。
  • 试图想出一个更好的方法来写这个例子......我想让答案更清楚
【解决方案2】:

C# 方法中的可选参数在编译时在调用站点解析。省略参数的调用代码扩展为包含参数,调用双参数版本。这与您假设发生的情况相反,会产生过载。

【讨论】:

    【解决方案3】:

    这里的关键是编译器将方法的调用者更改为包含默认值。

    看看 dotPeek 中的新代码。

    注意:在您自己的文件上使用 dotPeek 时,您应该删除 pdb 文件!

    这是一个有两个方法的扩展类(用 dotPeek 反编译)。一个有默认值,一个没有

    public static class Extensions
    {
        public static bool Foo(this IFoo target)
        {
            return target.DoIt(true);
        }
    
        public static bool Foo(this IFoo2 target, bool doit = false)
        {
            return target.DoIt2(doit);
        }
    }
    

    那里一切看起来都很好。现在看看调用扩展方法的对象(用dotPeek反编译)。

    public class Bar
    {
        public Bar(IFoo foo)
        {
            Extensions.Foo(foo);
        }
    
        public Bar(IFoo2 foo)
        {
            Extensions.Foo(foo, false);
        }
    }
    

    请注意,IFoo2 扩展方法的调用者实际上包含扩展类的方法 Foo 的值 false。所以你的方法Extensons.Foo 现在有两个参数。第一个方法是对象,第二个是“默认”参数。因此,使用第一个版本创建的任何代码现在都会失败,因为没有只有一个参数的方法。

    禁用方法周围的 resharper,你就不会再看到它困扰你了 :)

    【讨论】:

      猜你喜欢
      • 2012-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多