Delegates and Events

Delegates and Events are two of the very important fundamental concepts in the .NET Framework. These two concepts we end up using every day in a shape or form inside of our code. Not so much of Events, but Delegates for sure.

In this blog post, I am going to walk through some of the fundamental concepts of Events and Delegates and how to use them in applications.

So first in this video I am going to start explaining what is a delegate, and then write a class which is going to show some of the delegate features, and then finally talk about the event and how delegates and events work together.

First of all, let us discuss what is delegate.

What is Delegate

Delegate is a type in C#. It represents a reference to a method (it is like a function pointer).

Most of the time when we hear about delegates, in a lot of places it will come up as delegate is a function pointer. But as I mentioned above, in the .NET Framework delegate is essentially a type which represents a reference to a particular method. And that is why sometime it is called it’s like a function pointer because it points to a function.

Delegate is declared with a list of parameters (zero or many) and a return type (can be void).

When a delegate is instantiated, we can map it with any method with a compatible signature. The method which the delegate points to can be either static or non-static (instance).

To call the underlying method for a particular delegate, we can use the Invoke function on the delegate or just call it with delegateinstance() signature. Once I walk through the code, it is going to become clearer what I mean.

In my personal experience, delegates are mainly used for callback functions. That is where they shine the most.

New .NET Core Application for Delegates and Events

Firstly, I will create a new ASP.Net Console application. To do that, I will open up Visual Studio 2019. Once Visual Studio opens up, I will select the menu File -> New -> Project. This will open the Create a new Project project popup window.

Secondly, in the Create a new Project popup window, I will select the Console Application (.NET Core) from the project template and click on the Next button.

Thirdly, on the next page, I will provide the name of the application as DelegateEvent.Demo and click on the Create button.

Delegate implementation

Once the project is created I will create a new class Calculator.

Firstly, inside of the class, I am going to create a new delegate type. I am going to make the return type of this delegate an Integer. And the name of the delegate will be Calculate. The delegate will take a single parameter of type Integer.

Secondly, I will create a new public method inside of the class. The name of the new method will be Execute.

Thirdly, the new method will return an Integer and it will have two parameters. The first parameter will of the type of delegate we declared earlier and the second parameter will be an Integer.

Fourthly, inside of this Execute function, I am just going to return executing the delegate with the incoming input. And as I mentioned earlier, we are using the syntax of delegateinstance() here.

using System;

namespace DelegateEvent.Demo
{
    public class Calculator
    {
        public delegate int Calculate(int input);

        public int Execute(Calculate calculate, int input)
        {
            return calculate(input);
        }
    }
}

Calling the delegate from Program class

Firstly, inside of the Program class, we will create a new static function, which has the same signature as the delegate Calculate. The name of the new static function will be Square.

Secondly, the function Square is just going to return the square of the input (input times input).

Thirdly, inside the Main method, we will create a new instance of the Calculator class.

Fourthly, we will declare an instance of the Calculate delegate and point it to the Square static method we declared above.

Fifthly, we will call the Execute function of the Calculator class, and we will pass the newly declared Calculate delegate as the first parameter and number 5 as the second parameter.

Finally, we will print out the response returned from the Execute method.

using System;

namespace DelegateEvent.Demo
{
    static class Program
    {
        static int Square(int input) => input * input;

        static void Main(string[] args)
        {
            var calculator = new Calculator();
            Calculator.Calculate calc = Square;

            var response = calculator.Execute(calc, 5);

            Console.WriteLine($"Response: {response}");
            Console.ReadLine();
        }
    }
}

However, in the code above, there is a very important concept about delegate that I want to point out. You can see that though the delegate is declared as a public instance member of this type Calculator, we cannot access it like a normal instance, public member.

Instead, we access it like an inner type. It is very similar to how we access an inner class. This is because when we declare a delegate, we declare an inner type, not an instance member.

Now if we run the application, we will see the response of 25 in the console output as expected.

delegates and events

Action and Func (OOB Delegates)

The delegate discussion is incomplete without discussing Action and Func. These the the two out of the box delegate types provided by the .NET Framework (.NET Core)

There are two high-level types of delegate that are possible. A delegate that returns a type and a delegate that returns nothing. Or in other words, one type of delegate is the one that returns a type and takes zero to a bunch of parameters as input. And the other type of delegate is the one that returns nothing (or void) and takes zero to a bunch of parameters.

The delegate type that does not return anything is represented by Action. And the delegate type that returns something is represented by Func.

Func can have 17 different possible forms. Starting with Func<TResult> with a total of 16 possible parameter combination, i.e., Func<T, TResult>, Func<T1, T2, TResult>, Func<T1, T2, T3, TResult>, etc.

Similarly, Action can also have 17 different possible forms. Starting with Action, then Action<T> with a total of 16 possible parameter combination, i.e., Action<T1, T2>, Action<T1, T2, T3>, etc.

Using Action and Func

Now that we understand what is Action and Func, let us now use these delegates in the code we created earlier.

Firstly, if we take this example in the Calculator, we can completely replace the Calculate delegate with a Func<int, int> i.e., a Func with input as an Integer and returns an output of Integer.

using System;

namespace DelegateEvent.Demo
{
    public class Calculator
    {
        public int Execute(Func<int, int> calculate, int input)
        {
            return calculate(input);
        }
    }
}

Secondly, in the Main function of the Program class, we will just pass the static function Square to the Execute call. That is all we need to change. And you can see here how simple the code becomes after using the Func here.

using System;

namespace DelegateEvent.Demo
{
    static class Program
    {
        static int Square(int input) => input * input;

