Implement And Use Events in C#

by Kenji Elzerman
Events in CSharp

Using events in C# isn’t something new. It’s been here for as long as I can remember. When people think of events they usually refer to WinForms, which isn’t wrong. Each control in WinForms uses events. Think for instance about a button with a click event.

But WinForms isn’t the only type that uses events in C# We can also use events with plain classes or console applications.

Events In C# Explained

So, what are these so-called events? We use events to send a signal to a subscriber. Something happens and other classes can react to it.

For example, an alarm clock is an event. It signals that it’s time to wake up by making this awful sound. I react to it because I need to wake up. Another example is when your car signals you need to get gas. Our days are filled with events that we need to react to.

If we translate this to code: you have a class A that can give a signal to class B. Class B is subscribed to that event that gives the signal. Class B reacts as soon as the event from class A gives the signal.

But how does this work, code-wise? Events in C# use delegates. A delegate is a variable or parameter that can hold a function. Delegates are very useful when you want to pass along a function instead of executing it. We need this principle if we want to use events in C#.

An event can have multiple subscribers. Each subscriber will react when the event is triggered.

Since the theory is boring and real-life examples are way more interesting, let’s dive into some code.

A Simple Example

In my country (The Netherlands) the max speed for traffic on a highway is 100 km/h (62.13712mph according to Google). I write code for my car and I want to get a signal as soon as the speed of the car is more than the allowed speed, which is 100.

Fun fact: After 7 PM the max speed is 130 km/h (80mph), but we don’t need to take that into effect at this time.

Preparations

Let’s create a simple console application. In this console application, I use the following class:

internal class CarSpeed
{
    private int _currentSpeed = 0;

    public void DoSpeed()
    {
        for (int i = 0; i < 200; i++)
        {
            _currentSpeed = i;
            Console.WriteLine($"Current speed: {i}");
        }
    }
}

Nothing special here. There is a public method that loops until it reaches 200. Within the loop, it sets the private variable _currentSpeed to the value of i. It also writes the current speed to the console.

I also add some code to the program.cs, otherwise nothing will happen.

CarSpeed carSpeed = new();
carSpeed.DoSpeed();

Console.WriteLine("Max speed reached");

I initialize the class CarSpeed and call the method DoSpeed(). When that method is done the text “Max speed reached” is written on my screen.

Okay, cool. This works. When you start the application with the above code something like this will appear in the console when you start it:

Showing the current speeds in Console - Events in C# - Kens Learning Curve
The current speed is written to the console.

Signal The Speeding

Next up is the signal. I want to see a signal on my screen as soon as the car is going faster than the allowed speed. There are two ways I can do this:

  1. Add a piece of code in the DoSpeed() method
  2. Write an event that can signal the subscriber (the Program.cs)

If I would do number 1 the method would look like this:

public void DoSpeed()
{
    for (int i = 0; i < 200; i++)
    {
        _currentSpeed = i;

        if(_currentSpeed > 100)
        {
            ConsoleColor currentColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("SPEEDING!!!!");
            Console.ForegroundColor = currentColor;
        }

        Console.WriteLine($"Current speed: {i}");
    }
}

This is not wrong, but it is not convenient either. What if this car is a race car? It goes way faster than a normal car and it is allowed to! It would be better if the car itself decides if there is a speed limit or not. In this case, the car is the Program.cs. I need an event so I can subscribe to the signal if needed. If I write code for a race car I don’t need to subscribe to the event.

Using Events In C#

It is better to use an event, which gives a signal to the subscriber. Events in C# are declared in a class, which has the method that raises the event. Such a class is called a publisher. A method in the publisher raises the event to give the signal.

First, we need to declare a delegate, which will hold the method that needs to be called on the subscriber. We connect this delegate to an event. Then we invoke the event when we want to signal the subscribers. That’s the theory. This is how it looks in the code:

internal class CarSpeed
{
    public delegate void NotifySpeeding();
    public event NotifySpeeding OnSpeeding;

    private int _currentSpeed = 0;

    public void DoSpeed()
    {
        for (int i = 0; i < 200; i++)
        {
            _currentSpeed = i;

            if (_currentSpeed > 100 && OnSpeeding != null)
                OnSpeeding.Invoke();

            Console.WriteLine($"Current speed: {i}");
        }
    }
}

