Friday, March 23, 2007

kick it on DotNetKicks.com

March 26, 2007: Added performance tests, corrected several typos and clarified the performance issues with the lightweight code generation approach.

The Problem (A Recap)

As discussed last time, delegates can produce memory leaks in our applications if not used carefully. Most often, this happens with events when we add a handler to an event, forget to remove it and the object on which the event is declared lives longer than the object that handles it. The following diagram illustrates the problem.

diagram

In the diagram above, there is an object ("eventExposer") that declares an event ("SpecialEvent"). Then, a form is created ("myForm") that adds a handler to the event. The form is closed and the expectation is that the form will be released to garbage collection but it isn't. Unfortunately, the underlying delegate of the event still maintains a strong reference to the form because the form's handler wasn't removed.

The question is, how do we fix this problem?

Most of you are probably saying "Fix the form you idiot! Remove the handler!" That might seem reasonable—especially if we have control over all of the code. But what if we don't? What if this is a large plug-in based architecture where the event is surfaced by some event service that exists for the lifetime of the application and the form is in a third-party plug-in for which we don't have the code? In this scenario, we would have a leaky application that couldn't be corrected by simply modifying the form to properly remove the handler on close.

Some Solutions

As I mentioned in my before, the ideal solution would be a CLR-level mechanism for creating "weak delegates" which hold weak references to their target objects instead of strong references. Unfortunately, such a mechanism doesn't yet exist (and won't for the foreseeable future) so we have to roll our own.

Greg Schechter presented a well-known solution a couple of years ago that is used in Avalon (err... WPF). This solution works perfectly except that it isn't all that convenient to use. It requires the declaration of a new class that has intimate knowledge of both the container and containee (or subject and observer). Schechter's essay inspired several developers in the community to come up with architectures that make this approach easier to use and a couple of articles were submitted to code project. Notably, "Observable property pattern, memory leaks, and weak delegates for .NET" and "An Easy to Use Weak Referenced Event Handler Factory for .NET 2.0". Unfortunately, neither of these solutions are ideal. First, both of them have several support classes. In addition, the first is extraordinarily heavy because it uses Refelection.Emit to generate types and assemblies on the fly (more on this type of thing later) and the second one requires extra client code and uses Reflection to add add and remove handlers from an event. I should mention however that both solutions do work.

Over the past few years, the community has offered several "magic class" approaches (where a special class is instantiated that wraps your event handler). A "magic class" is an attractive solution because it results in far less client code. Unfortunately, the majority of these offerrings got it wrong. Most of them make the mistake of creating a weak reference to the event handler itself rather than the target of the event handler. Shawn A. Van Ness eventually removed his solution because of problems. Ian Griffiths kept his available with a note that it doesn't actually work. Even Joe Duffy (of whom I'm a big fan) presented a "magic class"-type solution that doesn't really do the job. A very interesting approach that does work is the one presented by Xavier Musy. Xavier chose to essentially re-implement System.MulticastDelegate but it suffers from performance issues and is not safe multi-threaded scenarios. Finally, Gregor R. Peisker presented a real working (and performant) solution just a few months ago that takes advantage of a technique that is crucial to getting this right.

Obstacles

I have to admit, there are several obstacles to creating a working solution. First of all, you can't simply inherit from System.Delegate or System.MulticastDelegate and override the appropriate methods. That would be great but it simply isn't possible. You can't even do it IL (believe me—I've tried). In addition, there isn't a generic constraint for delegates. We can work around that but it certainly makes some code that we might write less elegant.

However, the biggest obstacle is performance. There are two areas of performance that we can consider: 1) adding/removing a handler and 2) actually invoking the handler. On the average, invocation occurs far more often than adding and removing handlers so we'll focus our attention on that. There are a lot of ways to dynamically invoke a delegate and some of them are very slow when compared with a normal typed delegate invocation. Using a weak delegate or event handler is expected to have some overhead but we should keep it to a minimum.

Another potentially serious issue is removing the handler. Let me explain what I mean. When we use a "magic class" to wrap an event handler and add it to an event, we create a weak reference to the event handler's target but the event still maintains a strong reference to our wrapper object. So, when the target object is garbage collected, the wrapper object is kept alive for the same reason that the target wasn't being garbage collected before. Granted, it will likely take far less memory than some large object (e.g. the form in the diagram that I mentioned earlier) but it's still a leak. That might be acceptable to some of you but there's actually a very simple solution to this so we should deal with it properly.

The last obstacle that I want to mention is how to create a class that will handle any delegate type. I'm going to present a solution that work with any delegate but we can get much higher-performance if we can use a specific delegate type. Because of that, I'll focus most of my attention on that only work with the handy generic System.EventHandler<TEventArgs> delegate from the .NET 2.0 framework.

A First Stab

OK, let's dig into some code and take a shot at creating a "magic class" so that I can introduce the players. Here's a very a naive implementation:

using System;
using System.Reflection;

public class WeakDelegate
{
  private WeakReference m_TargetRef;
  private MethodInfo m_Method;

  public WeakDelegate(Delegate del)
  {
    m_TargetRef = new WeakReference(del.Target);
    m_Method = del.Method;
  }

  public object Invoke(params object[] args)
  {
    object target = m_TargetRef.Target;

    if (target != null)
      return m_Method.Invoke(target, args);
  }
}

This approach would actually work in very simple delegate scenarios but it isn't robust enough to be used with an event. However, it serves to demonstrate how we can use the System.WeakReference clsss to create a weak reference to the delegate's target. To use, instantiate a new WeakReference and pass the object that you want to track to its constructor. Then, you can check the WeakReference.IsAlive or WeakReference.Target properties to see if the object has been garbage collected. Keep in mind that you should never write code like this:

if (m_TargetRef.IsAlive) // race condition!!!!
  return m_Method.Invoke(m_TargetRef.Target, args);

