如果我的代码看起来像这样:
public void Foo()
{
Bar bar = new Bar();
bar.SomeEvent += (sender, e) =>
{
//Do something here
};
bar.DoSomeOtherThingAndRaiseSomeEvent();
}
当方法用完范围时会收集bar
,还是我必须手动取消订阅事件以防止由于引用SomeEvent
导致的内存泄漏?
答案 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
实例无法收集
FooEvent
的调用列表中删除这是因为事件(它只是普通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;