谈谈C#中的事件注册和注销

由于 .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.
定义一个接口,子窗体对象实现这个接口,并把该目标方法提升为该接口的成员。由主窗体适时调用这个接口成员方法。

转载于:https://www.cnblogs.com/springxie/archive/2008/12/16/1356133.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值