Soon, my ownership area will extend to include delegates. Since I'm fairly excited about this, I thought I'd celebrate by writing a little something about them. So, what are delegates? A casual observer might be tempted to write off delegates as a sort of managed function pointer. While this comparison is certainly accurate, there's much more to explaining the power of delegates.
In general, delegates are a sort of universal method dispatch mechanism. Initially, the scenario they supported was callbacks. Delegates are one of the things that distinguish the CLR from other VMs like Java. Java requires the use of interfaces to implement callbacks. (I'm only calling that out as a distinction, not saying the Java way is bad. although personally I like what delegates bring to the table) So, delegates let you wrap up a method as an object to pass around, with the expectation that it will be called from another context.
Its sort of hard to talk about delegates because the discussion is often framed by the language that's exposing them. Currently, no managed language exposes them in the way that they are represented in IL. In C# and VB, you declare a delegate by simply defining a method signature. From an IL perspective, the compilers generate a class that inherits from MulticastDelegate (another story I'll get to later), with an Invoke method that matches your signature, and some various constructors to support different things. (You also usually get the corresponding asynchronous calling pattern support methods, but I don't want to get into that) Some other delegate-related compiler trickery is involved in declaring events, which I'll cover later.
Under the covers, a delegate [conceptually] contains 2 things:
- A target object
- A target method
Now, generally speaking, the target method is the method to be run, and the target object is the object on which the target method will be run, but there are cases where this line is blurred a bit. For instance, when a delegate is pointing to a static method, the target object is conceptually null (internally it's not, but that's an implementation detail). I'll get into the other cases later.
So now you're saying, "Yup, that's a delegate. Big deal. What's so cool about that?" What's cool about that, my friend is that delegates are the things that power virtually all of the coolest new language features that came out in v2.0 and will be coming out, including all the dynamic language goodness like IronPython. It's the dynamic stuff that is really exciting, so let's talk about how delegates enable dynamic languages on top of a statically typed system.
(To be fair, Jim Hugunin did his initial Iron Python work before these features were available, but they now play a big role) One of the pieces of work done in v2.0 was called delegate relaxation. Previously, the target method had to match the delegate signature exactly. Now, as you might expect intuitively, the signature can be relaxed such that the target method can have "more general" parameters and return something "more specific" than the delegate's signature. This is typically defined in terms of covariance and contravariance, terms that even people who understand them get confused. Here's the way I usually remember it: If I could wrap the target method with a method having the delegate's signature without casting, it will work. Anyway, this feature makes delegates quite a bit more flexible.
Before I go into the other features, lets talk a little about implementation. In normal method calling in the CLR, the first parameter becomes the "this" object. (Which is why you see ldarg.0 in IL to put it on the stack.) So, conceptually, the target object represents the first argument for the method. (There is an implementation detail that allows static methods to be called using the same convention, which is a really elegant solution) So, by extending this idea of the target object simply being the first argument, we get a couple of interesting variants.
The first is what is called "closed" static delegates. This allows you to specify the first argument of a static method at delegate creation rather than at the callsite. Notice this maps quite nicely to the dynamic language concept of adding a method to an existing instance of an object. The language runtime just needs to be able to track these extra methods as part of its method dispatch logic.
The second feature is "open" instance delegates. This allows you to create a delegate that points to an instance method, but doesn't define the target object. Instead, the delegate signature can have an extra first argument that will specify the target object at the callsite. When used with LCG (DynamicMethod), this can be used to implement things like adding a method to an existing type. Again, the language runtime merely needs to add the logic to method dispatch.
These 2 features are intriguing to me because they are not directly exposed from VB or C#. I believe VB9 exposes these, but they are not accessible in an early bound way in C#. You can, however, create them via Delegate.CreateDelegate() using reflection, or use Reflection.Emit to generate the corresponding IL.
Hopefully, I'll have some time in the future to do some samples of these as well as discuss more about how these improve the dynamic language support in the CLR.