示例6.1 非虚方法的分发
Public class Base
{
Protected void DoItForReal(){a();}
Public void DoIt(){this.DoItForReal();}
}
Public Class Derived:Base
{
Protected new void DoItForReal(){b();}
}
Void Main(){
Base r1=new Derived();
R1.DoIt(); //调用a()
}
因就是在不了解基类型内部工作相关知识的情况下,几乎不可能知道使用哪种途径工作。
示例6.4 虚方法的难题
Public class Base{
Protected virtual void DoitPorReal(){a();}
Public void DoIt(){this.DoItForReal();}
}
Public class Derived1:Base{
Protected override void DoItForReal(){
Base.DoItForReal();
B(); //基类型方法工作之后调用方法
}
}
Public class Derived2:Base
{
Protected override void DoItForReal(){
B(); //基类型方法工作之前调用方法
Base.DoItForReal();
}
}
接口、虚方法和抽象方法
CLR与c++、COM相比,在处理对象和接口类型上是不同的。在C++和COM中中,一个给定的具体类型对于每个基类型或支持的接口类型,都有一个方法表。而在CLR中中,一个给定的具体类型只有一个方法表。以此类推,一个基于CLR的对象只有一个类型句柄。对于c++和COM而言,由于这个原因,CLR的castclass在以c++的dynamic cast或COM的Querylnterface的同样方式下工作时,将不会产生第二个指针值。
每个CLR类型都有一个与类型层次结构无关的方法表。方法表中开始的插槽将对应于自基类型声明的虚方法,紧随这些插槽之后的表项,将对应于派生类型引入的新的虚方法。CLR是这样安排这个方法表的区域的:对于一个特定的声明接口,它的所有方法表插槽都将被连续地安排。然而,由于不同的具体类型可能支持不同的接口.咽此对于支持给定接口的所有类型,方法表对应表项范围的绝对偏移量可能是不同的。为了处理这种变化,当通过基于接口的对象引用调用虚力法时,CLR将添加另外一层间接性。
CORINFO_CLASS_STROCT包含指向描述类型所支持接口的哪个表的指针。isinst和casLclass操作码使用其中的一个表确定类型是否支持给定的接口。第二个表是接口偏移量表,当由基于接口的对象引用分发虚方法调用时.CLR将会使用它。
如图6.4所示,接口偏移量表(
interface offset table)在类型方法表中是一个偏移量的数组。对于CLR初始化的每个接口类型在这个表中都有一个表项,而与该类型是否支持这个接口无关。当CLR初始化接口类型时,在这个表中它将赋予它们一个基于零的索引。当CLR初始化一个具体类型时,它将为该类型分配一个新的接口偏移量表。这个接口偏移量表将被稀疏地填充,但它至少有被声明的接口索引。当CLR忉始化一个具体类型时,它将适当的方法表偏移量存储到所支持接口对应的表项中,这样哑填充接口偏移量表。因为CLR的验证器保证基于接口的引用只引用所声明类型的对象。对于不支持的接口的表项是不会用到的,并且,其内容也是无关紧要的。
如图6.4所示,一个基于接口引用的方法调用,必须首先在该接口相应的方法表中定位表项的范围,在CLR找到这个偏移量后,它将添加方法相关的偏移量,并且分发调用。与基于类引用的虚方法调用相比,基于接口引用的方式导致代码有点臃肿,并且速度慢一些,其原因就是要使用额外的间接性.不过,如果相同的对象引用被多次使用,那么,对于JIT编译器来说,优化这种额外的间接性就是可能的。