Geeks With Blogs

News Locations of visitors to this page
Brian Genisio's House of Bilz
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?

Posted on Sunday, February 15, 2009 9:38 PM | Back to top


Comments on this post: Re-Thinking C# Events

# re: Re-Thinking C# Events
Requesting Gravatar...
I use something similar, except the event raiser extension method checks for cross-thread-UI issues (both WinForms and WPF), and marshals over the call to each handler if it is needed.

Some may argue this is the consumer's duty, which is fine. Others use a "SynchronizingObject" pattern like the System.Timers.Time does, which is also fine. I actually have two methods: Raise() and RaiseSafely() - the first one does not check for cross-thread stuff; but we tell everyone to use the RaiseSafely because worse-case scenario is an extra IF check for each subscriber (although it does use reflection, which is expensive).

I use the extension method name "Raise" instead of "Fire" because "Fire" implies something BIG is going to happen when in reality maybe nothing at all is going to happen, such as when there are no subscribers.
Left by Joe on Feb 16, 2009 7:18 AM

# re: Re-Thinking C# Events
Requesting Gravatar...
Joe,

Thanks for the feedback. Good point on the cross-thread-UI thing. In some situations, that type of code is needed, and makes a good extension method as well.

As for Raise vs Fire, I chose "Fire" because I didn't want to overload the word "Raise", which I associate with exceptions. I always consider events to be fired and exceptions to be raised... but we are really just talking about semantics at this point.

Thanks,
Brian
Left by Brian Genisio on Feb 16, 2009 7:27 AM

# re: Re-Thinking C# Events
Requesting Gravatar...
I like the generic-ized EventArgs implementation.

I think a drawback of the Fire extension method is that I usually see that "OnSomeEvent" method declared as virtual, allowing an inheriting class to "override" the event (there is no way to declare an event as virtual).
Left by Daniel Pratt on Feb 16, 2009 8:58 AM

# re: Re-Thinking C# Events
Requesting Gravatar...
Daniel,

Yeah, I had thought of that. In those cases, you can just create a virtual OnSomeEvent method... but in most cases, this is unnecessary.

Thanks for the comment!
Brian
Left by Brian Genisio on Feb 16, 2009 9:04 AM

# re: Re-Thinking C# Events
Requesting Gravatar...
Hi,

If you declare your event like:

public event EventHandler<EventArgs<Tribesman>> TribesmanAdded = delegate {};

then you never need to worry about the null check. That would mean you wouldn't need the extension method at all.

Cheers,

Geoff
Left by Geoff Taylor on Feb 17, 2009 1:52 PM

# re: Re-Thinking C# Events
Requesting Gravatar...
It gets even shorter/nicer if you define the following delegate:

public delegate void Event<T>(object sender, EventArgs<T> e);

and rewrite the Fire extension to use Event<T>.

In that case you could write

public event Event<string> NameChanged;

instead of the quite verbose

public event EventHandler<EventArgs<string>> NameChanged;

and raise it as

NameChanged.Fire(this, "NewName");

And I would rename 'Fire' to 'Raise' ;-)
Left by Jochen Zeischka on Feb 19, 2009 5:37 AM

# re: Re-Thinking C# Events
Requesting Gravatar...
I would also return the event args from the Fire method. For example for CancelEventArgs<T> scenarios.

Very, very nice extension! Congratulations!
Left by Jochen Zeischka on Feb 19, 2009 5:46 AM

# re: Re-Thinking C# Events
Requesting Gravatar...
What do I think?

I think this makes sense. The only problem I see is with people who need to support events in a multi-threaded app as the compiler emitted add/remove accessors are not thread safe.
Left by Jim on Apr 23, 2009 2:19 PM

# re: Re-Thinking C# Events
Requesting Gravatar...
Doesn't work, keep getting this error --
The non-generic type 'System.EventArgs' cannot be used with type arguments
Left by Craig on May 20, 2010 4:35 AM

Your comment:
 (will show your gravatar)


Copyright © Brian Genisio's House Of Bilz | Powered by: GeeksWithBlogs.net | Join free