Events

Events

The Crash Course

  • Events are a simple and easy way for one section of code to notify other sections that a certain condition has been met.
  • Events rely on delegates to do their work, so a basic understanding of how delegates work is very helpful.
  • To create an event inside of a class, you would use code similar to this: public event EventHandler PointChanged; In this case EventHandler is the name of the delegate event handlers must use, and PointChanged is the name of the event.
  • You raise events by first checking to make sure that there are event handlers attached, then raising the event: if(PointChanged != null) { PointChanged(this, EventArgs.Empty); }
  • To attach a method (it must meet the requirements of the delegate being used) to an event, you use the += operator: PointChanged += PointChangedHandler;
  • To detach a method from an event, you use the -= operator: PointChanged -= PointChangedHandler;

Introduction

One of the cool things about C# is a feature called events. Events allow classes to notify others when something specific occurs. This is extremely common in GUI-based applications, where there are things like buttons and checkboxes. These things have the ability to indicate when something of interest happens, like the button was pressed, or the user checked the checkbox, and other classes will be notified of the change, and can handle the event in whatever way they need to.

In the previous tutorial, we talked about delegates. Delegates are going to be a key part of events, so if you don't have at least a basic understanding of delegates, you should jump back to there and learn a bit about them. We'll be using delegates to receive the notifications from events.

Defining an Event

Our first step will be to create an event. Let's say we have a class that represents a point in two dimensions. So it has an x-coordinate or value, and a y-coordinate or value. This class might look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Events
{
    public class Point
    {
        private double x;
        private double y;
 
        public double X
        {
            get
            {
                return x;
            }
            set
            {
                x = value;
            }
        }
 
        public double Y
        {
            get
            {
                return y;
            }
            set
            {
                y = value;
            }
        }
    }
}

To define an event inside a class, we'll need to add only a single line of code, which gets added in as a member of the class:

public event EventHandler PointChanged;

So now our code should look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Events
{
    public class Point
    {
        private double x;
        private double y;
 
        public double X
        {
            get
            {
                return x;
            }
            set
            {
                x = value;
            }
        }
 
        public double Y
        {
            get
            {
                return y;
            }
            set
            {
                y = value;
            }
        }
 
        public event EventHandler PointChanged;
    }
}

Like other members (properties, methods, and instance variables) we can use public, protected, or private for events, but typically, public is exactly what you're looking for.

We use the event keyword, so that the C# compiler knows we're defining an event.

Next we specify the delegate type that other places will use to handle the events. So remembering how delegates work, we're basically saying here the parameters and return type required of any method that is going to be notified of the event. In this case, we use a delegate created by the guys over at Microsoft that is a really basic delegate called EventHandler. This delegate has a return type of void and has two parameters, an object, which is the "sender" of the event, and an EventArgs object. The EventArgs object, by the way, is really simple.

We could, of course, create our own delegate and use it instead of the EventHandler delegate, but for now, this is as good of an option as any. If you want something more specific, then you'll need to create your own delegate.

And by the way, it is pretty common practice to follow this same pattern. You'd create a different "Args" class (like, say, ButtonEventArgs) that is derived from the EventArgs class, and adds in any information that you'd want (like a reference to the button that was pressed, or whatever), and then you have a matching delegate (in our case, ButtonEventHandler) that takes two parameters, the object that is the sender, and the "Args" class you made (ButtonEventArgs, in our case). You're not required to follow this pattern, but you'll see it time and time again, when you use things like Windows Forms and XNA.

Raising an Event

Now that we've got an event, we'll need to also add in a few lines of code to "raise" the event when the right conditions occur.

Now, don't add this into your code (yet) but take a look at the following piece of code, which does the work of raising an event:

// This code "raises" the PointChanged event, but it is
// best to do this in its own method, as we'll see in a second.
if(PointChanged != null)
{
    PointChanged(this, EventArgs.Empty);
}

