委派是 C# 型別安全的「函式指標」,事件則是委派的受限封裝,兩者構成 .NET 觀察者模式的基石。本文由 C/C++ 函式指標類比切入,層層深入至委派多播、事件封裝、標準事件模式與最佳實務,並附完整可執行範例。
// C 風格函式指標
void square(int x) { printf("square: %d\n", x*x); }
void cube(int x) { printf("cube: %d\n", x*x*x); }
int main() {
void (*calc)(int) = square; // 指向 square
calc(5); // 輸出 25
calc = cube; // 改指向 cube
calc(5); // 輸出 125
return 0;
}
C# 為了型別安全與記憶體管理,不允許直接操作指標,改以 委派型別 封裝方法簽章:
delegate void MathOp(int x); // 定義委派型別
static void Square(int x) => Console.WriteLine($"Square: {x*x}");
static void Cube(int x) => Console.WriteLine($"Cube: {x*x*x}");
static void Main()
{
MathOp op = Square; // 實例化委派
op(5); // 呼叫 Square
op += Cube; // 多播:加入 Cube
op(5); // 依序呼叫 Square → Cube
op -= Square; // 移除 Square
op(5); // 僅呼叫 Cube
}
delegate void MathOp(int x); 編譯後等價於:
public sealed class MathOp : MulticastDelegate
{
public MathOp(object target, IntPtr methodPtr);
public virtual void Invoke(int x);
public virtual IAsyncResult BeginInvoke(int x, AsyncCallback cb, object state);
public virtual void EndInvoke(IAsyncResult result);
}
MulticastDelegate → 天然支援多播(呼叫清單)。Invoke 同步呼叫;BeginInvoke/EndInvoke 非同步模式(APM)。+=/-= 實際產生新實例。public class Calculator
{
public MathOp OnCalculate; // 外部可任意指派、清空、直接呼叫
}
public class Calculator
{
// 編譯器自動產生:
// private MathOp _onCalculate;
// public event MathOp OnCalculate { add { _onCalculate += value; } remove { _onCalculate -= value; } }
public event MathOp OnCalculate;
public void Calculate(int x) => OnCalculate?.Invoke(x); // 安全觸發
}
event 限制外部僅能 +=/-=,不可直接指派 = 或呼叫 Invoke。OnCalculate?.Invoke(...) 觸發。框架規範建議使用 EventHandler<TEventArgs> 與 EventArgs 衍生類別:
// 事件參數
public class CalcEventArgs : EventArgs
{
public int Input { get; }
public int Result { get; set; }
public CalcEventArgs(int input) => Input = input;
}
// 發送者
public class Calculator
{
public event EventHandler Calculated;
public int Compute(int x)
{
var args = new CalcEventArgs(x);
args.Result = x * x; // 範例:平方
OnCalculated(args);
return args.Result;
}
protected virtual void OnCalculated(CalcEventArgs e)
=> Calculated?.Invoke(this, e); // 傳遞 this 為 sender
}
// 訂閱者
static void Main()
{
var calc = new Calculator();
calc.Calculated += (s, e) => Console.WriteLine($"計算完成:{e.Input}² = {e.Result}");
calc.Compute(10);
}
| 場景 | 建議 |
|---|---|
| 單一方法回呼、輕量、需多播 | 委派 |
| 多組相關方法、需強型別契約、DI 容器管理 | 介面 |
| 事件通知、符合 .NET 標準模式 | event + EventHandler<T> |
public async Task RaiseAsync(EventArgs e)
{
var tasks = Calculated?.GetInvocationList()
.Cast>()
.Select(h => Task.Run(() => h(this, e)));
if (tasks != null) await Task.WhenAll(tasks);
}
⚠ 注意:事件處理器中若有 UI 親和性操作,需回主執行緒(Dispatcher.Invoke/Control.Invoke)。
WeakEventManager (WPF)、WeakReference 自訂、或於 Dispose/Unload 時明確 -=。using System;
delegate void MathOp(int x); // 1. 定義委派型別
class Calculator
{
public event MathOp Calculated; // 2. 宣告事件
public void Compute(int x)
{
int result = x * x;
Console.WriteLine($"[Calculator] 計算 {x}² = {result}");
OnCalculated(result); // 3. 觸發事件
}
protected virtual void OnCalculated(int result)
=> Calculated?.Invoke(result); // 安全觸發
}
class Program
{
static void Main()
{
var calc = new Calculator();
// 4. 訂閱多個處理器
calc.Calculated += r => Console.WriteLine($" → 記錄器:結果 {result}");
calc.Calculated += r => Console.WriteLine($" → 通知器:已完成");
calc.Compute(5);
calc.Compute(10);
// 5. 取消訂閱
calc.Calculated -= calc.Calculated.GetInvocationList()[0] as MathOp;
calc.Compute(3);
}
}
委派提供「方法即資料」的頭等公民地位,事件在其之上加上發布/訂閱語意與封裝保護。掌握「委派多播機制、事件標準模式、非同步觸發與弱參考」四大面向,即能在 C# 中靈活實作觀察者模式、回呼管線與領域事件,寫出低耦合、高可測試的程式碼。