我有一个.NET应用程序,它使用一个定义某种方法的程序集(.dll):
public void DoSomething()
{
// Do work
}
假设此方法签名更改为包含字符串返回类型:
public string DoSomething()
{
// Do work
return "something";
}
为什么使用此方法的代码在 System.MissingMethodException 上失败?
在我看来,在这个方法的所有调用站点,没有使用返回值(因为它之前不存在)。
为什么这种改变会破坏代码?
答案 0 :(得分:16)
其他答案表明您已更改方法的签名,因此必须重新编译调用方,这是正确的。我想我可能会在你的问题中添加一些额外的信息:
在我看来,在这个方法的所有调用站点,没有使用返回值(因为它之前不存在)。
这是完全正确的。现在,请考虑以下问题:如何编写不使用数据的代码?您似乎在完全错误的假设下工作,不使用值不需要代码,但不使用值肯定需要代码!
假设你有方法:
static int M1(int y) { return y + 1; }
static void M2(int z) { ... }
你有一个电话
int x;
x = M1(123);
IL级别会发生什么??以下内容:
假设你现在这样做:
M1(345);
会发生什么? 同样的事情:
但是没有任何指令可以在任何地方将值存储在堆栈中,所以我们必须发出一个pop指令:
现在假设你打电话
M2(456);
会发生什么?
现在你明白为什么将一个方法从void返回值返回是一个突破性的变化? 每个调用者现在必须从堆栈中弹出未使用的值。使用数据执行 nothing 仍需要将其从堆栈中清除。如果你没有弹出那个值,你就是错误的堆栈; CLR要求堆栈在每个语句的开头为空,以确保不会发生这种错位。
答案 1 :(得分:2)
如果没有涉及反射,但链接是静态的(我假设这是从描述中),那么运行时将尝试使用精确签名找到一个方法。这就是CIL callvirt
的工作原理。
无论该值是否被消耗都没关系 - 运行时无法找到void YourClass::DoSomething()
,甚至没有尝试查找string YourClass::DoSomething()
。
如果可以进行此类更改,则可能会通过导致堆栈下溢/溢出而轻易炸毁运行时。
答案 2 :(得分:1)
因为您已经更改了方法签名。
当外部代码需要定位方法时,需要确保调用的内容是正确的。它在编译时将一些此类信息存储为签名 - 签名信息包括返回类型(无论它是否实际在任何地方使用)。
就CLR而言,具有void返回类型的方法不再存在 - 因此MissingMethodException
。
答案 3 :(得分:1)
因为你做了一次破坏性的api改变。您确实在基类或接口中更改了方法签名。
调用者与此方法相关联。在IL中,方法引用不仅是对类型及其方法的引用,还有方法的一些索引,但调用方法引用确实包括完整的方法签名。
因此,可以通过重新编译调用此方法的所有程序集来修复此更改,但只有在重新编译已更改的程序集时才会遇到运行时异常,并希望使用程序集能够神奇地获取更改的方法签名。情况并非如此,因为方法引用确实包含完整的方法签名和定义类型。
它确实发生在我身上。你是对的,没有人可以使用返回类型,所以这个更改是安全的,但你需要重新编译所有受影响的目标。
答案 4 :(得分:0)
C#的制造商决定对方法签名应该严格。