Delegates in C#
Introduction
Delegates in C# are like the instructions you give to different pieces of code, telling them what to do and how to work together. Delegates were introduced in C# to provide a flexible and powerful mechanism for implementing callback functions, event handling, functional programming and for asynchronous programming.
Use Cases
Callback function: Invoke one piece of code after an event or after execution of some code.
Eg: Imagine a weather app where you need to fetch real time weather data from 3rd party sources. Instead of freezing the entire application while waiting for data, initiating an asynchronous data retrieval process can be optimal. You provide a callback function (delegate) that processes the weather once it is fetched. This ensures that your application remains responsive, and the weather information is seamlessly integrated when available.
Event handling: Communicate and respond to certain occurrences, specially in event driven architecture.
Eg: You have two buttons in your weather app interface: one for Celsius and another for Fahrenheit. Each button is associated with a click event. When a user clicks the Celsius button, the Celsius button click event is triggered which shows the data in °C.
Functional programming: Delegates allow passing of function or methods as parameter which is the main idea behind functional programming. Think of it like legos. Just like Lego pieces that can be interchanged and combined to create various structures, delegates enable the passing of functions or methods as parameters to other functions. This paradigm is the core principle of functional programming.
Asynchronous programming: Delegates allow you to define callback methods that get invoked when an asynchronous task completes.
Eg: Imagine you're uploading photos to the cloud. Without asynchronous help, you'd have to wait for each photo to be uploaded before doing anything else. Asynchronous operation helps your app to start sending photos and not wait around. So, you're free to use the app while all your pictures get uploaded to the cloud in the background.
Example
By using delegate
keyword with return type, delegate name, and parameters, you declare a delegate. A simple example portraying use of delegate:
delegate int Addition(int a, int b);
// method that takes two numbers (a and b) and returns their sum.
private int AddNumbers(int a, int b)
{
return a + b;
}
public void OperateAddNumbers()
{
// create an instance of the Addition delegate and point it to the private AddNumbers method.
Addition addition = new Addition(AddNumbers);
// invokes the delegate, calling the AddNumbers method with following arguments.
int ret = addition(1,2);
Console.WriteLine($"Result of addition: {ret}");
}
Delegates are like messengers that deliver your instructions to someone who knows how to perform a specific task. In this example, we declare a delegate named Addition
that represents the idea of adding two numbers. The private method AddNumbers
is the actual source which computes in this task. Now, when the public method OperateAddNumbers
wants to add numbers, it creates a helpful messenger (delegate) named addition
and tells it, 'Go to AddNumbers
, and ask it to add 1 and 2.' The messenger delivers the request, and we get the result.
So, delegates simplify our code by acting as messengers between different parts of our program, making sure the right source handle specific tasks.
Another scenario, imagine you have a notification service that can send messages, and you have users who want to receive those messages. A messenger is the one who delivers news to everyone.
We will build a NotificationService
class that consists of the delegate NotificationHandler
, messenger to notify of type NotificationHandler
and the message sender SendNotification
.
public class NotificationService
{
// Delegate handling notification
public delegate void NotificationHandler(string message);
// "messenger" to notify
public NotificationHandler Notify;
// send notification
public void SendNotification(string message)
{
// ensure that there are subscribers to send notification
if(Notify != null)
{
Notify(message);
}
}
}
Above, we declare a special kind of helper named NotificationHandler
(a delegate). It's like saying, "Hey, I'm creating a messenger that can carry messages." Notify
is our messenger. It's the one who will deliver the messages. When the SendNotification
method is called, it checks if there is a messenger (Notify
). If yes, it tells the messenger to deliver the message. User receives the notification through the messenger who ensures that the message is sent to every subscribed user.
// Class portraying subscriber.
public class Subscriber
{
public string Username { get; set; }
// Imagine this as a smartphone, showing user notification.
public void ReceiveNotification(string message)
{
Console.WriteLine($"New notification for you {Username}: {message}");
}
}
Each Subscriber
has a method ReceiveNotification
, which is what happens when the messenger delivers a message. In this case, it shows the message on the console.
// create notification service
NotificationService notificationService = new NotificationService();
// create subscribers using collection initializer
List<Subscriber> listOfSubscriber = new List<Subscriber>()
{
new Subscriber(){ Username = "Priti"},
new Subscriber(){ Username = "Jon"},
new Subscriber(){ Username = "Ram"}
};
// Subscribe Users to the Notification Service
foreach (Subscriber subscriber in listOfSubscriber)
{
notificationService.Notify += subscriber.ReceiveNotification;
}
// random amount of voucher
Random randAMount = new Random();
int discountAmount = randAMount.Next(10, 251);
// send notification to the subscribers who are subscribed.
notificationService.SendNotification($"Here's a voucher of Rs. {discountAmount}! Enjoyyyyyy.");
Now, each time the SendNotification
method is called, it generates a random discount amount and includes it in the notification message. Subscribers will receive notifications with different voucher amounts every single time. Each user in the listOfSubscribers
is subscribed to the Notify
event of the NotificationService
, its like saying, "Hey, when there's a message, let me know, and here's what I'll do with it". When the SendNotification
method is called, it triggers the Notify
event, and each subscribed user's ReceiveNotification
method is invoked, displaying the received message.
Output:
In simple terms, the delegate (NotificationHandler
) acts as a messenger between the NotificationService
and the subscribers (Subscriber
). It allows the service to notify all subscribers when there's something exciting happening, like sending a voucher. So, the delegate facilitates communication and ensures everyone who's interested gets the news.
Delegates are extensively useful when implementing Observer Design Pattern and in Event-Driven architecture where an object (in our example, NotificationService
) needs to notify other objects (such as Subscriber
) about changes or events.
Built-in delegates
C# introduced built in delegates types Func<>
, Predicate<>
, and Action<>
in version 2.0. Instead of writing delegates yourself, these built in delegate types can be more expressive way to work with delegates. Func<>
, Predicate<>
, and Action<>
are of generic delegate type meaning they can be used with different types of parameter and return types. This makes them highly reusable in various scenarios reducing the need for custom delegate. It also seamlessly integrate with LINQ expressions, making it more expressive on collections.
I often skip writing my own custom delegates.
Func<>
,Predicate<>
, andAction<>
are my preferences due to the versatility and readability.
Func<> | Predicate<> | Action<> | |
Purpose | Represents a method with parameters and a return type. | Represents a method that evaluates a condition. | Represents a method with parameters and no return. |
Return type | Has a return type (can be any type, including void). | Always returns a boolean (true or false). | Does not have a return type (void). |
Parameters | Can have zero or more input parameters. | Usually has one input parameter. | Can have zero or more input parameters. |
Example | public Func<int, int, string> AddNumbersFunc = (a, b) => (a + b).ToString(); | public Predicate<int> isEven = num => num % 2 == 0; | public Action<int, int> printSumOfNums = (a, b) => Console.WriteLine($"Sum : {a + b}"); |
Example call | Console.WriteLine(concatenateNumbers(1, 4)); // Output: 5 | Console.WriteLine(isEven(2)); // Output: True | printSum(30, 20); // Output: Sum: 50 |
Use case | Mathematical calculations, and custom logic in LINQ queries, leveraging its versatility to define functions tailored to specific needs in a single line of code. | Filtering or testing conditions. | Performing an action without returning a value. |
The versatile: Func<>
The versatility of Func<>
allows it to produce result based on the input parameter. It's a generic delegate type that can represent any method with parameters and a return type.
Where to use: you want to get result after doing something.
Example: You want to calculate the total price of items in a shopping cart.
public Func<List<int>, int> calculateTotalPriceInTheCart = items => items.Sum();
List<int> itemInCartPrices = new List<int> { 210, 450, 1500, 2600 };
int totalPrice = eg.calculateTotalPriceInTheCart(itemInCartPrices);
Console.WriteLine(totalPrice);
// Prints: 4760
Conditional crafter: Predicate<>
Predicate<>
is perfect for scenarios where you need to evaluate a condition and return a boolean. Predicate<>
can be used in filtering collections, conditional validation, removing elements, custom conditions in LINQ, event handling, conditional logic in algorithms, and testing conditions in unit testing, leveraging its true or false outcomes to express a wide range of conditions in a single line of code.
Where to use: you want to check some condition.
Example: You want to check if a customer is eligible for a discount based on their purchase amount. If the total amount exceed 1000, customer receives 10% discount.
public Predicate<int> isEligibleForDiscount = purchaseAmount => purchaseAmount > 1000;
bool eligible = eg.isEligibleForDiscount(totalPrice); // Result: True
Console.WriteLine($"Is eligible for discount: {eligible}");
if (eligible)
{
Console.WriteLine($"Total price before discount: {totalPrice}");
totalPrice = totalPrice - (int)(0.1 * totalPrice); // explicit casting. Implicit casting is not allowed from decimal to int.
Console.WriteLine($"Total price after 10% discount: { totalPrice}");
}
/*
Prints:
Is eligible for discount: True
Total price before discount: 4760
Total price after 10% discount: 4284
*/
The silent executor: Action<>
Action<>
is a go-to delegate when you need to represent a method that takes parameters but doesn't return anything (its a void
). It focus on action rather than returning the values. Action<>
can be used where the focus is on execution rather than computation.
Where to use: focus is on some action rather than computation.
Example: You want to log a confirmation message when a customer makes a purchase.
public Action<string> logPurchaseConfirmationMessage = message => Console.WriteLine($"Log: {message}");
eg.logPurchaseConfirmationMessage("Purchase confirmed for username12345.");
// Action: it prints the log message.
// Log: Purchase confirmed for username12345.
Multicast delegate
Up to this point, we understood that a delegate references to a method allowing for a level of abstraction and flexibility in function invocation.
Moving on, the concept of multicast delegate is its ability to point to and invoke multiple methods concurrently. Multicast delegate can maintain a list of methods and invoke them concurrently. Its declaration is similar to regular delegate. The difference is we keep adding methods to the delegate. Basically, multicast delegate is a collection with multiple references to methods allowing it to invoke all of them when triggered at the same time.
In our previous example, Subscriber
had only one method to receive message ReceiveNotification(string message)
. We will add another method here ReceiveAnotherNotification(string message)
. Think of this like apps in your smartphone. You can have bunch of them installed. You can receive messages/notifications from all of them. In our case, we can have as much notification as our subscribers want but we will invoke both of them using multicast delegate. Here is the added method:
public void ReceiveAnotherNotification(string message)
{
Console.WriteLine($"New notification for you from other service {Username}: {message}");
}
Subscriber
class now look like this:
public class Subscriber
{
public string Username { get; set; }
public void ReceiveNotification(string message)
{
Console.WriteLine($"New notification for you {Username}: {message}");
}
// newly added method to demonstrate multicast delegate
public void ReceiveAnotherNotification(string message)
{
Console.WriteLine($"New notification for you from other service {Username}: {message}");
}
}
Now, we will add ReceiveAnotherNotification(string message)
to be included in our notify. +=
is used to add event handlers (methods or delegates) to an event (notify), allowing multiple subscribers to respond to an event when it occurs.
// create notification service
NotificationService notificationService = new NotificationService();
// create subscribers
List<Subscriber> listOfSubscriber = new List<Subscriber>()
{
new Subscriber(){ Username = "Priti"},
new Subscriber(){ Username = "Jon"},
new Subscriber(){ Username = "Ram"}
};
// Subscribe Users to the Notification Service
foreach (Subscriber subscriber in listOfSubscriber)
{
notificationService.Notify += subscriber.ReceiveNotification;
// add reference of another method to our Notify delegate.
notificationService.Notify += subscriber.ReceiveAnotherNotification;
// this is multicast delegate.
}
// random amount of voucher
Random randAMount = new Random();
int discountAmount = randAMount.Next(10, 251);
// send notification to the subscribers who are subscribed.
notificationService.SendNotification($"Here's a voucher of Rs. {discountAmount}! Enjoyyyyyy.");
Everything looks similar to above delegate example except in the foreach
loop where we added reference of ReceiveAnotherNotification
in our Notify
a "messenger". Now, when the SendNotification
method is called, the multicast delegate Notify
invokes both the ReceiveNotification
and ReceiveAnotherNotification
methods for each subscriber. It's like receiving notifications from multiple services or apps on your smartphone. The power of multicast delegates shines in scenarios where you want to notify multiple methods or subscribers about an event concurrently, enhancing the flexibility and extensibility of your event-driven architecture.
Summary
Delegate:
- Delegates in C# act as messengers, providing a flexible and powerful mechanism for callback functions, event handling, functional programming, and asynchronous programming.
Use Cases:
Callback Function: Invoke code after an event or execution.
Event Handling: Communicate and respond to occurrences in event-driven architecture.
Functional Programming: Pass functions or methods as parameters.
Asynchronous Programming: Define callback methods for asynchronous tasks.
Example:
NotificationService
class andSubscriber
class demonstrate delegates in action, facilitating communication between a service and subscribers.
Built-in Delegate Types:
Func<>
: Represents a method with parameters and a return type.Predicate<>
: Represents a method that evaluates a condition (always returns boolean).Action<>
: Represents a method with parameters and no return (its avoid
).
Func<>
:Used when you want to produce a result based on input parameters.
Example: Calculating the total price of items in a shopping cart.
Predicate<>
:Perfect for checking conditions and returning a boolean result.
Example: Checking if a customer is eligible for a discount based on purchase amount.
Action<>
:Used when the focus is on executing actions without returning values.
Example: Logging a confirmation message when a customer makes a purchase.
Multicast Delegates:
Extension of regular delegates, pointing to and invoking multiple methods concurrently.
Maintain a list of methods for simultaneous invocation.
Example: Sending notifications to subscribers using a multicast delegate in
NotificationService
.
More :
Delegates simplify code by acting as messengers between different parts of the program.
Func<>
,Predicate<>
, andAction<>
are built-in delegate types for added expressiveness and versatility.Multicast delegates enhance flexibility and extensibility in event-driven architectures.
Multicast delegate can maintain a list of methods and invoke them concurrently.
Subscribe to my newsletter
Read articles from Sushant Pant directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by