I've seen way too many developers (including myself many moons ago) make this mistake. The problem is that the garbage collector runs on a different thread. This causes a race condition to occur between the calls to m_TargetRef.IsAlive and m_TargetRef.Target. In other words, m_TargetRef.IsAlive could return true but the garbage collector could kick in and reclaim the target instance before m_TargetRef.Target is called, potentially causing a NullReferenceException to be thrown. The proper way to use this class is to store the value of m_TargetRef.Target in a local variable. If m_TargetRef.Target doesn't return null, the local variable creates a strong reference to the target instance that can safely be used.

So, using the System.WeakReference is fairly simple but it has one non-obvious problem that I'd like to highlight: it is finalizable but does not implement IDisposable. What does that mean and why is it bad? It means that any System.WeakReference instance will add a small amount of pressure to the garbage collector because its finalizer method must be called before it can be reclaimed. Internally, WeakReference uses a System.GCHandle to track the target object and it declares a finalizer to clean up that GCHandle. If it implemented IDisposable properly, we could call the Dispose method when finished with the WeakReference. That would release the GCHandle and call GC.SuppressFinalize(this) to keep the finalizer from being called (thus removing the GC pressure). (In CLR via C#, Jeffrey Richter refers to this lack of an IDisposable implementation as a bug.) Now, we could use System.GCHandle ourselves instead of System.WeakReference but there is a problem with that. The public methods on GCHandle require a link demand for the unmanaged code security permission. That means that our WeakDelegate class would need the same permission. WeakReference actually gets around those security permissions by using internal methods on GCHandle that don't declare them (it does have an inheritance demand though—beware inheritors!). Because it requires less security checks, using WeakReference is actually a little bit faster. Sigh...

There is one other important player from our initial attempt that I'd like to point out. The delegate is invoked by calling MethodInfo.Invoke() and passing the target instance and the arguments. This is really slow. Directly invoking the delegate would be much faster but we can't store a reference to the original delegate because it strongly references the target. So, we have to rely upon late-bound mechanisms that are much slower. This is the main reason that we will shift our focus from wrapping System.Delegate to System.EventHandler<TEventArgs> shortly.

The robustness of this solution can be improved by making WeakDelegate multi-casting. Currently, it only supports single delegates which is why it is insufficient for using with events. To do this, we would need to use a similar approach to the one presented by Xavier Musy. The idea is that we add Combine and Remove static methods that are equivalent to those that are found on System.Delegate. With these in place, we could use this class to declare a weak event like this:

public class EventProvider
{
  private WeakDelegate m_WeakEvent;

  protected virtual void OnWeakEvent(EventArgs e)
  {
    if (m_WeakEvent != null)
      m_WeakEvent.Invoke(this, e);
  }

  public event EventHandler WeakEvent
  {
    add
    {
      m_WeakEvent = WeakDelegate.Combine(m_WeakEvent, value);
    }
    remove
    {
      m_WeakEvent = WeakDelegate.Remove(m_WeakEvent, value);
    }
  }
}

The main issue here is that all handlers are weak—whether they need to be or not. It would be more flexible to have a weak delegate class that can be used like an ordinary delegate. That way, a subscriber could make specific handlers weak and other handlers not. The ideal syntax for a subscriber might look something like this:

public class EventProvider
{
  public event EventHandler MyEvent;
}
public class EventSubscriber
{
  public EventSubscriber(EventProvider provider)
  {
    provider.MyEvent += new WeakDelegate(MyWeakEventHandler);
  }

  private void MyWeakEventHandler(object sender, EventArgs e)
  {
  }
}

The Second Attempt

At this point, we're in a bit of a pickle if we want to continue trying to provide a solution that wraps any System.Delegate. In fact, to go down this road, we have use the System.Reflection.Emit classes to generate a new assembly, a new module, a new type, a WeakReference field, a new method that matches the signature of the delegate and all of the IL necessary to access the WeakReference.Target property, load the parameters onto the stack and call the method referenced by the delegate. Now, this is certainly possible to do (yes, I did it and it made me weep like a child) but the result is a mechanism that is so heavy that it's almost worthless. Plus, the assembly, module and type are not reclaimable by garbage collection unless you actually create a new AppDomain to contain the code so it can be unloaded later. However, using another AppDomain will cause invocations of the delegate to cross AppDomain boundaries (ahem... remoting) and it is fraught with peril because you need to work out some serialization mechanism for the delegate and its target. Are you beginning to realize why I'm not showing any code here? Obviously, this approach kills both performance and memory use. And, since we're trying to create something that's both fast and lightweight, this is simply not acceptable.

OK, so I'm going to abandon the idea of creating a magic class that works with any delegate type and focus on creating a solution for a specific delegate type: System.EventHandler<TEventArgs>. That will limit the solution to .NET 2.0-only but that's ok. The techniques that we'll employ are 2.0 anyway. Microsoft actually recommends that you use EventHandler<TEventArgs> delegate for declaring events if you are targeting .NET 2.0+ anyway. (Microsoft even managed to follow their own recommendation and used this delegate type throughout WCF and WF. They seemingly forgot about WPF though. Sigh...)

With a specific signature, it is much easier to create a "magic" WeakEventhandler class that achieves the syntax that we're looking for.

using System;
using System.Reflection;

public class WeakEventHandler<E>
  where E: EventArgs
{
  private WeakReference m_TargetRef;
  private MethodInfo m_Method;
  private EventHandler<E> m_Handler;

  public WeakEventHandler(EventHandler<E> eventHandler)
  {
    m_TargetRef = new WeakReference(eventHandler.Target);
    m_Method = eventHandler.Method;
    m_Handler = Invoke;
  }

  public void Invoke(object sender, E e)
  {
    object target = m_TargetRef.Target;
    if (target != null)
      m_Method.Invoke(target, new object[] { sender, e });
  }

  public static implicit operator EventHandler<E>(WeakEventHandler<E> weh)
  {
    return weh.m_Handler;
  }
}

