多態編程的CLR探索

我們知道在定義類字段的時候可以對其初始化,如果子類和父類都有多個字段需要初始化,那麼初始化的順序如何呢?

實例化字段

測試代碼如下:

class A
{
    public int m = 5;
}

class B : A
{
    public int n = 8;
    public B(int v)
    {
        n = v;
    }
}

static void Main(string[] args)
{
    B a = new B(10);
}

使用ildasm工具查看生成的B類構造函數的IL代碼

可以看到,在生成的B類構造函數中,先初始化了自己的字段,然後調用基類構造函數,最後執行構造函數剩餘部分代碼,而在執行基類B的構造函數時同樣先初始化類B本身的字段,然後調用基類object的構造函數,接着執行B類構造函數的剩餘部分。

因此我們可以得出結論:

當創建對象時,CLR會自動調用類的構造函數,在此構造函數中初始化自身字段和調用基類的構造函數,這是一個遞歸的過程,一致遞歸到.Net最頂層基類Object的構造函數,然後再返回。

靜態字段

靜態字段的初始化與實例字段略有不同,測試代碼如下:

class A
{
   public static int m = 5;
}

class B : A
{
  public static int n = 8;
}

使用ildasm工具查看

c#編譯器自動生成了一個.cctor方法,完成的功能是初始化自身所在類型的靜態字段,但它不會調用基類的cctor方法。cctor方法稱爲'類型構造器",或者叫"靜態構造函數",c#編譯器會爲每一個擁有靜態成員的類型生成一個類型構造器方法,例中字段n如果不在定義的同時初始化,也可以在靜態構造函數中初始化,代碼如下,c#編譯器會爲靜態構造函數生成一個.cctor方法,因此我們可以說:類的靜態構造函數用於對靜態字段進行初始化,它不會自動調用基類的靜態構造函數

class B : A
{
    public static int n;
    static B()//靜態構造函數
    {
        n = 8;
    }
}

實例字段的內存佈局

測試代碼如下:

class parent
{
   public int pD = 100;
}

class Child : parent
{
   public int cD = 200;
}
Child objChild = new Child();

創建完對象之後,內存佈局如下圖所示

靜態字段的內存佈局

測試代碼如下:

class parent
{
    public static int pS = 100;
}
class Child : parent
{
    public static int cS = 200;
    public static void VisitStaticField()
    {
        cS += pS;   //訪問靜態字段
    }
}

上述代碼對應的內存佈局圖如下

可以看到靜態方法和靜態字段都存在於方法表中,子類方法表有一個指針與父類方法表聯繫起來,Child類的基類型方法表指針指向Parent方法表,而Parent方法表的基類型方法表指針指向Object方法表,由於Object是最頂層的基類,所以其基類型方法表指針爲空。

雖然子類和基類方法表通過指針相聯,但子類中的靜態方法訪問父類的靜態字段並不需要使用此指針,這是因爲c#編譯器會將相應的代碼編譯爲ldsfld或stsfld指令,在這些指令中直接定位到了相應的類型方法表,不需要在程序運行時“臨時”通過類型方法表指針去找。

這些相關聯的方法表,能夠爲程序運行時的類型動態轉換提供方便。因此CLR通過類型方法表知道某個類型的父類和子類是什麼,從而對能否將此類型轉換爲特定類型作出判斷。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章