33

我知道在库方法中添加可选参数是一项重大更改

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)(source code) 仍然可以编译,并且Foo(0, 5)(compiled code) 仍然会找到合适的重载,因此,理论上,这应该可以工作。

它在实践中是否有效,即.NET 运行时和 C#/VB 编译器是否“正式支持”这种场景?还是对带有可选参数的方法的调用以某种方式“标记”,导致当可选参数被重载替换时它们会失败?


编辑:为了澄清,我问的是二进制兼容性:是否可以在不重新编译的情况下library.dll (old)替换?library.dll (new)projectUsingLibrary.exe

4

2 回答 2

12

我认为这是一个很好的问题,所以这是我的看法。

使用执行此操作的快速客户端:

        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

因此,如果您将实现从可选更改为重载,但将客户端保持原样,它将有效地为您添加默认参数,并始终调用具有两个参数的函数,这可能是也可能不是期望的行为。

于 2012-10-01T10:02:57.860 回答
4

我不确定我的测试方法是否是最好的,但这是我发现的开始:(为类和命名空间名称道歉)

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复制到控制台应用程序文件夹中并运行控制台应用程序;更改签名没有问题,但正如我所说,我不确定我的测试方法是否足够。

因此,要回答您的问题,您可以按照您指定的方式更改库,而无需重新编译可执行文件。

于 2012-10-01T10:12:53.437 回答