This little bit of code is pretty simple. We check to see if the event is null. If the event is null, this means that there are no event handlers attached to the event. (In a second, we'll see how to attach event handlers.) Raising an event with no event handlers results in a NullReferenceException, so we need to check this before we raise the event. Once we know the event has event handlers attached to it, we can go ahead and raise the event by calling the event with the parameters needed by the delegate—in this case, a reference to the sender (this) and an EventArgs object (though we're just using the static EventArgs.Empty object in this particular case).

Like I said, though, it is usually best to put this code in its own method, and it is very common to name these methods the same thing as the event, but with the word "On" attached at the beginning. OnPointChanged, in our particular case. Note that it is not required to put this code in a method, nor is it required to name it "On…", but that is the typical process.

So we need to add the following method to our code:

public void OnPointChanged()
{
     if(PointChanged != null)
    {
        PointChanged(this, EventArgs.Empty);
    }
}

Now, whenever we detect that the conditions of the event have been met, we want to actually call this method, to raise the event.

In the particular case that we're working on, since we want an event for any time the point changes, we'll want to call this method when the value of X gets set, or when the value of Y gets set.

To accomplish this, we'll simply add a method call to the setters of both of these properties:

public double X
{
    get
    {
        return x;
    }
    set
    {
        x = value;
        OnPointChanged();  // Add the method call here.
    }
}
 
public double Y
{
    get
    {
        return y;
    }
    set
    {
        y = value;
        OnPointChanged();  // Add the method call here.
    }
}

So now, our completed code, with the declared event, a method to raise the event, and the code to actually trigger the event when the right conditions are met will look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Events
{
    public class Point
    {
        private double x;
        private double y;
 
        public double X
        {
            get
            {
                return x;
            }
            set
            {
                x = value;
                OnPointChanged();
            }
        }
 
        public double Y
        {
            get
            {
                return y;
            }
            set
            {
                y = value;
                OnPointChanged();
            }
        }
 
        public event EventHandler PointChanged;
 
        public void OnPointChanged()
        {
            if(PointChanged != null)
            {
                PointChanged(this, EventArgs.Empty);
            }
        }
    }
}

Attaching and Detaching Events

Now that we've got our Point class all set up with an event for whenever it changes, we're going to want to know how to attach a method as an event handler for the event we've created.

Remember that events are based on delegates, so we'll need a method that matches the needs of the delegate we're using—EventHandler in this case. The EventHandler delegate we're using requires a void return type, and two parameters, an object that represents the "sender", or the thing that triggered the event, and an EventArgs object, which has specific information about the event being raised. (In our particular case, this isn't going to hold a whole lot of information, but in other events that you create, it might.

So somewhere else (perhaps in the Main class, but anywhere, really) we can create a method to handle the event that looks something like this:

public void HandlePointChanged(object sender, EventArgs eventArgs)
{
    // Do something intelligent when the point changes.  Perhaps redraw the GUI,
    // or update another data structure, or anything else you can think of.
}

It is a simple task to actually attach an event handler to the event. Again, the following code can go anywhere you need it to go:

Point point = new Point();
 
// Here's the real important line:
point.PointChanged += HandlePointChanged;
 
// Now if we change the point, the PointChanged event will get raised,
// and HandlePointChanged will get called.
point.X = 3;

The key line there is the one that attaches the handler to the event: point.PointChanged += HandlePointChanged;. The += operator can be used to add the HandlePointChanged method to our event. If you try to attach a method that doesn't match the needs of the delegate the event uses, you'll get an error when you go to compile your program.

You can attach a many event handlers to an event as you want.

It is also possible to detach event handlers from an event. This is important to do, when you no longer need to know about the event. Without detaching events, depending on how you've organized your code, it is very easy to end up with the same event handler attached to an event multiple times, which best case scenario, slows things down a lot, or worst case scenario, breaks your program.

To detach an event, you simply use the -= operator in a manner very similar to what we saw with attaching events:

point.PointChanged -= HandlePointChanged;

After this code executes, HandlePointChanged will no longer be notified when the PointChanged event occurs.

What's Next?

Events can be extremely powerful because they are a convenient way for one section of code to notify other sections that something specific has happened. This is a very common need, so events are a great solution in many cases.

The next thing we're going to tackle is a quick introduction to threading in C#. This means will allow us to run multiple sections of code at the same time, speeding things up.