Introduction to Delegates
Tags: csharp, dotnet, tutorial, beginners
Published: 2024-07-17 08:37:14

## Hi there!
**Delegates**, **Funcs**, and **Actions** are all **C#** words, and are used extensively throughout the language, the frameworks you use, and in almost every project you're going to work on, you'll encounter them in one form or another, so what are they, why are they used, what's their benefit, and what are the different types of **Delegates** do we have in C#?

**Let's Get Started!**

## Introduction 🎬
Delegates are an important feature of C#, and they're widely used, they're considered an advanced topic and sometimes aren't taught early on, but they're one of those concepts that's once learned, you'll find yourself using a lot. I always try to keep things as simple, and as straightforward as possible so that you don't only understand the concept discussed, but also feel comfortable using it.
So in this series about delegates, we'll be exploring the concept in depth, with practical code samples, easy to understand explanations, and dedicated posts to each delegate variation we have in C#. If that sounds interesting, keep reading!

## Defining Delegates 📜
To begin our trip, we'll start by a definition from Microsoft Documentation:
> A delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type.

In summary, a delegate is like a type that defines how a method should look like, i.e its signature and return type, and instances of that delegate can be assigned different methods as long as they comply with the signature of the delegate.

If you don't know what a method signature means, let me explain.
Take a look at this simple `Sum` method:
```csharp

private int Sum(int x, int y) => x + y;

```

The name of the method, `Sum`, the parameters `int, int`, and their order, makes up what's known as a method signature. Two methods with the exact same signature cannot co-exist in the same class, like assuming you have another `Sum` method that uses a different adding algorithm, will cause a compile time exception if the 2 methods shared the same name, same parameter list, and same parameters order.
 
> **NOTE: ⚠️** The name of the parameters isn't equated to in the method signature, like defining `Sum` again but as `Sum(int a, int b)` will still not work.

```csharp

public int Sum(int x, int y) => x + y;

public int Sum(int a, int b) => a + b;

```

This code will throw this compile time exception:
`Member with the same signature is already declared`

Now with the definition out of the way, let's declare our first delegate that we'll invoke the previous sum method through.

## Declaring a Delegate 🛠️
To define a delegate, we need to use the following template:
`[ACCESS_MODIFIER] delegate [RETURN_TYPE] [NAME]([PARAMETERS_LIST]);`

Let's break it down:

- Access Modifier: Defines the accessibility level for the delegate type, can be `public`, `private`, `internal` just to mention a few.
- `delegate`: This keyword is a C# reserved keyword, used to denote that the declared type is a delegate type.
- Return Type: Defines the return type of the delegate type, can be any primitive data type like `int`, `string`... Or a user defined type like `CustomerDetails`, `Employee`... In addition to `void` indicating the delegate returns nothing.
- Name: A unique identifier for the delegate type.
- Parameters List: The list of the parameters the delegate type is expecting, remember the order matters, this can be up to 254 parameters for instance methods, and 255 for static methods including `this` keyword.

### Let's Declare Our First Delegate 🚀
In a text editor of your choice, type out this code in a console application:

```csharp

public delegate int SumDelegate(int x, int y);

```

Now a compatible method should look like that:
```csharp

int Sum(int x, int y) => x + b;

```

It's got the same return type, and parameters list. Let's see how we can put these together and invoke the `Sum` method using our `SumDelegate`.

Inside Program.cs, we'll write this code:
```csharp

int x = 5;
int y = 6;

SumDelegate sumDel = new SumDelegate(Sum);
int result = sumDel(x,y);

Console.WriteLine(result); // OUTPUT: 11

static int Sum(int x, int y) => x + y;

public delegate int SumDelegate(int x, int y);

```
> **NOTE: ⚠️** Please make sure to put the delegate definition at the bottom of the file, because we're using top-level statements where namespace and type declaration should go below any code that we want to execute (Any top-level statements).

This program will output `11` to the console window, which is the correct result of our addition operation. The interesting part to notice, is how we instantiated our delegate instance.

