Functions delegates
A function can have one or more parameters of different data types, but what if you want to pass a function itself as a parameter? How does C# handle the callback functions or event handler? The answer is - delegate.
A delegate is like a pointer to a function. It is a reference type data type and it holds the reference of a method. All the delegates are implicitly derived from System.Delegate class.
A delegate can be declared using delegate keyword followed by a function signature as shown below.
1<access_modifier> delegate <return_type> <delegate_name>(<parameters>)
In C#, delegates form the basic building blocks for events. A delegate is a type that defines a method signature. In C# you can instantiate a delegate and let it point to another method. You can invoke the method through the delegate.
1internal class Program
2{
3 // declare delegate
4 public delegate void Print(int value);
5 static void Main(string[] args)
6 {
7 // Print delegate points to PrintNumber
8 Print printDel = PrintNumber;
9 // or
10 // Print printDel = new Print(PrintNumber);
11 printDel(100000);
12 printDel(200);
13 // Print delegate points to PrintMoney
14 // Print delegate points to PrintMoney
15 Console.OutputEncoding = Encoding.UTF8;
16 printDel = PrintMoney;
17 printDel(10000);
18 printDel(200);
19 Console.ReadLine();
20 }
21
22 public static void PrintNumber(int num)
23 {
24 Console.WriteLine("Number: {0,-12:N0}",num);
25 }
26
27 public static void PrintMoney(int money)
28 {
29 Console.WriteLine("Money: {0:C}", money);
30 }
31}
In the above example, we have declared Print delegate that accepts int
type parameter and returns void. In the Main() method, a variable of Print type is declared and assigned a PrintNumber method name. Now, invoking Print delegate will in-turn invoke PrintNumber method. In the same way, if the Print delegate variable is assigned to the PrintMoney method, then it will invoke the PrintMoney method.
The following image illustrates the delegate.
Optionally, a delegate object can be created using the new operator and specify a method name, as shown below:
1Print printDel = new Print(PrintNumber);
The delegate can be invoked like a method because it is a reference to a method. Invoking a delegate will in-turn invoke a method which id referred to. The delegate can be invoked by two ways: using () operator or using the Invoke() method of delegate as shown below.
1Print printDel = PrintNumber;
2printDel.Invoke(10000);
3//or
4printDel(10000);
1internal class Program
2{
3 public delegate double MathDelegate(double value1, double value2);
4 public static double Add(double value1, double value2)
5 {
6 return value1 + value2;
7 }
8
9 public static double Subtract(double value1, double value2)
10 {
11 return value1 - value2;
12 }
13
14 static void Main(string[] args)
15 {
16 MathDelegate mathDelegate = Add;
17 var result = mathDelegate(5, 2);
18 Console.WriteLine(result);
19 // output: 7
20 mathDelegate = Subtract;
21 result = mathDelegate(5, 2);
22 Console.WriteLine(result);
23 // output: 3
24 Console.ReadLine();
25 }
26}
As you can see, we use the delegate keyword to tell the compiler that we are creating a delegate type. Instantiating delegates is easy with the automatic creation of a new delegate type.
You can also use the new keyword method of instantiating a delegate
1MathDelegate mathDelegate = new MathDelegate(Add);
An instantiated delegate is an object; you can pass it around and give it as an argument to other methods.
Another great feature of delegates is that you can combine them together. This is called multicast. You can use the + or += operator to add another method to the invocation list of an existing delegate instance. Similarly, you can also remove a method from an invocation list by using the decrement assignment operator (- or -=). This feature forms the base for events in C#. Below is a multicast delegate example.
1internal class Program
2{
3 static void Hello(string s)
4 {
5 Console.WriteLine(" Hello, {0}!", s);
6 }
7
8 static void Goodbye(string s)
9 {
10 Console.WriteLine(" Goodbye, {0}!", s);
11 }
12
13 delegate void Del(string s);
14 static void Main(string[] args)
15 {
16 Del a, b, c;
17 Del? d, k;
18 // Create the delegate object a that references
19 // the method Hello:
20 a = new Del(Hello);
21 // Create the delegate object b that references
22 // the method Goodbye:
23 b = Goodbye;
24
25 // The two delegates, a and b, are composed to form c:
26 c = a + b;
27 //allo stesso modo possiamo inizializzare una variabile delegate Del a null
28 //e usare += per aggiungere un metodo
29 k = null;
30 //aggiungo un metodo
31 k += a;
32 //aggiungo un altro metodo
33 k += b;
34 // Remove a from the composed delegate, leaving d,
35 // which calls only the method Goodbye:
36 d = c - a;
37 Console.WriteLine("Invoking delegate a:");
38 a("A");
39 Console.WriteLine("Invoking delegate b:");
40 b("B");
41 Console.WriteLine("Invoking delegate c:");
42 c("C");
43 Console.WriteLine("Invoking delegate d:");
44 d("D");
45 Console.WriteLine("Invoking delegate k:");
46 k("K");
47 // Output:
48 // Invoking delegate a:
49 // Hello, A!
50 // Invoking delegate b:
51 // Goodbye, B!
52 // Invoking delegate c:
53 // Hello, C!
54 // Invoking delegate d:
55 // Goodbye, D!
56 // Invoking delegate k:
57 // Hello, K!
58 // Goodbye, K!
59 //quanti sono i delegati associati a a k?
60 Console.WriteLine("quanti sono i delegati associati a k?");
61 int invocationCount = k.GetInvocationList().GetLength(0);
62 Console.WriteLine($"Sono esattamente {invocationCount}");
63 Console.ReadLine();
64 }
65}
All this is possible because delegates inherit from the System.MulticastDelegate class that in turn inherits from System.Delegate. Because of this, you can use the members that are defined in those base classes on your delegates.
For example, to find out how many methods a multicast delegate is going to call, you can use the following code:
1int invocationCount = d.GetInvocationList().GetLength(0);
When you assign a method to a delegate, the method signature does not have to match the delegate exactly. This is called covariance and contravariance. Covariance makes it possible that a method has a return type that is more derived than that defined in the delegate. Contravariance permits a method that has parameter types that are less derived than those in the delegate type.
Here is an example of covariance:
1class Program
2{
3 public delegate TextWriter CovarianceDel();
4 public static StreamWriter MethodStream() { return null; }
5 public static StringWriter MethodString() { return null; }
6
7 static void Main(string[] args)
8 {
9 CovarianceDel del;
10 del = MethodStream;
11 del = MethodString;
12 Console.ReadLine();
13 }
14}
Because both StreamWriter and StringWriter inherit from TextWriter, you can use the CovarianceDel with both methods.
Below is an example of contravariance.
1internal class Program
2{
3 public static void DoSomething(TextWriter textWriter) { }
4 public delegate void ContravarianceDel(StreamWriter streamWriter);
5
6 static void Main(string[] args)
7 {
8 ContravarianceDel del = DoSomething;
9 Console.ReadLine();
10 }
11}
Because the method DoSomething can work with a TextWriter, it surely can also work with a StreamWriter. Because of contravariance, you can call the delegate and pass an instance of StreamWriter to the DoSomething method
You can learn more about this concept here.
<function name>(<parameters>)
Delegate is also used with Event, Anonymous method, Func delegate, Action delegate.