Internals of Delegate chaining
By Aarthi Saravanakumar

  Download C# Source Code

Introduction to Delegate Chaining

Anyone who has written a simple .aspx page or a Windows form that has any kind of user interaction has already worked with delegates. You may not have realised it, but you have also used delegate chaining. Surprised? Ever seen the += in C# or AddHandler in VB.NET used with events? That is VS .NET generating delegate chains for you!  This article aims at covering the internals of delegate chaining in .NET 1.1. I hope at the end of it, you will be able to write a += for delegates in c#  or AddHandler in VB.NET with a lot more understanding.

An inside look at Delegates

Before we look more into delegate chaining, let us look at a class that has a delegate. Nothing very fancy, a simple class that has a delegate named InterestingSubs and a method named TriggerEvent, that checks if the delegate has any registered methods(not null) and if so calls those methods with a string argument of "Something Very Interesting'.

 
public class InterestingInfo 
{ 
  public delegate void InterestingSubs(string info); 
  public static void TriggerEvent(InterestingSubs subs) 
  {
    if(subs !=null) 
    { 
      subs("Something very interesting"); 
    } 
  } 
}				


Let us now compile this exe and use ILDASM to look at the result..and what do we see? We see a class named InterestingSubs..but didn't we declare InterstingSubs as a delegate? Yes, we did. But when the C# compiler comes across the delegate keyword, it generates a class of the same name with a method named Invoke with the same signature of the delegate.So it does not come as much of a surprise that our subs("Something very interesting");  gets translated to DelegatesSample.InterestingInfo/InterestingSubs::Invoke(string), a method call to the auto-generated "Invoke". Also notice one other thing. The class InterestingInfo derives from System.MulticastDelegate. Why is this significant? Because it is MulticastDelegate has all the hidden magic to make delegate chains possible!

Delegate and MulticastDelegate

There are two flavors of delegates, at least there were when .NET framework started out. (Many predicted that this was an oversight and that in version 2.0, we would have just one version namely MulticastDelegate. But by the look of the betas of 2.0, looks like Microsoft carried over System.Delegate .) There were one set of delegates that were supposed to be used when return values were expected from the delegate call, and when multiple methods were not invoked as part of the delegate invocation.This started out as System.Delegate. Then came about System.MulticastDelegate. This class inherits from System.Delegate and was meant for use when no return values were expected and when multiple delegate calls were to be chained together. But then System.Delegate well on the wayside and all delegate declarations started inheriting from System.MulticastDelegate.So now that we have talked about these two classes, let us see what the structure of these two classes look like. I have omitted some fields for clarity's sake

 
public abstract class Delegate:ISerialzable,ICloneable 
{ 
  public static Delegate Combine(Delegate[] delegates); 
  public static Delegate Combine(Delegate a,Delegate b); 
  public static Delegate Remove(Delegate source, Delegate value); 
  public static Delegate RemoveAll(Delegate source, Delegate value);
  public override bool Equals(object obj);
  private IntPtr _methodPtr;
  private object _target;
}

Here _methodPtr is the pointer to the method that needs to be called by the delegate and the _target is the object that contains the method. This is null, if the delegate maps to a static method, and contains a reference to the object for an instance method.

The interesting methods that you need make a note of are the Combine overrides. Just remember that in the override that takes two delegates, "a" is the chain to which "b" should be added to.We will look deeper into this in the delegate chaining section. Now that we have talked of System.Delegate, what does the Multicast delegate comprise of? A lot of fields but most importantly a field named "_prev" because of which delegate chaining is possible.

Delegate Chaining

Before we look into how delegate chains are implemented, let us look at what a delegate chain's C# code looks like

class Subscriber
{

   public static void IgnoreEvent(string info)
   {
     //Ignore the event
     Console.WriteLine("SOMETHING IMPORTANT IGNORED " + info);
   }
   public void PublishEventOnWeb (string info)
   {
     //Code to publish event on web
     Console.WriteLine("SOMETHING IMPORTANT ON WEB " + info);
   }
   public void FaxEvent (string info)
   {
     //Code to publish event on web
     Console.WriteLine("SOMETHING IMPORTANT FAXED " + info);
   }


}

