.NET Weak Event Handlers – Part I

In .NET when you subscribe to an event providing an instance method as the event handler the event source stores a reference to the target instance for which the event handler will be triggered.

In a scenario where you have short-lived event listeners and event sources with a long lifetime this will pose a problem because the strong reference in the event source will become the sole reason these objects don’t get garbage collected. Depending on the memory footprint of each event listener this may lead to problems in the application.

WPF introduced the weak event pattern that specifically targets this situation but involves implementing a new WeakEventManager and the IWeakEventListener interface.

However, I’ll present another approach to solve this problem, more specifically a helper class that creates weak event handlers used to subscribe to any .NET event without forcing a strong reference to the event listener.

This helper class will support two flavors of weak event handlers:

  1. Reflection based;
  2. Delegate based.

They will share a common base class since both will store a WeakReference to the event listener instance to allow it to be garbage collected if no other references exist.

internal abstract class WeakEventHandler<T> where T : EventArgs
{
    public WeakEventHandler(Delegate handler)
    {
        this.WeakRef = new WeakReference(handler.Target, false);
        this.Method = handler.Method;
    }

    protected WeakReference WeakRef { get; set; }

    protected MethodInfo Method { get; set; }

    public abstract void Invoke(object sender, EventArgs e);

    public static implicit operator EventHandler(WeakEventHandler<T> weakHandler)
    {
        return weakHandler.Invoke;
    }

    public static implicit operator EventHandler<T>(WeakEventHandler<T> weakHandler)
    {
        return weakHandler.Invoke;
    }
}

They differ on how the method representing the handler is invoked at runtime. The former uses reflection to invoke the handler…

internal sealed class ReflectionBasedWeakEventHandler<T> : WeakEventHandler where T : EventArgs
{
    public ReflectionBasedWeakEventHandler(Delegate handler)
        : base(handler) { }

    public override void Invoke(object sender, EventArgs e)
    {
        object target = this.WeakRef.Target;

        if (target != null)
        {
            this.Method.Invoke(target, new object[] { sender, e });
        }
    }
}

… while the latter creates an open instance delegate that will then be used to invoke the handler.

internal delegate void OpenInstanceEventHandler<TTarget, TArgs>(
    TTarget target,
    object sender,
    TArgs e) where TArgs : EventArgs;

internal sealed class DelegateBasedWeakEventHandler<TArgs, TListener> : WeakEventHandler where TArgs : EventArgs
{
    public DelegateBasedWeakEventHandler(Delegate handler)
        : base(handler)
    {
        var weakHandler = Delegate.CreateDelegate(
            typeof(OpenInstanceEventHandler<TListener, TArgs>),
            null,
            handler.Method,
            true);

        this.Handler = (OpenInstanceEventHandler<TListener, TArgs>)weakHandler;
    }

    private OpenInstanceEventHandler<TListener, TArgs> Handler { get; set; }

    public override void Invoke(object sender, EventArgs e)
    {
        object target = this.WeakRef.Target;

        if (target != null)
        {
            this.Handler((TListener)target, sender, (TArgs)e);
        }
    }
}

These weak event handler are created through a static helper class. The contract defined by this helper class is provided here while the full implementation with documentation is available at GitHub where you can also find the NUnit unit tests used to test it.

// Implementation available @ GitHub

public static class WeakEventHandlerWrapper
{
 public static EventHandler WrapInDelegateCall(EventHandler handler);

 public static EventHandler WrapInDelegateCall<TListener>(EventHandler handler);

 public static EventHandler<TArgs> WrapInDelegateCall<TArgs>(EventHandler<TArgs> handler) where TArgs : EventArgs;

 public static EventHandler<TArgs> WrapInDelegateCall<TArgs, TListener>(EventHandler<TArgs> handler) where TArgs : EventArgs;

 public static EventHandler<TArgs> WrapInReflectionCall<TArgs>(EventHandler<TArgs> handler) where TArgs : EventArgs;

 public static EventHandler WrapInReflectionCall(EventHandler handler);
}

This is already getting long, so stay tuned for the second part of this article where I will be talking about the performance impact of this technique.

About these ads
Previous Post
Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 27 other followers