弱事件模式可避免内存泄漏,因普通事件订阅使发布者强引用订阅者导致GC无法回收;WeakEventManager是.NET内置稳妥方案,需自定义管理器类并正确使用AddHandler;手动实现易出错,推荐用CommunityToolkit.Mvvm等成熟封装。

弱事件模式不是万能解药,但不用它,EventHandler 引用常导致后台服务、ViewModel 或长期存活对象无法被 GC 回收。
为什么普通事件订阅会引发内存泄漏
当对象 A 订阅对象 B 的事件(如 B.PropertyChanged += A.OnPropertyChanged),B 会持有一个对 A 的强引用(通过委托内部的 Target 字段)。只要 B 活着,A 就不会被回收——哪怕 A 本该随 UI 页面卸载而释放。
典型场景包括:
- WPF 中
INotifyPropertyChanged订阅者是 ViewModel,而发布者是长期存活的静态服务或单例 - WinForms 控件订阅了某个业务类的事件,但控件已 Dispose,业务类仍强引用着它
- 使用
DispatcherTimer.Tick时,回调委托捕获了窗体实例,而 Timer 未 Stop/Dispose
WeakEventManager 是最稳妥的内置方案
.NET Framework 4.5+ 和 .NET Core/.NET 5+ 均支持 WeakEventManager。它不持有事件处理者的强引用,GC 可正常回收订阅者。
实操要点:
- 必须为每个事件类型定义专属管理器类,继承自
WeakEventManager - 重写
StartListening和StopListening,内部调用源对象的+=/-= - 注册时用
CurrentManager.AddHandler,而非直接+= - 确保事件参数类型明确(不能用
EventArgs通配,否则泛型推导失败)
public class PropertyChangeWeakEventManager : WeakEventManager{ private static readonly PropertyChangeWeakEventManager _current = new(); public static PropertyChangeWeakEventManager Current => _current; protected override void StartListening(INotifyPropertyChanged source) => source.PropertyChanged += DeliverEvent; protected override void StopListening(INotifyPropertyChanged source) => source.PropertyChanged -= DeliverEvent; } // 使用方式 PropertyChangeWeakEventManager.Current.AddHandler(viewModel, "PropertyChanged", handler);
手动实现 WeakReference + 委托包装要避开三个坑
若需更轻量或兼容老框架,可手写弱事件包装,但极易出错:
-
委托缓存失效:每次用
new EventHandler(...)包装都会生成新委托,-=时无法匹配,导致重复订阅或无法取消 -
Target 为空时未检查:弱引用可能已回收,但事件触发时仍尝试调用
target?.Method(),若没判空就抛NullReferenceException -
未同步清理 WeakEventManager 内部字典:.NET 的
WeakEventManager内部用ConditionalWeakTable自动清理;手动实现若用Dictionary,需自行轮询或依赖GC.KeepAlive干预
推荐用现成封装,比如 CommunityToolkit.Mvvm 中的 WeakReferenceMessenger 替代事件,或直接用 WeakEventManager。
调试内存泄漏时优先查 EventHandlers 字段
在 Visual Studio 的内存快照中,筛选 “EventHandler” 或 “Delegate”,看其 Target 是否指向不该存在的 UI 元素或 ViewModel 实例。若发现大量同名委托 Target 指向已关闭窗口,基本可判定是事件未解订阅或未走弱事件路径。
注意:+= 和 -= 必须成对出现;Lambda 表达式订阅(x => Handle(x))无法安全 -=,除非把委托存为字段。










