1. 程式人生 > >深入理解.net - 1.繼承的本質

深入理解.net - 1.繼承的本質

col ride 人的 所有 子類 alt 強烈 main 引用

最近偶然看到這個博客你必須知道的.net,作者6的飛起啊,幹貨十足,還是07年寫的。。。寫的也很贊,評論更精彩,在此強烈推薦一波,看的感覺就像沙漠裏發現了綠洲一樣,很興奮,意猶未盡,迫不及待的看完一篇再看下一篇,但是知識還是需要整理,沈澱的,那就寫博客吧,於是有了接下來的文章。本文將通過看此書和相關博客以及結合自己目前的理解所寫,如有不對之處,歡迎指正。

對象的創建過程

要了解繼承的本質首先我們要清楚一個對象的創建過程,這裏有個 Chicken 類:

public class Chicken 
{
    private  string type = "Chicken";
    
    public Chicken()
    {
    }
    
    public void ShowType()
    {            
        Console.WriteLine($"Type is {type}");
    }
}

當我們需要使用這個類的時候,我們通常是這樣寫的:

Chicken chicken = new Chicken(); 

它是如何工作的呢?先上圖:
技術分享圖片

具體過程如下:

  1. 首先執行的是 "Chicken chicken" 語句,即線程棧Stack上聲明了一個Chicken類型的引用chicken,此時值為null,Stack上內存分配由高到低地址開始創建, 而Heap上則相反;
  2. 執行 "new Chicken()" ,new 操作符會在托管堆(具體在GCH:Garbage Collection Heap)上申請創建實例的內存空間,初始化類的字段(Feild)信息,並調用構造函數。結合上圖,實例在GCHeap創建的詳細過程如下:
    • 對象實例地址的開始4個字節為SyncBlockIndex,指向SyncEntryTable,存儲的是多線程同步的一些信息,詳細內容可查看文章末尾參考連接;
    • 緊接著是TypeHandle,指向的是Loader Heap(加載器堆) 中的MethodTable,而MethodTable中存儲該類型的靜態字段,方法表以及實現的接口等信息,從這裏我們也就清楚了,一個類不管實例成員有多少,static成員和方法信息只存儲一份在內存中,並先於實例創建,使用的時候則通過TypeHandle到MethodTable查找,並編譯成cpu指令,存儲在內存中,以後再使用時則直接執行該指令即可。
    • 初始完SyncBlockIndex和TypeHandle,則加載Chicken類型的字段信息,本文初始的也就是type字段(字符串信息的存儲比較特殊實際存儲模型詳見此鏈接),另外強調的是屬性不在此處初始,屬性本質上還是 **_Get/**_Set方法;
    • 初始完字段後,則調用構造函數Chicken(),並返回this。

3.最後將this賦值給Stack上的chicken引用類型,即chicken維護一個指向heap上Chicken實例的指針,實際stact上的chicken存儲的是GCHeap上實例存儲的地址;

繼承的本質

如果你看到這裏,那說明你已經對一個對象的創建過程有了清晰的認識。回歸主題那繼承的本質是什麽?先別急,下面我們寫一個 Animal 類,讓上文中的Chicken類繼承它,並重寫父類中的ShowType方法,本示例代碼參考書中示例略微有所調整代碼如下:

public class Animal 
{
    private string type ="Animal";

    public Animal()
    {

    }

    public virtual void ShowType()
    {
        Console.WriteLine($"Type is {type}");
    }
}
public class Chicken : Animal
{
    public string type = "Chicken";

    public Chicken()
    {
    }
    
    public override void ShowType()
    {            
        Console.WriteLine($"Type is {type}");
    }
    
}

那麽這個時候我們去執行 Chicken chicken = new Chicken(); 發生了什麽呢?
技術分享圖片

根據上圖我們可以很直觀的看出(此處暫時不考慮Object):

  1. 首先會先初始化chicken.type 字段,然後調用Chicken 構造函數;
  2. 此時編譯器發現還有父類則去為父類Animal 申請內存,即初始Animal.type 字段,然後調用Animal的構造函數;因為所有類型都是繼承自System.Object 所以實際上會一直遍歷到Object類型;此外從這個過程中我們也可以發現子類是可以繼承父類私有成員信息,即chicken可以繼承Animal的type字段,字段存儲順序是父類在前子類在後,跟蹤截圖如下:
    技術分享圖片
  3. Animal()方法體執行完後,然後在執行Chicken()的方法體。
  4. 此處額外說下關於方法的加載,在繼承過程子類會將父類中的方法copy一份,並將重寫的方法覆蓋掉父類中的方法,這也就為多態提供了基礎。

最後

寫這篇博客參考了不少其它牛人的博客,發現關於這塊往深裏東西還有很多,如AppDomain應用程序域,ManagerHeap可以分多種不同的類型,GC對不同的Heap處理規則也是不同的,近期也會持續分享相關內容。寫博文期間內容也不斷反復調整了幾輪,希望在此我都表達清楚了,限於篇幅主要內容還是關於對象和繼承的本質過程,內容基本上也都是根據自己的理解寫出來的,難免有疏漏的地方,如有不對的對方還請指出,那將是我不斷進步的源泉:-)。

參考

  • 《你必須知道的.net(第2版)》 - 王濤
  • 你必須知道的.net博客目錄:http://www.cnblogs.com/anytao/archive/2007/09/14/must_net_catalog.html - 王濤
  • 類型實例的創建位置、托管對象在托管堆上的結構 - Silent Void
  • 關於CLR內存管理一些深層次的討論下篇 - Artech

深入理解.net - 1.繼承的本質