Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/291.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么callvirt IL指令会导致虚拟方法中的递归调用?_C#_.net_Cil - Fatal编程技术网

C# 为什么callvirt 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,为什么递归调用会一

在这种情况下,IL并不总是对
virtual
方法使用
callvirt
指令:

class MakeMeASandwich{
  public override string ToString(){
    return base.ToString();
  }
}
在这种情况下,据说IL将生成
call
而不是
callvirt
,其中生成
callvirt
,以检查
variable
是否为
null
,否则抛出
NullReferenceException

  • 如果使用
    callvirt
    而不是
    call
    ,为什么递归调用会一直发生到堆栈溢出
  • 如果使用了
    call
    ,那么何时检查用于调用方法的实例变量是否为null
  • callvirt将调用MakeMeASandwich实现,而不是对象实现。这就是堆栈溢出的原因

  • 最初的调用是使用callvirt的,它确定引用不为null。如果控件在这个ToString实现中,那么您已经知道有一个对象


  • Callvirt调用可用的最派生的方法。在本例中,这是MakeMeASandwich.ToString()

    callvirt的目的不仅是检查null,还可以执行虚拟方法调用

  • 之所以使用
    call
    ,是因为它知道要调用哪个方法,并且不应该在运行时查找它(
    callvirt
    会导致代码调用在最特定的类中定义的方法,从而导致堆栈溢出)

  • callvirt
    表示空检查,而
    call
    则不表示空检查

  • 如果使用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
    似乎没有检查变量是否为空:)@Braveyard
    callvirt
    执行空检查<代码>呼叫
    没有。请您再解释一下项目,好吗
    callvirt
    将调用
    MakeMeASandwich
    一,因此它调用
    ,为什么这里出现堆栈溢出?2可能不完全正确-
    可能为空(因为MakeMeASandwich.ToString可能已被
    调用
    )并且不会进行额外的检查-通过创建相应的IL应该很容易检查。@Braveyard,只需手动编写IL和跟踪-您将看到如果base.ToString被翻译为
    callvirt base.ToString
    ,它实际上等于
    调用MakeMeASandwich.ToString
    ,因为它使用了最新的重写