我知道adding an optional parameter in a library method is a breaking change,
void Foo(int x) // OLD
void Foo(int x, int y = 5) // NEW
因为在编译的代码中新版本被视为Foo(int, int)
。每次调用Foo(0)
(源代码)都由编译器转换为Foo(0, 5)
(编译代码)。因此,使用Foo(0)
的编译调用的旧客户端将找不到合适的方法。
另一个方向呢?
void Foo(int x, int y = 5) { ... } // OLD
void Foo(int x) { Foo(x, 5); } // NEW
void Foo(int x, int y) { ... } // NEW
Foo(0)
(源代码)仍然会编译,Foo(0, 5)
(编译代码)仍然会找到合适的重载,所以理论上说这应该可行。
它是否在实践中有效,即.NET运行时和C#/ VB编译器“正式支持”这种情况?或者调用带有可选参数的方法以某种方式“标记”,导致它们在可选参数被重载替换时失败?
编辑:澄清一下,我问的是二进制兼容性:是否可以在不重新编译library.dll (old)
的情况下将library.dll (new)
替换为projectUsingLibrary.exe
?
答案 0 :(得分:10)
我认为这是一个很好的问题,所以这就是我的看法。
使用执行此操作的快速客户端:
c1.Foo(1);
c1.Foo(1, 2);
使用可选参数时,客户端IL看起来像:
IL_0000: nop
IL_0001: newobj instance void [ClassLibrary1]ClassLibrary1.Class1::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldc.i4.1
IL_0009: ldc.i4.5
IL_000a: callvirt instance void [ClassLibrary1]ClassLibrary1.Class1::Foo(int32, int32)
IL_000f: nop
IL_0010: ldloc.0
IL_0011: ldc.i4.1
IL_0012: ldc.i4.2
IL_0013: callvirt instance void [ClassLibrary1]ClassLibrary1.Class1::Foo(int32, int32)
IL_0018: nop
IL_0019: ret
当使用重载时,它看起来像:
IL_0000: nop
IL_0001: newobj instance void [ClassLibrary2]ClassLibrary2.Class2::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldc.i4.1
IL_0009: callvirt instance void [ClassLibrary2]ClassLibrary2.Class2::Foo(int32)
IL_000e: nop
IL_000f: ldloc.0
IL_0010: ldc.i4.1
IL_0011: ldc.i4.2
IL_0012: callvirt instance void [ClassLibrary2]ClassLibrary2.Class2::Foo(int32, int32)
IL_0017: nop
IL_0018: ret
因此,如果您将实现从可选更改为重载,但是将客户端保留为原来的,那么它将有效地为您添加默认参数,并始终调用具有两个参数的函数,这可能是也可能不是理想的行为。
答案 1 :(得分:3)
我不确定我的测试方法是否是最好的,但这是我从以下开始发现的:(为类和命名空间名称道歉)
namespace ClassLibrary1
{
public class Class1
{
private int x;
private int y;
public void Foo(int x)
{
Foo(x, 0);
}
public void Foo(int x, int y = 5)
{
this.x = x;
this.y = y;
}
}
}
我构建了这个并将dll添加到另一个解决方案中的控制台应用程序,并通过浏览它引用了dll:
using ClassLibrary1;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var c = new Class1();
c.Foo(1);
c.Foo(2, 3);
c.Foo(3, 5);
}
}
}
然后我将类库的方法签名更改为:
namespace ClassLibrary1
{
public class Class1
{
private int x;
private int y;
public void Foo(int x)
{
Foo(x, 0);
}
public void Foo(int x, int y)
{
this.x = x;
this.y = y;
}
}
}
然后我编译了类库并将dll复制到控制台应用程序文件夹中并运行控制台应用程序;改变签名没有问题,但正如我所说,我不确定我的测试方法是否足够。
因此,要回答您的问题,您可以按照指定的方式更改库,而无需重新编译可执行文件。