In the above code, I have declared the delegate and the event, as mentioned in the theory. In the method DoSpeed() I check if the speed is above the allowed speed and if OnSpeeding-event is not null. When no subscriber has been subscribed to the event, the event will be null. If both are true I invoke the OnSpeeding.

When I start the application nothing special will happen, not even a warning.

Result after adding an event - Events in C# - Kens Learning Curve
After adding an event to the publisher. Nothing to see here.

To trigger an action on events in C#, we subscribe to such an event. When we subscribe we connect a method that will be executed when the publisher event gives a signal. That way we can act or interact as soon as needed. In this case when the car is going faster than 100.

using EventsDemo;

CarSpeed carSpeed = new();
carSpeed.OnSpeeding += CarSpeed_OnSpeeding;

void CarSpeed_OnSpeeding()
{
    var tempColor = Console.ForegroundColor;
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine("This car is speeding!");
    Console.ForegroundColor = tempColor;
}

carSpeed.DoSpeed();

Console.WriteLine("Max speed reached");

On line 4 I subscribe to the OnSpeeding event. I tell the code to execute CarSpeed_OnSpeeding when the publisher invokes the event.

On line 6 is that method, where I can do whatever it is I need to do.

A Bit More Realistic

Cool. Now we have a car that accelerates to 200 and an event that will be invoked as soon the car hits 101 or more. But what if we go to Germany? The Max speed on the highway is 130, not 100. Better would be to initialize the car with a maximum speed. Let’s add the parameters, just for the sake of completion.

internal class CarSpeed
{
    public delegate void NotifySpeeding();
    public event NotifySpeeding OnSpeeding;

    private int _currentSpeed = 0;
    private readonly int maxSpeed;

    public CarSpeed(int maxSpeed)
    {
        this.maxSpeed = maxSpeed;
    }

    public void DoSpeed()
    {
        for (int i = 0; i < 200; i++)
        {
            _currentSpeed = i;

            if (_currentSpeed > maxSpeed && OnSpeeding != null)
                OnSpeeding.Invoke();

            Console.WriteLine($"Current speed: {i}");
        }
    }
}

In the CarSpeed class, I have added a constructor for the maximum speed. This maximum speed is being checked on line 20. Thus making the max speed variable.

Let’s adjust the Program.cs:

using EventsDemo;

CarSpeed carSpeed = new(130);
carSpeed.OnSpeeding += CarSpeed_OnSpeeding;

void CarSpeed_OnSpeeding()
{
    var tempColor = Console.ForegroundColor;
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine("This car is speeding!");
    Console.ForegroundColor = tempColor;
}

carSpeed.DoSpeed();

Console.WriteLine("Max speed reached");

On line 3 I add the max speed for Germany… All done!

Result after adding an event with max speed - Events in C# - Kens Learning Curve
Running the application after adding the variable max speed.

EventHandlers

I am not a big fan of using front-end code in logic, like a class. You should leave this to the files intended for the front end. In this case, I have placed the front-end code in the CarSpeed class, while this should be in the Program.cs. The CarSpeed class is doing a Console.WriteLine on line 26. I am going to remove this.

But now I have no idea how fast the car is going when speeding. It would be awesome if there is a way to send the speed to the method that is being called after the event gives the signal… But there is!

We can send data with the invoked event and it isn’t even that hard to create. All I need to do is use an event handler, which is a very common practice when using events in C#. This is a .NET built-in delegate. We can use event handlers to send data from an invoked event to the subscriber method.  The subscriber method needs two parameters: The source of the event and the event data. And that second parameter is what we need.

A nice side effect of using the built-in event handler is that we don’t have to declare a delegate; this comes with the event handler. Let’s prepare for the CarSpeed class:

internal class CarSpeed
{
    public event EventHandler OnSpeeding;

    private int _currentSpeed = 0;
    private readonly int maxSpeed;

    public CarSpeed(int maxSpeed)
    {
        this.maxSpeed = maxSpeed;
    }

