Popular Categories
« One does not have to be a pro developer to make a lot of money | Main | BFG: DSL configuration syntax for Autofac IoC Container »
Tuesday
Sep232008

Some tips on writing event handling code in C# .NET

Shahar has posted on using EventArgs.Empty. Let's be a bit more efficient with the events than that.

How often do you write repetitive code like this?

public delegate void MessageEventHandler(object sender, MessageEventArgs e);

[Serializable]
public sealed class MessageEventArgs : EventArgs
{
  // some class contents
}
// ...
event MessageEventHandler OnMessageArrived;

private void RaiseOnMessageArrived(string message)
{
  // BTW, what about thread-safety of this call?
  if (OnMessageArrived != null)
  {
    OnMessageArrived(this, new MessageEventArgs(message));
  }
}

Let's drop the entire RaiseOnMessageArrived method, MessageEventArgs class, the delegate and replace them by:

event EventHandlerFor<string> OnMessageArrived = (sender,e) => {};

OnMessageArrived.Raise(this, message)

In order to do that we have to follow simple rules.

1. Apply good citizenship rules to events, too and always initialize them with empty handler. This rule is optional, but new syntax makes this a breeze with (sender, args) => {};

2. Leverage generics were it is appropriate (i.e. see EventHandlerFor{T} and EventArgs{T} from the Lokad.Shared library within Photon.NET):

/// <summary>
/// Represents the method that will handle a typed event.
/// Associated data is read-only
/// </summary>
public delegate void EventHandlerFor<T>(object sender, EventArgs<T> args);

[NoCodeCoverage]
[Serializable]
public class EventArgs<T> : EventArgs
{
  /// <summary> Read-only data associated with the event </summary>
  public T Data { get; private set; }

  /// <summary>
  /// Initializes a new instance of the <see cref="EventArgs{T}"/> class.
  /// </summary>
  /// <param name="data">The data.</param>
  public EventArgs(T data)
  {
    Data = data;
  }
}

3. Use extension methods to streamline your code. One of these extensions might look like this one (there are more extensions in Lokad.Shared):

public static void Raise<T>(this EventHandler<EventArgs<T>> handler,
  object sender, T data)
{
  Enforce.ArgumentNotNull(handler, "handler");
  handler(sender, new EventArgs<T>(data));
}

Note: if you do not initialize your events with non-null empty block, the Enforce call has to be replaced with null check.

Update: here's one thing reminded by Denis. If you follow the good citizenship principle and initialize your event with non-null empty block (and do not do null assignments), then you are pretty much thread-safe. Enforce statement can be ignored from the thread-safety point of view, since it is just an sanity check to help you enforce good citizenship code. It even could be dropped from the production code by simply adding conditional attribute on the method.

But if you allow your event statements to be null and use the event field directly, then you have to take possible threading issues into account.

By the way, there is an open source Lokad Shared Libraries project that contains these helper routines (as well as lots of other things that help me in my everyday C# development)

Reader Comments (6)

It's nice solution!
With extension methods we can make very interesting things.
With this little correction your code is thread safe:
public static void Raise(this EventHandler<EventArgs> handler,
object sender, T data)
{
var safeHandeler = handler;
Enforce.ArgumentNotNull(safeHandler, "handler");
safeHandler(sender, new EventArgs(data));
}

September 23, 2008 | Unregistered CommenterDenis

Denis,

Thank you for the comment.

Well, the code is originally thread-safe anyway. That's because Enforce statement is just a short-cut assertion (sanity check) statement used for verifying the the developer is really sticking to the "initialize your events with empty handler" rule.

In the production builds the assertion could be disabled completely everywhere by adding conditional attribute to the Enforce.ArgumentNotNull method.

September 23, 2008 | Unregistered CommenterRinat Abdullin

[...] Speaking about the thread safety of working with the event fields. [...]

Yes, Enforce guard 'null' value for 'handler' argument. But EventHandler is multicast delegate.
Look:
public event EventHandler TestEvent;
[TestMethod]
public void TestMethod1()
{
TestEvent += TestMethod;
Assert.IsNotNull(TestEvent);
TestEvent -= TestMethod;
Assert.IsNull(TestEvent);
}
void TestMethod(object sender, EventArgs e) {}

'handler' argument can be null after Enforce.ArgumentNotNull guarding, if all subscribers suddenly remove their delegates.

September 24, 2008 | Unregistered CommenterDenis

But, you right!!!
Extension method is just a static method. And when you call TestEvent.Raise(...) (periviouse comment example), then 'handler' argument is copy of real event MulticastDelegate. And TestEvent can be changed, but 'handler' argument never changed.

September 24, 2008 | Unregistered CommenterDenis

Yes, exactly)

September 24, 2008 | Unregistered CommenterRinat Abdullin
Comments for this entry have been disabled. Additional comments may not be added to this entry at this time.