由于.NET框架对消息循环机制进行了很好的封装,开发人员不再需要深入的了解Windows事件/消息实现的具体机制,也无需创建复杂的事件结构体和所谓的消息句柄。我们所要做的无非就是——1、使用重载运算符“+=”注册一个事件;2、编写对于该事件的处理方法。(关于C#2.0中事件处理的相关介绍,请参阅我的文章:C#2.0的泛型代理和事件 :以一当百的快感 )

 
如此简单,以至于习惯了Win32编程的伙计们对此嗤之以鼻,讽之:“我们是开手排挡车的专业选手,你们.NET一族只能玩玩自动档。什么?你们还看《头文字D》?能看懂吗?”…
 
不理他们!咱们说咱们的。转头前甩给他们一句话:“迂腐!”。如果不解恨,那么在引用一段名师的话:“我们从不乐意改变自己的工作习惯,就像把妻子的照片放在台灯下面一样。然而,当一种新的方法确实能极大的提高我们的工作效率和行动力时,我们干嘛要固执呢?”——够效果了吧?
 
言归正传。
 
前几天,我在编写主窗体与子模块的事件通信时,遇到了一个极其堪称郁闷的问题。说这个问题前,我和大家交代一下我的设计思路。
主窗体(frmMain :IParentForm)
事件成员:
public event ParentEventHandler OnUserListCreated;
 
事件处理方法:
void ToDoOnRequestUserList(object sender, EventArgs e){
         //创建DataTable dt
         …
         This.OnUserListCreated(this, new ParentEventArgs(dt));
}
 
某一行注册子窗体事件:
frmChild.OnRequestUserList += new EventHandler (ToDoOnRequestUserList);
子窗体(frmChild)
事件成员:
public event EventHandler OnUserListCreated;
 
事件处理方法:
void ToDoOnRequestUserReturned(object sender, ParentEventArgs e){}
 
在OnLoad事件处理方法中注册主窗体的事件:
(this.MdiParent as IParentForm). OnUserListCreated += new ParentEventHandler (ToDoOnRequestUserReturned);
 
 
主窗体对象为frmMain,它实现了IParentForm接口,该接口定义了事件成员OnUserListCreated(它的EventArgs为自定义的ParentEventArgs)。frmMain对象在某处创建了一个子窗体frmChild,并注册了frmChild的事件OnRequestUserList。
 
子窗体对象frmChild在载入时(OnLoad方法中)获得frmMain的引用,并注册了frmMain的事件OnUserListCreated。
 
根据业务逻辑,子窗体运行的某一时刻,用户行为触发了事件OnRequestUserList,此时frmMain将捕获此事件并调用自身的处理方法生成一个被请求的用户列表(DataTable)。然后,frmMain发出了事件OnUserListCreated以提示列表生成完毕,并将刚刚创建的DataTable作为ParentEventArgs参数插入事件中。随后,子窗体将接收到这个事件,并在自己的事件处理方法中对传来的DataTable进行自己的业务逻辑动作。
 
在接下来程序的运行中,可爱的代码心情愉悦地顺利执行…但是,好景不长!
 
当我将打开的子窗体关闭后再重新打开,主窗体在触发OnUserListCreated事件后发生调用目标异常,子窗体在该事件的处理方法中也抛出NullReferenceException异常(未将对象引用设置到对象实例)。当我在子窗体的事件处理方法ToDoOnRequestUserReturned中设置断点调试后发现:所有的控件、变量都为null!!
 
那叫郁闷,那叫惆怅…公车上、步行中、如厕时、入睡前,我估摸着这种灵异现象可能与最近隔壁邻居家小猫的突然消失有着千丝万缕的联系…当然,作为基督教徒的我,也后怕这是主,耶稣基督对于我大前天横闯马路的惩罚…
 
无助中,我极其盲目的在frmChild的ToDoOnRequestUserReturned方法中加入了一行语句:“MessageBox.ShowDialog(“So boring a thing!”)”以发泄心情。保存、编译、运行——大坏蛋的面目露了出来!当我第一次打开子窗体的时候,如我所料,程序正常运行并弹出了MessageBox。关键是,当我关闭子窗口并第二次打开它执行时,MessageBox弹出了两次!恩…
 
带着疑问,我重复了以上关闭、打开步骤,MessageBox弹出了三次!——事情已经有了眉目。在我辗转反复的思考后(也许有人会骂我菜鸟…),终于明白了所有事情的缘由:
 
因为程序一直处在运行中,所以主窗体对象一直驻留内存中并保持着自身的状态(它没有的disposed),所以,每次子窗体创建时,主窗体都会注册它的OnRequestUserList事件,同样的,该子窗体在加载时,自身也会把主窗体的OnUserListCreated事件注册一次。
 
问题就出在这里,虽然子窗体关闭了,并disposed了。但是,它关闭时并没有把在主窗体注册的事件同时注销。随着子窗体一次次的打开,主窗体的OnUserListCreated就被+=了N多了注册用户,其中的N-1个用户其实早已经不存在了,而主窗体全然不知。所以当发出OnUserListCreated事件后,主窗体还会以无反顾地去调用这N多个方法代理,这必然会导致异常抛出——唯一打开的那个子窗体接受到一次次传来的事件,并企图调用ToDoOnUserListReturned方法,如果此方法中包含着对本对象成员变量的操作,自然会引出“未将引用设置到对象实例”的异常。
 
也许有朋友会问,为什么主窗体调用那些早已disposed的frmChild的方法的代理时,会被当前存在的那个frmChild执行呢?我认为这可能是由于类实例的同一个方法在内存栈中共享空间造成的;而成员变量在堆中存放,各自维护其状态,当其所属的对象被释放回收时,其值也就置为null了。(个人观点,望兄弟姐们给予指正)
 
综上,我做一下总结:
 
子窗体在关闭时,应当把自己注册的主窗体对象(或者是长久驻留内存对象)事件一一注销。例如本例中,应在子窗体的OnClosed事件处理方法中加入以下代码:
(this.MdiParent as IParentForm). OnUserListCreated  -=  new ParentEventHandler    (ToDoOnRequestUserReturned)
 
如果仅仅是为了在主窗体执行完某项操作后触发子窗体某一方法的执行,我们通常不采用事件机制,而采用以下两种方法:

A. 将此方法访问属性改为public,然后由主窗体适时调用。
B. 定义一个接口,子窗体对象实现这个接口,并把该目标方法提升为该接口的成员。由主窗体适时调用这个接口成员方法。

C#中的事件注册和注销的更多相关文章

  1. HTML 事件(二) 事件的注册与注销

    本篇主要介绍HTML元素事件的注册.注销的方式. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流.事件委托 4. ...

  2. libevent (三) 事件注册与循环监听

    事件注册与循环监听 在libevent中为了监听某种事件的发生,设置事件触发后的回调函数,也就是说对该事件注册到当前的IO模型中. 事件注册 事件初始化 使用`event_new`函数来对事件进行初始 ...

  3. Android学习笔记(十一)BroadcastReceiver动态注册、注销示例

    在上一篇博文中简单介绍了一下BroadcastReceiver的相关知识点,本篇举一个在代码中动态的注册.注销BroadcastReceiver的栗子. 1.首先创建一个MyReceiver并继承Br ...

  4. java基础66 JavaScript中的事件、localtion对象、screen对象(网页知识)

    1.JavaScript中的事件注册 1.1.事件的注册方式 方式1:直接在html元素上注册 <body onLoad="ready()"></body > ...

  5. Knative Eventing 中如何实现 Registry 事件注册机制

    摘要: 在最新的 Knative Eventing 0.6 版本中新增了 Registry 特性, 为什么要增加这个特性, 该特性是如何实现的.针对这些问题,希望通过本篇文章给出答案. 背景 作为事件 ...

  6. 《C#高级编程》学习笔记------C#中的事件和委托

    本文转载自张子阳 目录 委托的作用 将方法绑定到委托 事件的来由 Observer设计模式 .Net Framework中的委托与事件   引言 委托 和 事件在 .Net Framework中的应用 ...

  7. BroadcastReceiver的使用,动态注册和注销,优先级和中断控制

    BroadcastReceiver: BroadcastReceiver(广播接收器)是Android中的四大组件之一,用来通知某些事件的相关信息,如下载完成,设置改变等. 默认的BroadcastR ...

  8. C++中的事件分发

    本文意在展现一个C++实现的通用事件分发系统,能够灵活的处理各种事件.对于事件处理函数的注册,希望既能注册到普通函数,注册到事件处理类,也能注册到任意类的成员函数.这样在游戏客户端的逻辑处理中,可以非 ...

  9. 怎么理解js中的事件委托

    怎么理解js中的事件委托 时间 2015-01-15 00:59:59  SegmentFault 原文  http://segmentfault.com/blog/sunchengli/119000 ...

随机推荐

  1. nprogress 转

    转载:http://www.xuanfengge.com/front-end-nprogress-and-lightweight-web-progress-bar-nanobar.html 前言 进度 ...

  2. java基础之Calender类

    Calendar: Calendar类概述 Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR.MONTH.DAY_OF_MONTH.HOUR 等 日历字段之间的转换提供了一些方法,并 ...

  3. [自学]数据库ER图基础概念整理(转)

    ER图分为实体.属性.关系三个核心部分.实体是长方形体现,而属性则是椭圆形,关系为菱形. ER图的实体(entity)即数据模型中的数据对象,例如人.学生.音乐都可以作为一个数据对象,用长方体来表示, ...

  4. badboy的录制和jmeter的使用

    v  Jmeter是什么 Apache Jmeter是Apache组织开发的基于Java的压力测试工具. Jmeter可以用于对服务器.网络或对象模拟巨大的负载,来自不同压力类别下测试它们的强度和分析 ...

  5. request与session的区别

    request对象和session对象的最大区别是生命周期与范围. request request范围较小一些,只是一个请求. request对象的生命周期是针对一个客户端(说确切点就是一个浏览器应用 ...

  6. 高德地图(AMap)JavaScript API的使用

    申请JSAPI的开发者key 申请地址:http://lbs.amap.com/dev/key 引入高德地图JavaScript API文件: <script type="text/j ...

  7. Python ——tempfile

    主要有以下几个函数: tempfile.TemporaryFile 如何你的应用程序需要一个临时文件来存储数据,但不需要同其他程序共享,那么用TemporaryFile函数创建临时文件是最好的选择.其 ...

  8. NVIDIA驱动安装、CUDA安装、cudnn安装

    1.禁用 nouveau 驱动 sudo vim /etc/modprobe.d/nvidia-installer-disable-nouveau.conf 或者 sudo vim /etc/modp ...

  9. 转:linux驱动开发的经典书籍

    源地址:http://www.cnblogs.com/xmphoenix/archive/2012/03/27/2420044.html Linux驱动学习的最大困惑在于书籍的缺乏,市面上最常见的书为 ...

  10. Hadoop Serialization -- hadoop序列化详解 (2)【Text,BytesWritable,NullWritable】

    回顾: 回顾序列化,其实原书的结构很清晰,我截图给出书中的章节结构: 序列化最主要的,最底层的是实现writable接口,wiritable规定读和写的游戏规则 (void write(DataOut ...