「多載」與「覆寫」是 C# 多型的兩大支柱,初學者極易混淆。本文從語言規格、IL 層面、版本相容性、介面實作、泛型限制等多維度剖析兩者差異,並列舉常見編譯錯誤與運行時陷阱,助你徹底掌握。
| 維度 | 多載 | 覆寫 |
|---|---|---|
| 發生範圍 | 同一類別/結構內 | 衍生類別覆寫基底類別 virtual/abstract 成員 |
| 方法名稱 | 必須相同 | 必須相同 |
| 參數簽章 | 必須不同(數量、型別、順序、ref/out/in、可選參數) | 必須完全相同(含 ref/out/in) |
| 回傳型別 | 可相同可不同(不參與簽章) | 必須相同(或共變 Covariant Return,C# 9+) |
| 修飾詞 | 無特殊關鍵字 | 基底 virtual/abstract/override,衍生 override |
| 存取層級 | 獨立決定 | 不可縮小(protected → public 合法,public → private 非法) |
| 靜態/非靜態 | 可混合 | 不可變更(static 不可覆寫 instance) |
| 泛型型別參數 | 可不同 | 必須相同 |
| IL 指令 | call(靜態綁定) | callvirt(動態派發) |
| 版本相容性 | 新增多載為非破壞性變更 | 移除 virtual/改簽章為破壞性變更 |
Foo() vs Foo(int x)Foo(int) vs Foo(string)Foo(int, string) vs Foo(string, int)ref/out/in 不同:Foo(ref int) vs Foo(out int) vs Foo(in int)(三者互為不同簽章)Foo(int x = 1) vs Foo(int x = 2) 仍視為相同簽章,編譯錯誤 CS0111。Foo(int x) vs Foo(int y) 衝突。public int Parse(string s) => int.Parse(s);
public bool Parse(string s) => bool.Parse(s); // CS0111:型別 'C' 已定義 'Parse(string)'
解法:改名(TryParse)、改參數(Parse(string, out int))、或用泛型 Parse<T>(string)。
params 陣列展開範例:
void M(int x) { } // 1
void M(long x) { } // 2
void M(params int[] xs) { } // 3
void M(int x = 0) { } // 4 (與 1 衝突,編譯錯誤)
M(5); // 呼叫 1
M(5L); // 呼叫 2
M(); // 呼叫 4 (若 1 不存在)
M(1,2,3); // 呼叫 3
virtual、abstract 或 overrideoverride(不可漏)override object Clone() → override MyType Clone())| 錯誤代碼 | 原因 | 修正 |
|---|---|---|
| CS0506 | 基底非 virtual/abstract | 加上 virtual 或改用 new 隱藏 |
| CS0507 | 存取層級縮小 | 調整為相同或更寬鬆 |
| CS0115 | 簽章不符(參數/回傳/泛型) | 對齊基底簽章 |
| CS0508 | 回傳型別不相容 | 修正為相同或共變型別 |
| CS0544 | static/instance 不匹配 | 移除 static 或基底改 static |
new 隱藏 vs override 覆寫class Base { public virtual void M() => Console.WriteLine("Base"); }
class Derived : Base
{
public new void M() => Console.WriteLine("Derived"); // 隱藏
}
Base b = new Derived();
b.M(); // 輸出 "Base" —— 靜態綁定呼叫 Base.M
((Derived)b).M(); // 輸出 "Derived" —— 編譯期型別為 Derived
new 斷開多型鏈結,僅影響編譯期型別為衍生類別時的呼叫。sealed override,衍生類別只能用 new 隱藏。
interface ILog { void Write(string msg) => Console.WriteLine(msg); } // C# 8 預設實作
class Logger : ILog { } // 繼承預設實作
class CustomLogger : ILog
{
public void Write(string msg) => File.AppendAllText("log.txt", msg); // 隱式實作,非 override
}
介面成員無 virtual 概念,衍生類別以「隱式實作」或「顯式實作」提供實作,不使用 override。
class Base
{
public virtual void M(T t) { }
public virtual void M(int x) { } // 非泛型多載
}
class Derived : Base
{
public override void M(T t) { } // 覆寫泛型
public override void M(int x) { } // 覆寫非泛型
// public void M(string s) { } // 新多載,合法
}
class Animal { }
class Cat : Animal { }
class Base { public virtual Animal Create() => new Animal(); }
class Derived : Base { public override Cat Create() => new Cat(); } // C# 9+
僅支援參考型別共變,值型別不支援。
virtual,即承諾未來不變更簽章,否則屬破壞性變更。
using System;
public class Printer
{
// 多載群組 1:列印單一值
public void Print(int value) => Console.WriteLine($"Int: {value}");
public void Print(string value) => Console.WriteLine($"Str: {value}");
public void Print(object value) => Console.WriteLine($"Obj: {value}");
// 多載群組 2:列印集合(params 多載)
public void Print(params int[] values) => Console.WriteLine($"Int[]: {string.Join(",", values)}");
public void Print(params string[] values) => Console.WriteLine($"Str[]: {string.Join(",", values)}");
// 虛擬方法供覆寫
public virtual void PrintHeader() => Console.WriteLine("=== Header ===");
}
public class ColorPrinter : Printer
{
// 覆寫虛擬方法
public override void PrintHeader() => Console.WriteLine("=== \u001b[32mColor Header\u001b[0m ===");
// 新增多載(不影響基底)
public void Print(DateTime dt) => Console.WriteLine($"Date: {dt:yyyy-MM-dd}");
// 隱藏基底多載(不建議,示範用)
public new void Print(int value) => Console.WriteLine($"\u001b[31mInt:\u001b[0m {value}");
}
class Program
{
static void Main()
{
Printer p = new ColorPrinter();
p.Print(42); // 呼叫基底 Print(int) —— 靜態綁定
p.Print("hello"); // 基底 Print(string)
p.PrintHeader(); // 動態派發 → ColorPrinter.PrintHeader
ColorPrinter cp = new ColorPrinter();
cp.Print(42); // 隱藏版 Print(int) → 紅色輸出
cp.Print(DateTime.Now); // 新增多載
}
}
多載解決「同名不同參數」的靜態多型,覆寫實現「同名同簽章」的動態多型。牢記:多載看參數列表,覆寫看 virtual/override;多載綁定在編譯期,覆寫派發在執行期。遵循「介面優於繼承、組合優於繼承、非虛擬優於虛擬」的設計原則,能有效降低版本演進風險,打造穩健的 C# 類別庫。