首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> C# 入門

C# 父類與子類轉換實驗:向上轉型、向下轉型、is/as 模式匹配、虛方法多態

     編輯:C# 入門

C# 類型轉換中,父類與子類之間的**向上轉型 (隱式)**、**向下轉型 (顯式、風險)**、**模式匹配**、**虛方法調用**是核心考點與常見 Bug 源頭。本文通過完整實驗代碼、內存模型圖解、IL 分析、最佳實踐,徹底講透轉換機制。

一、實驗場景定義


public class Animal
{
    public string Name { get; set; }
    public virtual void Speak() => Console.WriteLine($"[Animal] {Name} makes a sound.");
    public void Breathe() => Console.WriteLine($"[Animal] {Name} breathes.");
}

public class Dog : Animal
{
    public string Breed { get; set; }
    public override void Speak() => Console.WriteLine($"[Dog] {Name} ({Breed}) barks: Woof!");
    public void Fetch() => Console.WriteLine($"[Dog] {Name} fetches the ball.");
}

public class Cat : Animal
{
    public bool IsIndoor { get; set; }
    public override void Speak() => Console.WriteLine($"[Cat] {Name} meows: Meow~");
    public void Purr() => Console.WriteLine($"[Cat] {Name} purrs.");
}

二、向上轉型 — 隱式、安全、多態生效


Dog dog = new Dog { Name = "Buddy", Breed = "Golden Retriever" };

// 隱式向上轉型
Animal animal = dog; // 編譯器自動插入轉換

// 調用虛方法:運行時調用 Dog.Speak() (多態)
animal.Speak(); // 輸出: [Dog] Buddy (Golden Retriever) barks: Woof!

// 調用非虛方法:調用 Animal.Breathe()
animal.Breathe(); // 輸出: [Animal] Buddy breathes.

// 訪問父類成員:OK
Console.WriteLine(animal.Name); // Buddy

// 訪問子類特有成員:編譯錯誤
// animal.Breed; // CS1061: 'Animal' 不包含 'Breed' 定義
// animal.Fetch(); // CS1061

本質:引用變量類型變為 `Animal`,但堆上對象實例 **不變**,依然是完整的 `Dog` 對象 (包含 `Breed` 字段、方法表指向 `Dog` 的虛方法槽)。只是編譯器限制了通過此引用可訪問的成員集合。

三、向下轉型 — 顯式、需運行時檢查、風險


Animal animal = new Dog { Name = "Max", Breed = "Husky" };

// 1. 強制轉型 (舊式) - 失敗拋 InvalidCastException
try 
{
    Dog d = (Dog)animal; // 運行時檢查類型兼容
    d.Fetch(); // 安全調用
}
catch (InvalidCastException) { Console.WriteLine("轉型失敗"); }

// 2. as 操作符 - 失敗返回 null (不拋異常)
Dog d2 = animal as Dog;
if (d2 != null) d2.Fetch(); // 安全

// 3. is 類型模式 (C# 7+) - 判斷並聲明變量 (推薦)
if (animal is Dog d3) 
{
    d3.Fetch(); // d3 作用域僅在 if 內
}

// 4. switch 表達式模式匹配 (C# 8+)
string sound = animal switch
{
    Dog d => $"Dog {d.Breed} barks",
    Cat c => $"Cat {(c.IsIndoor ? "indoor" : "outdoor")} meows",
    Animal a => $"Animal {a.Name} sounds",
    _ => "Unknown"
};

四、關鍵實驗:內存佈局與方法調用


