在wpf中应用弱事件模式

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

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

XAML:

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


CS:

ExpandedBlockStart.gif ContractedBlock.gif public  partial  class  Window1 : Window  dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public Window1() dot.gif{
InBlock.gif            InitializeComponent();
InBlock.gif
InBlock.gif            _spy 
= new Spy();
InBlock.gif
InBlock.gif            _spy.MonitorButton(_btnA);
InBlock.gif            _spy.MonitorButton(_btnB);
InBlock.gif            _spy.MonitorButton(_btnC);
InBlock.gif            _spy.MonitorButton(_btnD);
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
private void KillSpy(object sender, RoutedEventArgs e) dot.gif{
InBlock.gif            _spy 
= null;
InBlock.gif            GC.Collect();
InBlock.gif            GC.WaitForPendingFinalizers();
InBlock.gif            _btnKillSpy.IsEnabled 
= false;
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        
private Spy _spy;
InBlock.gif
ExpandedBlockEnd.gif    }

None.gif
ExpandedBlockStart.gifContractedBlock.gif    
public   class  Spy dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public void MonitorButton(Button button)dot.gif{
InBlock.gif            button.Click 
+= new RoutedEventHandler(button_Click);
ExpandedSubBlockEnd.gif        }

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

ExpandedBlockEnd.gif    }


      运行,任意点击按钮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:

ExpandedBlockStart.gif ContractedBlock.gif namespace  WeakEventDemo  dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/**//// <summary>
InBlock.gif    
/// 按钮Click事件的管理类,负责以弱事件模式来分发事件。
InBlock.gif    
/// 
InBlock.gif    
/// 单态。
ExpandedSubBlockEnd.gif    
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif    public class ButtonClickManager : WeakEventManager dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 供事件源调用,为管理的弱事件添加侦听者。
InBlock.gif        
/// </summary>
InBlock.gif        
/// <param name="source"></param>
ExpandedSubBlockEnd.gif        
/// <param name="listener"></param>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public static void AddListener(Button source, IWeakEventListener listener) dot.gif{
InBlock.gif            CurrentManager.ProtectedAddListener(source, listener);
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 供事件源调用,为管理的弱事件移除侦听者。
InBlock.gif        
/// </summary>
InBlock.gif        
/// <param name="source"></param>
ExpandedSubBlockEnd.gif        
/// <param name="listener"></param>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public static void RemoveListener(Button source, IWeakEventListener listener) dot.gif{
InBlock.gif            CurrentManager.ProtectedRemoveListener(source, listener);
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 挂接处理方法到事件源,并开始侦听。
InBlock.gif        
/// </summary>
ExpandedSubBlockEnd.gif        
/// <param name="source"></param>

ExpandedSubBlockStart.gifContractedSubBlock.gif        protected override void StartListening(object source) dot.gif{
InBlock.gif            Button button 
= source as Button;
InBlock.gif            button.Click 
+= new RoutedEventHandler(OnButtonClick);
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 终止对事件的侦听。
InBlock.gif        
/// </summary>
ExpandedSubBlockEnd.gif        
/// <param name="source"></param>

ExpandedSubBlockStart.gifContractedSubBlock.gif        protected override void StopListening(object source) dot.gif{
InBlock.gif            Button button 
= source as Button;
InBlock.gif            button.Click 
-= new RoutedEventHandler(OnButtonClick);
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 事件处理方法,负责转发事件给侦听者。
InBlock.gif        
/// </summary>
InBlock.gif        
/// <param name="sender"></param>
ExpandedSubBlockEnd.gif        
/// <param name="e"></param>

ExpandedSubBlockStart.gifContractedSubBlock.gif        private void OnButtonClick(object sender, RoutedEventArgs e) dot.gif{
InBlock.gif            DeliverEvent(sender, e);
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 返回当前的管理类实例。
InBlock.gif        
/// </summary>
InBlock.gif        
/// <remarks>
InBlock.gif        
/// 单态实现。
ExpandedSubBlockEnd.gif        
/// </remarks>

ExpandedSubBlockStart.gifContractedSubBlock.gif        private static ButtonClickManager CurrentManager dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif                
lock (obj) dot.gif{
InBlock.gif                    Type managerType 
= typeof (ButtonClickManager);
InBlock.gif                    ButtonClickManager clickManager 
= GetCurrentManager(managerType) as ButtonClickManager;
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
if (clickManager == nulldot.gif{
InBlock.gif                        clickManager 
= new ButtonClickManager();
InBlock.gif                        SetCurrentManager(managerType, clickManager);
ExpandedSubBlockEnd.gif                    }

InBlock.gif                    
return clickManager;
ExpandedSubBlockEnd.gif                }

ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        
private static readonly object obj = new object();
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}



整个工程在 HERE下载。

转载于:https://www.cnblogs.com/rickiedu/archive/2007/03/15/676021.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值