委託 是 C# 中類型安全的「函數指針」,事件 是基於委託的發布-訂閱機制。本文通過**紅綠燈觀察者模式**、**動態調用 (DynamicInvoke)**、**異步模式 (BeginInvoke/EndInvoke)** 三個遞進案例,徹底講透委託/事件的本質、多播行為、線程模型與進階用法。
| 概念 | 本質 | 關鍵特征 |
|---|---|---|
委託 delegate | 繼承 MulticastDelegate 的密封類 | 類型安全、面向對象、封裝方法引用、支援多播 (+=/-=) |
事件 event | 委託的**受限包裝** | 僅允許 +=/-= 訂閱/退訂,禁止外部直接調用/賦值,保護封裝性 |
| 標準事件模式 | EventHandler<TEventArgs> | sender (object), e (EventArgs) — 雙參數、void 返回、命名約定 |
public interface IPilotLamp {
void TurnOn();
string Notice { get; set; }
}
public delegate void EventHandler(); // 無參無返委託
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(); // 安全觸發
}
}
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},請向前走。");
}
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();
/* 輸出:
張三司機,綠燈亮了,請向左開車。
李四司機,綠燈亮了,請向左開車。
王五同志,綠燈亮了,請向前走。
麻六同志,綠燈亮了,請向前走。
*/


委託基類 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;
}
}
// 訂閱者方法簽名完全一致即可,無需繼承關係

Invoke():同步阻塞,當前線程逐個執行訂閱者。BeginInvoke():線程池併發執行,**調用者立即返回**,不阻塞。EndInvoke():阻塞等待完成,獲取返回值/傳播異常。GetInvocationList() 遍歷單播委託逐個 BeginInvoke。EndInvoke() 時重新拋出。若不調用 EndInvoke,異常被吞沒 (需 AsyncCallback 捕獲)。BeginInvoke 定義在具體委託類中,需 (MyDelegate)d 轉型後調用。
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;
}
}

// 真正的異步非阻塞模式
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# 事件驅動架構的鑰匙。