C# 弱事件模式
传统事件侦听模式潜在内存泄漏问题
普通事件使用 source.event+=listener.delegate,这时event所在的委托会持有listener的强引用;如果listener不再监听event时,需要使用source.event-=listener.delegate来解除引用关系。
若忘记解决引用,则listener的生命周期会受到event的影响,从而产生内存泄漏
⚠⚠⚠人生建议:不需要订阅事件的时候,请取消事件订阅。

弱事件模式主要针对的场景:
- 当listener不知道何时注销事件监听
- 当事件源生存周期比监听者生存周期长
- 当事件源和事件监听者的生命周期并不明确
就可以使用弱事件模式。
弱事件模式的宗旨就是,通过引入事件源和监听者的中间人"WeakEventManager",当事件源的事件触发时,由它负责向事件监听者传递事件。由于WeakEventManager对事件监听者的引用是弱引用,所以,并不会影响监听者被垃圾回收:
弱事件模式的三种实现方式
- 使用WeakEventManager<TEventSource,TEventArgs> ;
- 使用自定义的WeakEventManager;
- 使用现有的WeakEventManager,如:PropertyChangedEventManager等等。
下面以ColorChange事件为Demo,进行对上面介绍的几种实现方式演示:
在演示前,我们先将事件、事件源、事件参数等进行定义
using System;
namespace WeakEventDemo
{
public class ColorChangeEventArgs : EventArgs
{
public int Color { get; set; }
}
public delegate void ColorChangeHanlder(object sender, ColorChangeEventArgs e);
public class ColorController
{
private static ColorController instance;
public static ColorController Instance
{
get
{
if (instance == null)
{
instance = new ColorController();
}
return instance;
}
}
public event ColorChangeHanlder ColorChanged;
public void RaiseColorChangeEvent(int color)
{
var e = new ColorChangeEventArgs();
e.Color = color;
this.ColorChanged?.Invoke(this, e);
}
}
}
使用+=,但是忘记使用-=
- 在UserControl1中订阅ColorController的ColroChanged事件,当ColroChanged事件发生,会弹出消息框以表示响应了ColroChanged事件;
- 按钮MainWindow中第一个按钮,打开子窗口,其中子窗口的内容是UserControl1对象;
- 点击MainWIndow中第二个按钮,引发ColorChanged事件,由于UserControl1订阅了ColroChanged事件,所以会有消息框弹出;
- 关闭子窗口,再次点击MainWindow中第二个按钮,观察是否有消息框弹出。
- 可以看到,还有消息框弹出!为什么!!!难道子窗口复活了?
MainWindow逻辑
<Window
x:Class="WeakEventDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WeakEventDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Grid>
<StackPanel>
<Button
Padding="0,8"
Click="ShowBtn_OnClick"
Content="open a window with content is listenning color change" />
<Rectangle Height="8" />
<Button
Padding="0,8"
Click="RaiseBtn_OnClick"
Content="raise color change event" />
</StackPanel>
</Grid>
</Window>
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace WeakEventDemo
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private void ShowBtn_OnClick(object sender, RoutedEventArgs e)
{
var newWindow = new Window();
newWindow.Width = 200;
newWindow.Height = 200;
newWindow.Owner = this;
newWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner;
newWindow.Content = new UserControl1();
newWindow.Show();
}
private void RaiseBtn_OnClick(object sender, RoutedEventArgs e)
{
ColorController.Instance.RaiseColorChangeEvent(22);
}
}
}
UserControl1逻辑
<UserControl
x:Class="WeakEventDemo.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WeakEventDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="listen to color change event" />
</Grid>
</UserControl>
using System.Windows;
using System.Windows.Controls;
namespace WeakEventDemo
{
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
ColorController.Instance.ColorChanged += Instance_ColorChanged;
}
private void Instance_ColorChanged(object sender, ColorChangeEventArgs e)
{
MessageBox.Show($"color change to {e.Color.ToString()}");
}
}
}
运行效果如下:
通过查看内存中快照可知UserControl1被ColorChanged持有了强引用,所以当子窗口关闭后,UserControl1不能被正常释放。
使用WeakEventManager<TEventSource,TEventArgs> 方式
使用弱事件管理器,解决上面内存泄漏问题,代码很简单,只需对UserControl1进行改造:
using System.Windows;
using System.Windows.Controls;
namespace WeakEventDemo
{
/// <summary>
/// UserControl1.xaml 的交互逻辑
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
//ColorController.Instance.ColorChanged += Instance_ColorChanged;
WeakEventManager<ColorController, ColorChangeEventArgs>.AddHandler(ColorController.Instance, nameof(ColorController.ColorChanged), Instance_ColorChanged);
}
private void Instance_ColorChanged(object sender, ColorChangeEventArgs e)
{
MessageBox.Show($"color change to {e.Color.ToString()}");
}
}
}
下面是运行效果:
可以看到在等待一段时间后,触发ColorChange事件,不会再得到任何响应。
使用自定义的WeakEventManager方式
不使用泛型版本的弱事件管理器的话,可以通过实现WeakEventManager抽象类,来管理事件和事件监听者。不过会多写一些代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace WeaEventDemo
{
public class ColorChangeWeakEventManager : WeakEventManager
{
protected override void StartListening(object source)
{
var eventSource = source as ColorController;
if (eventSource != null)
{
eventSource.ColorChangedEvent += EventSource_ColorChangedEvent;
}
}
private void EventSource_ColorChangedEvent(object sender, ColorChangeEventArgs e)
{
this.DeliverEvent(sender, e);
}
protected override void StopListening(object source)
{
var eventSource = source as ColorController;
if (eventSource != null)
{
eventSource.ColorChangedEvent -= EventSource_ColorChangedEvent;
}
}
protected override ListenerList NewListenerList()
{
return new ListenerList();
}
public void AddListener(object source, IWeakEventListener listener)
{
base.ProtectedAddListener(source, listener);
}
public void RemoveListener(object source, IWeakEventListener listener)
{
base.ProtectedRemoveListener(source, listener);
}
}
}
事件监听者需要实现IWeakEventListener接口
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WeaEventDemo
{
public partial class UserControl1 : UserControl, IWeakEventListener
{
public UserControl1()
{
InitializeComponent();
// 将事件监听者加入事件管理器中
var colorWeakEventManager = new ColorChangeWeakEventManager();
colorWeakEventManager.AddListener(ColorController.Instance, this);
}
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(ColorChangeWeakEventManager))
{
var colorChangeArgs = e as ColorChangeEventArgs;
MessageBox.Show($"color changed to {colorChangeArgs?.Color.ToString(CultureInfo.InvariantCulture)}");
return true;
}
return false;
}
}
}
为特殊场景定制WeakEventManager
如果要处理的事件是一类相关性很高的事件,那么我们可以为这类事件写一套专用的事件处理程序。比如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WeaEventDemo
{
public delegate void ColorChangeHandlerNew(int color);
public static class ColorControllerNew
{
public static event ColorChangeHandlerNew ColorChangedEvent;
public static void RaiseColorChangedEvent(int color)
{
ColorChangedEvent?.Invoke(color);
}
}
}
上面的事件属于和WPF常见的事件不太一样,它没有使用EventArgs来传递事件信息。如何改造事件管理器和事件监听者呢?
事件管理器对外提供添加、移除监听者的静态方法;在事件管理器内部处理事件,然后发送给监听者。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace WeaEventDemo
{
public class SpecificSeriesWeakEventManager : WeakEventManager
{
protected override void StartListening(object source)
{
ColorControllerNew.ColorChangedEvent += EventSource_ColorChangedEvent;
//handle more events here,such as fontsize change,language change ect...
}
private void EventSource_ColorChangedEvent(int color)
{
ColorChangeEventArgs args = new ColorChangeEventArgs();
args.Color = color;
this.DeliverEvent(null, args);
}
protected override void StopListening(object source)
{
ColorControllerNew.ColorChangedEvent -= EventSource_ColorChangedEvent;
}
protected override ListenerList NewListenerList()
{
return new ListenerList();
}
private static SpecificSeriesWeakEventManager CurrentManager
{
get
{
var cachedManager = WeakEventManager.GetCurrentManager(typeof(SpecificSeriesWeakEventManager)) as SpecificSeriesWeakEventManager;
if (cachedManager == null)
{
cachedManager = new SpecificSeriesWeakEventManager();
WeakEventManager.SetCurrentManager(typeof(SpecificSeriesWeakEventManager), cachedManager);
}
return cachedManager;
}
}
public static void AddListener(object source, IWeakEventListener listener)
{
CurrentManager.ProtectedAddListener(source, listener);
}
public static void RemoveListener(object source, IWeakEventListener listener)
{
CurrentManager.ProtectedRemoveListener(source, listener);
}
}
}
实现一个专门用于处理这类事件的监听者基类
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WeaEventDemo
{
public class WeakEventControl : Border, IWeakEventListener
{
public WeakEventControl()
{
SpecificSeriesWeakEventManager.AddListener(null, this);
}
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(SpecificSeriesWeakEventManager))
{
if (e != null && e is ColorChangeEventArgs)
{
OnColorChanged((e as ColorChangeEventArgs).Color);
}
//handle more events here,such as FontsizechangeEventArgs,LanguageChangeEventArgs ect...
return true;
}
return false;
}
protected virtual void OnColorChanged(double color) { }
}
}
具体的监听者
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WeaEventDemo
{
public partial class UserControl1 : WeakEventControl
{
public UserControl1()
{
InitializeComponent();
}
protected override void OnColorChanged(double color)
{
MessageBox.Show($"color changed to {color}");
}
}
}
上面的写法的好处是:对于已知类型的一类特定事件,在监听侧只需接收管理器发送过来的消息;在事件触发侧只需将事件发送出去。事件管理器内部处理订阅、取消订阅事件、通知监听者等工作,较好地实现了解耦。
改造:


浙公网安备 33010602011771号