CodeGym /課程 /C# SELF /C# 中的方法覆寫 (Method Overriding)

C# 中的方法覆寫 (Method Overriding)

C# SELF
等級 21 , 課堂 2
開放

1. 前言

如果你現在聽到 覆寫 這個詞有點冒汗,別怕啦~其實一點都不可怕,還超方便的!方法覆寫 就是讓你可以在衍生類別裡,把基底類別的方法行為換成你自己的。這樣我們的程式碼就變得很彈性、很好擴充,也更貼近現實生活,畢竟每隻動物都不想只是「某種聲音」而已。

要讓方法可以被覆寫,基底類別要用 virtual 標記它。衍生類別如果要換掉實作,就要用 override 關鍵字

範例


public class Animal
{
    public string Name { get; set; }
    public virtual void MakeSound()
    {
        Console.WriteLine("某種通用的動物聲音...");
    }
}
        

然後在狗狗的類別裡:


public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪汪!");
    }
}
        
  • 基底類別用 virtual
  • 衍生類別用 override
  • 方法簽章(名字、回傳型別、參數)要一模一樣。

視覺圖解

Dog 繼承 Animal,可以覆寫 MakeSound()

2. 實際運作示範

來看看覆寫實際上怎麼跑。先創幾隻動物來測試聲音:

Animal pet1 = new Animal { Name = "無名動物" };
Dog pet2 = new Dog { Name = "巴布斯" };

pet1.MakeSound(); // 會印出:某種通用的動物聲音...
pet2.MakeSound(); // 會印出:汪汪!

現在來點進階的:
如果我們把狗存進 Animal 型別的變數會怎樣?

Animal pet3 = new Dog { Name = "沙力克" };
pet3.MakeSound(); // ???

你覺得會發生什麼事?
答案: 會印出「汪汪!」
因為雖然變數型別是 Animal,但它「指向」的是狗,所以會呼叫覆寫過的方法!
這就是動態(或延遲)繫結的魔法啦。

3. 覆寫時用 base 關鍵字

有時候你不想整個換掉方法,只想「加料」——比如多做點自己的事,再跑舊的行為。這時就可以用 base 關鍵字,讓你呼叫基底類別的方法版本。


public class Cat : Animal
{
    public override void MakeSound()
    {
        base.MakeSound(); // 呼叫基底實作
        Console.WriteLine("喵!");
    }
}
        

執行這個方法時,會先印出「某種通用的動物聲音...」,然後再來「喵!」

4. 覆寫時方法選擇怎麼決定

為了讓你更直觀理解「底層」發生什麼事,想像有這樣一張表(虛擬調度):

變數型別 物件型別 會呼叫哪個方法
Animal Animal Animal.MakeSound
Animal Dog Dog.MakeSound
Animal Labrador Labrador.MakeSound
Dog Labrador Labrador.MakeSound
Dog Dog Dog.MakeSound

最重要的規則:
變數型別只對編譯器有用,執行時會看實際物件型別(就是你用 new 建出來的那個)。
這個機制叫做 動態(或延遲)繫結——多型就是靠這個實現的(下堂課會再講!)。

為什麼要覆寫方法

  • 在 GUI 框架:你有個基底視窗類別,要覆寫方法來畫出不同的元件。
  • 在遊戲引擎:基底類別 Enemy,繼承類別實作不同行為。
  • 在單元測試:可以做「假物件」(stubs, mocks)來取代方法。

現代 .NET 框架超常用這個機制來做事件、樣板程式碼、繼承設定,甚至物件序列化(像是用虛擬屬性)。

5. 用 new 關鍵字隱藏方法

我們剛剛說過,要覆寫方法要用 virtual/override 這對組合。但 C# 還有另一個跟繼承方法有關的修飾詞——就是 new

new 到底幹嘛用?

new 是給你在衍生類別裡宣告同簽章的方法時用的,你不想覆寫虛擬方法,而是要隱藏(遮蔽)基底方法。

  • 這不是覆寫,是隱藏
  • 這種方法是看變數型別來決定呼叫哪個(沒有動態多型!)。
  • 如果你「不小心」沒寫 new 就遮蔽了方法,編譯器會警告你。

範例:override 跟 new 的差別


public class Animal
{
    public virtual  void MakeSound()
    {
        Console.WriteLine("動物發出某種聲音...");
    }
}

public class Dog : Animal
{
    // 隱藏基底類別的方法(不是覆寫)
    public new void MakeSound()
    {
        Console.WriteLine("這不是 override!只是狗狗的方法。");
    }
}
        

來看一下行為:

Animal a = new Dog();
Dog d = new Dog();

a.MakeSound(); // "動物發出某種聲音..."
d.MakeSound(); // "這不是 override!只是狗狗的方法。"
  • 如果變數型別是 Dog——會呼叫 Dog 的方法。
  • 如果變數型別是 Animal,即使裡面放的是 Dog——還是呼叫 Animal 的方法!

6. 回饋與實作細節

剛開始寫程式時,常常會遇到「明明覆寫了,怎麼還是舊的行為」這種困惑。通常原因很簡單:基底類別沒寫 virtual,或是衍生類別用 new 而不是 override。第二種特別陰險——如果你用基底型別呼叫方法,會跑基底版本,不會跑覆寫的。所以一定要記得用對關鍵字。

除了語法錯誤,有時新手會想在覆寫時換掉回傳型別。比如基底方法回傳 object,衍生類別想回傳 string。這樣不行啦:方法簽章要完全一樣。

比較表:override vs new

特性 override new
機制 覆寫虛擬方法 隱藏基底類別的方法
延遲繫結 有——靠動態多型 沒有——看變數型別
基底類別需要... virtualabstract 或已經 override 不用
建議使用時機 幾乎都該用 只有特殊情況才用

7. 覆寫方法遇到繼承鏈

覆寫這套東西,遇到長繼承鏈時就更有趣了:


public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("動物做了什麼...");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪汪!");
    }
}

public class Labrador : Dog
{
    public override void MakeSound()
    {
        Console.WriteLine("我是拉布拉多:汪~汪~!");
    }
}
        

如果我們寫:

Animal pet = new Labrador();
pet.MakeSound(); //   => "我是拉布拉多:汪~汪~!"

C# 會永遠選最「深」的虛擬方法實作,也就是物件本身的那個。

8. 覆寫方法常見錯誤

世界不完美,學生(還有老手!)都會犯錯。我們直接來看最常見的「地雷」怎麼避開:

1. 忘了在基底類別寫 virtual


public class Animal
{
    public void MakeSound() { ... } // 沒有 'virtual'
}

public class Dog : Animal
{
    // 編譯錯誤!不能覆寫。
    public override void MakeSound()
    {
        Console.WriteLine("汪!");
    }
}
        

C# 會直接說:'Dog.MakeSound()': 找不到可覆寫的方法

2. 簽章不一樣

要確定方法名稱、回傳型別、參數都一樣:


public class Animal
{
    public virtual void MakeSound() { ... }
}

public class Dog : Animal
{
    // 錯誤:簽章不同(比如多加了參數)
    public override void MakeSound(string sound)
    {
        Console.WriteLine(sound);
    }
}
        

3. 沒必要別用 new 取代 override

new 這個關鍵字是用來隱藏基底類別的方法,不是覆寫,也不會有動態多型。這是另一種機制,沒特別理由最好別用。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION