1、JIT编译器调用虚实例方法和非虚实例方法的区别
在讲本文的主题之前,让我们先来看一下method overriding和method hiding的区别。
图1 method hiding
图2 method overriding
从以上两图中可以发现,虽然new出来的Derived对象都隐式转型为Base基类型,但随后在基类型引用变量objBaseRefToDerived上调用Show方法时,却调用了不同的方法,一个来自基类型Base,一个来自子类型Derived。为什么会发生这种情况呢?
造成上述现象的原因与JIT编译器调用非虚实例方法和虚实例方法的方式有关。
对于method hiding的情形来说,当在引用变量objBaseRefToDerived上调用非虚实例方法Show时,JIT编译器会查找与发出调用的那个变量的类型(本例中就是Base)对应的类型对象(Base类型对象),然后在该类型对象中查找Show方法。注意,若在Base类型对象中未找到Show方法,JIT编译器会回溯类层次结构(一直回溯到Object),并在沿途的每个类型中查找Show方法。之所以能这样回溯,是由于每个类型对象都有一个字段引用了它的基类型。
而对于method overriding的情形来说,当在引用变量objBaseRefToDerived上调用虚实例方法Show时,JIT编译器要在方法中生成一些额外的代码;方法每次调用都会执行这些代码。这些代码会首先检查发出调用的变量即objBaseRefToDerived,然后跟随地址找到发出调用的对象即Derived。之后,代码检查对象内部的类型对象指针成员,该成员指向对象的实际类型,即Derived类型。最后,代码在Derived类型对象的方法表中查找Show方法的记录项,对方法进行JIT编译(如果需要的话),再调用JIT编译好的代码。
2、实例对象和类型对象在运行时的职责划分
在运行时,实例对象负责分配字节容纳类型定义的所有实例数据字段、从基类型继承的实例字段,此外,还包括两个额外的成员,即类型对象指针和同步块索引。
定义类型时,可在类型内部定义静态数据字段。在运行时,该类型的类型对象负责为这些静态数据字段分配字节空间。此外,类型中定义的所有方法均在类型对象的方法表中有对应的记录项。在客户端程序中调用该类型定义的方法时,就是在类型对象的方法表中查找对应的记录项然后进行编译并执行的。
3、调用static静态方法的方式
在调用类型的静态方法时,CLR会直接定位到与定义静态方法的类型对应的类型对象,然后,JIT编译器在类型对象的方法表中查找与被调用的方法对应的记录项,之后,对方法进行JIT编译,最后,调用JIT编译好的本机代码执行。
4、类型对象的本质
类型对象本质上也是对象,它也包含类型对象指针成员。CLR在创建类型对象时,必须初始化该成员,那么,该把它初始化成什么呢?
当CLR开始在一个进程中运行时,会立即为MSCorLib.dll中定义的System.Type类型创建一个特殊的类型对象。其余所有类型的类型对象都是该类型的“实例”,因此,这些类型对象的类型对象指针成员会初始化成对System.Type类型对象的引用。当然,System.Type类型对象也是对象,也有类型对象指针成员,不过,这个指针指向它自身。
System.Object的GetType方法返回存储在指定对象的“类型对象指针”成员中的地址,也即,返回指向对象的类型对象的指针,这样就可以判断系统中任何对象包括类型对象本身的真实类型了。