This version is starting to look a little more like what we want. Being tied to a specific delegate type, we know what the method signature will be and we can create our own method that matches to use as a sort of "delegate proxy". And, thanks to the implicit conversion we can write the golden syntax that we're looking for.

public class EventProvider
{
  public event EventHandler<EventArgs> MyEvent;
}
public class EventSubscriber
{
  public EventSubscriber(EventProvider provider)
  {
    provider.MyEvent += new WeakEventHandler<EventArgs>(MyWeakEventHandler);
  }

  private void MyWeakEventHandler(object sender, EventArgs e)
  {
  }
}

I ran a simple performance test that adds and fires 100 event handlers in the standard way and with the WeakEventHandler class. Here are the results:

Added 100 normal listeners to notifier: 0.000289 seconds.
Added 100 weak listeners to notifier: 0.002701 seconds.
Fired 100 normal listeners: 0.000263 seconds.
Fired 100 weak listeners: 0.001531 seconds.

Obviously, this approach is pretty slow. Invocation through MethodInfo.Invoke() is nearly 6 times slower than normal invocation.

This leaves us with two problems left to solve:

  1. Invocation performance. We're still using MethodInfo.Invoke() and it's slow.
  2. Removal of the weak event handler from the event after the target has been garbage collected.

The second problem is actually very simple so let's look at the performance issue first.

Tweaking Performance

Using MethodInfo.Invoke() is really slow. What we'd really like is some way to generate a delegate that does not actually store a reference to its target but allows us to specify the target when we call it. That way, our invocation performance characteristic is very similar to that of a standard delegate invocation. In .NET 1.1 there really wasn't a way to create such a delegate but there are two options available to us (that I am aware of) in .NET 2.0.

The first option that I want to explore is lightweight code generation (LCG). Lightweight code generation is a mechanism in the .NET 2.0 framework that allows methods to be generated on the fly without the requirement of an assembly, module, type, etc. These "dynamic methods" can be associated with a specific module (making them effectively global) or a specific type (making them static). Also, unlike the Reflection.Emit story in 1.0/1.1, they have the benefit of being reclaimable by the garbage collector. In fact, the only real downside of LCG is that you have to spit out the IL of the dynamic method. There is a small hit when creating a dynamic method but, as discussed earlier, the invocation performance is far more important.

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

public class WeakEventHandler<E>
  where E: EventArgs
{
  private delegate void EventHandlerThunk(object @this, object sender, E e);

  private static int g_NextThunkID = 1;

  private WeakReference m_TargetRef;
  private EventHandlerThunk m_Thunk;
  private EventHandler<E> m_Handler;

  public WeakEventHandler(EventHandler<E> eventHandler)
  {
    m_TargetRef = new WeakReference(eventHandler.Target);
    m_Thunk = CreateDynamicThunk(eventHandler);
    m_Handler = Invoke;
  }

  private EventHandlerThunk CreateDynamicThunk(EventHandler<E> eventHandler)
  {
    MethodInfo method = eventHandler.Method;
    Type declaringType = method.DeclaringType;

    int id = Interlocked.Increment(ref g_NextThunkID);
    DynamicMethod dm = new DynamicMethod("EventHandlerThunk" + id, typeof(void),
      new Type[] { typeof(object), typeof(object), typeof(E) }, declaringType);

    ILGenerator il = dm.GetILGenerator();

    // load and cast "this" pointer...
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Castclass, declaringType);

    // load arguments...
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Ldarg_2);

    // call method...
    il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method);

    // done...
    il.Emit(OpCodes.Ret);

    return (EventHandlerThunk)dm.CreateDelegate(typeof(EventHandlerThunk));
  }

  public void Invoke(object sender, E e)
  {
    object target = m_TargetRef.Target;

    if (target != null)
      m_Thunk(target, sender, e);
  }

  public static implicit operator EventHandler<E>(WeakEventHandler<E> weh)
  {
    return weh.m_Handler;
  }
}

Clearly, this code is far more complicated than when it used MethodInfo.Invoke(). First, there is a new delegate declared ("EventHandlerThunk") which has the same signature as the EventHandler that we're wrapping with an additional parameter to take the target. We create an EventHandlerThunk as a DynamicMethod and when invoking it, we pass the target along with the parameters (see the Invoke method above). Most of the magic code is in the CreateDynamicThunk method. I don't want to get too detailed about the IL that is generated but it essentially calls the method referenced by "eventHandler" directly instead of through the MethodInfo.Invoke(). Very snazzy.

Using LCG, the invocation performance is not great. I ran the same test that I ran earlier and got these results:

Added 100 normal listeners to notifier: 0.000292 seconds.
Added 100 weak listeners to notifier: 0.010599 seconds.
Fired 100 normal listeners: 0.000272 seconds.
Fired 100 weak listeners: 0.01572 seconds.

Whoa! It turns out that that invoking the LCG solution is almost 58 times slower than normal invocation! Obviously, this is a step backward and not really what we're looking for. Part of this poor performance is caused by the fact that we're actually creating three method calls here: WeakEventHandler.Invoke() calls the dynamic EventHandlerThunk which calls the real method. The really problem, however, is that the "lightweight" in "lightweight code generation" really refers to memory and not performance. So, this isn't an acceptable solution for is.

The best option that is available to us was mentioned by Joe Duffy in a comment on another blog. Instead of creating a DynamicMethod and generating IL, we can use an open instance or unbound delegate.

Now, some of you might be scratching your heads right now and saying, "what's an open instance delegate and why haven't I heard of them before?"

An open instance delegate is a delegate that references an instance method but doesn't reference a target. Instead the target is to be specified when the delegate is called. To allow for this, the delegate type of the open instance delegate must declare an additional parameter that takes the instance that the delegate is called on when invoked (just like our EventHandlerThunk delegate type).