To new up a delegate, we use the `new` keyword, just like with classes, structs and so on. Then in the constructor invocation, we pass the target method the delegate will point to, in our case it's the `Sum` method. If you try to pass a method with an incompatible signature, this code will simply not compile, take a look at the following code:
```csharp

SumDelegate sumDel = new SumDelegate(Sum);

...

static double Sum(double a, double b) => a + b;
// static int Sum(int a, int b) => a + b;

public delegate int SumDelegate(int a, int b);

```

I replaced the old Sum with a `Sum` that uses `double` as both return type and parameters, now my editor will show this compile-time error:
`Expected a method with 'int Sum(int, int)' signature`

The other thing to note, is how we invoked the delegate, we did so like how we would invoke a normal method, by typing the delegate name, followed by parenthesis, passing down the parameters we want to invoke `Sum` with. Also, because our delegate `SumDelegate` returns an `int`, we were able to store the result inside a variable of type `int` too, we then used that to log the operation result to the console.

🎉 **Congratulations!** You declared your first delegate and invoked a method through it!

## Clearing Some Confusion 🤔
I know by now, you're having the question as to why did we do all of that, just to call a method which we could've done the normal way, and got the same result, like I'm sure you're asking yourself why don't we make the code look like this:
```csharp

int x = 5;
int y = 6;

int result = Sum(x, y);

Console.WriteLine(result);

static int Sum(int a, int b) => a + b;

```

Wouldn't that result in the same output but with just less code, and less complexity? The answer is, you're absolutely right.
But the thing is, delegates aren't just used to invoke methods like we just did, instead, the power of delegates shines when you get to pass methods as arguments to other methods, which in normal method invocation, is simply not possible, in addition to being able to invoke multiple methods at once, using multicast delegates, defining callbacks or respond to events, all of which we're going to discuss later in this series.

## Assigning Methods to Delegates 🧪
We declared a delegate earlier, and we assigned a target method to it through the constructor. However, there are other ways of doing that in C#, plus the ability to replace target methods with different ones.

We'll code a basic message logging example, which will set us up to look at **multicast delegates** and see them in action.

## Coding a Basic Logger 🪵
We'll start by declaring a class called `ConsoleLogger`, which has the following code:
```csharp

public static class ConsoleLogger
{
    public static void Log(string message)
    {
        Console.WriteLine($"Log: {message} - {DateTime.Now}");
    }
}

```

The logger is pretty basic, it's got a single static method called `Log`, accepting a `string` message which it writes to the console as well as the current date time.

In Program.cs, I added a new delegate declaration at the bottom which has the following definition:
`public delegate void LogDelegate(string message);`

As you can see, the `Log` method matches the signature and return type of the `LogDelegate`, which means it's compatible with it, so what we will do now is, instantiate an instance of `LogDelegate`, and assign it the `Log` method of `ConsoleLogger` like this:

```csharp

LogDelegate logDel = ConsoleLogger.Log;
logDel("Hello, world!");

public delegate void LogDelegate(string message);

public class ConsoleLogger
{
    public static void Log(string message)
    {
        Console.WriteLine($"Log: {message} - {DateTime.Now}");
    }
}

```

That's some syntactic sugar, we didn't have to use the `new` keyword and invoke the constructor explicitly, we just assigned `ConsoleLogger.Log` to a variable of type `LogDelegate` right away.

If you were to run this, you would see something like this:

![Output of using ConsoleLogger.Log with the LogDelegate](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o5qx4h9f1edatb7mbq7u.png)

Ok now let's assume we introduced a new logger, called `FileLogger`, which also has a `Log` method, but instead of writing a message to the console, it rather logs it into a text file called `log.txt`, like this:
```csharp

public class FileLogger
{
    public static void Log(string message)
    {
        File.AppendAllText("log.txt", $"Log: {message} - {DateTime.Now}" + Environment.NewLine);
    }
}

```

