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

C# 委託與事件機制 深度解析:從函數指針到異步調用模式

     編輯:關於C語言

委託 是 C# 中類型安全的「函數指針」,事件 是基於委託的發布-訂閱機制。本文通過**紅綠燈觀察者模式**、**動態調用 (DynamicInvoke)**、**異步模式 (BeginInvoke/EndInvoke)** 三個遞進案例,徹底講透委託/事件的本質、多播行為、線程模型與進階用法。

一、 核心概念速覽

概念本質關鍵特征
委託 delegate繼承 MulticastDelegate 的密封類類型安全、面向對象、封裝方法引用、支援多播 (+=/-=)
事件 event委託的**受限包裝**僅允許 +=/-= 訂閱/退訂,禁止外部直接調用/賦值,保護封裝性
標準事件模式EventHandler<TEventArgs>sender (object), e (EventArgs) — 雙參數、void 返回、命名約定

二、 案例 1:紅綠燈 — 經典觀察者模式

1. 定義契約


public interface IPilotLamp {
    void TurnOn();
    string Notice { get; set; }
}
public delegate void EventHandler(); // 無參無返委託

2. 發布者 (TrafficLight)


public class TrafficLight : IPilotLamp {
    public event EventHandler Notices; // 事件
    private string _notice;
    public string Notice { get => _notice; set => _notice = value; }
    
    public void TurnOn() {
        Console.WriteLine("綠燈亮了!");
        Notices?.Invoke(); // 安全觸發
    }
}

3. 訂閱者


public class Driver {
    private string _name; private IPilotLamp _lamp;
    public Driver(string name, IPilotLamp lamp) { _name = name; _lamp = lamp; }
    public void GoLeft() => Console.WriteLine($"{_name}司機,{_lamp.Notice},請向左開車。");
}
public class Pedestrian {
    private string _name; private IPilotLamp _lamp;
    public Pedestrian(string name, IPilotLamp lamp) { _name = name; _lamp = lamp; }
    public void GoThrough() => Console.WriteLine($"{_name}同志,{_lamp.Notice},請向前走。");
}

4. 組裝運行


var light = new TrafficLight();
var d1 = new Driver("張三", light);
var d2 = new Driver("李四", light);
var p1 = new Pedestrian("王五", light);
var p2 = new Pedestrian("麻六", light);

light.Notices += d1.GoLeft;
light.Notices += d2.GoLeft;
light.Notices += p1.GoThrough;
light.Notices += p2.GoThrough;

light.Notice = "綠燈亮了。";
light.TurnOn();
/* 輸出:
張三司機,綠燈亮了,請向左開車。
李四司機,綠燈亮了,請向左開車。
王五同志,綠燈亮了,請向前走。
麻六同志,綠燈亮了,請向前走。
*/

控制台輸出
結果圖

三、 案例 2:動態調用 — 突破簽名限制

委託基類 Delegate 提供 DynamicInvoke(params object[] args),允許**運行時綁定**任意簽名。


public delegate string GeneralEventHandler();
public class Publisher {
    public event GeneralEventHandler NumberChanged;
    public List<string> DoSomething() {
        var results = new List<string>();
        if (NumberChanged == null) return results;
        foreach (Delegate d in NumberChanged.GetInvocationList()) {
            // 無需強制轉換,直接動態調用
            string rtn = d.DynamicInvoke(null).ToString(); 
            results.Add(rtn);
        }
        return results;
    }
}
// 訂閱者方法簽名完全一致即可,無需繼承關係

DynamicInvoke 結果

四、 案例 3:異步調用 — BeginInvoke/EndInvoke 模式

1. 原理

  • Invoke():同步阻塞,當前線程逐個執行訂閱者。
  • BeginInvoke():線程池併發執行,**調用者立即返回**,不阻塞。
  • EndInvoke():阻塞等待完成,獲取返回值/傳播異常。

2. 關鍵限制

  1. 多播委託不能直接 BeginInvoke:會拋出「委託只能包含一個目標方法」。必須 GetInvocationList() 遍歷單播委託逐個 BeginInvoke。
  2. 異常處理:異步執行的異常僅在 EndInvoke() 時重新拋出。若不調用 EndInvoke,異常被吞沒 (需 AsyncCallback 捕獲)。
  3. 向下轉型:BeginInvoke 定義在具體委託類中,需 (MyDelegate)d 轉型後調用。

3. 完整示例


public delegate string GeneralEventHandler(object sender, EventArgs e);
public class Publisher {
    public event GeneralEventHandler NumberChanged;
    public List<string> DoSomethingAsync() {
        var results = new List<string>();
        if (NumberChanged == null) return results;
        foreach (Delegate d in NumberChanged.GetInvocationList()) {
            var del = (GeneralEventHandler)d;
            IAsyncResult ar = del.BeginInvoke(this, EventArgs.Empty, null, null);
            // 這裡同步等待演示,實際可異步回調
            results.Add(del.EndInvoke(ar));
        }
        return results;
    }
}

異步調用結果

4. AsyncCallback 與 AsyncState


// 真正的異步非阻塞模式
del.BeginInvoke(this, EventArgs.Empty, ar => {
    try {
        string result = del.EndInvoke(ar);
        // 處理結果,如 UI 更新 (需 Invoke/BeginInvoke 回 UI 線程)
    } catch (Exception ex) {
        // 記錄異常
    }
}, userState: "custom data");

五、 總結:委託/事件選型指南

場景推薦方式
單一回調、內部控制流委託字段/屬性 (Func/Action)
多訂閱者、鬆耦合通知event + EventHandler<T>
運行時動態綁定、插件系統DynamicInvoke / Expression<TDelegate>.Compile()
耗時操作、UI 響應性async/await (現代替代) 或 BeginInvoke (Legacy)
高性能熱路徑介面 + 結構體委託 (C# 11+ delegate* 函數指針)

委託是「方法的一等公民化」,事件是「委託的封裝化」。理解 MulticastDelegate 內部鏈表結構、調用約定與線程模型,是駕馭 C# 事件驅動架構的鑰匙。

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