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 John
Code 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
4
Code 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
5
Code 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.