Sunday, November 16, 2008

Empty delegate makes event raising easy and thread-safe

I'm currently working on a project that will use some of the guidelines from the WPF Composite Application Guidance. And as I was studying the source code I kept seeing a strange
initialization of events that looked something like this;

public event EventHandler EmptyDelegateEvent = delegate { };

I mean; I do know my way around delegates, but these empty delegates puzzled me. After googling around a bit I found that this was a just a clever way of avoiding null reference exception when raising the event. So instead of this;

public event EventHandler CheckForNullEvent;

void RaiseEvent()
{
// the usuall way
if (CheckForNullEvent != null)
CheckForNullEvent(this, new EventArgs());
}

...you can simply do this;

public event EventHandler EmptyDelegateEvent = delegate { };

void RaiseEvent()
{
// the easy way
EmptyDelegateEvent(this, new EventArgs());
}

When using empty delegates you can simply raise the event without checking for null. Note though, that performance wise it will be better to do the "if not null" check than a "delegate {}". But I ran some performance testing to compare the two, and the performance hit is really not that big of an issue. Even though the performance of the empty delegate was 20-30% slower than the null check, the time it takes to do either of them is miniscule. This is really not where you should put your performance tuning effort. A 'null reference exception', which is much more likely to happen if you forget to check for the null, will have a much bigger impact on the overall performance of your application. Or even worse; if a raise condition occurs. Which again reminds me that the correct way of checking the event for null and at the same time making sure it's thread-safe, is this;

void RaiseEvent()
{
// the thread-safe way
EventHandler copyOfEvent = CheckForNullEvent;
if (copyOfEvent != null)
copyOfEvent(this, new EventArgs());
}

And how many does actually do that? And talking of performance; what about 'developer performance'? Instantiating an empty delegate and no need for null checking or possible raise conditions, means faster coding and less bugs - e.g. better developer performance.

4 comments:

pro-thoughts said...

I saw somewhere a discussion about efficiency of empty delegates approach; they found that it is not good to use if a lot of events are used (an array of EventHandlers or something like that).

On the other hand, there is a nice alternative solution with extension method, described here http://kohari.org/2009/02/07/eventhandler-extension-method/. As it was said in the comments, it is actually thread safe to use even simpler extension method:

public static class EventHandlerExtensions {
public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs {
if (handler != null) handler(sender, args);
}
}

Vladimir Kelman said...

I actually found that efficiency discussion: http://stackoverflow.com/questions/170907/is-there-a-downside-to-adding-an-anonymous-empty-delegate-on-event-declaration

Kjetil Klaussen said...

@pro-thoughts:
Kohari's use of extension a method was really sweet! Didn't think of that one :)

@Vladimir Kelman:
Thanks for the link, and as you can see in the performance test by Boogart the performance hit is really not that high. Yes, the diff in precentage is high, but remember that he's firing 50 million events here. And so what acutally happends with the event is probably much more critical than the performance hit you get by using an empty delegate. And what happends if one of those 50 million event raisings doesn't check for null and raises an exception? Clearly this doesn't happen in the loop in the example, but think of a more realistic scenario where you have event-raising is spread all around your app. One exception raised and you can be assure that the empty delegate will be the winner performance wise.

I guess my point is that if you are really concerned about performance, this is probably not the place where your app will swim or sink.

Vladimir Kelman said...

Right, I think both empty delegate approach and an extension method are good in a regular situation.