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

.NET 事件與委派深度解析:單播/多播、顯式實作、EventHandlerList 與記憶體優化

     編輯:關於.NET

委派與事件基礎釐清

單播 vs 多播委派

定義:單播委派僅能指向單一方法;多播委派可串聯多個方法 (呼叫清單)。

關鍵事實: 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;
}

編譯器自動產生:

  1. 私有委派欄位 _mailMsg
  2. add_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。

顯式事件:自訂 Add/Remove 邏輯

public event MailMsgEventHandler MailMsg
{
    add
    {
        _mailMsg = (MailMsgEventHandler)Delegate.Combine(_mailMsg, value);
        // 可加入鎖定、日誌、驗證等自訂邏輯
    }
    remove
    {
        _mailMsg = (MailMsgEventHandler)Delegate.Remove(_mailMsg, value);
    }
}

優勢:完全控制訂閱/取消訂閱行為,支援執行緒安全、條件式訂閱等進階需求。

.NET 事件模型與記憶體優化:EventHandlerList

問題:Control 類別約 60 個事件

若每個事件皆由編譯器產生獨立委派欄位,每個 Control 實例將佔用大量記憶體 (60 個委派參考),但大多數實例僅使用少數事件。

解決方案:集中式事件儲存

事件鍵值對儲存示意圖

使用雜湊表以 **事件鍵 (Object) → 委派鏈** 映射,僅在訂閱時建立條目。

Framework 內建:System.ComponentModel.EventHandlerList

  • 內部使用鏈結串列 (非 HashTable),適合少量事件
  • 提供 AddHandler、RemoveHandler、索引器存取

自訂高效 EventHandlerSet (HashTable 實作)

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;

    // 執行緒安全包裝...
}

完整應用範例:自訂事件與 EventHandlerSet

定義事件參數類別

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 優勢在於**大量事件、稀疏訂閱**場景。

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