    public void DoSpeed()
    {
        for (int i = 0; i < 200; i++)
        {
            _currentSpeed = i;

            if (_currentSpeed > maxSpeed && OnSpeeding != null)
                OnSpeeding.Invoke(this, EventArgs.Empty);
        }
    }
}

I removed the delegate Notify() and changed the event to EventHandler. Note line 20. Instead of a parameterless invoke, we need to add the source (this class) and the event arguments (none at this point). But that’s all! Except, the Program.cs will give an error. This is because the parameters of the CarSpeed_OnSpeeding method are incorrect. We need to add the parameters for the source (sender) and the event arguments (e).

void CarSpeed_OnSpeeding(object? sender, EventArgs e)
{
    var tempColor = Console.ForegroundColor;
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine("This car is speeding!");
    Console.ForegroundColor = tempColor;
}

Event Data

Let’s zoom in on the EventArgs (event arguments). The publisher sends EventArgs.Empty, which is equivalent to no-event arguments. But we can send our arguments back to the subscriber. All we have to do is tell the EventHandler which type to return. In this case, I want to add the current speed of the car to the event arguments, which is an int:

public event EventHandler<int> OnSpeeding;

Then I can add the current speed to the invoke-call:

if (_currentSpeed > maxSpeed && OnSpeeding != null)
    OnSpeeding.Invoke(this, _currentSpeed);

Lastly, I need to change the subscriber method:

void CarSpeed_OnSpeeding(object? sender, int e)
{
    var tempColor = Console.ForegroundColor;
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine($"This car is speeding! It's going {e}");
    Console.ForegroundColor = tempColor;
}

The second parameter (the event arguments) is now of type int and I can use that in the message on line 5.

If you run the code now it will work as expected: Show the speed when the initialized car is speeding.

Custom EventArgs

Apart from an int, we can also write our EventArgs. We use this when we have more complex data to send back to the subscriber. Let’s say we want to raise the event each time the speed is increasing and we want to see that speed in the console window. But we also want to know if the initialized car is speeding or not. These are two properties we can use in a customer EventArgs. All we have to do is create a class with these properties and inherit from EventArgs. I call this class SpeedInfoEventArgs:

public class SpeedInfoEventArgs : EventArgs
{
    public int CurrentSpeed { get; set; }
    public bool IsSpeeding { get; set; }
}

I can use this class instead of the int.

public event EventHandler<SpeedInfoEventArgs> OnSpeeding;

That means I need to initialize a SpeedInfoEventArgs when invoking the OnSpeeding. After initialization, I can set the fields CurrentSpeed and IsSpeeding.

public void DoSpeed()
{
    for (int i = 0; i < 200; i++)
    {
        _currentSpeed = i;

        if (OnSpeeding != null)
            OnSpeeding.Invoke(this, new SpeedInfoEventArgs
            {
                CurrentSpeed = _currentSpeed,
                IsSpeeding = _currentSpeed > maxSpeed
            });
    }
}

I removed the check of the maximum speed because I will be using the IsSpeeding property of SpeedInfoEventArgs.

Let’s head back to the Program.cs. There is an error because the signature of CarSpeed_OnSpeeding doesn’t match the OnSpeeding. Let’s fix this first:

void CarSpeed_OnSpeeding(object? sender, SpeedInfoEventArgs e)
{
    var tempColor = Console.ForegroundColor;
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine($"This car is speeding! It's going {e}");
    Console.ForegroundColor = tempColor;
}

But with this code, the warning for speeding is always shown… Well, not quite. It tells me the car is speeding, but not with a speed. The variable ‘e’ is not a speed, but a SpeedInfoEventArgs. Let’s make this work:

void CarSpeed_OnSpeeding(object? sender, SpeedInfoEventArgs e)
{
    if (e.IsSpeeding)
    {
        var tempColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine($"This car is speeding! It's going {e.CurrentSpeed}");
        Console.ForegroundColor = tempColor;
    }
}

Conclusion On Events in C#

And there you have it: Events in C#! A lot of text and information, but it does show you the basic idea: communication with events between subscriber and publisher. 

Events can and will be used in a wide range of situations. Usually, a method wants to update the caller when something is done. Events are also used in WinForms. Yes, with other templates too, but WinForms is showing it off.

Related Articles

Table Of Contents
Kens Learning Curve