[你必须知道的.NET]第十五回:继承本质论
本文將介紹以下內(nèi)容:
- 什么是繼承?
- 繼承的實現(xiàn)本質(zhì)
?
?
1. 引言
關(guān)于繼承,你是否駕熟就輕,關(guān)于繼承,你是否了如指掌。
本文不討論繼承的基本概念,我們回歸本質(zhì),從編譯器運行的角度來揭示.NET繼承中的運行本源,來發(fā)現(xiàn)子類對象是如何實現(xiàn)了對父類成員與方法的繼承,以最為簡陋的示例來揭示繼承的實質(zhì),闡述繼承機制是如何被執(zhí)行的,這對于更好的理解繼承,是必要且必然的。
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);
??????? }
??? }
然后,在測試類中創(chuàng)建各個類對象,由于Animal為抽象類,我們只創(chuàng)建Bird對象和Chicken對象。
????public?class?TestInheritance
??? {
????????public?static?void?Main()
??????? {
????????????Bird?bird =?new?Bird();
????????????Chicken?chicken =?new?Chicken();
??????? }
??? }
下面我們從編譯角度對這一簡單的繼承示例進行深入分析,從而了解.NET內(nèi)部是如何實現(xiàn)我們強調(diào)的繼承機制。
(1)我們簡要的分析一下對象的創(chuàng)建過程:
????????????Bird?animal =?new?Bird();
Bird bird創(chuàng)建的是一個Bird類型的引用,而new Bird()完成的是創(chuàng)建Bird對象,分配內(nèi)存空間和初始化操作,然后將這個對象賦給bird引用,也就是建立bird引用與Bird對象的關(guān)聯(lián)。
(2)我們從繼承的角度來分析在編譯器編譯期是如何執(zhí)行對象的創(chuàng)建過程,因為繼承的本質(zhì)就體現(xiàn)于對象的創(chuàng)建過程。
在此我們以Chicken對象的創(chuàng)建為例,首先是字段,對象一經(jīng)創(chuàng)建,會首先找到其父類Bird,并為其字段分配存儲空間,而Bird也會繼續(xù)找到其父類Animal,為其分配存儲空間,依次類推直到遞歸結(jié)束,也就是完成System.Object內(nèi)存分配為止。我們可以在編譯器中單步執(zhí)行的方法來大致了解其分配的過程和順序,因此,對象的創(chuàng)建過程是按照順序完成了對整個父類及其本身字段的內(nèi)存創(chuàng)建,并且字段的存儲順序是由上到下排列,object類的字段排在最前面,其原因是如果父類和子類出現(xiàn)了同名字段,則在子類對象創(chuàng)建時,編譯器會自動認為這是兩個不同的字段而加以區(qū)別。
然后,是方法表的創(chuàng)建,必須明確的一點是方法表的創(chuàng)建是類第一次加載到CLR時完成的,在對象創(chuàng)建時只是將其附加成員TypeHandle指向方法列表在Loader Heap上的地址,將對象與其動態(tài)方法列表相關(guān)聯(lián)起來,因此方法表是先于對象而存在的。類似于字段的創(chuàng)建過程,方法表的創(chuàng)建也是父類在先子類在后,原因是顯而易見的,類Chicken生成方法列表時,首先將Bird的所有方法拷貝一份,然后和Chicken本身的方法列表做以對比,如果有覆寫的虛方法則以子類方法覆蓋同名的父類方法,同時添加子類的新方法,從而創(chuàng)建完成Chicken的方法列表。這種創(chuàng)建過程也是逐層遞歸到Object類,并且方法列表中也是按照順序排列的,父類在前子類在后,其原因和字段大同小異,留待讀者自己體味。
結(jié)合我們的分析過程,現(xiàn)在將對象創(chuàng)建的過程以簡單的圖例來揭示其在內(nèi)存中的分配情形,如下:
?
從我們的分析,和上面的對象創(chuàng)建過程可見,對繼承的本質(zhì)我們有了更明確的認識,對于以下的問題就有了清晰明白的答案:
- 繼承是可傳遞的,子類是對父類的擴展,必須繼承父類方法,同時可以添加新方法。
- 子類可以調(diào)用父類方法和字段,而父類不能調(diào)用子類方法和字段。
- 虛方法如何實現(xiàn)覆寫操作,使得父類指針可以指向子類對象成員。
- new關(guān)鍵字在虛方法繼承中的阻斷作用。
你是否已經(jīng)找到了理解繼承、理解動態(tài)編譯的不二法門。
3. 思考
通過上面的講述與分析,我們基本上對.NET在編譯期的實現(xiàn)原理有了大致的了解,但是還有以下的問題,一定會引起一定的疑惑,那就是:
????????????Bird?bird2 =?new?Chicken();
這種情況下,bird2.ShowType應(yīng)該返回什么值呢?而bird2.type有該是什么值呢?有兩個原則,是.NET專門用于解決這一問題的:
- 關(guān)注對象原則:調(diào)用子類還是父類的方法,取決于創(chuàng)建的對象是子類對象還是父類對象,而不是它的引用類型。例如Bird bird2 = new Chicken()時,我們關(guān)注的是其創(chuàng)建對象為Chicken類型,因此子類將繼承父類的字段和方法,或者覆寫父類的虛方法,而不用關(guān)注bird2的引用類型是否為Bird。引用類型不同的區(qū)別決定了不同的對象在方法表中不同的訪問權(quán)限。
?
注意
根據(jù)關(guān)注對象原則,那么下面的兩種情況又該如何區(qū)別呢?
????????????Bird?bird2 =?new?Chicken();
????????????Chicken?chicken =?new?Chicken();
根據(jù)我們上文的分析,bird2對象和chicken對象在內(nèi)存布局上是一樣的,差別就在于其引用指針的類型不同:bird2為Bird類型指針,而chicken為Chicken類型指針。以方法調(diào)用為例,不同的類型指針在虛擬方法表中有不同的附加信息作為標(biāo)志來區(qū)別其訪問的地址區(qū)域,稱為offset。不同類型的指針只能在其特定地址區(qū)域內(nèi)進行執(zhí)行,子類覆蓋父類時會保證其訪問地址區(qū)域的一致性,從而解決了不同的類型訪問具有不同的訪問權(quán)限問題。
?
- 執(zhí)行就近原則:對于同名字段或者方法,編譯器是按照其順序查找來引用的,也就是首先訪問離它創(chuàng)建最近的字段或者方法,例如上例中的bird2,是Bird類型,因此會首先訪問Bird_type(注意編譯器是不會重新命名的,在此是為區(qū)分起見),如果type類型設(shè)為public,則在此將返回“Bird”值。這也就是為什么在對象創(chuàng)建時必須將字段按順序排列,而父類要先于子類編譯的原因了。
?
思考
1. 上面我們分析到bird2.type的值是“Bird”,那么bird2.ShowType()會顯示什么值呢?答案是“Type is Chicken”,根據(jù)本文上面的分析,想想到底為什么?
2. 關(guān)于new關(guān)鍵字在虛方法動態(tài)調(diào)用中的阻斷作用,也有了更明確的理論基礎(chǔ)。在子類方法中,如果標(biāo)記new關(guān)鍵字,則意味著隱藏基類實現(xiàn),其實就是創(chuàng)建了與父類同名的另一個方法,在編譯中這兩個方法處于動態(tài)方法表的不同地址位置,父類方法排在前面,子類方法排在后面。
?
4. 結(jié)論
在.NET中,如果創(chuàng)建一個類,則該類總是在繼承。這緣于.NET的面向?qū)ο筇匦?#xff0c;所有的類型都最終繼承自共同的根System.Object類。可見,繼承是.NET運行機制的基礎(chǔ)技術(shù)之一,一切皆為對象,一切皆于繼承。本文從基礎(chǔ)出發(fā),深入本質(zhì)探索本源,分析疑難比較鑒別。對于什么是繼承這個話題,希望每個人能從中尋求自己的答案,理解繼承、關(guān)注封裝、玩轉(zhuǎn)多態(tài)是理解面向?qū)ο蟮钠瘘c,希望本文是這一旅程的起點。
?
[祝福]
僅以此篇獻給我的老師們:湯文海老師,陳樺老師。?
參考文獻
(USA)Don Box, Essential .NET
(中國)蟲蟲,從編譯的角度看對象
總結(jié)
以上是生活随笔為你收集整理的[你必须知道的.NET]第十五回:继承本质论的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《王者荣耀》新英雄戈娅实机演示出炉:超飒
- 下一篇: 信用卡买房有积分吗