1. 前言
想像一下,你有個基礎類別 Animal,裡面有個方法 Speak()。預設所有動物都會印出 "某種聲音"。你想要做一些繼承類別,比如 Dog 跟 Cat,讓牠們不要只會說 "某種聲音",而是各自的台詞("汪!" 跟 "喵!")。
你可以在每個繼承類別裡寫自己的 Speak(),但如果你用 Animal[] 這種集合來操作,呼叫 animal.Speak() 還是只會跑 Animal 的方法,不會跑到狗或貓的版本——因為 C# 預設是看 變數型別,不是背後實際的物件型別。
來個超直白的例子:
Animal dog = new Dog();
dog.Speak(); // 欸,這會印什麼?
預設會是 "某種聲音",不是 "汪!",就算 Dog 有自己的 Speak()。怎麼辦?你要在基礎類別把方法宣告成 virtual,然後在子類別用 override。
什麼是虛擬方法?
virtual 方法就是在基礎類別用 virtual 修飾詞宣告的方法,目的是讓繼承類別可以覆寫( override)它。當你用基礎類別的參考去呼叫這種方法時,會跑到「實際」物件的那個方法——就像現實生活:你養了一隻動物,牠其實是貓,牠就會說 "喵!",不會只會 "某種聲音"。
虛擬方法就是 C# 多型的核心。
2. 虛擬方法怎麼宣告
怎麼宣告虛擬方法
在基礎類別用 virtual 關鍵字宣告方法:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("某種聲音");
}
}
在繼承類別用 override:
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("汪!");
}
}
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("喵!");
}
}
範例:多型展示
// 比如在 Program.cs 檔案
// 基礎類別 Animal,有虛擬方法 Speak
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("某種聲音");
}
}
// Dog 類別,覆寫 Speak 行為
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("汪!");
}
}
// Cat 類別,也覆寫 Speak
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("喵!");
}
}
public static class Program
{
public static void Main()
{
Animal[] animals = new Animal[]
{
new Animal(),
new Dog(),
new Cat()
};
foreach (Animal animal in animals)
{
animal.Speak(); // 會印出:"某種聲音", "汪!", "喵!"
}
}
}
有看到虛擬方法的魔法了吧?就算變數型別是 Animal,還是會呼叫到實際實作的那個版本。
3. 底層怎麼跑:虛擬表(virtual table)
簡單說明機制
當你把方法宣告成虛擬,編譯器會幫每個型別建一個「虛擬表」來記錄方法(有點像每道菜的菜單)。呼叫這種方法時,C# 不是看變數型別,而是看參考背後的實際物件型別——然後「塞」進去對應的那個方法。
可以說,C# 會自動幫你換成專屬的食譜,如果類別有自己的版本。
比喻:合約可以「加註小條款」
想像你有個租約(基礎類別),裡面寫說承租人預設每月付固定金額(方法 PayRent())。但你這個房東很聰明,允許在附錄裡寫特殊條件(虛擬方法)。如果某個承租人(繼承類別)用這權利,他就會寫自己的 override,之後收費就會變成特別的方式。
4. 什麼時候該用虛擬方法?
- 如果基礎類別的方法大多數繼承類別都適用,但有些需要自己特別的行為。
- 當你設計類別繼承結構,預期有些邏輯會被擴充或改寫。
- 當你要做彈性的架構,比如商業邏輯,不同操作型別可能要有自己的行為。
在實際專案裡,虛擬方法是 模板方法(Template Method)、策略(Strategy)等設計模式的基礎,還有一堆 OOP 的核心。
跟一般方法比較
| 一般方法 | 虛擬方法 | |
|---|---|---|
| 能不能覆寫 | 不能 | 可以(override) |
| 呼叫方式 | 看變數型別 | 看「實際」物件型別 |
| 多型 | 沒有 | 有 |
常見問題
- 建構子可以虛擬嗎?
不行!建構子永遠不能虛擬——它們只會根據物件型別執行。 - 虛擬方法可以是靜態的嗎?
不行,只有實例方法能虛擬。靜態方法沒物件,根本沒東西可以覆寫。 - 欄位可以虛擬嗎?
不行,只有方法、屬性和事件可以。
5. 實作練習:「動物世界」App 進階
在我們的學習 App 裡,你已經做了一個簡單的動物繼承結構。現在來加點虛擬方法,讓不同動物吃東西的方式都不一樣!
帶註解的程式碼範例
// 在基礎類別加個虛擬方法 Eat
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("某種聲音");
}
public virtual void Eat()
{
Console.WriteLine("吃一般食物。");
}
}
// Dog 覆寫 Eat
public class Dog : Animal
{
public override void Speak() => Console.WriteLine("汪!");
public override void Eat() => Console.WriteLine("吃狗飼料。");
}
// Cat 也有自己的行為
public class Cat : Animal
{
public override void Speak() => Console.WriteLine("喵!");
public override void Eat() => Console.WriteLine("吃魚。");
}
public static class Program
{
public static void Main()
{
Animal[] animals = new Animal[]
{
new Animal(),
new Dog(),
new Cat()
};
foreach (var animal in animals)
{
animal.Speak();
animal.Eat();
Console.WriteLine("---");
}
}
}
這樣每隻貓貓狗狗都會有自己的吃法啦!而基礎 Animal 還是吃一些很奇怪的東西,完全就是「預設值」的感覺。
6. 常見錯誤與細節
- 有時會忘記在繼承類別加 override,結果還是跑基礎類別的方法。
- 或者反過來,忘了在基礎類別加 virtual,那繼承類別就不能寫 override。
沒 virtual 不能寫 override
C# 編譯器不會讓你覆寫一個沒宣告成虛擬(或 abstract)的基礎類別方法。
為什麼要 override
override 就是明確告訴編譯器跟看你程式的人:「我不是隨便寫新方法,我是有意識地改掉基礎類別的行為」。這樣可以避免你以為「覆寫」了,其實只是多寫了一個同名方法。
如果忘了 override,直接寫新方法
public class Dog : Animal
{
public void Speak()
{
Console.WriteLine("汪!");
}
}
這種情況不是覆寫!如果你用 Animal 型別的變數指到 Dog,還是只會跑動物的那個方法("某種聲音"),完全就是型別的陷阱啊!
GO TO FULL VERSION