In this article, we are going to talk more about delegates in C#.
A delegate is a reference to a method. We can use a delegate object to pass it to the code in which we want to call a referenced method, without knowing at compile time which method will be invoked.
If you want to see the complete navigation of this tutorial, you can do that here C# Intermediate Tutorial.
To download the source code, you can visit Delegates in C# Source Code.
We are going to divide this article into the following sections:
Delegate Syntax
A base syntax to create a delegate object is:
delegate Result_Type identifier([parameters]);
There are three steps in defining and using delegates:
- Declaration of our delegate
- Instantiation, creating the delegate’s object
- Invocation, where we call a referenced method
//Declaration public delegate void WriterDelegate(string text); class Program { public static void Write(string text) { Console.WriteLine(text); } static void Main(string[] args) { //Instantiation WriterDelegate writerDelegate = new WriterDelegate(Write); //Invocation writerDelegate("Some example text."); } }
It is important to understand that the return type of a method and the number of parameters must match the delegate’s return type and the number of parameters. Otherwise, we will get a compiler error. We can see in our example that our Write
method has a void as return type and only one string parameter as well as our delegate.
Delegates are very useful in the encapsulation of our methods.
C# has the two built-in delegates: Func<T>
and Action<T>
, there are widely used, so let’s talk more about them.
Func<T> Delegate
This delegate encapsulates a method that has up to sixteen parameters and returns a value of the specified type. So, in other words, we use the Func
delegate only with a method that has a return type other than void.
We can instantiate the Func
delegate with this syntax:
Func<Type1, Type2..., ReturnType> DelegateName = new Func<Type1, Type2..., ReturnType>(MethodName);
We can see that the last parameter inside square brackets is a return type. Of course, we don’t have to initialize a delegate object like this, we can do it in another way:
Func< Type1, Type2..., ReturnType> name = MethodName;
Let’s see how to use Func
delegate with an example:
class Program { public static int Sum(int a, int b) { return a + b; } static void Main(string[] args) { Func<int, int, int> sumDelegate = Sum; Console.WriteLine(sumDelegate(10, 20)); } }
Action<T> Delegate
This delegate encapsulates a method that has up to sixteen parameters and doesn’t return any result. So we can assign to this delegate only methods with the void return type.
We can instantiate the Action
object with this syntax:
Action<Type1, Type2...> DelegateName = new Action<Type1, Type2...>(MethodName);
Or, we can use another way:
Action < Type1, Type2...> DelegateName = MethodName;
Let’s see how to use Action
delegate with an example:
public static void Write(string text) { Console.WriteLine(text); } static void Main(string[] args) { Action<string> writeDelegate = Write; writeDelegate("String parameter to write."); }
Practical Example
In this example, we are going to create an application that executes one of three methods (Sum, Subtract, Multiply) based on a single provided parameter. Basically, if we send Sum
as a parameter, the Sum method will be executed, and so on. First, we will write this example without delegates and then we will refactor that code by introducing delegates.
So let’s start with the first part:
public enum Operation { Sum, Subtract, Multiply } public class OperationManager { private int _first; private int _second; public OperationManager(int first, int second) { _first = first; _second = second; } private int Sum() { return _first + _second; } private int Subtract() { return _first - _second; } private int Multiply() { return _first * _second; } public int Execute(Operation operation) { switch (operation) { case Operation.Sum: return Sum(); case Operation.Subtract: return Subtract(); case Operation.Multiply: return Multiply(); default: return -1; //just to simulate } } } class Program { static void Main(string[] args) { var opManager = new OperationManager(20, 10); var result = opManager.Execute(Operation.Sum); Console.WriteLine($"The result of the operation is {result}"); Console.ReadKey(); } }
If we start this application, we will get the correct response for any operation we send to the Execute
method. But this code could be much better and easier to read without switch-case
expression. If we are going to have more than ten operations (for example), this switch
block would be very ugly to read and maintain as well.
So, let’s change our code to make it readable, maintainable, and more object-oriented. Let’s introduce a new class ExecutionManager
:
public class ExecutionManager { public Dictionary<Operation, Func<int>> FuncExecute { get; set; } private Func<int> _sum; private Func<int> _subtract; private Func<int> _multiply; public ExecutionManager() { FuncExecute = new Dictionary<Operation, Func<int>>(3); } public void PopulateFunctions(Func<int> Sum, Func<int> Subtract, Func<int> Multiply) { _sum = Sum; _subtract = Subtract; _multiply = Multiply; } public void PrepareExecution() { FuncExecute.Add(Operation.Sum, _sum); FuncExecute.Add(Operation.Subtract, _subtract); FuncExecute.Add(Operation.Multiply, _multiply); } }
Here, we create a dictionary that will hold all the operations and all the references towards our methods (Func delegates). Now we can inject this class into the OperationManager
class and change the Execute
method:
public class OperationManager { private int _first; private int _second; private readonly ExecutionManager _executionManager; public OperationManager(int first, int second, ExecutionManager executionManager) { _first = first; _second = second; _executionManager = executionManager; _executionManager.PopulateFunctions(Sum, Subtract, Multiply); _executionManager.PrepareExecution(); } private int Sum() { return _first + _second; } private int Subtract() { return _first - _second; } private int Multiply() { return _first * _second; } public int Execute(Operation operation) { return _executionManager.FuncExecute.ContainsKey(operation) ? _executionManager.FuncExecute[operation]() : -1; } }
Now, we are configuring all in the constructor of the OperationManager
class and executing our action in the Execute
method if it contains the required operation. At the first look, we can see how much better this code is.
Finally, we need to change the Program
class:
class Program { static void Main(string[] args) { var executionManager = new ExecutionManager(); var opManager = new OperationManager(20, 10, executionManager); var result = opManager.Execute(Operation.Sum); Console.WriteLine($"The result of the operation is {result}"); Console.ReadKey(); } }
Conclusion
In this article, we have learned:
- How to instantiate a delegate
- The way to use Func and Action delegates
- How to write a better code by using delegates
If you have been with us along with this entire intermediate series, you’ve hopefully mastered OOP concepts in C#.
This whole series has been great as a reminder on c# for me as I have not used it in years, really enjoyed it, thank you 😁
Thank you Gerry, glad to hear that.
Clear and Concise.
Thank you 🙂
You are most welcome.
Thank you for the writeup – very interesting. Personally, I prefer the switch expression Execute method.
Unless you create different groups of operations, you don’t need to instantiate execution manager outside the operation manager. This is better for unit testing and as a general solution, but in this particular case, someone might get confused why do we need that.