There are two ways (that I know of) to cause an unintentional memory leak in C#:
IDisposable
Read Jon Skeet's excellent article on events. It's not a true "memory leak" in the classic sense, but more of a held reference that hasn't been disconnected. So always remember to -=
an event handler that you +=
at a previous point and you should be golden.
There are, strictly speaking, no "memory leaks" within the "sandbox" of a managed .NET project; there are only references held longer than the developer would think necessary. Fredrik has the right of it; when you attach a handler to an event, because the handler is usually an instance method (requiring the instance), the instance of the class containing the listener stays in memory as long as this reference is maintained. If the listener instance contains references to other classes in turn (e.g. backreferences to containing objects), the heap can stay quite large long after the listener has gone out of all other scopes.
Maybe someone with a bit more esoteric knowledge of Delegate and MulticastDelegate can shed some light on this. The way I see it, a true leak COULD be possible if all of the following were true:
I've never heard of any best practice involving calling Dispose() on delegate Targets, much less event listeners, so I can only assume the .NET developers knew what they were doing in this case. If this is true, and the MulticastDelegate behind an event tries to properly dispose of listeners, then all that is necessary is proper implementation of IDisposable on a listening class that requires disposal.
When a listener attaches an event listener to an event, the source object will get a reference to the listener object. This means that the listener cannot be collected by the garbage collector until either the event handler is detached, or the source object is collected.
Consider the following classes:
class Source
{
public event EventHandler SomeEvent;
}
class Listener
{
public Listener(Source source)
{
// attach an event listner; this adds a reference to the
// source_SomeEvent method in this instance to the invocation list
// of SomeEvent in source
source.SomeEvent += new EventHandler(source_SomeEvent);
}
void source_SomeEvent(object sender, EventArgs e)
{
// whatever
}
}
...and then the following code:
Source newSource = new Source();
Listener listener = new Listener(newSource);
listener = null;
Even though we assign null
to listener
, it will not be eligible for garbage collection, since newSource
is still holding a reference to the event handler (Listener.source_SomeEvent
). To fix this kind of leak, it is important to always detach event listeners when they are no longer needed.
The above sample is written to focus on the problem with the leak. In order to fix that code, the easiest will perhaps be to let Listener
hold on to a reference to Source
, so that it can later detach the event listener:
class Listener
{
private Source _source;
public Listener(Source source)
{
_source = source;
// attach an event listner; this adds a reference to the
// source_SomeEvent method in this instance to the invocation list
// of SomeEvent in source
_source.SomeEvent += source_SomeEvent;
}
void source_SomeEvent(object sender, EventArgs e)
{
// whatever
}
public void Close()
{
if (_source != null)
{
// detach event handler
_source.SomeEvent -= source_SomeEvent;
_source = null;
}
}
}
Then the calling code can signal that it is done using the object, which will remove the reference that Source
has to ´Listener`;
Source newSource = new Source();
Listener listener = new Listener(newSource);
// use listener
listener.Close();
listener = null;