.NET陷阱 四 事件监听带来的问题与弱监听器

大家可能都遇到过没有取消事件监听而带来的一些问题,像内存泄露、访问无效数据等。当我们写下如下代码时:

source.StateChanged += observer.SourceStateChangedHandler

实际上source会保持有对observer的一个引用,所以如果source的生命期长于observer的话,则当其它地方不引用observer时,如果不显示解除监听,则observer不会被垃圾回收。这可能会带来两个问题:其一,如果observer占用了大量内存的话,则这部分内存不会被释放;其二,程序的其它地方可能已经处于不一致的状态,这样当source.StateChanged事件再次发生时,observer.SourceStateChanged方法仍然会被调用,而此方法内部的逻辑可能会造成异常。

当然最直接的办法是在不使用observer时显示解除监听,像下面那样:

source.StateChanged -= observer.SourceStateChangedHandler

但程序员经常会忘记这一点。所以便有了“弱事件监听器”的概念,我们期望在监听时多做些工作,然后能达到自动取消监听的目的。废话不说,先上代码。

/// <summary>
 /// 当弱监听器发现被包装的监听者已经被垃圾收集时所调用的委托。
 /// </summary>
 /// <typeparam name="E">事件参数类型。</typeparam>
 /// <param name="handler">MakeWeak方法返回的事件处理函数,提供此委托的地方
 /// 要负责把此对象从被监听对象的事件处理方法列表中移除。</param>
 /// <param name="param">在调用MakeWeak方法时传入的额外参数。</param>
 public delegate void UnregisterCallback<E>(EventHandler<E> handler, object param) where E : EventArgs;   

 /// <summary>
 /// 当进行事件处理时,如果被监听对象的生命期比监听器的生命周期长,我们就必
 /// 须在监听器的生命期内取消对被监听对象的事件监听,否则被监听对象会持有监
 /// 听器的一个强引用,而阻止它被垃圾收集。但有时我们经常忘记取消事件监听,
 /// 或者不容易确定何时解除监听。此时可以使用弱监听器,把如下代码:
 /// <code>
 /// observed.SomeEvent += observer.SomeEventHandler;
 /// </code>
 /// 改为:
 /// <code>
 /// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak(
 ///     observer.SomeEventHandler,
 ///     (handler, param) => observed.SomeEvent -= handler,
 ///     null);
 /// </code>
 /// 上面的代码使用了lambda表达式以捕获变量,也可以像下面那样使用param参数:
 /// <code>
 /// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak(
 ///     observer.SomeEventHandler,
 ///     OnUnregisterWeakEvent,
 ///     observed);
 ///
 /// void OnUnregisterWeakEvent(EventHandler&lt;E&gt; handler, object param)
 /// {
 ///     ((ObservedType)param).SomeEvent -= handler;
 /// }
 /// </code>
 /// 或者使用如下形式:
 /// <code>
 /// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak2(
 ///     observer.SomeEventHandler, observed, "SomeEvent");
 /// </code>
 /// 其中MakeWeak的第二个参数将弱监听器从事件源中移除。即使将第二个参数指定
 /// 为null,也不会阻止observer对象被垃圾收集,但事件源中将始终保持一个轻量
 /// 对象的引用。
 /// </summary>
 public static class WeakEventHandlerFactory
 {
     /// <summary>
     /// 我们在MakeWeak方法中使用反射创建WeakEventHandler的实例,所以在(1)
     /// 处理无法指定泛型参数T,以完成转换,此接口用于简化这一步骤。
     /// </summary>
     /// <typeparam name="E">事件参数类型。</typeparam>
     private interface IWeakEventHandler<E> where E : EventArgs
     {
         /// <summary>
         /// 事件处理器。
         /// </summary>
         EventHandler<E> Handler
         {
             get;
         }
     }   

     /// <summary>
     /// 对指定的事件处理函数创建一个弱监听器。
     /// </summary>
     /// <typeparam name="E">事件参数类型。</typeparam>
     /// <param name="handler">被包装的事件处理器。</param>
     /// <param name="unregister">用于将弱监听器从事件源中移除的委托。可以指
     /// 定为null,这时事件源中将始终保持一个轻量对象的引用,但不会阻止被包
     /// 装的对象被垃圾收集。</param>
     /// <param name="param">在调用unregister时使用的额外参数,可以是null。</param>
     /// <returns>生成的弱监听器。</returns>
     public static EventHandler<E> MakeWeak<E>(EventHandler<E> handler,
         UnregisterCallback<E> unregister, object param) where E : EventArgs
     {
         if (handler == null)
         {
             throw new ArgumentNullException("handler");
         }   

         if (handler.Method.IsStatic || handler.Target == null)
         {
             throw new ArgumentException("Only instance methods are supported.", "handler");
         }   

         var type = typeof(WeakEventHandler<,>).MakeGenericType(
             handler.Method.DeclaringType, typeof(E));   

         var wehConstructor = type.GetConstructor( new[]
         {
             typeof(EventHandler<E>),
             typeof(UnregisterCallback<E>),
             typeof(object)
         });   

         // (1)
         var weak = (IWeakEventHandler<E>)wehConstructor.Invoke(
             new [] { handler, unregister, param });
         return weak.Handler;
     }   

     /// <summary>
     /// 此方法相当于MakeWeak(handler, unregister, null)。
     /// </summary>
     public static EventHandler<E> MakeWeak<E>(EventHandler<E> handler,
         UnregisterCallback<E> unregister) where E : EventArgs
     {
         return MakeWeak(handler, unregister, (object)null);
     }   

     /// <summary>
     /// 使用CreateUnregisterCallback创建取消弱监听器委托的形式注册监听器。
     /// </summary>
     /// <typeparam name="E">事件参数类型。</typeparam>
     /// <param name="handler">被包装的事件处理器。</param>
     /// <param name="observed">被监听的对象。</param>
     /// <param name="eventName">监听的事件名称。</param>
     /// <returns>生成的弱监听器。</returns>
     public static EventHandler<E> MakeWeak2<E>(EventHandler<E> handler,
         object observed, string eventName) where E : EventArgs
     {
         return MakeWeak(handler, CreateUnregisterCallback<E>(observed, eventName));
     }   

     /// <summary>
     /// 创建一个用于取消弱监听器注册的委托。
     /// </summary>
     /// <typeparam name="E">事件参数类型。</typeparam>
     /// <param name="observed">被监听的对象。</param>
     /// <param name="eventName">监听的事件名称。</param>
     /// <returns>创建结果,不会是null。</returns>
     public static UnregisterCallback<E> CreateUnregisterCallback<E>(
         object observed, string eventName) where E : EventArgs
     {
         return new UnregisterHelper<E>(observed, eventName).Callback;
     }   

     /// <summary>
     /// 用于将弱监听器从事件源中移除的辅助类,在C++/CLI等不支持lambda表示式
     /// 和自动委托的语言中,使用弱监听器的语法可能很复杂,此类用于简化这种
     /// 情况。
     /// </summary>
     /// <typeparam name="E">委托事件参数类型。</typeparam>
     private class UnregisterHelper<E> where E : EventArgs
     {
         /// <summary>
         /// 被监听的对象。
         /// </summary>
         private readonly object observed;   

         /// <summary>
         /// 事件名称。
         /// </summary>
         private readonly string eventName;   

         /// <summary>
         /// 构造函数。
         /// </summary>
         internal UnregisterHelper(object observed, string eventName)
         {
             this.observed = observed;
             this.eventName = eventName;
         }   

         /// <summary>
         /// 用于取消监听的委托。
         /// </summary>
         internal UnregisterCallback<E> Callback
         {
             get
             {
                 return (handler, param) =>
                 {
                     var info = observed.GetType().GetEvent(eventName);
                     info.RemoveEventHandler(observed, handler);
                 };
             }
         }
     }   

     /// <summary>
     /// 弱事件监听器。
     /// </summary>
     /// <typeparam name="T">监听者的类型。</typeparam>
     /// <typeparam name="E">事件参数类型。</typeparam>
     private class WeakEventHandler<T, E> : IWeakEventHandler<E>
         where T : class where E : EventArgs
     {
        /// <summary>
         /// 对监听器的弱引用。
         /// </summary>
         private readonly WeakReference weakReference;   

         /// <summary>
         /// 用于调用被包装的监听器的委托。
         /// </summary>
         private readonly OpenEventHandler openHandler;   

         /// <summary>
         /// 调用unregister时的额外参数。
         /// </summary>
         private readonly object param;   

         /// <summary>
         /// 监听器移除委托。
         /// </summary>
         private UnregisterCallback<E> unregister;   

         /// <summary>
         /// 构造函数。
         /// </summary>
         /// <param name="handler">被包装的事件处理器。</param>
         /// <param name="unregister">用于移除弱监听器的代码。</param>
         /// <param name="param">调用unregister时的额外参数。</param>
         public WeakEventHandler(EventHandler<E> handler,
             UnregisterCallback<E> unregister, object param)
         {
             weakReference = new WeakReference(handler.Target);
             openHandler = (OpenEventHandler)Delegate.CreateDelegate(
                 typeof(OpenEventHandler), null, handler.Method);
             Handler = Invoke;
             this.unregister = unregister;
             this.param = param;
         }   

         /// <summary>
         /// 包装监听器事件处理函数的开放委托类型。
         /// </summary>
         private delegate void OpenEventHandler(T @this, object sender, E e);   

         /// <summary>
         /// <see>IWeakEventHandler.Handler</see>
         /// </summary>
         public EventHandler<E> Handler
         {
             get;
             private set;
         }   

         /// <summary>
         /// 弱监听器事件处理函数。
         /// </summary>
         /// <param name="sender">引发事件的对象。</param>
         /// <param name="e">事件参数。</param>
         private void Invoke(object sender, E e)
         {
             T target = (T)weakReference.Target;
             if (target != null)
             {
                 openHandler.Invoke(target, sender, e);
             }
             else if (unregister != null)
             {
                 unregister(Handler, param);
                 unregister = null;
             }
         }
     }
 }

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索参数
, 事件
, handler
, 旁路监听
, 事件监听
, 监听
, 监听器
, 计算器监听事件
, 弱一致性
, 监听fiddlerhttpclient
, 监听器事件
, eventhandler
, 弱类型
弱引用实例
java事件监听器原理、activiti 事件监听器、android事件监听器、javascript事件监听器、js事件监听器,以便于您获取更多的相关知识。

