上一篇最后提到了怎么解决由“事件编程”引起的内存泄露问题,最后几句说到了由“弱引用”的概念引申出来“弱委托”。具体没说怎么去实现,这篇文章介绍一下具体实现过程。(请先看前一篇文章)
先来看一下MSDN上对Delegate(委托)的解释:
表示委托,委托是一种数据结构,它引用静态方法或引用类实例及该类的实例方法。
我们先不去管网上对“委托”的其他形象比如,比如“类似函数指针”、“对同一类方法的签名”等等。先来看看MSDN上的解释是个什么意思。首先它是一种数据结构,基本可以看做包含两个东西,一个是方法,一个是方法的所有者,即对象(静态方法的所有者为类)。那么我们可以简单的用图来画一下:
图1
如果对委托比较熟悉的人可能已经知道,所有我们定义的委托变量,都有两个属性Target和Method,这也正验证了上图的正确性。
那么Event(事件)是个什么东西?看一下MSDN对“事件”的一段解释:
在发生其他类或对象关注的事情时,类或对象可通过事件通知它们。发送(或引发)事件的类称为“发行者”,接收(或处理)事件的类称为“订户”。
这个解释基本上没什么用,因为它跟之前谈到的“委托”没任何联系,那我们再回忆一下我们之前在代码中是怎么去定义一个事件的?书上说网上也说,定义一个委托变量,在声明前面加一个event关键字,event关键字小写,那么这个委托变量就可以叫事件,编写代码时,VS IDE智能提示会在事件前面有一个“闪电”的图标。那么,根据这个描述,事件明显就是一种特殊的委托实例,没错,事件就是一种特殊的委托实例,它具备所有委托所具备的特点(有一些限制,跟主题没关系,略过)。
当事件的观察者(Observer)向事件的发布者(Subject)注册一个事件时,事件发布者就会产生一个对事件观察者的强引用(strong reference)(不然,当事件发生时,怎么才能通知观察者),也就是说,“注册事件”这个动作将事件的发布者和事件的观察者绑定到了一起,具体体现在:发布者包含了一个对观察者的强引用。
结合上一篇文章中的“图2”,我们可以看出,一个事件对应一个委托链,激发事件的过程可以看做:遍历这个委托链,在每一个节点中的Target上调用Method。这就完成了“事件发布者->激发事件->通知观察者->观察者响应事件->调用事件处理程序”这一过程。那么,对于一个事件,每被注册一次,它对应的委托链上就应该增加一个节点,该节点中存储着观察者的信息,由于同一个事件,同一个对象可能注册好几次,因此某一事件对应委托链中的节点的个数并不等于该事件的观察者数目,一般前者大于后者。再来看一张图:
图2
由此看出,委托链维持着事件发布者与事件观察者之间的联系,在某些情况下,当我们认为我们程序中的观察者已死(代码中再没有对观察者的引用),GC会回收观察者的内存时,实际情况往往不是我们认为的这样,由于委托链中仍然有对观察者的强引用,GC是不会对它进行内存回收的,这样一来,如果观察者数目大,观察者属于大对象,系统运行时间过长,就会导致内存不足。
注:GC回收对象内存的前提是程序中不再有该对象的强引用(strong reference)。
解决以上问题关键在于,怎么在观察者即将死亡的时候通知发布者将自己的引用删除,但是这个几乎做不到,因为你根本不知道观察者什么时候死亡,再者,当系统复杂庞大之后,你根本不知道谁是谁的观察者,谁又暗自拥有谁的强引用。所以,最好的情况是,观察者在必要的时候,能够正常的被GC回收内存,对于事件发布者来说,观察者的存在与否没有多大关系,如果观察者还存在,那么当事件发生时就通知观察者,如果观察者不存在了,那么当事件发生时就不通知观察者。这就要求事件发布者对于观察者之间的关联不能像之前那种“强引用”的关系,而应该是“弱引用”的关系。
因此,我们可以仿照原有的Delegate数据结构,模拟一种新的数据结构,将原来Target这个“强引用”变为“弱引用”,我们取名叫做“弱委托”(WeakDelegate),代码类似如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif?x-oss-process=image/resize,w_1400/format,webp)
包含两个属性WeakTarget和Method,分别对应原来委托的Target和Method属性,一个Invoke公共方法,对应原来委托的Invoke方法,用于执行委托。
由此一来,图2中委托链中的各个节点就可以由原来的Delegate(其派生类,我们自己定义的委托)变为我们现在的WeakDelegate,那么在写事件发布者类(Subject)的代码时,我们需要自己来维持这些委托链了,如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif?x-oss-process=image/resize,w_1400/format,webp)
在事件发布者中,我定义了三个使用我们自己WeakDelegate的事件,由_delegateList管理,分别是XX1、XX2、XXn事件,他们对应的委托链中的每一个节点属于WeakDelegate类型,不会保存观察者的强引用。另外为了对比,我定义了一个传统的使用Delegate的事件CommonEvent,它对应的委托链中的每一个节点属于Delegate(派生类)类型,保存观察者的强引用。
定义了一个观察者(Observer)类,代码为:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif?x-oss-process=image/resize,w_1400/format,webp)
该类除了注册Subject类的事件外,没有其他内容,当事件发生后,输出与该事件相关的信息。
测试代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif?x-oss-process=image/resize,w_1400/format,webp)
被注释部分事件发布者与观察者之间属于弱关联,观察者能够正常死亡;未被注释部分事件发布者与观察者属于强关联,观察者不能正常死亡。
运行结果如下:
图3
图4
本文代码均已调试通过,环境(XP+.net 3.5)。
本文所有事件,均没有考虑“多线程”情况。
希望有帮助,O(∩_∩)O~
作者:周见智
出处:http://www.cnblogs.com/xiaozhi_5638/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。