首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 多載與覆寫差異深度解析:規則、陷阱與最佳實務

     編輯:關於C語言

「多載」與「覆寫」是 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/改簽章為破壞性變更

多載規則細節

1. 什麼構成「不同簽章」?

  • 參數數量不同: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) 衝突。

2. 回傳型別不參與多載解析

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)。

3. 多載解析優先順序

  1. 精確匹配(含隱含轉換)
  2. 隱含數值轉換(int→long, float→double)
  3. params 陣列展開
  4. 可選參數省略
  5. 使用者定義隱含轉換

範例:

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

覆寫規則細節

1. 必要條件檢查清單

  • 基底成員標記 virtual、abstract 或 override
  • 衍生成員標記 override(不可漏)
  • 簽章完全一致:名稱、參數數量/型別/順序、ref/out/in、泛型參數數量與約束
  • 回傳型別相同(C# 9 起支援共變:override object Clone() → override MyType Clone())
  • 存取層級不得更嚴格
  • static/非 static 不可互換

2. 常見錯誤與修正

錯誤代碼原因修正
CS0506基底非 virtual/abstract加上 virtual 或改用 new 隱藏
CS0507存取層級縮小調整為相同或更寬鬆
CS0115簽章不符(參數/回傳/泛型)對齊基底簽章
CS0508回傳型別不相容修正為相同或共變型別
CS0544static/instance 不匹配移除 static 或基底改 static

3. 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 隱藏。

進階主題

1. 介面預設實作與覆寫


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。

2. 泛型方法多載與覆寫


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) { } // 新多載,合法
}
  • 泛型型別參數數量、約束必須一致才能覆寫。
  • 非泛型方法與泛型方法可共存構成多載。

3. 共變回傳型別


class Animal { }
class Cat : Animal { }

class Base { public virtual Animal Create() => new Animal(); }
class Derived : Base { public override Cat Create() => new Cat(); } // C# 9+

僅支援參考型別共變,值型別不支援。

版本控管與 API 設計建議

  1. 公開 API 僅新增多載,勿修改既有簽章,確保二進位相容。
  2. 虛擬成員視為擴充點:一旦標記 virtual,即承諾未來不變更簽章,否則屬破壞性變更。
  3. 密封類別預設 non-virtual:避免不必要的虛擬呼叫開銷與版本風險。
  4. 介面優於抽象基底:介面可多重實作、支援預設實作(C# 8+),耦合度更低。
  5. 文件化多載解析行為:XML 註釋說明各多載用途、預設參數值、拋出例外時機。

完整對照範例


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# 類別庫。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved