C# Delegates: A deep dive


Introduction

This article will explain all about delegates in C#. The article focuses more on practical implementations and less on theory. The article explains the concept in-depth.

Delegates (The definition)

Let’s start with the definition taken from MSDN

"A delegate declaration defines a reference type that can be used to encapsulate a method with a specific signature. A delegate instance encapsulates a static or an instance method. Delegates are roughly similar to function pointers in C++; however, delegates are type-safe and secure."

Delegates

A delegate is one of the most interesting features of the C# programming language. And it can directly be placed under namespace as classes are placed. Delegates completely follow the rule of object oriented programming. Delegates extend the System.Delegate class.

Delegates are the best suit for anonymous invocations as they can call any method provided in the signature of the method i.e. the return type and the parameters is the same. Let’s try to cover the topic via practical examples.

Lab 1

Create a console application and name it what you want, I named it EventsAndDelegates. Add a public class named DelegateExercises with the following implementation.

DelegateExercises

using System;  namespace DelegatesAndEvents{ public class DelegateExercises { public delegate void MyDelegate(); void Method1() { Console.WriteLine("Method1"); Console.ReadLine(); } public void Method2() { MyDelegate myDelegate = new MyDelegate(Method1); myDelegate(); }   }}

Call the method Method2 from Program.cs class.

Program

namespace DelegatesAndEvents{ class Program { static void Main(string[] args) { DelegateExercises delegateExercises=new DelegateExercises(); delegateExercises.Method2(); } }}

Output

https://www.codeproject.com/KB/cs/1153164/image002.png

In the program class, delegateExercises is an instance of the DelegateExercises class and the delegateExercises.Method2() method is invoked. While creating the instance we follow the rule of creating instances using a new keyword. In a similar way, we can also use new with delegate name as shown in Method2 of the class delegateExercises. A delegate is very similar to properties or indexers in C#, i.e. it is a first class member of the class. It seems to be a function but is defined with a keyword named delegate. In the above example of Method2, we created the instance of delegate and passed the whole function i.e. Method1 as a parameter. That means a method itself can also be passed as a parameter using delegates. This is the way in which C# normally handles callback methods or event handlers. To instantiate a delegate, the traditional "new" keyword is used with one parameter i.e. the methods name itself "Method1". Method1 is the member of class "DelegateExercises" with void return type and taking no parameters. The new keyword (as usual) creates an object of type delegate, and a method.

"Method1" is invoked in a nontraditional way i.e. without using Method1() syntax. "myDelegate" is the delegate object of method "Method1" because the method is passed as a parameter when the object was created. Therefore when "myDelegate" is called, it means "Method1" is called, thus providing a level of abstraction.

Lab 2

DelegateExercises

using System;  namespace DelegatesAndEvents{ public class DelegateExercises { public delegate void MyDelegate(); void Method1() { Console.WriteLine("Method1"); Console.ReadLine(); } public void Method2() { MyDelegate myDelegate = new MyDelegate(Method1); myDelegate(50); }   }}

Call the method Method2 from the Program.cs class.

Program

namespace DelegatesAndEvents{ class Program { static void Main(string[] args) { DelegateExercises delegateExercises=new DelegateExercises(); delegateExercises.Method2(); } }}

Output

https://www.codeproject.com/KB/cs/1153164/image003.png

We get an error: "Delegate 'DelegateExercises.MyDelegate' does not take 1 arguments" when we try to call delegate with a parameter. We passed 50 as a parameter for the method Method1, but you can clearly see that Method1 does not takes an integer parameter, therefore an error is shown at compilation.

Lab 3

DelegateExercises

using System;  namespace DelegatesAndEvents{ public class DelegateExercises { public delegate void MyDelegate(); void Method1(int i) { Console.WriteLine("Method1"); Console.ReadLine(); } public void Method2() { MyDelegate myDelegate = new MyDelegate(Method1); myDelegate(); }   }}

Program

namespace DelegatesAndEvents{ class Program { static void Main(string[] args) { DelegateExercises delegateExercises=new DelegateExercises(); delegateExercises.Method2(); } }}

Output

https://www.codeproject.com/KB/cs/1153164/image004.png