That explains what they are but why haven't you heard of them? Well, there's no language support for them in C# and VB. In order to create one, you have to use an overload of the Delegate.CreateDelegate() method. Open instance delegates were designed to support STL.NET and C++/CLI (where they're called unbound delegates). Additionally, nobody has really gotten excited about them except for Gregor R. Peisker. His working solution that I mentioned earlier uses an open instance delegate.

Here is the code reworked to use an open instance delegate:

using System;

public class WeakEventHandler<E>
  where E: EventArgs
{
  private delegate void OpenEventHandler(object @this, object sender, E e);

  private WeakReference m_TargetRef;
  private OpenEventHandler m_OpenHandler;
  private EventHandler<E> m_Handler;

  public WeakEventHandler(EventHandler<E> eventHandler)
  {
    m_TargetRef = new WeakReference(eventHandler.Target);
    m_OpenHandler = (OpenEventHandler)Delegate.CreateDelegate(typeof(OpenEventHandler),
      null
, eventHandler.Method);
    m_Handler = Invoke;
  }

  public void Invoke(object sender, E e)
  {
    object target = m_TargetRef.Target;

    if (target != null)
      m_OpenHandler(target, sender, e);
  }

  public static implicit operator EventHandler<E>(WeakEventHandler<E> weh)
  {
    return weh.m_Handler;
  }
}

Oops! That compiles but it doesn't actually work. When a WeakEventHandler is instantiated, the call to Delegate.CreateDelegate fails with a System.ArgumentException and the (unhelpful) message: "Error binding to the target method." What the heck is going on? Well, if you look at the documentation for unbound delegates in C++ it states that "the first parameter of the delegate signature is the type of this for the object you want to call." In other words, using "object" as the type of the first parameter won't work. It has to match. To correct this problem, we have to add another generic type parameter to our WeakEventHandler class to represent the type of the instance on which the handler is declared. Here's the new version:

using System;

public class WeakEventHandler<T, E>
  where T: class
  where E: EventArgs
{
  private delegate void OpenEventHandler(T @this, object sender, E e);

  private WeakReference m_TargetRef;
  private OpenEventHandler m_OpenHandler;
  private EventHandler<E> m_Handler;

  public WeakEventHandler(EventHandler<E> eventHandler)
  {
    m_TargetRef = new WeakReference(eventHandler.Target);
    m_OpenHandler = (OpenEventHandler)Delegate.CreateDelegate(typeof(OpenEventHandler),
      null
, eventHandler.Method);
    m_Handler = Invoke;
  }

  public void Invoke(object sender, E e)
  {
    T target = (T)m_TargetRef.Target;

    if (target != null)
      m_OpenHandler(target, sender, e);
  }

  public static implicit operator EventHandler<E>(WeakEventHandler<T, E> weh)
  {
    return weh.m_Handler;
  }
}

This works perfectly but now we've broken our client code syntax. The second generic type parameter has to be specified like this:

public class EventSubscriber
{
  public EventSubscriber(EventProvider provider)
  {
    provider.MyEvent += new WeakEventHandler<EventSubscriber, EventArgs>(MyWeakEventHandler);
  }

  private void MyWeakEventHandler(object sender, EventArgs e)
  {
  }
}

I really dislike the additional generic type parameter because it requires cerebral activity on the part of the user to determine what type needs to be filled in. But, we've corrected the problem that we set out to fix: invocation performance. There is a performance bump when the open instance delegate is created but that's acceptable because the invocation performance is very good.

Automatic Unregistration

