如果使用 callvirt 而不是调用,为什么递归调用会发生直到堆栈溢出?
因为那么您的代码与以下内容完全相同:
override string ToString()
{
return this.ToString();
}
这显然是一个无限递归,只要给定的方法是最重要的 ToString 版本。
如果使用 call ,那么它如何检查用于调用方法的实例变量是否为空?
这个问题是无法回答的,因为这个问题是假的。call 指令不检查对接收者的引用是否为 null,因此询问call 指令为什么检查 null没有任何意义。
让我把它改写成一些更好的问题:
C#编译器在什么情况下生成调用vs callvirt?
如果 C# 代码对虚拟方法执行非虚拟调用,则编译器必须生成调用,而不是 callvirt。真正发生这种情况的唯一一次是在使用调用虚拟方法时。base
如果 C# 代码正在执行虚拟调用,则编译器必须生成一个 callvirt。
如果 C# 代码对非虚拟方法执行非虚拟调用,则编译器可以选择生成 call 或 callvirt。要么会工作。C# 编译器通常选择生成 callvirt。
call 指令不会自动进行空值检查,但 callvirt 会。如果 C# 编译器选择生成调用而不是 callvirt,是否也有义务生成空检查?
否。如果已知接收器不为空,C# 编译器可以跳过空检查。例如,如果您说(new C()).M()
的是非虚拟方法 M,那么编译器生成call
没有空值检查的指令是合法的。我们知道 (1) 方法不是虚拟的,所以它不必是callvirt
; 我们可以选择是否使用callvirt
。而且我们知道 (2)new C()
永远不会为空,因此我们不必生成空检查。
如果 C# 编译器不知道接收者不为 null,那么它将生成一个 callvirt,或者生成一个 null 检查,然后进行调用。