Eslam HelmyEslam Helmy
9 min readEslam

From Confusion to Clarity: Delegates Explained Right


From Confusion to Clarity: Delegates Explained Right

Every article I’ve seen talking about Delegates always starts with “what are delegates?” and continues like this: “it’s a way to pass methods to methods.” Although this is true, it’s really hard to understand what they’re talking about. Where do they come from?

If you have this feeling, you’re not alone. That’s why I’m writing this article. Let’s get started!

I want to start with variables. Yes, you’ve read that right — variables!

We all know that variables can be either reference type or value type. What we’re interested in are the reference type variables. We have two flavors of reference type variables:

• Built-in variables

• User-defined variables

Examples of built-in variables are like string, this alias that refers to the System.String class.

Examples of user-defined types are classes that we use to create objects that hold user-defined data and methods.

The common thing between these two flavors is the way they’re stored. Each variable holds a reference/location of its object that is stored in the memory.

We’re used to seeing something like this:

Person x = new Person();

x is the variable that holds the location of the person object in memory.

We feel really comfortable with these objects, but what if I told you that we can use reference variables to hold methods? 🙂

You might feel uncomfortable and ask these two questions:

• How? Are we going to use user-defined types or built-in types?

• Why?

The answer to the second question will be one word that we’ll get back to after answering the first question very clearly.

It’s Callbacks!

Now let’s answer “How?”

Just as we have classes to help us create variables that hold objects, we have another thing that is called delegates to help us create variables that hold methods.

To create variables using delegates, we have to do two steps:

  1. Define your method signature that you want to create variable to hold it, and create type for it using the keyword delegate
  2. Create variable of that type and assign the actual method that matches the method signature we declared for that type. That actual method can take one of 3 forms:

• Named method

• Anonymous method

• Lambda expression

Let’s take an example. Suppose we have this method:

public static void PrintToConsole(string message)
{
    Console.WriteLine("Console: " + message);
}

What we want is a variable that can hold methods, so let’s stick to our 2 steps:

  1. What’s the method signature that we want to hold? Well, it’s PrintToConsole. Wait, I said signature, not name! 😞

Well, it’s a method that takes one parameter and returns nothing.

So let’s declare our variable:

delegate void PrintToConsoleDelegate(string x);
  1. Let’s assign the name of the method:
PrintToConsoleDelegate printDelegate = PrintToConsole;

After defining the delegate, what can we do with it? A lot, but for now, we’ll do one thing: invoke the delegate.

printDelegate("Hello, this is a delegate invoke");

Let’s put them to see the full picture:

class Program
{
    delegate void PrintToConsoleDelegate(string x);
 
    public static void Main()
    {
        PrintToConsoleDelegate printDelegate = PrintToConsole;
        printDelegate("Hello, this is a delegate invoke");
    }
 
    static void PrintToConsole(string message)
    {
        Console.WriteLine(message);
    }
}

Run the project, and you’ll see a black console window with this text: “Hello, this is a delegate invoke”

We already mentioned that in the second step, we need to assign a method, and we mentioned 3 ways to assign the method. We’ve seen the first way, which was a named method. Now let’s see the second way: Anonymous method.

Before we create it, the idea is very simple. If you don’t want to create a specific method for your delegate and you only want to use the method for delegates, then create an anonymous one.

Let’s redo the previous example again without defining any method explicitly. Let’s stick to our 2 steps:

delegate void PrintToConsoleDelegate(string x);
 
PrintToConsoleDelegate printDelegate = delegate(string message)
{
    Console.WriteLine("Console: " + message);
};

The way we call it here is the same as we did in the previous example.

The third way is a more concise way to declare methods, which is Lambda expressions. So without further ado, here are the 2 steps:

delegate void PrintToConsoleDelegate(string x);
 
PrintToConsoleDelegate printDelegate = (string x) => Console.WriteLine("Console: " + x);

Congrats! We now know what delegates are, how to create them, and how to assign methods to them using 3 ways.

You can stop here if you want to catch your breath. Our next concept is Predefined delegates.

So far, we’ve been creating our own delegates, but imagine how hard it would be if we had 100 methods in our code that we needed to assign to delegate variables. We’d have to create 100 delegate variables, which is silly and makes the code spaghetti-like.

So Microsoft was there to help us with predefined delegates. What does this mean? Remember our 2 steps? Apparently, we don’t need the first step (yes!). We only need the second step.

