一、引言
所谓万事开头难,哪怕是写一个学习笔记,以前看惯别人写的博客文章,当时看了就看了,只有一种感觉就是解决了心中的好久疑团,现在自己去写才了解,也许你看了只有短短几分钟,可别人却下了功夫,用了很多心,在此对那些分享文章的朋友表示敬意,想把自己理解的东西写出来十分不易,何况自己可能对写的东西也不自信,害怕自己的错误理解影响了别人,这是十分罪过的,费话不多说,在此表示,在此写的只是自己在学习的过程中自己的一些理解,只作为自己的学习笔记。
此次主要想把自己对多态机制的一些理解心得写下,写此文之前有看过网上的一篇关于讲多态机制的一篇文章,此笔记有多处是借鉴其博文内容,但是我为了使得自己理解更清楚,加了许多自己的理解
参考博文:http://www.cnblogs.com/qianyz/archive/2011/11/04/2235237.html
二、多态的概念
关于继续和多态的概念,各种资料和书籍中都有各样的解释,因为OO的概念并不是微软一个人提出来的,所以对于多态的概念没有一个确定的定义
MSDN 上面的定义:通过继承,一个类可以有多种类型:可以用作它自己的类型,任何基类型,或者在实现接口时用作任何接口的类型。
三、CLR如何创建运行时对象
在讲多态性之前,我们觉得有必要了解了一下CLR是如何创建运行时对象,对于CLR创建运行对象的全部过程很复杂,在此不打算细说,但不影响我们去了解多态的机制,在此我主要理解几个重要的概念和环节就可以,具体想深入的去了解.NET框架内部CLR如何创建运行时对象,参见http://www.microsoft.com/china/MSDN/library/netFramework/netframework/JITCompiler.mspx?mfr=true
在此,我们需要了解下面两个概念:类型对象和对象实例:
类型对象:类型对象是类型本身如(Object、String等),JIT在编译一段代码之前会检查这段代码中用到的所有类型,并且会在GC堆中检索这些类型对象是否已在GC堆中创建,如果没有,则会在GC堆中为其分配内存,并创建类型对象,在创建类型对象的过程里包括对类型对象中方法槽表创建,类型对象主要包括,类型对象指针、同步块索引、静态字段、方法表,在此要说明的是,类型对象的创建发生在JIT编译时期.
对象实例:某个具体类型对象的实例,一般通过new的方式在GC中创建,创建的过程主要包括初始化实例字段,对象类型创建后包括:类型对象指针、同步块索引、实例字段,其中类型对象指针指向对应的类型对象,具体new的过程参见CLR VIA C#第三版中的介绍,在此我又要说明的是,对象实例的创建发生在运行期.
在此我们可以知道对于所有的引用类型的实例对象(包括本身的类型对象),都包括类型对象指针和同步块索引.
对于对象实例来讲,类型对象指针指向的是对应的类型对象
对于类型对象来本身来讲,类型对象指针指向的是System.Type,其实类型对象我们其实也可以把当作视作为一个对象
在此对于同步块索引,这里暂不讨论
因此理解上面的两个概念后,我们知道对于所有形为的描述(方法)都是放在类型对象中的方法糟表中。
注意:
1、 对于对象实例,类型对象指针(TypeHandle)在有些文章中称为方法表指针,在此文中都称为类型对象指针
2、 对于类型对象,如BaseClass类型对象,在有些文章中称为BaseClass方法表,在此文中都称为类型对象,因为称为BaseClass方法表 从名称很容易让人误解,以为整这个类型对象都是一个方法列表,其实类型对象除了方法表,还有很多其他的内容
3、 对于类型对象中的方法表,在此文中都称为方法槽表(Method Slots Table)
类型对象与对象实例的创建过程:
以下面的一段代码和图表说明:
public class BaseClass
{
public virtual void VirtualMethod()
{
Console.WriteLine("BaseClass VirtualMethod");
}
public static void StaticMethod()
{
Console.WriteLine("BaseClass StaticMethod");
}
public void InstanceMethod()
{
Console.WriteLine("BaseClass InstanceMethod");
}
}
图-1
过程分析:
1、 JIT编译时,注意是J IT编译时,遇到BaseClass Instance=new BaseClass();这行代码时,会先检查GC堆中是否已加载该类型对象,如无未加载,则计算并分配内存,并加载BaseClass类型,构建BaseClass类型对象,且该类型对象中记录了方法槽表,静态字段等。
2、 在JIT编译器生成汇编代码后,运行时,new一个BaseClass类型对象的实例时,计算分配内存,初始实例字段(包括基类的所有实例字段),并将类型对象指针指向BaseClass类型对象,并返回对象实例地址给栈上的Instance
对于JIT编译期创建类型对象的过程中,我们主要要关心的是,类型对象中方法槽表的创建机制,这里马上要讲到我们的核心内容,方法槽表.
方法糟表
方法槽表就是类型对象中的记录方法描述的那一部分,以图1为例的话,方法表就是图1的橙色区域.
为了更好分析,我们再看一个更详细结构图,如图2:
图-2
图2中的白色区域代表着一个类型对象的方法槽表,方法槽表里面记录着类型对象拥有的所有方法,对于这个方法槽表的创建过程,我们可按下面的顺序去了解:
1、 JIT加载类型对象
2、 遍历基类的虚方法(检查基类的virtual)并复制到方法糟表中
3、 检查类型对象是否对基类的虚方法是否有重写(检查override),如有重写则覆盖对应的虚方法
4、 添加类型对象自已新增加的虚方法(检查virtual)
5、 添加类型对象的构造方法(.ctor)
6、 添加类型对象的静态方法(检查static)
7、 添加类型对象的实例方法
方法糟表内存布局和层次结构:
观察图1和图2方法槽表的结构后,我们很容易发现,在图1和图2的方法槽表中,前4个方法都是Object类型对象中的4个虚方法,结合方法槽表的创建过程及顺序,我们大概可以知道一个类型对象中的方法槽表的内存布局了。
方法槽表的内存布局规则:类型对象永远是按照:基类虚方法—子类虚方法—构造方以—静态方法—实例方法