Ideally, our WeakEventHandler would be automatically removed from the event on which it is registered when its target is reclaimed by the garbage collector. Unfortunately, that's not possible. There simply isn't any sort of notification when a garbage collection occurs (unless you're using the CLR hosting, debugging or profiling APIs). Because of this, we have to enlist an idea from Joe Duffy and do the next best thing: we pass a delegate to the WeakEventHandler's constructor that will be called during invocation if the target has been garbage collected. Here's the code:

using System;

public class WeakEventHandler<T, E>
  where T: class
  where E: EventArgs
{
  public delegate void UnregisterCallback(EventHandler<E> eventHandler);

  private delegate void OpenEventHandler(T @this, object sender, E e);

  private WeakReference m_TargetRef;
  private OpenEventHandler m_OpenHandler;
  private EventHandler<E> m_Handler;
  private UnregisterCallback m_Unregister;

  public WeakEventHandler(EventHandler<E> eventHandler, UnregisterCallback unregister)
  {
    m_TargetRef = new WeakReference(eventHandler.Target);
    m_OpenHandler = (OpenEventHandler)Delegate.CreateDelegate(typeof(OpenEventHandler),
      null
, eventHandler.Method);
    m_Handler = Invoke;
    m_Unregister = unregister;
  }

  public void Invoke(object sender, E e)
  {
    T target = (T)m_TargetRef.Target;

    if (target != null)
      m_OpenHandler(target, sender, e);
    else if (m_Unregister != null)
    {
      m_Unregister(m_Handler);
      m_Unregister = null;
    }
  }

  public static implicit operator EventHandler<E>(WeakEventHandler<T, E> weh)
  {
    return weh.m_Handler;
  }
}

If we pass an UnregisterCallback into the constructor of WeakEventHandler, it will be called once if the WeakEventHandler is invoked after the target has been garbage collected. That neatly solves the problem. Of course, our WeakEventHandler leaks if the event never fires again but I think that's acceptable. Here is what the client code looks like now:

public class EventSubscriber
{
  public EventSubscriber(EventProvider provider)
  {
    provider.MyEvent += new WeakEventHandler<EventSubscriber, EventArgs>(MyWeakEventHandler,
      delegate(EventHandler<EventArgs> eh)
      {
        provider.MyEvent -= eh;
      });
  }

  private void MyWeakEventHandler(object sender, EventArgs e)
  {
  }
}

Now that we have a working "magic class" solution, let's take a look at how we might improve the syntax.

Making It Pretty

The most important thing is to get rid of the additional generic type parameter that we were forced to add. It just requires too much thinking to assign it properly. The use of an anonymous method might confuse the code for some but it's easy enough to move that into a separate method.

To remove the generic type parameter from the client code, we'll have to resort to reflection to construct the WeakEventHandler<T, E> type (because we have to fill that type parameter in somehow). Gregor R. Peisker's solution employs this trick so we can use it as a model. Here's my version:

public delegate void UnregisterCallback<E>(EventHandler<E> eventHandler)
  where E: EventArgs;

public
interface IWeakEventHandler<E>
  where E: EventArgs
{
  EventHandler<E> Handler { get; }
}

public class WeakEventHandler<T, E>: IWeakEventHandler<E>
  where T: class
  where E: EventArgs
{
  private delegate void OpenEventHandler(T @this, object sender, E e);

  private WeakReference m_TargetRef;
  private OpenEventHandler m_OpenHandler;
  private EventHandler<E> m_Handler;
  private UnregisterCallback<E> m_Unregister;

  public WeakEventHandler(EventHandler<E> eventHandler, UnregisterCallback<E> unregister)
  {
    m_TargetRef = new WeakReference(eventHandler.Target);
    m_OpenHandler = (OpenEventHandler)Delegate.CreateDelegate(typeof(OpenEventHandler),
      null, eventHandler.Method);
    m_Handler = Invoke;
    m_Unregister = unregister;
  }

  public void Invoke(object sender, E e)
  {
    T target = (T)m_TargetRef.Target;

    if (target != null)
      m_OpenHandler.Invoke(target, sender, e);
    else if (m_Unregister != null)
    {
      m_Unregister(m_Handler);
      m_Unregister = null;
    }
  }

  public EventHandler<E> Handler
  {
    get { return m_Handler; }
  }

  public static implicit operator EventHandler<E>(WeakEventHandler<T, E> weh)
  {
    return weh.m_Handler;
  }
}

public static class EventHandlerUtils
{
  public static EventHandler<E> MakeWeak<E>(EventHandler<E> eventHandler, UnregisterCallback<E> unregister)
    where E: EventArgs
  {
    if (eventHandler == null)
      throw new ArgumentNullException("eventHandler");
    if (eventHandler.Method.IsStatic || eventHandler.Target == null)
      throw new ArgumentException("Only instance methods are supported.", "eventHandler");

    Type wehType = typeof(WeakEventHandler<,>).MakeGenericType(eventHandler.Method.DeclaringType, typeof(E));
    ConstructorInfo wehConstructor = wehType.GetConstructor(new Type[] { typeof(EventHandler<E>),
      typeof(UnregisterCallback<E>) });

    IWeakEventHandler<E> weh = (IWeakEventHandler<E>)wehConstructor.Invoke(
      new object[] { eventHandler, unregister });

    return weh.Handler;
  }
}

That looks like a lot more code but it's not too bad. Our magic class has remained relatively unchanged except that the UnregisterCallback delegate type is no longer nested inside and it has a Handler property to implement the new IWeakEventHandler<E> interface. The meat is in EventHandlerUtils.MakeWeak<E>(). This generic method performs the reflection magic necessary to instantiate WeakEventHandler<T, E> with its generic type parameters dynamically filled in. This is where the IWeakEventHandler<E> interface becomes handy. Without it, we would have to use reflection to access the m_Handler field.

At this point, I expect that you're wondering what the performance characteristics of this approach are. Well, using the same performance test with the EventHandlers.MakeWeak<E>() method, I got the following results:

Added 100 normal listeners to notifier: 0.000298 seconds.
Added 100 weak listeners to notifier: 0.011509 seconds.
Fired 100 normal listeners: 0.000288 seconds.
Fired 100 weak listeners: 0.000745 seconds.

This is a much better performance story and closer to what we're looking for. This test demonstrates that using an open instance delegate in the WeakEventHandler class results in performance that is only about 2.5 times slower than standard delegate invocation. That is actually about what we should expect since a WeakEventHandler causes two delegate invocations.

There is room for improvement in EventHandlerUtils.MakeWeak(). A final version could cache the ConstructorInfo (probably using the .NET 2.0 reflection token APIs) and use the values of the two generic type parameters as a composite key. Also, a dynamic method might be employed (and cached) so that ConstructorInfo.Invoke() doesn't have to be called. So, there are several tweaks available.

We have improved the client code syntax a bit. It's easier to use now:

public class EventSubscriber
{
  public EventSubscriber(EventProvider provider)
  {
    provider.MyEvent += EventHandlerUtils.MakeWeak<EventArgs>(MyWeakEventHandler,
      delegate(EventHandler<EventArgs> eh)
      {
        provider.MyEvent -= eh;
      });
  }

  private void MyWeakEventHandler(object sender, EventArgs e)
  {
  }
}

The biggest syntax improvement occurs with C# 3.0. When calling EventHandlerUtils.MakeWeak in C# 3, we can use a lambda expression instead of an anonymous method like this:

public class EventSubscriber
{
  public EventSubscriber(EventProvider provider)
  {
    provider.MyEvent += EventHandlerUtils.MakeWeak<EventArgs>(MyWeakEventHandler,
      eh => provider.MyEvent -= eh);
  }

  private void MyWeakEventHandler(object sender, EventArgs e)
  {
  }
}

Even better, the EventHandlerUtils.MakeWeak method can be turned into an extension method for EventHandler<TEventArgs>. Then we can call it like this:

public class EventSubscriber
{
  public EventSubscriber(EventProvider provider)
  {
    provider.MyEvent += new EventHandler<EventArgs>(MyWeakEventHandler).MakeWeak(eh => provider.MyEvent -= eh);
  }

  private void MyWeakEventHandler(object sender, EventArgs e)
  {
  }
}

Now that's syntax that I can really get behind. If you're wondering how you might use this from within the class that declares an event to make all subscribed event handlers weak, here's one possibility:

public class EventProvider
{
  private EventHandler<EventArgs> m_MyEvent;
  public event EventHandler<EventArgs> MyEvent
  {
    add
    {
      m_Event += value.MakeWeak(eh => m_Event -= eh);
    }
    remove
    {
    }
  }
}

Have fun!

kick it on DotNetKicks.com

posted on Friday, March 23, 2007 10:12:02 AM (Pacific Standard Time, UTC-08:00)  #    Comments [14]

kick it on DotNetKicks.com
Saturday, March 24, 2007 1:22:40 PM (Pacific Standard Time, UTC-08:00)
Fantastic article Dustin -- this is the most elegant solution I have seen to date. I appreciate the narrative and your success at demonstrating where previous solutions came up short.

Regards,
Shaun
www.suva3d.com
Saturday, March 24, 2007 1:43:29 PM (Pacific Standard Time, UTC-08:00)
How does one unhook events using the code specified above without storing a reference to the original instanced weak referenced EventHandler?
ST
Sunday, March 25, 2007 5:50:57 AM (Pacific Standard Time, UTC-08:00)
With this solution, you don't have to store a reference to anything. The UnregisterCallback that you pass to the MakeWeak method will be called the very first time that the weak event handler is invoked after its target has been reclaimed by the garbage collector. Just place the code to unhook the event inside that delegate (as shown in the article).
Sunday, March 25, 2007 12:15:29 PM (Pacific Standard Time, UTC-08:00)
Great article, great narrative :)
Only one question remains in my mind... Why no go ahead with the LCG solution and opt in for the OpenDelegate one?

