为什么C#编译器为GetType()方法调用发出callvirt指令?

我很想知道为什么会这样。 请阅读下面的代码示例以及每个部分下面的注释中发出的相应IL:

using System; class Program { static void Main() { Object o = new Object(); o.GetType(); // L_0001: newobj instance void [mscorlib]System.Object::.ctor() // L_0006: stloc.0 // L_0007: ldloc.0 // L_0008: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType() new Object().GetType(); // L_000e: newobj instance void [mscorlib]System.Object::.ctor() // L_0013: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType() } } 

为什么编译器为第一部分发出callvirt而第二部分call ? 是否有任何理由编译器会为非虚方法发出callvirt指令? 如果在某些情况下编译器会为非虚拟方法发出callvirt ,这是否会导致类型安全问题?

只是玩得安全。

从技术上讲,C#编译器并不总是使用callvirt

对于在值类型上定义的静态方法和方法,它使用call 。 大多数是通过callvirt IL指令提供的。

摆动两者之间投票的区别在于, call假定“用于进行调用的对象”不为空。 另一方面, callvirt检查not null并在需要时抛出NullReferenceException。

  • 对于静态方法,该对象是一个类型对象,不能为null。 同上价值类型。 因此, call被用于他们 – 更好的性能。
  • 对于其他语言,语言设计者决定使用callvirt因此JIT编译器会validation用于进行调用的对象是否为空。 即使对于非虚拟实例方法,他们也重视安全性而非性能。

另请参阅:Jeff Richter在此处做得更好 – 在CLR中的“设计类型”一章中通过C#2nd Ed

请参阅Eric Gunnerson撰写的这篇旧博客文章。

这是post的文字:

为什么C#总是使用callvirt?

这个问题出现在内部C#别名上,我认为答案是普遍感兴趣的。 这是假设答案是正确的 – 已经有一段时间了。

.NET IL语言提供call和callvirt指令,callvirt用于调用虚函数。 但是如果你仔细查看C#生成的代码,即使在没有涉及虚函数的情况下,你也会看到它生成一个“callvirt”。 为什么这样做?

我回过头来看过我的语言设计笔记,他们非常清楚地说我们决定在12/13/1999使用callvirt。 不幸的是,他们没有抓住我们这样做的理由,所以我将不得不离开我的记忆。

我们收到了某人的报告(很可能是一个使用C#的.NET组(当时认为它还没有命名为C#))谁编写了一个在空指针上调用方法的代码,但是他们没有得到一个例外,因为该方法没有访问任何字段(即“this”为null,但方法中没有使用它)。 那个方法然后调用另一个方法,它确实使用了这一点并引发了一个exception,随后出现了一些令人头疼的问题。 在他们弄明白之后,他们给我们发了一张关于它的说明。

我们认为能够在null实例上调用方法有点奇怪。 Peter Golde做了一些测试,看看总是使用callvirt的性能影响是什么,它足够小,我们决定做出改变。

作为(也许)有趣的一面…… GetType()很不寻常,因为它不是 virtual – 这会导致一些非常非常奇怪的事情 。

(标记为维基,因为它与实际问题有些偏离主题)

编译器不知道第一个表达式中o的实际类型,但它知道第二个表达式中的实际类型。 看起来它一次只看一个声明。

这很好,因为C#在很大程度上依赖于JIT进行优化。 在这么简单的情况下,两个调用都很可能在运行时成为实例调用。

我不认为callvirt是为非虚方法发出的,但即使它是,也没有问题,因为该方法永远不会被覆盖(出于显而易见的原因)。

我猜测它是因为第一个分配给一个变量,它可能包含一个可以覆盖GetType的另一个类型的下载实例(即使我们可以看到它没有); 第二个永远不会是Object之外的任何东西。