用三个类来描述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(加载堆)上的地址,也就是先有方法表再有对象,这样就完成了对象与动态方法表的关联操作。
方法表是如何生成的?