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

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

public void Foo() { Bar bar = new Bar(); bar.SomeEvent += (sender, e) => { //Do something here }; bar.DoSomeOtherThingAndRaiseSomeEvent(); } 

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

你的情况很好; 事件订阅者不会阻止收集发布者 ,但可能发生相反的情况。

例如,

 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的实例)。

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

好吧,对象bar引用不会立即自动垃圾收集…只是bar变量不会阻止它被垃圾收集。

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

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

以上答案是正确的; 我只想做个便笺。 如果您保留对委托/ 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;