Click here to Skip to main content
15,867,330 members
Articles / Desktop Programming / Windows Forms
Article

An Easy to Use Weak Referenced Event Handler Factory for .NET 2.0

Rate me:
Please Sign up or sign in to vote.
4.84/5 (19 votes)
9 Mar 200710 min read 151.5K   931   87   40
An article on creating weak event handlers that show how to use them in a Windows Form.

A picture of the sample application running

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:

C#
private WeakEventHandlerFactory eventHandlers;

And then add as many event handlers as you want, to any event source, using the AddWeakHandler method:

C#
eventHandlers.AddWeakHandler<MyEventArgs>(eventSource, 
              "EventSourceChanged", eventSource_EventSourceChanged);

To remove an event handler, use RemoveWeakHandler:

C#
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:

C#
private WeakEventHandler<MyEventArgs> eventSourceChangedHandler;

Then in your constructor you initialize the WeakEventHandler<> object in the following way:

C#
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:

C#
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:

C#
~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:

C#
public void AddHandler(object eventSource, string eventName)
{
    // Store the event source details
    this.eventName = eventName;
    weakReferenceToEventSource = new WeakReference(eventSource);

    // Create and intermediate handler that the event source will
    // have a strong reference to.
    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:

C#
public void RemoveHandler()
{
    if (weakReferenceToEventSource==null)
      return;

    object eventSource = weakReferenceToEventSource.Target;
    if (eventSource != null)
    {
        // Get the event using reflection
        EventInfo eventInfo = eventSource.GetType().GetEvent(eventName);

        // Remove the intermediate event handler, which will dereference
        // this weak reference and allow it to be garbage collected.
        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:

C#
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:

C#
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Founder Cheesy Design
Taiwan Taiwan
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.

Comments and Discussions

 
GeneralWeak Event Patterns .NET Framework 4 Pin
krn_2k23-Aug-10 21:52
krn_2k23-Aug-10 21:52 
GeneralRe: Weak Event Patterns .NET Framework 4 Pin
John Stewien24-Aug-10 2:00
John Stewien24-Aug-10 2:00 
QuestionTradeoff: Speed vs. Knowing your sources Pin
Thomas Hamilton24-Oct-08 9:03
Thomas Hamilton24-Oct-08 9:03 
AnswerRe: Tradeoff: Speed vs. Knowing your sources Pin
John Stewien26-Oct-08 22:43
John Stewien26-Oct-08 22:43 
GeneralIt's illegal Pin
uecasm11-Jun-07 16:12
uecasm11-Jun-07 16:12 
GeneralRe: It's illegal Pin
John Stewien15-Jun-07 2:27
John Stewien15-Jun-07 2:27 
GeneralRe: It's illegal Pin
uecasm17-Jun-07 13:03
uecasm17-Jun-07 13:03 
AnswerRe: It's illegal [modified] Pin
John Stewien19-Jun-07 22:01
John Stewien19-Jun-07 22:01 
GeneralRe: It's illegal Pin
uecasm20-Jun-07 12:47
uecasm20-Jun-07 12:47 
GeneralRe: It's illegal Pin
John Stewien20-Jun-07 18:16
John Stewien20-Jun-07 18:16 
GeneralRe: It's illegal Pin
uecasm20-Jun-07 18:31
uecasm20-Jun-07 18:31 
GeneralRe: It's illegal Pin
Chernichkin Stanislav30-Aug-07 1:00
Chernichkin Stanislav30-Aug-07 1:00 
GeneralRe: It's illegal Pin
supercat915-Aug-08 15:11
supercat915-Aug-08 15:11 
QuestionHave you read this article? Pin
Zoltan Balazs10-Mar-07 3:59
Zoltan Balazs10-Mar-07 3:59 
AnswerRe: Have you read this article? Pin
John Stewien10-Mar-07 4:02
John Stewien10-Mar-07 4:02 
GeneralRe: Have you read this article? Pin
Zoltan Balazs10-Mar-07 4:09
Zoltan Balazs10-Mar-07 4:09 
GeneralRe: Have you read this article? Pin
John Stewien10-Mar-07 4:12
John Stewien10-Mar-07 4:12 
GeneralRe: Have you read this article? Pin
Zoltan Balazs10-Mar-07 4:23
Zoltan Balazs10-Mar-07 4:23 
GeneralRe: Have you read this article? Pin
John Stewien10-Mar-07 4:27
John Stewien10-Mar-07 4:27 
GeneralRe: Have you read this article? Pin
Zoltan Balazs10-Mar-07 4:23
Zoltan Balazs10-Mar-07 4:23 
General.How to find Path of a given file in C# Pin
Member 37839969-Mar-07 18:55
Member 37839969-Mar-07 18:55 
GeneralRe: .How to find Path of a given file in C# Pin
John Stewien10-Mar-07 2:14
John Stewien10-Mar-07 2:14 
QuestionFailure on very basic load test Pin
Stéphane Issartel6-Mar-07 5:17
Stéphane Issartel6-Mar-07 5:17 
AnswerRe: Failure on very basic load test Pin
John Stewien6-Mar-07 13:26
John Stewien6-Mar-07 13:26 
AnswerRe: Failure on very basic load test Pin
John Stewien6-Mar-07 18:20
John Stewien6-Mar-07 18:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.