http://www.cnblogs.com/rickiedu/archive/2007/03/15/676021.html

在wpf中应用弱事件模式

 
     感谢VS 的Intellisense功能,当需要为一个Button的Click事件添加一个处理方法的时候,你的手指也许很自然地就敲出了“button.Click +=”,随后习惯性地按两下Tab,然后在VS自动添加的button_Click中开始一段世上最优美的代码:)绞尽脑汁或者一气呵成地写完代码后,按F5,点击button,一切都按预期运行,太完美了,提交代码,收工,到来福士二楼去看MM。生活很美好,不是么?

对于已经在CSDN 上混了N个裤衩的你来说,以上情景绝非痴人说梦,恰恰相反,这些简直就是理所当然的。说句实在话,偶也一直认为生活就是那么简单美好,直到昨天偶在摆弄WPF的时候居然碰到了一个费解的问题,还是代码来说话吧。

XAML:

<Window x:Class="WeakEventDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WeakEventDemo" Height="300" Width="300"           
    >    
    <StackPanel>        
        <Button Name="_btnA">A</Button>
        <Button Name="_btnB">B</Button>
        <Button Name="_btnC">C</Button>
        <Button Name="_btnD">D</Button>        
        <Button Name="_btnKillSpy" Click="KillSpy">干掉打小报告的</Button>
    </StackPanel>    
</Window>

CS:

public partial class Window1 : Window {
        public Window1() {
            InitializeComponent();

            _spy = new Spy();

            _spy.MonitorButton(_btnA);
            _spy.MonitorButton(_btnB);
            _spy.MonitorButton(_btnC);
            _spy.MonitorButton(_btnD);
        }

        private void KillSpy(object sender, RoutedEventArgs e) {
            _spy = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            _btnKillSpy.IsEnabled = false;
        }

        private Spy _spy;

    }

    public class Spy{
        public void MonitorButton(Button button){
            button.Click += new RoutedEventHandler(button_Click);
        }

        private void button_Click(object sender, RoutedEventArgs e){
            Button button = sender as Button;
            MessageBox.Show(string.Format("You have just clicked button {0}", button.Content),
                            "小报告", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    }

运行,任意点击按钮A,B,C,D ,弹出来的对话框忠实地报告了你所点击的按钮。按一下按钮“干掉打小报告的”,再挑个按钮点点看。怪事出现了,怎么还有人再打小报告啊???Debug进去,_spy的确给置空了,但是button_Click仍会执行,分明_spy没有被销毁嘛,难道GC跟_spy有一腿?算了,还是别胡思乱想了,老老实实地拜访Gooooooooogle吧。真是不查不知道,一查吓一跳。先译一段MSDN里面的话吧:

侦听事件可能会导致内存泄露。侦听事件的典型做法是把处理方法附加到事件源上,具体写法因编程语言而异。举例而言,在C#中的写法是:source.SomeEvent += new SomeEventHandler(MyEventHandler) 。这种方法产生一个从事件源到侦听者的强引用。一般来说,除非显示去除侦听关系,侦听事件使得侦听者的生命期受到事件源生命期的制约。而在现实情况中,你可能并不想让侦听者的生命期依赖于事件源,而是希望用一些其他的因素(比如它是否属于当前的视觉树)来控制。当事件源的生命期超过侦听者的时候,常规的事件模式就会导致内存泄露:侦听者的存活超过了想要的时间。

乖乖,一直习以为常的写法居然有这么严重的隐患,难道是偶得人品太好所以才一直没有出大问题?喝口水,继续看MSDN:

一般而言,负责开发控件的程序员。。。。。。

怪不得偶一直没有出大问题,原来起作用的不是偶的人品,而是因为偶没有写过几个自定义控件:)

MSDN写的一如既往地晦涩拗口,翻来覆去看了几个来回才懂了个大概,下面来理理思路:

假设有一个类DemoClass公开了DemoEvent事件,ListenerClass的实例listener希望侦听了前者的实例demo的DemoEvent事件。如果是通过 += 的方式来侦听,那么在demo被销毁之前,listener无法被销毁。拿前面的代码来举例吧,_spy侦听了button的Click事件,虽然在_btnKillSpy_Click里面貌似干掉了_spy,但是你点击按钮A,B,C,D,仍然会有人打小报告。到底为啥呢?因为在.net对委托和事件的实现中,当listener侦听了demo的某个事件时,就隐式创建了一个对listener的强引用,任凭你怎么折腾GC,实际的listener对象在demo销毁之前是不会被销毁回收的。至于什么是强引用,偶就这里就不解释了,不明白的看这里。可想而知,当listener侦听了多个其他对象的事件时,情况就变得更加糟糕。网上有关于如何在.net 2.0中解决该问题的blog,也有人说实际上该问题究其本质还是程序员的写法有问题,对clr的理解不够…… 偶才疏学浅,就不参与争论了,这里单单说说在.net 3.0中如何解决这个问题。

可以看到,问题的症结其实就在于那个隐式创建的对侦听者的强引用,很容易想到的一个解决方法就是化强为弱,因为弱引用不会阻止GC的回收行为。WPF中就是以这个思想为指导引入了一个所谓的弱事件模式。具体而言,WPF提供了一个接口和一个类来实现该模式,分别为 IWeakEventListenerWeakEventManager

IWeakEventListener接口定义了希望以弱事件模式侦听事件的类的公约,而WeakEventManager为封装弱事件管理逻辑提供了基类。对于上文中的例子而言,要应用弱事件模式,则SPY类应实现IWeakEventListener,另外为按钮的Click事件添加一个继承于WeakEventManager的管理类。

IWeakEventListener接口仅包含一个方法:

bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e);

