Delegate and its Memory Allocation in C#

Delegates can be thought of as an interface with a single method. Delegate is a type that represents references to methods with a specific parameter list and return type.

Those with a C++ background may find it analogous to a function pointer.

Now a delegate, once defined, can refer to any method that matches the input parameters and return type.

Basically, a delegate is a reference type variable that holds the reference to a method. The reference can be changed at runtime.

Delegates are Reference types

Delegates are reference types and hence copy by reference.

There are two types of delegates, Single and Multicast. Now we need delegates to be a reference type for Multicast delegate siince it must refer to multiple methods, but what about single method delegates? The face is that now all delegates inherit from multicast delegates (even Delegates that refer to a single method).

Note: Also, you can imagine if delegates were implemented as value-types, instances would be very expensive to copy around since a delegate-instance is relatively heavy.

Declaration

The following example shows how to declare delegates.

  1. Public delegate int mydelegate(int a,int b);  

Any method from any accessible class or struct with the same signature (return type and input type) can be referenced from it. The method can also be static.

Do methods referred to by a delegate need to have exact the same signature?

No, here comes the scenario of covariance and contra-variance.

For example, if we have something like the following:

class Dogs: Mammals{}

then:

  1. HandlerMethod handlerMammals = MammalsHandler; //ofcourse, it will be Legal  // Covariance enables this assignment.  
  2.   
  3. HandlerMethod handlerDogs = DogsHandler; //Leagl  
We will discuss covariance and contravariance sometime later.

Simple delegate example:
  1. using System;  
  2.   
  3. delegate int MyDelegate(int a,int b);  
  4. namespace DelegateAppl  
  5. {  
  6.     class DelegateTest  
  7.     {  
  8.           
  9.         public static int AddNum(int p,int q)  
  10.         {  
  11.           int  add = p + q;  
  12.           return add;  
  13.         }  
  14.   
  15.         public static int MultNum(int p, int q)  
  16.         {  
  17.             int mul = p * q;  
  18.             return mul;  
  19.         }  
  20.          
  21.   
  22.         static void Main(string[] args)  
  23.         {  
  24.             //create delegate instances  
  25.             MyDelegate nc1 = new MyDelegate(AddNum); //1  
  26.             MyDelegate nc2 = MultNum; //2  
  27.   
  28.             //calling the methods using the delegate objects  
  29.            int result= nc1(2,5);  
  30.            Console.WriteLine("Value of Num: {0}", result);  
  31.            result=nc2(2,5);  
  32.            Console.WriteLine("Value of Num: {0}", result);  
  33.             Console.ReadKey();  
  34.         }  
  35.     }  
  36. }  
Notice that we created an instance of delegates in the following two ways.
  1. MyDelegate nc1 = new MyDelegate(AddNum); //1  
  2. MyDelegate nc2 = MultNum; //2  
Difference

It is just to show, there is no difference. In the first example, the compiler will automatically infer the delegate you would like to instantiate. In the second example, you explicitly define the delegate.

How C# Compiler Understands

Whenever the compiler encountered a delegate declaration like the following:
  1. public delegate string StringOperation(string myString);  
Then the compiler generates the following code:
  1. public sealed class StringOperation: System.MulticastDelegate  
  2. {  
  3.    public StringOperation (object target, int method);  
  4.    public virtual void Invoke(string myString);  
  5.    public virtual IAsyncResult BeginInvoke(string myString,  
  6.    AsyncCallback callback, object obj);  
  7.    public virtual void EndInvoke(IAsyncResult result);  
  8. }  
Memory management

Now, we can create an instance of a delegate with the new and without the new keyword, so does it mean memory is not allocated for delegates without new? No, it is actually impossible to construct a new delegate without allocating memory because delegates are reference type objects that live on the heap.

Even if you create a delegate without new, the compiler creates it and implements it using Action<T> (we will cover this in the next article). So, writing without new is just a simple way (a syntactic sugar as it is called). A delegate is itself an object, so if you create a delegate perhaps for an anonymous method and give this to some other method to execute and don't store the delegate for future reference, then yes, that will produce garbage. But calling a delegate itself does not produce garbage (you can always use a memory profiler to identify and optimize).

Muticast Delegate

Let’s call a delegate.

Hide the copy code.
  1. int result = ObjDel.invoke(15,5);   
It will return 3.

If the function wraps in the sequence Add, Divide, Mul.

Hide the copy code.
  1. Result = ObjDel.invoke(15,5);  
It will return 75.

It always returns the value of the last function. So what about the other function return values?

Key point

If any delegate object binds or wraps 5 return type functions then only the 5th function will return the value but the rest of the four functions could not return a value. So always the end of the calling of a function by returning a value, the remaining four functions will ignore the return value.

Memory Allocation Of Multicast delegates-

A delegate is compiled to a private sealed class (of the containing class) hidden from you, it is generated during compile time. Every time you initialize a delegate, like MyDelegate myDel = new MyDelegate(AddNum);, you are actually initializing an instance of that hidden class. That class also contains a field called _invocationList, of type System.Object, that is used to reference another delegate and is assigned via Delegate.Combine(Delegate that, Delegate other), which is what happens when you write myDel += new MyDelegate(MultNum);. From that you can figure out how memory is allocated, it's an instantiation of regular objects.