Getting started with delegates–part II
As we’ve seen in the previous post, delegates are really easy to use: you create a new delegate definition and then instantiate it through the new operator, passing it a compatible method reference. However, there’s a lot more going on behind the scenes and that’s what we’ll start seeing in this post. The first thing we need to understand is what happens when the compiler sees a delegate definition like the one show in the next snippet:
In these cases, the compiler will automatically transform the previous delegate expression into a new class which inherits from MulticastDelegate and looks like this:
Notice that you can’t really reuse the MulticastDelegate type in C# because the compiler won’t allow you to use those classes directly from your C# code.
As you can see, a delegate definition expression ends up generating a new class with a new constructor and three methods: Invoke, BeginInvoke and EndInvoke. Lets start with the constructor. The two parameters it receives are used for initializing, respectively, the MulticastDelegate’s target and method private fields. When we use a delegate to call a static method, the target field is initialized to null. On the other hand, when we use it to call an instance method, the target field is initialized with a reference to the object whose instance member is being invoked. In both cases, the private method field references the method that should be called. Btw, you can access these values through the inherited Target and Method properties:
If you take a look at the MulticastDelegate’s code, you’ll quickly notice that it doesn’t define these properties. In fact, these properties are exposed by the Delegate type, which is used as base by the MulticastDelegate type. To be honest, I really don’t have any good explanation for having two delegate classes since our delegates definitions end up always creating new classes which expand the MulticastDelegate type (and no, you can’t either create a new type which uses Delegate as a base type in C#)…Going back to the snippet, I believe I should mention that the Method property returns a reference to a MethodInfo instance (this type provides us access to a method metadata – this means you also use it, for instance, to get the name of the method that is being invoked).
Now that we understand what happens when we instantiate a new delegate, it’s type to see how invocation works. So, what happens when the compiler finds something like this:
If we take a peek at the generated IL, we’ll find the following code:
As you can see, our aux method called is translated into an instance Invoke method call. Btw, nothing prevents us from doing that call from our C# text:
And yes, it does compile without any errors (thank you for asking!). Internally, Invoke will use the information returned by the Target and Method properties to invoke the method. As you’ve probably inferred, BeginInvoke and EndInvoke can be used to invoke the methods asynchronously (and have been used for several years as a way to run code in parallel). We’ll probably return to the async methods in a future post…
But there’s more…the astute reader has probably noticed the “Multicast” string in the MulticastDelegate name’s type. It’s obvious that using indirection to invoke a method is really powerful, but what about using a delegate to chain the execution of several methods? We’ll see how we can do this in the next post. Stay tuned for more.