To me it seems it would ultimately boil down to simpler code (considering the mess the MakeWeak has laid over the code)...

Dustin, your comments would be highly appreciated.

Dan Shechter
Monday, March 26, 2007 12:05:01 PM (Pacific Standard Time, UTC-08:00)
Dan, thanks for your comments. They forced me to do my homework. The real reason that I chose to show both solutions was because the LCG solution is much uglier to look at (IL generation -- shiver!) and is *really* hard to debug. However, I decided to actually run some real performance tests and found that the open instance delegate version is *far* superior to the LCG in performance. In fact, the LCG version is slower than simply calling MethodInfo.Invoke(). I have updated the article to include the results of these tests so that you can see the value.

As for the MakeWeak method making a mess of things, I'm mostly concerned with the client code and when MakeWeak becomes an extension method, I think it actually is superior in elegance. In addition, as an extension method, MakeWeak is more discoverable because it appears in Intellisense for any EventHandler<EventArgs> instance.
Wednesday, April 04, 2007 12:17:44 PM (Pacific Standard Time, UTC-08:00)
Hi Justin,
Thanks for the comments, I was away for a bit so didn't get to follow up.

I will most definitely try to fiddle with this according to your article.
Is there a chance to download your test project somehow?
Dan Shechter
Thursday, April 05, 2007 12:39:45 PM (Pacific Standard Time, UTC-08:00)
Dan you can make the usage much more elegant and pleasent.. basically to use

in VB
AddHandler a.Event, New WeakEventHandler(Of EventArgs)(AddressOf b.OnEvent)
or in C#
a.MyEvent += new WeakEventHandler<EventArgs>(b.OnEvent);

I've nested the interface and the class.. plus I activated the generic object instance with parameters.. which makes it neat and clean.. see code below:

Public Delegate Sub UnregisterEventHandler(Of E As EventArgs)(ByVal eventHandler As EventHandler(Of E))

Public Class WeakReference(Of T)
Inherits WeakReference

Public Sub New(ByVal target As T)
MyBase.New(target)
End Sub

Public Sub New(ByVal target As T, ByVal trackResurrection As Boolean)
MyBase.New(target, trackResurrection)
End Sub

' operator

Shared Widening Operator CType(ByVal value As WeakReference(Of T)) As T

' basic check
If value Is Nothing Then Return Nothing

'If value.IsAlive Then Return Nothing -- we don't ue this as it leads to race condition
Dim _target As Object = value.Target

If _target Is Nothing Then Return Nothing
Return DirectCast(value.Target, T)

End Operator

End Class

Public Class WeakEventHandler(Of E As EventArgs)

Private _weakEventHandler As IWeakEventHandler(Of E)

Public Sub New(ByVal eventHandler As EventHandler(Of E))

Me.New(eventHandler, Nothing)

End Sub

Public Sub New(ByVal eventHandler As EventHandler(Of E), _
ByVal unregisterEventHandler As UnregisterEventHandler(Of E))

' we create the generic type, with the relevant arguments
_weakEventHandler = CType(Activator.CreateInstance( _
GetType(WeakEventHandler(Of ,)).MakeGenericType( _
eventHandler.Target.GetType, GetType(E)), _
eventHandler, unregisterEventHandler), IWeakEventHandler(Of E))

' we create the generic type, with the relevant arguments
'_weakEventHandler = CType(Utils.Reflection.ObjectFactory.CreateGenericObject( _
' GetType(WeakEventHandler(Of ,)), _
' New Type() {eventHandler.Target.GetType, GetType(E)}, _
' eventHandler, unregisterEventHandler), IWeakEventHandler(Of E))

End Sub

Public Overridable Sub Handler(ByVal Sender As Object, ByVal e As E)

' we delegate the handling
_weakEventHandler.Handle(Sender, e)

End Sub

' Internal Classes

Private Interface IWeakEventHandler(Of E1)

Sub Handle(ByVal sender As Object, ByVal e As E1)

End Interface

Private Class WeakEventHandler(Of T, E1 As EventArgs)
Implements IWeakEventHandler(Of E1)

Private Delegate Sub _TargetEventHandler(ByVal target As T, ByVal sender As Object, ByVal e As E1)

Private _target As WeakReference(Of T)
Private _targetHandler As _TargetEventHandler
Private _removeTargetHandler As UnregisterEventHandler(Of E1)