So excited! Where are these predefined delegates? How many are there? How do they cover all method signatures?

Well, there are 3 predefined delegates:

Action <T1>: Use it whenever you have a method in your code that takes one parameter (whatever its type) and returns nothing.

Func<T1,..T16, TR>: Use it whenever you have a method in your code that takes one parameter (up to 16) and returns one result.

Predicate <T1,bool>: Use it whenever you have a method in your code that takes one parameter and returns a Boolean.

You might ask why we have Predicate — isn’t it the same as Func<T,bool>?

Yes, it is, but why not? 😉

Let’s revisit our example again. Our signature is the same: it’s a method that takes one parameter and returns nothing. So which one of the predefined delegate types can fit in? It’s Action.

So our code will be updated to:

Action<string> printDelegate = (string x) => Console.WriteLine("Console: " + x);

Isn’t it easier? Using predefined delegates with lambda expressions is really fun, and this is how LINQ is built. We’ll come to this later. 🙂

Let’s define another method to make the concept of predefined delegates sticky.

Assume we have this method:

public int Add(int x, int y)
{
    return x + y;
}

If we want to assign this to a delegate variable, it’s Func<int, int, int>.

So it’s like this:

Func<int, int, int> addDelegate = (int x, int y) => x + y;

Now, we’ve come a long way, and you might think I forgot the second question: Why do we need them? I hope you remember my short answer: Callbacks.

First, what are callbacks? Well, it’s a piece of executable code (method) that is passed as an argument to other code (another method). The other code is expected to execute that piece of code at some convenient time.

All of this is because of callbacks? 😞 Why are they important?

Actually, they’re so important! Callbacks allow us to plug new code into existing code.

So we can inject the behavior (method) we need into any existing piece of code, which makes the code more flexible (dynamic invocation, different strategies at runtime) and applies the Open-Closed Principle. Existing code is not open for editing; we can add any new code without touching the old one.

Let’s see an example. Suppose we have this method:

public void ProcessData(string data)
{
    var processedData = data.ToUpper();
}

We have this method, and our client who wants to use this method wants to have the flexibility to print processed data to the console or a file. One way we could think of is to have a boolean flag in the signature that indicates the way we print the data.

So we’ll have something like this:

public void ProcessData(string data, bool isFile)
{
    var processedData = data.ToUpper();
    if (isFile)
        Console.WriteLine("File: " + processedData);
    else
        Console.WriteLine("Console: " + processedData);
}

Later, the client came and said they want to introduce another printing strategy. So we have to update our method and add another extra block, which means our code is more fragile and can introduce bugs if we just miss the condition of one block. Also, later, if you have SonarQube or any analysis tool that analyzes the complexity of the code, it will ask you to simplify these blocks (‘Cyclomatic complexity’).

Let’s think in a different way. We want to pass different strategies of printing at runtime and let the control be in the client’s hands.

So the client wants us to handle different printing strategies based on behavior they have in mind now or might have later. Whenever you hear “strategy at runtime” think of delegates, even if it’s not the case. Give it a try!

The question now is, if we’re thinking in delegates, what does this behavior look like? Well, we need to pass processed data to a method that returns nothing. I can see that this is the same as an Action Delegate.

Let’s see how we code this:

public void ProcessData(string data, Action<string> printStrategy)
{
    var processedData = data.ToUpper();
    printStrategy(processedData);
}

How do we call it? The client will do code like this:

ProcessData("Delegates article", x => Console.WriteLine("Console: " + x));

How readable and flexible is it? 🙂

So now we have an understanding of how to use delegates as callbacks, but why can’t I do it in spaghetti code and forget about all of this?

Well, you can do this, but the problem is you’re seeing delegates every day at your work, and a good developer is one who knows what they’re coding.

I bet you’ve seen something like this:

var list = list.Where(x => x == 5).ToList();
 
var orders = collection.Select(x => new Order { }).ToList();
 
users.ForEach(x => x.UpdatedDate = DateTime.Now);

Those are LINQ operations which are built on top of delegates and lambda expressions. Why delegates? Because this is how we can pass the behavior we want inside their methods.

Delegates will be seen in various situations and in many examples, but I believe if you understand the basic idea of it. You would understand anything that’s built on top of it.

Thanks for reading!

Share this post