We again got an error message, but this is different, we assumed that adding an integer parameter will solve the earlier error, but this error says that the delegate signature should match the method’s signature.

Lab 4

using System;  namespace DelegatesAndEvents{   public class DelegateExercises { public delegate int MyDelegate(int intValue);   public int Method1(int intMethod1) { return intMethod1*2; }   public int Method2(int intMethod2) { return intMethod2*10; }   public void Method3() { MyDelegate myDelegate = new MyDelegate(Method1); int result1 = myDelegate(10); System.Console.WriteLine(result1); myDelegate = new MyDelegate(Method2); int result2 = myDelegate(10); System.Console.WriteLine(result2); } }   public class Program { public static void Main() { DelegateExercises delegateExercises = new DelegateExercises(); delegateExercises.Method3(); Console.ReadLine();   } }}

Output

https://www.codeproject.com/KB/cs/1153164/image005.png

To get rid of the error, an integer parameter is added to the delegate MyDelegate. This means the method that will call the delegate will automatically handle this parameter. So this delegate now returns an integer instead of a void. Finally, when the method is run through delegate, the compiler checks for the return parameters too. In Method3, we now make the implementation more tricky, and create one more delegate type having another method name as an argument, and we again execute the delegate with the same parameter having integer value : 10, but the method to be called changes each time, therefore the code could be written more dynamically.

Lab 5

namespace DelegatesAndEvents{ public class Program { public static void Main() { DelegateExercises delegateExercises = new DelegateExercises(); delegateExercises.Method3(); } }   public class DelegateExercises { public delegate int MyDelegate();   void Method1() { System.Console.WriteLine("MyDelegate"); }   public void Method3() { MyDelegate myDelegate = new MyDelegate(Method1); myDelegate(); } }}

Output

Error : 'void DelegateExercises.Method1()' has the wrong return type

Here the compiler finds a mismatch, the return type of the delegate should ideally be integer if used in the context as mentioned in above code, but Method1 returns void.

Lab 6

using System;  namespace DelegatesAndEvents{ public class Program { public static void Main() { DelegateExercises delegateExercises = new DelegateExercises(); delegateExercises.Method3(); Console.ReadLine(); } }   public class DelegateExercises { public delegate int MyDelegate(int intValue);   int Method1(int intMethod1) { return intMethod1*2; }   int Method2(int intMethod1) { return intMethod1*10; }   public void Method4(MyDelegate myDelegate) { int result = myDelegate(10); Console.WriteLine(result); }   public void Method3() { MyDelegate myDelegate = new MyDelegate(Method1); Method4(myDelegate); myDelegate = new MyDelegate(Method2); Method4(myDelegate); } }}

Output

https://www.codeproject.com/KB/cs/1153164/image005.png

The above written code just tries to explain that a delegate is nothing but a class. A delegate’s objects could be passed as a parameter as shown in Method4.

Point to remember: Since a delegate is a datatype, it could be passed to a method.

In the first and second invocation of method Method4, the same delegate is passed a type of MyDelegate. The first time it calls for the method MyDelegate and the second time it calls for Method2. Using the object myDelegate a different method is executed each time. Indirectly, a different method is given each time to Method4 as a parameter. This is like writing a generic and abstract code, where Method4 really doesn’t care about how it is called, and what parameters are passed to it. It segregates execution from the implementation.

Lab 7

using System;  namespace DelegatesAndEvents{ public class Program { public static void Main() { DelegateExercises delegateExercises = new DelegateExercises(); delegateExercises.Method3(); Console.ReadLine(); } }   public class DelegateExercises { public delegate int MyDelegate(int intValue);   int Method1(int intMethod1) { return intMethod1*4; }   int Method2(int intMethod1) { return intMethod1*20; }   public void Method4(MyDelegate myDelegate) { for (int i = 1; i <= 5; i++) System.Console.Write(myDelegate(i) + " "); }   public void Method3() { MyDelegate myDelegate = new MyDelegate(Method1); Method4(myDelegate); myDelegate = new MyDelegate(Method2); Method4(myDelegate); } }}

Output

https://www.codeproject.com/KB/cs/1153164/image006.png

