2026/1/10 13:24:17
网站建设
项目流程
网站建设的行业客户,重庆最新新闻头条,长沙口碑最好网站建设公司排行榜,网易企业邮箱改密码一、IWeakEventListener 核心定义
IWeakEventListener 是 WPF 框架中弱事件模式#xff08;Weak Event Pattern#xff09; 的核心接口#xff0c;用于实现弱引用事件监听。其核心目的是解决普通事件订阅导致的内存泄漏问题——让事件订阅者#xff08;Listener#xff09…一、IWeakEventListener 核心定义IWeakEventListener是 WPF 框架中弱事件模式Weak Event Pattern的核心接口用于实现弱引用事件监听。其核心目的是解决普通事件订阅导致的内存泄漏问题——让事件订阅者Listener在无其他强引用时能被垃圾回收GC即使发布者Publisher仍持有对订阅者的弱引用。二、为什么需要 IWeakEventListener先理解普通事件订阅的问题WPF 中普通事件订阅如publisher.Event subscriber.Handler会让发布者持有订阅者的强引用。如果发布者生命周期远长于订阅者比如全局数据源订阅了临时窗口的事件即使订阅者窗口关闭发布者的强引用仍会阻止 GC 回收订阅者最终导致内存泄漏。而弱事件模式的核心是发布者通过「弱引用」关联订阅者订阅者无其他强引用时可被 GC 回收同时事件管理器会自动清理无效的弱引用避免内存泄漏。IWeakEventListener就是订阅者需要实现的接口用于接收弱事件管理器分发的事件。三、IWeakEventListener 接口结构该接口仅定义一个方法是弱事件的「事件处理入口」publicinterfaceIWeakEventListener{// 接收弱事件管理器分发的事件boolReceiveWeakEvent(TypemanagerType,objectsender,EventArgse);}参数/返回值说明成员作用managerType触发事件的弱事件管理器类型如PropertyChangedEventManager用于区分不同事件源sender事件发布者同普通事件的 sendere事件参数同普通事件的 EventArgs返回值bool表示是否成功处理该事件true已处理false未处理可用于事件冒泡四、弱事件模式的核心组成弱事件模式需要 3 个角色配合IWeakEventListener是「订阅者」的核心发布者Publisher触发事件的对象如实现INotifyPropertyChanged的 ViewModel订阅者Subscriber实现IWeakEventListener的对象如临时窗口弱事件管理器WeakEventManager协调发布者和订阅者的中间层WPF 内置了常用管理器也可自定义。WPF 内置的常用弱事件管理器无需自定义管理器类对应事件适用场景PropertyChangedEventManagerINotifyPropertyChanged.PropertyChanged数据绑定的属性变更CollectionChangedEventManagerINotifyCollectionChanged.CollectionChanged集合变更如ObservableCollectionRoutedEventManagerWPF 路由事件如Button.Click控件路由事件的弱订阅五、完整使用示例以「ViewModel发布者→ Window订阅者实现 IWeakEventListener」为例演示弱事件订阅步骤 1定义发布者ViewModel// 发布者实现INotifyPropertyChanged触发PropertyChanged事件publicclassDataViewModel:INotifyPropertyChanged{privatestring_name;publicstringName{get_name;set{_namevalue;OnPropertyChanged(nameof(Name));}}publiceventPropertyChangedEventHandler?PropertyChanged;privatevoidOnPropertyChanged(stringpropertyName){PropertyChanged?.Invoke(this,newPropertyChangedEventArgs(propertyName));}}步骤 2实现订阅者Window实现 IWeakEventListener// 订阅者临时窗口实现IWeakEventListener避免内存泄漏publicpartialclassWeakEventWindow:Window,IWeakEventListener{privatereadonlyDataViewModel_viewModel;publicWeakEventWindow(DataViewModelviewModel){InitializeComponent();_viewModelviewModel;// 关键通过弱事件管理器订阅事件而非直接PropertyChangedEventManager.AddListener(source:_viewModel,// 事件发布者listener:this,// 事件订阅者实现IWeakEventListenereventName:nameof(_viewModel.PropertyChanged)// 订阅的事件名);}// 实现IWeakEventListener的核心方法处理弱事件publicboolReceiveWeakEvent(TypemanagerType,objectsender,EventArgse){// 仅处理PropertyChangedEventManager的事件if(managerTypetypeof(PropertyChangedEventManager)){varargseasPropertyChangedEventArgs;if(args?.PropertyNamenameof(_viewModel.Name)){// 处理Name属性变更逻辑Console.WriteLine($Name变更为{_viewModel.Name});returntrue;// 标记为已处理}}returnfalse;// 未处理其他类型的事件}// 窗口关闭时可选手动移除监听非必须GC会自动清理protectedoverridevoidOnClosed(EventArgse){base.OnClosed(e);PropertyChangedEventManager.RemoveListener(_viewModel,this,nameof(_viewModel.PropertyChanged));}}步骤 3使用示例// 模拟全局长生命周期的ViewModelvarglobalViewModelnewDataViewModel();// 创建临时窗口订阅者vartempWindownewWeakEventWindow(globalViewModel);tempWindow.Show();// 关闭窗口后无其他强引用指向tempWindowGC可回收它tempWindow.Close();tempWindownull;// 触发GCtempWindow会被回收普通事件订阅则不会GC.Collect();GC.WaitForPendingFinalizers();六、自定义弱事件管理器针对自定义事件如果需要订阅自定义事件非 WPF 内置管理器覆盖的事件需自定义WeakEventManager子类核心是重写 3 个方法// 自定义弱事件管理器针对自定义事件 CustomEventpublicclassCustomWeakEventManager:WeakEventManager{// 单例获取管理器privatestaticCustomWeakEventManager?_instance;privatestaticCustomWeakEventManagerInstance{get{_instance??newCustomWeakEventManager();return_instance;}}// 订阅事件对外暴露的订阅方法publicstaticvoidAddListener(ICustomPublishersource,IWeakEventListenerlistener){Instance.ProtectedAddListener(source,listener);}// 取消订阅publicstaticvoidRemoveListener(ICustomPublishersource,IWeakEventListenerlistener){Instance.ProtectedRemoveListener(source,listener);}// 重写开始监听发布者的事件protectedoverridevoidStartListening(objectsource){if(sourceisICustomPublisherpublisher){publisher.CustomEventOnCustomEvent;}}// 重写停止监听发布者的事件protectedoverridevoidStopListening(objectsource){if(sourceisICustomPublisherpublisher){publisher.CustomEvent-OnCustomEvent;}}// 事件触发时分发到弱事件管理器的所有订阅者privatevoidOnCustomEvent(object?sender,EventArgse){DeliverEvent(sender,e);}}// 自定义发布者接口publicinterfaceICustomPublisher{eventEventHandlerCustomEvent;}七、注意事项性能开销弱事件模式通过反射和弱引用实现比普通事件略耗性能仅在「发布者生命周期远长于订阅者」时使用如全局数据源→临时控件普通场景无需使用强引用排查即使使用弱事件若订阅者被其他强引用如静态变量、集合持有仍无法被 GC 回收需确保无额外强引用事件参数类型转换ReceiveWeakEvent中需手动将EventArgs转换为具体类型如PropertyChangedEventArgs建议加空值判断手动移除监听虽然 GC 会自动清理但窗口/控件关闭时手动移除监听RemoveListener可提前释放资源更优雅。八、总结IWeakEventListener是 WPF 弱事件模式的「订阅者规范」核心价值是让事件订阅者摆脱发布者的强引用束缚避免内存泄漏配合弱事件管理器实现「订阅者可被 GC 自动回收」的事件订阅是 WPF 中处理「长生命周期发布者 短生命周期订阅者」场景的标准方案。简单来说普通事件用 订阅强引用易泄漏弱事件通过 IWeakEventListener 事件管理器订阅弱引用安全回收。下面提供一个可直接运行的WPF测试代码能直观看到「多次点击创建弹窗→关闭弹窗→内存泄漏」的现象。第一步创建WPF项目替换代码新建WPF项目.NET Framework 4.8或.NET 6/7/8都可以替换以下3个文件的代码1. 发布者长生命周期静态对象GlobalPublisher.csusingSystem;namespaceWpfMemoryLeakTest{// 全局发布者静态对象程序运行期间一直存在长生命周期publicstaticclassGlobalPublisher{// 定义一个普通事件强引用订阅publicstaticeventEventHandler?WeatherChanged;// 模拟触发事件测试用这里不用触发也能看泄漏publicstaticvoidRaiseWeatherChanged(){WeatherChanged?.Invoke(null,EventArgs.Empty);}}}2. 泄漏的子窗口订阅者短生命周期LeakWindow.xaml.csusingSystem;usingSystem.Windows;namespaceWpfMemoryLeakTest{/// summary/// LeakWindow.xaml 的交互逻辑/// 这个窗口会订阅全局事件且关闭时不取消订阅 → 导致内存泄漏/// /summarypublicpartialclassLeakWindow:Window{publicLeakWindow(){InitializeComponent();// 关键用普通订阅全局事件强引用// 问题核心GlobalPublisher是静态的会持有当前窗口的强引用GlobalPublisher.WeatherChangedOnWeatherChanged;}// 空的事件处理方法只是为了订阅不需要实际逻辑privatevoidOnWeatherChanged(object?sender,EventArgse){}// 窗口关闭时只关闭窗口不取消事件订阅泄漏的关键protectedoverridevoidOnClosed(EventArgse){base.OnClosed(e);// 【如果要修复泄漏只需加这一行取消订阅】// GlobalPublisher.WeatherChanged - OnWeatherChanged;}}}3. LeakWindow.xaml空窗口即可Windowx:ClassWpfMemoryLeakTest.LeakWindowxmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentationxmlns:xhttp://schemas.microsoft.com/winfx/2006/xamlTitle泄漏测试窗口Height200Width300GridTextBlockHorizontalAlignmentCenterVerticalAlignmentCenterText这个窗口关闭后会泄漏//Grid/Window4. 主窗口测试入口MainWindow.xaml.csusingSystem;usingSystem.Windows;usingSystem.Windows.Threading;namespaceWpfMemoryLeakTest{publicpartialclassMainWindow:Window{// 记录创建的窗口数量方便看点击次数privateint_windowCount0;publicMainWindow(){InitializeComponent();}// 点击按钮创建并显示泄漏窗口privatevoidCreateLeakWindow_Click(objectsender,RoutedEventArgse){_windowCount;CountText.Text$已创建窗口数{_windowCount};// 每次点击都新建一个窗口对象关键每次都是新实例varleakWindownewLeakWindow();leakWindow.Show();// 显示后立即关闭模拟“打开就关”的场景leakWindow.Close();}// 点击按钮强制触发GC看内存是否回收privatevoidForceGC_Click(objectsender,RoutedEventArgse){// 强制GC连续两次确保回收彻底GC.Collect();GC.WaitForPendingFinalizers();GC.Collect();MessageBox.Show(已触发GC请查看内存变化);}}}5. MainWindow.xamlWindowx:ClassWpfMemoryLeakTest.MainWindowxmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentationxmlns:xhttp://schemas.microsoft.com/winfx/2006/xamlTitle内存泄漏测试Height300Width400StackPanelHorizontalAlignmentCenterVerticalAlignmentCenterSpacing20Buttonx:NameCreateLeakWindowContent创建并关闭泄漏窗口Width200Height40ClickCreateLeakWindow_Click/TextBlockx:NameCountTextFontSize16Text已创建窗口数0/Buttonx:NameForceGCContent强制触发GCWidth200Height40ClickForceGC_Click//StackPanel/Window第二步测试步骤直观看到泄漏运行程序打开Windows「任务管理器」→ 详细信息 → 找到你的WPF程序WpfMemoryLeakTest.exe关注「内存专用工作集」列初始内存程序刚运行时内存大概在50~80MB左右疯狂点击「创建并关闭泄漏窗口」按钮比如点击500次任务管理器里的内存会持续上涨比如涨到200~300MB点击「强制触发GC」按钮内存几乎不会下降因为GlobalPublisher还持有所有窗口的强引用GC收不走修复测试验证泄漏原因打开LeakWindow.xaml.cs把OnClosed里注释的那行取消注释GlobalPublisher.WeatherChanged - OnWeatherChanged;重新运行程序重复步骤3~4→ 点击500次后内存上涨但触发GC后内存会大幅回落接近初始值说明窗口被回收了。第三步核心解释为啥会泄漏每次点击「创建窗口」都会new LeakWindow()→ 生成一个全新的窗口对象窗口构造函数里用订阅了静态GlobalPublisher的事件 → 静态对象持有窗口的强引用窗口关闭后虽然你看不到它了但静态对象的强引用还在 → GC认为“这个窗口还有用”不会回收点击次数越多内存里堆的无效窗口对象越多 → 内存泄漏。第四步如果用IWeakEventListener替换修复泄漏不用手动取消订阅如果不想手动写-取消订阅也可以把LeakWindow改成实现IWeakEventListener弱事件核心改法// 改写LeakWindow用弱事件订阅无需手动取消publicpartialclassLeakWindow:Window,IWeakEventListener{publicLeakWindow(){InitializeComponent();// 替换普通用弱事件管理器订阅CustomWeakEventManager.AddListener(this);}// 实现IWeakEventListenerpublicboolReceiveWeakEvent(TypemanagerType,objectsender,EventArgse){if(managerTypetypeof(CustomWeakEventManager)){// 空处理仅满足接口returntrue;}returnfalse;}}// 自定义弱事件管理器适配GlobalPublisher的WeatherChanged事件publicclassCustomWeakEventManager:WeakEventManager{privatestaticCustomWeakEventManager?_instance;privatestaticCustomWeakEventManagerInstance_instance??newCustomWeakEventManager();publicstaticvoidAddListener(LeakWindowlistener){Instance.ProtectedAddListener(GlobalPublisher,listener);}protectedoverridevoidStartListening(objectsource){GlobalPublisher.WeatherChangedOnEvent;}protectedoverridevoidStopListening(objectsource){GlobalPublisher.WeatherChanged-OnEvent;}privatevoidOnEvent(object?sender,EventArgse){DeliverEvent(sender,e);}}改完后即使不手动写-关闭窗口后触发GC内存也会回落因为弱引用不会阻止GC回收。最终结论不使用IWeakEventListener/不手动取消订阅 → 长生命周期发布者静态/全局对象会持有短生命周期订阅者弹窗的强引用 → 内存泄漏用IWeakEventListener弱事件→ 发布者只持有弱引用 → 订阅者没用后会被GC回收 → 无泄漏。可以直接跑这个代码肉眼就能看到内存涨了之后GC收不回来的现象非常直观。