网站建设实训致谢语,陕西交通建设集团网站贴吧,为什么一个网站做中英文双语版,深圳仿站定制模板建站本文将介绍以下内容#xff1a;
什么是继承#xff1f;继承的实现本质1. 引言
关于继承#xff0c;你是否驾熟就轻#xff0c;关于继承#xff0c;你是否了如指掌。
本文不讨论继承的基本概念#xff0c;我们回归本质#xff0c;从编译器运行的角度来揭示.NET继承中的…本文将介绍以下内容
什么是继承继承的实现本质1. 引言
关于继承你是否驾熟就轻关于继承你是否了如指掌。
本文不讨论继承的基本概念我们回归本质从编译器运行的角度来揭示.NET继承中的运行本源来发现子类对象是如何实现了对父类成员与方法的继承以最为简陋的示例来揭示继承的实质阐述继承机制是如何被执行的这对于更好的理解继承是必要且必然的。
2. 分析
下面首先以一个简单的动物继承体系为例来进行说明 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类并且方法列表中也是按照顺序排列的父类在前子类在后其原因和字段大同小异留待读者自己体味。
结合我们的分析过程现在将对象创建的过程以简单的图例来揭示其在内存中的分配情形如下 从我们的分析和上面的对象创建过程可见对继承的本质我们有了更明确的认识对于以下的问题就有了清晰明白的答案
继承是可传递的子类是对父类的扩展必须继承父类方法同时可以添加新方法。子类可以调用父类方法和字段而父类不能调用子类方法和字段。虚方法如何实现覆写操作使得父类指针可以指向子类对象成员。new关键字在虚方法继承中的阻断作用。
你是否已经找到了理解继承、理解动态编译的不二法门。
3. 思考
通过上面的讲述与分析我们基本上对.NET在编译期的实现原理有了大致的了解但是还有以下的问题一定会引起一定的疑惑那就是 Bird bird2 new Chicken();
这种情况下bird2.ShowType应该返回什么值呢而bird2.type有该是什么值呢有两个原则是.NET专门用于解决这一问题的
关注对象原则调用子类还是父类的方法取决于创建的对象是子类对象还是父类对象而不是它的引用类型。例如Bird bird2 new Chicken()时我们关注的是其创建对象为Chicken类型因此子类将继承父类的字段和方法或者覆写父类的虚方法而不用关注bird2的引用类型是否为Bird。引用类型不同的区别决定了不同的对象在方法表中不同的访问权限。注意 根据关注对象原则那么下面的两种情况又该如何区别呢 Bird bird2 new Chicken(); Chicken chicken new Chicken(); 根据我们上文的分析bird2对象和chicken对象在内存布局上是一样的差别就在于其引用指针的类型不同bird2为Bird类型指针而chicken为Chicken类型指针。以方法调用为例不同的类型指针在虚拟方法表中有不同的附加信息作为标志来区别其访问的地址区域称为offset。不同类型的指针只能在其特定地址区域内进行执行子类覆盖父类时会保证其访问地址区域的一致性从而解决了不同的类型访问具有不同的访问权限问题。 执行就近原则对于同名字段或者方法编译器是按照其顺序查找来引用的也就是首先访问离它创建最近的字段或者方法例如上例中的bird2是Bird类型因此会首先访问Bird_type注意编译器是不会重新命名的在此是为区分起见如果type类型设为public则在此将返回“Bird”值。这也就是为什么在对象创建时必须将字段按顺序排列而父类要先于子类编译的原因了。思考 1. 上面我们分析到bird2.type的值是“Bird”那么bird2.ShowType()会显示什么值呢答案是“Type is Chicken”根据本文上面的分析想想到底为什么 2. 关于new关键字在虚方法动态调用中的阻断作用也有了更明确的理论基础。在子类方法中如果标记new关键字则意味着隐藏基类实现其实就是创建了与父类同名的另一个方法在编译中这两个方法处于动态方法表的不同地址位置父类方法排在前面子类方法排在后面。 4. 结论
在.NET中如果创建一个类则该类总是在继承。这缘于.NET的面向对象特性所有的类型都最终继承自共同的根System.Object类。可见继承是.NET运行机制的基础技术之一一切皆为对象一切皆于继承。本文从基础出发深入本质探索本源分析疑难比较鉴别。对于什么是继承这个话题希望每个人能从中寻求自己的答案理解继承、关注封装、玩转多态是理解面向对象的起点希望本文是这一旅程的起点。
[祝福] 仅以此篇献给我的老师们汤文海老师陈桦老师。 参考文献
USADon Box, Essential .NET
中国虫虫从编译的角度看对象