C# Delegates

Summary: in this tutorial, you’ll learn about C# delegates and how to use them effectively.

Introduction to the C# delegates

In C#, delegates are types that represent references to methods with a particular parameter list and return type.

To define a delegate, you use the delegate keyword and specify the method signature. For example:

delegate void Greeting(string message);Code language: C# (cs)

In this example, we define the Greeting delegate type that can reference any method which accepts a string argument and returns void.

Since the Greeting is a delegate type, you can declare it outside a class like other classes.

The following defines the SayHi() method for the Program class, which has the same signature as the Greeting delegate:

class Program
{
    static void SayHi(string name)
    {
        Console.WriteLine($"Hi {name}");
    }
    // ...
}Code language: C# (cs)

To call the SayHi() method via the Greeting delegate, you create an instance of the Greeting delegate with the SayHi method as an argument and call the Invoke() method of the delegate instance like this:

Greeting greeting = new Greeting(SayHi);
greeting.Invoke("John");Code language: C# (cs)

In this syntax, the greeting is an instance of the Greeting delegate type. The greeting delegate holds a reference to the SayHi() method. Internally, the greeting delegate maintains an invocation list that has a reference to the SayHi() method.

When you call the Invoke() method of the greeting delegate, C# will call the SayHi() method with the same argument. Therefore, the following statements are functionally equivalent:

greeting.Invoke("John");Code language: JavaScript (javascript)

And:

SayHi("John");Code language: JavaScript (javascript)

C# provides you with a shorter way to create a new instance of the Greeting delegate by assigning the SayHi method to a delegate variable and calling the referenced method via the delegate:

Greeting greeting = SayHi;
greeting("John");Code language: JavaScript (javascript)

Note that you assign the method name SayHi without parentheses () to the delegate variable.

Put it all together.

delegate void Greeting(string message);

class Program
{
    static void SayHi(string name)
    {
        Console.WriteLine($"Hi {name}");
    }

    static void Main(string[] args)
    {
        Greeting greeting = SayHi;
        greeting("John");
    }
}Code language: C# (cs)

Output:

Hi JohnCode language: C# (cs)

If you have C++ background, the fastest way for you to understand delegates is to think of them as function pointers. However, a delegate is fully object-oriented in C#. And unlike C++ function pointers, delegates encapsulate both an object instance and a method.

Why delegates

Since you can directly call the SayHi() method, you don’t need to call it via the delegate. The question is why do you need a delegate?

Because delegates hold references to methods, you can pass methods as arguments to other methods via the delegates. Therefore, delegates are ideal for defining callback methods.

Suppose you want to define a method that filters a list of integers based on the result of another method. To do that, you can use a delegate.

First, define a delegate type that accepts an integer and returns a boolean value:

delegate bool Callback(int x);Code language: C# (cs)

Second, define the Filter() method that accepts a list of integers and an instance of the Callback. If an integer causes the callback to return true, the result of the Filter() method will include that integer:

static List<int> Filter(List<int> numbers, Callback callback)
{
    var results = new List<int>();

    foreach (var number in numbers)
    {
        if (callback(number))
        {
            results.Add(number);
        }
    }

    return results;
}Code language: C# (cs)

Third, define the isOdd() method that returns true if a number is odd and the isEven() method that returns true if a number is even:

static bool IsOdd(int x) => x % 2 != 0;
static bool IsEven(int x) => x % 2 == 0;Code language: C# (cs)

Fourth, call the Filter() method and pass an instance of the Callback delegate that references the IsEven() method. The Filter() method returns a list of even integer numbers:

var numbers = new List<int> { 1, 2, 3, 4, 5 };

var evenNumbers = Filter(numbers, IsEven);

Console.WriteLine("Even numbers:");
foreach (var number in evenNumbers)
{
    Console.WriteLine($"{number}");
}Code language: C# (cs)

Output:

Even numbers:
2
4Code language: C# (cs)

Fifth, call the Filter() method and pass an instance of the Callback delegate that references the isOdd() method. The Filter() method returns a list of odd integer numbers:

var numbers = new List<int> { 1, 2, 3, 4, 5 };

var oddNumbers = Filter(numbers, IsOdd);

Console.WriteLine("Odd numbers:");
foreach (var number in oddNumbers)
{
    Console.WriteLine($"{number}");
}Code language: C# (cs)

