更改库中的委托签名以省略参数不会破坏使用它的应用程序

dzs*_*dzs 18 c#

请考虑类库中的以下代码:

public class Service
{
    public delegate string Formatter(string s1, string s2);

    public void Print(Formatter f)
    {
        Console.WriteLine(f("a", "b"));
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个使用它的控制台应用程序:

static void Main(string[] args)
{
    s = new Service();
    s.Print(Concat);
}

static string Concat(string s1, string s2)
{
    return string.Format("{0}-{1}", s1, s2);
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,它打印" ab ",正如人们所期望的那样.

现在,我按如下方式更改类库:

public class Service
{
    public delegate string Formatter(string s1);

    public void Print(Formatter f)
    {
        Console.WriteLine(f("a"));
    }
}
Run Code Online (Sandbox Code Playgroud)

即我从委托中删除了一个参数.我只编译类库并覆盖控制台应用程序旁边的dll(控制台应用程序重新编译).我希望这是库中的一个重大变化,如果我执行应用程序,它会发现不匹配,导致一些运行时异常.

相比之下,当我运行应用程序时,根本没有例外,我得到令人惊叹的输出" -a ".当我调试时,我可以看到调用Concat方法(带有2个参数),下面的调用堆栈级别显示Print调用f("a")(一个参数),任何地方都没有错误指示.最有趣的是,在Concat中,s1为null,s2为"a".

我也使用相同的结果,对签名(添加参数,更改参数类型)进行了不同的更改.当我将s2的类型从string更改为int时,我得到了一个异常,但是当调用Concat方法时却没有,但是当它尝试调用string.Format时.

我尝试使用.NET目标框架4.5.1和3.5,x86和x64.

任何人都可以回答这是预期的行为还是错误?这对我来说似乎很危险.

Mar*_*ell 5

这是一个更简单的repro - 基本上,我在委托类型(IL使用的那个)上使用"引擎盖下"构造函数来传递带有错误签名的方法目标,并且......它工作正常(通过它我的意思是它不会抛出异常 - 它的行为就像你的代码一样):

using System;

static class P
{
    static void Main()
    {
        // resolve the (object, IntPtr) ctor
        var ctor = typeof(Func<string, string>).GetConstructors()[0];

        // resolve the target method
        var mHandle = typeof(P).GetMethod(nameof(Concat))
            .MethodHandle.GetFunctionPointer();
        object target = null; // because: static

        // create delegate instance
        var del = (Func<string, string>)ctor.Invoke(new object[] { target, mHandle });
        var result = del("abc");
        Console.WriteLine(result); // "-abc"
    }
    public static string Concat(string s1, string s2)
    {
        return string.Format("{0}-{1}", s1, s2);
    }
}
Run Code Online (Sandbox Code Playgroud)

这不是一个真正的解释.但如果你想问一个更多的CLR专家可能会有所帮助!我本来预期的委托构造函数有关于目标是不正确大声抱怨.

在一个猜测(纯猜测),它是一个例子:如果你传递一个IntPtr(本机int),那么你完全靠自己 - 代码尽可能快.不过,对于粗心的人来说,这似乎是一个令人讨厌的陷阱!

至于为什么s2有值并且s1是空的:我这是因为堆栈向下(不是向上),因此在双参数方法中,arg1紧邻堆栈上的前一个位置的参数.当我们传递一个值而不是两个时,我们只在下面放一个值,因此s2有一个值,并且s1是未定义的(可能是以前代码中的垃圾).