One C#, but that’s another story. Back to event handlers.
A while back when I was working on ANTS Profiler, I chatted to a few customers using the memory profiler, a lot of whom were struggling to work out why their applications apparently had memory leaks. A lot of the time it turned out to be event handlers not getting unhooked, and stopping objects (usually user controls) getting garbage collected as a result.
Fast forward to now, and I’m currently working with a COM object model that also has events on it. .NET does a pretty good job of generating COM Interop wrappers, and so you can pretty much use these as if they were .NET objects. However, there’s a few gotchas when it comes to event handlers, as we’ll see in a moment.
First, let’s have a quick recap of the way event handlers tie in with the garbage collector when we’re in managed code. Here’s a quick bit of sample code:
public class ClassWithEvent
{
public event EventHandler Evt;
public void Poke()
{
if (Evt != null)
Evt(this, new EventArgs());
}
~ClassWithEvent()
{
Console.WriteLine(“Being garbage collected”);
}
}
public class OtherClass
{
public static void Main()
{
WeakReference wr = MakeCWE();
GC.Collect();
GC.WaitForPendingFinalizers();
if (wr.IsAlive)
Console.WriteLine(“CWE still alive after GC!”);
else
Console.WriteLine(“CWE died in a garbage collection :-(“);
Console.WriteLine(“nPress enter to continue…”);
Console.ReadLine();
}
static WeakReference MakeCWE()
{
ClassWithEvent cwe = new ClassWithEvent();
cwe.Evt += new EventHandler(cwe_Evt);
cwe.Poke();
return new WeakReference(cwe);
}
static void cwe_Evt(object sender, EventArgs e)
{
Console.WriteLine(“Event fired!”);
}
}
What’s going on here? The main entry point, OtherClass.Main(), calls MakeCWE() to create a new instance of ClassWithEvent, and hook in an event handler to its event. We check that the event fires, and then return a WeakReference to that object. The idea of the WeakReference is that we can see whether the object is still alive, without preventing it from being garbage collected. (An aside: we need to create the object in its own method, so the local variable “cwe” goes out of scope before we run the garbage collection.)
Running the code shows the object being created, the event fired, and then when the GC runs, the ClassWithEvent object gets collected. This means the event handler we hooked in is now disconnected. A little surprising at first maybe, but thinking about it, if there are no other references to the ClassWithEvent object, there’s no way it can fire its event, so there’s no need to keep the event handler connected.
Changing the code slightly, and adding an event on OtherClass, and causing ClassWithEvent to hook up to this event in its constructor, changes this significantly. At this point, it can’t get garbage collected any more, because OtherClass’s event may still fire, causing “stuff” to happen within ClassWithEvent.
This is exactly what tends to bite developers working with Winforms – a user control is created, hooks itself into the some of the parent Form’s events, and some time later, it gets discarded by removing it from the Form’s Controls collection. However, the user control is still hooked into the Form’s events, so even though you’ve no way of getting at the object, it’s still sitting there in memory, unable to be garbage collected until the Form is. The solution here is to unhook any event handlers in a Dispose method, and call that Dispose method after removing the control from the Form.
So far so good. But what happens when you aren’t using nice .NET objects, but rather COM objects?
I had one object, let’s say Foo, which I kept a reference to throughout the life of my application. It had a property, Bar, which returned an object that had an event on it. So, I got my Foo, and did something like:
foo.Bar.Evt += Bar_Evt
And everything worked nicely, for a while. But a little later, the event would mysteriously stop firing. Some head scratching later, it turned out that this mystery happened whenever a garbage collection ran, and that if I kept a reference to the value of the Bar property in a member variable, the event would stay around.
When you get a reference to a COM object in .NET, you actually get a wrapper around it. So if you access one of its properties which returns another COM object, you get a wrapper around that, and so on. The wrapper around the Foo object doesn’t itself reference the wrapper around the Bar object, even though the Foo COM object does reference the Bar COM object. Phew.
Now, as we saw earlier, just having an event handler hooked up to an object doesn’t stop that object being garbage collected. What this means is that my Bar wrapper was being garbage collected, there being no references to it any more, even though the underlying COM object was still there…
With hindsight, everything makes sense, and you can see why it happens like it does. Unfortunately, that wasn’t enough to stop me being rather confused for a while yesterday!
Load comments