C# Delegates
Delegates are type-safe function pointers in C#. They provide a mechanism for invoking methods indirectly and are fundamental to implementing event handlers and callback patterns.
What is a Delegate?
A delegate is a reference type that represents references to methods with a particular parameter list and return type. When you create a delegate instance, you can associate it with any method that has a matching signature (same return type and same parameter types).
Delegate Declaration
You declare a delegate using the delegate keyword. The syntax is similar to a method signature, but without a method body.
public delegate void MyDelegate(string message);
This declares a delegate named MyDelegate that can point to methods returning void and accepting a single string argument.
Using Delegates
To use a delegate, you typically perform these steps:
- Declare the delegate.
- Define one or more methods that match the delegate's signature.
- Create an instance of the delegate, associating it with a method.
- Invoke the delegate to call the associated method(s).
1. Declaring a Delegate
// Declare a delegate that can point to methods
// that take an int and return an int.
public delegate int MyMathOperation(int x, int y);
2. Defining Methods
public class Calculator
{
public static int Add(int a, int b)
{
return a + b;
}
public static int Subtract(int a, int b)
{
return a - b;
}
}
3. Instantiating and Invoking a Delegate
// Create a delegate instance pointing to the Add method.
MyMathOperation operation = new MyMathOperation(Calculator.Add);
// Invoke the delegate.
int result = operation(5, 3); // result will be 8
// You can also reassign the delegate to point to another method.
operation = Calculator.Subtract;
result = operation(10, 4); // result will be 6
Multicast Delegates
Delegates can also point to multiple methods. This is known as a multicast delegate. When you invoke a multicast delegate, all the methods it points to are executed sequentially.
Multicast Delegate Example
You can use the + and - operators to add or remove methods from a delegate instance.
public delegate void MyNotification(string notification);
public class UserManager
{
public void SendEmail(string message)
{
Console.WriteLine($"Sending email: {message}");
}
public void SendSMS(string message)
{
Console.WriteLine($"Sending SMS: {message}");
}
}
// In another part of your code:
UserManager userManager = new UserManager();
MyNotification notificationDelegate = userManager.SendEmail; // Start with one method
// Add another method to the delegate
notificationDelegate += userManager.SendSMS;
// Invoke the delegate - both methods will be called
notificationDelegate("User logged in!");
// Remove a method
notificationDelegate -= userManager.SendEmail;
// Invoke again - only SendSMS will be called
notificationDelegate("User profile updated.");
Output:
Sending email: User logged in!
Sending SMS: User logged in!
Sending SMS: User profile updated.
Event Handlers and Delegates
Delegates are the backbone of C#'s event handling mechanism. Events are essentially a way to declare a delegate type and then declare a field of that delegate type. This allows objects to broadcast messages (events) to other objects that have registered to listen.
Common Delegate Types
The .NET Framework provides several built-in generic delegate types that cover most common scenarios:
Action<T1, T2, ...>: For methods that do not return a value (void).Func<TResult>,Func<T1, TResult>,Func<T1, T2, ..., TResult>: For methods that return a value.Predicate<T>: A special type ofFunc<T, bool>used for methods that return a boolean value based on a single input parameter.
Using Built-in Delegates (Func)
// Using Func for a method that returns a value
Func<int, int, int> multiply = (a, b) => a * b;
int product = multiply(7, 6); // product will be 42
// Using Action for a method that returns void
Action<string> printMessage = Console.WriteLine;
printMessage("Hello from Action delegate!");
Key Benefits of Delegates
- Type Safety: Delegates ensure that the method signature matches the delegate signature, preventing runtime errors.
- Flexibility: They allow you to pass methods as parameters to other methods, enabling callback mechanisms and advanced design patterns.
- Event Handling: They are essential for implementing event-driven programming.
- Decoupling: They can decouple sender and receiver objects, as the sender doesn't need to know the specific type of the receiver, only that it can handle the event/callback.