You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中封装临时事件处理器的添加与移除?

临时事件处理器的优雅实现方案

问题背景

我有一段可能触发c.MyEvent事件的代码(实际场景中有大量类似事件)。需要在执行这段代码前为事件添加MyHandler处理器,执行完成后立即移除。目前的安全实现方式如下:

try
{
    c.MyEvent += MyHandler;
    // 可能触发c.MyEvent事件的代码段...
}
finally
{
    c.MyEvent -= MyHandler;
}

但这个方案存在两个明显缺点:

  • 必须重复指定同一个处理器MyHandler:一次用+=添加,一次用-=移除,容易因疏忽指定错误处理器,违反DRY原则。
  • try/finally的嵌套会让核心代码被大量冗余代码包裹,可读性下降。

期望的理想方案

我希望实现一个通用方法AddScopedEventHandler,满足以下要求:

  • 能将指定处理器(如MyHandler)添加到任意事件(如c.MyEvent)中;
  • 返回一个IDisposable对象,在调用Dispose时自动移除该处理器。

使用该方法后,代码可以简化为:

using (var scopedEventHandling = c.MyEvent.AddScopedEventHandler(MyHandler))
{
        // 可能触发c.MyEvent事件的代码段...
}

实现难点

  • 事件仅支持+=和-=运算符,无法直接为事件编写AddScopedEventHandler扩展方法,编译器会报错;
  • 即使获取到事件对应的委托(移除event关键字),也没有公开方法可以修改委托内部的处理器列表。

现有反射实现(存在不足)

目前我已经实现了基于反射的版本,但使用时需要重复输入对象实例c,不够简洁:

using (var scopedEventHandling = c.AddScopedEventHandler(nameof(c.MyEvent), MyHandler))
{
        // 可能触发c.MyEvent事件的代码段...
}

完整的反射实现代码如下:

using System;
using System.Reflection;

public class C
{
    public event Action<int>? MyEvent;
    public void OnMyEvent(int v) => MyEvent?.Invoke(v);
}

public class Program
{
    private static void MyHandler(int v) { _Dummy = v; }
    private static int _Dummy = 0;

    static void Main(string[] args)
    {
        Console.WriteLine("Test explicit:");
        TestExplicit(new C());
        Console.WriteLine("Test encapsulated:");
        TestEncapsulated(new C());
        Console.ReadKey();
    }

    static void TestExplicit(C c)
    {
        _Dummy = 1;
        try
        {
            c.MyEvent += MyHandler;
            c.OnMyEvent(2);
            Console.WriteLine($"  Expected 2: {_Dummy}");
        }
        finally
        {
            c.MyEvent -= MyHandler;
        }
        _Dummy = 3;
        c.OnMyEvent(4);
        Console.WriteLine($"  Expected 3: {_Dummy}");
    }

    static void TestEncapsulated(C c)
    {
        _Dummy = 1;
        using (var scopedEventHandling = c.AddScopedEventHandler(nameof(c.MyEvent), MyHandler))
        {
            c.OnMyEvent(2);
            Console.WriteLine($"  Expected 2: {_Dummy}");
        }
        _Dummy = 3;
        c.OnMyEvent(4);
        Console.WriteLine($"  Expected 3: {_Dummy}");
    }
}

public static class EventExtensions
{
    public static ScopedEventHandling<T> AddScopedEventHandler<T>(this T eventContainer, string eventName, Delegate handler) where T : class
    {
        Type type = typeof(T);
        var eventInfo = type.GetEvent(eventName);
        if (null == eventInfo)
        {
            throw new ArgumentException($"No event with name \"{eventName}\" found.", nameof(eventName));
        }
        eventInfo.AddEventHandler(eventContainer, handler);
        return new ScopedEventHandling<T>(eventContainer, eventInfo, handler);
    }
}

public class ScopedEventHandling<T> : IDisposable
{
    public ScopedEventHandling(T eventContainer, EventInfo? eventInfo, Delegate handler)
    {
        _EventContainer = eventContainer;
        _EventInfo = eventInfo;
        _Handler = handler;
    }

    public T EventContainer => _EventContainer;
    public EventInfo? EventInfo => _EventInfo;
    public Delegate Handler => _Handler;

