如何监听C# EventHandler的创建与销毁以管控高成本操作?
我想用C#的EventHandler,只在必要的时候触发事件。现有示例代码如下:
class EventHandlerExample { const int Max = 10; static void Main() { var counter = new EventHandlerExample(); counter.OnFinished += (_, args) => Console.WriteLine($"More than threshold: {args.Threshold}"); Console.WriteLine("Starting the count..."); for (int i = 0; i < Max; i++) { Console.WriteLine($" - incrementing to {i + 1}"); counter.Increment(); } } private readonly int _threshold = new Random().Next(Max); private int _total; private void Increment() { _total++; if (_total >= _threshold) { var args = new FinishedEventArgs { Threshold = _threshold, }; OnFinished?.Invoke(this, args); } } public event EventHandler<FinishedEventArgs> OnFinished; } public class FinishedEventArgs : EventArgs { public int Threshold { get; init; } }
OnFinished这个EventHandler会在添加第一个监听器时完成初始化,所以示例里它始终非空;如果去掉counter.OnFinished += ...这行,它就一直是null。哪怕执行下面的操作:
EventHandler<FinishedEventArgs> onFinished = (_, args) => Console.WriteLine($"More than threshold: {args.Threshold}"); counter.OnFinished += onFinished; counter.OnFinished -= onFinished;
OnFinished也会变回null,这点是没问题的。
但我的场景里,触发事件的前置设置(比如示例中Increment()里的相关逻辑)成本极高;而且因为要用Harmony打补丁,我没办法在Increment()里先判断OnFinished是否非空再执行这些高成本设置。同时,我希望所有监听器注销后,能移除这些高成本代码。
我想要一种能监听EventHandler创建和销毁的机制,比如:
OnFinished.OnCreate += SetupHighCostCode; OnFinished.OnDestruction += TearDownHighCostCode;
这种实现可行吗?或者有没有其他办法?目前我只能手动管理事件监听器列表,在Add()和Remove()时做响应。
C#并没有内置的OnCreate/OnDestruction事件来监听EventHandler的生命周期,但你可以通过以下两种方式实现需求,其中自定义事件访问器的方案最简洁且符合标准事件用法:
1. 自定义事件的add/remove访问器(推荐)
C#允许我们重写事件的添加和移除逻辑,不需要手动维护监听器列表——编译器仍会帮我们管理委托字段,但我们可以在add/remove中插入判断逻辑,在第一个监听器添加时初始化高成本资源,最后一个监听器移除时清理资源。
改造后的代码如下:
class EventHandlerExample { const int Max = 10; // 私有委托字段,替代编译器自动生成的字段 private EventHandler<FinishedEventArgs> _onFinished; // 标记高成本资源是否已初始化 private bool _highCostResourcesReady = false; // 线程安全锁(多线程场景必备) private readonly object _eventLock = new object(); static void Main() { var counter = new EventHandlerExample(); counter.OnFinished += (_, args) => Console.WriteLine($"More than threshold: {args.Threshold}"); Console.WriteLine("Starting the count..."); for (int i = 0; i < Max; i++) { Console.WriteLine($" - incrementing to {i + 1}"); counter.Increment(); } // 移除监听器,触发资源清理 var handler = (EventHandler<FinishedEventArgs>)((_, args) => Console.WriteLine($"More than threshold: {args.Threshold}")); counter.OnFinished -= handler; } private readonly int _threshold = new Random().Next(Max); private int _total; private void Increment() { // 只有高成本资源初始化后才执行相关逻辑 if (_highCostResourcesReady) { _total++; if (_total >= _threshold) { var args = new FinishedEventArgs { Threshold = _threshold, }; // 加锁避免多线程下委托为空的情况 lock (_eventLock) { _onFinished?.Invoke(this, args); } } } } // 自定义事件的add/remove逻辑 public event EventHandler<FinishedEventArgs> OnFinished { add { lock (_eventLock) { // 首次添加监听器时初始化高成本资源 if (_onFinished == null) { SetupHighCostResources(); _highCostResourcesReady = true; } _onFinished += value; } } remove { lock (_eventLock) { _onFinished -= value; // 最后一个监听器移除时清理资源 if (_onFinished == null) { TearDownHighCostResources(); _highCostResourcesReady = false; } } } } private void SetupHighCostResources() { Console.WriteLine("初始化高成本资源:安装Harmony补丁、加载重型资源等"); // 这里写你的高成本初始化逻辑 } private void TearDownHighCostResources() { Console.WriteLine("清理高成本资源:卸载Harmony补丁、释放重型资源等"); // 这里写你的资源清理逻辑 } } public class FinishedEventArgs : EventArgs { public int Threshold { get; init; } }
这个方案的优势:
- 完全兼容标准的
+=/-=事件用法,外部调用者无需修改代码 - 无需手动维护监听器列表,避免自己管理带来的线程安全问题(加锁后可支持多线程)
- 精准控制高成本资源的生命周期,只在有监听器时才启用相关逻辑
2. 封装自定义事件类(适合复杂场景)
如果你的场景需要让多个外部组件监听事件的创建/销毁,可以封装一个自定义事件类,内部维护监听器列表并暴露对应的生命周期事件:
public class TrackedEvent<TEventArgs> where TEventArgs : EventArgs { private readonly List<EventHandler<TEventArgs>> _listeners = new List<EventHandler<TEventArgs>>(); private readonly object _lock = new object(); // 暴露事件生命周期的回调 public event EventHandler OnFirstListenerAdded; public event EventHandler OnLastListenerRemoved; public void AddListener(EventHandler<TEventArgs> handler) { lock (_lock) { bool wasEmpty = _listeners.Count == 0; _listeners.Add(handler); if (wasEmpty) { OnFirstListenerAdded?.Invoke(this, EventArgs.Empty); } } } public void RemoveListener(EventHandler<TEventArgs> handler) { lock (_lock) { bool removed = _listeners.Remove(handler); if (removed && _listeners.Count == 0) { OnLastListenerRemoved?.Invoke(this, EventArgs.Empty); } } } public void Invoke(object sender, TEventArgs args) { lock (_lock) { // 遍历副本避免修改列表时的异常 foreach (var handler in _listeners.ToArray()) { handler(sender, args); } } } }
在业务类中使用这个自定义事件:
class EventHandlerExample { const int Max = 10; private readonly TrackedEvent<FinishedEventArgs> _onFinished = new TrackedEvent<FinishedEventArgs>(); private bool _highCostResourcesReady = false; static void Main() { var counter = new EventHandlerExample(); // 监听事件的生命周期 counter._onFinished.OnFirstListenerAdded += (_, __) => counter.SetupHighCostResources(); counter._onFinished.OnLastListenerRemoved += (_, __) => counter.TearDownHighCostResources(); // 添加业务监听器 counter._onFinished.AddListener((_, args) => Console.WriteLine($"More than threshold: {args.Threshold}")); Console.WriteLine("Starting the count..."); for (int i = 0; i < Max; i++) { Console.WriteLine($" - incrementing to {i + 1}"); counter.Increment(); } // 移除业务监听器 var handler = (EventHandler<FinishedEventArgs>)((_, args) => Console.WriteLine($"More than threshold: {args.Threshold}")); counter._onFinished.RemoveListener(handler); } private readonly int _threshold = new Random().Next(Max); private int _total; private void Increment() { if (_highCostResourcesReady) { _total++; if (_total >= _threshold) { var args = new FinishedEventArgs { Threshold = _threshold, }; _onFinished.Invoke(this, args); } } } private void SetupHighCostResources() { Console.WriteLine("初始化高成本资源"); _highCostResourcesReady = true; } private void TearDownHighCostResources() { Console.WriteLine("清理高成本资源"); _highCostResourcesReady = false; } } public class FinishedEventArgs : EventArgs { public int Threshold { get; init; } }
这个方案适合需要多组件感知事件生命周期的场景,但外部调用者需要使用AddListener/RemoveListener而非标准的事件运算符。
内容的提问来源于stack exchange,提问作者Stefan S.