侦听者在该方法中依据传入的managerType来识别事件的类型,并分发给相应的处理方法。

WeakEventManager包含的东西相对较多一些,这里就不一一赘述,大家看代码里面的注释吧。

CS:

namespace WeakEventDemo {
    /// <summary>
    /// 按钮Click事件的管理类,负责以弱事件模式来分发事件。
    /// 
    /// 单态。
    /// </summary>
    public class ButtonClickManager : WeakEventManager {
        /// <summary>
        /// 供事件源调用,为管理的弱事件添加侦听者。
        /// </summary>
        /// <param name="source"></param>
        /// <param name="listener"></param>
        public static void AddListener(Button source, IWeakEventListener listener) {
            CurrentManager.ProtectedAddListener(source, listener);
        }

        /// <summary>
        /// 供事件源调用,为管理的弱事件移除侦听者。
        /// </summary>
        /// <param name="source"></param>
        /// <param name="listener"></param>
        public static void RemoveListener(Button source, IWeakEventListener listener) {
            CurrentManager.ProtectedRemoveListener(source, listener);
        }

        /// <summary>
        /// 挂接处理方法到事件源,并开始侦听。
        /// </summary>
        /// <param name="source"></param>
        protected override void StartListening(object source) {
            Button button = source as Button;
            button.Click += new RoutedEventHandler(OnButtonClick);
        }

        /// <summary>
        /// 终止对事件的侦听。
        /// </summary>
        /// <param name="source"></param>
        protected override void StopListening(object source) {
            Button button = source as Button;
            button.Click -= new RoutedEventHandler(OnButtonClick);
        }

        /// <summary>
        /// 事件处理方法,负责转发事件给侦听者。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnButtonClick(object sender, RoutedEventArgs e) {
            DeliverEvent(sender, e);
        }

        /// <summary>
        /// 返回当前的管理类实例。
        /// </summary>
        /// <remarks>
        /// 单态实现。
        /// </remarks>
        private static ButtonClickManager CurrentManager {
            get {
                lock (obj) {
                    Type managerType = typeof (ButtonClickManager);
                    ButtonClickManager clickManager = GetCurrentManager(managerType) as ButtonClickManager;
                    if (clickManager == null) {
                        clickManager = new ButtonClickManager();
                        SetCurrentManager(managerType, clickManager);
                    }
                    return clickManager;
                }
            }
        }

        private static readonly object obj = new object();
    }
}