class TestDelegates
{
					
   static void Main(string[] args)
   {
     Subscriber sub = new Subscriber();
     InterestingInfo.InterestingSubs a = new DelegatesSample.InterestingInfo.InterestingSubs(Subscriber.IgnoreEvent);
     InterestingInfo.InterestingSubs b =new DelegatesSample.InterestingInfo.InterestingSubs(sub.PublishEventOnWeb);
     InterestingInfo.InterestingSubs c =new DelegatesSample.InterestingInfo.InterestingSubs(sub.FaxEvent);
     InterestingInfo.InterestingSubs subs =a;
     subs+=b;
     subs+=c;
     InterestingInfo.TriggerEvent(subs);
     Console.ReadLine();
   }
}
									
					

A fairly straight-forward Subscriber class that has three methods (one static and two instance) that match the signature of the delegate InterestingSubs. The TestDelegates class just creates an instance of Subscribe and attaches the subscriber's methods to the delegate and calls TriggerEvent. The point of interest here is subs+=new InterestingInfo.InterestingSubs(sub.PublishEventOnWeb); Let us compile this executable and look at the Main method using Lutz's reflector

					
private static void Main(string[] args)
{
  Subscriber subscriber1 = new Subscriber();
  InterestingInfo.InterestingSubs subs1 = new InterestingInfo.InterestingSubs(Subscriber.IgnoreEvent);
  InterestingInfo.InterestingSubs subs2 = new InterestingInfo.InterestingSubs(subscriber1.PublishEventOnWeb);
  InterestingInfo.InterestingSubs subs3 = new InterestingInfo.InterestingSubs(subscriber1.FaxEvent);
  InterestingInfo.InterestingSubs subs4 = subs1;
  subs4 = (InterestingInfo.InterestingSubs) Delegate.Combine(subs4, subs2);
  subs4 = (InterestingInfo.InterestingSubs) Delegate.Combine(subs4, subs3);
  InterestingInfo.TriggerEvent(subs4);
  Console.ReadLine();
}
 
				
See how the line subs+=new InterestingInfo.InterestingSubs(sub.PublishEventOnWeb); in our code got translated to a call to Delegate.Combine(subs1, new InterestingInfo.InterestingSubs(subscriber1.PublishEventOnWeb)); So looks like Delegate.Combine is doing all the hard work of implementing the delegate chain, doesn't it? So how does this delegate chain look like?
Delegate.Combine

The twist lies in the fact that Delegate class if you remember was not really designed for Multicasting delegates. It was designed for single delegates, so why are we calling a static method in Delegate class to do our chaining? The Delegate.Combine method does two things:one: checks if the types of the Delegates being passed in match, and if not throws an exception. The second thing that it does is calls a protected virtual method named CombineImpl (that is overriden in MulticastDelegate). Now remember that all our delegates derive from System.MulticastDelegate , so because of runtime polymorphism, the CombineImpl in MulticastDelegate gets called.

Also if you remember(or scroll up) you will see that Delegate.Combine takes two "System.Delegate" s as parameter ( Not "System.MulticastDelegate"). Because of this, when CombineImpl in MulticastDelegate is called, the method parameter passed to it is also of type Delegate.Now, we know that System.Delegate class has no functionality that will help us in chaining. But wait, MulticastDelegate on the other hand has the pointer _prev. So the first step is to clone the delegate b and cast this to a MulticastDelegate type. Now, we can access its _prev field. A mention about the _prev field. This contains the reference to the next delegate in the chain. So the first delegate added has _prev as null. When the next delegate gets added, it is cloned internally into a MulticastDelegate and its _prev pointer is fixed up to point to the first delegate added and so on. The following diagram better explains this. The small yellow boxes are (arbitrary) memory addresses where the objects are created. The green ones denote the _prev pointer.

The pointer 4010 is what gets returned as the return parameter of Delegate.Combine.As you would have realized, a delegate chain is internally implemented as a linked list with the the first delegate added to be the one with the _prev pointer as null and the last delegate added being at the head of the list.So if a,b and c are delgates added to a chain as per the code shown above , the chain looks like the following figure.

