2009-09-07 85 views
17

我有以下代码:C# - 匿名函数和事件处理

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
{ 
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>(); 
    this.FoundStep += delegate(object sender, WalkerStepEventArgs e) 
         { 
         if (e.Step.ResourceType == res) retval.Add(e.Step); 
         }; 
    this.Start(); 
    return retval; 
} 

通知我如何注册我的事件成员(FoundStep)到当地就地匿名函数。

我的问题是:当函数'FindStepByType'将结束 - 将匿名函数从事件的委托列表中自动删除,或者我必须在执行该函数之前手动删除它? (以及我该怎么做?)

我希望我的问题很清楚。

回答

34

您的代码(有些你和其他人已经确定)的几个问题:

  • 匿名委托无法从活动中移除的编码。
  • 匿名代理的寿命比调用它的方法的寿命要长,因为您已将它添加到FoundStep这是这个的成员。
  • 每进入FindStepsByType增加另一个匿名代表FoundStep
  • 匿名委托是一个封闭和有效地延长的RETVAL寿命,所以即使你停止引用代码中的其他地方RETVAL,它仍然通过匿名委托举行。

为了解决这个问题,并且仍然使用匿名委托,将其分配给一个局部变量,然后移除终于块内的处理程序(必要的情况下,处理程序抛出异常):

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
    { 
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>(); 
    EventHandler<WalkerStepEventArgs> handler = (sender, e) => 
    { 
     if (e.Step.ResourceType == res) retval.Add(e.Step); 
    }; 

    this.FoundStep += handler; 

    try 
    { 
     this.Start(); 
    } 
    finally 
    { 
     this.FoundStep -= handler; 
    } 

    return retval; 
    } 

用C#7.0或更高版本,你可以用本地函数替换匿名委托,达到同样的效果:

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
    { 
     var retval = new List<IWFResourceInstance>(); 

     void Handler(object sender, WalkerStepEventArgs e) 
     { 
      if (e.Step.ResourceType == res) retval.Add(e.Step); 
     } 

     FoundStep += Handler; 

     try 
     { 
      this.Start(); 
     } 
     finally 
     { 
      FoundStep -= Handler; 
     } 

     return retval; 
    } 
5

不,它不会被自动删除。从这个意义上说,匿名方法和“正常”方法之间没有区别。如果你愿意,你应该手动退订该事件。

实际上,它会捕获其他变量(例如在您的示例中为res)并使它们保持活动状态(防止垃圾收集器收集它们)。

+0

是不是就像使用谓词一样?当我使用谓词时,我不释放谓词委托。 – 2009-09-07 14:02:32

+2

谓词不保存在任何地方,但在这里,您正在订阅一个事件。只要包含该事件的对象处于活动状态,它就会持有对您的委托的引用,并间接对其变量进行引用。当你通过'.Where(x => x.Hidden)'来传递某个方法时,该方法将对它进行处理并将其扔掉(就'Where'方法而言,它只是一个局部变量。这不适用于你的情况。另外,如果'Where'存储在某个地方,你也应该担心这一点。 – 2009-09-07 14:05:49

3

使用匿名委托(或lambda表达式)订阅事件时,不允许您以后轻松取消订阅该事件。事件处理程序永远不会自动取消订阅。

如果你看看你的代码,即使你声明和订阅了一个函数中的事件,你订阅的事件也是在类中,所以一旦订阅了它,即使在函数退出后,它也会被订阅。另一个重要的事情是,每次调用这个函数时,它都会再次订阅该事件。这是完全合法的,因为事件本质上是多播委托并允许多个订阅者。 (这可能或可能不是你想要的。)

为了在退出函数之前取消订阅委托,您需要将匿名委托存储在委托变量中,并将该委托添加到事件中。然后,您应该能够在函数退出之前从该事件中移除该委托。

由于这些原因,如果您稍后必须退订该活动,建议不要使用匿名代表。请参阅How to: Subscribe to and Unsubscribe from Events (C# Programming Guide)(特别是标题为“使用匿名方法订阅事件”的部分)。

5

下面是有关在如何退订事件方法诡异的方法:

DispatcherTimer _timer = new DispatcherTimer(); 
_timer.Interval = TimeSpan.FromMilliseconds(1000); 
EventHandler handler = null; 

int i = 0; 

_timer.Tick += handler = new EventHandler(delegate(object s, EventArgs ev) 
{ 
    i++; 
    if(i==10) 
     _timer.Tick -= handler; 
}); 

_timer.Start();