最近对CLR的本质部分非常感兴趣,也看了园子里面很多高手的文章,特别是AnyTao同志的[你必须知道的.NET]系列文章。AnyTao同志在继承本质论中,提出了使用关注对象和就近原则的方法来确认继承类具体调用方法的问题。文章非常精彩,下面的回复更加精彩,对文章进行了必要的补充。
这篇文章的中,具体集中关注了以下这个问题
一个被声明为基类(A),却使用派生类(C)进行实例化的对象,其调用的方法(foo,M)到底是基类的,还是派生类的?
在回复中,有一个例子很好的说明了这个问题。
class A
{
public virtual void Foo()
{
Console.WriteLine("Call on A.Foo()");
}
public virtual void M()
{
Console.WriteLine("Call on A.M()");
}
}
class C : A
{
public override void Foo()
{
Console.WriteLine("Call on C.Foo()");
}
public virtual new void M()
{
Console.WriteLine("Call on C.M()");
}
}
class D
{
static void Main()
{
A c1 = new C();
c1.Foo();//关注对象
c1.M();//执行就近
Console.ReadLine();
}
}
在这个例子中,基类A的方法Foo和M都是虚方法,这些方法可以被(非必须)覆盖。派生类C则覆盖(重写)了基类的Foo方法,同时在重新定义了自己的M方法。(由于使用了New关键字,说明这个方法是C类专有的,向基类隐藏了该方法)。
作者在文章中,说明了一个类的实例将其TypeHandler指针和一个类型的MethodTable进行了关联(具体和什么类型的MethodTable进行关联,要看New关键字后面的类型名称),这样的话,一个类的实例,在调用具体方法的时候,其实就是到关联的方法表中去查询方法的首地址(具体是可执行代码还是IL,取决于该方法是否被执行过),这个就是关注对象原则。
此图引用自原文回复中的图
如果这样的话(根据这张图来看),实例c1应该分别调用C.Foo()和C.M()方法。但是通过就近原则,由于C1在声明的时候是A类型,所以M方法使用了A类的A.M()方法。
原文中的关注对象原则,说得很好了,但是,就近原则,我没有完全理解。刚才想了一下,其实,如果将上面这个图画得再完整些,可能就可以理解为什么foo和M分别调用基类和派生类的方法了(不需要理解就近原则)。
在原文的回复的图中,如果不考虑Object对象的方法的话,A类型的MethodTable,没有大的问题,问题是C类型的MethodTable。这个MethodTable有两个问题,其一,在图上只写了一个Foo()和一个M()方法,其实我觉得,M()方法应该写二个才对,其二,没有标明方法是继承于基类的方法,还是派生类本身自己的方法(例如重写的方法和New的方法)。
重新画了一个图,可能大家能明白我的意思:
之所以我想这么画图,基于以下理由:
在生成派生类methodTable的时候,首先会拷贝一张基类的Methodtable,这样的话,一开始C的Methodtable应该和A的一模一样。然后,由于C重写了A的Foo()方法,所以,将C的Methodtable的MethodTable的A.Foo()被替换为C.Foo().然后因为使用了New操作符新建了一个C.M()方法,这个方法理应追加到C的Methodtable里面,而不是替换原来的A.M()方法。
这样的话,C的Methodtable里面的内容就完整了。
在调用方法的时候,我们是使用对象的引用来调用方法的,既然是对象的引用,具体调用什么方法,应该考虑到引用的类型。
这里的c1的引用的类型是A类型,所以检索c1的Methodtable(也就是类型C的Methodtable)时候,它始终要将自己是A类型放在首位来检索,所以它发现两个M方法的时候,就会毫不犹豫的使用方法名称和类型名称都匹配A.M()方法作为检索结果了。当然如果没有两者都匹配的方法,就只能选择一个方法名称一样的方法作为检索结果了C.Foo()。
如果在声明c1的时候,使用的是C类型,则c1.M()方法将毫无疑问的选择C.M()方法来执行。。。。。
以上只是个人推断,CLR的事情,太复杂,我也只能靠现象和想象力来解释一些问题了。。。。。
感谢AnyTao的文章,虽然最近才开始读,但是受益匪浅。。。。。
本人是C#的初学者,如果文章有什么问题,请大家拍砖指教。