c# - 为什么 callvirt IL 指令会导致虚方法中的递归调用?

标签 c# .net cil

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

class MakeMeASandwich{
  public override string ToString(){
    return base.ToString();
  }
}

在这种情况下,据说 IL 将产生 call 而不是 callvirt 其中产生 callvirt 是为了检查 variable 是否为 null,否则抛出 NullReferenceException

  1. 如果使用 callvirt 而不是 call,为什么递归调用会发生直到堆栈溢出?
  2. 如果使用call,那么它什么时候检查它用来调用方法的实例变量是否为空?

最佳答案

Why does a recursive invocation happen till stack overflow if callvirt is used instead of call?

因为这样你的代码就完全一样了:

override string ToString()
{
    return this.ToString();
}

这显然是无限递归,前提是给定的方法是 ToString 的最重要版本。

If call is used, then how come it checks whether the instance variable it uses to call the methods is null or not?

这个问题是不可回答的,因为这个问题假定了一个谎言。调用指令检查接收者的引用是否为空,所以问为什么调用指令检查空值没有任何意义。

让我为您改写成一些更好的问题:

Under what circumstances does the C# compiler generate a call vs a callvirt?

如果 C# 代码对虚方法 执行非虚调用,则编译器必须生成调用,而不是 callvirt。唯一一次真正发生这种情况是在使用 base 调用虚拟方法时。

如果 C# 代码正在执行一个虚拟调用,那么编译器必须生成一个 callvirt。

如果 C# 代码正在非虚拟方法 上执行非虚拟调用,则编译器可以选择生成 call 或 callvirt。两者都会起作用。 C# 编译器通常选择生成 callvirt。

The call instruction does not automatically do a null check, but callvirt does. If the C# compiler chooses to generate a call instead of a callvirt, is it also obligated to generate a null check?

没有。如果已知接收者不为空,则 C# 编译器可以跳过空检查。例如,如果您为非虚方法 M 说 (new C()).M(),那么编译器生成 call 指令是合法的没有空检查。我们知道 (1) 该方法不是虚拟的,因此它不必是 callvirt;我们可以选择是否使用callvirt。我们知道 (2) new C() 永远不会为空,因此我们不必生成空检查。

如果 C# 编译器不知道接收者不为空,那么它将生成一个 callvirt,或者生成一个空检查然后调用。

关于c# - 为什么 callvirt IL 指令会导致虚方法中的递归调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10213413/

相关文章:

c# - 当我要求 dotMemory 强制垃圾收集时究竟会发生什么

c# - 我应该在方法结束时停止秒表吗?

C# 编译器不会优化不必要的转换

.net - 为什么将 Int32 转换为 Float64 会导致数据更改?

c# - HttpPost 操作方法中的 View 模型参数为空

c# - 在 Entity Framework 中动态传递 Select for projection 中的属性

.net - vb中获取项目中文件的路径

c# - .NET 中 CIL 知识的优势

c# - ASP.Net Core 2 身份验证

c# - 如果值是枚举值的一部分,则查找值