Quantcast
Channel: Alxandr.me » c#
Viewing all articles
Browse latest Browse all 11

Dynamic Dispatch – How dynamic work in C#

$
0
0

This post was inspired by this Stack Overflow question.

Dynamic Dispatch

Let’s start at the beginning. What is a dynamic dispatch?

In computer sciencedynamic dispatch is the process of selecting which implementation of a polymorphic operation (method or function) to call at runtime. Dynamic dispatch contrasts with static dispatch in which the implementation of a polymorphic operation is selected at compile-time. The purpose of dynamic dispatch is to support cases where the appropriate implementation of a polymorphic operation can’t be determined at compile time because it depends on the runtime type of one or more actual parameters to the operation. – Wikipedia

In C# there are several ways of making calls that would qualify as being a dynamic dispatch according to the Wikipedia documentation (or my understanding of it), however today we’re going to look at a specific one. We’re going to look at operations involving C#’s “dynamic” keyword/type. However, before we get to that, let’s start of simple:

string obj = "test";
int length = obj.Length;

Now, before people start screaming about there being nothing dynamic about this code, I already know that. This is the basic, statically-compiled code that we’re going to work with to explorer how dynamic actually works. But to understand how dynamic works, it’s imperative to understand how non-dynamic (ie. static) works. When the following code get’s compiled (inside a method of cause, otherwise it wouldn’t be valid), it creates a string-variable named obj and puts “test” into said variable. Then it creates a int-variable named length, and it puts the result of calling the getter of the Length-property on string on obj. This can be done efficiently because the compiler knows (at compile time) that obj is indeed a string, and it knows how to resolve Length on a string. In other words, what happens (pseudo-code) is more or less this:

string obj;
int length;
obj = (string)"test";
length = (int)string::get_Length((string)obj);

Note; the explicit casts are just to illustrate the types of things, not to be interpreted as actual casts. There’s nothing really interesting about this code, so let’s see what happens when we throw a “dynamic” in there.

Dynamic Call Sites

If we “spice up” the code a little by changing both variables to be of type “dynamic”, like this:

dynamic obj = "test";
dynamic length = obj.Length;

and then decompile the result, the code looks quite different from what I stated above. What you end up with is something like this:

