24

我有以下代码:

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”函数何时结束 - 匿名函数会自动从事件的委托列表中删除,还是我必须在退出函数之前手动删除它?(我该怎么做?)

我希望我的问题很清楚。

4

4 回答 4

45

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

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

要解决这个问题,并且仍然使用匿名委托,请将其分配给局部变量,然后在finally块中删除处理程序(如果处理程序抛出异常,这是必要的):

  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;
    }
于 2009-09-07T16:55:41.777 回答
11

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

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();
于 2010-10-03T10:15:23.750 回答
5

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

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

于 2009-09-07T13:57:17.930 回答
2

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

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

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

由于这些原因,如果您以后必须取消订阅该事件,则不建议使用匿名委托。请参阅如何:订阅和取消订阅事件(C# 编程指南)(特别是标题为“使用匿名方法订阅事件”的部分)。

于 2009-09-07T14:57:32.177 回答