65.9K
CodeProject 正在变化。 阅读更多。
Home

匿名方法作为事件处理程序 - 第二部分

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2009年9月9日

CPOL

1分钟阅读

viewsIcon

20080

匿名方法作为事件处理程序

上一篇文章讨论了匿名方法作为事件处理程序,并以一个问题结尾——为什么订阅有效,但取消订阅却不起作用?

Vivek给出了正确的答案——C# 编译器处理和转换匿名方法的方式是原因所在。

这是涉及的代码

   1: public void Initialize()
   2: {
   3:     control.KeyPressed += IfEnabledThenDo(control_KeyPressed);
   4:     control.MouseMoved += IfEnabledThenDo(control_MouseMoved);
   5: }
   6:  
   7: public void Destroy()
   8: {
   9:     control.KeyPressed -= IfEnabledThenDo(control_KeyPressed);
  10:     control.MouseMoved -= IfEnabledThenDo(control_MouseMoved);
  11: }
  12:  
  13: public EventHandler<Control.ControlEventArgs> 
             IfEnabledThenDo(EventHandler<Control.ControlEventArgs> actualAction)
  14: {
  15:     return (sender, args) => { if (args.Control.Enabled) actualAction(sender, args); };
  16: }

编译器将 IfEnabledThenDo 转换为以下代码

   1: public EventHandler<Control.ControlEventArgs> 
             IfEnabledThenDo(EventHandler<Control.ControlEventArgs> actualAction)
   2: {
   3:     <>c__DisplayClass1 CS$<>8__locals2 = new <>c__DisplayClass1();
   4:     CS$<>8__locals2.actualAction = actualAction;
   5:     return new EventHandler<Control.ControlEventArgs>(
                         CS$<>8__locals2.<IfEnabledThenDo>b__0);
   6: }

现在问题应该很明显了——每次调用该函数时,都会创建一个新对象,而返回的事件处理程序实际上引用了新实例上的一个方法(<IfEnabledThenDo>b__0)。 这就是取消订阅失败的原因。 –= 不会从调用列表中删除同一类的不同实例的委托——如果这样做,如果同一类的多个实例订阅事件,后果将不堪设想。

但是,编译器为什么会将我们的 lambda 表达式这样转换? Raymond Chen 撰写了一篇很棒的博文解释了原因,但简而言之,这是为了“保存”actualActionIfEnabledThenDo 的方法参数),以便在事件处理程序实际执行时可用。

既然我们知道了原因,解决此问题的办法是缓存 IfEnabledThenDo 返回的委托实例,并在订阅和取消订阅时使用相同的实例。

   1: EventHandler<Control.ControlEventArgs> keyPressed;
   2: EventHandler<Control.ControlEventArgs> mouseMoved;
   3:  
   4: public void Initialize()
   5: {
   6:    keyPressed = IfEnabledThenDo(control_KeyPressed);
   7:    mouseMoved = IfEnabledThenDo(control_MouseMoved);
   8:  
   9:    control.KeyPressed += keyPressed;
  10:    control.MouseMoved += mouseMoved;
  11: }        
  12:  
  13: public void Destroy()
  14: {
  15:    control.KeyPressed -= keyPressed;
  16:    control.MouseMoved -= mouseMoved;
  17: }

了解底层工作原理确实有其优势,我想。

附言:对原始示例进行一个非常小的语法更改,代码就能立即工作。 如果你一直关注到这里,你应该能够弄清楚原因。

   1: public void Initialize()
   2: {
   3:    actualAction = control_KeyPressed;
   4:    control.KeyPressed += IfEnabledThenDo();
   5: }        
   6:  
   7: public void Destroy()
   8: {
   9:     control.KeyPressed -= IfEnabledThenDo();
  10: }
  11:  
  12: EventHandler<Control.ControlEventArgs> actualAction;
  13: public EventHandler<Control.ControlEventArgs> IfEnabledThenDo()
  14: {
  15:     return (sender, args) => { if (args.Control.Enabled) actualAction(sender, args); };
  16: }

© . All rights reserved.