After my last few CLR posts, I've had a couple of private inquiries regarding the usefulness of closed static delegates. To bring everyone up to speed, a delegate pointing to an instance method needs a "target" instance to operate on (we'll get to open instance delegates later). A static method, needs no such target, so we can leverage the "space" used for the instance case to carry around another object of interest. We call a delegate with a provided value for this space "closed over the first argument".
For example, let's say we have a static method that does some operation on two numbers. For simplicity, let's just say it adds them. Our silly class and method might look like this:
public static class NumberFunctions {
public static double Add(double first, double second) {
return first + second;
}
}
Normally, a delegate for this method would look like:
public delegate double BinaryOperation(double first, double second);
But, we're going to create a closed static delegate, which means we're going to "burn" the first argument into the delegate itself, so it's not needed in the delegate signature. Instead, we'll use the following delegate signature (I didn't spend much time thinking up these names, I hope they make sense:
public delegate double ClosedCall(double other);
So, how do we create the delegate? Normally, since C# (pre-Orcas) doesn't have syntax for creating closed static delegates, you are forced to use one of the Delegate.CreateDelegate overloads:
ClosedCall addToOne = (ClosedCall)Delegate.CreateDelegate(
typeof(ClosedCall),
1.0,
typeof(NumberFunctions).GetMethod("Add", BindingFlags.Public | BindingFlags.Static));
Of course, we just spent 2 entries looking at a helper that can do this for us (I'm not claiming this is better, I just want you to be able to see what's happening):
ClosedCall addToOne = DelegateBinder.Bind<ClosedCall>(1.0,
typeof(NumberFunctions).GetMethod("Add", BindingFlags.Public | BindingFlags.Static));
Now, a call to addToOne(someNumber) will yield the result of adding the supplied argument to one. This is a contrived example, but you could imagine taking a method (perhaps generated on the fly via LCG), and "attaching" an instance to it via the first argument. Then, being able to call it many times with different subsequent arguments, or passing it to another component that would provide the rest of the arguments. In this way, you get the benefits of not having to keep track of an instance, without having to own the API for the instance. Additionally, you could "chain" delegates together so that many arguments are captured in a stack of delegate calls, allowing closure-type semantics at the cost of some stack space (although since C# has closure support, you'd never really need to do that).
What's really cool is that with C# 3.0's Extension Methods feature, we now have language support for creating early-bound closed-static delegates. If you bind a delegate to an extension method (using the regular syntax for an instance method), you will get the exact IL for creating an early-bound closed static method without our fancy helper class. Let's see how that would look. Let's use a different example to keep us on our toes. Here's a helper function that creates email addresses:
public static class StringExtensions {
public static string MakeEmailAddressWithAlias(this string domain, string alias) {
return string.Format("{0}@{1}", alias, domain);
}
}
Notice the "this" in front of the first parameter, this tells the compiler that the method should be considered when resolving method calls for string. We'll use one of the delegate types provided in Orcas. Now, here's how the bind looks:
string fooDotCom = "foo.com";
Func<string, string> makeFooDotComAddress = fooDotCom.MakeEmailAddressWithAlias;
string email = makeFooDotComAddress("bar");
So, the result is that email will be bar@foo.com.
Hopefully, through these contrived examples, you can see the scenarios that closed static methods provide, as well as learn how you can create one the easy way with extension methods in Orcas.