



Introduction
This article discusses some of the interplay between two important features of .NET, Event Handling and Garbage Collection. It examines how the use of events and event handlers helps determine an object's lifetime. The article also looks at what the potential repercussions are when two objects have a mutual dependency of event handling, i.e. they each have an event and also a handler attached to the other's event. Two strategies are considered for how this mutual dependency should be handled and a recommendation given as to which one is the preferred solution.
This article assumes that the reader is familiar with the Event Handling and Garbage Collection in .NET.
The code sample with this article is C#, but the points are as relevant to VB.NET.
Background
Event Handling allows software to implement asynchronous behaviour. The objects that raise the event and the objects that react to it are isolated from each other and can be designed and built independently. It is possible for two objects to form a mutual event handling relationship with each attaching an event handler to the other's event. However, implementing a relationship like this can have unintended consequences both for how the event handling behaves and the lifetime of the objects concerned in terms of when the objects are deemed to be unreferenced by the Garbage Collector and eligible to be removed from the managed heap.
I became interested in this subject when some code I was working on exhibited some unexpected behaviour. It took me quite a while to puzzle out what was happening when event handlers were being called more times than they should have been and objects that I thought had been unreferenced, and hence no longer active, were very much alive and kicking. The code samples I give with this article reflect what I had in real life in terms of essential structure, but stripped down to what is relevant to the point in hand. I have also added a couple of embellishments where necessary to help identify what is happening.
The Code
The sample code is a simple Windows Form application. There are two main classes, Form1
, which is the form itself, and the EventGenerator
class. Both Form1
and EventGenerator
have an event member and a method that can be attached to the other's event to handle it. EventGenerator
has a private _string
member which is set to the value of the string passed into the constructor. This _string
acts as a signature to uniquely identify every instance of EventGenerator
.
Here is the code for Form1
.
public partial class Form1 : Form
{
private EventGenerator _eventGenerator;
private int _counter;
public delegate void FormEventHandler(object o, EventArgs e);
public event FormEventHandler FormEvent;
public Form1()
{
InitializeComponent();
}
private void handlerMethod(object o, EventArgs e)
{
listBox1.Items.Add("handlerMethod Called: " + e.ToString());
}
private void Generate_Click(object sender, EventArgs e)
{
if (_eventGenerator != null)
{
if (radioRemove.Checked)
FormEvent -= _eventGenerator.theFormHandlerMethod;
else if (radioDispose.Checked)
_eventGenerator.Dispose();
}
listBox1.Items.Clear();
_eventGenerator = new EventGenerator
("Creating eventGenerator: " + _counter.ToString());
++_counter;
FormEvent += _eventGenerator.theFormHandlerMethod;
_eventGenerator.Event += handlerMethod;
FormEvent(null, null);
foreach (string s in FinalizeRegister.Messages)
{
listBox1.Items.Add(s);
}
}
}
The code for EventGenerator
is here:
public sealed class EventGenerator : IDisposable
{
public delegate void EventHandler(object o, EventArgs e);
public event EventHandler Event;
private string _string;
public EventGenerator(string s)
{
_string = s;
}
~EventGenerator()
{
FinalizeRegister.AddMessage("Finalized called for " + _string);
}
public void theFormHandlerMethod(object o, EventArgs e)
{
if (Event != null)
Event(null, new DerivedEventArgs(_string));
}
public void Dispose()
{
Event = null;
}
}
There are also two additional classes:
DerivedEventArgs
, is as you might think, derived from EventArgs
, it has a string as a private
member which is set in its constructor. Its inherited 'ToString
' method is overridden to return the value of this string member.
public sealed class DerivedEventArgs : EventArgs
{
private string _string;
public DerivedEventArgs(string s)
{
_string = s;
}
public override string ToString()
{
return _string;
}
}
The FinalizeRegister
is a static
class that wraps a list of string
s. It has an 'AddMessage
' method to add a string
to the list and a read only 'Messages
' property to return the list.
public static class FinalizeRegister
{
private static List<string> _messages = new List<string>();
public static void AddMessage(string s)
{
_messages.Add(s);
}
public static List<string> Messages
{
get { return _messages; }
}
}
EventGenerator
implements a Finalizer. In the Finalizer, it adds its _string
member to the FinalizeRegister
.
Running The Software
If you ran the software, you would see that the Form looks like this:
The code behind the 'Generate
' button is like this:
private void Generate_Click(object sender, EventArgs e)
{
if (_eventGenerator != null)
{
if (radioRemove.Checked)
FormEvent -= _eventGenerator.theFormHandlerMethod;
else if (radioDispose.Checked)
_eventGenerator.Dispose();
}
listBox1.Items.Clear();
_eventGenerator = new EventGenerator
("Creating eventGenerator: " + _counter.ToString());
++_counter;
FormEvent += _eventGenerator.theFormHandlerMethod;
_eventGenerator.Event += handlerMethod;
FormEvent(null, null);
foreach (string s in FinalizeRegister.Messages)
{
listBox1.Items.Add(s);
}
}
Ignore the first few lines that deal with the state of the Program Mode radio buttons for the moment. The code does the following:
- Clears the contents of the
ListBox
. - Creates a new instance of the
EventGenerator
class and assigns it to the _eventGenerator
reference. - The
EventGenerator
constructor is passed a string which will be unique because it contains the value of the _counter
variable which is incremented each time this method is run. _eventGenerator's
'theFormHandlerMethod
' is attached to the Form1's
'FormEvent
' event.Form1's
'handlerMethod
' is attached to _eventGenerator
's Event method.- The
FormEvent
method is raised. - Every message in the
FinalizeRegister
is added to the ListBox
.
An important point to note is that when a new EventGenerator
is assigned to _eventGenerator
, the previous instance of EventGenerator
is no longer referenced. One might expect it to be no longer active and to be sitting quietly for the Garbage Collector to clean it up. As we will see later, this is not the case.
Let's have a look at how the event handling works. As we have seen, Form1
's event is raised when the 'Generate
' button of the form is clicked. The EventGenerator
handles this with theFormHandlerMethod
.
public void theFormHandlerMethod(object o, EventArgs e)
{
if (Event != null)
Event(null, new DerivedEventArgs(_string));
}
It checks to see if handlers are attached to the Event method (an important check as you will get a NullException
thrown if there are no attached handlers) and then raises its own Event. When it raises the Event, it creates an instance of DerivedEventArgs
passing in its own private _string
member as a parameter to the constructor.
The control will then pass back to Form1
which will handle EventGenerator's
Event here:
private void handlerMethod(object o, EventArgs e)
{
listBox1.Items.Add("handlerMethod Called: " + e.ToString());
}
The EventArg
's ToString
method is called and added to the ListBox
control. This will be the string which uniquely identifies the instance of the EventGenerator
.
If we run the Form and click generate a few times, we should get something like this:
Remember that each time you click 'Generate', the ListBox
is cleared. You might expect that if you had clicked the button five times only, the fifth EventGenerator
object to be active and only 'handlerMethod
called: Creating eventGenerator
: 5' to be in the ListBox
. The first four instances of EventGenerator
should be no longer active as they are no longer referenced by Form1's
_eventGenerator
reference. But, what this code shows is that not only are these instances still active, but they are also handling Form1
's event to which they have a handler attached. Although these objects are no longer referenced, the fact that they have handlers still attached to the vent of an active object, Form1
, means that they themselves are still active. As they are active, they can still handle events that they are attached to and they will not be Garbage Collected.
This is probably not what we expect or what we want!
A couple of solutions to this are examined:
Solution 1 - Breaking the Event Handling on EventGenerator's Side
EventGenerator
implements the IDisposable
interface. Anything that implements IDisposable
has a 'Dispose
' method that allows a bit of tidying up to occur and resources to be freed. EventGenerator's
'Dispose
' is like this:
public void Dispose()
{
Event = null;
}
The Event is set to null
.
Look again at the first lines of code behind Form1
's Generate button:
if (_eventGenerator != null)
{
if (radioRemove.Checked)
FormEvent -= _eventGenerator.theFormHandlerMethod;
else if (radioDispose.Checked)
_eventGenerator.Dispose();
}
As you can see, if the 'Dispose
' radio button in the form is selected then _eventGenerator's Dispose
method is called before the reference is reused on a new instance of EventGenerator
.
Run the application again, but this time select the 'Dispose
' radio button. Click the 'Generate
' button a few times and you will get something like this.
As you can see, Form1
is now only handling the event generated by the most recent instance of EventGenerator
. All the previous instances are silent. This is nearer to what we want, but, and this is a big but, the code is still wrong. Remember that EventGenerator
has a Finalizer in which it adds its unique string to the FinalizeRegister
object. Form1
will add the contents of FinalizeRegister
to the ListBox
each time 'Generate' is clicked. However many times you click 'Generate' when the application is in 'Dispose' mode the unreferenced EventGenerator
objects are not having their 'Finalize
' methods called and, hence, we know that they are not being Garbage Collected. This could be quite serious if the memory they consume on the managed heap is never released. This is not what we want!
Solution 2: Breaking event handling on Form1's Side
If the 'Remove Event Handler' radio button is selected, then Form1
's event handler is removed from _eventGenerator's
event.
Run the application again with 'Remove Event Handler' selected.
Click 'Generate' for a while and you will see that only the current EventGenerator
object is raising events, which is what you want, and that eventually the Garbage Collector steps in and cleans up the unreferenced EventGenerator
objects. Note that Finalizers are called in no particular order, which illustrates the indeterminate nature of Garbage Collection.
It is only by removing all the event handlers attached to an object's events that object is considered by the Garbage Collector
to be unreferenced.
Conclusion
A mutual dependence between objects for Event Handling can have unintended consequences. Even when an instance of an object has no references to it, it will still handle any events on which it itself is attached. It will also not be a candidate for Garbage Collection. Breaking the dependency on the side of the referenced object is only a partial solution. Removing event handlers attached to events on the object that is about to be unreferenced is the correct approach. Not only will these objects no longer handle events they will also be Garbage Collected. May they Rest In Peace!
History
- First draft: 14th November 2009