C#: getting delegates

Well, today it”s all about delegates! If you”re looking for an intro on the subject, then there”s already several articles out there (this one by Chris Sells is good way to start). This pos tries to explain what happens when you compile a delegate from C# code. Ok, so lets start with a quick delegate definition that I”ll be using in this post to explain how things work:

public void delegate SayHi(string name); 

If you use reflector and look at IL, you”ll see something similar to this:

.class public auto ansi sealed SayHi
    extends [mscorlib]System.MulticastDelegate
{
    .method public hidebysig specialname rtspecialname instance void .ctor(object ”object”, native int ”method”) runtime managed
    {
    }

    .method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(string name, class [mscorlib]System.AsyncCallback callback, object ”object”) runtime managed
    {
    }

    .method public hidebysig newslot virtual instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
    {
    }

    .method public hidebysig newslot virtual instance void Invoke(string name) runtime managed
    {
    }

}

This is quitesimilar to what happens when you declare an enum. In other words, when the compiler sees the delegate keyword, it generates a new class which inherits from MulticastDelegate (and again, don”t try this in C# because you”ll get a compiler error). Besides the constructor, the compiler also defines three methods:BeginInvoke,EndInvoke and Invoke (the first two are used when you want to perform an async call; the second is the one to call when you”re into synchronous calls).

So, our delegate declaration results in a new class that inherits from MulticastDelegate which, in turn, ends up extending the Delegate class. If we”re really interested in getting under the hood, we should start by seeing what these classes do. The Delegate base class introduces two important fields: _target and _methodPtr. The first field points to the instance of the class over which the method is being called (it”s null when you”re passing a static method). As you expect, _methodPtr identifies the method that will be called when you invoke the Invoke or the BeginInvoke method exposed by the SayHy class (oops, or should I say delegate?).

do notice that the constructor injected by the compiler receives a reference to an object and method that are used to initialize these fields. this might seem odd since you set up a delegate with code similar to this one:

namespace Livro
{
    public delegate void SayHi(string name);
    class Start
    {
        public void SayingHi(string name)
        {
            Console.WriteLine(name);
        }
        public static void Main()
        {
            Start t = new Start();
            SayHi test = t.SayingHi;
        }
    }
}

Well, if we really really wanted to follow the “rules”, we should really have written SayHi test = new SayHi( t.SayingHi ). However, it”s really simpler to just use the previous syntax since the C# compiler can infer the correct code from it.

If you look at the IL generate by this code, you”ll see that the C# compiler translates the previous reference in a call to the constructor, generating values for the expected parameters:

.method public hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] class Livro.Start t,
        [1] class Livro.SayHi test)
    L_0000: nop
    L_0001: newobj instance void Livro.Start::.ctor()
    L_0006: stloc.0
    L_0007: ldloc.0
    L_0008: ldftn instance void Livro.Start::SayingHi(string)
    L_000e: newobj instance void Livro.SayHi::.ctor(object, native int)
    L_0013: stloc.1
    L_0014: ret
}

Going back to the Delegate class, there”s still time to talk about the Target and Method read-only properties. If you add the following code before the end of the Main method presented on the previous C# code snippet:

Console.WriteLine(test.Target);
Console.WriteLine(test.Method.Name);

You”ll see that you”llg et the following output:

Livro.Start
SayingHi

These properties let you get the reference passed to the delegate (Target) and the MethodInfo object that  describes the method that was passed to the delegate (Method). As you can see, there are many potential uses for these properties (though I”ve never seen anyone use them from a “standard” C# program). Before we tal about the “all important” MulticastDelegate class, lets see what happens when you call the delegate by using what i call the “method call syntax” (using the text variable introduce on the previous example):

test(“Luis”);

L_0015: ldstr “Luis”
L_001a: callvirt instance void Livro.SayHi::Invoke(string)

The important thing to notice is that you”ll end up calling the Invoke method of the class generated by the compiler to represent the delegate.

MulticastDelegate is responsible for introducing the _invocationList field, which is used for keeping a list of methods that should be called when you invoke the delegate. In other words, the class is responsible for letting you call several methods through a single “delegate method invocation”. For instance, if you modify the previous code so that it looks like this:

Start t = new Start();
SayHi test = null;
test += t.SayingHi;
test += t.SayingHi;
test(“Luis”);

and run it, you”ll see Luis twice.

L_000b: ldftn instance void Livro.Start::SayingHi(string)
L_0011: newobj instance void Livro.SayHi::.ctor(object, native int)
L_0016: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
L_001b: castclass Livro.SayHi

btw, in this case Reflector is “cool” (ok, sorry. i meant really cool!) and does translate that code in C# (which i”m putting here so that it”s easier to understand):

Start t = new Start();
SayHi test = null;
test = (SayHi) Delegate.Combine(test, new SayHi(t.SayingHi));
test = (SayHi) Delegate.Combine(test, new SayHi(t.SayingHi));
test(“Luis”);

The Combine method is static and receives two delegates: an existing delegate (or null) and the new one that we want to add to the internal list. Btw, do not
ice that this method is really defined by the Delegate class. Internally, it ends up calling the protected sea
led CombineImpl instance method, that ends up being overriden by the MulticastDelegate class (which is where the most interesting things happen! ).

The behavior of this method is interesting, to say the least. when one of the delegates is null (this happens, for instance, on the first call of the Delegate.Combine method shown on the previous snippet – notice that test is set up to null when it”s declared), this method isn”t invoked since the Delegate”s Combine method simply returns the other non-null reference that it receives. When that isn”t the case, then the method has to build a new MulticastDelegate which is returned back to the caller and has its _invocationList correctly filled with the values maintaned in both delegates. basically, this means that it has to combine a possible non-null _invocalistList of a delegate with an empty one from the other.

Internally, the method garantees that the new MulticastDelegate”s _invocationList respects the order of declaration. This means that if we have something like this:

Start t = new Start();
SayHi test = null;
test += t.SayingHi;
test += t.SayingHi2; //suppose the class has a SayingHi2 method
test(“Luis”);

Then calling test will call the SayingHi method before the SayingHi2 method. So, now we can assume what happens when you call  the Invoke method of the class you create when you define a new delegate: it”ll simply go through the list and call each method maintained by the existing delegates. btw, do not loose any time looking for the Invoke implementation since these methods are implemented by the CLR itself (notice that they”re annotated with the runtime managed qualifier). If you”re looking into more info about delegates and the way these mehods are implemented, then take a look at this Brad Adam”s post.

As you might expect by now, you can also remove a delegate from a MulticastDelegate”s internal _invocationList by calling the Remove method. In C# you”ll just call the -= operator:

test -= t.SayingHi;

I think that you could still write a lot more about delegates. However, i do believe that this post has presented enough insight on what happens when you use delegates from C# code.  And that”s a  wrap!

Advertisements

~ by Luis Abreu on September 10, 2007.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: