Skip to content
Search lessons, topics, tests…
Esc

    ↑ ↓ moveEnter openEsc close

    Module 1 · Expression Trees, Delegates and Generics · Lesson 2 of 8

    Delegates, Func/Action and custom delegates

    2. Delegates, Func / Action and custom delegates

    2.1 What is a delegate?

    A delegate is a type that represents references to methods. It is similar to a function pointer in C/C++, but it is type-safe and object-oriented. Delegates enable methods to be passed as parameters and stored for later invocation. Delegates can be combined (multicast) and support variance.

    The official documentation states that delegates are used to pass methods as arguments to other methods. They also note that the signature of the method includes the return type when matching a delegate. You can assign any method that matches the delegate’s signature to the delegate variable, whether it is static or instance.

    C#
    // Declare a delegate type that takes two ints and returns an int
    public delegate int PerformCalculation(int x, int y);
    
    // Methods that match the signature
    static int Add(int a, int b) => a + b;
    static int Multiply(int a, int b) => a * b;
    
    // Assign methods to the delegate
    PerformCalculation calc = Add;
    Console.WriteLine(calc(3, 4)); // 7
    calc = Multiply;
    Console.WriteLine(calc(3, 4)); // 12

    Delegates make excellent callbacks. For example, List<T>.Sort accepts a Comparison<T> delegate that compares two elements; Task.Run accepts a Func<Task> delegate representing an asynchronous method. Delegates are used extensively in LINQ and asynchronous programming.

    2.2 Built-in generic delegates: Func and Action

    To avoid declaring lots of custom delegate types, .NET introduces generic delegates:

    • Func<T1, …, TResult> – represents a method that takes zero or more parameters and returns a value. The last type argument is the return type.
    • Action<T1, …> – represents a method that takes zero or more parameters and returns void.
    • Predicate<T> – represents a method that returns a boolean and takes one parameter. It is basically a Func<T, bool> but with a descriptive name.

    Because these generic delegates are type-safe, they replace many custom delegate declarations. Example:

    C#
    Func<int, int, int> add = (a, b) => a + b;
    Action<string> logger = message => Console.WriteLine(message);
    Predicate<int> isEven = n => n % 2 == 0;

    2.3 Custom generic delegates

    You can create your own generic delegate types if the built-in ones don’t suit your needs. A delegate can define its own type parameters just like a generic class. The generic delegate can then be closed over specific types, similar to generic classes. Microsoft Learn gives an example:

    C#
    public delegate void Del<T>(T item);
    public static void Notify(int i) { /*...*/ }
    Del<int> m1 = new Del<int>(Notify); // explicit assignment
    Del<int> m2 = Notify;
    // method group conversion

    Delegates can also be defined inside generic classes and use the class’s type parameters. A common pattern in .NET uses EventHandler<TEventArgs> – a generic delegate for events where the sender and event argument types are strongly typed. In this pattern, the sender is often the object raising the event and TEventArgs derives from EventArgs.

    2.4 Multicast delegates

    Delegates can point to multiple methods at once. When invoked, they call each method in the invocation list. This is called multicast. The return value (if any) is the return value of the last delegate invoked.

    Multicast delegates are the foundation of events; they let multiple subscribers listen to a single event.

    2.5 Variance and covariance

    Delegates support covariance (return type can be more derived) and contravariance (parameter types can be less derived). This allows methods with compatible signatures to be assigned even if the types differ slightly. For example, Func<object> can be assigned a method returning a string, and Action<string> can refer to a method that accepts an object. Variance is especially useful with events and generic delegates.

    2.6 Practical examples of delegates in a console application

    Delegates come alive when you start passing methods around. The following examples use simple console applications to show different delegate use cases.

    Example 1 – passing a delegate as a callback

    Often you need to perform an operation and then notify code about the result. The method below accepts a Func<int, int, bool> that takes two integers and returns a boolean, and counts how many pairs of numbers satisfy the predicate:

    C#
    static int CountMatches(int[] xs, int[] ys, Func<int, int, bool> predicate)
    {
        int count = 0;
        for (int i = 0; i < xs.Length; i++)
        {
            if (predicate(xs[i], ys[i]))
                count++;
        }
        return count;
    }
    
    // Use the method with different delegates
    int[] a = { 1, 2, 3, 4 };
    int[] b = { 2, 2, 1, 4 };
    int equalCount = CountMatches(a, b, (x, y) => x == y);
    int greaterCount = CountMatches(a, b, (x, y) => x > y);
    Console.WriteLine($"Equal: {equalCount}, Greater: {greaterCount}");

    Here the delegate acts as a callback telling the algorithm how to compare the numbers. Because Func<int,int,bool> is generic, you do not need a custom delegate type.

    Example 2 – multicast delegates

    Delegates can chain multiple methods. When you invoke a multicast delegate, each subscribed method runs in order. Only the result of the last method is returned:

    Example 2 – multicast delegates

    C#
    public delegate void Notification(string message);
    
    // Define two handlers
    static void SendEmail(string msg) => Console.WriteLine($"Email: {msg}");
    static void SendSms(string msg) => Console.WriteLine($"SMS: {msg}");
    
    Notification notify = SendEmail;
    notify += SendSms;
    notify("Your order has shipped");
    
    // Output:
    // Email: Your order has shipped
    // SMS: Your order has shipped

    This pattern underpins events: an event is essentially a multicast delegate with restricted invocation.

    Example 3 – generic custom delegates

    If built-in delegates don’t convey intent, you can define your own generic delegate and use it in a type-safe manner:

    C#
    public delegate TResult Transformer<T, TResult>(T input);
    
    // Use it to transform strings to numbers
    static int LengthOfString(string s) => s.Length;
    
    Transformer<string, int> getLength = LengthOfString;
    Console.WriteLine(getLength("hello")); // 5

    Generic delegates allow you to create domain-specific abstractions while still benefiting from generic type inference and compile-time checking.

    Sign in to mark lessons done and keep your place in the course.Sign in