Wednesday, March 28, 2007

C# Delegates Explained

This may be the first time you've read about this new .NET type. A delegate is an object that refers to a static method or an instance method. In this article I discuss what delegates are, how you can create and use them, and how the C# compiler saves us time by generating the delegate's class. We will also look at the MSIL code, talk about multicast delegates and provide callback methods through the use of delegates.
A delegate is an object that is created to refer to a static method or an instance method, and then used to call this method. To start off, you create a new delegate type in a different way than you create any other class. You use the delegate keyword as in the following statement.

public delegate int DelegateToMethod(int x, int y);

It seems unusual I know, but I will explain how it's done. Let's take a look at the very first example that explains how to use a delegate.

The First Delegate Example

Copy the following code into your VS.NET class file and run the project.

using System;

namespace Delegates
{
public delegate int DelegateToMethod(int x, int y);

public class Math
{
public static int Add(int first, int second)
{
return first + second;
}

public static int Multiply(int first, int second)
{
return first * second;
}

public static int Divide(int first, int second)
{
return first / second;
}
}

public class DelegateApp
{
public static void Main()
{
DelegateToMethod aDelegate = new DelegateToMethod(Math.Add);
DelegateToMethod mDelegate = new DelegateToMethod(Math.Multiply);
DelegateToMethod dDelegate = new DelegateToMethod(Math.Divide);
Console.WriteLine("Calling the method Math.Add() through the aDelegate object");
Console.WriteLine(aDelegate(5,5));
Console.WriteLine("Calling the method Math.Multiply() through the mDelegate object");
Console.WriteLine(mDelegate(5,5));
Console.WriteLine("Calling the method Math.Divide() through the dDelegate object");
Console.WriteLine(dDelegate(5,5));
Console.ReadLine();
}

}
}

When you run the above code you will get the following:



Let's explain what's going on in this example step-by-step. We have defined a new delegate type using the statement

public delegate int DelegateToMethod(int x, int y);

You are used to defining a new class using the class keyword, then an identifier followed by {, then implementation in the form of methods, properties and fields followed by }. The case is different with delegates. When we define a new delegate type (like DelegateToMethod) the C# compiler generates a class called DelegateToMethod that derives the System.MultipcastDelegate as follows:

public sealed class DelegateToMethod : System.MulticastDelegate
{
public DelegateToMethod(object target, int method);
public virtual void Invoke(int x, int y);
public virtual IAsyncResult BeginInvoke(int x, int y,
AsyncCallback callback, object obj);
public virtual void EndInvoke(IAsyncResult result);
}

The Constructor method of this class takes two arguments. The first is an object reference of the type that defined the instance method that the delegate refers to, and the second is an int value of the function pointer to the method that the delegate encapsulates.

The Invoke() method has the same signature as our delegate declaration. This method is used to call the delegate's encapsulated method. Note that when we defined the delegate we provided a signature for the method that can be encapsulated. In other words, the delegate can't refer to a method with a different signature than the one that it is created with. The BeginInvoke() and EndInvoke() provide asynchronous calls, which are beyond the scope of this article.

The C# compiler generates the sealed class with the four virtual methods, but it doesn't generate any implementation for those methods because they have to be implemented by the Common Language Runtime. So up to the point we have discussed, the folks at Microsoft saved us a lot of time by providing the delegate keyword which we can use to generate a class based on the System.MulticastDelegate. Let's continue our example.

The Math class contains three simple methods (Add, Multiply and Divide) that accept two int values and return an int value. Note that those are static methods. The DelegateApp class creates three DelegateToMethod objects as shown next:

DelegateToMethod aDelegate = new DelegateToMethod(Math.Add);
DelegateToMethod mDelegate = new DelegateToMethod(Math.Multiply);
DelegateToMethod dDelegate = new DelegateToMethod(Math.Divide);

Those three statements create three delegate objects. I think that the issue that would confuse someone is illustrated in the following screen shot:



As you can see, the signature of the Constructor method is not shown; instead, the signature of the method that can be encapsulated by the delegate is shown, or we can say the signature of the delegate. Any method that accepts two int values can return an int. We can look at a delegate as a type-safe function pointer which means that the parameter list and the return type are known.

We create a delegate object using the new operator and pass it the method to be encapsulated. Note that we have said that the generated class' constructor is passed two parameters, and because the passed methods are static, the first parameter will be null value (if it was an instance method it would be the object reference that defined the method instead of the null value).

We have said that in order to call the delegate's encapsulated method we need to call the delegate's Invoke() method. Actually, we can't call this method directly; instead we use the object reference and pass it the arguments as we did in the above code. Take a look again:

Console.WriteLine(aDelegate(5,5));

The statement calls the encapsulated method (which is Math.Add) and passes the parameters as arguments to the method, which returns the value 10.

Put simply, to use a delegate:

Define a new type that inherits from System.MulticastDelegate class and provide the signature of the methods that can be encapsulated by the new type.

public delegate int DelegateToMethod(int x, int y);

Create an instance or static method that has the same signature as defined by the new delegate type.

public static int Add(int first, int second)
{
return first + second;
}

Create a new delegate object using the new operator and pass the method as a parameter to it.

DelegateToMethod aDelegate = new DelegateToMethod(Math.Add);

Invoke the delegate object and pass the arguments to it which in turn calls the referenced method.

Console.WriteLine(aDelegate(5,5));

No comments: