为什么将返回类型添加到void返回方法会导致MissingMethodException

lys*_*cid 10 .net c# clr

我有一个.NET应用程序,它使用一个定义一些方法的程序集(.dll):

    public void DoSomething()
    {
        // Do work
    }
Run Code Online (Sandbox Code Playgroud)

假设此方法签名更改为包含字符串返回类型:

    public string DoSomething()
    {
        // Do work
        return "something";
    }
Run Code Online (Sandbox Code Playgroud)

为什么使用此方法的代码在System.MissingMethodException上失败?

在我看来,在这个方法的所有调用站点,没有使用返回值(因为它之前不存在).

为什么这个改变会破坏代码呢?

Eri*_*ert 16

其他答案表明您已更改方法的签名,因此必须重新编译调用方,这是正确的.我想我可能会在你的问题中添加一些额外的信息:

在我看来,在这个方法的所有调用站点,没有使用返回值(因为它之前不存在).

这是完全正确的.现在,请考虑这个问题:如何编写不使用数据的代码?您似乎在完全错误的假设下工作,即不使用值不需要代码,但不使用值肯定需要代码!

假设你有方法:

static int M1(int y) { return y + 1; }
static void M2(int z) { ... }
Run Code Online (Sandbox Code Playgroud)

你有一个电话

int x;
x = M1(123);
Run Code Online (Sandbox Code Playgroud)

在IL级别会发生什么?下列:

  • 在临时池上为x分配空间.
  • 按下堆栈上的123
  • 调用M1.
  • 在堆栈上按1.Stack现在是1,123
  • 在堆栈上添加前两个东西.这会弹出并推送结果.Stack现在是124.
  • 回到来电者
  • 堆栈仍然是124.
  • 将堆栈上的值存储到x的临时存储中.这会弹出堆栈,因此堆栈现在为空.

假设你现在做:

M1(345);
Run Code Online (Sandbox Code Playgroud)

怎么了? 同样的事情:

  • 在堆栈上按345
  • 调用M1.
  • 在堆栈上按1.堆栈现在是1,345
  • 在堆栈上添加前两个东西.这会弹出并推送结果.Stack现在是346.
  • 回到来电者
  • 堆栈仍然是346.

但是没有指令可以在任何地方将值存储在堆栈中,所以我们必须发出一个pop指令:

  • 从堆栈中弹出未使用的值.

现在假设你打电话

M2(456);
Run Code Online (Sandbox Code Playgroud)

怎么了?

  • 将456推入堆栈
  • 调用M2.
  • M2做到了.当它返回调用者时,堆栈为空,因为它返回void.
  • 堆栈现在是空的,所以不要弹出任何东西.

现在你明白为什么将一个方法从void返回值返回是一个突破性的变化? 现在每个调用者都必须从堆栈中弹出未使用的值.对数据执行任何操作仍需要将其从堆栈中清除.如果你没有弹出那个值,你就是错误的堆栈; CLR要求堆栈在每个语句的开头都是空的,以确保不会发生这种错位.

  • +100成就解锁 - (Eric Lippert回答了你的问题) (2认同)

Alo*_*aus 1

因为你做了一个重大的 api 更改。您确实更改了基类或接口中的方法签名。

调用者与此方法相关联。在 IL 中,方法引用不仅是对类型及其方法以及方法索引的引用,而且调用者方法引用确实包含完整的方法签名。

因此,可以通过重新编译调用此方法的所有程序集来修复此更改,但是当您仅重新编译更改的程序集并希望使用的程序集能够神奇地获取更改的方法签名时,您会遇到运行时异常。情况并非如此,因为方法引用确实包含完整的方法签名和定义类型。

这也确实发生在我身上。您是对的,没有人可以使用返回类型,因此此更改是安全的,但您需要重新编译所有受影响的目标。