deft flux

A portal into the creative workings of David Meyer

Safely invoking event delegates

Yesterday, my friend and I had an interesting conversation.  He was venting about the code one of his co-workers wrote which caused a problem he spent some time tracking down.  Here's the gist of it:

void RaiseEvent(EventArgs e) { Event(this, e); }

Now most .NET developers should immediately notice what's wrong here.  I certainly hope they do.  What if Event has not been subscribed to?  Then it is null, and invoking it will cause a NullReferenceException.  So this is how I have always invoked event delegates:

if (Event != null) { Event(null, e); }

But one thing my friend pointed out, which was actually new to me, is that the above code is actually not thread-safe, because another thread can unsubscribe from Event after the check for null and before the invocation.  So to make it thread safe, you would first copy the delegate reference to a local variable:

EventHandler handler = Event; if (handler != null) { handler(this, e); }

Now I thought, "This is a lot of code to write just to raise an event.  How can I make this shorter?"  So thanks to C# 3.0 extension methods and lambda expressions, I was able to reduce it to this:

Event.SafeInvoke(d => d(this, e));

Here's the extension method:

public static void SafeInvoke<T>(this T obj, Action<T> action) where T : class { if (obj != null) { action(obj); } }

This accomplishes both objectives: 1) Copy the delegate reference.  This is implicitly done when it is passed as a method parameter.  And 2) Check the delegate for null.  Now I know calling Event.SafeInvoke if Event is null looks a little odd, because you would think it would cause a NullReferenceException, but this is actually not the case with extension methods.  You can call an extension method on null.  This is because the compiler translates this to:

SafeInvoke(Event, d => d(this, e));

Now, unfortunately, you cannot set a restraint on generic types to restrict them to the Enum or Delegate types.  (This makes absolutely no sense to me, I think you should be able to.)  So the above extension method applies to all reference types.  At first I was afraid this would clutter the IntelliSense for non-delegate types, but actually, this extension method would be useful for any reference type.  What if, for instance, you want to call a method on an object but only if it isn't null?

obj.SafeInvoke(o => o.MyMethod());

And the best part is that this will also make the check for null thread-safe if the object in question has a greater scope than a local variable!  So, to wrap up this post, here are all the extension methods I wrote to support both return values and dispatching to another thread (feel free to plagiarize!):

public static void SafeInvoke<T>(this T obj, Action<T> action) where T : class { if (obj != null) { action(obj); } } public static TResult SafeInvoke<T, TResult>(this T obj, Func<T, TResult> func) where T : class where TResult : class { return obj.SafeInvoke(func, null); } public static TResult SafeInvoke<T, TResult>(this T obj, Func<T, TResult> func, TResult defaultResult) where T : class { TResult result = defaultResult; if (obj != null) { result = func(obj); } return result; } public static void SafeDispatch<T>(this T obj, Action<T> action) { if (obj != null) { action.BeginInvoke(obj, null, null); } }

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by deftflux on Monday, August 11, 2008 9:49 AM
Permalink | Comments (4) | Post RSSRSS comment feed

Comments