public static void MemoryLayoutDemo()
{
    Dog dog = new Dog { Name = "Rex", Breed = "Lab" };
    
    // 1. 同一對象,不同引用類型
    Animal a = dog;
    object o = dog;
    
    Console.WriteLine($"dog.GetHashCode(): {dog.GetHashCode()}");
    Console.WriteLine($"a.GetHashCode():   {a.GetHashCode()}");
    Console.WriteLine($"o.GetHashCode():   {o.GetHashCode()}");
    // 三者相同,證明指向同一堆對象
    
    // 2. typeof vs GetType
    Console.WriteLine($"dog.GetType(): {dog.GetType()}");     // Dog (運行時類型)
    Console.WriteLine($"a.GetType():   {a.GetType()}");       // Dog (對象真實類型)
    Console.WriteLine($"typeof(Animal): {typeof(Animal)}");   // Animal (編譯時類型)
    Console.WriteLine($"typeof(Dog):    {typeof(Dog)}");       // Dog
    
    // 3. is/as 驗證
    Console.WriteLine($"dog is Animal: {dog is Animal}");     // true
    Console.WriteLine($"a is Dog:      {a is Dog}");          // true
    Console.WriteLine($"o is Dog:      {o is Dog}");          // true
    
    // 4. 虛方法表驗證
    a.Speak(); // 通過 Animal 引用調用 Dog.Speak() - 多態
    ((Dog)a).Speak(); // 顯式轉型後調用 - 同一方法
}

五、危險場景與避坑

場景錯誤代碼後果正確做法
盲目向下轉型`((Dog)animal).Fetch();``animal` 是 `Cat` 時拋 `InvalidCastException` 崩潰`if (animal is Dog d) d.Fetch();`
非虛方法隱藏`Animal a = new Dog(); a.Breathe();` 調用父類方法無法多態,若子類 `new void Breathe()` 會隱藏而非重寫標記 `virtual`/`override`;避免 `new` 隱藏
值類型裝箱轉型`object o = 123; int i = (int)o; long l = (long)o;`第二行拋異常:裝箱後只能拆箱為**原確切類型**`long l = (int)o;` 或 `Convert.ToInt64(o)`
接口轉型`IEnumerable e = new List(); IList l = (IList)e;`若對象未實現 `IList` 拋異常`if (e is IList l) ...`

六、設計原則:里氏替換原則 (LSP) 與組合優於繼承

  • 向上轉型是多態基石,體現「是一種」關係。`Dog` 可作為 `Animal` 使用。
  • 向下轉型通常違反 LSP,意味著代碼依賴具體實現而非抽象。**盡量避免**,通過虛方法、策略模式、訪問者模式消除。
  • 模式匹配 (`is`/`switch`) 是現代 C# 表達類型判斷與轉換的**標準方式**,安全、可讀、編譯器優化。
  • 組合優於繼承:若頻繁需向下轉型訪問特有行為,說明繼承層次設計不當,考慮接口隔離或組合。

七、IL 視角 (進階)


// C#: Animal a = dog;
// IL: 
// ldloc.0 (dog) 
// stloc.1 (a)  // 直接複製引用,無任何指令!隱式向上轉型零開銷。

// C#: Dog d = (Dog)a;
// IL:
// ldloc.1 (a)
// castclass Dog  // 運行時類型檢查指令,不通過拋 InvalidCastException

// C#: if (a is Dog d) ...
// IL: 
// ldloc.1
// isinst Dog     // 檢查類型,兼容留引用,不兼容壓入 null
// dup
// brfalse.s SKIP
// stloc.2 (d)    // 存儲到局部變量

`isinst` 比 `castclass` 快 (無異常開銷),`is` 模式編譯器優化極佳。

總結

  1. 向上轉型:隱式、零開銷、安全、啟用多態。
  2. 向下轉型:顯式、有開銷、有風險、**優先用 `is` 模式匹配** (`if (x is T t)`)。
  3. 虛方法:運行時按對象真實類型調用 (多態);非虛方法按引用類型調用 (靜態綁定)。
  4. GetType() 永遠返回對象真實類型;`typeof()` 返回編譯時類型。
  5. 值類型裝箱後只能拆箱為原類型,這是區別於引用類型轉換的關鍵。
  6. 架構建議:盡量面向抽象 (接口/基類) 編程,消除向下轉型;不可避免時,集中封裝在工廠/策略/訪問者中。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved