带默认参数值的c#方法没有参数会产生过载?

时间:2015-01-28 04:03:15

标签: c#

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

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引入一个可选参数。

resharper introduce optional parameter

如果我遵循重构,它基本上完成了我上面展示的内容。但是,这对现有代码不起作用。

resharper refactored

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

我错过了什么?

3 个答案:

答案 0 :(得分:4)

在默认参数的情况下,重写实际的呼叫站点以使用默认值。这意味着根本不会生成重载,您的调用代码会被修改!

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更改时重新编译所有调用方。出于这个原因,强烈不鼓励将这些方法放在公共接口上(例如,由其他人的代码调用)。在你自己的链接项目中使用它们很好,因为它们应该同时编译

答案 1 :(得分:4)

C#中方法中的可选参数在编译时在调用站点解析。省略参数的调用代码将扩展为包含参数,调用双参数版本。这与你想象的相反,创造了一个过载。

答案 2 :(得分:1)

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

查看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扩展方法的调用者实际上包含类Extensions的方法Foo的值false。所以你的方法Extensons.Foo现在有两个参数。第一种方法是对象,第二种方法是"默认"参数。因此,使用第一个版本创建的任何代码现在都将失败,因为没有只有一个参数的方法。

禁用方法周围的resharper,你不会再看到它纠缠不清了:)