Just a mention of the other Delegate.Combine method that takes an array of delegates. This overload, internally calls the Delegate.Combine overload that takes two delegates (which we just examined )for all the elements of the array. So the previous example can be re-written as below and the delegates will be called in the order a,b.c and would be structured the same as the diagram above.
Delegate[] dels= new Delegate[3];
           dels[0]=a;
           dels[1]=b;
           dels[2]=c;
 InterestingInfo.InterestingSubs subs1=(InterestingInfo.InterestingSubs)Delegate.Combine(dels);
 InterestingInfo.TriggerEvent(subs1);
Delegate.Remove
This article would not be complete without a small discussion on Delegate.Remove. First, let us talk a little bit about the Object.Equals() method that System.Delegate overrides. This implementation of Equals considers two delegates as equal if they have the same _target and _methodPtr. Remember that _target points to the object that contains the method to be called and is null for static methods. _methodPtr is the pointer to the method in the object. So the code below, will display "a1 equals b1: true"
 InterestingInfo.InterestingSubs a1 =new DelegatesSample.InterestingInfo.InterestingSubs(Subscriber.IgnoreEvent);
 InterestingInfo.InterestingSubs b1 =new DelegatesSample.InterestingInfo.InterestingSubs(Subscriber.IgnoreEvent);
 Console.WriteLine("a1 equals b1 : " + Delegate.Equals(a1,b1));
 Console.ReadLine();
A warning: I am oversimplifying Delegate.Remove here. When you call Delegate.Remove(a,b) or use -= ,what happens is very similar to the chain of events that occur for Delegate.Combine. The _prev pointers are re-arranged so that delegate "b" is removed from the calling tree. The reason we covered Equals is that, Delegate.Remove uses this Equals implementation when it tries to remove a delegate. For example: the below two pieces of code produce the same results.
	
				
//Using Same Delegate Instance
  InterestingInfo.InterestingSubs a =new DelegatesSample.InterestingInfo.InterestingSubs(Subscriber.IgnoreEvent);
  InterestingInfo.InterestingSubs b =new DelegatesSample.InterestingInfo.InterestingSubs(sub.PublishEventOnWeb);
  InterestingInfo.InterestingSubs c =new DelegatesSample.InterestingInfo.InterestingSubs(sub.FaxEvent);
  InterestingInfo.InterestingSubs subs =a;
  subs+=b;
  subs+=c;
  InterestingInfo.TriggerEvent(subs);
  Console.WriteLine("*************REMOVING ONE DELEGATE*****************");
  subs-=b;
  InterestingInfo.TriggerEvent(subs);
						
			
  //Demonstrate Remove with a new delegate instance
  Console.WriteLine("*************REMOVING DELEGATE USING A DIFFERENT DELEGATE INSTANCE*****************");
  InterestingInfo.InterestingSubs a4 =new DelegatesSample.InterestingInfo.InterestingSubs(Subscriber.IgnoreEvent);
  InterestingInfo.InterestingSubs b4 =new DelegatesSample.InterestingInfo.InterestingSubs(sub.PublishEventOnWeb);
  InterestingInfo.InterestingSubs c4 =new DelegatesSample.InterestingInfo.InterestingSubs(sub.FaxEvent);
  InterestingInfo.InterestingSubs subs6=a4;
  subs6+=b4;
  subs6+=c4;
  Console.WriteLine("Before removing a4");
  InterestingInfo.TriggerEvent(subs6);
  InterestingInfo.InterestingSubs b5 =new DelegatesSample.InterestingInfo.InterestingSubs(sub.PublishEventOnWeb);
  subs6-=b5;
  Console.WriteLine("After removing a4");
  InterestingInfo.TriggerEvent(subs6);
			
Summary
In this article, we have taken a brief look at Delegates and concentrated on Delegate chaining in .NET 1.1 framework. Some things have changed with .NET 2.0 so far as delegates are concerned. So we will cover those in a future article. If this article has whetted your appetite for delegates, I would suggest reading Jeff Ritcher's articles on the same and using Lutz Roeder's Reflector and exploring these classes on your own.
Article Discussion: