Introduction

One of the hardest things to explain to a beginner developer is delegate. It usually proves to be difficult because this piece of functionality’s use case is not obvious like variables, loops, collections etc.

In this blog post we will explore exactly what is that thing called a delegate, where do we use them in our daily work or even if it is that important, so…

What is a Delegate?

I dare you to go ask a programmer what a delegate is and you can bet your monthly salary that the answer will be:

A delegate is a pointer to a method.

It is almost an automatic reply like a defense mechanism. Don’t get me wrong the statement is not incorrect, it just does not explain anything to a beginner and the person is more confused than before.

Here is a dead simple explanation for you: A delegate is a way to use methods as variables. You can assign a method to a delegate variable just as you assign integers to int variables, strings to string variables, booleans to bool variables etc. you got the point.

This allows us to pass methods as parameters to other methods, use them as properties of classes, or even add or subtract them with other delegate variables. We will look at the use cases in a minute but first…

How to Create a Delegate

There are various ways of creating a delegate. As I said before delegates enables us to use methods as variables. So, in order to create a delegate let’s first create a simple method:

static string Power(double a, int power)
{
    return $"Power of {a} is {Math.Pow(a, power)}.";
}

Pay attention that this method takes 1 double parameter and 1 int parameter then returns a string type. In order to use this as a variable we have to declare a delegate that has the same signature as the method:

public delegate string PowerOfNumber(double a, int b);

We declared a public delegate which takes 1 double, 1 int parameter and returns a string. Keep in mind that this is only a type. So, we have to declare a variable with that type and assign our Add method to this variable. Complete example:

class Program
{
    public delegate string PowerOfNumber(double a, int b);

    static void Main(string[] args)
    {
        PowerOfNumber powerOfNumber = new PowerOfNumber(Power);
        string result = powerOfNumber(10.00, 2);

        Console.WriteLine(result);
    }

    static string Power(double a, int power)
    {
        return $"Power of {a} is {Math.Pow(a, power)}.";
    }
}

Here we declared a variable from our PowerOfNumber delegate type and assigned Power method to this type. After that we executed the Add method and printed the result to the console.

Even though this is a perfectly fine approach of creating a delegate almost no one uses it. C# has predefined delegate types that you can use. In that way you won’t have to declare your own type. Let’s rewrite our application using one of the build in types…

Func<T> Delegate Type

Func is a special delegate type that we can use instead of defining our own. The special thing in Func is that it can take 0 or more input types but always has to return a value. Which makes it ideal for our scenario. Here is how the Func representation of our Power method will look like:

Func<double, int, string> powerOfNumber

We first write our parameters and lastly the return type. Keep in mind that this parameter list is not infinite. Which means that if your method has 16+ parameters you cannot use Func.

How we would write the Func if our method did not take any parameter and only returned a string? Here is how:

Func<string> MethodWithNoParams

This Func representation will only return a string without taking any parameters. Let’s get back to our example and completely replace the custom delegate with Func:

class Program
{
    static void Main(string[] args)
    {
        Func<double, int, string> powerOfNumber = Power;
        string result = powerOfNumber(10.00, 2);

        Console.WriteLine(result);
    }

    static string Power(double a, int power)
    {
        return $"Power of {a} is {Math.Pow(a, power)}.";
    }
}

This is all good stuff but what happens if our method does not return a value? Say welcome to…

Action<T> Delegate Type

Similar to Func, Action is another built in delegate type with only difference is it does not return value and takes from 0 to 16 parameters;

Let’s conside this method:

static void PrintPerson(string name, int age, DateTime birthDate)
{
    Console.WriteLine($"{name}, {age}, {birthDate}");
}

It just takes some parameters and print them. We cannot represent this method as a Func because it does not return anything so we have to use Action:

Action<string, int, DateTime> printer

As you see the action only takes the parameters of the method. If the method has no parameter, you can use the non-generic version of the method:

Action methodWithNoParams

Let’s take a look to a complete example with Action type:

class Program
{
    static void Main(string[] args)
    {
        Action<string, int, DateTime> printer = PrintPerson;
        printer("Yeva Kohar", 66, new DateTime(1954, 7, 4));
    }

    static void PrintPerson(string name, int age, DateTime birthDate)
    {
        Console.WriteLine($"{name}, {age}, {birthDate}");
    }
}

Anonymous Delegate Methods

Having said all this, we can use delegates without even having a method to assign to our delegate. How is this possible you ask? By using an anonymous method and lambdas of course.

I will not go through what a lambda expression and anonymous function is since they are worth a separate article, but we will take a look at some of their syntax.