object obj = "test";
CallSite<Func<CallSite, Object, Object>> callSite =
    CallSite<Func<CallSite, Object, Object>>
        .Create(Binder.GetMember(CSharpBinderFlags.None, "Length", typeof(Program),
            new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
object length = callSite.Target(callSite, obj);

Note that this code has been slightly simplified.
Now, there is a lot of information about our dynamic call generated from the simple line of obj.Length. The first thing to notice, is that a “Binder” is created (by the “Binder.GetMember” method). GetMember indicates that we want a GetMemberBinder, which is used to access members of types, and as we all knowns, a property is a kind of member. The binder needs information about what property we want to access (“Length” in this case), and what arguments we want to supply to the operation (only one argument in our case). It also takes some binding-flags (which is just set to None), and the encapsulating type (which in my case is Program). Now, you might be wondering, what’s that one argument? The property takes no arguments, so having one argument looks kind of strange. Well, if you look at the pseudo-code in the previous section, you’ll see that what happens when you call a property (or any member-method for that sake), the object on which you call the property/method is passed in as the first (zeroth) parameter behind the scenes. This than get’s access through the this-keyword. So the parameter we need for our dynamic property-method is the object on which we want to find the property (in our case, the obj object).

After a binder has been constructed, it’s fed into a CallSite<Func>. Now this callsite looks scary, but it’s actually quite simple. The callsite provides some logic to cache a bound operation (bound by the binder, we’ll get back to that later), so that if you give it the same type of object multiple times in succession, it’ll just use the same function it used last time, and it’ll be almost as fast as if you ran without using dynamic at all! With exception of the first run ofcause, where it has to do the actual binding.

Dynamic Binders

If the callsite itself has not cached a method (target) on how to perform the dynamic action (or if the cached action does not fit with the given type), it asks the binder to do so for it. The binder also maintains (a larger) cache of operations it has already bound, and if any these are a match it uses it, if else it performs a new binding. This is where things start to get interesting. The callsite calls “Bind” on the binder (which is overridden and sealed in DynamicMetaObjectBinder). The Bind-method creates DynamicMetaObjects for each of the given arguments (which in our case is only 1), it then executes another overload of Bind which takes DynamicMetaObjects, which in GetMemberBinder get’s rerouted to a call to the first DynamicMetaObject’s BindGetMember (we’ll look at this in a bit). The default DynamicMetaObject simply calls into the binder’s FallbackGetMember-method. At a glance, this is more or less what happens:

1. we call callsite.Target(callsite, obj);
2. callsite realizes it has no cache (or an invalid one), and calls update
3. DynamicMetaObjectBinder::Bind(object[] args, Expression[] expressions) is invoked
4. DynamicMetaObject is created for each arg, and GetMemberBinder::Bind(DynamicMetaObject target, DynamicMetaObject[] args) is invoked
5. GetMemberBinder invokes target.BindGetMember(this /*binder*/);
6. DynamicMetaObject (target) invokes binder.FallbackGetMember(this /*target*/);
7. GetMemberBinder invokes this.FallbackGetMember(target, null /*errorSuggestion*/)
8. CSharpGetMemberBinder uses some black magic (reflection) to figure out that "Length" corresponds to string.Length property.
9. A function (looking like (string str) => str.Length;) is returned to the callsite (through the chain, and some caching).

The important thing to notice here is the fact that on line 5, BindGetMember is invoked on the actual DynamicMetaObject representing the target (in our case the string named obj). This means that objects that implement IDynamicMetaDataProvider has the ability to override the behavior that happens when they are involved in a dynamic dispatch. However, since string does not implement IDynamicMetaDataProvider the default DynamicMetaObject is used, which simply calls back into the binder to let it decide what happens. This means that if you switch out the binder, you’d get different behavior for all objects that do not implement IDynamicMetaObjectProvider (and for those that chose not to change the default behavior). This is the reason why C#’s compiler generates a CSharpGetMemberBinder, while VB would probably generate a VBGetMemberBinder (or something like that), to make sure the language’s semantics (like the fact that VB is case independent) are maintained in dynamic operations.

DynamicObject

The .NET framework provides us with a simple utility-class if one wants to create a dynamic object (instead of going through the whole IDynamicMetaObject and custom DynamicMetaObject approach). This is called DynamicObject. So let’s go ahead and implement our own (very simple, mind you) dynamic type.

class CustomString : DynamicObject
{
    private readonly string _value;

    public CustomString(string <span class="hiddenGrammarError" pre="">value)
    {
        _value</span> = value;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (binder.Name.Equals("Length", binder.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
        {
            result = _value.Length;
            return true;
        }

        return base.TryGetMember(binder, out result);
    }
}

DynamicObject implements IDynamicMetaObjectProvider, and it returns a custom DynamicMetaObject which overrides all the Bind*-methods. What it does however, is quite simple, yet also quite brilliant. The BindMember method of the hidden MetaDynamic inner class on DynamicMetaObject simply invokes TryGetMember on the DynamicMetaObject. Now, I say simply, but there’s a good bit of logic to handle cases where you haven’t overridden TryGetMember, and if you return false, or an exception is thrown, etc. However, the general gist is that TryGetMember is invoked. This means that if we do this:

dynamic obj = new CustomString("test");
dynamic length = obj.Length;

our override of TryGetMember will be invoked. The binder will also have it’s Name set to “Length”, so as you might expect we would get the int 4 back, just as with the normal string. This actually makes it possible to make CustomString work like string in almost all cases if we were to override all the required methods in DynamicObject, however, I’ll leave that as an exercise to the reader.

I hope by now you have gotten a better understanding of how dynamic dispatch works in C#. I will not go into implementing a custom DynamicMetaObject, because that’s worthy a whole blog-post for itself. Maybe another time. If there are any questions, please leave them in the comments.

Until next time.



Viewing all articles
Browse latest Browse all 11

Trending Articles