定義:單播委派僅能指向單一方法;多播委派可串聯多個方法 (呼叫清單)。
關鍵事實: C# delegate 關鍵字編譯後**一律產生繼承自 System.MulticastDelegate 的類別**,即**所有委派本質都是多播**。單播委派 (System.Delegate) 在框架設計中屬缺陷殘留。平時定義的委派皆可使用 += 組合成呼叫鏈。
// 編譯器產生的委派類別結構
public class Back : System.MulticastDelegate
{
public Back(object target, int methodPtr);
public virtual void Invoke(object value, int item, int numItems);
public virtual IAsyncResult BeginInvoke(...);
public virtual void EndInvoke(IAsyncResult result);
}
public class MailManager
{
public delegate void MailMsgEventHandler(object sender, EventArgs e);
public event MailMsgEventHandler MailMsg;
}
編譯器自動產生:
_mailMsgadd_MailMsg / remove_MailMsg 存取器 (對應 +=/-=)private MailMsgEventHandler _mailMsg = null;
public void add_MailMsg(MailMsgEventHandler handler)
=> _mailMsg = (MailMsgEventHandler)Delegate.Combine(_mailMsg, handler);
public void remove_MailMsg(MailMsgEventHandler handler)
=> _mailMsg = (MailMsgEventHandler)Delegate.Remove(_mailMsg, handler);
**事件本質 = 委派欄位的存取器**,類似屬性的 get/set,事件使用 add/remove。
public event MailMsgEventHandler MailMsg
{
add
{
_mailMsg = (MailMsgEventHandler)Delegate.Combine(_mailMsg, value);
// 可加入鎖定、日誌、驗證等自訂邏輯
}
remove
{
_mailMsg = (MailMsgEventHandler)Delegate.Remove(_mailMsg, value);
}
}
優勢:完全控制訂閱/取消訂閱行為,支援執行緒安全、條件式訂閱等進階需求。
若每個事件皆由編譯器產生獨立委派欄位,每個 Control 實例將佔用大量記憶體 (60 個委派參考),但大多數實例僅使用少數事件。

使用雜湊表以 **事件鍵 (Object) → 委派鏈** 映射,僅在訂閱時建立條目。
AddHandler、RemoveHandler、索引器存取public class EventHandlerSet : IDisposable
{
private Hashtable _events = new Hashtable();
public virtual Delegate this[object eventKey]
{
get => (Delegate)_events[eventKey];
set => _events[eventKey] = value;
}
public virtual void AddHandler(object eventKey, Delegate handler)
=> _events[eventKey] = Delegate.Combine((Delegate)_events[eventKey], handler);
public virtual void RemoveHandler(object eventKey, Delegate handler)
=> _events[eventKey] = Delegate.Remove((Delegate)_events[eventKey], handler);
public virtual void Fire(object eventKey, object sender, EventArgs e)
{
var d = (Delegate)_events[eventKey];
d?.DynamicInvoke(new object[] { sender, e });
}
public void Dispose() => _events = null;
// 執行緒安全包裝...
}
public class WorkStartEventArgs : EventArgs
{
public DateTime WorkStartTime { get; }
public string Message { get; }
public WorkStartEventArgs(string msg, DateTime time) { Message = msg; WorkStartTime = time; }
}
public class WorkEndEventArgs : EventArgs
{
public DateTime WorkEndTime { get; }
public string Message { get; }
public WorkEndEventArgs(string msg, DateTime time) { Message = msg; WorkEndTime = time; }
}
public delegate void WorkStartEventHandler(object sender, WorkStartEventArgs e);
public delegate void WorkEndEventHandler(object sender, WorkEndEventArgs e);
protected static readonly object WorkStartEventKey = new object();
protected static readonly object WorkEndEventKey = new object();
private EventHandlerSet _workEventSet = EventHandlerSet.Synchronized(new EventHandlerSet());
public event WorkStartEventHandler WorkStart
{
add => _workEventSet.AddHandler(WorkStartEventKey, value);
remove => _workEventSet.RemoveHandler(WorkStartEventKey, value);
}
public event WorkEndEventHandler WorkEnd
{
add => _workEventSet.AddHandler(WorkEndEventKey, value);
remove => _workEventSet.RemoveHandler(WorkEndEventKey, value);
}
protected virtual void OnWorkStart(WorkStartEventArgs e)
=> _workEventSet.Fire(WorkStartEventKey, this, e);
protected virtual void OnWorkEnd(WorkEndEventArgs e)
=> _workEventSet.Fire(WorkEndEventKey, this, e);
public Form1()
{
InitializeComponent();
this.WorkStart += WorkStart1;
this.WorkStart += WorkStart2;
this.WorkEnd += WorkEnd2;
this.WorkEnd += WorkEnd1;
}
private void btnStart_Click(object sender, EventArgs e)
=> OnWorkStart(new WorkStartEventArgs("工作開始!", DateTime.Now));
private void btnEnd_Click(object sender, EventArgs e)
=> OnWorkEnd(new WorkEndEventArgs("工作結束!", DateTime.Now));
private void WorkStart1(object s, WorkStartEventArgs e)
=> MessageBox.Show(e.Message + " 被 WorkStart1 捕獲");
private void WorkStart2(object s, WorkStartEventArgs e)
=> MessageBox.Show(e.WorkStartTime + " 被 WorkStart2 捕獲");
// ... 同理 WorkEnd1/WorkEnd2
關鍵點: 靜態鍵值物件確保所有實例共用同一鍵值參考,避免重複建立鍵值物件浪費記憶體。EventHandlerSet 優勢在於**大量事件、稀疏訂閱**場景。