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

《Effective C#》精華:委托實現回調模式深度解析

     編輯:關於C#

委托本質:類型安全的函數指針

委托 是 C# 中**類型安全、面向對象的函數指針**。核心特性:

  • 封裝**方法簽名** (參數列表、返回類型)。
  • 可綁定**靜態方法**、**實例方法**、**Lambda 表達式**。
  • 支持**多播**:`+=` 串聯多個目標方法,按序調用。
  • 事件 是委托的特殊受限用法 (僅 `+=`/`-=`,外部不可調用)。

基本語法與調用

// 1. 聲明委托類型
public delegate void HelloHandler(string msg);

// 2. 目標方法 (簽名匹配)
private void SayHello(string s) => MessageBox.Show(s);

// 3. 實例化並調用
HelloHandler h = new HelloHandler(SayHello);
h("Hello World!"); // 同步調用 SayHello

// 等價簡寫 (C# 2.0+)
HelloHandler h2 = SayHello; // 方法組隱式轉換
h2("Hi");

多播委托:串聯多個處理器

private void Write1(string s) => Debug.WriteLine("Write1: " + s);
private void Write2(string s) => Debug.WriteLine("Write2: " + s);

HelloHandler h = Write1;
h += Write2; // 串聯
h("Test"); 
// 輸出:
// Write1: Test
// Write2: Test

⚠️ 多播兩大陷阱

  1. 異常中斷鏈路:`Write1` 拋出異常 → `Write2` **不會執行**。
    對策:目標方法內部 `try/catch` 吞噬異常,或自定義調用邏輯 (`GetInvocationList()` 逐個 `try/catch`)。
  2. 返回值丟失:委托返回類型非 `void` 時,**只返回最後一個方法的返回值**,前者被覆蓋。
    對策:回調委托建議返回 `void`**;需聚合結果時用 `List` 參數輸出或事件參數對象。

何時使用委托實現回調?決策清單

面臨「對象 A 需通知對象 B 某事發生」時,問自己四個問題:

  1. 傳遞什麼信息? 參數類型、時機 (一次/多次)、同步/異步。
  2. 委托是否最優? 替代方案:介面回調 (`ICallback`)、基類虛方法、事件、直接傳遞數據對象 (如 `DataRow` 引用)。
  3. 誰持有委托?誰調用? 調用方 = 擁有委托實例並調用 `Invoke()`;提供方 = 實現目標方法並註冊 (`+=`)。**別搞反**。
  4. 委托在哪裡初始化/註冊?** 採用**就近原則**:在雙方關聯建立處 (構造函數、屬性設置、初始化方法) 註冊,防止遺漏。

實戰場景對比:委托 vs 介面 vs 構造函數注入

場景:彈窗編輯行數據,保存後刷新主表格

方案 A:委托回調

// 編輯窗體
public partial class EditForm : Form {
    public Action OnSaved; // 委托屬性
    
    private void btnSave_Click(object sender, EventArgs e) {
        // ... 保存邏輯 ...
        OnSaved?.Invoke(updatedRow); // 觸發回調
        this.Close();
    }
}

// 主窗體
var form = new EditForm();
form.OnSaved = row => { grid.RefreshRow(row); }; // 註冊
form.ShowDialog();

方案 B:介面回調 (更正式、可多方法)

public interface IRowEditorCallback {
    void OnRowSaved(DataRow row);
    void OnRowCancelled();
}

public partial class EditForm : Form {
    public IRowEditorCallback Callback { get; set; }
    // ... 調用 Callback?.OnRowSaved(row) ...
}

// 主窗體實現介面
public class MainForm : Form, IRowEditorCallback {
    public void OnRowSaved(DataRow row) => grid.RefreshRow(row);
    public void OnRowCancelled() { }
    // ... new EditForm { Callback = this } ...
}

方案 C:構造函數傳遞數據對象 (最簡直接)

// DataRow 為引用類型,修改即影響原對象
var form = new EditForm(existingRow); // 構造函數接收行
form.ShowDialog();
// 無需回調,主窗體直接感知行變更 (若綁定 BindingSource 自動刷新)

選型建議

場景推薦方案理由
單一通知、輕量級、臨時關聯委托 / `Action` / `Func`語法輕量、無需定義介面
多通知點、契約化、需編譯期檢查介面回調語義清晰、支持多方法、利於測試 Mock
共享可變數據對象 (引用類型)直接傳遞對象 / 構造函數注入最簡單、零樣板、自動同步
廣播式、多訂閱者、生命週期獨立事件 (`event` 關鍵字)封裝 `+=/-=`、防止外部調用、標準模式

委托最佳實踐總結

  • ✅ **優先使用 `Action`/`Func`/`Predicate` 內置委托**,避免重複聲明 `delegate void X()`。
  • ✅ **回調委托返回 `void`**,避免多播返回值困惑。
  • ✅ **目標方法內部處理異常**,防止中斷多播鏈路。
  • ✅ **異步回調用 `Task`/`async`**:`Func` 替代 `Action`,調用時 `await callback(arg)`。
  • ✅ **事件模式規範**:`public event EventHandler MyEvent;` + `protected virtual void OnMyEvent(...)` + `MyEvent?.Invoke(this, args)`。
  • ✅ **弱事件模式** (大量訂閱者/長壽命發布者) 防止內存洩漏:`WeakEventManager` / `WeakReference`。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved