下面首先以一个简单的动物继承体系为例,来进行说明:
public abstract class Animal
{
public abstract void ShowType();
public void Eat()
{
Console.WriteLine("Animal always eat.");
}
}
public class Bird: Animal
{
private string type = "Bird";
public override void ShowType()
{
Console.WriteLine("Type is {0}", type);
}
private string color;
public string Color
{
get { return color; }
set { color = value; }
}
}
public class Chicken : Bird
{
private string type = "Chicken";
public override void ShowType()
{
Console.WriteLine("Type is {0}", type);
}
public void ShowColor()
{
Console.WriteLine("Color is {0}", Color);
}
}
然后,在测试类中创建各个类对象,由于Animal为抽象类,我们只创建Bird对象和Chicken对象。
public class TestInheritance
{
public static void Main()
{
Bird bird = new Bird();
Chicken chicken = new Chicken();
}
}
下面我们从编译角度对这一简单的继承示例进行深入分析,从而了解.NET内部是如何实现我们强调的继承机制。
(1)我们简要的分析一下对象的创建过程:
Bird animal = new Bird();
Bird bird创建的是一个Bird类型的引用,而new Bird()完成的是创建Bird对象,分配内存空间和初始化操作,然后将这个对象赋给bird引用,也就是建立bird引用与Bird对象的关联。
(2)我们从继承的角度来分析在编译器编译期是如何执行对象的创建过程,因为继承的本质就体现于对象的创建过程。
在此我们以Chicken对象的创建为例,首先是字段,对象一经创建,会首先找到其父类Bird,并为其字段分配存储空间,而Bird也会继续找到其父类Animal,为其分配存储空间,依次类推直到递归结束,也就是完成System.Object内存分配为止。我们可以在编译器中单步执行的方法来大致了解其分配的过程和顺序,因此,对象的创建过程是按照顺序完成了对整个父类及其本身字段的内存创建,并且字段的存储顺序是由上到下排列,object类的字段排在最前面,其原因是如果父类和子类出现了同名字段,则在子类对象创建时,编译器会自动认为这是两个不同的字段而加以区别。
然后,是方法表的创建,必须明确的一点是方法表的创建是类第一次加载到CLR时完成的,在对象创建时只是将其附加成员TypeHandle指向方法列表在Loader Heap上的地址,将对象与其动态方法列表相关联起来,因此方法表是先于对象而存在的。类似于字段的创建过程,方法表的创建也是父类在先子类在后,原因是显而易见的,类Chicken生成方法列表时,首先将Bird的所有方法拷贝一份,然后和Chicken本身的方法列表做以对比,如果有覆写的虚方法则以子类方法覆盖同名的父类方法,同时添加子类的新方法,从而创建完成Chicken的方法列表。这种创建过程也是逐层递归到Object类,并且方法列表中也是按照顺序排列的,父类在前子类在后,其原因和字段大同小异,留待读者自己体味。