override (覆寫) 與 new (隱藏) 是 C# 版本控制與多型的核心關鍵字。兩者編譯後 IL 指令、執行期行為、設計意圖截然不同。本文透過代碼實驗、IL 分析、MSDN 官方文件對照,完整釐清兩者差異與適用場景。
| 維度 | override (覆寫) | new (隱藏) |
|---|---|---|
| 前提條件 | 基底成員需為 virtual/abstract/override | 基底成員可為任何非靜態成員 |
| 多型行為 | ✅ 執行期動態派發 (callvirt) | ❌ 編譯期靜態綁定 (call) |
| 基底參考呼叫衍生實作 | ✅ | ❌ (呼叫基底實作) |
| 可再次被覆寫 | ✅ (預設 virtual) | ✅ (需加 virtual) |
| 版本控制意圖 | 擴充/修改基底行為,保持合約 | 引入新成員,避免破壞基底合約 |
| 編譯器警告 | 無 (符合預期) | 若基底有同名成員 → CS0108 (建議加 new 明確意圖) |
| IL 指令 | callvirt 方法表槽位 | call 直接指定方法 |
using System;
namespace OverrideVsNew
{
// 基底類別
class Car
{
public virtual void DescribeCar()
{
Console.WriteLine("Call DescribeCar() defined in Car class.");
}
}
// 使用 new 隱藏
class ConvertibleCar : Car
{
public new virtual void DescribeCar() // new + virtual 允許進一步覆寫
{
Console.WriteLine("Call DescribeCar() defined in ConvertibleCar class.");
}
}
// 使用 override 覆寫
class Minivan : Car
{
public override void DescribeCar()
{
Console.WriteLine("Call DescribeCar() defined in Minivan class.");
}
}
class Program
{
static void Main()
{
Console.WriteLine("=== 以各類別型別宣告變數 ===");
Car car1 = new Car();
car1.DescribeCar(); // Car
Console.WriteLine("----------");
ConvertibleCar car2 = new ConvertibleCar();
car2.DescribeCar(); // ConvertibleCar
Console.WriteLine("----------");
Minivan car3 = new Minivan();
car3.DescribeCar(); // Minivan
Console.WriteLine("----------");
Console.WriteLine("=== 多型測試:基底參考指向衍生實例 ===");
Car poly1 = new ConvertibleCar();
poly1.DescribeCar(); // ❌ 輸出 Car (new 隱藏,靜態綁定)
Car poly2 = new Minivan();
poly2.DescribeCar(); // ✅ 輸出 Minivan (override,動態派發)
}
}
}
=== 以各類別型別宣告變數 === Call DescribeCar() defined in Car class. ---------- Call DescribeCar() defined in ConvertibleCar class. ---------- Call DescribeCar() defined in Minivan class. ---------- === 多型測試:基底參考指向衍生實例 === Call DescribeCar() defined in Car class. ← new 隱藏:基底參考呼叫基底實作 Call DescribeCar() defined in Minivan class. ← override:基底參考呼叫衍生實作
.method public hidebysig virtual instance void DescribeCar() cil managed
{
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Call DescribeCar() defined in Minivan class."
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
}
// 方法表槽位:繼承 Car 的槽位,覆寫同一槽位
.method public hidebysig newslot virtual instance void DescribeCar() cil managed
{
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Call DescribeCar() defined in ConvertibleCar class."
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
}
// 關鍵:newslot = 分配新方法表槽位,不覆寫基底槽位
// poly1.DescribeCar() - ConvertibleCar 實例,Car 參考
IL_0013: ldloc.0 // 載入 poly1 (Car 參考)
IL_0014: callvirt instance void Car::DescribeCar() // 呼叫 Car 槽位 → 執行 Car 實作
// poly2.DescribeCar() - Minivan 實例,Car 參考
IL_001f: ldloc.1 // 載入 poly2 (Car 參考)
IL_0020: callvirt instance void Car::DescribeCar() // 呼叫 Car 槽位 → 執行 Minivan 實作 (方法表已被覆寫)
class Base
{
public virtual void M() { Console.WriteLine("Base.M"); }
}
class Derived : Base
{
public new virtual void M() { Console.WriteLine("Derived.M"); } // 新槽位 + 可被再覆寫
}
class MoreDerived : Derived
{
public override void M() { Console.WriteLine("MoreDerived.M"); } // 覆寫 Derived 的新槽位
}
// 測試
Base b1 = new Derived(); b1.M(); // Base.M (new 隱藏)
Derived d1 = new Derived(); d1.M(); // Derived.M
Derived d2 = new MoreDerived(); d2.M(); // MoreDerived.M (多型生效於 Derived 分支)
Base b2 = new MoreDerived(); b2.M(); // Base.M (Base 槽位未被覆寫)
abstract)new 明確意圖、消除警告
// ❌ 混淆語意:基底虛擬,衍生用 new 卻期望多型
class Base { public virtual void M() {} }
class Bad : Base { public new void M() {} } // 基底參考呼叫 Base.M,極易產生 Bug
// ❌ 新增 new 卻忘記標記,編譯器警告 CS0108
class Base { public void M() {} }
class Bad : Base { public void M() {} } // 警告:隱藏繼承成員,建議加 new
// ✅ 明確意圖
class Good : Base { public new void M() {} } // 清楚表達:這是新成員,非覆寫
callvirt 動態派發newslot),call 靜態綁定override 顯示衍生行為,new 顯示基底行為new virtual 可建立新的多型分支 (衍生類別間生效,但基底分支不受影響)new 關鍵字** 當隱藏基底成員,讓意圖明確、消除 CS0108 警告