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

.NET 事件監聽機制的限制與擴展:優先級排序與高效能訂閱/退訂

     編輯:ASP.NET 基礎

前言:內建事件機制的兩大痛點

.NET 將事件視為一等公民,語法優雅(btn.Click += Handler)。但在實務大型系統中,MulticastDelegate 內部以陣列儲存訂閱者,導致兩大限制:

  1. 無法保證呼叫順序:雖然目前實作依註冊順序呼叫,但屬於實作細節,未來可能變更;跨模組時更難控制順序。
  2. 大量訂閱者時增刪效能差:陣列查找 O(N)、移除需搬移元素,訂閱者達千級以上明顯拖慢效能。

本文分享兩種擴展模式:介面式優先級訂閱與雙向鏈表 + Token 模式,分別解決順序與效能問題。

方案一:介面式優先級訂閱——解決呼叫順序

參考 Java ActionListener 模式,定義介面 IInteractor,每個 UI 事件對應一個方法;發布者 InteractSource 維護 SortedList<int, IInteractor> 或 List<(int Priority, IInteractor)>,依優先級排序後依序呼叫。


public interface IInteractor
{
    void OnMouseDown(MouseEventArgs e);
    void OnMouseMove(MouseEventArgs e);
    void OnMouseUp(MouseEventArgs e);
    // ...
}

public class InteractSource
{
    private readonly List<(int Priority, IInteractor Interactor)> _interactors = new();

    public void AddInteractor(int priority, IInteractor interactor)
    {
        _interactors.Add((priority, interactor));
        _interactors.Sort((a, b) => a.Priority.CompareTo(b.Priority)); // 數字小 = 優先級高
    }

    internal void DispatchMouseDown(MouseEventArgs e)
    {
        foreach (var (_, interactor) in _interactors)
            interactor.OnMouseDown(e);
    }
    // 其他事件同理
}

優點:順序明確、跨模組可控、單元測試友善。缺點:需手動實作每個事件的 Dispatch 邏輯,無法自動對應 CLR 事件語法。

方案二:雙向鏈表 + Token——解決增刪效能

核心思想:訂閱時回傳包含鏈表節點參考的 Token,退訂時直接用節點 O(1) 移除。


public class EventSource
{
    private readonly LinkedList<IEventListener> _list = new();

    public Token AddListener(IEventListener listener)
    {
        var node = _list.AddLast(listener);
        return new Token(node);
    }

    public void RemoveListener(Token token) => _list.Remove(token.Node);

    public void RaiseEvent(EventArgs e)
    {
        foreach (var listener in _list) listener.OnEvent(e);
    }

    public class Token
    {
        internal LinkedListNode<IEventListener> Node { get; }
        public Token(LinkedListNode<IEventListener> node) => Node = node;
    }
}

public interface IEventListener { void OnEvent(EventArgs e); }

使用方式:


var token = source.AddListener(myListener);
// ...
source.RemoveListener(token); // O(1)

進階:Token 可再攜帶 EventSource 參考,退訂時自動驗證來源,防止「退訂從未訂閱的來源」Bug。

兩者結合:優先級 + 高效能

若同時需要順序與效能,可在鏈表節點中額外儲存 Priority,訂閱時依 Priority 尋找插入位置(O(N) 查找、O(1) 插入),退訂仍 O(1)。或改用 SortedDictionary<int, LinkedList<IEventListener>> 分級鏈表。

對比總結

方案順序保證訂閱/退訂效能適用場景
MulticastDelegate (內建)不保證O(N)一般 UI、訂閱者<100
介面 + Priority✅ 明確O(N) 訂閱 / O(N) 退訂需嚴格順序的管線、解釋器模式
雙向鏈表 + Token依插入順序O(1) / O(1)高頻動態訂閱/退訂、插件系統
混合方案✅ 明確O(N) 訂閱 / O(1) 退訂兩者皆需

現代化替代:Reactive Extensions (Rx.NET)

若專案已引入 Rx,Subject<T>、Observable 內部同樣採用無鎖鏈表/不可變陣列,提供 Subscribe 回傳 IDisposable(相當於 Token),並支援 Priority 擴充套件,建議直接使用成熟程式庫而非自行實作。

總結

.NET 內建事件機制適用於 90% 場景。當遭遇「順序敏感」或「大量動態訂閱」時,採用介面式優先級或Token + 雙向鏈表模式擴展,能以最小代價獲得確定性與高效能。理解背後資料結構(陣列 vs 鏈表)權衡,是架構師進階的關鍵一課。

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