Broaden Your Event Horizons, part 1

Raising Better Events For Fun And Profit

I use the snot out of events. I use them to future-proof my controls and components. They are my secret weapon when I am untangling spaghetti code. I even sprinkle them in my soup.

In fact, I am reasonably sure that developers who don’t use the snot out of events are the number one cause of spaghetti code. Straight out of the snippet box, however, coding an event in .NET is a bit of an anemic experience. So today we’re going to pull our socks up and spice up the Event coding process, so you too can stop referencing that Form instance in your business logic.

The Chef Boyardee Version

For literary irony‘s sake, let’s say we have a class named Spaghetti, with a method named Plate(). An instance of Spaghetti is going to be Plated by a different component than the one that is going to consume it, so we want the Spaghetti to raise an event to indicate that it has been Plated. We whip out the quickest and dirtiest of events, like this:

public class Spaghetti
{
    public event EventHandler Plated;

    public void Plate()
    {
        // TODO: plate the spaghetti!
    }
}

At the end of the Plate() method’s code, we raise it like this:

public void Plate()
{
    // TODO: plate the spaghetti!

    // raise the plated event
    if (this.Plated != null)
        this.Plated(this, new EventArgs());
}

Over in the HungryDude class, we can now attach a handler method for this event, so we know when our spaghetti dinner is Plated:

public class HungryDude
{
    public HungryDude()
    {
        // instantiate my dinner
        Spaghetti dinner = new Spaghetti();

        // listen for the dinner bell
        dinner.Plated += new EventHandler(dinner_Plated);
    }

    void dinner_Plated(object sender, EventArgs e)
    {
        // TODO: yum
    }
}

While this may mean that it’s Miller Time for the Spaghetti developer, it leaves the HungryDude developer a little high and dry. Granted, it’s way better than no event being raised at all, but by declaring the event with the standard System.EventHandler and System.EventArgs, we have provided no additional data to the handling method about what has happened. Also, by raising the event with a direct call to the event itself, the Spaghetti developer has pretty well missed the bus on enabling extensibility.

Allow me to demonstrate by upgrading our simple event to a richer model.

The Louis’ Italian American Restaurant Upgrade

The first issue we should address is that there we’re not providing any level of detail about the event to the handler. While this is not always necessary, it’s better to have and not need than to need and not have. That goes double for distributed development teams, and triple for component developers who get hit by busses, presumably while trying not to miss them.

The best way to ensure that developers who consume our components have enough event-related data long after we have shuffled off this mortal coil is by extending the System.EventArgs class, and adding our own custom properties and a constructor to match:

public class PlatedEventArgs : EventArgs
{
    public bool IsCold { get; set; }
    public PlatedEventArgs(bool isCold)
    {
        this.IsCold = isCold;
    }
}

At this point we could get away with passing an instance of PlatedEventArgs to our event’s ‘e’ argument. However, any code handling the event would only see the EventArgs part if it did an ugly cast up to PlatedEventArgs. In order to make sure that the handler always presents our custom arguments to consuming code, we need to declare a custom handler delegate, too.

public delegate void PlatedHandler(object sender, PlatedEventArgs e);

Our original event was declared as an EventHandler, so now we must change it to be declared as our new handler’s type:

public event PlatedHandler Plated;

The handler code for our event now takes on the signature of our custom EventHandler, including our custom EventArgs:

void dinner_Plated(object sender, Spaghetti.PlatedEventArgs e)
{
    if (e.IsCold)
    {
        // TODO: send it back
    }
    else
    {
        // TODO: yum
    }
}

The next upgrade we should make is to provide a more extensible component by wrapping up our event-raising code in an overridable method:

protected virtual void OnPlated(PlatedEventArgs e)
{
    if (this.Plated != null)
        this.Plated(this, e);
}

Not only does this reduce the cumbersome null-checking code for raising the event to a single method call:

public void Plate()
{
    // TODO: plate the spaghetti!

    // raise the plated event
    OnPlated(new PlatedEventArgs(false));
}

…but it also directly exposes the raising of this event to any component that may inherit from ours in the future. For example, if the inheriting component does not want the event to be raised, it can override it with an empty method:

public class ExtremelyRunnySpaghetti : Spaghetti
{
    // ...

    protected override void OnPlated(PlatedEventArgs e)
    {
        // this spaghetti needs to be in a bowl,
        // and should never be plated!
        // base.OnPlated(e);
    }
}

Or, it can raise the event conditionally:

public class PartTimeNinjaSpaghetti : Spaghetti
{
    public bool NinjaMode { get; set; }

    // ...

    protected override void OnPlated(PlatedEventArgs e)
    {
        if (this.NinjaMode)
        {
            // quiet like the night
        }
        else
        {
            // raise the Plated event
            base.OnPlated(e);
        }
    }
}

In part 2 of this article, I will demonstrate how to take the heavy lifting out of all of this rich event madness by adding a rich event code snippet in Visual Studio.

Stay tuned!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s