C# – 匿名函数和事件处理程序

我有以下代码:

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

请注意我如何将我的事件成员(FoundStep)注册到本地就地匿名函数。

我的问题是:当函数’FindStepByType’结束时 – 匿名函数会从事件的委托列表中自动删除,还是我必须在逐步退出函数之前手动删除它? (我该怎么做?)

我希望我的问题很明确。

您的代码有一些问题(您和其他人已经确定了一些问题):

  • 匿名代表无法从编码的事件中删除。
  • 匿名委托的寿命比调用它的方法的寿命长,因为你已经将它添加到FoundStep ,这是其中的一员。
  • FindStepsByType的每个条目都会向FoundStep添加另一个匿名委托。
  • 匿名委托是一个闭包,有效地延长了retval的生命周期,所以即使你停止在代码中的其他地方引用retval ,它仍然由匿名委托持有。

要修复此问题,仍然使用匿名委托,将其分配给局部变量,然后删除finally块中的处理程序(在处理程序抛出exception时必需):

  public List FindStepsByType(IWFResource res) { List retval = new List(); EventHandler 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 FindStepsByType(IWFResource res) { var retval = new List(); 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; } 

下面是关于如何在匿名方法中取消订阅事件的方法:

 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(); 

不,它不会自动删除。 从这个意义上讲,匿名方法和“普通”方法之间没有区别。 如果需要,您应手动取消订阅该活动。

实际上,它会捕获其他变量(例如你的例子中的res )并保持它们存活(防止垃圾收集器收集它们)。

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

如果查看代码,即使您在函数中声明和订阅事件,您订阅的事件也在类中,因此一旦订阅,即使函数退出也将始终订阅。 要实现的另一个重要的事情是,每次调用此函数时,它都会再次订阅该事件。 这是完全合法的,因为事件本质上是多播代理并允许多个订阅者。 (这可能是也可能不是你想要的。)

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

出于这些原因,如果您稍后必须取消订阅该事件,则不建议使用匿名代理。 请参见如何:订阅和取消订阅事件(C#编程指南) (特别是标题为“使用匿名方法订阅事件”一节)。