    #region IDisposable
    ~ScopedEventHandling()
    {
        Dispose(disposing: false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_Disposed)
        {
            if ((null != _EventContainer) && (null != _EventInfo))
            {
                _EventInfo.RemoveEventHandler(_EventContainer, _Handler);
            }
            _Disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    private bool _Disposed;
    #endregion IDisposable

    private readonly T _EventContainer;
    private readonly EventInfo? _EventInfo;
    private readonly Delegate _Handler;
}

非反射的最优实现方案

要实现无反射且无需重复输入对象实例的版本,我们可以通过封装事件的添加/移除操作来实现,核心思路是将事件的+=和-=操作封装到IDisposable对象中,同时保证类型安全。

通用泛型实现

我们可以创建一个通用的扩展方法和IDisposable实现,通过传入添加、移除处理器的委托来避免反射:

using System;

public class C
{
    public event Action<int>? MyEvent;
    public void OnMyEvent(int v) => MyEvent?.Invoke(v);
}

public class Program
{
    private static void MyHandler(int v) { _Dummy = v; }
    private static int _Dummy = 0;

    static void Main(string[] args)
    {
        Console.WriteLine("Test explicit:");
        TestExplicit(new C());
        Console.WriteLine("Test optimized:");
        TestOptimized(new C());
        Console.ReadKey();
    }

    static void TestExplicit(C c)
    {
        _Dummy = 1;
        try
        {
            c.MyEvent += MyHandler;
            c.OnMyEvent(2);
            Console.WriteLine($"  Expected 2: {_Dummy}");
        }
        finally
        {
            c.MyEvent -= MyHandler;
        }
        _Dummy = 3;
        c.OnMyEvent(4);
        Console.WriteLine($"  Expected 3: {_Dummy}");
    }

    static void TestOptimized(C c)
    {
        _Dummy = 1;
        using (c.ScopedHandler(h => c.MyEvent += h, h => c.MyEvent -= h, MyHandler))
        {
            c.OnMyEvent(2);
            Console.WriteLine($"  Expected 2: {_Dummy}");
        }
        _Dummy = 3;
        c.OnMyEvent(4);
        Console.WriteLine($"  Expected 3: {_Dummy}");
    }
}

public static class ScopedHandlerExtensions
{
    public static IDisposable ScopedHandler<T>(this object _, Action<T> addHandler, Action<T> removeHandler, T handler)
        where T : Delegate
    {
        addHandler(handler);
        return new ScopedEventHandler<T>(removeHandler, handler);
    }
}

public class ScopedEventHandler<T> : IDisposable
    where T : Delegate
{
    private readonly Action<T> _removeHandler;
    private readonly T _handler;
    private bool _disposed;

    public ScopedEventHandler(Action<T> removeHandler, T handler)
    {
        _removeHandler = removeHandler;
        _handler = handler;
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            _removeHandler(_handler);
            _disposed = true;
        }
    }
}

贴近需求的语法糖实现

如果想要更贴近最初设想的c.MyEvent.AddScopedEventHandler(MyHandler)语法,可以针对具体的事件委托类型编写扩展方法:

public static class EventScopedExtensions
{
    public static IDisposable AddScopedEventHandler(this Action<int>? @event, Action<int> handler, Action<Action<int>> add, Action<Action<int>> remove)
    {
        add(handler);
        return new ScopedEventHandler<Action<int>>(remove, handler);
    }
}

使用时传入添加和移除的委托即可:

using (c.MyEvent.AddScopedEventHandler(MyHandler, h => c.MyEvent += h, h => c.MyEvent -= h))
{
    // 核心代码
}

这种方式完全避免了反射,保证了类型安全,也不会出现处理器指定错误的问题。

特定事件的专属简化扩展

如果是自定义类,还可以为特定事件编写专属扩展方法,进一步简化调用:

public static class CEventExtensions
{
    public static IDisposable AddScopedMyEventHandler(this C c, Action<int> handler)
    {
        c.MyEvent += handler;
        return new ScopedEventHandler<Action<int>>(h => c.MyEvent -= h, handler);
    }
}

调用时非常简洁:

using (c.AddScopedMyEventHandler(MyHandler))
{
    // 核心代码
}

这种方式完全满足DRY原则,没有冗余代码,无需反射,类型安全且可读性高。


内容的提问来源于stack exchange,提问作者Carsten Scholtes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 20:22:05