Output:

Odd numbers:
1
3
5Code language: C# (cs)

Put it all together:

class Program
{
    delegate bool Callback(int x);

    static List<int> Filter(List<int> numbers, Callback callback)
    {
        var results = new List<int>();

        foreach (var number in numbers)
        {
            if (callback(number))
            {
                results.Add(number);
            }
        }

        return results;
    }

    static bool IsOdd(int x) => x % 2 != 0;
    static bool IsEven(int x) => x % 2 == 0;

    static void Main(string[] args)
    {
        var numbers = new List<int> { 1, 2, 3, 4, 5 };

        var evenNumbers = Filter(numbers, IsEven);
        Console.WriteLine("Even numbers:");
        foreach (var number in evenNumbers)
        {
            Console.WriteLine($"{number}");
        }


        var oddNumbers = Filter(numbers, IsOdd);
        Console.WriteLine("Odd numbers:");
        foreach (var number in oddNumbers)
        {
            Console.WriteLine($"{number}");
        }
    }
}Code language: C# (cs)

By using a delegate as a callback, you can pass a method as an argument to another method. In this example, the Filter() method is very dynamic that can accept any method for filtering the integer list.

Adding methods to a delegate

A delegate can hold references to multiple methods. In this case, the delegate is called a multicast delegate.

To add a method to a delegate, you use the += operator. For example:

delegate void Greeting(string message);

class Program
{
    static void SayHi(string name) => Console.WriteLine($"Hi {name}");

    static void SayBye(string name) => Console.WriteLine($"Bye {name}");
   
    static void Main(string[] args)
    {
        Greeting greeting = SayHi;
        greeting += SayBye;
        greeting("John");
    }
}Code language: JavaScript (javascript)

Output:

Hi John
Bye John

How it works.

First, define the Greeting delegate type:

delegate void Greeting(string message);Code language: JavaScript (javascript)

Next, define two static methods SayHi() and SayBye() in the Program class:

static void SayHi(string name) => Console.WriteLine($"Hi {name}");
static void SayBye(string name) => Console.WriteLine($"Bye {name}");Code language: JavaScript (javascript)

Then, create a new instance of the Greeting delegate type and assign the SayHi method to greeting variable:

Greeting greeting = SayHi;

After that, add a new method to the invocation list of the greeting delegate:

greeting += SayBye;

Finally, call the methods in the invocation list of the greeting delegate:

greeting("John");Code language: JavaScript (javascript)

This statement invokes the SayHi() and SayBye() method in the invocation list of the greeting method. It’s important to note that the delegate may call the methods in its invocation list in any order. Therefore, you should not rely on the order of the methods.

Delegates are immutable. It means that a delegate cannot be changed once it is created. Therefore, the following creates a new delegate and assigns it to the greeting variable:

greeting += SayBye;

Removing a method from a delegate

To remove a method from a delegate, you use the -= operator. Note that C# will issue an error if you attempt to call a delegate with an empty invocation list.

The following example illustrates how to remove a method from the invocation list of a delegate:

delegate void Greeting(string message);

class Program
{
    static void SayHi(string name) => Console.WriteLine($"Hi {name}");

    static void SayBye(string name) => Console.WriteLine($"Bye {name}");

    static void Say(string message) => Console.WriteLine(message);

    static void Main(string[] args)
    {
        Greeting greeting = SayHi;
        greeting += Say;
        greeting += SayBye;

        greeting -= SayHi;

        greeting("John");
    }
}Code language: C# (cs)

Output:

John
Bye John

In this example, before calling the methods, we remove the SayHi method from the invocation list of the greeting delegate:

greeting -= SayHi;Code language: C# (cs)

If you remove all the methods from the invocation list of a delegate, the delegate will be null. To invoke the delegate with a null check, you can use a null conditional operator like this:

greeting?.Invoke("John");Code language: C# (cs)

Summary

  • A delegate is a type that references methods with a particular parameter list and return type.
  • Use a delegate as a callback to pass a method as an argument to another method.
  • Delegates are immutable.
  • Use += operator to add a method to the invocation list of a delegate.
  • Use -= operator to remove a method from the invocation list of a delegate.
Was this tutorial helpful ?