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

C#-匿名函数和事件处理程序,c#,scope,anonymous-methods,C#,Scope,Anonymous Methods,我有以下代码: public List<IWFResourceInstance> FindStepsByType(IWFResource res) { List<IWFResourceInstance> retval = new List<IWFResourceInstance>(); this.FoundStep += delegate(object sender, WalkerStepEventArgs e)

我有以下代码:

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;
}  
public List FindStepsByType(IWFResource res)
{  
List retval=新列表();
this.FoundStep+=委托(对象发送方,WalkerStepEventArgs e)
{   
如果(e.Step.ResourceType==res)retval.Add(e.Step);
};  
这个。Start();
返回返回;
}  
注意我是如何将我的事件成员(FoundStep)注册到本地就地匿名函数的

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


我希望我的问题是清楚的

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


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

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

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

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


出于这些原因,如果您必须在以后某个时间取消订阅活动,则不建议使用匿名代理。请参阅(特别是标题为“使用匿名方法订阅事件”的部分)。

您的代码存在一些问题(您和其他人已经发现了一些问题):

  • 无法按编码从事件中删除匿名委托
  • 匿名委托的寿命将比调用它的方法的寿命长,因为您已将其添加到FoundStep,它是this的成员
  • FindStepByType中的每个条目都会向FoundStep添加另一个匿名委托
  • 匿名委托是一个闭包,它有效地延长了retval的生存期,因此即使您停止在代码中的其他地方引用retval,它仍然由匿名委托持有
要解决此问题,并且仍然使用匿名委托,请将其分配给局部变量,然后在finally块中删除处理程序(在处理程序引发异常时需要):

public List FindStepsByType(IWFResource res)
{
List retval=新列表();
EventHandler处理程序=(发送方,e)=>
{
如果(e.Step.ResourceType==res)retval.Add(e.Step);
};
this.FoundStep+=处理程序;
尝试
{
这个。Start();
}
最后
{
this.FoundStep-=处理程序;
}
返回返回;
}

使用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;
    }
public List FindStepsByType(IWFResource res)
{
var retval=新列表();
无效处理程序(对象发送方,WalkerStepEventArgs e)
{
如果(e.Step.ResourceType==res)retval.Add(e.Step);
}
FoundStep+=处理程序;
尝试
{
这个。Start();
}
最后
{
FoundStep-=处理程序;
}
返回返回;
}

以下是如何以匿名方式取消订阅事件的方法:

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

这和使用谓词不一样吗?当我使用谓词时,我不会释放谓词委托。谓词不会保存在任何地方,但在这里,您订阅了一个事件。只要包含事件的对象处于活动状态,它就会保存对委托的引用并间接引用其变量。当您将say,
.Where(x=>x.Hidden)
传递给某个方法时,该方法将使用它完成工作并将其丢弃(就
方法而言,它只是一个局部变量。你的情况并非如此。此外,如果
方法将它存储在某个地方,你也应该担心这一点。局部函数,实现同样的效果?你的意思是同样的问题吗?
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();