1. 前言
想像一下,我們有一個用來記錄動物園動物的應用程式(就是我們之前課程開始做的那個例子啦)。我們做了一個基礎類別 Animal,裡面有個 MakeSound 方法,讓所有動物都可以「發出」某種聲音。
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("動物發出聲音。");
}
}
但很快你會發現,獅子跟鸚鵡的聲音根本完全不一樣。通用的「動物發出聲音」根本不夠用。這時候就要用到 方法覆寫 啦。你需要讓每個子類別都能有自己的聲音!
2. 語法:override 怎麼用
基本規則
- 要能覆寫方法,基礎類別裡的方法一定要標記成 virtual(或 abstract)。
- 在子類別裡要用 override 關鍵字,方法簽名要一模一樣。
例子:獅子和鸚鵡
public class Lion : Animal
{
public override void MakeSound()
{
Console.WriteLine("吼吼吼!");
}
}
public class Parrot : Animal
{
public override void MakeSound()
{
Console.WriteLine("波卡是笨蛋!");
}
}
現在,如果你建立一個動物陣列,然後對每個動物呼叫 MakeSound,每個都會「講」自己的話:
Animal[] zoo = new Animal[]
{
new Lion(),
new Parrot(),
new Animal()
};
foreach (var animal in zoo)
{
animal.MakeSound();
}
// 輸出:
// 吼吼吼!
// 波卡是笨蛋!
// 動物發出聲音。
3. 虛擬方法表(v-table)
當你用 virtual 跟 override,編譯器會幫類別產生一個特別的「虛擬方法表」(v-table)。
當你透過基礎類別的參考呼叫方法時,CLR 會去查表:這個方法有沒有被子類覆寫?有的話就呼叫子類的版本。
這就是「晚期繫結」(late binding)或動態多型的魔法啦。
4. 組合起來:繼續進化我們的應用程式
我們再加一個類別:
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("汪汪!");
}
}
然後在我們的小型動物園管理程式裡用它:
Animal[] zoo = new Animal[]
{
new Lion(),
new Parrot(),
new Dog()
};
foreach (var animal in zoo)
{
animal.MakeSound();
}
這樣你的程式碼擴充性跟維護性都大大提升,想加新動物超簡單超爽!
5. 覆寫屬性和帶回傳值的 override
其實不只方法可以覆寫,你也可以覆寫虛擬的 屬性 跟索引子。
public class Animal
{
public virtual string Name { get; set; } = "動物";
}
public class Lion : Animal
{
public override string Name { get; set; } = "獅子";
}
這在你想針對特定種類補充資訊時超方便。
6. 用 base 呼叫基礎實作
有時候你想要擴充基礎方法的行為,而不是整個取代。
這時就在覆寫的方法裡用 base 關鍵字呼叫基礎方法。
public class Parrot : Animal
{
public override void MakeSound()
{
base.MakeSound(); // "動物發出聲音。"
Console.WriteLine("波卡是笨蛋!");
}
}
這樣鸚鵡會先發出一般「動物園」的聲音,再來自己的「波卡是笨蛋!」。
7. 實用場景:什麼時候會用到
懂 override 的機制,幾乎是每個認真寫 C# 的 OOP 專案都必備的技能。
- 我們動物園的動物模型? 已經做過啦。
- 自訂 UI 控制項: 你會覆寫標準的視覺方法,加入自己的邏輯。
- 彈性的商業邏輯: 可以在基礎類別定「骨架」,細節讓子類自己搞定。
- 測試跟 mock: 很容易用繼承來「取代」方法做單元測試。
- 外掛和擴充: 介面或抽象基礎類別,多種實作都靠正確覆寫來運作。
8. sealed override 跟虛擬方法鏈
如果你不想讓別人再往下覆寫你已經覆寫過的方法,可以用 sealed override:
public class Base
{
public virtual void Foo() { }
}
public class Middle : Base
{
public sealed override void Foo() { }
}
public class Last : Middle
{
public override void Foo() {} // 錯誤 — 不能再覆寫!
}
這樣可以在你覺得很重要的地方「封住」覆寫鏈,確保系統正確運作。
9. 新方法(new 關鍵字)
有時你想在子類別裡宣告一個同名方法,但基礎類別的不是 virtual。
這時可以用 new 關鍵字——但這不是多型,只是「遮蔽」而已:
public class Animal
{
public void MakeSound()
{
Console.WriteLine("我是動物!");
}
}
public class Cat : Animal
{
public new void MakeSound()
{
Console.WriteLine("我是貓咪!");
}
}
Animal animal = new Cat();
animal.MakeSound(); // "我是動物!"
Cat cat = new Cat();
cat.MakeSound(); // "我是貓咪!"
這裡呼叫時完全看變數型別!所以要真正的動態多型,還是要用 virtual + override 組合。
10. 覆寫方法時常見錯誤
錯誤一: 想覆寫一個沒標 virtual、abstract 或 override 的方法。
在 C# 裡,普通方法不能被覆寫。如果基礎類別的方法沒特別標(virtual、abstract 或 override),你在子類別寫 override 會直接編譯錯誤。
錯誤二: 方法簽名不一樣。
要能真的覆寫,方法名稱、回傳型別、參數都要跟基礎類別一模一樣。只要有一點不同(像是參數型別不同),就會變成新方法,不是覆寫。
錯誤三: 忘記加 override 修飾詞。
如果你寫了一個跟基礎類別一樣簽名的方法,但沒加 override,編譯器不會當成覆寫。這叫做 方法遮蔽(之後會講)。這樣你用基礎型別變數呼叫時,會呼叫到基礎類別的方法,不是你寫的那個。
GO TO FULL VERSION