首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 虛擬方法覆寫 vs 隱藏:override、new、virtual 關鍵字行為差異完整解析

     編輯:關於C語言

C# 多型核心在於 virtual、override、new 三個關鍵字的互動。許多開發者混淆「覆寫」與「隱藏」,導致執行期行為不符預期。本文透過 IL 層面解析、多型呼叫實測、版本控制語意、最佳實務,徹底釐清兩者差異。

關鍵字定義速查

關鍵字位置語意IL 指令
virtual基底類別方法宣告此方法**可被衍生類別覆寫**,支援多型調度callvirt
override衍生類別方法**覆寫**基底虛擬/抽象方法,**保持多型** (虛擬表替換槽位)callvirt (槽位相同)
new衍生類別方法**隱藏**基底同名方法,**中斷多型** (新增虛擬表槽位,非虛擬則無槽位)call (非虛擬) 或 callvirt (新槽位)
(無修飾)衍生類別方法同 new,**編譯器警告 CS0108** (建議加 new 明確語意)同 new

核心差異:多型調度行為


public abstract class Animal
{
    public virtual string Speak() => "Animal sound"; // 虛擬方法
}

public class Dog : Animal
{
    public override string Speak() => "Woof!"; // 覆寫:槽位替換
}

public class Cat : Animal
{
    public new string Speak() => "Meow!"; // 隱藏:新槽位 (非虛擬)
}

// --- 測試 ---
Animal a1 = new Dog();
Animal a2 = new Cat();
Dog d = new Dog();
Cat c = new Cat();

Console.WriteLine(a1.Speak()); // "Woof!"  多型:呼叫 Dog.Speak
Console.WriteLine(a2.Speak()); // "Animal sound" 靜態繫結:Animal.Speak (Cat 隱藏未進虛擬表)
Console.WriteLine(d.Speak());  // "Woof!"
Console.WriteLine(c.Speak());  // "Meow!"

記憶體佈局圖解

Animal 虛擬表 (vtable):
Slot 0: Speak() -> Animal.Speak

Dog 虛擬表:
Slot 0: Speak() -> Dog.Speak       (override 覆寫同槽位)

Cat 虛擬表:
Slot 0: Speak() -> Animal.Speak    (Cat.Speak 非虛擬,不佔槽位)
Slot 1: (Cat 新增虛擬方法才會佔槽位)

版本控制語意:為何 C# 強制要求 override/new?

C# 設計目標:**版本安全**。假設基底類別庫 v1 有方法 Calculate()。衍生類別覆寫它。基底類別庫 v2 新增 virtual Calculate()。

  • Java/C++:衍生類別方法自動變成覆寫 → **脆弱基底類別問題**,行為靜默改變。
  • C#:衍生類別若未加 override,視為 **new 隱藏** → **編譯警告 CS0108**,強迫開發者明確選擇:
    • 加 override:確認要覆寫新虛擬方法。
    • 加 new:確認要隱藏,保持原行為。

進階情境:抽象方法、介面、密封

情境規則範例
abstract 方法**必須** override,不可 new 隱藏 (編譯錯誤)public abstract void Do(); → public override void Do()
介面方法隱含 virtual,實作類別用 override (C# 8+ 可預設實作)interface I { void M(); } → class C : I { public void M() {} } (隱含 override)
sealed override**封鎖**進一步覆寫,子類別只能 new 隱藏public sealed override void Save()
靜態方法**不可** virtual/override/new (隱藏靜態僅編譯期繫結)static void M()
建構子不可虛擬,無多型

常見錯誤與除錯技巧

❌ 錯誤 1:忘記 override,導致基底參考呼叫基底方法


class Base { public virtual void Init() { Console.WriteLine("Base"); } }
class Derived : Base { public void Init() { Console.WriteLine("Derived"); } } // 警告 CS0108

Base b = new Derived();
b.Init(); // 輸出 "Base" !!! 非預期

修正: 加 override 或刻意加 new 並理解行為差異。

❌ 錯誤 2:以為 new 也能多型


Base b = new Derived();
b.NewMethod(); // 編譯錯誤:Base 無此方法
// 必須轉型
((Derived)b).NewMethod(); // 才能呼叫

✅ 除錯技巧:查看 IL 或使用 typeof(Derived).GetMethod("M").GetBaseDefinition()


var m = typeof(Derived).GetMethod("Speak");
var baseDef = m.GetBaseDefinition();
Console.WriteLine(baseDef.DeclaringType == typeof(Derived) ? "新增/隱藏" : "覆寫");

最佳實務指南

  1. 設計為繼承時:刻意決定 virtual,文件說明覆寫契約。
  2. 衍生類別覆寫:永遠加 override,IDE 自動完成會帶上。
  3. 刻意隱藏基底非虛擬成員:明確加 new,消除警告、表達意圖。
  4. 封鎖覆寫鏈:sealed override 防止子類別意外改變核心邏輯。
  5. 避免新增虛擬方法於公開基底類別庫:破壞現有衍生類別的 new 隱藏語意 (雖 C# 編譯器警告保護,但仍是破壞性變更)。
  6. 單元測試多型行為:測試 Base b = new Derived(); b.Method() 確保呼叫正確實作。

結語

override = **擴充多型** (虛擬表槽位替換);new = **中斷多型** (靜態繫結/新槽位)。C# 強制明確宣告,是為了**版本演進安全**。理解虛擬表機制、IL 差異、版本語意,即能自信駕馭繼承體系,避免「明明覆寫了卻呼叫基底方法」的經典坑。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved