If you've ever found .NET events a bit confusing, here are four simple steps that will get anyone up and running.

1. Define the data our event will send.

Events will typically have some information associated with them. The .NET Framework provides an EventArgs class, which serves as the foundational class for all event data in the .NET Class Library. It's recommended that we use it as the base class for event data when creating our own events.

class LocationChangedEventArgs : EventArgs
{
    public LocationChangedEventArgs(DateTime inTime, double inLatitude, double inLongitude)
    {
        mTime = inTime;
        mLatitude = inLatitude;
        mLongitude = inLongitude;
    }

    private double mLatitude;
    public double Latitude
    {
        get { return mLatitude; }
    }

    private double mLongitude;
    public double Longitude
    {
        get { return mLongitude; }
    }

    private DateTime mTime;
    public DateTime Time
    {
        get { return mTime; }
    }
}

If an event doesn't pass any information, it can still be a good idea to derive an EventArgs-based class for the event. Putting it in place from the start makes it easy to add data to the event in the future without needing to refactor all the code that uses the event. Instead of refactoring, we can simply add members to our subclass of EventArgs.

2. Declare the event.

Now that we've defined the data being sent for our event, we need to declare the actual event. Event declarations are done using the event keyword followed by a delegate type and the event name. If we follow the Microsoft guidelines, we can specify our delegate type using the generic EventHandler class. All we need to do is specify our EventArg-based class type for the EventHandler. (More will be said about delegates below.)

class TrackingBeacon
{
    public event EventHandler<LocationChangedEventArgs> LocationChanged;
}

3. Raise the event.

Once the event has been declared, we just need to "raise" the event by passing it parameters that match the delegate type. Using the generic EventHandler class as the delegate type means that we will pass two parameters through our event, just like all the events within the .NET Framework. The first parameter will be an object that specifies the sender of the event. The second parameter will be an instance of our EventArgs-based class.

class TrackingBeacon
{
    public event EventHandler<LocationChangedEventArgs> LocationChanged;

    // If we raise the event from within a virtual method, a derived class
    // will be able to override our event behavior.
    protected virtual void OnRaiseLocationChangedEvent(LocationChangedEventArgs e)
    {
        // We can work with a copy of the event in case we
        // gain or lose subscriptions while calling any
        // event handlers.
        EventHandler<LocationChangedEventArgs> handler = LocationChanged;

        // The handler will be null if no event handlers
        // have been added.
        if (handler != null)
        {
            handler(this, e);
        }
    }

    // A method within our class can raise an event
    // by creating an instance of our EventArgs class
    // and calling our method to raise the event.
    public void UpdateLocation(double inLatitude, double inLongitude)
    {
        if ((mLatitude != inLatitude) || (mLongitude != inLongitude))
        {
            mLatitude = inLatitude;
            mLongitude = inLongitude;
            OnRaiseLocationChangedEvent(new LocationChangedEventArgs(DateTime.Now, mLatitude, mLongitude));
        }
    }
}

4. Subscribe to the event.

We accomplish subscription to an event by "adding" an event handler to an event. To complete our example, we will create a Monitor class that subscribes to the LocationChanged event of a TrackingBeacon.

class Monitor
{
    private TrackingBeacon mBeacon;

    public Monitor(TrackingBeacon inBeacon)
    {
        mBeacon = inBeacon;

        // Adding an event handler is accomplished through
        // the event's += operator.
        mBeacon.LocationChanged += HandleLocationChanged;
    }

    // The event handler will be called when the
    // TrackingBeacon raises the event.
    private void HandleLocationChanged(object sender, LocationChangedEventArgs e)
    {
        LogLocation(e.Time, e.Latitude, e.Longitude);
    }
}

And that's it! Define. Declare. Raise. Suscribe. Now we have an event!

Delegates

Oh, yes. Aren't we supposed to use the delegate keyword somewhere?

A delegate type is simply a data type that specifies what our event handling method will look like. Instead of using the EventHandler type, we could have defined a delegate type as:

public delegate void LocationChangedEventHandler(object sender, LocationChangedEventArgs e);

Then we would have defined our event as:

public event LocationChangedEventHandler LocationChanged;

EventHandler simply provides a small shortcut (assuming we want to follow the Microsoft guidelines for events). If we want to depart from those guidelines, creating our own delegate type is always an option. (It is also necessary when targeting a version of the .NET Framework that does not support Generics.)

"new" Subscriptions

When we add a handler to an event, aren't we supposed to use new to add an object to the event?

Initial versions of C# required us to add event handlers by explicitly wrapping the handler in a delegate object like so:

    mBeacon.LocationChanged += new LocationChangedEventHandler(HandleLocationChanged);

With the release of C# 2.0, we can simply add the method without creation of the delegate object and C# takes care of everything for us.

Additional Information:
Event Design (MSDN, Framework Design Guidelines)
Events (MSDN, C# Programming Guide)