如何取消注册“匿名”事件处理程序 [重复]

Posted

技术标签:

【中文标题】如何取消注册“匿名”事件处理程序 [重复]【英文标题】:How do I Unregister 'anonymous' event handler [duplicate] 【发布时间】:2010-11-23 19:19:33 【问题描述】:

如果我监听一个事件就说:

Subject.NewEvent += delegate(object sender, NewEventArgs e)

    //some code
); 

现在如何取消注册此活动?还是让内存泄漏?

【问题讨论】:

看这里***.com/questions/183367/… 【参考方案1】:

为您的匿名委托实例命名:

EventHandler<NewEventArg> handler = delegate(object sender, NewEventArgs e)

    //some code
;

Subject.NewEvent += handler;
Subject.NewEvent -= handler;

【讨论】:

为什么这比仅仅让它成为一个非匿名方法更好?这一点不那么明显。 @PK:我认为这是你能得到的最接近的。您无法取消注册您无法引用的内容。 @Reed:匿名委托还有一个额外的好处,那就是创建一个闭包,这是使用非匿名方法无法做到的。如果 OP 希望能够包含无法传递到 eventargs 的范围内值,那么这是最好的方法。 代码示例很可能不起作用,该事件很可能永远不会被触发。取消订阅必须在处理程序内部进行。我刚刚在这里发布了一个类似的问题:***.com/questions/2147116/… 这可能意味着你需要将委托创建为私有成员,如果你在类的不同部分进行取消订阅,那么使用方法并没有太大的好处 【参考方案2】:

如果您需要取消注册事件,我建议避免事件处理程序的匿名委托。

这是将其分配给本地方法更好的一种情况 - 您可以完全取消订阅该事件。

【讨论】:

我是这么认为的。但是,有一个疑问。谢谢。 我不同意 - 如果您需要创建一个闭包,那么您将不得不使用匿名方法。 @free-dom:总是有一些选项可以避免关闭(最坏的情况,你可以做编译器为你做的事情)。大多数情况下,您计划取消订阅事件的事件处理程序不是 IMO,您希望关闭的事件的良好候选者。您应该使用易于跟踪的类级别状态信息,而不是让编译器为您创建闭包。在这种情况下,随着时间的推移,闭包往往会导致奇怪的、难以跟踪的问题,并且不那么可维护。 我完全不同意这一点。闭包可以让你更干净简洁地传递状态。创建小类并在其上设置状态是粗俗和骇人听闻的,因此难以维护。 关于闭包与方法的争论,到目前为止,我更喜欢闭包。主要原因是我不想在类中添加字段,除非必要(阅读:两个或更多方法需要共享变量)。使用其他类型来保存捕获的值会更好,但由于这正是编译器为我使用闭包所做的,所以我宁愿使用闭包。【参考方案3】:

在第一次调用时删除处理程序:

//SubjectType Subject = ..... already defined if using (2)

EventHandler handler = null;
handler = delegate(object sender, EventArgs e)

    // (1)
    (sender as SubjectType).NewEvent -= handler;
    // or
    // (2) Subject.NewEvent -= handler;

    // do stuff here
;

Subject.NewEvent += handler;

【讨论】:

使用这种代码,Resharper 抱怨访问修改后的闭包......这种方法可靠吗?我的意思是,我们确定匿名方法体内的“foo”变量真的引用了匿名方法本身吗? 我想我自己得到了答案:Resharper 是对的,一旦将匿名方法分配给它,捕获的变量(上例中的“处理程序”)就会更改。因此,它确实发生了变化,但这种机制确保了“处理程序”变量存储对匿名方法 itslef 的引用。 我不得不这样做,以便处理程序可以从事件的引发者那里捕获状态(例如调用方法的参数和本地)和状态,这一切都是因为引发者没有提供一些需要的信息在调用方法退出之前的任何其他方式。这很曲折,但它确实有效,并且不需要创建人工类来处理事件。 正是我需要的。谢谢。【参考方案4】:

您可以创建从所有事件侦听器中注销的方法。这并不完全是您想要的,但有时它可能会有所帮助。例如(这确实有效=)):

    class Program 
    static void Main(string[] args) 
        A someClass = new A();
        someClass.SomeEvent += delegate(object sender, EventArgs e) 
            throw new NotImplementedException();
        ;

        someClass.ClearEventHandlers();
        someClass.FireEvent();

        Console.WriteLine("No error.");
    

    public class A 
        public event EventHandler SomeEvent;

        public void ClearEventHandlers() 
            Delegate[] delegates = SomeEvent.GetInvocationList();
            foreach (Delegate delegate in delegates) 
                SomeEvent -= (EventHandler) delegate;
            
        

        public void FireEvent() 
            if (SomeEvent != null) 
                SomeEvent(null, null);
            
        
    

【讨论】:

不知道为什么这被否决了——当然,最好不要一开始就有持久的匿名方法,但是如果你只需要清理它们,这个效果很好。 设置someClass.SomeEvent = null 比迭代 InvocationList 更容易。【参考方案5】:

您需要一个匿名函数的名称,然后,只要名称在范围内,您就可以这样做:

    var handler = new EventHandler(delegate(object o, EventArgs e)
    
        //do something...
    ;

    Subject.NewEvent += handler;

    // later on while handler is still in scope...

    Subject.NewEvent -= handler;

【讨论】:

【参考方案6】:

您是否需要出于泄漏以外的原因取消注册?

关于“或者只是允许内存泄漏”位,当垃圾收集器清理主题时,你的匿名委托也应该被清理,所以不应该有泄漏。

【讨论】:

内存泄漏是一个原因,另一个原因可能是我只想停止监听事件 那么你必须存储它,正如 dtb 的回答所暗示的那样 不幸的是,这可能会导致泄漏。只要 "Subject" 仍然有根,"this" 就永远不会被收集,因为 Subject.NewEvent 后面的委托将持有对 "this" 的强引用,直到 Subject 没有根。 WeakEvent 模式的存在正是出于这个原因。 @Reed:啊,所以如果你在匿名委托中使用“this”,那么它会创建一个循环引用(对象 委托)来阻止垃圾收集器清理?是这个意思吗? 不,GC 可以很好地处理循环引用。但是,如果 Subject 永远不会被 GC 处理(例如,它可能是在应用程序范围内存在的“MainFrame”),那么匿名委托和委托中隐含的“this”也不会。【参考方案7】:

还有一个(我的)问题在一些(太多)细节中涉及到这个问题:Weak event handler model for use with lambdas。

但是,既然Reactive Framework 已经问世,我会认真考虑在这种情况下进行调查。

【讨论】:

以上是关于如何取消注册“匿名”事件处理程序 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何使用推送通知处理注销事件?

通过匿名委托取消订阅事件[重复]

jQuery 多事件处理程序 - 如何取消?

jquery 事件,注册 与重复事件处理

Java学习笔记7.2.1 事件处理 - Swing事件处理机制

Java学习笔记7.2.1 事件处理 - Swing事件处理机制