早期 C# 開發者常面臨「將方法作為參數傳遞」的需求。例如問候功能需支援多國語言,若用 if/else 或 switch 判斷語言再呼叫對應方法,每新增語言都得修改核心邏輯,違反開放封閉原則。委派解決了這個問題:它是一個型別安全的方法參考,讓方法可像變數一樣傳遞、組合、呼叫。
// 定義委派型別:無回傳值、接受一個 string 參數
public delegate void GreetingDelegate(string name);
// 具體方法
public static void EnglishGreeting(string name) => Console.WriteLine($"Morning, {name}");
public static void ChineseGreeting(string name) => Console.WriteLine($"早上好, {name}");
// 使用委派作為參數
public static void GreetPeople(string name, GreetingDelegate makeGreeting)
{
makeGreeting(name); // 呼叫傳入的方法
}
// 呼叫端
GreetPeople("Jimmy", EnglishGreeting); // Morning, Jimmy
GreetPeople("張三", ChineseGreeting); // 早上好, 張三
關鍵點:委派宣告語法與方法簽章幾乎相同,只多一個 delegate 關鍵字。編譯器會將其轉換為繼承自 System.MulticastDelegate 的密封類別。
委派可串連多個方法,呼叫時依序執行:
GreetingDelegate d = EnglishGreeting;
d += ChineseGreeting; // 串聯
d("Alice");
// 輸出:
// Morning, Alice
// 早上好, Alice
d -= EnglishGreeting; // 移除
d("Bob"); // 僅輸出 早上好, Bob
=,後續串聯用 +=,移除用 -=。+= 實際產生新委派實例 (Delegate.Combine),原變數指向新實例。null (C# 6+ 可用 d?.Invoke(args))。直接暴露 public 委派欄位會破壞封裝:外部可任意 = 覆寫、清空訂閱者。event 關鍵字限制外部僅能 +=/-=,內部仍可自由呼叫:
public class GreetingManager
{
// 事件宣告 = 私有委派欄位 + 公開 add/remove 存取器
public event GreetingDelegate MakeGreet;
public void GreetPeople(string name)
{
MakeGreet?.Invoke(name); // 安全呼叫
}
}
// 使用端
var gm = new GreetingManager();
gm.MakeGreet += EnglishGreeting; // OK
gm.MakeGreet += ChineseGreeting; // OK
// gm.MakeGreet = EnglishGreeting; // 編譯錯誤:事件只能出現在 +=/-= 左側
gm.GreetPeople("Test");
public event GreetingDelegate MakeGreet; 實際編譯為:
// 私有委派欄位
private GreetingDelegate MakeGreet;
// 同步化的 add/remove 存取器
[MethodImpl(MethodImplOptions.Synchronized)]
public void add_MakeGreet(GreetingDelegate value)
=> MakeGreet = (GreetingDelegate)Delegate.Combine(MakeGreet, value);
[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_MakeGreet(GreetingDelegate value)
=> MakeGreet = (GreetingDelegate)Delegate.Remove(MakeGreet, value);
因此事件本質是 一對同步化的 add/remove 方法包裝的私有委派。
public delegate void GreetingDelegate(string name); 編譯為:
.class public auto ansi sealed GreetingDelegate
extends [mscorlib]System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname
instance void .ctor(object 'object', native int 'method') runtime managed
{ ... }
.method public hidebysig newslot virtual
instance class [mscorlib]System.IAsyncResult
BeginInvoke(string name, class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
{ ... }
.method public hidebysig newslot virtual
instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
{ ... }
.method public hidebysig newslot virtual
instance void Invoke(string name) runtime managed
{ ... }
}
繼承 MulticastDelegate 意味著天生支援呼叫清單 (invocation list)。
場景:熱水器燒水,溫度超過 95°C 通知所有訂閱者 (警報器響、顯示器閃)。
public class BoiledEventArgs : EventArgs
{
public int Temperature { get; }
public BoiledEventArgs(int temp) => Temperature = temp;
}
public class Heater
{
// .NET 規範:委派名以 EventHandler 結尾,參數 (sender, EventArgs)
public delegate void BoiledEventHandler(object sender, BoiledEventArgs e);
public event BoiledEventHandler Boiled;
protected virtual void OnBoiled(BoiledEventArgs e)
{
Boiled?.Invoke(this, e); // 傳送自己作為 sender
}
public void BoilWater()
{
for (int i = 0; i <= 100; i++)
{
if (i > 95) OnBoiled(new BoiledEventArgs(i));
}
}
}
public class Alarm
{
public void MakeAlert(object sender, BoiledEventArgs e)
{
var heater = (Heater)sender;
Console.WriteLine($"Alarm: {heater.Area} - {heater.Model}: 嘀嘀嘀,水已經 {e.Temperature} 度了!");
}
}
public class Display
{
public static void ShowMsg(object sender, BoiledEventArgs e)
{
var heater = (Heater)sender;
Console.WriteLine($"Display: {heater.Area} - {heater.Model}: 水快燒開了,當前 {e.Temperature}°C");
}
}
var heater = new Heater { Model = "RealFire 001", Area = "China Xian" };
var alarm = new Alarm();
heater.Boiled += alarm.MakeAlert;
heater.Boiled += (new Alarm()).MakeAlert; // 匿名實例
heater.Boiled += Display.ShowMsg; // 靜態方法
heater.BoilWater();
輸出:每溫度觸發一次,所有訂閱者依序收到通知。
| 規範項目 | 要求 | 範例 |
|---|---|---|
| 委派名稱 | 以 EventHandler 結尾 | BoiledEventHandler |
| 委派簽章 | void (object sender, TEventArgs e) | TEventArgs : EventArgs |
| 事件名稱 | 委派名移除 EventHandler | Boiled |
| 事件參數類別 | 以 EventArgs 結尾 | BoiledEventArgs |
| 觸發方法 | protected virtual void OnXxx(EventArgs e) | 允許衍生類覆寫/抑制事件 |