Re-Thinking C# Events

Shout it kick it on DotNetKicks.com

Back when I was learning C#, I was taught a pattern for events that went something like this:

public class Tribe
{
    // For demonstration only.  Please do not write code like this.
    public class TribesmanAddedEventArgs : EventArgs
    {
        private readonly Tribesman _tribesman;
        public TribesmanAddedEventArgs(Tribesman tribesman)
        {
            _tribesman = tribesman;
        }

        public Tribesman NewTribesman
        {
            get { return _tribesman; }
        }
    }

    public delegate void TribesmanAddedDelegate(object sender, TribesmanAddedEventArgs args);

    public event TribesmanAddedDelegate TribesmanAdded;

    private void OnTribesmanAdded(Tribesman tribesman)
    {
        if(TribesmanAdded != null)
            TribesmanAdded(this, new TribesmanAddedEventArgs(tribesman));
    }
}

Needless to say, this is a pretty awful pattern.  Add a few more events to this class (TribesmanRemoved, TribesmanModified, etc) and your code becomes a complete mess really quickly.

With some of the advances that C# language has made, and the EventHandler<> generic delegate, we can thankfully clean this up a bit:

public class Tribe
{
    public class TribesmanAddedEventArgs : EventArgs
    {
        public TribesmanAddedEventArgs(Tribesman tribesman)
        {
            NewTribesman = tribesman;
        }

        public Tribesman NewTribesman { get; private set; }
    }

    public event EventHandler<TribesmanAddedEventArgs> TribesmanAdded;

    private void OnTribesmanAdded(Tribesman tribesman)
    {
        if(TribesmanAdded != null)
            TribesmanAdded(this, new TribesmanAddedEventArgs(tribesman));
    }
}

Can we take this any further? You bet. The EventArgs classes tend to be very boilerplate. Lets generalize it so we only have to write this code once:

public class EventArgs<PayloadType> : EventArgs
{
    public EventArgs(PayloadType payload)
    {
        Payload = payload;
    }

    public PayloadType Payload { get; private set; }
}

Now that we have that out of the way, the event pattern can be drastically reduced:

public class Tribe
{
    public event EventHandler<EventArgs<Tribesman>> TribesmanAdded;

    private void OnTribesmanAdded(Tribesman tribesman)
    {
        if(TribesmanAdded != null)
            TribesmanAdded(this, new EventArgs<Tribesman>(tribesman));
    }
}

This code is really starting to look better.  Next, through the magic of extension methods, we can eliminate the "OnTribesmanAdded" method as well:

public static class EventExtensions
{
    public static void Fire<T>(this EventHandler<EventArgs<T>> handler, object sender, T payload)
    {
        if(handler != null)
            handler(sender, new EventArgs<T>(payload));
    }
}

There we go!  Now, anywhere in the class that you want to fire the event, you can just call TribesmanAdded.Fire(this, newTribesman) and not worry if the event has been subscribed to. This is a case where you can call a method on a null object safely, because "Fire" is actually a static method.  Even better, the event code in our class can be reduced to this:

public class Tribe
{
    public event EventHandler<EventArgs<Tribesman>> TribesmanAdded;
}

In my opinion, this is the way we should be writing events in C# 3.0 and beyond.  What do you think?

4 Responses to “Re-Thinking C# Events”

  1. Petr says:

    So actually we save just one line “public delegate void TribesmanAddedDelegate”.
    And that’s it?

  2. admin says:

    Petr,

    Once you have the extensions and base classes written in place, it avoids writing three things:

    1. Custom event args
    2. The custom delegate
    3. The firing code (OnTribesmanAdded)

    Essentially, the first block of code gets abstracted to one line of code. It significantly cleans up event-heavy code.

    *Note* this is a pretty old post. Some of these abstractions have been added to the framework since then.

  3. Adam says:

    This was very useful, thank you. What has been added to the framework since your original post?

    In my case I want the custom EventArgs objects–purely a design preference–so I modified your approach by removing the generic EventArgs class and adding a constraint to the Fire extension method:

    public static void Fire(this EventHandler handler, object sender, T args) where T:EventArgs
    {
    if (handler != null)
    handler(sender, args);
    }

    Then I declare:

    public class TribesmanEventArgs { … }

    public EventHandler TribesmanAdded;

    and call:

    TribesmanAdded.Fire(new TribesmanEventArgs() { … } );

  4. frigate says:

    Brian, thank you for interesting post.
    Try to run Code Analyze on this project – you’ll be surprised what rules are broken!

Leave a Reply