在WPF中应用弱事件模式的更多相关文章

  1. WPF程序中的弱事件模式

    在C#中,得益于强大的GC机制,使得我们开发程序变得非常简单,很多时候我们只需要管使用,而并不需要关心什么时候释放资源.但是,GC有的时并不是按照我们所期望的方式工作. 例如,我想实现一个在窗口的标题 ...

  2. 【转载】详细解读C#中的 .NET 弱事件模式

    你可能知道,事件处理是内存泄漏的一个常见来源,它由不再使用的对象存留产生,你也许认为它们应该已经被回收了,但不是,并有充分的理由. 在这个短文中(期望如此),我会在 .Net 框架的上下文事件处理中展 ...

  3. C#中的 .NET 弱事件模式

    引言 你可能知道,事件处理是内存泄漏的一个常见来源,它由不再使用的对象存留产生,你也许认为它们应该已经被回收了,但不是,并有充分的理由. 在这个短文中(期望如此),我会在 .Net 框架的上下文事件处 ...

  4. 整理:WPF中应用附加事件制作可以绑定命令的其他事件

    原文:整理:WPF中应用附加事件制作可以绑定命令的其他事件 目的:应用附加事件的方式定义可以绑定的事件,如MouseLeftButton.MouseDouble等等 一.定义属于Control的附加事 ...

  5. WPF中的路由事件(转)

    出处:https://www.cnblogs.com/JerryWang1991/archive/2013/03/29/2981103.html 最近因为工作需要学习WPF方面的知识,因为以前只关注的 ...

  6. WPF中的Command事件绑定

    在项目中使用Command绑定能够使我们的代码更加的符合MVVM模式.不了解的同学可能不清楚,只有继承自ButtonBase类的元素才可以直接绑定Command(Button.CheckBox.Rad ...

  7. WPF中自定义路由事件

    public class MyButtonSimple: Button { // Create a custom routed event by first registering a RoutedE ...

  8. WPF 中的 路由事件

    public class ReportTimeEventArgs:RoutedEventArgs { public ReportTimeEventArgs(RoutedEvent routedEven ...

  9. WPF 弱事件

    因为在接触WPF的过程中追查INotifyPropertyChanged的通知原理的时候,发现了 PropertyChangedEventManager这个类,它是继承与WeakEventManage ...

随机推荐

  1. Verilog学习笔记基本语法篇(七)········ 生成块

    生成块可以动态的生成Verilog代码.可以用于对矢量中的多个位进行重复操作.多个模块的实例引用的重复操作.根据参数确定程序中是否包含某段代码.生成语句可以控制变量的声明.任务和函数的调用.还能对实例 ...

  2. git 本地分支与远程分支 新建/删除/合并

    github上已经有master分支 和dev分支 在本地 git checkout -b dev 新建并切换到本地dev分支 git pull origin dev 本地分支与远程分支相关联 在本地 ...

  3. struts2官方 中文教程 系列十:Form标签

    介绍 在本教程中,我们将探索其他Struts 2表单控件.在前面的教程中,我们介绍了如何使用Struts 2表单(处理表单.表单验证和消息资源文件),我们介绍了如何使用Struts 2 head, f ...

  4. SXOI2018游记

    day0 动身去太原.太原五中虽然挺小的但是很好看啊qwq(进门口一个"通天堂"(逃 试机.似乎看到了__stdcall!!然而没敢去认orz.linux选手似乎是9个.准考证(一 ...

  5. 【APUE】Chapter3 File I/O

    这章主要讲了几类unbuffered I/O函数的用法和设计思路. 3.2 File Descriptors fd本质上是非负整数,当我们执行open或create的时候,kernel向进程返回一个f ...

  6. Vue学习(一):Vue实例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. 做小Leader的心得体会

    只是自己的工作心得体会,代码属于也不够专业,大家不喜勿喷. 8月份来到这家新公司,没过一个月给派了个活:带着两个小弟给某银行开发一个小工具.功能很简单,就是用Java做一个windows上的C端工具, ...

  8. 第二十四篇configparser(**)

    configparser模块 config:配置,parser:解析.字面意思理解configparser模块就是配置文件的解析模块. 来看一个好多软件的常见文档格式如下: [DEFAULT] # 标 ...

  9. CSP201312-1:出现次数最多的数

    引言:CSP(http://www.cspro.org/lead/application/ccf/login.jsp)是由中国计算机学会(CCF)发起的“计算机职业资格认证”考试,针对计算机软件开发. ...

  10. 简单的素数问题(C++)

    [问题描述] 已知三个素数的和为 n ,正整数 n 由键盘输入,计算并输出这三个素数乘积的最大值. [代码展示] # include<iostream>using namespace st ...