C# Events (& Delegates) Tutorial

In this tutorial we learn how to facilitate communication between objects in C# with events.

We cover the two stage process of using delegates and events, and how to declare events. We also do a full breakdown of a real world scenario where events would be used.

Here's a table of contents of what you'll learn in this lesson:
(click on a link to skip to its section)

Let's jump right in.

What is an event

An event is a mechanism for communication between objects. When something happens such as a key press or an error, an event can notify other objects about it.

Delegates and Events

Events are used in two stages. An event is published by the class that contains the event, and classes that accept the event, subscribe to it.

The publisher is the object that contains the definition of the event and the delegate. When the publisher class invokes the event, it notifies the subscribers.

The subscriber is the object that accepts and handles the event. The delegate from the publisher class invokes the handling method of the subscriber class.

How to declare an event

Before we can declare the event, we must declare a delegate type for the event.

To declare an event, we use the event keyword before the delegate type.

Syntax:
// delegate
access_modifier delegate return_type delegate_name(parameter_list);

// event
access_modifier event delegate_type event_name;

We don’t specify a type for the event.

Example:
// delegate
public delegate void AnEvent();

// event
public event AnEvent anEvent;

The delegate becomes the event.

The example below is a real world example, meaning, it’s how you would write events in a real application.

Example:
using System;

namespace EventsAndDelegates
{
    class Program
    {
        static void Main()
        {
            Image image = new Image();
            ImageConverter converter = new ImageConverter(); // publisher
            LogService logger = new LogService(); // subscriber

            // subscribing
            converter.ImageConverted += logger.OnImageConverted;

            converter.Convert(image);

            Console.ReadLine();
        }
    }

    public class Image { }

    public class ImageConverter
    {
        public delegate void ImageConvertedEventHandler(object source, EventArgs args);

        // event based on delegate
        public event ImageConvertedEventHandler ImageConverted;

        public void Convert(Image image)
        {
            Console.WriteLine("Converting, please wait...");
            System.Threading.Thread.Sleep(1000); // simulate conversion

            // conversion finished so call event publisher
            OnImageConverted();
        }

        protected virtual void OnImageConverted()
        {
            if(ImageConverted != null)
            ImageConverted(this, EventArgs.Empty);
        }
    }

    // subscriber
    public class LogService
    {
        public void OnImageConverted(object source, EventArgs args)
        {
            Console.WriteLine("LogService Event: Image has been converted.");
        }
    }
}

The example above may seem a bit confusing, so let’s go through it step by step. The application is an Image Converter application.

Example:
public class Image { }

This class is responsible for loading an image to be converted. To keep things simple we’ve left out its implementation.

Example:
public class ImageConverter
{
    public delegate void ImageConvertedEventHandler(object source, EventArgs args);

    // event based on delegate
    public event ImageConvertedEventHandler ImageConverted;

    public void Convert(Image image)
    {
        Console.WriteLine("Converting, please wait...");
        System.Threading.Thread.Sleep(1000); // simulate conversion

        // conversion finished so call event publisher
        OnImageConverted();
    }

    protected virtual void OnImageConverted()
    {
        if(ImageConverted != null)
            ImageConverted(this, EventArgs.Empty);
    }
}

1. This class is responsible for converting our image, the Convert method takes care of that. To keep things simple we only simulate that an image is converting with a console message and a 1 second delay.

2. Before we can create an event, we need a delegate so we define one called ImageConvertedEventHandler.

Note the parameters of object source and EventArgs args. These are conventions and are not strictly necessary, but in this tutorial we’ll use them so that you know about them.

  • object source is the class that sends the event.
  • EventArgs args is, basically, any additional data we want to send with the event.

Note also, the naming convention is to append EventHandler to the name. So if the name is ImageConverted, the name becomes ImageConvertedEventHandler.

3. The event is of type ImageConvertedEventHandler delegate. That’s why we needed to declare the delegate first.

4. To raise (publish) an event, we need a method to do so. The OnImageConverted method handles the publishing by doing a null check, and then calling the event ImageConverted with the parameters that we included.

  • The object source is the class that sends the event, because we send the event from this class, we can simply specify this.
  • The EventArgs args is the additional data we want to send. To keep things simple we won’t send data here so we can just specify EventArgs.Empty. Normally we would send, for example, other classes or parameters etc.

The .NET recommended convention for event publishing methods is to be protected virtual void and the name should be prefixed with On. We follow the convention in this case so that you know about it.

5. Lastly, in the Convert function, when the image has been converted we call the event publisher so that it can notify other classes that the event has executed.

Example:
// subscriber
public class LogService
{
    public void OnImageConverted(object source, EventArgs args)
    {
        Console.WriteLine("LogService Event: Image has been converted.");
    }
}
  1. The LogService class is going to be one of the subscribers to the event. It’s responsible for logging a message when it receives the communication that the event has executed.
  2. An event handler method will do the logging. It needs the same signature as the delegate, void return type and the two parameters source and args.

Because it’s an event handler we follow the naming convention of prefixing the name with On.

Example:
class Program
{
    static void Main()
    {
        Image image = new Image();
        ImageConverter converter = new ImageConverter(); // publisher
        LogService logger = new LogService(); // subscriber

        // subscribing
        converter.ImageConverted += logger.OnImageConverted;

        converter.Convert(image);

        Console.ReadLine();
    }
}
  1. In the Main() function we create a new instance of the Image class, remember that it’s responsible for loading the image to convert.
  2. We create new instances of the ImageConverter (publisher) and LogService (subsrciber) classes.
  3. To subscribe to the ImageConverted event we use the += operator. The += means we add the OnImageConverted subscriber to the list of methods that subscribe to the ImageConverted event.
  4. Then we call the function that does the actual conversion and publishes the event.

We subscribe to the event before the conversion. We need to be subscribed before the event is published, which is done inside the Convert function.

Summary: Points to remember

  • An event is used to notify objects around it that something has occurred.
  • An event is first published from its own class, then, other classes can subscribe to it.
  • We must declare a delegate type for the event before we can declare the event itself.
  • We declare an event with the event keyword before the delegate type.