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 bird = new Bird();
Bird bird创建的是一个Bird类型的引用,而new Bird()完成的是创建Bird对象,分配内存空间和初始化操作,然后将这个对象引用赋给bird变量,也就是建立bird变量与Bird对象的关联。
(2)我们从继承的角度来分析CLR在运行时如何执行对象的创建过程,因为继承的本质正体现于对象的创建过程中。
在此我们以Chicken对象的创建为例,首先是字段,对象一经创建,会首先找到其父类Bird,并为其字段分配存储空间,而Bird也会继续找到其父类Animal,为其分配存储空间,依次类推直到递归结束,也就是完成System.Object内存分配为止。我们可以在编译器中用单步执行的方法来大致了解其分配的过程和顺序,因此,对象的创建过程是按照顺序完成了对整个父类及其本身字
段的内存创建,并且字段的存储顺序是由上到下排列,最高层类的字段排在最前面。其原因是如果父类和子类出现了同名字段,则在子类对象创建时,编译器会自动认为这是两个不同的字段而加以区别。
从我们的分析和上面的对象创建过程中,我们应对继承的本质有了以下更明确的认识:
— 继承是可传递的,子类是对父类的扩展,必须继承父类方法,同时可以添加新方法。
— 子类可以调用父类方法和字段,而父类不能调用子类方法和字段。
— 虚方法如何实现覆写操作,使得父类指针可以指向子类对象成员。
— 子类不光继承父类的公有成员,同时继承了父类的私有成员,只是在子类中不被访问。
— new关键字在虚方法继承中的阻断作用。
两个重要原则:
1.关注对象原则:调用子类还是父类的方法,取决于创建的对象是子类对象还是父类对象,而不是它的引用类型。例如Bird bird2 = new Chicken()时,我们关注的是其创建对象为Chicken类型,因此子类将继承父类的字段和方法,或者覆写父类的虚方法,而不用关注bird2的引用类型是否为Bird。引用类型的区别决定了不同的对象在方法表中不同的访问权限。
2.执行就近原则:对于同名字段或者方法,编译器是按照其顺序查找来引用的,也就是首先访问离它创建最近的字段或者方法,例如上例中的bird2,是Bird类型,因此会首先访问Bird_type(注意编译器是不会重新命名的,在此是为区分起见),如果type类型设为public,则在此将返回“Bird”值。这也就是为什么在对象创建时必须将字段按顺序排列,而父类要先于子类编译的原因了。
上面我们分析到bird2.type的值是“Bird”,那么bird2.ShowType()会显示什么值呢?答案是“Type is Chicken”
(本文处:你必须知道的.NET)