方法多載允許在同一類別/結構中定義同名但參數列表不同的多個方法。編譯器根據呼叫時的實參類型、數量、順序,靜態繫結至對應的多載版本 (靜態多態)。
| 區別維度 | 範例 | 說明 |
|---|---|---|
| 參數數量 | void M() / void M(int a) / void M(int a, int b) | 最直觀 |
| 參數類型 | void M(int x) / void M(string s) / void M(double d) | 核心機制 |
| 參數順序 | void M(int a, string b) / void M(string a, int b) | 類型相同但順序不同 |
| ref/out/in 修飾詞 | void M(int a) / void M(ref int a) / void M(out int a) / void M(in int a) | 簽章一部分 (C# 7.2+ in) |
| 泛型型參數數量 | void M<T>(T x) / void M<T,U>(T x, U y) | 泛型方法多載 |
int M() vs string M() ❌ CS0111void M(int x) vs void M(int y) ❌void M(params int[] a) vs void M(int[] a) ❌ (視為相同簽章)void M(int x = 0) vs void M() ❌ (會造成模糊)where T : class vs where T : struct ❌1. 識別候選方法集 ├─ 同名方法 ├─ 可存取性檢查 (private/protected/internal) └─ 類型參數推斷 (泛型) 2. 篩選適用方法 ├─ 實參數量符合 (含 params 展開) ├─ 隱含轉換存在 (int→long, Derived→Base, T→interface) └─ ref/out/in 修飾詞匹配 3. 尋找「最佳」方法 ├─ 優先:不需轉換 (精確匹配) ├─ 次優:隱含數值轉換 (int→long 優於 int→object) ├─ 再次:隱含參考轉換 (Derived→Base) ├─ 再次:装箱轉換 (int→object) ├─ 最後:使用者定義轉換 (implicit operator) └─ params 陣列視為最低優先序 4. 單一最佳 → 繫結成功 多個同等最佳 → 編譯錯誤 CS0121 (模糊呼叫) 無適用 → 編譯錯誤 CS1503 (引數不匹配)
class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
public int Add(int a, int b, int c) => a + b + c;
public string Add(string a, string b) => a + b; // 字串串接
}
// 呼叫
calc.Add(1, 2); // → int 版本
calc.Add(1.5, 2.5); // → double 版本
calc.Add(1, 2, 3); // → 三參數版本
calc.Add("A", "B"); // → string 版本
class Demo
{
public void M(int x) => Console.WriteLine("int");
public void M(long x) => Console.WriteLine("long");
public void M(object x) => Console.WriteLine("object");
public void M(double x) => Console.WriteLine("double");
}
demo.M(42); // int (精確匹配)
demo.M(42L); // long (精確匹配)
demo.M(3.14); // double (精確匹配)
demo.M('A'); // int (char→int 隱含轉換,優於 long/object)
demo.M(3.14f); // double (float→double 隱含轉換)
demo.M("text"); // object (string→object 隱含參考轉換)
class ParamsDemo
{
public void M(int[] arr) => Console.WriteLine("int[]");
public void M(params int[] arr) => Console.WriteLine("params int[]"); // ❌ 編譯錯誤:簽章相同
// 正確區別:參數類型不同
public void M(params object[] args) => Console.WriteLine("params object[]");
}
var d = new ParamsDemo();
d.M(1, 2, 3); // → params object[] (int→object 裝箱)
d.M(new int[] {1}); // → int[] (精確匹配,優於 params object[])
d.M(new object[] {1}); // → params object[]
class GenericDemo
{
public void M(T x) => Console.WriteLine($"M: {x.GetType().Name}");
public void M(T x, T y) => Console.WriteLine($"M: {x.GetType().Name}");
public void M(int x) => Console.WriteLine("M"); // 非泛型優先
}
var g = new GenericDemo();
g.M(10); // → M (非泛型優於泛型)
g.M("hello"); // → M (型別推斷)
g.M(1, 2); // → M
g.M(1, "two"); // ❌ CS0411: 無法推斷 T (需同類型)
g.M
class Ambiguity
{
public void M(int x, double y) => Console.WriteLine("int, double");
public void M(double x, int y) => Console.WriteLine("double, int");
}
var a = new Ambiguity();
a.M(1, 2); // ❌ CS0121: 模糊 (int→int+int→double vs int→double+int→int 同等)
a.M(1, 2.0); // → int, double (精確匹配第二參數)
a.M(1.0, 2); // → double, int (精確匹配第一參數)
// 解決:強制轉型 a.M((int)1, (double)2) 或重新設計 API
class OptionalTrap
{
public void M(int x, int y = 10) => Console.WriteLine($"M(x,y): {x},{y}");
public void M(int x) => Console.WriteLine($"M(x): {x}");
}
var o = new OptionalTrap();
o.M(5); // ❌ CS0121: 模糊 (M(5) vs M(5,10) 皆適用且同等)
o.M(5, 20); // → M(x,y) (精確匹配)
// 解決:避免「單一必要參數 + 可選參數」與「單一參數」多載共存
所有多載應表達相同邏輯意圖,僅參數形式不同。例如 WriteLine 系列皆為「輸出文字」。
// ✅ 好:必要參數在前,可選在後
void Send(string to, string subject, string body = "", bool highPriority = false)
// ❌ 差:順序不一致造成困惑
void Send(string to, string body, string subject = "")
void Send(EmailMessage msg)new EmailBuilder().To(...).Subject(...).Send()
///
/// 發送郵件。若 未指定,預設為 Normal。
///
public void Send(string to, string subject, string body, MailPriority priority = MailPriority.Normal)
int→long):可能改變多載解析結果,屬破壞性變更
struct Temperature
{
public double Celsius { get; }
public Temperature(double c) => Celsius = c;
// 允許 double 隱式轉為 Temperature
public static implicit operator Temperature(double c) => new Temperature(c);
}
class Thermo
{
public void Show(Temperature t) => Console.WriteLine($"{t.Celsius}°C");
}
var th = new Thermo();
th.Show(25.0); // 自動轉換 → Show(Temperature)
th.Show(new Temperature(30)); // 直接呼叫
方法多載是 C# 表達力的核心機制,讓 API 同時具備便利性 (預設參數、params) 與型別安全 (精確匹配)。深入理解「隱含轉換優先序」、「params 最低優先」、「非泛型優於泛型」、「可選參數模糊陷阱」等解析規則,能設計出直觀、穩健、易維護的公開 API,避免呼叫端遭遇神秘的編譯錯誤或執行時意外行為。