最近,我想在扩展方法中添加一个可选参数。原始方法看起来像这样:
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。两者都没有显示过载的产生。
我错过了什么?
答案 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,你不会再看到它纠缠不清了:)