我是否必须取消订阅本地变量的匿名事件处理程序?

时间:2011-02-22 17:30:53

标签: c# .net events event-handling

如果我的代码看起来像这样:

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

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

    bar.DoSomeOtherThingAndRaiseSomeEvent();
}

当方法用完范围时会收集bar,还是我必须手动取消订阅事件以防止由于引用SomeEvent导致的内存泄漏?

3 个答案:

答案 0 :(得分: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) => { };
    }
}

在这种情况下,Bar内创建的LeakMemory实例无法收集

  • lambda表示的匿名方法将从FooEvent的调用列表中删除
  • 可以收集附加的Foo实例

这是因为事件(它只是普通delegate实例上的一些语法糖)保存在调用它时要调用的委托列表,而这些委托中的每一个都依次引用它所附加的对象(在本例中是Bar的实例)。

请注意,我们这里只谈论集合资格。只是因为它符合条件并没有说明什么何时(甚至,真的,如果)它将被收集,只是它可以

答案 1 :(得分:1)

嗯,引用的对象bar不会立即自动垃圾收集......只是bar变量不会阻止垃圾收集

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

假设没有其他任何东西让你的Bar实例保持活着,那么你的代码应该没问题。

答案 2 :(得分:1)

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

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;