Weak Event implementation that works for any event type

(Download at the bottom)

Edit: Thanks to Marcelo, in the comments, we found a pretty basic issue with the implementation. Since we were weak-referencing the delegate, it would go away too soon. We changed the code to hold a weak-reference to the target object instead. Enjoy.

Most of the time, when you develop Windows Phone (or WPF, or Silverlight) apps, you don’t need to worry about weak events. However, every now and again, you run into a memory leak issue caused by an event that was not freed.

Blog posts have vexed poetic about the reasoning for needing weak events. You can read about them in many many places. Note – this post assumes you know what weak events are and how they generally work – if you don’t, read some of the linked posts – they are excellent.

There are two drawbacks to all examples I have seen.. Either they require you to implement a special set of classes per event provider (such as INotifyPropertyChanged) or they use reflection and relatively large amounts of code to do the work.

I was looking for something simple and I believe I found it with LINQ expressions – the idea is simple and is implemented in 40 lines (including comments). It does require you to supply disconnect code.

How it works

The idea is relatively simple – generate a delegate that removes itself from the event yo dawg i heard you like delegates so we put a delegate in  - Xzibit memehandler when the source object goes away – that’s the working logic behind all the mechanisms you will see – they all use WeakReference to do that and so does this solution.

The problem all of the solutions are facing is that when you are hooking an event, you need it to be of the correct signature.

My solution looks like this:

MyEvent += EasyWeakEvent.CreateWeakDelegate<EventHandler>(c.Object_MyEvent, x => MyEvent -= x);

 

That’s it…It will work on any event – no need to implement anything special. Pass in the delegate as the 1st parameter and a removal callback as the 2nd parameter and you are good to go.

What happens behind the scenes is that CreateWeakDelegate() uses LINQ expressions to generate a delegate with the appropriate signature and with a WeakReference to the event that was passed in target of the callback that was passed in. It then generates the correct invocation calls and calls the removal callback with itself as a parameter when the delegate target object is no longer alive.

That’s about it.

The code is fairly simple as well:

public class EasyWeakEvent
{
private static readonly PropertyInfo s_isAliveProperty = typeof(WeakReference).GetProperty("IsAlive");
private static readonly PropertyInfo s_targetProperty = typeof(WeakReference).GetProperty("Target");
private static readonly MethodInfo s_createDelegateMethod = typeof(Delegate).GetMethod("CreateDelegate", new Type[] { typeof(Type), typeof(Object), typeof(MethodInfo) });

public static T Create<T>(T handler, Action<T> remover)
{
T generatedDelegate = default(T);
Func<T> returnGeneratedDelegate = () => generatedDelegate;

Delegate delegateHandler = (Delegate)(object)handler;
Expression methodInfoExpression;
MethodInfo methodInfo = delegateHandler.Method;

methodInfoExpression = Expression.Constant(methodInfo, typeof(MethodInfo));
object target = delegateHandler.Target;
var weakTarget = new WeakReference(target);

var parameters = methodInfo.GetParameters();

Expression[] argArray = new Expression[parameters.Length];
ParameterExpression[] paramExpressions = new ParameterExpression[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
paramExpressions[i] = Expression.Parameter(parameters[i].ParameterType, parameters[i].Name);
argArray[i] = paramExpressions[i];
}


var weakTargetExpression = Expression.Constant(weakTarget, typeof(WeakReference));

// weakHandler.IsAlive
var isAliveExpression = Expression.MakeMemberAccess(weakTargetExpression, s_isAliveProperty);

// weakHandler.Target
var weakReferenceTargetExpression = Expression.MakeMemberAccess(weakTargetExpression, s_targetProperty);

// CreateDelegate(type, weakReferenceTarget, Method)
MethodCallExpression weakReferenceDelegateCreateExpression = Expression.Call(
s_createDelegateMethod,
Expression.Constant(delegateHandler.GetType(), typeof(Type)),
weakReferenceTargetExpression,
methodInfoExpression);

// (T)CreateDelegate(...)
var weakReferenceTargetCastExpression = Expression.TypeAs(weakReferenceDelegateCreateExpression, typeof(T));

// ((T)CreateDelegate(...))(p0, p1, p2)
var delegateInvokeExpression = Expression.Invoke(weakReferenceTargetCastExpression, argArray);

// returnGeneratedDelegate()
var returnGeneratedDelegateExpression = Expression.Invoke(Expression.Constant(returnGeneratedDelegate, typeof(Func<T>)));

// remover(generatedDelegate)
var removerExpression = Expression.Invoke(Expression.Constant(remover, typeof(Action<T>)), returnGeneratedDelegateExpression);


// weakHandler.IsAlive ? ((T)weakHandler.Target)(p0, p1, p2) : remover(returnGeneratedDelegate());
var conditionExpression = Expression.Condition(isAliveExpression, delegateInvokeExpression, removerExpression);

var impl = Expression.Lambda(typeof(T), conditionExpression, paramExpressions).Compile();
generatedDelegate = (T)(object)impl;
return generatedDelegate;
}
}

Download

About these ads
This entry was posted in Uncategorized. Bookmark the permalink.

6 Responses to Weak Event implementation that works for any event type

  1. Marcelo says:

    There is some problems with this implementation as you use the closure as the Target of the WeakReference. Sometimes the compiler will generate a static class (method) to represent the closure and the “remover” will never be called.

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