[你必须知道的.NET]第十五回:继承本质论

本文将介绍以下内容:

  • 什么是继承?
  • 继承的实现本质

 

 

1. 引言

关于继承,你是否驾熟就轻,关于继承,你是否了如指掌。

本文不讨论继承的基本概念,我们回归本质,从编译器运行的角度来揭示.NET继承中的运行本源,来发现子类对象是如何实现了对父类成员与方法的继承,以最为简陋的示例来揭示继承的实质,阐述继承机制是如何被执行的,这对于更好的理解继承,是必要且必然的。

2. 分析

下面首先以一个简单的动物继承体系为例,来进行说明:

    public abstract class Animal

    {

        public abstract void ShowType();

        public void Eat()

        {

            Console.WriteLine("Animal always eat.");

        }

    }

    public class BirdAnimal

    {

        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运行机制的基础技术之一,一切皆为对象,一切皆于继承。本文从基础出发,深入本质探索本源,分析疑难比较鉴别。对于什么是继承这个话题,希望每个人能从中寻求自己的答案,理解继承、关注封装、玩转多态是理解面向对象的起点,希望本文是这一旅程的起点。
 

[祝福]
仅以此篇献给我的老师们:汤文海老师,陈桦老师。 

参考文献

(USA)Don Box, Essential .NET

(中国)虫虫,从编译的角度看对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438739.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

海岸鸿蒙2018年标准物质,海岸鸿蒙——20年权威的标准物质研制单位

摘要:海岸鸿蒙创办于1996年,是专业从事国家标准物质研发、生产、销售的高新技术企业。海岸鸿蒙创办于1996年,是专业从事国家标准物质研发、生产、销售的高新技术企业。海岸鸿蒙自创办以来,一直秉持“以市场为导向,以科…

[你必须知道的.NET]第十六回:深入浅出关键字---using全接触

本文将介绍以下内容: using指令的多种用法using语句在Dispose模式中的应用1. 引言 在.NET大家庭中,有不少的关键字承担了多种角色,例如new关键字就身兼数职,除了能够创建对象,在继承体系中隐藏基类成员,还在…

华为双系统是鸿蒙系统吗,华为p50pro是鸿蒙系统吗-华为p50pro有双系统吗

华为p50pro的外观基本上就是延续了上一代的风格,没有什么太大的变化,不过影像能力还是非常令人期待的,下面一起来了解华为p50pro的系统方面,看看有没有你暂所不知的消息。近日,有消息曝光了华为 P50 Pro将会有 Harmony…

[你必须知道的.NET]第十七回:貌合神离:覆写和重载

本文将介绍以下内容: 什么是覆写,什么是重载覆写与重载的区别覆写与重载在多态特性中的应用1. 引言 覆写(override)与重载(overload),是成就.NET面向对象多态特性的基本技术之一,两…

鸿蒙系统正式开源,余承东:鸿蒙系统正式开源,友商也可以使用!

鸿蒙OS2.0正式开源:从PPT走向前台,该谁脸红了?在9月10日的开发者大会上,华为鸿蒙2.0发布,已经不再是某些人嘴中的PPT、又哄又蒙的鸿蒙了。说鸿蒙是PPT的言论,在前不久还能看到,如今鸿蒙已经发布…

[你必须知道的.NET]第十八回:对象创建始末(上)

本文将介绍以下内容: 对象的创建过程内存分配分析内存布局研究1. 引言 了解.NET的内存管理机制,首先应该从内存分配开始,也就是对象的创建环节。对象的创建,是个复杂的过程,主要包括内存分配和初始化两个环节。例如&…

[你必须知道的.NET]第十九回:对象创建始末(下)

本文将介绍以下内容: 对象的创建过程内存分配分析内存布局研究接上回[第十八回:对象创建始末(上)],继续对对象创建话题的讨论>>> 2.2 托管堆的内存分配机制 引用类型的实例分配于托管堆上,而线…

android material 颜色值,Android Material Colors 谷歌 Material Design 标准颜色

Android Material Colors谷歌 Material Design 标准颜色。调色板资源文件通过 doc-getter 自动抓取生成。运行 Demo 来查看效果。UsageGradlecompile com.takwolf.android.materialdesign:color:0.0.1Stylecolor/material_indigo_500color/material_indigo_700color/material_p…

突然吐字不清_要注意说话吐字不清小心是脑中风前兆

任何疾病发病之前往往会有一些前兆出现,像是脑中风这种疾病在发作之前也是有前兆的,若是朋友们能够尽早的发现就能够在发病前进行治疗了。朋友们要注意的是说话吐字不清小心是脑中风前兆,这是脑中风发病前的典型前兆,还有头晕、呕…

c++ vs release没有exe_未来安全 | 第一次Geant4培训总结 | 有没有你关注的问题呢?...

Geant4简介Geant4是蒙卡工具包,模拟很多粒子,记录一些统计量,用这些统计量去估计真实的物理实验的结果。蒙卡模拟程序,从最老的MCNP,到PENELOPE,FLUKA等。MCNP是用输入卡片(输入文件)实现的,在一…

[你必须知道的.NET]第二十一回:认识全面的null

说在,开篇之前 null、nullable、??运算符、null object模式,这些闪亮的概念在你眼前晃动,我们有理由相信“存在即合理”,事实上,null不光合理,而且重要。本文,从null的基本认知开始&#xff0…

html用表格做个人主页页面,利用HTML的表格进行页面布局

在DIVCSS布局出现前,基本上所用的网站都使用table来进行布局。因为table布局很简单,但是table布局不灵活且代码很多。下面将介绍怎样使用table来进行布局。实例:我们来布局一个常见网站后台程序的架构。布局图如下所示:实例代码&a…

cesium坡度坡向分析_景观设计分析图制作技巧到底是什么?

国外设计中,人们都开始用动态分析图啦厉害的不要不要啊!如果你也想做如此高逼格的分析图记得往下看!景观设计分析为:人文,背景,区位,现状,历史,功能,流线&…

采购模板html5,蓝色的采购信息管理系统手机界面wap模板

手机版大气信息管理系统界面模板,采购信息管理wap手机模板下载。资源下载此资源下载价格为4D币,请先登录资源文件列表codedown123-080801-25/business_log.html , 6657codedown123-080801-25/choose.html , 6869codedown123-080801-25/css/animate.css ,…

adobe audition cs6 能打开mpcm文件吗?_Adobe全家桶出现这些漏洞,赶紧上官网下载补丁吧...

导语:Adobe已发布了计划的2020年7月安全更新,涵盖了五个不同产品领域的缺陷:Creative Cloud Desktop;媒体编码器;下载管理器; 真正的服务;和ColdFusion。其中四个错误的严重性被评为严重,而其他…

详解CSS的相对定位和绝对定位

CSS的相对定位和绝对定位 一、Static 静态定位 通常情况下,我们元素的position属性的值默认为static 就是没有定位,元素出现在正常的文档流中,这个时候你给这个元素设置的left,right,bottom,top这些偏移属性都是没有效果的,不会生…

观看实验中微型计算机虚拟拆装演示,虚拟仿真实验 北斗一号微机原理虚拟仿真实验系统64位 v3.0...

下面我们对虚拟仿真实验 北斗一号微机原理虚拟仿真实验系统64位 v3.0文件阐述相关使用资料和虚拟仿真实验 北斗一号微机原理虚拟仿真实验系统64位 v3.0文件的更新信息。虚拟仿真实验 北斗一号微机原理虚拟仿真实验系统64位 v3.0“微机原理虚拟仿真实验”适用于《微机原理》《微…

[你必须知道的.NET]第二十二回:字符串驻留(上)---带着问题思考

走钢丝的人,在刺激中体验快感。带着问题思考,在问题上迸发火花。 或者给问题以答案,或者给答案以问题,你可能永远无法看清全部,但是总能从一点突破很多。事实的关键就在于面对问题,我该如何思考&#xff1…

springboot超详细教程_超详细便当袋教程 || 特殊时期,自己带饭最安心!

持续受疫情影响,闷在家里的广大网友们早就坐不住了。尤其是最近各地复工陆续开始,小心心是不是开始躁动了?终于可以出门放飞自我,放肆吃吃喝喝了嘛?再忍一忍呀同志们!疫情还没结束,病毒还没被消…

[你必须知道的.NET]第二十三回:品味细节,深入.NET的类型构造器

1 引言今天Artech兄在《关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释》一文中让我们认识了一个关于类型构造器调用执行的有趣示例,其中也相应提出了一些关于beforefieldinit对于类型构造器调用时机的探讨,对于我…