Author : MD TAREQ HASSAN | Updated : 2022/01/23
What is a Delegate?
- A delegate type lets us define a safe function pointer object. Therefore, we can think of the delegate type as a reference to a class method.
- Delegate type represents references to methods with a particular parameter list and return type
- We can register multiple compatible methods to a delegate type variable and all of those methods can be invoked by that delegate
delegate
is a keyword in C# to define delegate type- To invoke methods or lambda expressions dynamically at runtime, we need to use delegates
- We can use a predefined method or lambda expression as an argument when calling a method with a parameter of the delegate type
Delegate Syntax
Define delegate type
public delegate double MyDelegate(int x, int y);
// ... ... ...
Create delegate type variable and assign methods
public double FooMethod(int p, int q)
{
// ... ... ...
}
public double BarMethod(int r, int s)
{
// ... ... ...
}
// ... ... ...
MyDelegate md = FooMethod;
md += BarMethod;
Now, invoke registered methods
md.Invoke(4, 5);
Usage of Delegate
We can use Action and Func to implement features in which we want to call referenced method without knowing which method will be invoked at compile time. Practical examples:
- Callback function
- Event handler
- To declare predicate when implementing strategy design pattern
- Creating custom LINQ
About Built-in Generic Delegate
- Built-in delegate:
- Built-in means it is already available in the C# base class library
- We do not need to define delegate ourselves, we can just use the delegate type when needed
- Generic delegate:
- Generic means we can decide the actual type when using it
- If we use a lambda expression, the actual type will be deferred
Advantages of built-in generic delegates
- No need to define new delegate type to implement new features (we just need to used it)
- The flexibility to either decide actual type or defer type (in case of lambda) when instantiated by client code
- Save time and minimize the number of new types that we need to create in order to work with delegates
- Improves developer’s productivity
- Reusable, type safe and efficient than non-generic counterparts
Action Delegate
- Action is a built-in generic delegate used for a parameterless method with a void return type
- We can use this delegate to pass a method or lambda expression without explicitly declaring a custom delegate
- Normally we use Action delegate to perform an operation that does not return any result
There are variations of Action delegate with up to 16 arguments:
Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16>
Usage of Action Delegate
- As callback function
- Event handler
Example - writing a message to both console and debug output
Action<string> messageBroker = Console.WriteLine;
messageBroker += Debug.WriteLine;
var message = "This a test message";
messageBroker.Invoke(message);
Example - updating UI on receiving API response
public interface IGithubHttpClient
{
public void FetchRepositoryList(string githubId, int fetchCount, Action<List<string>> OnReceivedRepos);
}
public class GithubHttpClient : IGithubHttpClient
{
public async void FetchRepositoryList(string githubId, int fetchCount, Action<List<string>> OnReceivedRepos)
{
//
// For simplicity, simulating API call instead of actual API call
// (in actual app, Flurl.Http or native HttpClient will be used)
//
var twoSeconds = 2000;
await Task.Delay(twoSeconds);
var repoList = new List<string>();
for (int i = 0; i < fetchCount; i++)
{
repoList.Add(@"https://github.com/hovermind");
}
//
// Invoke callback
//
OnReceivedRepos(repoList);
}
}
//
// Program.cs
//
IGithubHttpClient githubClient = new GithubHttpClient();
Console.WriteLine("Calling Github API \n");
githubClient
.FetchRepositoryList(
githubId: "CodeMazeBlog",
fetchCount: 3,
OnReceivedRepos: (repoList) =>
{
// Update UI when data is received
Console.WriteLine($"Received repository list \nRepositoryCount : {repoList.Count}");
});
Console.WriteLine("When data is being fecthed, perforing other tasks in UI i.e. updating progress bar.\n");
var _ = Console.ReadKey();
Func Delegate
- Func is a built-in generic delegate
- Func always returns a value, therefore it must have at least one generic argument type
- When only one argument is used, it will indicate return type
- Func is used when need to call a method or lambda expression dynamically at runtime and that method or lambda expression must return some value
Similar to Action, Func also have variations with up to 16 arguments and the last argument of Func is always return type:
Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>
Usage of Func Delegate
- To declare predicate when implementing strategy design pattern
- Creating custom LINQ
Example - giving user the option to decide type of opertaion they want to perform
//
// user determines what kind of arithmetic operation should be performed by passing a lambda expression
//
public interface IMathService
{
public string PerformOperation(int x, int y, Func<int, int, double> arithmeticOperation);
}
public class MathService : IMathService
{
public string PerformOperation(int x, int y, Func<int, int, double> arithmeticOperation)
{
var result = arithmeticOperation(x, y);
var xPercentage = x / result * 100;
var yPercentage = y / result * 100;
return $"X: {xPercentage:N2}% and Y: {yPercentage:N2}%";
}
}
//
// Program.cs
//
IMathService mathService = new MathService();
var resultString = mathService.PerformOperation(2, 8, arithmeticOperation: (x, y) => x + y );
Console.WriteLine(resultString);
Predicate Delegate
- A built-in delegate that returns
bool
- We can say predicate is a spefial type of Func with only on generic type
T
and return typebool
Syntax
public delegate bool Predicate<in T>(T obj);
Usage of Predicate Delegate
- Creating custom LINQ
- Used by several methods of the Array and List
classes to search for elements in the collection
Example - creating custom LINQ
public static class CustomLinqExtensionMethod
{
public static IEnumerable<int> Filter(this IEnumerable<int> numberList, Func<int, bool> predicate)
{
foreach (var number in numberList)
{
if (predicate(number))
{
yield return number;
}
}
}
}
//
// Program.cs
//
var numberList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var evenNumbersOnly = numberList.Filter(number => number % 2 == 0);
Console.WriteLine($"Numbers after filtering: { string.Join(",", evenNumbersOnly) }");
var _ = Console.ReadKey();