關於繼承,你是否駕輕就熟、了如指掌?本文不討論基本概念,回歸本質,從編譯器運行角度揭示 .NET 繼承的運行本源,發現子類別物件如何實現對父類別成員與方法的繼承,以最簡陋示例揭示繼承實質,闡述繼承機制如何被執行——對更好理解繼承,必要且必然。
以簡單動物繼承體系為例:
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); }
}
測試類別建立 Bird、Chicken 物件(Animal 為抽象類別)。
繼承本質體現於物件建立過程。以 Chicken 物件建立為例:
Bird 為其欄位配置儲存空間,Bird 再找 Animal,遞歸至 System.Object 結束。欄位儲存順序由上到下,父類別在前、子類別在後。原因:若父子類別出現同名欄位,編譯器自動視為兩個不同欄位加以區別。TypeHandle 指向 Loader Heap 上的方法表位址。建立過程:Chicken 先拷貝 Bird 所有方法,再與自身方法比對,覆寫虛方法以子類別方法覆蓋同名父類別方法,新增子類別方法。逐層遞歸至 Object。方法表排序亦父類別在前、子類別在後。
new 關鍵字在虛方法繼承中的阻斷作用。Bird bird2 = new Chicken(); 情況下,bird2.ShowType() 回傳什麼?bird2.type 是什麼?兩大原則解決:
調用子類別還是父類別方法,取決於建立的物件是子類別還是父類別物件,而非引用型別。Bird bird2 = new Chicken() 關注建立物件為 Chicken 型別,因此子類別繼承父類別欄位/方法或覆寫虛方法,不關注 bird2 引用型別。引用型別差異決定不同物件在方法表中不同存取權限(透過 offset 標識區分存取位址區域)。
同名欄位/方法,編譯器按順序查找引用——首先存取離建立最近的欄位/方法。bird2 為 Bird 型別,優先存取 Bird.type(若 public 則回傳 "Bird")。這也是物件建立時欄位必須按順序排列、父類別先編譯的原因。
bird2.type 為 "Bird",那 bird2.ShowType() 顯示什麼?答案:"Type is Chicken"。依本文分析思考原因。new 關鍵字阻斷虛方法動態調用:子類別方法標記 new 即隱藏基類別實現,實為建立同名另一方法,在動態方法表不同位址位置,父類別方法在前、子類別在後。.NET 中建立類別,該類別總在繼承(最終繼承自 System.Object)。繼承是 .NET 運行機制基礎技術之一,一切皆物件,一切皆繼承。本文從基礎出發深入本質探索本源,分析疑難比較鑒別。希望每個人能尋求自己的答案,理解繼承、關注封裝、玩轉多態是理解面向物件起點,願本文為此旅程起點。