In the above implementation, more segregation and abstraction is achieved. This implementation says, "What we are repeating over and over again is that a delegate can also be executed from within a loop construct as well." A delegate in the above code is passed as an argument to a method, and it executes the name of the method at run time, without knowing its detail at compile time.

Lab 8

using System;  namespace DelegatesAndEvents{ public class Program { public static void Main() { DelegateExercises delegateExercises = new DelegateExercises(); delegateExercises.Method3(); Console.ReadLine(); }   } public delegate void MyDelegate();  public class DelegateExercises { void Method1() { System.Console.WriteLine("Method1"); } public void Method3() { MyDelegate myDelegate = new MyDelegate(Method1); myDelegate(); } }  }

Output

https://www.codeproject.com/KB/cs/1153164/image007.png

The output is Method1 because MyDelegate being a delegate defines a class that extends System.Delegate.

Lab 9

using System;  namespace DelegatesAndEvents{ public delegate void MyDelegate(); public class DelegateExercises : MyDelegate { }}

Output

Error : 'DelegateExercises': cannot derive from sealed type 'MyDelegate'

A delegate is represented internally with a class of the same name. In the above code, the class MyDelegate is implicitly sealed and another class cannot derive from a sealed class.

Point to remember: System.Delegate is an abstract class and all the delegates automatically derive from

Lab 10

using System;  namespace DelegatesAndEvents{ public class Program { public static void Main() { DelegateExercises delegateExercises = new DelegateExercises(); delegateExercises.Method3(); Console.ReadLine(); } }   public delegate void MyDelegate();   public class DelegateExercises { void Method1() { System.Console.WriteLine("Method1");     }   public void Method3() { MyDelegate myDelegate = new MyDelegate(Method1); myDelegate(); System.Console.WriteLine(myDelegate.ToString()); } }    }

Output

https://www.codeproject.com/KB/cs/1153164/image008.png

This simply shows that the .ToString() method could be called over delegates as well because System.Delegate also derives from the ultimate base class i.e. Object.

Lab 11

public delegate void MyDelegate();   public class DelegateExercises { void Method3() { System.Console.WriteLine(MyDelegate.ToString()); } }

Output

Error : An object reference is required for the non-static field, method, or property 'object.ToString()'

The name of the delegate MyDelegate could not be used with .ToString() method because it is not static whereas myDelegate as an object of the class could be used with no errors.

Lab 12

using System;  namespace DelegatesAndEvents{ using System;   delegate void ExampleDelegate(string xyz);   class Program { public static void Method1(string xyz) { Console.WriteLine(xyz + " Method1"); }   public static void Method2(string xyz) { Console.WriteLine(xyz + " Method2"); }   public static void Main() { ExampleDelegate ex1Delegate, ex2Delegate, ex3Delegate, myDelegate;   ex1Delegate = new ExampleDelegate(Method1); ex2Delegate = new ExampleDelegate(Method2); ex3Delegate = ex1Delegate + ex2Delegate; myDelegate = ex1Delegate - ex2Delegate; ex1Delegate("AAA"); ex2Delegate("BBB"); ex3Delegate("CCC"); myDelegate("DDD"); myDelegate = ex3Delegate - ex1Delegate; myDelegate("EEE"); myDelegate = ex3Delegate - ex2Delegate; myDelegate("FFF"); Console.ReadLine(); } }}

Output

https://www.codeproject.com/KB/cs/1153164/image009.png

Now this is a bit more complex scenario, but worth understanding. Instances ex1Delegateex2Delegateex3DelegatemyDelegate are delegate type objects. Instance ex1Delegate represents Method1 and ex2Delegate represents Method2. So ex1Delegate("AAA") calls Method1 and ex2Delegate("BBB") calls Method2. Instance ex3Delegate shows the addition of ex1Delegate and ex2Delegate. Here, addition does not mean literally adding two delegates as a number, but it means that both the delegates should be invoked and executed. First, Method1 is called and then Method2, and it is clearly visible in the output as well. Now the instance myDelegate is initialized to ex1Delegate – ex2Delegate, this again is not a mathematical subtraction, but this implementation will remove methods contained in Method2 from Method1. Since we do not have a common method in method2, it has no significance and Method1 is invoked.

