Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Study of Delegates

0.00/5 (No votes)
19 Jul 2006 1  
An article on delegates in C#.

Introduction

Many of us already have some idea of delegates. But let's go deep inside and examine how the CLR/runtime and the compiler interprets the delegates, and what happens when you declare and manipulate delegates.

A delegate is a reference type, thus it does not hold the actual value, rather it holds the address of an object residing somewhere in the heap. To make the understanding of delegates simple, compilers represent the delegates in a different way than how it is interpreted by the CLR/runtime. We will first study how the compiler exposes delegates, and then we will jump into some ground realities of delegates from the CLR/runtime perspective.

Delegates and .NET Compilers

To make life simpler, most .NET compilers symbolize delegates as pointers to a function, just like the function pointers we have in C++. Thus, in C#, you declare delegates as follows:

public delegate void MyDelegate (int someParam, string anotherParam);

From a C# compiler perspective, MyDelagate is a delegate that could hold the address of a function that has the similar signature.

The ability of holding the address of a function makes delegates ideal for:

  1. Callback functionality
  2. Event modeling
  3. Having more than one implementation against a method

Though the C# compiler exposes delegates as reference types, you could not instantiate the delegate by using the new operator; rather delegates could be instantiated by:

  1. Providing the name of the method to the delegate. These types of delegates are known as "Named Delegates".
    void DoSomething(int someParam, string anotherParam)
    {
        //Method implementation
    
    }
    
    MyDelegate del = DoSomething;
    //del is a Named Delegate holding 
    
    //the address of function DoSomething.
  2. Providing the implementation of the method to the delegate. These type of delegates are known as "Anonymous Delegates".
    del = delegate(int someParam, string anotherParam)
          { //Method implementation };

Invoking of named or anonymous delegates is same as calling the function:

Del (29, "some value");

Named Delegates

Named delegates could hold the address of instance methods as well as the address of static methods. If the return type of a method is inheriting from the return type defined in the delegate, then the delegate is known as a Covariance Delegate. If the parameters of the methods are the base types of the parameters defined in the delegate, then the delegate is known as a Contravariance Delegate.

public delegate ICollection MyDelegate (int someParam, 
                                 string anotherParam); 

IList DoSomething()
{
    //Method implementation

}

MyDelegate del = DoSomething;

//As IList is child of ICollection thus, 

//Del is covariance delegate.

public delegate ICollection MyDelegate (IList param);

IList DoSomething(ICollection param)
{
    //Method implementation

}

MyDelegate del = DoSomething;

//As IList is child of ICollection thus, 

//Del is contravariance delegate.

Anonymous Delegates

If an anonymous delegate is referring to some local variables, then the life of that variable would be extended to the life of that delegate.

public delegate void MyDelegate (int someParam, string anotherParam); 

MyDelegate del
void DoSomething()
{
    int n;
    del = delegate(int someParam, string anotherParam)
    { 
        n++;
    }    
}
//In above example life of local variable n 

//would be same as life of anonymous delegate del.

Delegates and the CLR/Runtime

.NET compilers have made our life easy in handling delegates. In reality, there is neither any concept of pointers to a function nor any significance to the word "delegate" in the CLR/runtime. Rather, the CLR provides an abstract class System.Delegate that holds the reference of a static method or class instance and the instance method of that class. The System.Delegate has two properties.

  • Target: Holds the reference of an instance on which the current delegate invokes the instance method. If the method is a static method, this property holds a null value.
  • Method: Holds the instance of the MethodInfo class for the method represented by the delegate.

The delegate also exposes the DynamicInvoke function to call the method represented by the Method property. This function takes an array of objects that are the arguments to pass to the target method. If the target method does not take any arguments, the parameter to the DynamicInvoke function would be null.

The CLR also exposes an abstract class System.MultiCastDelegate that inherits from System.Delegate. System.MultiCastDelegate maintains a list, called the Invocation List, of a delegate object. When a multicast delegate is invoked, all the delegates in the invocation list are called synchronously. The System.MultiCastDelegate exposes the Combine method to add delegates to the invocation list.

When you define a delegate in C#, a new class with the name of the delegate inheriting from System.MultiCastDelegate is created by the C# compiler.

public delegate ICollection MyDelegate (IList param);

The above statement causes a a new class to be added in your assembly by the C# compiler.

public class MyDelegate : System.MultiCastDelegate

You can view this class by opening your assembly using the Ildasm utility.

When you assign a method to a delegate, an instance of the compiler generated class is created with the Target and Method as parameter to this class. In case the method is static, null is passed to the argument, Target.

As compiler generated classes are inheriting from System.MultiCastDelegate, thus delegate could hold the addresses of more than one method. C# exposes a simple method to add a method in the invocation list of underlying multiCast delegate object.

MyDelegate del;
del += DoSomething;
del += DoAnotherThing;

Internally, the C# compiler is calls the Combine method of the compiler-generated delegate class to add the methods to the invocation list.

When you invoke a delegate, C# internally calls the DynamicInvoke method of the compiler generated class. The compiler generated class also exposes methods to invoke the target methods asynchronously.

A delegate is known as an Open Instance Delegate if the Target is also provided at the time of invocation.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here