For more info: Anonymous functions (C# Programming Guide)

Let’s take our Func example and use it without the method:

class Program
{
    static void Main(string[] args)
    {
        Func<double, int, string> powerOfNumber = (a, power) =>
        {
            return $"Power of {a} is {Math.Pow(a, power)}.";
        };

        string result = powerOfNumber(10.00, 2);
        Console.WriteLine(result);
    }
}

In this example we get rid of the Power method and used anonymous method with lambda syntax to define what will happen to our delegate when we invoke it. You do not have to define the types using anonymous methods. C# is smart enough to determine them. Take a look at (a, power) =>, a and power are just typeless variables.

We can do the same for our Action example too:

class Program
{
    static void Main(string[] args)
    {
        Action<string, int, DateTime> printer = (name, age, birthDate) =>
        {
            Console.WriteLine($"{name}, {age}, {birthDate}");
        };

        printer("Yeva Kohar", 66, new DateTime(1954, 7, 4));
    }
}

I hope that you understood what a delegate is and how to use it. So the big questions are:

  • Are they useful?
  • When to use delegates?
  • Why not call the method directly?

When and Why, You Should Use a Delegate

One of the most common use cases of delegates is to give user a choice developing a library. Let’s develop one that calculates the quadratic equation given a, b and c.

public class QuadraticEquationSolver
{
    public double[] Solve(double a, double b, double c)
    {
        double discriminant, denominator, x1, x2;
        if (a == 0)
        {
            x1 = -c / b;

            Console.WriteLine($"The roots are equal: {x1}");

            return new double[] { x1 };
        }
        else
        {
            discriminant = (b * b) - (4 * a * c);
            denominator = 2 * a;
            if (discriminant > 0)
            {
                Console.WriteLine("The roots are real and not equal");

                x1 = (-b / denominator) + (Math.Sqrt(discriminant) / denominator);
                x2 = (-b / denominator) - (Math.Sqrt(discriminant) / denominator);

                Console.WriteLine($"Roots are found {x1} and {x2}");

                return new double[] { x1, x2 };
            }
            else if (discriminant == 0)
            {
                Console.WriteLine($"Discriminant is 0, the roots are equal");

                x1 = -b / denominator;

                Console.WriteLine($"Roots is found {x1}");

                return new double[] { x1 };
            }
            else
            {
                Console.WriteLine($"Roots are imaginary");
                return Array.Empty<double>();
            }
        }
    }
}

The user could use our library like this:

class Program
{
    static void Main(string[] args)
    {
        QuadraticEquationSolver solver = new QuadraticEquationSolver();
        double[] answers = solver.Solve(5, 3, 6);

        foreach (var answer in answers)
        {
            Console.WriteLine(answer);
        }
    }
}

This library consists of just a single class with a single method inside. It calculates x1 and x2 based on the given parameters. Since the user has no idea what happens inside our method, we give him a sneak peek by outputting some logs. Unfortunately, not all users are built equal.

What happens if this user did not appreciate the logs on his console and wants to get rid of them? He simply cannot if he got our library as a NuGet package and does not have the source code. What happens if another user wants these logs but not on the console but on a text file?

If you started to think that you cannot please every user on the planet I am here to prove you wrong. You can and the name of this pleasure is called delegate.

We can simply ask our users to provide us where to log the information and even if we should log it.

Let’s make a delegate field in our class and assign it with a constructor:

private readonly Action<string> _log;

public QuadraticEquationSolver(Action<string> log)
{
    _log = log;
}

Then we have to change how we print message in our solve method:

public double[] Solve(double a, double b, double c)
{
    double discriminant, denominator, x1, x2;
    if (a == 0)
    {
        x1 = -c / b;

        _log($"The roots are equal: {x1}");

        return new double[] { x1 };
    }
    else
    {
        discriminant = (b * b) - (4 * a * c);
        denominator = 2 * a;
        if (discriminant > 0)
        {
            _log("The roots are real and not equal");

            x1 = (-b / denominator) + (Math.Sqrt(discriminant) / denominator);
            x2 = (-b / denominator) - (Math.Sqrt(discriminant) / denominator);

            _log($"Roots are found {x1} and {x2}");

            return new double[] { x1, x2 };
        }
        else if (discriminant == 0)
        {
            _log($"Discriminant is 0, the roots are equal");

            x1 = -b / denominator;

            _log($"Roots is found {x1}");

            return new double[] { x1 };
        }
        else
        {
            _log($"Roots are imaginary");
            return Array.Empty<double>();
        }
    }
}

By doing this we give our users a choice on where they want these logs. Here is a use case for a user that wants us to print the logs in Debug window instead of Console:

class Program
{
    static void Main(string[] args)
    {
        Action<string> logger = (message) =>
        {
            Debug.WriteLine(message);
        };
        QuadraticEquationSolver solver = new QuadraticEquationSolver(logger);
        double[] answers = solver.Solve(5, 3, 6);

        foreach (var answer in answers)
        {
            Console.WriteLine(answer);
        }
    }
}

What happens if the user does not care about the logs? He can just pass a delegate that does nothing:

Action<string> logger = (message) => { };

Or even better, we can create another constructor and do this ourselves:

private readonly Action<string> _log;

public QuadraticEquationSolver()
{
    _log = (message) => { };
}

public QuadraticEquationSolver(Action<string> log)
{
    _log = log;
}

Now we pleased all of our users and made their lives a little bit better.

Conclusion

In this post we took a look at delegates. There are much more things to say about them but as I said at the beginning, I wanted to keep things simple and give simple examples. Even if some of them are not clear how they work you can clearly see the use case.

As always, you can check all the examples in my github: Back to Basics - What Is a Delegate

Become a Subscriber

Subscribe to my blog and get the latest posts straight to your inbox.