In this article, we’re going to talk about C# delegates, which are a prerequisite for learning events-based programming. Delegates are one of the fundamental building blocks of flexible applications, as they can be found in the vast majority of .NET framework code base and client libraries.

To download the source code for this article, you can visit our GitHub repository.

What is a C# Delegate?

A C# delegate is a reference to a method. If we call a delegate, the referenced method is called instead. Delegates have all the necessary information to call a method with a specific signature and return type. We can pass delegates as an argument to other methods or we can store it in a structure or a class. We use delegates when we don’t know which method is going to be called at design time but only during runtime.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Delegates are widely used to implement events and callback methods.

Declaring Delegates

To declare a delegate we use the delegate keyword:

delegate void PrintMessage(string text);

This delegate can reference any method that has one string parameter and doesn’t return a result. 

Additionally, we can take a look at the delegate syntax:

[access modifier] delegate [return type] [delegate name]([parameters])

With that out of the way, let’s see how we can instantiate a delegate.

Instantiating Delegates

Let’s define a method that this delegate can point to:

public static void WriteText(string text) => Console.WriteLine($"Text: {text}");

Now we can instantiate the delegate in a few different ways:

var delegate1 = new PrintMessage(WriteText);

There’s also a shorter version of the same syntax:

var delegate2 = WriteText;

And we can do it with an anonymous method as well:

var delegate3 = delegate(string text)
    { Console.WriteLine($"Text: {text}"); };

Or even more concisely with a lambda method:

PrintMessage delegate4 = text =>  
    { Console.WriteLine($"Text: {text}"); };

As you can see, the last two examples don’t need our method, we can just add the implementation directly. The last example is probably something you’re used to seeing most of the time since it’s the most modern approach (C# 3 onwards).

For a more structured code base, we would use the first two approaches, and if we want a quick solution we would use the third and the fourth approach.

Invoking Delegates

Delegates can be invoked by using the Invoke() method:

delegate1.Invoke("Go ahead, make my day.");

or just by invoking the delegate directly using ():

delegate1("Go ahead, make my day.");

This results in the text being printed to the console:

Text: Go ahead, make my day.
Text: You're gonna need a bigger boat.

That’s all there is to it.

Or is it?

Multicasting Delegates

Delegates are even more powerful than that. One delegate can reference multiple different methods. These delegates are called multicast delegates.

We’ve printed some text to the console. Let’s say we need to print it in the reverse order as well. Let’s create that does that:

public static void ReverseWriteText(string text) => Console.WriteLine($"Text in reverse: {Reverse(text)}");

private static string Reverse(string s)
{
    char[] charArray = s.ToCharArray();
    Array.Reverse(charArray);
    return new string(charArray);
}

And then we can implement the multicast delegate in two different ways. We can create a delegate that’s the combination of the existing ones:

var delegate1 = new PrintMessage(WriteText);
var delegate2 = new PrintMessage(ReverseWriteText);
// with + sign
var multicastDelegate = delegate1 + delegate2;

multicastDelegate.Invoke("Go ahead, make my day.");
multicastDelegate("You're gonna need a bigger boat.");

This prints out:

Text: Go ahead, make my day.
Text in reverse: .yad ym ekam ,daeha oG
Text: You're gonna need a bigger boat.
Text in reverse: .taob reggib a deen annog er'uoY

We can also use the = and += to achieve the same thing:

var delegate1 = new PrintMessage(WriteText);
var delegate2 = new PrintMessage(ReverseWriteText);

// with =, +=, and -=
multicastDelegate = delegate1;
multicastDelegate += delegate2;

multicastDelegate.Invoke("Go ahead, make my day.");
multicastDelegate("You're gonna need a bigger boat.");

The result is exactly the same.

Generic Delegates

We can make our delegates even more useful, by extending them with generic types.

We can create a generic delegate:

delegate T Print<T>(T param1);

This delegate both accepts and returns generic types. 

Let’s say we have a method that accepts a string and then returns a reversed string to the caller:

public static string ReverseText(string text) => Reverse(text);

While instantiating generic delegates we need to declare the concrete types:

Print<string> delegate3 = new Print<string>(ReverseText);
Console.WriteLine(delegate3("I'll be back."));

This prints out the reversed string as expected:
.kcab eb ll'I

But now we’re not bound to use just strings, we can use anything we like, even the complex objects. The implementation is entirely up to the methods we reference.

Action<T> and Func<T> Delegates

Now that we know how to create generic delegates, we can mention Func<T>, and  Action<T> delegates readily available within the .NET framework. These delegates already do what we’ve done manually with delegates so far.

In our case, we can use the Action<T> delegate to encapsulate the ReverseWriteText method with no return types, or the Func<T> delegate to encapsulate the ReverseText method because it has a return type.

Action<string> executeReverseWrite = ReverseWriteText;
executeReverseWrite("Are you not entertained?");

Func<string, string> executeReverse = ReverseText;
Console.WriteLine(executeReverse("Are you not entertained?")); 

The results are exactly the same as before.

Now we can go ahead and create all sorts of useful methods. For example, the useful Where() Linq extension method uses the Func delegate:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

Say we have a list of strings. We can easily extract all the strings that contain the specific substring:

var stringList = new List<string>();
stringList.Where(x => x.Contains("some text"));

The Contains() method evaluates the expression, and returns the source (string in this case) if it fulfills the condition, which is in this case that the string contains “some text”. We’ll end up with the IEnumerable<string> of all the strings that contain the phrase “some text”.

Conclusion

Delegates are an exceptionally useful concept in C#. Once you wrap your head around them, you can use them in all kinds of scenarios and create more modular and flexible applications. We’ve seen how we can declare, instantiate, use, and multicast delegates. Then we’ve introduced the generic delegates which we see all around the codebases, sometimes even without noticing them.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!