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

趣味程式設計:將事件封裝為物件的四種建構模式

     編輯:關於.NET

在 .NET 中,事件本質上是一對 add/remove 存取器,若能將其封裝為獨立物件 DelegateEvent<TDelegate>,便能像委派一樣自由傳遞、組合、管理事件訂閱。本文探討四種建構 DelegateEvent 的模式,各有適用場景與優缺點。

核心介面設計

public class DelegateEvent<TDelegate> where TDelegate : Delegate
{
    private Action<TDelegate> _addHandler;
    private Action<TDelegate> _removeHandler;

    public DelegateEvent<TDelegate> AddHandler(TDelegate handler) { ... }
    public DelegateEvent<TDelegate> RemoveHandler(TDelegate handler) { ... }
}

關鍵在於如何初始化 _addHandler 與 _removeHandler 兩個委派欄位,以下介紹四種建構函式模式。

模式一:直接注入 Add/Remove 邏輯

最直觀的方式,由呼叫端提供具體的加減邏輯:

public DelegateEvent(Action<TDelegate> add, Action<TDelegate> remove)
{
    if (add == null || remove == null) throw new ArgumentNullException();
    if (!typeof(Delegate).IsAssignableFrom(typeof(TDelegate)))
        throw new ArgumentException("TDelegate must be a delegate type.");

    _addHandler = add;
    _removeHandler = remove;
}

用法:

var myClass = new MyClass();
var de = new DelegateEvent<EventHandler>(
    h => myClass.MyEvent += h,
    h => myClass.MyEvent -= h);

缺點: 無法驗證注入邏輯正確性(如誤用 += 取代 -=),且呼叫端程式碼冗長。

模式二:物件實例 + 事件名稱(反射綁定)

透過反射取得 EventInfo,自動繫結存取器:

public DelegateEvent(object obj, string eventName)
{
    if (obj == null || string.IsNullOrEmpty(eventName))
        throw new ArgumentNullException();
    BindEvent(obj.GetType(), obj, eventName);
}

private void BindEvent(Type type, object obj, string eventName)
{
    var flags = BindingFlags.Public | BindingFlags.NonPublic |
                (obj == null ? BindingFlags.Static : BindingFlags.Instance);
    var eventInfo = type.GetEvent(eventName, flags);
    if (eventInfo == null)
        throw new ArgumentException($"Event {eventName} not found in {type.FullName}");
    if (eventInfo.EventHandlerType != typeof(TDelegate))
        throw new ArgumentException($"Event handler type mismatch.");

    _addHandler = h => eventInfo.AddEventHandler(obj, (Delegate)(object)h);
    _removeHandler = h => eventInfo.RemoveEventHandler(obj, (Delegate)(object)h);
}

用法: new DelegateEvent<EventHandler>(myClass, "MyEvent")

缺點: 字串型別事件名稱無編譯期檢查,重構不支援;無法綁定靜態事件(需另行處理)。

模式三:型別 + 事件名稱(支援靜態事件)

針對靜態事件,傳入 Type 而非實例:

public DelegateEvent(Type type, string eventName)
{
    if (type == null || string.IsNullOrEmpty(eventName))
        throw new ArgumentNullException();
    BindEvent(type, null, eventName); // obj 為 null 表示靜態事件
}

用法: new DelegateEvent<EventHandler>(typeof(MyClass), "MyStaticEvent")

同樣面臨字串硬編碼的維護性問題。

模式四:運算式樹指定事件(強型別、支援重構)

利用 Expression<Func<TDelegate>> 捕獲事件成員資訊:

public DelegateEvent(Expression<Func<TDelegate>> eventExpr)
{
    if (eventExpr == null) throw new ArgumentNullException(nameof(eventExpr));
    var memberExpr = eventExpr.Body as MemberExpression;
    if (memberExpr == null) throw new ArgumentException("Not an event access expression.");

    object instance = null;
    if (memberExpr.Expression != null)
    {
        var instanceExpr = Expression.Lambda<Func<object>>(memberExpr.Expression);
        instance = instanceExpr.Compile().Invoke(); // 取得實例(或 null 為靜態)
    }
    BindEvent(memberExpr.Member.DeclaringType, instance, memberExpr.Member.Name);
}

輔助工廠簡化呼叫:

public static class EventFactory
{
    public static DelegateEvent<T> Create<T>(Expression<Func<T>> eventExpr)
        => new DelegateEvent<T>(eventExpr);
}

用法:

class Program
{
    public event EventHandler MyEvent;
    public static event EventHandler MyStaticEvent;

    static void Main()
    {
        var sde = EventFactory.Create(() => MyStaticEvent); // 靜態事件
        var p = new Program();
        var de = EventFactory.Create(() => p.MyEvent);      // 實例事件
    }
}

優勢: 完整支援 IntelliSense、編譯期檢查、重構重命名。
限制: 僅能在「定義事件的類別內部」使用,因需存取事件成員(外部無法直接存取 event 的 add/remove)。

總結與選型建議

模式適用場景優點缺點
直接注入快速原型、特殊邏輯彈性最大無驗證、程式碼冗長
實例+字串執行期動態綁定、外部組件通用性強無靜態檢查、無法重構
型別+字串靜態事件綁定支援靜態事件同字串缺點
運算式樹事件定義者內部暴露強型別、完整工具鏈支援僅限內部使用、語法稍複雜

優秀的 API 設計應預見多種使用情境,提供多重建構方式,並儘早暴露錯誤(編譯期優於執行期)。這正是「趣味程式設計」的核心:在有限範圍內深入挖掘,考慮周全,而非僅求「能跑」。

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