Unlocking delegate's potential in C#
Working on event-driven applications can be challenging, where you need to write a large number of methods. Remembering those methods and deciding their usage for every requirement is tedious, especially when methods have different signatures. C# brings delegates to cope with this problem. Delegating is a powerful feature introduced solely to ease developers in event-driven programming. Understanding delegates is difficult sometimes. So, I brought a comprehensive post to let you know the delegate's definition, analogy, and magic.
![Unlocking delegate's potential in C#](https://blog.elmah.io/content/images/2025/02/unlocking-delegates-potential-in-csharp-o-83d5276a7b816653.png)
What is Delegates in C#?
Delegates are function pointers that dynamically encapsulate and invoke one or multiple methods at runtime. They act as a decoupling layer between methods and the caller in a type-safe manner. All the methods passed as a parameter to a delegate must have the same signature(return type and parameters list). Delegate store references to methods and does not know the implementations of the methods. Its job is to execute the appropriate method upon calling.
To understand better, consider the analogy of a waiter in a restaurant. The waiters are the middleman between you (the customer) and the chef(s) in the kitchen.
- The Delegate (Waiter): The waiter knows references to all the chefs and what dishes they cook. The waiter doesn’t cook the food themselves. Instead, they take your order and decide on their own which chef should handle it based on the type of dish you ordered.
- The Method (Chef): The Chef is the executor or performer here. Each chef in the kitchen prepares certain types of dishes, just like a method that performs a specific task.
- The Encapsulation: As a customer, you don’t need to know which chef cooks what dish and don't need to go to the chef yourself to order. The waiter (delegate) abstracts that detail and ensures the correct chef is called.
Let's understand deeper with the help of code
Example 1: A Printing methods’ Delegate
Create delegate:
public delegate void PrintMessage(string message);
Create a method matching delegate signature (return type and parameters)
void PrintToConsole(string message)
{
Console.WriteLine($"{message} printed to console");
}
Create a delegate instance and assign the PrintToConsole method to it:
PrintMessage printMessageDel = new PrintMessage(PrintToConsole);
printMessageDel("the message");
Output
![Output](https://blog.elmah.io/content/images/2024/12/output.png)
Now, assign another method to this delegate.
The method
void PrintToFile(string message)
{
Console.WriteLine($"{message} printed to file");
}
Add it to the delegate.
printMessageDel += PrintToFile;
Calling the delegate is the same as above.
printMessageDel("the message");
Output
![Output](https://blog.elmah.io/content/images/2024/12/output2-1.png)
It calls the methods in the order they were added to the delegate.
Hence, we abstracted the method calls with the delegate. Without delegates, we had to do
PrintToConsole("the message");
PrintToFile("the message");
A delegate that references more than one method is called a Multicast delegate.
Example 2: Menu with conditional method execution
This example will be simple but will meet real-world applications of delegating. We are asking for user input and invoking methods based on the input. Lets employee delegate here
Create a delegate
public delegate void PrintMessage(string name);
Methods are
void AddContact(string name)
{
Console.WriteLine($"Added contact: {name}");
}
void ViewContact(string name)
{
Console.WriteLine($"Viewing contact: {name}");
}
void DeleteContact(string name)
{
Console.WriteLine($"Deleted contact: {name}");
}
Assigning methods to the delegate
Dictionary<string, PrintMessage> menu = new Dictionary<string, PrintMessage>
{
{ "1", AddContact },
{ "2", ViewContact },
{ "3", DeleteContact }
};
Main program
while (true)
{
Console.WriteLine("\nMenu:");
Console.WriteLine("1. Add Contact");
Console.WriteLine("2. View Contact");
Console.WriteLine("3. Delete Contact");
Console.WriteLine("4. Exit");
Console.Write("Enter your choice: ");
string choice = Console.ReadLine();
if (menu.ContainsKey(choice))
{
Console.Write("Enter the contact name: ");
string name = Console.ReadLine();
menu[choice](name); // Pass the input string (contact name) to the method
if (choice == "4") break;
}
else
{
Console.WriteLine("Invalid choice. Please try again.");
}
}
Output
![Output](https://blog.elmah.io/content/images/2024/12/output3-1.png)
![Output](https://blog.elmah.io/content/images/2024/12/output4-1.png)
Example 3: Delegates with Lambda expression
You don't always need to create methods separately before assigning them to a delegate. With the modern lambda expression of C#, you can simply add a lambda expression directly to the delegate.
PrintMessage printMessageDel = (name) =>
{
Console.WriteLine($"Greetings, {name}!");
};
printMessageDel("Wyne");
printMessageDel("Doug");
Output
![Output](https://blog.elmah.io/content/images/2024/12/output5-1.png)
Advantages of delegates in C#
One of the delegate's most significant additions in coding is decoupling between the caller and the receivers (methods). Calling code only needs to know the method signature while its implementation and names are abstracted. Delegates help in event-driven programming, where one object can notify other objects of changes (like button clicks, data changes, etc.) following the observer pattern. The same signature methods are assigned to a single delegate, avoiding compile-time type safety errors that may arise due to incompatible method calls. You can design callback patterns where one method is passed to another as a parameter and executes in a specific flow. Multicasting allows the invocation of multiple methods on a single action or call.
Common Built-in Delegates in C#
C# provides several delegates in the System namespace. Some of the most common are
- Action
- Func
- Predicate
Action
Represents a method that does not return a value (void).
Action<string> greet = name => Console.WriteLine($"Hello, {name}!");
greet("Perry");
// Outputs Hello, Perry!
Func
Represents a method that returns a value.
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(5, 3));
// Output: 8
Predicate
Represents a method that takes one parameter of type T and returns a boolean (bool
).
Predicate<int> isEven = num => num % 2 == 0;
Console.WriteLine(isEven(4));
// Output: True
Built-in Delegates Used in LINQ
If you are using LINQ, you are already using its delegate. Methods like Select
, Where
, and OrderBy
used Func
delegate.
List<int> numbers = new List<int> { 1, 2, 3, 4 };
// Select - Project each element
var squares = numbers.Select(num => num * num);
Console.WriteLine(string.Join(", ", squares));
Output
![Output](https://blog.elmah.io/content/images/2024/12/output6-1.png)
Now you understand the magic behind LINQ usefulness and conciseness is delegate. When we pass a lambda function or lambda expression in LINQ, we actually assign it to a delegate.
Conclusion
C# delegates are a powerful tool for method decoupling in a type-safe manner. We saw the basics and real-world usage of delegates in the blog post. Delegates can be handy in dealing with a large number of method calls where you want to remember only some of the methods and abstract those details. Multicasting and callback mechanisms add further to the usability of delegates.
elmah.io: Error logging and Uptime Monitoring for your web apps
This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.
See how we can help you monitor your website for crashes Monitor your website