Same thing as before, but we're just writing to a file this time instead of the console window, we log the message, with the current date time, and append a new line at the end so our next log sits nicely on a separate line.

Now change Program.cs to this:
```csharp

LogDelegate logDel = ConsoleLogger.Log;
logDel("Hello, world!");

logDel -= ConsoleLogger.Log;
logDel += FileLogger.Log;

logDel("Hello, world, but from File!");

```
Here's a summary of the changes made:
- Removes the reference to the `ConsoleLogger.Log` method using `logDel -= ConsoleLogger.Log`.
- Adds a reference to the `FileLogger.Log` method using `logDel += FileLogger.Log` 
- Invokes `logDel` with a new message that says `Hello, world, but from File!`

Run the application now, and you should see a `Hello, world!` logged to the console, and the application should terminate. However, if you navigate to the project folder, go inside bin/debug/net8.0 (Assuming you're on .NET 8), you should see a file called `log.txt` that was created automatically, which would have the following content:

![The content of log.txt after logging a message to it](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gkbwmm0indu7k5b7wp54.png)

Awesome! You saw how we can replace method references and assign a target method to a delegate instance in 2 different ways!

## Delegates with Anonymous Methods 🎭
We've seen delegate instantiation with methods that are pre-defined like `Sum`, but sometimes, you want to instantiate a delegate instance with an inline-implementation. That's useful when the method is not going to be reused somewhere else, which means defining a named method will be just more code, or in some cases, the implementation is short and you want to keep it concise.

Using this new delegate declaration, we'll see how we can associate an anonymous method with a delegate instance.
```csharp

Operation op = delegate(double a, double b)
{
    return a + b;
}

delegate double Operation(double a, double b);

```

Invoking this delegate will add the two numbers together. However, there's a shorter way of doing it, because the language has evolved, we no longer need to use the `delegate` keyword, and also, we don't necessarily have to specify the types of the parameters, because the compiler is smart enough right now, and it knows the definition of the delegate and that the `Operation` delegate accepts 2 `double` parameters, with that, we can transform the previous assignment to this:
```csharp

Operation op = (x, y) => x + y;

delegate double Operation(double x, double y);

```

This does the same thing now, but it's much more concise. Now omitting the types names is not necessary, in cases where the parameters list is long with complex type names, it's a good idea to keep the names present for enhanced readability and understanding.

## Multicast Delegates 🐙
Earlier we've seen how to invoke a method through a delegate, then we replaced the method reference and called a different method which does a totally different job (Logging to a file instead of the console window), but what if we wanted to do both with one shot only? Enter **multicast delegates**.

A multicast delegate is a delegate that references 2 or more methods, which it invokes in the order they were added in, that would come in handy because right now, instead of logging to the console, then replacing the logging outlet to a file, then maybe in the future to a database, and who knows what else, we can simply reference all the `Log` methods and have the delegate invoke them all when we want to log something. This is how we would do that in C#:
```csharp

LogDelegate logDel = ConsoleLogger.Log;
logDel += FileLogger.Log;

logDel("Hello, world!"); // Logs Hello, world! to both the console and log.txt

``` 

Now the code logs the message to the console first, then logs it to the log file, because as I just mentioned, the order in which the references were added determines the order in which they'll be invoked, so pay good attention to that in case the operations your application is going to perform requires the processing to happen in a certain order.

## Conclusion ✅
We looked in this introductory post about delegates on some key concepts and discussed the `delegate` reference type in detail, we saw through some code samples to learn how we can declare a delegate, how to assign a target method to it in two different ways, and also how we can remove or add method references to a delegate object instance. Lastly, we briefly looked at delegates with anonymous methods for more concise delegate assignments, and multicast delegates which allowed us to reference multiple methods at once using a single delegate object instance. 

That's about it for this post, in the next one in the series, we'll see how we can pass methods as arguments into other methods using what we've learned from this article, so stay tuned for that one.

## **Thanks For Reading!**