The instance myDelegate is again made equal to ex3Delegate- ex1Delegate. Now this will eliminate all the methods that delegate ex1Delegate represents from object ex3Delegate. The instance ex3Delegate as shown earlier stands for methods Method1 and Method2, now since ex1Delegate represents method Method1, therefore, Method1 gets eliminated from myDelegate. So Method2 is called.

In another case of ex3Delegate – ex2Delegate, had the instance ex3Delegate executed, both Method1and Method2 would get called. But since we are doing a subtraction of ex2Delegate, only Method1 is called.

Point to remember: One can call as many methods we want through Delegates.

Lab 13

using System;  namespace DelegatesAndEvents{ public class Program { public static void Main() { DelegateExercises delegateExercises = new DelegateExercises(); delegateExercises.Method3(); Console.ReadLine(); } }   public delegate int MyDelegate(out int i);   public class DelegateExercises { int Method1(out int i) { System.Console.WriteLine("Method1"); i = 10; return 0; }   public void Method3() { MyDelegate myDelegate = new MyDelegate(Method1); MyDelegate myDelegate1 = new MyDelegate(Method1); MyDelegate myDelegate2 = myDelegate + myDelegate1; int intValue; myDelegate2(out intValue);   } }  }

Output

https://www.codeproject.com/KB/cs/1153164/image010.png

In the above example we see, that a delegate method could also accept out parameters, and hence we get our result too.

Lab 14

using System;  namespace DelegatesAndEvents{ public class Program { public static void Main() { DelegateExercises delegateExercises = new DelegateExercises(); delegateExercises.Method3(); Console.ReadLine(); } } public delegate int MyDelegate(out int i);  public class DelegateExercises { int Method1(out int i) { i = 100; System.Console.WriteLine("Method1 " + i); return 0; } public void Method3() { MyDelegate myDelegate = new MyDelegate(Method1); MyDelegate myDelegate1 = null; MyDelegate myDelegate2 = myDelegate + myDelegate1; int intValue; myDelegate2(out intValue); } }   }

Output

https://www.codeproject.com/KB/cs/1153164/image011.png

The same code works now. The only difference is that one of the delegate is assigned to null. These are the rules of simple delegates, but the rules of a multicast delegate are more constrained.

Two separate delegates can point to a same function and target object. So ‘+’ operator helps us add delegates and the ‘-’ operator helps us remove one delegate from another.

Lab 15

using System;  namespace DelegatesAndEvents{ public class Program { public static void Main() { DelegateExercises delegateExercises = new DelegateExercises(); try { delegateExercises.Method3(); Console.ReadLine(); } catch (System.Exception ex) { System.Console.WriteLine("Exception Occurred."); Console.ReadLine();   } } }   public delegate void MyDelegate();   public class DelegateExercises { void Method1() { throw new System.Exception(); }   public void Method3() { MyDelegate myDelegate = new MyDelegate(Method1); myDelegate(); } }  }

Output

https://www.codeproject.com/KB/cs/1153164/image012.png

In the above code, myDelegate() calls method Method1.

This method throws an exception. We choose not to handle this exception in Method3 but in the Main method where Method3 is called. We see that exception caused by a delegate keeps on propagating until it is caught.

Lab 16

using System;  namespace DelegatesAndEvents{ public class Program { public static void Main() { DelegateExercises delegateExercises = new DelegateExercises(); delegateExercises.Method3(); Console.ReadLine();   } }   public delegate void MyDelegate(ref int intValue);   public class DelegateExercises { void Method1(ref int intValue) { intValue = intValue + 5; System.Console.WriteLine("Method1 " + intValue); }   public void Method3() { MyDelegate myDelegate = new MyDelegate(Method1); MyDelegate myDelegate1 = new MyDelegate(Method1); MyDelegate myDelegate2 = myDelegate + myDelegate1; int intParameter = 5; myDelegate2(ref intParameter); } }  }

Output

https://www.codeproject.com/KB/cs/1153164/image013.png

In the earlier example, we saw the ‘out’ parameter and came to a conclusion that they are not used in multicast delegates. Whereas in the above example we see that passing the parameter by ‘ref’ is allowed in multicast delegate.

Conclusion

This article covered the topic of Delegate in detail. Delegates are very crucial to understand but are tricky to implement. I hope this post helped the readers to get an insight of delegates.