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);
}
}