Delegates
A delegate is a variable that holds a method, so a method can be stored, passed as an argument and called later.
delegate 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
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
// DELEGATESWhen 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.
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
// secondA 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.
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 | BA 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.
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