Public Sub New(ByVal eventHandler As EventHandler(Of E1))

Me.New(eventHandler, Nothing)

End Sub

Public Sub New(ByVal eventHandler As EventHandler(Of E1), _
ByVal removeTargetEventHandler As UnregisterEventHandler(Of E1))

' basic check
If eventHandler Is Nothing Then Throw New ArgumentNullException("eventHandler")
If eventHandler.Method.IsStatic OrElse eventHandler.Target Is Nothing Then _
Throw New ArgumentNullException("Event handler must target instance methods.", "eventHandler")

' we save the references
_target = New WeakReference(Of T)(CType(eventHandler.Target, T))
_targetHandler = CType([Delegate].CreateDelegate( _
GetType(_TargetEventHandler), Nothing, eventHandler.Method), _TargetEventHandler)
_removeTargetHandler = removeTargetEventHandler

End Sub

Public Sub Handler(ByVal sender As Object, ByVal e As E1) _
Implements IWeakEventHandler(Of E1).Handle

' idea from http://blogs.msdn.com/greg_schechter/archive/2004/05/27/143605.aspx
' and http://diditwith.net/PermaLink,guid,aacdb8ae-7baa-4423-a953-c18c1c7940ab.aspx

Dim _targetObj As Object = _target.Target
If _targetObj IsNot Nothing Then

_targetHandler.Invoke(_target, sender, e)

ElseIf _removeTargetHandler IsNot Nothing Then

_removeTargetHandler(AddressOf Handler)
_targetHandler = Nothing
_removeTargetHandler = Nothing

End If

End Sub

End Class

' Operator

Overloads Shared Widening Operator CType(ByVal value As WeakEventHandler(Of E)) As EventHandler(Of E)

' basic check
If value Is Nothing Then Return Nothing

' we return
Return (AddressOf value.Handler)

End Operator

End Class

Hope this helps..
Rishi
Friday, April 06, 2007 6:22:16 AM (Pacific Standard Time, UTC-08:00)
Sorry, Dustin not Dan.. just like your name the code wasn't right.. below is an updated version.. and perhaps you can run your tests to see if there are any performance improvements with nested types?

Public Delegate Sub UnregisterHandlerCallback(Of E As EventArgs)(ByVal eventHandler As EventHandler(Of E))

Public Class WeakReference(Of T)
Inherits WeakReference

Public Sub New(ByVal target As Object)
MyBase.New(target)
End Sub

Public Sub New(ByVal target As Object, ByVal trackResurrection As Boolean)
MyBase.New(target, trackResurrection)
End Sub

' Additional

Public Function GetTargetOrNull() As T

Dim _targetObj As Object = MyBase.Target
If _targetObj Is Nothing Then Return Nothing
Return CType(_targetObj, T)

End Function

' operators

Shared Widening Operator CType(ByVal target As T) As WeakReference(Of T)

' basic check
' If target Is Nothing Then Return Nothing - we allow nulls

' we return
Return New WeakReference(Of T)(target)

End Operator

Shared Narrowing Operator CType(ByVal value As WeakReference(Of T)) As T

' basic check
If value Is Nothing Then Return Nothing

'If value.IsAlive Then Return Nothing -- we don't ue this as it leads to race condition
Dim _target As Object = value.Target

If _target Is Nothing Then Return Nothing
Return DirectCast(value.Target, T)

End Operator

End Class

Public Class WeakEventHandler(Of E As EventArgs)

Private _weakHandler As IWeakEventHandler
Private _unregisterCallback As UnregisterHandlerCallback(Of E)

Public Sub New(ByVal eventHandler As EventHandler(Of E))

Me.New(eventHandler, Nothing)

End Sub

Public Sub New(ByVal eventHandler As EventHandler(Of E), _
ByVal unregisterCallback As UnregisterHandlerCallback(Of E))

' basic check
If eventHandler Is Nothing Then Throw New ArgumentNullException("eventHandler")
If eventHandler.Method.IsStatic OrElse eventHandler.Target Is Nothing Then _
Throw New ArgumentNullException("Event handler must invoke instance methods of non-null target.", "eventHandler")

' we create the generic type, with the relevant arguments - note we need to pass two generic types
' also one may use a factory or construct instantiation for this..
Dim _gType As Type = GetType(_WeakEventHandler(Of )).MakeGenericType(GetType(E), _
eventHandler.Target.GetType)
_weakHandler = CType(_gType.GetConstructor(New Type() {eventHandler.GetType}).Invoke( _
New Object() {eventHandler}), IWeakEventHandler)

' we create the generic type, with the relevant arguments - note we need to pass two generic types
'_weakHandler = CType(Utils.Reflection.ObjectFactory.CreateGenericObject( _
' GetType(_WeakEventHandler(Of )), New Type() {GetType(E), eventHandler.Target.GetType}, _
' eventHandler), IWeakEventHandler)

' we save this reference
_unregisterCallback = unregisterCallback

End Sub

' Handler

Public Overridable Sub Handler(ByVal sender As Object, ByVal e As E)

' basic check
If _weakHandler IsNot Nothing Then

' we get it handled
_weakHandler.Handle(sender, e)

' if the target has been collected then we remove the actual handler this
' makes it more light weight, since only the outer shell is left.. and even
' if there is no callback registered we are left with a pretty thin object
If Not _weakHandler.IsTargetAlive Then

' we clear the handler
_weakHandler = Nothing

' we unregister
If _unregisterCallback IsNot Nothing Then
_unregisterCallback(AddressOf Handler) ' we remove the reference
_unregisterCallback = Nothing ' we clear the reference
End If

End If

End If

End Sub

' Inner Constructs

Private Interface IWeakEventHandler

Sub Handle(ByVal sender As Object, ByVal e As E)
ReadOnly Property IsTargetAlive() As Boolean

End Interface

Private Class _WeakEventHandler(Of T)
Implements IWeakEventHandler

