An event in C# is a mechanism that allows a class or an object to notify other classes or objects that something has happened.
When an event occurs, the associated methods are called allowing classes to react to the triggered actions.
Events are based on delegates, which are types that encapsulate references to methods. However, events have small differences compared to delegates. One of them is that only the class that has the event can invoke it.
Event syntax
To define an event, first we have to define a delegate type that specifies the method signature that we want to respond to the event. This function is called a “handler”.
On the other hand, we have to declare the event itself. This uses the delegate type that we defined earlier, preceding the keyword event
.
It seems a bit complicated and rigid, but it makes sense 😉. Below we will see that there is a simpler way to do it. But for now, it’s good to understand the basics.
Let’s see an example,
// definition of the function that will handle the event
public delegate void MyEventHandler(string message);
public class Publisher
{
// definition of the event
public event MyEventHandler MyEvent;
public void Notify(string message)
{
MyEvent?.Invoke(message);
}
}
In this example,
- MyEventHandler is the type of function that we want to “respond” to our event. In this case, they are methods that take a
string
as a parameter and do not return a value. - MyEvent is an event itself. It is defined using the previous delegate, preceding the word
event
to indicate that it is an event.
Finally, when “whatever generates the event” has occurred, and we want to trigger it, we do
MyEvent?.Invoke(message)
How to consume events
We already know how to define and trigger Events. Now let’s see how other parts of the program can subscribe to these events to be informed that something has happened.
Subscribing to events
To subscribe to an event, the +=
operator is used, adding an event handling method that matches the event delegate’s signature.
public static void PrintMessage(string message)
{
Console.WriteLine("Event received: " + message);
}
// create a publisher object
var publisher = new Publisher();
// subscribe PrintMessage to the event
publisher.MyEvent += PrintMessage;
// force to trigger the event
publisher.Notify("Hello, event!");
// Output:
// Event received: Hello, event!
In this example, we subscribe the PrintMessage
method in the Subscriber
class to the MyEvent
event of the Publisher
.
In a real project, the Publisher
object would be doing its thing. When it wants to inform that it triggers
Unsubscribing from events
To unsubscribe from an event, the -=
operator is used, removing the event handling method from the event.
publisher.MyEvent -= subscriber.HandleEvent;
Unsubscribing from events is important to avoid memory and dangling reference issues, especially in long-running applications.
Using generic events
The normal syntax for creating Events in C# is a bit “verbose”, because we have to define a delegate with the method signature that will handle the event.
To simplify the syntax, the generic delegate EventHandler<TEventArgs>
was introduced in the .NET Framework version 2.0. It is the syntax you will normally use.
This delegate provides a generic way to handle events, which means it can handle any type of event arguments without having to define a custom delegate for each type of event.
public class Publisher
{
// definition of the event
public event EventHandler<string> MyEvent;
public void Notify(string message)
{
MyEvent?.Invoke(message);
}
}
EventHandler<TEventArgs>
is a useful abstraction that encapsulates a method that takes two parameters:
- The object that triggered the event (
sender
) - An object that contains data related to the event (
TEventArgs
). Usually,TEventArgs
is a class that inherits fromEventArgs
and may contain additional information about the event.
Therefore, the functions that subscribe to the Event would have the following form,
publisher.MyEvent += (s, e) => PrintMessage;
Which is the most common form of EventHandler you will find in C#.
Practical examples
Using events with a button
Although events are not exclusive to the User Interface, it is one of the main uses of events.
Let’s see how we could subscribe a function to the Click Event of a User Interface button.
Button button = new Button();
button.Click += (sender, args) => Console.WriteLine("Click Event has occurred.");
Creating an alarm
This example shows how to use Events outside the scope of the user interface. For example, creating an alarm that triggers an event when activated.
public class Alarm
{
public event EventHandler AlarmActivated; // Defines an AlarmActivated event
public void Activate()
{
AlarmActivated?.Invoke(this, EventArgs.Empty); // Triggers the AlarmActivated event
}
}
Alarm alarm = new Alarm();
// Subscribe to the AlarmActivated event
alarm.AlarmActivated += (sender, args) => Console.WriteLine("Alarm activated!");
// Activate the alarm
alarm.Activate();
Observer pattern with message exchange
This example implements a very simple observer pattern, where a sender sends messages to a receiver.
public class Sender
{
public event EventHandler<string> MessageSent; // Defines a MessageSent event
public void SendMessage(string message)
{
Console.WriteLine($"Message sent: {message}");
MessageSent?.Invoke(this, message); // Triggers the MessageSent event
}
}
public class Receiver
{
public Receiver(Sender sender)
{
// Subscribe to the MessageSent event of the sender
sender.MessageSent += (sender, message) => Console.WriteLine($"Message received: {message}");
}
}
Sender sender = new Sender();
Receiver receiver = new Receiver(sender);
sender.SendMessage("Hello, receiver!");