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

时间:2009-05-10 16:56:33

标签: c# compiler-construction il type-safety

我很想知道为什么会这样。请阅读下面的代码示例以及每个部分下面的注释中发出的相应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,这是否会造成类型安全问题?

5 个答案:

答案 0 :(得分:28)

请参阅Eric Gunnerson撰写的this旧博文。

这是帖子的文字:

为什么C#总是使用callvirt?

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

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

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

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

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

答案 1 :(得分:22)

只是玩得安全。

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

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

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

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

参见:Jeff Richter在这方面做得更好 - 在CLR中的“设计类型”一章中通过C#2nd Ed

答案 2 :(得分:3)

作为一个(或许)有趣的... ... GetType()是不寻常的,因为不是 virtual - 这会导致一些very, very odd things

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

答案 3 :(得分:1)

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

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

我不相信非虚拟方法会发出callvirt,但即使是这样,也没有问题,因为该方法永远不会被覆盖(原因很明显)。

答案 4 :(得分:0)

我会冒这个猜测,因为第一个分配给一个变量,它可能包含一个可以覆盖GetType的另一个类型的下载实例(即使我们看不到它);第二种情况永远不会是Object