4 Real World Examples of C# Delegates: Software Interview
The Delegate. Mighty software beast.
Delegates.
The other one of my problems when I stumbled upon them for the first time.
Delegates in C# are powerful constructs that play a crucial role in achieving flexibility and extensibility in software design.
A very important and yet difficult topic to fully understand.
Today, we are taking a look at the senior software interview question:
When would you use delegates?
We will take a look with 4 real world examples of how to use them, along with the theory behind them.
Place your booty in a comfortable position and Let’s Get Right Into it!
Introduction to Delegates
Delegates in C# are essentially type-safe function pointers.
They allow you to treat methods as first-class citizens, enabling you to pass methods as parameters, store them in variables, and invoke them dynamically.
This flexibility is particularly beneficial in scenarios where you want to achieve loose coupling between components or design extensible frameworks.
Event handlers are nothing more than methods that are invoked through delegates.
You create a custom method, and a class such as a windows control can call your method when a certain event occurs.
Here is how you can declare a delegate.
public delegate int PerformCalculation(int x, int y);
Let’s look at some examples.
Example1️⃣— Event Handling
Delegates are commonly used in event handling scenarios.
Consider a simple example where you have a button in a graphical user interface, and you want to perform some action when the button is clicked. Delegates facilitate this by allowing you to define and invoke methods dynamically.
public class Button
{
// Declare a delegate for the click event
public delegate void ClickHandler();
// Declare the click event using the delegate
public event ClickHandler OnClick;
// Method to simulate a button click
public void Click()
{
// Invoke the Click event
OnClick?.Invoke();
}
}
public class Program
{
public static void Main()
{
Button myButton = new Button();
// Subscribe to the Click event using a delegate
myButton.OnClick += () => Console.WriteLine("Button Clicked!");
// Simulate a button click
myButton.Click();
}
}
In the Program
class, an instance of Button
is created, and a delegate is used to subscribe to the OnClick
event. When the button is clicked, the subscribed method is dynamically invoked.
This example demonstrates how delegates provide a mechanism for defining and invoking events dynamically, promoting loose coupling in event-driven architectures.
Here, we can assign the lambda to the OnClick
, because the OnClick
event is based on the delegate of type void
.
Example2️⃣—Asynchronous Programming
Delegates play a crucial role in asynchronous programming, especially with the introduction of the Async
and await
keywords.
Consider a scenario where you need to perform parallel processing using asynchronous methods.
public class AsyncProcessor
{
// Declare a delegate for asynchronous operations
public delegate Task<int> AsyncOperation();
// Method to execute asynchronous operations
public async Task<int> ExecuteAsync(AsyncOperation operation)
{
// Asynchronously execute the operation
return await operation();
}
}
public class Program
{
public static async Task Main()
{
// Create an instance of the AsyncProcessor class
AsyncProcessor asyncProcessor = new AsyncProcessor();
// Define an asynchronous operation using a delegate
// Here, we store the operation (lambda) in the AsyncOp property
AsyncProcessor.AsyncOperation operation = async () =>
{
await Task.Delay(1000);
return 42;
};
// Execute the asynchronous operation
// Here, we can pass the previously created operation and execute it.
int result = await asyncProcessor.ExecuteAsync(operation);
Console.WriteLine($"Result: {result}");
}
}
In the Program
class, an instance of AsyncProcessor
is created.
An asynchronous operation is defined using a delegate, and then it is executed using the ExecuteAsync
method.
This example illustrates how delegates, especially when combined with asynchronous programming, enable the execution of dynamic operations asynchronously.
Example3️⃣ — Plugin Architecture
Delegates are instrumental in creating plugin architectures where components can register and execute dynamically loaded plugins.
This is common in extensible systems where you want to allow third-party developers to extend the functionality.
In the third example, we explore the use of delegates in creating a simple plugin architecture.
The PluginManager
class declares a delegate PluginAction
representing a plugin, and it maintains a list of registered plugins.
public class PluginManager
{
// Declare a delegate for plugin actions
public delegate void PluginAction();
// List to store registered plugins
private List<PluginAction> plugins = new List<PluginAction>();
// Method to register a plugin
public void RegisterPlugin(PluginAction plugin)
{
plugins.Add(plugin);
}
// Method to execute all registered plugins
public void ExecutePlugins()
{
foreach (var plugin in plugins)
{
plugin();
}
}
}
In the Program
class, an instance of PluginManager
is created, and plugins are registered using delegates.
Later, all registered plugins are executed.
public class Program
{
public static void Main()
{
// Create an instance of the PluginManager class
PluginManager pluginManager = new PluginManager();
// Register plugins using delegates
pluginManager.RegisterPlugin(() => Console.WriteLine("Plugin 1 executed"));
pluginManager.RegisterPlugin(() => Console.WriteLine("Plugin 2 executed"));
// Execute all registered plugins
pluginManager.ExecutePlugins();
}
}
This example showcases how delegates can facilitate the creation of extensible systems by allowing dynamic registration and execution of plugins.
Example4️⃣ — LINQ (Language Integrated Query)
Delegates are extensively used in LINQ to enable querying and manipulating collections.
Consider a scenario where you want to filter a list of numbers based on a certain condition.
In the fourth example, we demonstrate the use of delegates in LINQ for querying and filtering collections.
The Program
class defines a list of numbers and uses a delegate (Func<int, bool>
) to represent a filtering condition.
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
// Create a list of numbers
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Use a delegate to define the filtering condition
Func<int, bool> filterCondition = x => x % 2 == 0;
// Use LINQ to filter the numbers based on the condition
var evenNumbers = numbers.Where(filterCondition);
// Print the filtered numbers
Console.WriteLine("Even Numbers:");
foreach (var number in evenNumbers)
{
Console.WriteLine(number);
}
}
}
Here, the delegate filterCondition
is used to define the filtering logic for selecting even numbers from the list.
The LINQ Where
method then utilizes this delegate to filter the numbers accordingly.
Conclusion
These examples collectively illustrate the versatility of delegates in C# across various scenarios, showcasing their ability to enhance code modularity, flexibility, and maintainability.
It might be quite difficult to understand at first.
If you try them out on a VS for yourself, you will see that eventually everything will make sence.
I tried commenting as much as possible through my examples.
Hopefully, you found this technical article useful.
Don’t forget that practice makes perfect and this is your ringing bell for a coffee break, before you give it a try!
If you found this article useful, make sure to write a comment and let’s connect!
Thank you for being with me and I will see you in the next one! Happy coding, engineers!