So, these new (in 2005...) characteristics are Open Instance Delegates and Closed Static Delegates.
Maybe both characteristics could be considered part of the Delegate Relaxation concept that sometimes we can read in some pages, though in principle this Relaxation thing is used as a synonymous to the Delegate Variance characteristics also added in C# 2.0 (I plan to write something about variance in the near future).
As we all know, delegates are not much more than a reference to a method (well, a MethodInfo object pointed by the Method property), and a reference to the "this" object for that method (the Target property). If the delegate points to an Instance Method, the Target property points to the instance object that we used when creating the delegate. If the delegate points to a static method this Target property is null.
When we go to Native Code, the "this" object is passed to the function (method) as just one more parameter. So, when in runtime the JIT translates the CIL code to Native Code, each call to a delegate expecting n parameters involves calling to the native code representation of the MethodInfo, pushing into the stack those n parameters plus (in first position) the object pointed by the Target property (if it's not null). So it seems it should be feasible, fun and useful to play with combinations of Delegate Type and Target values to in the end invoke a method (instance or static) with all the needed parameters expected at the low level.
For example, it could seem useful to be able to alter an existing delegate object to change the "this" (Target), without having to resort to creating a new delegate object.
It can be done with CIL or it can be done with Reflection (and I should paste some example here...)
Or, instead of using a Closed Delegate (one with the Target Property pointing to somewhere), we can use an Open Instance Delegate (usually shortened as Open Delegate) which Target is null. So, in this case, how do we pass the instance parameter?
easy, we pass it as the first parameter in the Delegate invocation. What this means is that if we have an instance method of the Person class, that expects one string and one int as parameters, we'll have to use a delegate type that expects a Person, a string and an int as parameters. OK, and how do we create this Open Delegate?
Suppose we have a Person class with a method like this:
public string DoSomethingInstance(string st, int num) {return "";}
Given that the normal, closed delegate thing works like this:
Person p1 = new Person(){Name = "Iyan"};
//the normal way, create a closed delegate
Func<string, int, string> closedDelegate = new Func<string, int, string>(p1.DoSomethingInstance);
we would like to be able to do this:
Func<Person, string, int, string> openDelegate = new Func<Person,string, int, string>(p1.DoSomethingInstance);
but this is not possible (compile time error), it seems as though the compiler can not distinguish if we're trying to create an Open Delegate there or we're just making a mistake.
so, what other way we have to create delegates? well, we have the Delegate.CreateDelegate method, that we normally use when we depend on runtime information, but that this time could also be the way to go. There's a whole bunch of overloads for this method, but if we check just the first one Delegate.CreateDelegate Method (Type, MethodInfo), after a disappointing "Creates a delegate of the specified type to represent the specified static method" if we read on we find a promising:
"The MethodInfo describing the static or instance method the delegate is to represent. Only static methods are supported in the .NET Framework version 1.0 and 1.1."
so, if we write:
MethodInfo mInf = typeof(Person).GetMethod("DoSomethingInstance");
Func<Person, string, int, string> openDelegate = (Func<Person, string, int, string>)(Delegate.CreateDelegate(typeof(Func<Person, string, int, string>), mInf));
//now let's call our Open Delegate with different instances of Person
Console.WriteLine(openDelegate(p1,"formatting: ", 3));
Console.WriteLine(openDelegate(new Person(){Name = "Xurde"},"formatting: ", 4));
Console.WriteLine(openDelegate(new Person(){Name = "Nel"},"formatting: ", 2));
and test it, it works fine!
And now, let's think the other way. Could it be possible to point to a static method with a delegate which has bound as Target the first parameter expected by the static method? (when we create a delegate to a static method the normal way Target is null).
Yes, it's possible, and is called Closed Static Delegate. It does not seem so useful to me as the Open ones, but anyway if we're going to call that method with the same first parameter but varying other parameters, it can be helpful. After all, it's not much different from the partial function application that we have in Python or in the all-powerful JavaScript.
so, if we have a:
public static string DoSomethingStatic(string st, int num){return "";}
we can do (again we have to resort to one of the Delegate.CreateDelegate overloads):
string st = "Nenyures";
MethodInfo mInf2 = typeof(Person).GetMethod("DoSomethingStatic");
Func<int, string> closedStaticDeleg = (Func<int, string>)(Delegate.CreateDelegate(typeof(Func<int, string>), st, mInf2));
and now that we've bound the string "Nenyures" to the static method wrapped in the delegate (i.e. assigned it to the Target property), we can do a call like this:
Console.WriteLine(closedStaticDeleg(5));
To sum up:
By default when we create a delegate pointing to an instance method, we're creating a closed delegate (Target property points to the instance object used when instantiating the delegate) and when we create a delegate poiting to a static method, we're creatina an open delegate (Target property is null), but all this can be altered for fun and profit...
you can check the source here.
Thank you! I was hoping to find a way to create what I now know are "Open Delegates", in order to (or rather, in hope of) speeding up some property assignments, currently being handled through reflection and PropertyInfo.SetPropertyValue. I don't know whether it'll be any faster, but you've shown me how to do it!
ReplyDeleteHi Ann!
ReplyDeleteReally great to see that this post has been of some help to someone.