因为在接触WPF的过程中追查INotifyPropertyChanged的通知原理的时候,发现了 PropertyChangedEventManager这个类,它是继承与WeakEventManager,也就是弱事件管理器,另外在学习MVVM的时候,其类库中也有关于弱引用弱事件方面的代码,然后我又非常的不熟悉,今天我打算深入了解下这个方面。

首先得了解下事件,事件在我的另外一篇里面已经详细讲了,这里就不重复了,但是事件其实有可能会导致内存泄露的,下面先看一个例子:

先申明一个Spy类,里面订阅按钮的Click事件:

  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);
} private ArrayList _weapons = new ArrayList( * * );
}

然后在main里面侦听4个按钮:

            _strongSpy = new Spy();
_strongSpy.MonitorButton(_btnA);
_strongSpy.MonitorButton(_btnB);
_strongSpy.MonitorButton(_btnC);
_strongSpy.MonitorButton(_btnD);

然后在第五个按钮的Click事件里面把Spy类对象清理掉:

 private void KillSpy(object sender, RoutedEventArgs e) {
_strongSpy = null;
GC.Collect();
GC.WaitForPendingFinalizers(); MessageBox.Show("任务完成");
UpdateTitle();
_btnKillSpy.IsEnabled = false;
}

销毁掉Spy类对象后,再去点击按钮,发现事件依然得到响应,为什么呢?就算_strongSpy销毁,但是因为在_strongSpy里面订阅了按钮的click事件,这种订阅是属于强引用的订阅,除非去除订阅关系。貌似这有点不好理解,下面我先谈谈一般我们事件的一个过程:

1)定义事件委托。

 2)申明事件。

 3)触发事件。

4)订阅事件。其实也就是侦听事件。

 针对这个过程我举个2个例子:

  第一个例子是我另外一篇里面的那个热水器的例子,定义事件委托BoiledEventHandler,可以在很多地方都可以,申明事件对象在热水器类里面,事件名为Boiled,然后在另外调用的类里面,订阅这个事件,也就是观察或者侦听这个热水器对象(观察对象),在热水的过程中,超过95度,就触发Boiled事件。事件源指的应该就是热水器这个对象,因为事件是在热水器这个对象里面,侦听者应该就是调用这个热水器对象的类。

  第二个例子是按钮的Click事件,Click事件的委托类型在某个地方定义,这不重要,然后Click事件申明在按钮控件类里面,那个这个按钮类就是Click事件的源,谁来侦听谁就来订阅这个事件,假设form1来侦听,我们一般在按钮上双击,VS自动帮我们做好了这一切,我们直接写事件处理方法即可。

我们再回到_strongSpy销毁了但是按钮事件却仍然得到响应的这个问题上来,到底为什么呢?我下断点,发现_strongSpy确实为null了,但是点击按钮的事件处理方法依然存在,但是这个方法又是在Spy这个类里面,这岂不矛盾?如果一直点击按钮,内存的占用量会一直增长下去,这是为什么呢?其实原理是这样的:如果通过+=的方式来订阅了事件,那么就隐式创建了一个对这个侦听者的强引用(还有弱引用的概念),任凭怎么GC,实际的侦听对象在事件源没有销毁之前是不会真正的回收的,就跟这个例子一样,真正的源头是button,如果按钮没有销毁,那么_strongSpy是不会真正回收的,特别是当一个类要是侦听多个其他对象的事件的时候,那情况就更糟糕了,因为其他对象如果没有销毁,销毁这个类对象其实是不会被真正回收的。那么如何解决这种问题呢?肯定是要通过-=的方式来去除侦听关系,通过弱引用,弱事件模式来加强GC的回收。WPF提供了一个接口和一个类来实现该模式,我们就先看看代码吧:

 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();
}
}
 namespace WeakEventDemo {
/// <summary>
/// 应用弱事件模式的侦听类。
/// </summary>
public class WeakSpy : IWeakEventListener {
/// <summary>
/// 添加要侦听的button。
/// </summary>
/// <param name="button"></param>
public void MonitorButton(Button button) {
ButtonClickManager.AddListener(button, this);
} /// <summary>
/// 打小报告^_^
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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);
} #region IWeakEventListener Members /// <summary>
/// 接收事件。
/// </summary>
/// <param name="managerType"></param>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <returns>如果事件得到处理,则返回true,否则返回false</returns>
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) {
if (managerType == typeof (ButtonClickManager)) {
button_Click(sender, (RoutedEventArgs) e);
return true;
} else {
return false;
}
} #endregion /// <summary>
/// 间谍的私人武器库。
/// </summary>
private ArrayList _weapons = new ArrayList(**);
}
}

调用的代码跟之前强引用的时候一样。

我们发现通过弱事件模式,成功的解决了这个内存泄露的问题。侦听者得到了真正的销毁,成功的回收了。

