c#高级编程--弱事件
背景
普通.net事件是强引用,会导致内存泄漏,就是监听者被事件源引用了,得不到释放。因此引入弱事件。
通过事件,直接连接到发布程序和侦听器。但垃圾回收有一个问题。例如,如果侦听器不再直接引用,发布程序就仍有一个引用。垃圾回收器不能清空侦听器占用的内存,因为发布程序仍保有一个引用,会针对侦听器触发事件。这种强连接可以通过弱事件模式来解决,即使用WeekEventManager作为发布程序和侦听器之间的中介。
要使用弱事件,需要创建一个派生自WeekEventManager类的类。WeekEventManager类在程序集WindowsBase的名称空间System.Windows中定义。对于弱事件模式,弱事件管理器类需要静态方法AddListener()和StopListening()。侦听器使用这些方法连接发布程序,和断开与发布程序的连接,而不是直接使用发布程序中的事件。可以通过AddListener()和RemoveListener()方法,调用WeekEventManager基类中的方法,来添加和删除侦听器。
弱事件管理器还需要重写基类的StartListening()和StopListening()方法。添加一个侦听器时调用StartListening()方法,删除最后一个侦听器时调用StopListening()方法。DeliverEvent()方法在侦听器中调用IWeekEventLister接口中的ReceiveWeekEvent()方法。侦听器需要实现IWeekEventListener接口,这个接口定义了ReceiveWeekEvent()方法,触发事件时,从弱事件管理器中调用这个方法。
自定义弱事件
按顺序点击按钮,先初始化两个订阅类,并绑定publisher事件
然后将两个订阅者置为null,并执行GC
此时按道理来说,订阅者应该收不到消息了,因为已经被回收了。
但是因为publisher.event += subscriber.xxx
实际上导致 subscriber被人引用了,所以实际上是无法回收的。
通过第四步,手动执行事件,可以看出普通的订阅类依旧 还是会收到消息通知
但是继承了弱事件接口(IweakListener)的类,确实被回收了。
所以综上所述,弱引用模式引入,可以解决一些事件订阅后,我们要销毁订阅者类,但是实际没被回收的内存泄漏问题。
缺点:
需要引入一个接口,和一个管理类,代码结构上有些繁琐,而且看了下管理类的源码,内部较为复杂且使用了较多的反射
个人还是建议直接用减等于这种显示的写法, publisher.event -= subscriber.xxx 。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Forms; namespace weakEventTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); } // 消息发布者 Publisher publisher = new Publisher(); // 普通消息订阅者 Subscriber normalSubscriber = null; // 弱事件消息订阅者 Subscriber weakSubscriber = null; private void btnNormal_Click(object sender, EventArgs e) { // 创建普通消息订阅者,并绑定到事件源 normalSubscriber = new Subscriber("普通订阅者"); publisher.SampleEvent += normalSubscriber.Receiver; Console.WriteLine("普通订阅者初始化并绑定事件"); } private void btnWeakEvent_Click(object sender, EventArgs e) { // 创建弱事件消息订阅者,并添加到事件源 weakSubscriber = new Subscriber("弱引用订阅者"); WeakCarInfoEventManager.AddListener(publisher, weakSubscriber); Console.WriteLine("弱引用订阅者初始化并绑定事件"); } private void btnGC_Click(object sender, EventArgs e) { // 解除注册,GC将可以成功回收对象 //publisher.SampleEvent -= normalSubscriber.Receiver; // 尝试将普通事件订阅者销毁并使用GC回收,实际GC没有将对象回收 normalSubscriber = null; Console.WriteLine("将普通订阅者置为null"); // 尝试将弱事件订阅者销毁,并使用GC回收,GC成功将对象回收 weakSubscriber = null; Console.WriteLine("将弱引用订阅者置为null"); GC.Collect(); } private void btnRiseEvent_Click(object sender, EventArgs e) { // 触发事件 publisher.RaiseEvent(); Console.WriteLine("手动触发发布者事件"); } } /// <summary> /// 事件发布者 /// </summary> public class Publisher { public event EventHandler<EventArgs> SampleEvent; public virtual void RaiseEvent() { SampleEvent?.Invoke(this,new EventArgs()); } } /// <summary> /// 事件订阅者 /// </summary> public class Subscriber : IWeakEventListener { string SubName = ""; public Subscriber(string name) { SubName = name; } public void Receiver(object sender, EventArgs e) { Console.WriteLine("Subscriber: " + SubName + "接受到了事件通知"); } //通过该方法来处理弱事件管理器推送过来的订阅信息 public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { Receiver(sender,e); return true; } } /// <summary> /// 事件弱管理器 /// </summary> public class WeakCarInfoEventManager : WeakEventManager { /// <summary> /// 将订阅者添加到事件源 /// </summary> /// <param name="source">发布者</param> /// <param name="listener">订阅者</param> public static void AddListener(object source, IWeakEventListener listener) { CurrentManager.ProtectedAddListener(source, listener); } public static void RemoveListener(object source, IWeakEventListener listener) { CurrentManager.ProtectedRemoveListener(source, listener); } /// <summary> /// 管理器 /// </summary> public static WeakCarInfoEventManager CurrentManager { get { //从现有管理器中获取默认管理器对象 var manager = GetCurrentManager(typeof(WeakCarInfoEventManager)) as WeakCarInfoEventManager; if (manager == null) { manager = new WeakCarInfoEventManager(); //将新建的管理器对象设置为当前类型默认管理器 SetCurrentManager(typeof(WeakCarInfoEventManager), manager); } return manager; } } protected override void StartListening(object source) { //将发布者事件订阅到当前 (source as Publisher).SampleEvent += CarDealer_NewCarInfo; } void CarDealer_NewCarInfo(object sender, EventArgs e) { DeliverEvent(sender, e); } protected override void StopListening(object source) { (source as Publisher).SampleEvent -= CarDealer_NewCarInfo; } } }
WPF中的弱事件
private class RequerySuggestedEventManager : WeakEventManager { #region Constructors // // Constructors // private RequerySuggestedEventManager() { } #endregion Constructors #region Public Methods // // Public Methods // /// <summary> /// Add a handler for the given source's event. /// </summary> public static void AddHandler(CommandManager source, EventHandler handler) { if (handler == null) return; // 4.0-compat; should be: throw new ArgumentNullException("handler"); CurrentManager.ProtectedAddHandler(source, handler); } /// <summary> /// Remove a handler for the given source's event. /// </summary> public static void RemoveHandler(CommandManager source, EventHandler handler) { if (handler == null) return; // 4.0-compat; should be: throw new ArgumentNullException("handler"); CurrentManager.ProtectedRemoveHandler(source, handler); } #endregion Public Methods #region Protected Methods // // Protected Methods // /// <summary> /// Return a new list to hold listeners to the event. /// </summary> protected override ListenerList NewListenerList() { return new ListenerList(); } /// <summary> /// Listen to the given source for the event. /// </summary> protected override void StartListening(object source) { CommandManager typedSource = CommandManager.Current; typedSource.PrivateRequerySuggested += new EventHandler(OnRequerySuggested); } /// <summary> /// Stop listening to the given source for the event. /// </summary> protected override void StopListening(object source) { CommandManager typedSource = CommandManager.Current; typedSource.PrivateRequerySuggested -= new EventHandler(OnRequerySuggested); } #endregion Protected Methods #region Private Properties // // Private Properties // // get the event manager for the current thread private static RequerySuggestedEventManager CurrentManager { get { Type managerType = typeof(RequerySuggestedEventManager); RequerySuggestedEventManager manager = (RequerySuggestedEventManager)GetCurrentManager(managerType); // at first use, create and register a new manager if (manager == null) { manager = new RequerySuggestedEventManager(); SetCurrentManager(managerType, manager); } return manager; } } #endregion Private Properties #region Private Methods // // Private Methods // // event handler for CurrentChanged event private void OnRequerySuggested(object sender, EventArgs args) { DeliverEvent(sender, args); } #endregion Private Methods }