首页 > 代码库 > C#继承里的【就近原则】

C#继承里的【就近原则】

参考博客:http://www.cnblogs.com/waynechan/p/3570702.html#2883966

参考书籍:《你必须知道的.NET》,作者王涛http://www.cnblogs.com/anytao/

用三个类来描述C#继承中的就近原则:Animal类,Bird类,Chicken类

1.类关系

类之间的关系如下:

 

    public abstract class Animal
    {
        private string AnimalField = "";//某个字段

        public abstract void ShowType();//显示类型

        public void Eat()
        {
            Console.WriteLine("Animal Can Eat!");
        }
    }
    public class Bird : Animal
    {
        public string Type = "BirdType";
        public override void ShowType()
        {
            Console.WriteLine("this type is {0}", Type);
        }
        private string color;
        public string Color
        {
            get { return color; }
            set { color = value; }
        }
    }
    public class Chicken : Bird
    {
        private new string Type = "ChickenType";
        public override void ShowType()
        {
            Console.WriteLine("this type is {0}", Type);
        }
        public void ShowColor()
        {
            Console.WriteLine("this color is {0}", Color);
        }
    }

 

 

 

2.测试

执行如下代码的输出结果会是什么?

    static void Main(string[] args)
        {
            Bird bird = new Chicken();
            Console.WriteLine(bird.Type);   //bird.Type是什么?
            bird.ShowType();                //bird.Showtype()是什么?
            Console.ReadKey();
        }

3.结果

输出结果是

技术分享

4.原因分析

首先介绍两个C#中对象的原则

○ 关注对象原则:Bird bird=new Chicken(); 我们关注的应该是new的是什么类,也就是关注创建的是Chicken类型的对象;
○ 执行就近原则:首先访问离它创建最近的字段或者方法;

  4.1 根据内存分布原则,在Bird bird=new Chicken();时,栈上存储的是Bird类型的变量引用,而托管堆上的是Chicken类型的对象,内存分布图应该是这样子的。

技术分享

此时托管堆上字段有两个,分别是Bird_type、Chicken_type(注意:编译器不会重新命名,此处为了便于理解),那应该输出哪个才对?根据执行就近原则,bird.type 其中的bird是Bird类型的变量引用,所以,首先会访问 Bird_type="BirdType";

也就是说,在创建bird这个对象的时候,它第一时间找到的Type字段是Bird类中的字段,即BirdType,所以不管最终new的对象是Bird类的对象还是其子类的对象,bird.Type输出的内容都是BirdType,这个就是符合执行就近原则。

  4.2 由于Bird类中的ShowType()方法已经在Chicken类中被重写了,所以在内存分布图中的方法表上,只能找到Chicken.ShowType();而在Chicken类中, private string type = "Chicken"; type字段被赋值,所以输出结果为“this type is ChickenType”。

5.拓展

 5.1对象的创建过程

  对象的创建过程是按照顺序完成了对整个父类及其本身字段的内存创建,并且字段的存储顺序是按照类的高低层次来的,最高层的类排在最前面,如果父类和子类出现了同名字段,则子类创建的时候,编译器会自动加与区别这是两个不同的字段,比如:type字段,Bird_type、Chicken_type 这样子,到了这一步,用示例图来表示是这样子的:

技术分享

  5.1方法表 

  方法表是在什么时候创建的:     

       类第一次加载到AppDomain时完成的,留意到上图中的Type_Handle没有,在对象创建时,将附加成员Type_Handle指向方法表在LoaderHeap(加载堆)上的地址,也就是先有方法表再有对象,这样就完成了对象与动态方法表的关联操作。

  方法表是如何生成的?

       方法表的创建跟字段的创建过程类似,也是逐层递归知道Object类,Chicken子类生成方法列表时,先将Bird类所有虚方法复制一份,如果Chicken类有override父类的虚方法,则子类方法覆盖相应的虚方法,同时添加子类新方法,并且顺序也是父类在前,子类在后,这个顺序很重要,在后面一节方法调用的时候,就会跟这个顺序有关,也是很多面试题的考点。
总结:方法表 = 父类没有被override的方法 + 子类子类的方法,所以到了这一步,用示例图来表示应该是这样:

 

技术分享

6.总结

  我们可以理解继承的本质,无论把Bird.type设置为Public还是Private,父类的字段都早就已经存在子类对象所在托管堆的内存分配空间中了,只是设置为Private的时候,子类对象无法访问而已。

  博客图解来源于http://www.cnblogs.com/waynechan/p/3570702.html#2883966

 

C#继承里的【就近原则】