        static void Main(string[] args)
        {
            var calculator = new Calculator();

            var response = calculator.Execute(Square, 5);

            Console.WriteLine($"Response: {response}");
            Console.ReadLine();
        }
    }
}

Finally, if we run the application now, it is going to print 25 as before and as expected.

Using Action

Now if we want to use Action along with Func in this example, we can do that very easily.

Firstly, to demonstrate usage of Action, we can update the Execute function to take another parameter. And this third parameter will be an Action<int>. And now the Execute function will not return anything.

Secondly, we will update the implementation of the Execute, now it will first call the Func delegate, and after that, it will call the Action delegate passing the response returned from the Func.

using System;

namespace DelegateEvent.Demo
{
    public class Calculator
    {
        public void Execute(Func<int, int> calculate, Action<int> print, int input)
        {
            var result = calculate(input);
            print(result);
        }
    }
}

Thirdly, we will go back to the Main function inside of the Program class. And we will change the implementation where we call Execute function of the Calculator class. Now we will introduce the Action delegate in the parameter. And for the Action delegate, we can just pass the Console.WriteLine.

using System;

namespace DelegateEvent.Demo
{
    static class Program
    {
        static int Square(int input) => input * input;
 
       static void Main(string[] args)
        {
            var calculator = new Calculator();
 
            calculator.Execute(Square, Console.WriteLine, 5);
       }
    }
}

Finally, if we run the application, we will see the same response of 25 as before. After the Action and Func became available to the language, we don’t have to create our own delegate almost ever.

Where does Lambda fit in?

The delicate discussion is incomplete without discussing lambda. So where does exactly lambda fit in? To answer this question, let us first understand what is lambda.

Lambda expression is used to create an anonymous function. But the anonymous function in itself is kind of useless. Anonymous functions are usually used with a delegate or to represent a delegate.

Hence, any delegate type can be represented with a lambda expression. The parameter and return type of the delegate, of course, has to match the lambda.

Lambda expressions can be async or non-async.

Using Lambda

In the Main function of the Program class, where we are using the Square function to pass along to the Execute method, we can use lambda expression instead.

using System;

namespace DelegateEvent.Demo
{
    static class Program
    {
        static void Main(string[] args)
        {
            var calculator = new Calculator();

            calculator.Execute(x => x * x, Console.WriteLine, 5);

            Console.ReadLine();
        }
    }
}

Now if we run this application, we will see the same response as before. So that is essentially where the lambda fits in the whole discussion. The delegate representation usually ends up being a lambda.

And I don’t remember last when I used a function declaration for a delegate. Almost always we end up using lambda expression when it comes to declaring a delegate.

Events

Now that we understood Delegate and its associated concepts. It is time to get down to understand what are events.

Events as we all know are nothing but a feature that we use to trigger to invoke a function. The event keyword is what we use to declare an event in C# (.NET Core).

Unlike delegate which becomes like an enclosed type, the event is treated as an instance member. And that is a fundamental difference between event and delegate.

An event is always associated with the delegate. When an event is raised, the delegate is called back. This is what essentially happens when you raise an event, you are just invoking the delegate associated with an event.

The delegate associated with an event usually has two parameters as a standard practice. This is not mandatory but is a best practice.

  • The first one is an object representing the instance that raised the event.
  • The second one is a type representing event arguments.

Using Events

Now lets us see events in action.

Firstly, let us create an event argument class. And for that, I am going to go create a new class, and I am going to name it as CalculatorEventArgs. Usually, the naming convention for event argument is that we suffix the class name with EventArgs.

And the CalculatorEventArgs will have a single Integer property Value.

namespace DelegateEvent.Demo
{
    public class CalculatorEventArgs
    {
        public int Value { get; set; }
    }
}

Secondly, we will update the Calculator class. And we will declare a new event named CalculateTriggered. The type of event will be Action<object, CalculatorEventArgs>.

Thirdly, we will update the Execute method to remove the Action<int> delegate. And after getting the result from the Func call, we will trigger the CalculateTriggered event. And passing the result in the event arguments.

One important fact to remember here is that before invoking an event we always check if the event is null or not. It is because the event is instantiated only when someone attaches a delegate to it.

using System;

namespace DelegateEvent.Demo
{
    public class Calculator
    {
        public event Action<object, CalculatorEventArgs> CalculateTriggered;

        public void Execute(Func<int, int> calculate, int input)
        {
            var result = calculate(input);
            CalculateTriggered?.Invoke(this, new CalculatorEventArgs { Value = result });
        }
    }
}

Updating Main to use event

Now to attach the event to a delegate and use it we will go and update the Main method in the Program class.

Firstly, we will attach a delegate to the CalculateTriggered event. And once it is called back, we will use the Value from the CalculatorEventArgs to write to the console.

Secondly, update the call to Execute method and remove the Action<int> parameter.

using System;

namespace DelegateEvent.Demo
{
    static class Program
    {
        static void Main(string[] args)
        {
            var calculator = new Calculator();

            calculator.CalculateTriggered += (obj, eventARgs) => Console.WriteLine(eventARgs.Value);

            calculator.Execute(x => x * x, 5);

            Console.ReadLine();
        }
    }
}

Finally if we run this application, we will see the same response of 25 as before.

Conclusion for Delegates and Events

This wraps up the basics of delegates and events in the .NET Framework (.NET Core). These concepts are extremely critical and are in use in every single application we use. And as I mentioned earlier delegates are in use more often compared to events.

And I captured the entire process of the blog in my YouTube video here.