|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionWhile 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 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. BackgroundI 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 CodeI 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 private WeakEventHandlerFactory eventHandlers;
And then add as many event handlers as you want, to any event source, using the eventHandlers.AddWeakHandler<MyEventArgs>(eventSource,
"EventSourceChanged", eventSource_EventSourceChanged);
To remove an event handler, use 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 When your object is garbage collected, the 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 private WeakEventHandler<MyEventArgs> eventSourceChangedHandler;
Then in your constructor you initialize the 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 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 ~MyClass
{
eventSource.EventSourceChanged -= eventSourceChangedHandler;
}
Under the HoodI've written four utility classes for creating Weak Event Handlers:
WeakReferenceToEventHandlerThis class is exactly what the class name says. It is a weak reference to an event handler. This class is used by the 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 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 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. WeakEventHandlerInternalThe WeakEventHandlerFactoryThe
The The WeakEventHandlerThis class can be used directly, but cautiously, for directly creating weak event handlers. It sacrifices ease of use for speed. An instance of Using the Sample ApplicationIf 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 If you edit one of the collection items, the total displayed above the Now for the fun part. Below each 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 Speed TestingThe 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 ApplicationThere are two collection classes included in the source code for the sample application:
The sample application just uses the Points of InterestThe new public class SubTotalCollection : BindingList<SubTotal>
Then in Form.cs I created a binding source, attached it to the 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 History
| ||||||||||||||||||||