如何在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
相关产品推荐
相关产品推荐

