Unlocking the Delegate’s Potential in C#

What is a delegate?

A delegate is a type-safe function pointer that can reference one or more methods with a compatible signature.

Unlike pointers to functions in other languages, like C++, the delegates in C# are full-object oriented.

Having the delegate, you can pass a method as a parameter to other methods, return from methods, or even store delegates in the collections. 

You can call the methods via the delegate instance.

Creating and using a delegate

To create a delegate, you need to use a delegate keyword.

public delegate void NumberDelegate(int i);

In this example, we create a delegate with a NumberDelegate name, accepting methods that return nothing and get one integer parameter.

To create the delegate instance, you must provide the name of the method the delegate will wrap.

void PrintNumber(int i) =>
    Console.WriteLine($"A number is {i}");
// Create an instance of delegate
// assigning the PrintNumber method
NumberDelegate numberDelegate = PrintNumber;

// Calling the delegate
numberDelegate(2);
// Output: A number is 2

The delegate can point to many methods so that you can chain them to one delegate instance.

Lambda expressions

Lambda expressions are the second option for creating a delegate or adding another method to an existing delegate instance.  

numberDelegate += (int i) =>
    Console.WriteLine($"A multiplied number {i} is {i * i}");

numberDelegate(2);
// Output: A number is 2
// A multiplied number 2 is 4

As you can see, calling one delegate instance invokes two methods one by one.

Lambda expressions are compiled to delegates.

var action =
    (string message) => Console.WriteLine(message);

Built-in delegates

The most commonly used built-in delegates in .NET:

  • Action
  • Func
  • Predicate

You can notice that in the previous image, the action variable is an Action delegate.

It encapsulates the method that does not return value and has no parameters.

There is a generic equivalent Action<T> for methods taking parameters. The maximum count of parameters is 16.

My NumberDelegate is not needed. It can be substituted by Action<int> delegate.

Action<int> numberDelegate = PrintNumber;

The Func delegate encapsulates the methods that return value and can have parameters.

If the methods have parameters, you define all parameters and the return value as the last. 


Func<int, int> multipleNumbers =
    (int number) => number * number;

In this example, the delegate attaches the method, taking one integer parameter and returning an integer value.

The Func delegate can also accept a method with a maximum of 16 parameters.

The Predicate delegate encapsulates the methods that define a set of criteria and determine whether the specified object meets those criteria.

Basically, the Predicate delegate is the same as Func<in T, bool> delegate, but with an explicit name.

Predicate<int> predicate = x => x % 2 == 0;

Func<int, bool> funcAsPredicate = x => x % 2 == 0;

At this point, if you’re a fan of LINQ using method syntax, you can realize that you always use delegates. If you didn’t know that 😀

List<int> list = [1, 2, 3, 4];
list.Exists(predicate);
list.Where(funcAsPredicate);

Events

The event is a class member that can notify other objects when something happens.

If you develop a desktop application, this concept should be familiar.

The events require delegates. 

To show that, let’s create a naive implementation of the MyProgress class.

public delegate void NumberChanged(int i);

public class MyProgress(int end)
{
    private int _current = 0;
    private int _end = end;

    public event NumberChanged ProgressChanged;

    public void AddProgress(int currentProgress)
    {
        if (_current < _end)
        {
            _current += currentProgress;
            ProgressChanged(_current);
        }
    }
}

The class takes the end for progress and starts from 0. You need a delegate to create an event, which will occur every time we add progress.

The usage is simple.  

MyProgress myProgress = new MyProgress(100);
myProgress.ProgressChanged +=
    (int current) => Console.WriteLine($"Current progress is {current}");

myProgress.AddProgress(10);
myProgress.AddProgress(20);
myProgress.AddProgress(30);

// Current progress is 10
// Current progress is 30
// Current progress is 60

The event is fired on every valid AddProgress method call, and our delegate printing the current progress is invoked.

Summary

The delegates are a powerful feature in C#.

The delegate is a type-safe pointer to the method, making method encapsulation object-oriented.

The delegates allow passing methods as parameters. They can attach many methods.

You can create call-back methods with delegates.  

The lambda expression (in some contexts) is compiled to delegate.

With delegates, you can create events in C#.

The methods don’t have to match the delegate type exactly. You can read more about Using Variance in Delegates

Scroll to Top