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)
Published: September 23, 2008.
馃 Check out my newsletter! It is about building products with ChatGPT and LLMs: latest news, technical insights and my journey. Check out it out