When does these unsubscribed events memory leak occurs? Should I write destructor or implement IDisposable to unsubscribe an event?
问题:
回答1:
Let's say that A references B. Furthermore, say you think you're done with B and expect it to be garbage collected.
Now, if A is reachable[1], B won't be garbage collected, despite the fact that "you're done with it". This is, in all essence, a memory leak[2]
If B subscribes to an event in A, then we have the same situation: A has a reference to B via the event handler delegate.
So, when is this a problem? Only when the referencing object is reachable, as mentioned above. In this case, there can be a leak when a Foo instance isn't used any longer:
class Foo
{
Bar _bar;
public Foo(Bar bar)
{
_bar = bar;
_bar.Changed += BarChanged;
}
void BarChanged(object sender, EventArgs e) { }
}
The reason why there can be a leak is that the Bar instance passed in the constructor can have a longer lifetime than the Foo instance using it. The subscribed event handler can then keep the Foo alive.
In this case you need to provide a way to unsubscribe from the event to not get a memory leak. One way of doing that is by letting Foo implement IDisposable. The upside of that is that it clearly signals to the class consumer that he need to call Dispose() when done. Another way is to have separate Subscribe() and Unsubscribe() methods, but that doesn't convey the type's expectations - they are too optional to call and introduce a temporal coupling.
My recommendation is:
class sealed Foo : IDisposable
{
readonly Bar _bar;
bool _disposed;
...
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_bar.Changed -= BarChanged;
}
}
...
}
Or alternatively:
class sealed Foo : IDisposable
{
Bar _bar;
...
public void Dispose()
{
if (_bar != null)
{
_bar.Changed -= BarChanged;
_bar = null;
}
}
...
}
On the other hand, when the referencing object isn't reachable, there can't be a leak:
class sealed Foo
{
Bar _bar;
public Foo()
{
_bar = new Bar();
_bar.Changed += BarChanged;
}
void BarChanged(object sender, EventArgs e) { }
}
In this case any Foo instance will always outlive its composed Bar instance. When a Foo is unreachable, so will its Bar be. The subscribed event handler cannot keep the Foo alive here. The downside of this is that if Bar is a dependency in need of being mocked in unit testing scenarios, it can't (in any clean way) be explicitly instantiated by the consumer, but needs to be injected.
[1] http://msdn.microsoft.com/en-us/magazine/bb985010.aspx
[2] http://en.wikipedia.org/wiki/Memory_leak
回答2:
An event handler contains a strong reference to the object that declares the handler (in the delegate's Target
property).
Until the event handler is removed (or until the object owning the event is no longer referenced anywhere), the object containing the handler will not be collected.
You can fix this by removing the handler when you no longer need it (perhaps in Dispose()
).
A finalizer cannot help, because the finalizer would only run after it can be collected.