C# 为什么callvirt IL指令会导致虚拟方法中的递归调用?
在这种情况下,IL并不总是对C# 为什么callvirt IL指令会导致虚拟方法中的递归调用?,c#,.net,cil,C#,.net,Cil,在这种情况下,IL并不总是对virtual方法使用callvirt指令: class MakeMeASandwich{ public override string ToString(){ return base.ToString(); } } 在这种情况下,据说IL将生成call而不是callvirt,其中生成callvirt,以检查variable是否为null,否则抛出NullReferenceException 如果使用callvirt而不是call,为什么递归调用会一
virtual
方法使用callvirt
指令:
class MakeMeASandwich{
public override string ToString(){
return base.ToString();
}
}
在这种情况下,据说IL将生成call
而不是callvirt
,其中生成callvirt
,以检查variable
是否为null
,否则抛出NullReferenceException
callvirt
而不是call
,为什么递归调用会一直发生到堆栈溢出call
,那么何时检查用于调用方法的实例变量是否为null李>
Callvirt调用可用的最派生的方法。在本例中,这是MakeMeASandwich.ToString() callvirt的目的不仅是检查null,还可以执行虚拟方法调用
call
,是因为它知道要调用哪个方法,并且不应该在运行时查找它(callvirt
会导致代码调用在最特定的类中定义的方法,从而导致堆栈溢出)callvirt
表示空检查,而call
则不表示空检查override string ToString()
{
return this.ToString();
}
这显然是一个无限递归,前提是给出的方法是ToString的最重写版本
如果使用了call,那么它如何检查用于调用方法的实例变量是否为null
这个问题是不可回答的,因为这个问题假定是错误的。call指令不检查对接收器的引用是否为null,因此询问call指令为什么检查null没有任何意义
让我把这句话换成一些更好的问题:
C#编译器在什么情况下生成调用而不是callvirt
如果C#代码对虚拟方法执行非虚拟调用,那么编译器必须生成调用,而不是callvirt。只有在使用base
调用虚拟方法时才会发生这种情况
如果C#代码正在执行虚拟调用,那么编译器必须生成callvirt
如果C#代码对非虚拟方法执行非虚拟调用,那么编译器可以选择生成call或callvirt。两者都可以。C#编译器通常选择生成callvirt
call指令不会自动执行空检查,但callvirt会执行空检查。如果C#编译器选择生成调用而不是callvirt,它是否也有义务生成空检查
不可以。如果已知接收方不为空,C#编译器可以跳过空检查。例如,如果您对非虚拟方法M说(new C()).M()
,那么编译器在不进行空检查的情况下生成调用
指令是合法的。我们知道(1)该方法不是虚拟的,因此它不必是callvirt
;我们可以选择是否使用callvirt
。我们知道(2)newc()
从不为null,因此我们不必生成null检查
如果C#编译器不知道接收方不是空的,那么它将生成一个callvirt,或者在调用之后生成一个空检查
如果使用callvirt而不是call,为什么递归调用会一直发生到堆栈溢出
正如其他人回答的那样,callvirt
实际上调用了该方法。就好像你写过一样
public override string ToString() {
return ToString();
}
如果使用了call,那么它如何检查用于调用方法的实例变量是否为null
其他人提到,您已经知道,
这个
不是空的。那是不对的。如果使用callvirt
调用MakeMeASandwich.ToString
,则是,此
不能为空。但是,不要求使用callvirt
调用函数。其他语言确实允许您以生成操作码的方式编写调用,在这种情况下,不会执行空检查。我认为C++/CLI允许使用MakeMeandwich->MakeMeandwich::ToString()
,但我不能完全确定。除非你检查,否则你无法确定这个不是空的。@Lucero:不,我正在读一本关于它的书,当我在书中找不到答案时,我突然想到了。我(我想)已经澄清了我认为是你的问题2
。如果我错了,请回滚我的修订:)@AakashM:实际上这就是我要问的,但是,callvirt
似乎没有检查变量是否为空:)@Braveyardcallvirt
执行空检查<代码>呼叫
没有。请您再解释一下项目,好吗callvirt
将调用MakeMeASandwich
一,因此它调用基
,为什么这里出现堆栈溢出?2可能不完全正确-此
可能为空(因为MakeMeASandwich.ToString可能已被调用
)并且不会进行额外的检查-通过创建相应的IL应该很容易检查。@Braveyard,只需手动编写IL和跟踪-您将看到如果base.ToString被翻译为callvirt base.ToString
,它实际上等于调用MakeMeASandwich.ToString
,因为它使用了最新的重写