基類以 virtual 宣告方法,衍生類以 override 重寫,透過基類參考指向衍生類物件時,CLR 依實際物件型別動態分派方法:
class Base { public virtual void Print() => Console.WriteLine("Base"); }
class Derived : Base { public override void Print() => Console.WriteLine("Derived"); }
Base b = new Derived();
b.Print(); // 輸出: Derived (動態繫結)
new 宣告新成員隱藏基類同名成員,不參與虛擬分派:
class Base { public virtual void Print() => Console.WriteLine("Base"); }
class DerivedNew : Base { public new void Print() => Console.WriteLine("DerivedNew"); }
Base b = new DerivedNew();
b.Print(); // 輸出: Base (靜態繫結至 Base.Print)
DerivedNew d = new DerivedNew();
d.Print(); // 輸出: DerivedNew (靜態繫結至 DerivedNew.Print)
| 特性 | override | new |
|---|---|---|
| 多型參與 | 是 | 否 |
| 基類需求 | virtual/abstract/override | 任意成員 |
| 基類呼叫 | base.Method() | base.Method() (在衍生類內部) |
| 解除隱藏 | 不適用 | 轉型為基類型別呼叫 |
| 編譯警告 | 無 | 隱藏基類成員會警告 (CS0108) |
若衍生類同時實作介面,可用 new + 介面宣告「切斷」與基類虛擬方法的連結,並重新綁定至介面:
interface IFace { void Print(); }
class Base : IFace { public virtual void Print() => Console.WriteLine("Base"); }
class Derived : Base, IFace
{
public new void Print() => Console.WriteLine("Derived via IFace");
}
IFace f = new Derived();
f.Print(); // 輸出: Derived via IFace (介面呼叫解析至 Derived.Print)
Base b = new Derived();
b.Print(); // 輸出: Base (基類呼叫解析至 Base.Print)
原理:new 切斷與 Base.Print 的虛擬槽位關聯;class Derived : IFace 讓編譯器將 Derived.Print 視為 IFace.Print 的隱式實作,佔用新的虛擬槽位。
virtual 是合理的 (期望多型),衍生類用 new 破壞契約 → 衍生類設計錯誤,應改用 override 或更換方法名。virtual 不合理 (非所有衍生類皆適用),基類應改為 abstract 強制衍生類實作,或移除 virtual。abstract,預設實作下移至具體衍生類,避免 new 產生困惑。Stream.Seek 為 abstract,但 NetworkStream 不支援定位。設計採用 CanSeek 屬性讓呼叫端執行期檢查,而非編譯期強制。這比拋出 NotSupportedException 更優雅,也避免了「不支援的作業」違反里氏替換原則。
virtual/override 實現多型。new。new 時務必加上 // 隱藏基類成員: 說明原因 註解。abstract 強制衍生類正確實作,杜絕 new 濫用。