Introduction
While moving from C++/MFC to C#/Windows Forms has been a very positive experience for me, it didn't quite live up to the expectation I had that I could send objects in all directions and never have to worry about deleting them when they are no longer required. The problem is, when you add an event handler to an event, the event will hold a reference to the object that contains the event handler, and so long as that reference is there the garbage collector can't collect the object containing the event handler. Thus while the need to call delete
is gone, excessive use, on my behalf, of events has led me to a point where I need to track when objects are no longer required so that I can remove their event handlers from the event that they are servicing, and thus enable them to be garbage collected.
For a couple of years, I have been using many workarounds for this problem, including changing my coding style to reduce the incidence of this problem, and keeping lists of weak references of delegates that I iterate over to simulate events, but I finally I decided to sit down and write a simple-to-use solution to this problem that would allow me to use events in the way that I want to.
Background
I am not the first person to try to solve this problem. Microsoftie Greg Schechter has a very inspiring article on his blog about the problem in general, and the solution that he came up with. While the solution was good, it just didn't quite fill my needs as it required changing my existing codebase too much.
Ian Griffiths of Interact Software Ltd. UK had exactly the sort of solution I was looking for on his blog but unfortunately it didn't quite work. Tricky problem this one.
Wesner Moise mentions in his blog that he talked about weak delegates with the .NET Bass Class Library team when he had dinner with them back in March 2005, but I haven't seen any mention anywhere of when such a feature would be implemented so I've decided to go ahead with my own solution. He also mentions on the same page that he has seen two experts screw up an implementation of weak delegates, so here's hoping, with all the possible scrutiny that this article might get, that I haven't added my name to the list of people who have screwed this up.
Using the Code
I have two ways of using weak event handlers depending on whether you need speed, or simplicity. The simple way to use weak event handlers is to use the WeakEventHandlerFactory
class which requires that you create one instance of the WeakEventHandlerFactory
in your class:
private WeakEventHandlerFactory eventHandlers;
And then add as many event handlers as you want, to any event source, using the AddWeakHandler
method:
eventHandlers.AddWeakHandler<MyEventArgs>(eventSource,
"EventSourceChanged", eventSource_EventSourceChanged);
To remove an event handler, use RemoveWeakHandler
:
eventHandlers.RemoveWeakHandler<MyEventArgs>(eventSource,
"EventSourceChanged", eventSource_EventSourceChanged);
Not really too far removed from the code that is normally used to add and remove event handlers, is it? It should be noted that a new feature of the C# 2.0 compiler is that it will convert a function name to an event handler, so eventSource_EventSourceChanged
will be converted to new EventHandler<MyEventArgs>(eventSource_EventSourceChanged)
.
When your object is garbage collected, the WeakEventHandlerFactory
will get garbage collected around the same time, and its finalizer will remove all of the event handlers that it has created that haven't been removed yet. It doesn't matter if there is a delay between your object being garbage collected and the event handler factory being garbage collected as the event handler factory will not attempt to pass on events after your object has been garbage collected.
The caveat with using the event handler factory is that it is 40x slower than adding or removing normal event handlers. For some situations this is unacceptable, so I have created a WeakEventHandler<>
class that requires a little more code to use, but in fact ends up being 2x faster to add and remove when compared to normal event handlers. To use Weak Event Handlers directly a little more code and care is required. For each event handler in your class, you need to have an object like the following in your class declaration:
private WeakEventHandler<MyEventArgs> eventSourceChangedHandler;
Then in your constructor you initialize the WeakEventHandler<>
object in the following way:
eventSourceChangedHandler = new
WeakEventHandler<MyEventArgs>(eventSource_EventSourceChanged);
This is similar to the way you initialize a normal event handler, except that you need to keep an instance of the WeakEventHandler<>
in your class. To use the WeakEventHandler<>
object, simply add it or remove it as you would a normal event handler:
eventSource.EventSourceChanged += eventSourceChangedHandler;
eventSource.EventSourceChanged -= eventSourceChangedHandler;
The difference though is that you must remove the event handler before your object has been finalized otherwise your application will bomb with a null reference exception next time the event source fires an event that is handled by a WeakEventHandler<>
in your object. Therefore your finalizer needs to remove the WeakEventHandler<>
like so:
~MyClass
{
eventSource.EventSourceChanged -= eventSourceChangedHandler;
}
Under the Hood
I've written four utility classes for creating Weak Event Handlers:
WeakReferenceToEventHandler
WeakEventHandlerInternal
WeakEventHandlerFactory
WeakEventHandler
WeakReferenceToEventHandler
This class is exactly what the class name says. It is a weak reference to an event handler. This class is used by the WeakEventHandlerInternal
only, and there should not be any need to use it directly.
This class contains methods to add an event handler to the event source, and remove an event handler from the event source. All the required details such as the event source object and the event name are stored in this class so that it can remove itself from the event source should the original event handler be garbage collected.
As events can't be passed in through methods, the AddHandler
method uses reflection to add an intermediate event handler to the event source:
public void AddHandler(object eventSource, string eventName)
{
this.eventName = eventName;
weakReferenceToEventSource = new WeakReference(eventSource);
EventInfo eventInfo = eventSource.GetType().GetEvent(eventName);
eventInfo.AddEventHandler(eventSource,
new EventHandler<TEventArgs>(IntermediateEventHandler));
}
Similarly, the RemoveHandler
method uses reflection to remove the intermediate event handler:
public void RemoveHandler()
{
if (weakReferenceToEventSource==null)
return;
object eventSource = weakReferenceToEventSource.Target;
if (eventSource != null)
{
EventInfo eventInfo = eventSource.GetType().GetEvent(eventName);
eventInfo.RemoveEventHandler(eventSource,
new EventHandler<TEventArgs>(IntermediateEventHandler));
}
}
When the event gets fired, the intermediate event handler handles it. It's at this point that a test is done to see if the original event handler has been garbage collected, and if so then the intermediate event handler removes itself from the event source.
WeakEventHandlerInternal
The WeakEventHandlerInternal
class is used internally for storing weak event handlers. This class should not be used directly, so I made the constructor internal
. I have a class called WeakEventHandler
to be used directly that is 8x faster than WeakEventHandlerInternal
for adding and removing events. The WeakEventHandlerInternal
class is a container for the strong reference to the original event handler, and also contains an instance of the WeakReferenceToEventHandler
class. It contains methods for adding, removing, and comparing weak event handlers for equality. It is used by the WeakEventHandlerFactory
class.
WeakEventHandlerFactory
The WeakEventHandlerFactory
is the class that can be used to create weak event handlers. It contains two methods:
AddWeakHandler
RemoveWeakHandler
The AddWeakHandler
method creates a weak reference to the original event handler, but also stores a strong reference to the event handler so that it isn't garbage collected as soon as we leave the method. The weak reference and strong reference are stored together in an object that is added to an internal list.
The RemoveWeakHandler
does a search through all the stored event handlers looking for a match, and removes the event handler if it finds a match. This method returns a flag that indicates if it was successful or not.
WeakEventHandler
This class can be used directly, but cautiously, for directly creating weak event handlers. It sacrifices ease of use for speed. An instance of WeakEventHandler
must be held for each event handler that is represented by a WeakEventHandler
object, and each WeakEventHandler
object must be removed from the source event before the event handling object is garbage collected.
Using the Sample Application
If you compile the project linked in at the top of this article, you will get an application that looks like the image at the top. This application demonstrates the usefulness of a weak event handler.
The application has three DataGrid
controls that are used for editing the contents of up to three collections. Above each DataGrid
is a unique identifier for the collection being edited in that DataGrid
, as well as the total of all the values held in that collection.
If you edit one of the collection items, the total displayed above the DataGrid
will be updated. If you remove one of the items, you will see a message in the log window indicating that the event handler was successfully removed from the item that you removed from the collection. From this you can deduce that each item in the collection has an event that fires when you change the item. Thus each item has a reference to its parent collection(s) as it is the collection that handles the event and updates the value of the total held in the collection object. The purpose of this application is to show that a weak reference to the event handler is used allowing a container of objects (the collection) to be garbage collected if it is no longer required, even if the items in the collection still exist.
Now for the fun part. Below each DataGrid
are four buttons. They allow you to display, or copy one of the other two collections.
When you copy a collection, a new collection is created and the objects from the source collection are copied into it. Hitting the "Force Garbage Collection" button will force the replaced collection to be garbage collected if it is no longer being displayed anywhere, and the log window will show a message indicating which collection was garbage collected.
Editing an item in a copied collection will update the collection it was copied from, but items added to or removed from the new collection will have no effect on the source collection.
When you "View a Set", the actual collection itself will then be displayed in the destination DataGrid
, as well as the source DataGrid
. Actions of adding/deleting/editing objects in one DataGrid
will be mirrored in the other DataGrid
.
Speed Testing
The sample application includes a little benchmark to show how fast different event handler types can be added and removed. I tested adding and removing 1,000,000 event handlers on an old 3.0GHz Pentium 4 and got the following results:
Speed Test: Adding and Removing 1000000 Event Handlers
Adding Normally.........Seconds: 0.5781361
Removing Normally.........Seconds: 0.3906325
Adding WeakEventHandler.........Seconds: 0.156253
Removing WeakEventHandler.........Seconds: 0.2343795
Adding WeakEventHandlerInternal.........Seconds: 2.2812938
Removing WeakEventHandlerInternal.........Seconds: 1.9687878
Adding using WeakEventHandlerFactory.........Seconds: 19.7191286
Removing using WeakEventHandlerFactory.........Seconds: 17.1253288
In the sample application I have set the number of event handlers to add and remove to 100,000 so that you don't have to wait too long to see your own results.
Sample Code Included in the Sample Application
There are two collection classes included in the source code for the sample application:
SubTotalCollection
SubTotalCollectionFast
SubTotalCollection
uses the EventHandlerFactory
to create weak event handlers, and SubTotalCollectionFast
uses the WeakEventHandler
class directly. SubTotalCollectionFast
is a good example of managing WeakEventHandler
objects. It adds a handler when a SubTotal
object is added to the collection, it removes a handler when a SubTotal
object is removed from the collection, and when it is finalized or cleared it removes event handlers from all the SubTotal
objects still in the collection.
The sample application just uses the SubTotalCollection
class. To make it use the SubTotalCollectionFast
class, just do a search and replace in the Form1.cs file replacing SubTotalCollection
with SubTotalCollectionFast
.
Points of Interest
The new DataGrid
control is a lot easier to use than the old one, for editing simple collections. In this sample application, I have objects of type SubTotal
in a collection that I want to edit in a DataGrid
. There were three steps to doing this. The first was declaring a collection class that inherited from BindingList
, like so:
public class SubTotalCollection : BindingList<SubTotal>
Then in Form.cs I created a binding source, attached it to the DataGrid
, and attached a collection to the binding source, and that was it:
BindingSource leftSource = new BindingSource();
dataGridViewLeft.DataSource = leftSource;
leftSource.Source = new SubTotalCollection();
Between this version and the first version posted on September 23rd, 2005, I had many attempts at making the factory faster. After putting in a Dictionary
for finding matching event handlers in the RemoveHandler
method I tried changing it to SortedDictionary<,>
, however for this purpose the SortedDictionary<,>
seemed to take twice as long as using the Dictionary<,>
class.
History
- Original article submitted on September 23rd, 2005.
- October 13th 2005 - improved speed, added more comments, added faster
WeakEventHandler
class. - March 7th 2007 - fixed a bug in the faster
WeakEventHandler
class found by Stéphane Issartel.
John graduated from the University of South Australia in 1997 with a Bachelor of Electronic Engineering Degree, and since then he has worked on hardware and software in many fields including Aerospace, Defence, and Medical giving him over 10 of years experience in C++ and C# programming. In 2009 John Started his own contracting company doing business between Taiwan and Australia.