时间: 2024-08-04 13:25:51

.NET陷阱 四 事件监听带来的问题与弱监听器的相关文章

Java添加事件监听的四种方法代码实例_java

Java添加事件的几种方式(转载了codebrother的文章,做了稍微的改动): /** * Java事件监听处理--自身类实现ActionListener接口,作为事件监听器 * * @author codebrother */ class EventListener1 extends JFrame implements ActionListener { private JButton btBlue, btDialog; public EventListener1() { setTitle(

java Gui编程 事件监听机制

1.     GUI编程引言     以前的学习当中,我们都使用的是命令交互方式:        例如:在DOS命令行中通过javac java命令启动程序.     软件的交互的方式:      1. 命令交互方式           图书管理系统     2. 图形交互方式     ava提供了专业的API用于开发图形用户界面     GUI--> Graphic  User   Interface      将要了解GUI API的框架结构,以及GUI组件以及组件之间的关系,容器和布局管理

NodeJs——(1)封装,调用,执行,访问路径,http,函数编程,等待函数,事件监听

(1)如何封装一个模块: 首先,我们建立一个js文件,例如命名为test.js: 然后在里面写一个函数,函数名任意: 然后通过exports.变量名,将函数赋值给这个变量: 如代码: function test(){    //请注意这个函数名     console.log("1"); }exports.testBegin= test;   //等号后面的test,指的是上面的函数名.等号前面的testBegin,是调用时的函数名(注意区别)   这个test.js的文件就写完了,这

app-Android给文本绑定事件监听的问题?

问题描述 Android给文本绑定事件监听的问题? 最近在学习做一个跟小说有关的app. 我将每章小说的标题放在了一个数组,然后通过ArrayAdapter将数组放入了ListView,现在文章列表形成,我却不知道该如何实现通过点击每一章标题进入相应的内容? 有人可以给一些详细的解答吗? 解决方案 http://m.blog.csdn.net/blog/ygc87/8216946,可以借鉴他的方法 解决方案二: Android按钮点击事件的绑定 解决方案三: ListView 列表有点击事件可以

关于java事件监听的问题

问题描述 关于java事件监听的问题 有个登陆按钮 点击后登陆 我想得是按下回车键登陆 但是为什么不能在按键事件监听中调用那个点击事件那 解决方案 java事件监听监听事件问题JAVA监听键盘事件 解决方案二: 给那个按钮设个快捷键,让它在按下回车的时候触发监听事件 解决方案三: 提交按钮和enter按键的功能一样,只是一个是click事件,enter是keypress事件. 解决方案四: 可以在相应的设置中设置为快捷键,然后调用.或者设置shift顺序,调用doClick方法,差不多就是这样了

jQuery-mobile事件监听与用法详解_jquery

本文实例讲述了jQuery-mobile事件监听与用法.分享给大家供大家参考,具体如下: 触摸事件 - 当用户触摸屏幕时触发(敲击和滑动) 滚动事件 - 当上下滚动时触发 方向事件 - 当设备垂直或水平旋转时触发 页面事件 - 当页面被显示.隐藏.创建.加载以及/或卸载时触发 一.初始化事件 1. ready 事件 页面加载完成 $(document).ready(function(){ //your code here... }); 2. 页面加载完成事件二 pageinit $(docume

在左侧侧拉栏目中对控件添加事件监听,但点击后没有反应,为什么呢?

问题描述 在左侧侧拉栏目中对控件添加事件监听,但点击后没有反应,为什么呢? public class LeftMenuFragment extends BaseFragment { private ImageView headimage; private String id; @Override public View initView(LayoutInflater inflater) { View view = inflater.inflate(R.layout.left_menu, null

JAVA之旅(三十一)——JAVA的图形化界面,GUI布局,Frame,GUI事件监听机制,Action事件,鼠标事件

JAVA之旅(三十一)--JAVA的图形化界面,GUI布局,Frame,GUI事件监听机制,Action事件,鼠标事件 有段时间没有更新JAVA了,我们今天来说一下JAVA中的图形化界面,也就是GUI 一.GUI的概述 GUI全称叫做Graphical User Intergace(图形用户接口),用图形的方式,来显示计算机操作的界面,这样更加方便直观,与用户交互 说道交互,其实系统跟用户有两种交互,一种是GUI,一种叫做CLI,也就是命令行,全称叫做Command User Intergace

Android 触摸事件监听(Activity层,ViewGroup层,View层)详细介绍

Android不同层次的触摸事件监听 APP开发中,经常会遇到有关手势处理的操作,比如向右滑动返回上一个页面.关于触摸事件的处理,我们可以大概处理在不同的层次上. Activity层:可以看做触摸事件获取的最顶层 ViewGroup层:ViewGroup层可以自主控制是否让子View获取触摸事件 View层:可以决定自己是否真正的消费触摸事件,如果不消费抛给上层ViewGroup Activity级别的手势监听:(右滑动返回上层界面) Activity层手势监听的使用场景:一般用于当前页面中没有