Delegates

A delegate is a variable that holds a method, so a method can be stored, passed as an argument and called later.

Syntaxdelegate string Transform(string input); Transform t = Shout; t("hi");

A method is a named block of code. A delegate is a value that holds a method. That lets you store a method in a variable, pass it to another method as an argument, and call it later.

A delegate type describes the shape of method it accepts. delegate string Transform(string input); declares a type whose values are methods that take one string and return a string. A method of any other shape will not fit, and the compiler rejects it.

Assigning and calling

Transform t = Shout; is shorthand for Transform t = new Transform(Shout);. Both build a delegate pointing at the Shout method. t("hi") is shorthand for t.Invoke("hi"). Each pair compiles to the same code. The short forms are what you will see in real projects, and the long forms are what they mean.

Holding more than one method

Delegates are multicast. t += Quiet; adds a second method, and calling t then runs both, in the order they were added. t -= Quiet; removes one. If the delegate returns a value you only receive the last method's result and the earlier results are thrown away. That is why multicast is used almost only where the return type is void.

Where this leads

You will rarely declare your own delegate type today. The built-in Func and Action types cover nearly every shape, and lambda expressions are the usual way to supply the method. Both are built on exactly this mechanism.

Example

Example · csharp
string Shout(string input) => input.ToUpperInvariant();
string Quiet(string input) => input.ToLowerInvariant();

// Assigning a method to a delegate variable. The longhand for this line is
// Transform transform = new Transform(Shout); both mean the same thing.
Transform transform = Shout;
Console.WriteLine(transform("hello"));

transform = Quiet;                               // point the variable at another method
Console.WriteLine(transform.Invoke("HELLO"));    // t(x) is shorthand for t.Invoke(x)

// A method can take a delegate, so the caller supplies the behaviour.
string Apply(Transform t, string value) => t(value);

Console.WriteLine(Apply(Shout, "delegates"));

// Type declarations come after the top-level statements.
delegate string Transform(string input);

// Output:
// HELLO
// hello
// DELEGATES

When to use it

  • Let a sorting or searching helper accept whatever comparison the caller wants.
  • Pass a logging method into a helper so the helper does not decide where logs go.
  • Store a method now and run it later, such as the action behind a menu entry.

More examples

Multicast: adding and removing methods

Shows that one delegate variable can hold several methods. Removal compares delegates, so you can only remove something equal to what you added. Writing the lambda out a second time inside the -= would not remove it, because the two lambdas are different objects.

Example · csharp
Action notify = () => Console.WriteLine("first");
notify += () => Console.WriteLine("second");

notify();          // runs both, in the order they were added

Action third = () => Console.WriteLine("third");
notify += third;
notify -= third;   // works because `third` is the same delegate instance

notify();

// Output:
// first
// second
// first
// second

A delegate as a method parameter

This is the main reason delegates exist: the helper owns the loop, and the caller owns the decision about what happens to each item. Reach for this when a method is right except for one step you want to vary.

Example · csharp
static class Text
{
    // The caller decides how each line is transformed.
    public static string TransformAll(string[] lines, Transform transform)
    {
        string[] results = new string[lines.Length];

        for (int i = 0; i < lines.Length; i++)
        {
            results[i] = transform(lines[i]);
        }

        return string.Join(" | ", results);
    }
}

delegate string Transform(string input);

// Called as:
// Console.WriteLine(Text.TransformAll(["a", "b"], s => s.ToUpperInvariant()));
// prints: A | B

A named delegate type is not the same type as Func

Two delegate types with identical parameter and return types are still two types, and one will not assign to the other. Wrapping with .Invoke builds a delegate of the type you need. This comes up when a library declares its own delegate type and your code is holding a Func.

Example · csharp
delegate int Combine(int left, int right);

static class CombineDemo
{
    public static void Run()
    {
        Func<int, int, int> builtIn = (a, b) => a + b;

        // Combine bad = builtIn;                  // error CS0029: different types
        Combine shorthand = builtIn.Invoke;        // a new delegate wrapping the old one
        Combine longhand = new Combine(builtIn.Invoke);

        Console.WriteLine(shorthand(2, 3) + longhand(2, 3));

        // Output:
        // 10
    }
}

Discussion

  • Be the first to comment on this lesson.