由于.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. 转载:python操作excel表格(xlrd/xlwt)

    python操作excel表格(xlrd/xlwt)   最近遇到一个情景,就是定期生成并发送服务器使用情况报表,按照不同维度统计,涉及python对excel的操作,上网搜罗了一番,大多大同小异,而 ...

  2. rabbit mq 基础流程(转)

    从AMQP协议可以看出,MessageQueue.Exchange和Binding构成了AMQP协议的核心,下面我们就围绕这三个主要组件    从应用使用的角度全面的介绍如何利用Rabbit MQ构建 ...

  3. 用this 对方法的扩展

    *都是静态方法 this指向的是调用的object*

  4. HBase Ambari

  5. [转]Visual Studio 2010生成解决方案时,提示磁盘空间不足!

    最近几天,使用VS调试时总是出现提示:磁盘空间不足.我觉得可能有两种可能:    1.系统盘已被木马侵袭.历时4小时的全盘扫描没有病毒提示,只好删除了一些不必要的软件:    2.使用VS2010调试 ...

  6. github的账号密码 redis windows版连接方式

    账号:Pinshuducha 邮箱:java_zhoulu@163.com 密码:zhoulu1994 服务器端: 启动:redis-server.exe redis.windows.conf 客户端 ...

  7. bash: express: command not found及vue连接数据库调接口

    今天在使用express -e . 的命令时,cmd给我报了一段不识别的错误: bash: express: command not found ,在网上查了一下,有人指出是express4的版本将命 ...

  8. 可持久化线段树的学习(区间第k大和查询历史版本的数据)(杭电多校赛第二场1011)

    以前我们学习了线段树可以知道,线段树的每一个节点都储存的是一段区间,所以线段树可以做简单的区间查询,更改等简单的操作. 而后面再做有些题目,就可能会碰到一种回退的操作.这里的回退是指回到未做各种操作之 ...

  9. Python学习 备注

    python 3.x中urllib库和urilib2库合并成了urllib库..其中urllib2.urlopen()变成了urllib.request.urlopen() urllib2.Reque ...

  10. 5+App 基于HTML、JS、CSS编写的运行于手机端的App(DCloud开发)

    HTML5 Plus移动App(5+App) 工具: 开发工具HBuilder下载下载地址 开发工具HBuilderX下载下载地址(替代HBuilder) 框架: uni-app :是一个使用 Vue ...