我们再来分析其原理:

ButtonClickManger是继承与WeakEventManager类,干什么事情了?从名字上看就是管理弱事件的,有哪些管理任务了?

首先增加侦听者,这个侦听者负责侦听一些其他对象的事件,侦听的参数肯定要有源头,侦听谁,然后还有这个侦听者对象必须要实现IWeakEventListener接口,这样管理器就记录了这样的集合:侦听者与侦听对象的一个对照表:

public static void AddListener(Button source, IWeakEventListener listener) {
            CurrentManager.ProtectedAddListener(source, listener);
        }

其次要启动开始侦听,要启动侦听事件源的事件,并负责订阅事件,在事件处理程序里面分发事件,这个会触发执行侦听者的接口函数ReceiveWeakEvent,然后我们就可以在这个函数里面做我们想做的事情了。

protected override void StartListening(object source) {
            Button button = source as Button;
            button.Click += new RoutedEventHandler(OnButtonClick);
        }

   private void OnButtonClick(object sender, RoutedEventArgs e) {
            DeliverEvent(sender, e);
        }

有开始就有停止:

protected override void StopListening(object source) {
            Button button = source as Button;
            button.Click -= new RoutedEventHandler(OnButtonClick);
        }

WeakSpy这个类实现的是IWeakEventListener这个接口

作为一个侦听者,首先要把要侦听的对象通过弱事件管理器添加进来:

public void MonitorButton(Button button) {
            ButtonClickManager.AddListener(button, this);
        }

然后接口函数里面的方法中执行事件处理程序:

public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) {
            if (managerType == typeof (ButtonClickManager)) {
                button_Click(sender, (RoutedEventArgs) e);
                return true;
            } else {
                return false;
            }
        }

在WeakEventManager中,如果AddListener,就会自动调用StartListening.

在这里我串一下整个过程:

  侦听者添加侦听的对象(WeakSpy.MonitorButton)->弱事件管理者增加侦听者(ButtonClickManager.AddListener)->弱事件管理者开始侦听,并确定要侦听的事件,及订阅事件(ButtonClickManager.StartListening)

点击按钮->触发到弱事件管理器的分发事件函数(DeliverEvent)->触发监听者的接口函数(IWeakEventListener.ReceiveWeakEvent)->调用监听者的私有函数(这里是button_Click)

当我们清理了WeakSpy这个对象后再点击按钮->触发到弱事件管理器的分发事件函数(DeliverEvent)->发现不存在这个监听对象就调用清理操作最终调用StopListening。