Private Delegate Sub _TargetEventHandler(ByVal target As T, ByVal sender As Object, ByVal e As E)

Private _target As WeakReference(Of T)
Private _targetHandler As _TargetEventHandler

Public Sub New(ByVal eventHandler As EventHandler(Of E))

' we save the references
_target = New WeakReference(Of T)(eventHandler.Target)
_targetHandler = CType([Delegate].CreateDelegate( _
GetType(_TargetEventHandler), Nothing, eventHandler.Method), _TargetEventHandler)

End Sub

Public Sub Handler(ByVal sender As Object, ByVal e As E) Implements IWeakEventHandler.Handle

' idea from http://blogs.msdn.com/greg_schechter/archive/2004/05/27/143605.aspx
' and http://diditwith.net/PermaLink,guid,aacdb8ae-7baa-4423-a953-c18c1c7940ab.aspx

Dim _targetObj As T = _target.GetTargetOrNull
If _targetObj IsNot Nothing Then

_targetHandler.Invoke(_targetObj, sender, e)

Else

'IF _unregisterCallback IsNot Nothing _unregisterCallback(AddressOf Handler)
_targetHandler = Nothing
'_unregisterCallback = Nothing

End If

End Sub

Friend ReadOnly Property IsTargetAlive() As Boolean Implements IWeakEventHandler.IsTargetAlive
Get
Return (_targetHandler IsNot Nothing)
End Get
End Property

End Class

' Operator

Overloads Shared Widening Operator CType(ByVal value As WeakEventHandler(Of E)) As EventHandler(Of E)

' basic check
If value Is Nothing Then Return Nothing

' we return
Return (AddressOf value.Handler)

End Operator

End Class

Cheers,
Rishi
Rishi
Friday, April 06, 2007 3:36:42 PM (Pacific Standard Time, UTC-08:00)
Hi Rishi,

The only potential problem that I see with your code is that System.WeakReference has an inheritance demand security permission for unmanaged code. So, unless I'm mistaken, your solution must be granted security permission for unmanaged code as well. That might be a little cumbersome for some users.
Thursday, April 26, 2007 10:32:35 AM (Pacific Standard Time, UTC-08:00)
hi dustin,
if I have some event consumer that implements an interface defining a method with the same signature of EventHandler<E>, and then pass that method to MakeWeak, the performance of the calls drops to something similar to calling through reflection (a little better though). did you see this behavior? can you explain why?
Rui Silvestre
Thursday, April 26, 2007 10:48:02 AM (Pacific Standard Time, UTC-08:00)
just a helper to the problem....

public interface IConsumer { void DoNothing(object o, EventArgs e); }
public class EventConsumer : IConsumer
{
public EventConsumer() { }
public void DoNothing(object o, EventArgs e) { }
public void DoNothing2(object o, EventArgs e) { }
}

then, somewhere:
EventConsumer ec = new EventConsumer();
producer.Event1 += EventHandlerUtils.MakeWeak<EventArgs>(ec.DoNothing);

this gives worse performance that if I use DoNothing2:
producer.Event1 += EventHandlerUtils.MakeWeak<EventArgs>(ec.DoNothing2);
Rui Silvestre
Tuesday, May 08, 2007 4:04:14 AM (Pacific Standard Time, UTC-08:00)
instead of interface methods, try calling any virtual method bcs it is also slower... so, that must be the problem... does this mean that under the hood interface methods and virtual methods are implemented in the same way? it makes sense in the way that interfaces are like pure abstract classes... anyway, the unbound delegate approach gives worse performance in these cases.
Rui Silvestre
Friday, June 08, 2007 6:10:20 AM (Pacific Standard Time, UTC-08:00)
I created an unregister function that can be used to manually remove a listener. There may be cases where a consumer wants to stop listening on its own while it's still alive.

The changes are as follows:

To the IWeakEventHandler<E>, add

WeakReference Target { get; }

Then, to WeakEventHandler<T,E> expose the m_TargetRef member:

public WeakReference Target
{
get { return m_TargetRef; }
}

Then, in the Utils class, here's the code:

public static EventHandler<E> Unregister<E>(EventHandler<E> sourceHandler, EventHandler<E> value)
where E : EventArgs
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Method.IsStatic || value.Target == null)
throw new ArgumentException("Only instance methods are supported.", "value");

if (sourceHandler != null)
{
// look for the weak event handler in the invocation list
foreach (EventHandler<E> evt in sourceHandler.GetInvocationList())
{
IWeakEventHandler<E> weh = evt.Target as IWeakEventHandler<E>;
if(weh != null)
{
object target = weh.Target.Target;
if (target != null && ReferenceEquals(target, value.Target))
{
return weh.Handler;
}
}
}
}

// return the input as the default if we don't find a wrapped event handler
return value;
}

Then from the remove {} code, you can call

m_handler -= EventHandlerUtils.Unregister(m_handler, value);

Or, with an extension method, m_handler -= m_handler.Unregister(value);

Also, along the lines of minimizing the internal workings of this in library code, I made everything except the static methods and UnregisterCallback internal.
Oren Novotny
Friday, July 13, 2007 6:43:31 AM (Pacific Standard Time, UTC-08:00)
One further enhancement is to automatically detect and deal with double-subscribing with an already-weak event.

Suppose I have an event that automatically makes all events weak. As a consumer, if I don't know that and I use MakeWeak to subscribe with a weak event, the next GC will get rid of things as the event handers aren't rooted correctly.

The following code added into the EventHandlerUtils.MakeWeak after the parameter validation will do the trick:

// check to see if we're already weak
if (eventHandler.Method.DeclaringType.IsGenericType && eventHandler.Method.DeclaringType.GetGenericTypeDefinition() == typeof(WeakEventHandler<,>))
{
return eventHandler;
}

This works fine with the unsubscribe method I've posted above

Oren Novotny
Comments are closed.