As part of the next drop of nRoute, I've been doing some work around events and listeners in .NET - the main issues have been around memory-leaks, selectively multi-casting events, performance and loose-coupling. Certainly, this is not something new and there are a number of solutions out there, but usability and performance have been the compromises which was something I wanted to avoid.
The Idea : Wrap'em
From my work on it, I take that we don't want the semantics of exposing and using events to change - so basically the the producer and consumer syntax should remains the same, however if the producers and consumers are dumb about it, I suppose we should make the messenger smarter. So here is what I'm mean:
1: source.SomethingHappened += new WeakHandler((s, e)=>{ . });
2: //or
3: source.SomethingHappened += new WeakHandler(Handle_Something); // delegates to a method
On the left the source exposes a "SomethingHappened" event using the EventHandler delegate, and on the right we have a custom class that handles the event - the use API is actually not far off from where we are today. We just changed the normal handlers with the "WeakHandler" class that wraps (not inherits) the actual handler and introduces the logic for weak delegation. In my solution I make use of closures and lifting .NET features to create "self-referencing" event-handler that are aware of their wrappers. It's not that hard, however the benefit is that we can make it do smart things such as respond to say a specific property change on a PropertyChanged event, consider:
1: source.PropertyChanged += new WeakPropertyChangedHandler(
2: ()=> SelectedItem,
3: SelectedItem_PropertyChanged);
In this case the 'SelectedItem_PropertyChanged' method will only be called when the 'SelectedItem' property changes. And the best part is the listener or consumer is weakly referenced, so it can be available for garbage collection when it has no outstanding references (without having to remove the event handler itself). And if the listener has been disposed off, the handler will de-register itself and dispose off.
The Implementation
Like I said the event handling classes above, like WeakPropertyChangedHandler, are basically wrappers around the actual event handlers - however note, we don't need to create specific event handlers for each and every event type. We have a generic definition that can that can handle every type of event; its constructor signature is a follows:
1: public class Handler<E, H>
2: where E : EventArgs
3: {
4: public Handler(Func<Action<Object, E>, H> createAction, Action<Object, E> action,
5: Action<H> removeAction) { .. }
6: }
The H type is supposed to be your handler type which unfortunately cannot be constrained. All the same, just like every event handler we need to be able to do four things, namely create the handler, attach the handler, handle the event and remove the handler. Now, if you look at the constructor we don't do the attaching part, that's up to the user but for the rest we take in delegates - so a full blown handler can be written as:
1: Application.Current.MainWindow.SizeChanged +=
2: new Handler<SizeChangedEventArgs, SizeChangedEventHandler>(
3: (a) => new SizeChangedEventHandler(a), // How to create
4: (s, e) => Console.WriteLine("Main Window Size Changed."), // How to handle
5: (h) => Application.Current.MainWindow.SizeChanged -= h); // How to remove
I know that is somewhat verbose, however that's the full monty, whereas you have more specific implementations and additional constructors that can do the creation and removing part for you. The other important thing to realize is that the Handle<E,H> type can be paraded around as a named variable, consider:
1: var _handler = new RoutedHandler((s, e) => MessageBox.Show("GotFocus"));
2: this.GotFocus += _handler;
3: // when done
4: _handler.UnregisterHandler();
Here we create the handler variable, which when done can be specifically called to unregister. I specifically designed this to call an unregister method rather than have to -= the handler.
The Variants
As you can probably tell we have many different types of handlers, which can generally be qualified into three separate categories (see below). First, we have types deriving from Handler<E,H>, which are basically like normal events wrappers that hold a direct reference to the event listener and have to be manually unregistered. The second category is based on the SingleHandler<E,H> type that only handle the first call and then recuses itself - which might seem strange, but actually they are very useful in handling things like Loaded, Initialized, Errors related events. They save memory, plus ensure you don't end up holding unnecessary references. Lastly, we have the WeakHandler<E,H>based types, which as the name suggests holds a weak reference to the listener and allow for its disposing independent of the event source. Also note, WeakHandler types also allow you to manually call the UnregisterHandler method to discontinue the event handling.
As shown above, based on the three categories of handler wrappers we have specific types that handle EventHandler<E>, EventHandler, RoutedEventHandler and PropertyChangedEventHandler types of event delegates. They just allow for an easier to use API, however you could resort to the generic types if you wanted too.
Performance
Given the use syntax is very similar to the normal event handlers, the other important use tenant for me is the performance. Lets see how it fares (time in seconds):
| Handler Type |
Action Type |
1 Event |
10 Events |
1,000 Events |
10,000 Events |
| System EventHandler |
Attach Event |
0.0000059 |
0.0000272 |
0.0006216 |
0.0310925 |
| |
Raise Event |
0.0000041 |
0.0000102 |
0.0248883 |
1.1368489 |
| |
Detach Event |
0.0000065 |
0.0000254 |
0.0148008 |
0.8415036 |
| Handler<E,H> |
Attach Event |
0.0000852 |
0.0002054 |
0.0096227 |
0.0665443 |
| |
Raise Event |
0.0000041 |
0.0000148 |
0.0522444 |
3.1837825 |
| |
Detach Event |
0.0000071 |
0.0000272 |
0.0560088 |
2.7236017 |
| SingleHandler<E,H> |
Attach Event |
0.0000840 |
0.0001782 |
0.0098619 |
0.0592904 |
| |
Raise Event |
0.0000059 |
0.0000201 |
0.0375098 |
2.2381572 |
| |
Detach Event |
0.0000065 |
0.0000236 |
0.0530667 |
2.5753320 |
| WeakHandler<E,H> |
Attach Event |
0.00001415 |
0.0003268 |
0.0171803 |
0.1007618 |
| |
Raise Event |
0.0000840 |
0.00003208 |
0.1420596 |
12.9617411 |
| |
Detach Event |
0.0000065 |
0.0000272 |
0.0558754 |
2.7769959 |
Based on the table, the performance of the wrapped handlers have a disadvantage ranging from 1.5x-15x, primarily on the event raising front. Attaching is notably slower, however unless you are doing very processer intensive stuff, I think for most part these are within the range of "acceptable costs", especially for client-side apps like with Silverlight. Also, there are further optimizations to be had, as this is quite early stuff.
Listeners
Now if you like what you have seen, it gets better. Given some of the problems with eventing as it stands, the WPF team rolled out their own implementation based on the Weak Event Pattern. They essentially provided an event manager, a custom event definition wrapper, and a relative simple consumption interface - which all somewhat works like normal events. The consumption interface called IWeakEventListner has the following signature:
1: public interface IWeakEventListener
2: {
3: bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e);
4: }
The weak event pattern is performant, but the provider side requirements of obscure per-event setup, registration and the middle men solution are all a bit clumsy. So how about if we could attach IWeakEventListner implementation to normal event handler defined events, something like this:
1: source.SomethingHappened += new WeakListener(weakEventListnerObj);
2: // or
3: source.PropertyChanged += new WeakPropertyChangedListnerHandler(
4: () => Items,
5: weakEventListnerObj);
Here the "SomethingHappened" is a normal event defined by the EventHandler signature, and we attach a handler that directs the handling to the a class instance implementing IWeakEventListner. The use semantics are just about the same as with normal events, and you can also detach the same by using the UnregisterMethod. You need no special registration, event-definition class, or manager, just do as you always did but with the given handlers types and it works with every event out there.
The Variants
And just like the event wrappers above, we have three categories of listeners shown below:
The use semantics are basically similar to what I showed earlier, the only difference being we need to pass in an instance of IWeakEventListener rather than a delegate to handle the event. Further, because Silverlight doesn't have an build-in IWeakEventListener interface, I've provided one to compile with Silverlight whereas in the full-blown .NET version you would/can use the WPF defined interface. Also since we don't have a "managerType" as specified by the RecieveWeakEvent method, we pass in the type of EventHandler - so for a property changed event you will get the PropertyChangedHandler type passed in as the managerType. Further, by returning false you can unregister from the event and the wrapper will dispose itself.
Performance
Listeners with event handlers numbers (in seconds):
| Handler Type |
Action Type |
1 Event |
10 Events |
1000 Events |
10,000 Events |
| System EventHandler |
Attach Event |
0.0000059 |
0.0000272 |
0.0006216 |
0.0310925 |
| |
Raise Event |
0.0000041 |
0.0000082 |
0.0248883 |
1.1368489 |
| |
Detach Event |
0.0000065 |
0.0000254 |
0.0148008 |
0.8415036 |
| ListenerHandler<E,H> |
Attach Event |
0.0000976 |
0.0001598 |
0.0101366 |
0.0606196 |
| |
Raise Event |
0.0000059 |
0.0000142 |
0.0493853 |
3.6426401 |
| |
Detach Event |
0.0000071 |
0.0000219 |
0.0460088 |
2.6254094 |
| SingleListenerHandler<E,H> |
Attach Event |
0.0000828 |
0.0001557 |
0.0102970 |
0.0610187 |
| |
Raise Event |
0.0000102 |
0.0000159 |
0.0337023 |
2.4158356 |
| |
Detach Event |
0.0000082 |
0.0000165 |
0.0435121 |
2.5886397 |
| WeakListenerHandler<E,H> |
Attach Event |
0.0001053 |
0.00001409 |
0.0115611 |
0.0699055 |
| |
Raise Event |
0.0000106 |
0.0000224 |
0.099667 |
7.3137979 |
| |
Detach Event |
0.0000082 |
0.0000266 |
0.0446755 |
2.7211488 |
Based on the figures above the build in handlers still have a distinct advantage, however it is less pronounced. And again from my perspective, the costs of using listeners with events are nominal. And, with respect to handlers performance listed earlier, listeners fare much better considering the conversions between delegate to interface based handling.
There is More
As I mentioned this is early work, so I have a couple of things in mind for improvements:
- Possibly, implement IDisposable on all the handlers, not for reclaiming unmanaged resources use but more for user's remember-to-dispose considerations. I'm still thinking about this
- Introducing -= operator, I personally prefer the use of the UnregisterHandler method
- Introducing a common base class, with overridable methods
- Because I'ved used a lot of lambda statements, I want to see if there is a way to increase performance. Also if I understand correctly in .NET 4 lambda statements can be compiled (as opposed to only expressions right now), which should help with performance
- I also want to further squeeze performance out of the Weak handlers, I've used a number of techniques to ensure through-and-through performance, but I still think there might be better ways. I am looking at late-bound expression trees to see if they can help, if you know of any better way please let me know
- I'm also looking for alternative and strongly-typed ways to create and remove handlers. What I have currently, it's quite performant but there might be a better way
- Also I think with .NET 4.0's dynamic types we might be able to hopefully get some improvement in performance, especially since we will be able to rigorously match event defined signature with delegates
- For Silverlight, there is an access limitation to only being able to call public methods and not private ones - I want to if there is an alternative to that (note this only effects the Weak Handlers types). If you know of any alternatives, do let me know
The code is attached below, it references the "WindowsBase" dll for the IWeakEventListener interface - but if you want to use this in non-WPF environments then just expose the interface and remove the RoutedHandlers related classes. Similarly, for use in Silverlight, just link/add the code to a Silverlight project and it should compile as is.
Download Code (updated for Silverlight use)
Update: I re-did all the performance tests because they were highly skewed in favour of listeners - because earlier the handlers did not do any work. The new figures, better represent real-usage and reflect the expected costs of wrapping event handlers.