我再回到最开始的话题,我是在研究INotifyPropertyChanged这个课题的时候研究的弱事件这个问题,那么我们就先看看这个接口,这个接口只有一个成员,那就是一个事件event PropertyChangedEventHandler PropertyChanged;还是先看代码:

  public class Student : INotifyPropertyChanged
{
private event PropertyChangedEventHandler _propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add
{
this._propertyChanged += value;
}
remove
{
this._propertyChanged -= value;
}
}
private string name;
public string Name
{
get { return name; }
set {
name = value;
if (_propertyChanged != null)
{
_propertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
} public Student()
{
Name = "Wuming";
}
}

我们在实现这个接口的时候其实可以只照抄了一遍,在编译后,那么其实这个类就只是增加了Add, Remove函数,另外还有一个私有的委托对象成员(我这里显示写出来,方便我看代码图),在构造函数执行完后,PropertyChanged是为NULL的。那么我就有疑问:在属性变化的时候判断PropertyChanged是不为NULL的,那么到底什么时候把这个事件给订阅的呢?经过查看是绑定完成后PropertyChanged就有值了。那到底为什么呢?这个其实牵扯比较多,我们先看一个图:

从图中我们可以看出在设置绑定的时候,依赖对象(这里是TextBox)设置值,设置值的时候势必要调用Binding,最终要调用PropertyChangedEventManager.StartListening(object source),这个源自然是student这个对象了,开始监听里面的代码如

下:

 protected override void StartListening(object source)
{
INotifyPropertyChanged notifyPropertyChanged = (INotifyPropertyChanged)source;
notifyPropertyChanged.PropertyChanged += new PropertyChangedEventHandler(this.OnPropertyChanged);
}

PropertyChangedEventManager也就是一个弱事件管理器,到这里我就这样串以下,绑定完成后为什么可以实现数据与UI的同步呢?显然是通过事件,那么只要实现了INotifyPropertyChanged这个事件,在绑定的时候就会在弱事件管理器里面订阅事件,这样当我们属性更新的时候自然就会执行这个事件,另外一个问题就来了,虽然事件是执行了,但是源的改变为什么通过这个事件改变目标呢?我想应该是这样子的:

  在设置binding的时候,目标相当于监听者的地位,监听的对象就是源,这样当源有变化的时候,目标当然也会改变,当然这只是我的想象。求证未果。

到目前为止,我明白了这样一些事情:INotifyPropertyChanged接口的事件成员在什么时候订阅的,大概知道了数据同步的原理,了解了弱事件管理器的大致工作原理。这对于以后的学习打下了基础。

引用了这位仁兄的博客:

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

Demo:

http://files.cnblogs.com/files/monkeyZhong/WeakEventDemo.zip

WPF 弱事件的更多相关文章

  1. 在WPF中应用弱事件模式

    http://www.cnblogs.com/rickiedu/archive/2007/03/15/676021.html 在wpf中应用弱事件模式        感谢VS 的Intellisens ...

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

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

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

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

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

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

  5. [AaronYang]C#人爱学不学8[事件和.net4.5的弱事件深入浅出]

    没有伟大的愿望,就没有伟大的天才--Aaronyang的博客(www.ayjs.net)-www.8mi.me 1. 事件-我的讲法 老师常告诉我,事件是特殊的委托,为委托提供了一种发布/订阅机制. ...

  6. .net4.5的弱事件

    .net4.5的弱事件 没有伟大的愿望,就没有伟大的天才--Aaronyang的博客(www.ayjs.net)-www.8mi.me 1. 事件-我的讲法 老师常告诉我,事件是特殊的委托,为委托提供 ...

  7. c#弱事件(weak event)

    传统事件publisher和listener是直接相连的,这样会对垃圾回收带来一些问题,例如listener已经不引用任何对象但它仍然被publisher引用 垃圾回收器就不能回收listener所占 ...

  8. WPF 在事件中绑定命令(不可以在模版中绑定命令)

    其实这也不属于MVVMLight系列中的东东了,没兴趣的朋友可以跳过这篇文章,本文主要介绍如何在WPF中实现将命令绑定到事件中. 上一篇中我们介绍了MVVMLight中的命令的用法,那么仅仅知道命令是 ...

  9. WPF自学入门(三)WPF路由事件之内置路由事件

    有没有想过在.NET中已经有了事件机制,为什么在WPF中不直接使用.NET事件要加入路由事件来取代事件呢?最直观的原因就是典型的WPF应用程序使用很多元素关联和组合起来,是否还记得在WPF自学入门(一 ...

随机推荐

  1. json数值和结构

    JSON 值可以是: l  数字(整数或浮点数) l  字符串(在双引号中) l  逻辑值(true 或 false) l  数组(在方括号中) l  对象(在花括号中) l  null JSON建构 ...

  2. codeforce-191E-Thwarting Demonstrations(树状数组+二分+离散)

    题意: 求第K 大连续区间 分析: 二分答案,再n * log(n)判断有几个区间的区间和大于mid,然后调整上下界,使这个值不断的接近k. 判断符合条件的区间总数:线性扫描sum[n](前n项和) ...

  3. 【转】中兴G718C卡刷刷机教程(青漾2 4G)--不错

    原文网址:http://www.zterom.com/guide/2278.html 刷机包 B11纯净版 适合长久使用_B11_lite_0130.zip 刷机用了20多分钟. 在和大家分享过中兴G ...

  4. 枚举(分类讨论):BZOJ 1177: [Apio2009]Oil

    1177: [Apio2009]Oil Time Limit: 15 Sec  Memory Limit: 162 MBSubmit: 1477  Solved: 589[Submit] Descri ...

  5. 后缀自动机(SAM)模板

    struct SAM{ ],fa[maxn],len[maxn],cnt,last; void Init() { memset(ch,,sizeof(ch)); memset(fa,,sizeof(f ...

  6. Introduction to Glide, Image Loader Library for Android, recommended by Google

    In the passed Google Developer Summit Thailand, Google introduced us an Image Loader Library for And ...

  7. codevs3945 完美拓印

    3945 完美拓印 codevs月赛 第一场 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 黄金 Gold 题目描述 Description 小Q获得了一个神奇的印章,这个印章宽n ...

  8. js~this的陷阱

    在JS中,当前对象一般用this表示,在jquery中,当前的对象是用$(this)表示,这些都是最基础的知识,没什么可说的,但我要说的是,当this出现在某个深度时,它的含义你自己要清楚,它是指离当 ...

  9. [置顶] Android JNI必须掌握的五点

      1:JNI是什么? Java NativeInterface(JNI)是Java提供的一个很重要的特性.它使得用诸如C/C++等语言编写的代码可以与运行于Java虚拟机(JVM)中的 Java代码 ...

  10. 在安装软件CAJViewer时出现,“错误1327。无效驱动器:F:

    解决的方法:DOS中输入例如以下命令: [plain] view plaincopy subst F: %TEMP% 回车退出就可以,必要时重新启动电脑. 软件成功安装之后能够执行下面命令,将该虚拟分 ...