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

C# 反射取得事件已註冊的處理器清單完整實作

     編輯:關於C#

需求場景

在除錯、診斷、動態移除事件、防止重複註冊等情境下,需要知道某個事件目前綁定了哪些委派方法。C# 事件對外僅暴露 +=/-=,內部委派欄位為私有,需透過反射取得。

核心原理

  1. 事件在編譯後變為:private Delegate _eventField; + public event EventHandler EventName { add { _eventField = Delegate.Combine(_eventField, value); } remove { ... } }
  2. WinForms 控件將所有事件委派集中存放在 EventHandlerList(鍵值對集合),鍵為 static readonly object EVENT_KEY。
  3. 透過反射取得 EventHandlerList 與 EVENT_KEY,即可取出委派鏈,再用 GetInvocationList() 拆解每個方法。

通用輔助類別

using System;
using System.ComponentModel;
using System.Reflection;
using System.Collections.Generic;

public static class EventInspector
{
    /// 
    /// 取得物件指定事件目前註冊的所有處理器方法名稱
    /// 
    public static IEnumerable GetEventHandlers(object obj, string eventName)
    {
        if (obj == null) throw new ArgumentNullException(nameof(obj));
        if (string.IsNullOrEmpty(eventName)) throw new ArgumentException("事件名稱不可為空");

        // 1. 取得 EventHandlerList 屬性(非公開實例屬性)
        PropertyInfo eventsProp = obj.GetType().GetProperty("Events",
            BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
        if (eventsProp == null)
            throw new InvalidOperationException($"型別 {obj.GetType()} 不包含 Events 屬性");

        EventHandlerList list = (EventHandlerList)eventsProp.GetValue(obj);
        if (list == null) yield break;

        // 2. 取得事件對應的鍵欄位(通常命名為 EVENT_事件名)
        FieldInfo keyField = obj.GetType().GetField($"EVENT_{eventName.ToUpper()}",
            BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
        if (keyField == null)
            throw new InvalidOperationException($"找不到事件鍵欄位 EVENT_{eventName.ToUpper()}");

        object key = keyField.GetValue(null);
        Delegate d = list[key];
        if (d == null) yield break;

        // 3. 拆解委派鏈
        foreach (Delegate handler in d.GetInvocationList())
        {
            yield return $"{handler.Method.DeclaringType?.FullName}.{handler.Method.Name}";
        }
    }

    /// 
    /// 判斷事件是否已註冊特定處理器
    /// 
    public static bool IsHandlerRegistered(object obj, string eventName, Delegate targetHandler)
    {
        foreach (var handler in GetEventHandlers(obj, eventName))
        {
            // 這裡只比對方法名稱,嚴格比對需取得 Delegate 實例比較
            if (handler.EndsWith(targetHandler.Method.Name))
                return true;
        }
        return false;
    }
}

WinForms 實戰範例

public partial class DemoForm : Form
{
    public DemoForm()
    {
        InitializeComponent();
        Load += DemoForm_Load1;
        Load += DemoForm_Load2;
        Load += DemoForm_Load1; // 故意重複註冊

        // 檢查 Load 事件註冊情況
        var handlers = EventInspector.GetEventHandlers(this, "Load").ToList();
        foreach (var h in handlers)
            Console.WriteLine($"Load 事件處理器: {h}");

        // 輸出範例:
        // Load 事件處理器: DemoForm.DemoForm_Load1
        // Load 事件處理器: DemoForm.DemoForm_Load2
        // Load 事件處理器: DemoForm.DemoForm_Load1
    }

    private void DemoForm_Load1(object sender, EventArgs e) { }
    private void DemoForm_Load2(object sender, EventArgs e) { }
}

自訂事件類別的檢測(非 WinForms)

若類別未繼承 Component,事件通常直接用欄位儲存:

public class Publisher
{
    public event Action OnMessage;

    public void Raise(string msg) => OnMessage?.Invoke(msg);
}

public static class CustomEventInspector
{
    public static Delegate[] GetInvocationList(object publisher, string eventName)
    {
        FieldInfo field = publisher.GetType().GetField(eventName,
            BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        return (field?.GetValue(publisher) as Delegate)?.GetInvocationList() ?? Array.Empty();
    }
}

注意事項與限制

限制說明
僅能檢測實例事件靜態事件鍵欄位通常為 static,需調整 BindingFlags
匿名方法/Lambda 顯示為編譯器生成名稱如 <Main>b__0_0,無法直接對應原始碼行號
效能開銷反射較慢,避免在熱路徑/高頻迴圈中使用
非 WinForms 類別需自行尋找欄位名慣例為事件名同名欄位,但非強制

總結

透過 EventHandlerList + 事件鍵欄位 + GetInvocationList() 三步驟,即可完整列出 WinForms 控件任一事件的處理器清單。封裝成輔助類別後,可用於單元測試驗證事件綁定、執行期診斷記憶體洩漏(未移除處理器)、防止重複註冊等實務場景。

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