C# Delegates
Methods can be treated as first-class citizens in C# thanks to delegates, which also let you send methods as parameters, return methods from other methods, and store methods in variables. In C#, delegates offer a means of implementing callback mechanisms and event handling.
Delegates are types in C# that represent references to methods with specific signatures. It enables you to define a reference to a method and use the delegate to call it. Delegates are essential for loose coupling and separation of responsibilities in your codebase.
They are accommodating when you need to give one portion of your code a mechanism to call a method located in another part without explicitly understanding the details of the function.
Knowing Why Delegates Are Needed:
Imagine that you have a class that executes various actions, and you wish to enable clients of that class to alter specific aspects of the operation. Without delegates, you must create interfaces or base classes, override methods, and create strict code structures. By allowing customers to pass in methods to be executed inside the class, delegates offer a more flexible way to do this modification.
Syntax and Delegate Declaration
Creating and Declaring Delegates:
A delegate type defines the method signature referenced when creating or declaring a delegate. The methods that can be assigned to the delegate are modeled after the delegate type. The delegate keyword can be used to create a delegate.
Delegate Declaration Syntax:
The following is the syntax for a delegate declaration:
delegate returnType DelegateName(parameterList);
In this case, DelegateName is the name of the delegate type, returnType is the return type of the methods that the delegate can reference, and parameterList specifies the parameters of the methods.
The amount and types of parameters a method must have are specified in the delegate signature, along with the return types. If the delegate represents a method with a return value, it additionally provides the return type. The signature of a method assigned to a delegate must match that of the delegate.
Delegate Instantiation
The process of creating instances of delegate types and coupling them with methods is known as delegation. Delegates allow you to dynamically reference methods and call those methods from the delegate instance. Delegate instantiation in C# offers a versatile method for implementing callback mechanisms and event handling.
Assigning Methods to Delegates:
By making an instance of the delegate type and supplying the method as an argument, you can assign methods to delegates. The method's signature must match the defined signature of the delegate. You can use the delegate instance to call the method after assigning it to the delegate.
Instantiating Delegates with Method References:
Creating a delegate instance and associating it with a method is called delegate instantiation. Here's an example of how to instantiate a delegate with a method reference:
using System;
public delegate void MyDelegate(string message);
public class ProgramExample
{
public static void Main()
{
MyDelegate myDelegate = new MyDelegate(MethodToInvoke);
myDelegate("Hello World");
}
public static void MethodToInvoke(string message)
{
Console.WriteLine(message);
}
}
Output:
![C# Delegates](https://static.tutorialandexample.com/csharp/csharp-delegates1.png)
This code defines a delegate named MyDelegate, creates an instance of the delegate using the MethodToInvoke method, and then invokes the delegate with the message "Hello World" The MethodToInvoke method simply prints the provided message to the console.
Multicast Delegates and Combing Methods:
Multicast delegates, or delegates that can refer to many methods are created using the C# language. This is helpful when invoking several methods using a single delegate invocation. Use the += operator to combine methods in a multicast delegate. All the methods the delegate references are called in the order they were combined when the delegate is activated.
using System;
public delegate void MultiDelegate(string message);
public class Program
{
public static void Main()
{
MultiDelegate multiDelegate = Method1;
multiDelegate += Method2;
multiDelegate("Multicast delegates");
}
public static void Method1(string message)
{
Console.WriteLine($"Method1: {message}");
}
public static void Method2(string message)
{
Console.WriteLine($"Method2: {message}");
}
}
Output:
![C# Delegates](https://static.tutorialandexample.com/csharp/csharp-delegates2.png)
In this example, the multiDelegate is created and combined with methods Method1 and Method2. When the delegate is invoked, both methods are called.
Invoking Delegates
Executing the methods referred to by the delegate instances is invoking delegates. Once a delegate is connected to one or more methods, you can use the delegate instance to invoke those methods. This makes it possible to implement delegate-based callbacks and dynamic method invocation.
Depending on the needs of your application, delegate invocation can be carried out synchronously or asynchronously.
Synchronous Invocation
When a delegate is called synchronously, the caller thread holds off on continuing until the delegate methods have finished running. When calling delegates, this is what happens by default.
using System;
public delegate void MyDelegate();
public class Program
{
public static void Main()
{
MyDelegate myDelegate = MethodToInvoke;
myDelegate(); // Synchronously invoke the delegate
Console.WriteLine("Delegate invocation completed.");
}
public static void MethodToInvoke()
{
Console.WriteLine("MethodToInvoke executed.");
}
}
In this example, the myDelegate is invoked synchronously, and the output order will be:
![C# Delegates](https://static.tutorialandexample.com/csharp/csharp-delegates3.png)
Asynchronous Invocation:
When using asynchronous delegate invocation, the caller thread can continue while the delegate methods execute in their threads. This can help with parallel processing and boost efficiency.
To directly call the methods that the delegate has referenced, use the Invoke method of the delegate. It is beneficial when dealing with asynchronous operations or needing to regulate invocation.
using System;
public delegate int CalculationDelegate(int a, int b);
public class Program
{
public static void Main()
{
CalculationDelegate addDelegate = Add;
CalculationDelegate subtractDelegate = Subtract;
int result1 = addDelegate.Invoke(10, 5); // Explicitly invoke using Invoke method
int result2 = subtractDelegate(10, 5); // Implicit invocation
Console.WriteLine($"Add result: {result1}");
Console.WriteLine($"Subtract result: {result2}");
}
public static int Add(int a, int b)
{
return a + b;
}
public static int Subtract(int a, int b)
{
return a - b;
}
}
Output:
![C# Delegates](https://static.tutorialandexample.com/csharp/csharp-delegates4.png)
In this example, the Invoke method explicitly invokes the addDelegate, while the subtractDelegate is invoked implicitly. Both approaches yield the same results.
Delegate Use Cases
Delegate functions provide solid and adaptable techniques to manage various software development problems. The following are some common uses for delegates:
1. Managing an event with delegates:
In event-driven programming, delegates are frequently used to implement event-handling techniques. Events are a means through which things can alert other objects to the occurrence of interesting events so that they can react appropriately. You can specify and control the methods called when an event is triggered using delegates.
using System;
public delegate void EventHandler(object sender, EventArgs e);
public class Button
{
public event EventHandler Click;
public void OnClick()
{
Click?.Invoke(this, EventArgs.Empty);
}
}
public class Program
{
public static void Main()
{
Button button = new Button();
button.Click += Button_Click;
// Simulate a button click
button.OnClick();
}
private static void Button_Click(object sender, EventArgs e)
{
Console.WriteLine("Button clicked!");
}
}
Output:
![C# Delegates](https://static.tutorialandexample.com/csharp/csharp-delegates5.png)
In this example, the ''Button'' class specifies a 'Click' event using an event handler ('EventHandler') to manage subscribers. The 'Click' event is raised, and the subscribed methods (event handlers) are called when the button is pressed.
2. Delegate-based callback mechanisms:
A valuable tool for creating callback systems is the delegate. With callbacks, you may choose a method that will be executed once a particular operation or event has finished. Delegates allow you to pass methods as arguments to other methods, enabling flexible and adaptable behavior.
using System;
public delegate void CallbackDelegate(int result);
public class Calculator
{
public void PerformCalculation(int a, int b, CallbackDelegate callback)
{
int result = a + b;
callback(result);
}
}
public class Program
{
public static void Main()
{
Calculator calculator = new Calculator();
CallbackDelegate callback = DisplayResult;
calculator.PerformCalculation(24, 15, callback);
}
private static void DisplayResult(int result)
{
Console.WriteLine($"Calculation result: {result}");
}
}
Output:
![C# Delegates](https://static.tutorialandexample.com/csharp/csharp-delegates6.png)
The ''PerformCalculation'' method of the ''Calculator'' class here accepts a delegate (''CallbackDelegate'') as an argument. This delegate stands for a callback method that will be carried out following the calculation.
3. The Role of Delegates in Design Pattern Implementation:
Delegate participation is crucial when implementing design patterns that require dynamic behavior or modification. For instance, the Strategy Pattern calls for establishing a family of algorithms, isolating each algorithm in a different class, and allowing these classes to be used interchangeably. This flexibility can be achieved with delegated authority.
using System;
public delegate void SortingStrategy(int[] array);
public class BubbleSort
{
public void Sort(int[] array)
{
// Bubble sort implementation
int n = array.Length;
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (array[j] > array[j + 1])
{
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
}
public class QuickSort
{
public void Sort(int[] array)
{
// Quick sort implementation
QuickSortRecursive(array, 0, array.Length - 1);
}
private void QuickSortRecursive(int[] array, int low, int high)
{
if (low < high)
{
int pivot = Partition(array, low, high);
QuickSortRecursive(array, low, pivot - 1);
QuickSortRecursive(array, pivot + 1, high);
}
}
private int Partition(int[] array, int low, int high)
{
int pivot = array[high];
int i = (low - 1);
for (int j = low; j < high; j++)
{
if (array[j] < pivot)
{
i++;
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
int temp1 = array[i + 1];
array[i + 1] = array[high];
array[high] = temp1;
return i + 1;
}
}
public class Sorter
{
private SortingStrategy _strategy;
public Sorter(SortingStrategy strategy)
{
_strategy = strategy;
}
public void Sort(int[] array)
{
_strategy(array);
}
}
public class Program
{
public static void Main()
{
int[] array = { 5, 2, 8, 1, 7 };
SortingStrategy bubbleSort = new BubbleSort().Sort;
Sorter sorter = new Sorter(bubbleSort);
sorter.Sort(array);
Console.WriteLine("Sorted using Bubble Sort:");
foreach (var num in array)
{
Console.Write(num + " ");
}
Console.WriteLine();
SortingStrategy quickSort = new QuickSort().Sort;
sorter = new Sorter(quickSort);
sorter.Sort(array);
Console.WriteLine("Sorted using Quick Sort:");
foreach (var num in array)
{
Console.Write(num + " ");
}
}
}
Output:
![C# Delegates](https://static.tutorialandexample.com/csharp/csharp-delegates7.png)
The ''Sorter'' class takes a sorting strategy delegate as a parameter in this example. Different sorting algorithms (strategies) are implemented as separate classes, and their ''Sort'' methods are assigned to delegate instances. This approach enables the dynamic selection of sorting algorithms at runtime.
Delegate Chaining and Invocation List
Delegate chaining enables the creation of a series of method calls that can be called collectively by combining several methods into a single delegate instance. This functionality offers delegate-based programming scenarios flexibility and extensibility.
Combining Multiple Methods in a Delegate:
A delegate instance that has references to several methods is created by delegation chaining. These methods don't have to be of the same type as the delegate's signature; they can also be of different kinds. All chained methods are executed in the order they were introduced when the delegate is called.
Example:
using System;
public delegate void MyDelegate(string message);
public class Program
{
public static void Method1(string message)
{
Console.WriteLine($"Method1: {message}");
}
public static void Method2(string message)
{
Console.WriteLine($"Method2: {message}");
}
public static void Main()
{
MyDelegate delegateChain = Method1;
delegateChain += Method2; // Combine Method1 and Method2
delegateChain("Delegate Chaining");
}
}
Output:
![C# Delegates](https://static.tutorialandexample.com/csharp/csharp-delegates8.png)
Exploring the Invocation List:
The invocation list of a delegate contains references to the chained methods. You can access this list using the ''GetInvocationList()'' method, which returns an array of delegates representing the individual methods.
Example:
using System;
public delegate void MyDelegate(string message);
public class Program
{
public static void Method1(string message)
{
Console.WriteLine($"Method1: {message}");
}
public static void Method2(string message)
{
Console.WriteLine($"Method2: {message}");
}
public static void Main()
{
MyDelegate delegateChain = Method1;
delegateChain += Method2; // Combine Method1 and Method2
Delegate[] invocationList = delegateChain.GetInvocationList();
foreach (var method in invocationList)
{
MyDelegate individualDelegate = (MyDelegate)method;
individualDelegate("Individual delegate!");
}
}
}
Output:
![C# Delegates](https://static.tutorialandexample.com/csharp/csharp-delegates9.png)
Invoking Delegates in Sequence:
When a delegate with a chain of methods is invoked, each method in the invocation list is executed sequentially. If any method throws an exception, the subsequent methods are not invoked.
Example:
using System;
public delegate void MyDelegate();
public class Program
{
public static void Method1()
{
Console.WriteLine("Method1");
}
public static void Method2()
{
throw new Exception("Exception in Method2");
}
public static void Method3()
{
Console.WriteLine("Method3");
}
public static void Main()
{
MyDelegate delegateChain = Method1;
delegateChain += Method2;
delegateChain += Method3;
try
{
delegateChain();
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex.Message}");
}
}
}
Output:
![C# Delegates](https://static.tutorialandexample.com/csharp/csharp-delegates10.png)
Predefined Delegates
Predefined delegates in C# are built-in .NET Framework-provided delegate classes that let you encapsulate functions with specific signatures. These preconfigured delegates make it easier to declare and use delegates by removing the requirement to develop unique delegate types for frequent use cases. The 'System' namespace contains predefined delegates, frequently used in various programming tasks.
Using Action and Func for Common Scenarios:
1. Action Delegates: The 'Action' delegate is a built-in delegate symbolizing a method without a return value. It can accept up to 16 different sorts of input parameters. Action delegates are frequently utilized to encapsulate a method that does an action or has a side effect without returning a value.
Example:
Action<string> printMessage = (message) => Console.WriteLine(message);
printMessage("Hello, Action!");
2. Func Delegates: The 'Func' delegate is a predefined delegate representing methods with return values. Func delegates can accept up to 16 input parameters and have a return type as the last parameter. There are multiple Func delegate types with different numbers of input parameters and return types ('Func<TResult>', 'Func<T, TResult>', 'Func<T1, T2, TResult>', and so on).
Example:
Func<int, int, int> add = (a, b) => a + b;
int result = add(3, 5); // result = 8
Advantages of Using Action and Func:
1. Concise Syntax: Action and Func provide a succinct syntax for defining and utilizing delegates without explicitly declaring bespoke delegate types.
2. Type Safety: Using specified delegates minimizes the possibility of runtime mistakes by ensuring type safety at compile time.
3. Simplified Delegate Usage: Action and Func make building several delegate types for different method signatures unnecessary, simplifying delegate usage.
4. Readability: Especially for straightforward circumstances, the usage of Action and Func can make the code more understandable and expressive.
Common Scenarios for Action and Func:
1. Handling Events: Action delegates come in handy when you need to do actions in response to events without returning a value.
2. Callback Functions: For callback functions where you want to run a method and get the result, func delegates are frequently utilized.
3. Parallel and Multithreading: In parallel and multithreaded contexts, Action and Func are frequently used to encapsulate jobs and actions that must be carried out concurrently.
4. LINQ Expressions: LINQ expressions include Func delegates, which let you perform actions on data collections.
5. Error Handling: Action and Func can be used to group activities that handle errors or exceptions.