Events are an implementation of the observer pattern and consists of two parts:
- A publisher that notifies subscribers when an event happens.
- One or more subscribers that listen for events to happen and then react.
There are three simple steps to using events, which I’ll show below.
1 – Add an event field
The first step is to add an event field to a class with the event keyword and a delegate type (such as the generic EventHandler<T>). Here’s an example of declaring an event field:
public class MessageQueue
{
public event EventHandler<string> MessageAdded;
}
Code language: C# (cs)
Note: This class is the publisher part of the observer pattern.
This is using the EventHandler<string> delegate type. This means when the event is fired, it’s going to send a string (containing event data) to the event listeners.
Delegate types
You can declare your own delegate type to use with the event, but I’d suggest using one of the built-in types to simplify things:
- EventHandler<T>: Use when you need to send event data (of type T) to the event listeners.
- EventHandler: Use when you want to notify event listeners without sending them event data.
EventHandler<T> has the following definition (keep this in mind for the next step):
public delegate void EventHandler<T>(object? sender, T e);
Code language: C# (cs)
2 – Add an event listener
An event listener is a method that is called when the event is fired. Its signature needs to match the event delegate.
Here’s an example of adding an event listener as a lambda for the MessageAdded event added in step 1:
var messageQueue = new MessageQueue();
messageQueue.MessageAdded += (sender, msg) => Console.WriteLine($"Message added: {msg}");
Code language: C# (cs)
Note: This is the subscriber part of the observer pattern.
The lambda’s signature (void (object? sender, string message)) matches the event’s EventHandler<string> delegate. If the signature doesn’t match, you’ll get compiler error CS1593.
You can also add an event listener as a regular method, like this:
var messageQueue = new MessageQueue();
messageQueue.MessageAdded += MessageQueue_MessageAdded;
void MessageQueue_MessageAdded(object? sender, string msg)
{
Console.WriteLine($"Message added: {msg}");
}
Code language: C# (cs)
Note: This how it was done before lambdas were added to C#.
3 – Invoke the event
To actually fire the event and notify the subscribers, you have to invoke the event (like a method) from the publisher class (the one with the event field). Here’s an example:
public class MessageQueue
{
public event EventHandler<string> MessageAdded;
public void Add(string message)
{
MessageAdded?.Invoke(this, message);
}
}
Code language: C# (cs)
Note: Always null-check when invoking an event to avoid a NullReferenceException when there’s no subscribers. The most concise way to do that is with the null conditional operator (?.), as shown above.
Now when you call the Add() method, it fires the event, which notifies all of the subscribers (by calling the event listener methods). Here’s an example to wrap this up:
var messageQueue = new MessageQueue();
//event listener
messageQueue.MessageAdded += (sender, msg) => Console.WriteLine($"Message added: {msg}");
//Add messages to trigger the event
messageQueue.Add("Hello World");
messageQueue.Add("This is an example of using events");
messageQueue.Add("Well, bye");
Code language: C# (cs)
This outputs the following:
Message added: Hello World
Message added: This is an example of using events
Message added: Well, bye
Code language: plaintext (plaintext)