我需要取消本地变量的匿名事件处理程序吗?

12

如果我有如下代码:

public void Foo()
{
    Bar bar = new Bar();

    bar.SomeEvent += (sender, e) =>
    {
        //Do something here
    };

    bar.DoSomeOtherThingAndRaiseSomeEvent();
}

当方法运行超出范围时,bar是否会被收集,或者我是否需要手动取消对 SomeEvent 的引用以防止内存泄漏?

3个回答

18

您的情况没问题;事件订阅者不会阻止发布者被垃圾回收,但反过来可能会发生。

例如,

class Foo
{
    public event EventHandler FooEvent;

    void LeakMemory()
    {
        Bar bar = new Bar();

        bar.AttachEvent(this);
    }
}

class Bar
{
    void AttachEvent(Foo foo)
    {
        foo.FooEvent += (sender, e) => { };
    }
}

在这种情况下,在LeakMemory中创建的Bar实例只有在以下情况之一发生时才能被回收:

  • 表示lambda的匿名方法已从FooEvent的调用列表中移除
  • 它所附加到的Foo实例可以被回收

这是因为事件(它只是普通delegate实例上的一些语法糖)保存了一个委托列表,以便在调用事件时调用每个委托。而每个委托又转而引用它所附加到的对象(在这种情况下,是Bar实例)。

请注意,我们只谈论收集的资格。仅凭其资格并不意味着它会在何时(或甚至是否)被回收,只是说它可以被回收。


有没有“弱引用事件”的标准形式或习语? - user166390
@pst:很遗憾,不行。这是之前讨论过的问题(更普遍地说,只考虑委托中部分引用的情况),但是复杂性和潜在的陷阱迄今为止阻止了它的实现。自从我上次查看以来可能已经对这些计划进行了更改,但据我所知,事情就是这样,并将继续下去。即使使用WeakReference类的解决方案也只是将问题移动到另一种类型。 - Adam Robinson

1
以上答案是正确的,我只想做一个注释。用作处理程序的匿名委托只有在保留对委托/lambda的其他引用时才能取消订阅。这是因为lambda是“函数文字”,有点像字符串文字,但与字符串不同,它们在确定相等性时不进行语义比较:
public event EventHandler MyEvent;

...

//adds a reference to this named method in the context of the current instance
MyEvent += Foo;

//Adds a reference to this anonymous function literal to MyEvent
MyEvent += (s,e) => Bar();

...

//The named method of the current instance will be the same reference
//as the named method.
MyEvent -= Foo;

//HOWEVER, even though this lambda is semantically equal to the anonymous handler, 
//it is a different function literal and therefore a different reference,
//which will not match the anonymous handler.
MyEvent -= (s,e) => Bar();

var hasNoHandlers = MyEvent == null; //false

//To successfully unsubscribe a lambda, you have to keep a reference handy:

EventHandler myHandler = (s,e) => Bar();

MyEvent += myHandler;

...

//the variable holds the same reference we added to the event earlier,
//so THIS call will remove the handler.
MyEvent -= myHandler;

1

好的,对象bar不会立即被自动垃圾回收...只是bar变量不会阻止它被垃圾回收。

事件处理程序也不会阻止Bar实例被垃圾回收——“正常”的问题是事件处理程序会阻止事件的订阅者被垃圾回收(如果它使用实例方法或在匿名函数中捕获“this”)。通常不会影响发布者被垃圾回收。只需记住,发布者需要保留对所有订阅者的引用——订阅者不需要记住自己订阅了什么,除非它明确想要取消订阅或稍后使用其他成员。

假设没有其他东西使您的Bar实例保持活动状态,那么您的代码应该没问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接