Dynamic in C# II: Basics

Dynamic in C# II: Basics

Rate This
  • Comments 15

Last time, we began to dive into dynamic binding in C# and what happens through the pipeline. This time, we'll take a simple scenario and pick apart the details of what happens under the covers, both during compile time and runtime.

We can break down what the compiler does into three parts: type and member declarations with dynamics (ie methods that return dynamic), binding and lookup, and emitting. We'll deal now with the binding aspects of dynamic.

Dynamic binding

Dynamic binding itself can be broken into two scenarios. Lets consider the following example.

static void Main(string[] args)
{
    dynamic d = 10;
    C c = new C();

    // (1) Dynamic receivers.
    d.Foo(); // Call.
    d.PropOrField = 10; // Property.
    d[10] = 10; // Indexer.

    // (2) Statically typed receivers (or static methods)
    //     with dynamic arguments.
    c.Foo(d); // Instance method call.
    C.StaticMethod(d); // Static method call.
    c.PropOrField = d; // Property.
    c[d] = 10; // Indexer.
    d++; // Think of this as op_increment(d).
    var x = d + 10; // Think of this as op_add(d, 10).
    int x = d; // Think of this as op_implicit(d).
    int y = (int)d; // Think of this as op_explicit(d).
}

Consider the first set of examples under (1). Each of these dynamic invocations happen off of the dynamically typed expression. It is clear where the dynamicity (yes, I like that word, even though it isn't one...) comes from, and where it goes.

The second set of examples under (2) are a little more complex. The use of dynamic is indirect in each of these. Because the argument to each operation is dynamic, they flow into the containing operation and make them dynamic as well. As such, the compiler does sort of a mix of dynamic binding and static binding - it will use the static type of the receiver to determine the set of members to overload on, but will use the runtime types of the arguments to perform overload resolution.

The first set of examples are much more straight forward to understand, so we'll use this set as our foundation for exploring the feature.

Dynamic receivers

When the compiler encounters an expression typed dynamic, it knows to treat the subsequent operation as a dynamic operation. Whether its an index get, index set, method call etc, the result type of the operation will be determined at runtime, and so at compile time, the result of the operation must also be dynamic.

The compiler transforms all dynamic operations into what we'll call a dynamic call site. This consists of creating a compiler generated static field on a generated static class that stores the DLR site instance for the invocation, and initializing it as necessary.

The DLR call site is a generic object that is generic on the delegate type of the call. More on how this delegate gets generated later. The type names may not be final yet, but currently the creation of the DLR call site takes a CallSiteBinder which is an object that knows how to perform the specific binding that is required for the call site. The DLR provides a set of standard actions that can be used to take advantage of the DLR's support for interop with dynamic objects (more on that in a later post).

The call site contains a field of type T that is an instance of the delegate type that the site is instantiated with. This delegate is used to contain the DLR caching mechanism which you can learn about on Jim Hugunin's blog. It stores the results of each bind and is used to invoke the resulting operation.

Once the call site has been created, the compiler then emits the code to invoke the delegate, passing it the arguments that the user passed to the call site.

What happens at runtime?

Once the compiler has created the DLR call site, it then invokes the delegate, which causes the DLR to do its magic with interop types, and its magic with caching. Assuming that we don't have a true IDynamicObject and we don't have a cache hit, the CallSiteBinder that we seeded the DLR site with will be invoked. C# has its own derived CallSiteBinders that will know how to perform the correct binding, and will return an expression tree which will be merged into the DLR call site's target delegate for caching.

The current caching mechanism simply checks exact type matches on the arguments. For example, suppose our call looks like the following:

arg0.M(arg1, arg2, ...);

And suppose our call has arg0.Type == C, and all the arguments passed to the call are of type int. The cache check would look like the following:

if (arg0.GetType() == typeof(C) &&
    arg1.GetType() == typeof(int) &&
    arg2.GetType() == typeof(int) && 
    ...
    )
{
    Merge CallSiteBinder's bind result here.
}
... // More cache checks
else
{
    Call the CallSiteBinder to bind, and update cache.
}
C# CallSiteBinder creation

The last thing we need to paint a full picture of dynamic binding is to understand what the C# CallSiteBinder implementation does.

In our example, we have 3 different types of dynamic operations. We have a call, a property access, and an indexer. Each of these operations have their own unique pieces to them, but still share much of the common functionality. As such, they all are initialized with a common C# runtime binder, and are used by the runtime binder as data objects that describe the action that needs to be bound. We'll call these objects the C# payloads.

A good way to think of the C# runtime binder is a mini compiler - it has many of the concepts you'd expect in a traditional compiler, such as a symbol table, and a type system, and much of the functionality as well, such as overload resolution and type substitution.

Lets use the simple example of d.Foo(1) for our consideration.

Once the runtime binder gets invoked, it is given the payload for the current call site, and the runtime arguments the site is being bound against. It takes the types of the all the arguments (including the receiver) and populates its symbol table with those types. It then unpacks the payload to find out the name of the operation it's trying to perform on the receiver (in this case, "Foo"), and uses reflection to load all members named "Foo" off of the runtime type of d, putting those members into its symbol table.

From there, we have enough information in the binder's internal system to do the binding that the action describes. At this point, we fork off and bind based on the payload's description.

One of the design choices we made was that the runtime binder should have the exact same semantics that the static compiler has. This includes reporting the same set of errors that the compiler would produce, and perform the same set of conversions (user-defined or otherwise).

As such, each payload is bound exactly as the static compiler would have. The result of the bind is an expression tree that represents the action to take if the binding was successful. Otherwise, a runtime binder exception is thrown. The resulting expression tree is then taken and merged into the call site's delegate to become part of the DLR cache mechanism, and is then invoked so that the result of the user's dynamic bind gets executed.

A slight limitation

As I mentioned, we tried to keep the philosophy of matching exactly what the static compiler would do. However, there are several scenarios that will not work in Visual Studio 2010 that we will hopefully get to in a future release.

Several to note are lambdas, extension methods, and method groups.

Because we currently do not have a way of representing the source of a lambda at runtime without a binding, dynamic invocations that contain lambdas produce a compile time error.

Also, because we don't currently have a way of passing in using clauses and scopes during runtime, extension method lookup will also not be available for this next release.

There is currently no way to represent a method group at runtime (ie there is no MethodGroup type), and so without introducing that concept into .NET, there is no good way for us to allow method groups to be represented dynamically. This means that you cannot do the following:

delegate void D();
public class C
{
    static void Main(string[] args)
    {
        dynamic d = 10;
        D del = d.Foo; // This would bind to a method group at runtime. 
    }
}

Because we cannot represent method groups at runtime, a runtime exception will be thrown if the runtime binder binds d.Foo to a method group.

Hopefully you have a better understanding about the details of what happens when C# performs a dynamic bind. Next time we'll take a look at the second set of scenarios we discussed today. We'll also introduce the Phantom Method, and describe what it does and see how it affects overload resolution.

Until then, happy coding!

kick it on DotNetKicks.com
Leave a Comment
  • Please add 5 and 8 and type the answer here:
  • Post
  • PingBack from http://mstechnews.info/2008/11/dynamic-in-c-ii-basics/

  • Welcome to the 47th Community Convergence. We had a very successful trip to PDC this year. In this post

  • // Regarding:

    dynamic d = 10;

    d.Foo();

    How can you call a method on the 'dynamic' literal number 10?

  • > One of the design choices we made was that the runtime binder should have the exact same semantics that the static compiler has. This includes reporting the same set of errors that the compiler would produce, and perform the same set of conversions (user-defined or otherwise).

    Does this mean that, when dispatching on a dynamic receiver, members of explicitly implemented interfaces are not considered? I.e.:

    interface IFoo {

     void Bar();

    }

    class Foo : IFoo {

     void IFoo.Bar();

    }

    dynamic foo = new Foo();

    foo.Bar(); // exception?

  • PinkDuck -

    You cant - but at compile time, the compiler doesn't keep track of the fact that d is the literal 10. At compile time, d is simply a local variable typed dynamic, and since it is typed dynamic, all its calls are dispatched dynamically.

    At runtime, the runtime binder sees that the receiver 'd' is typed int, and looks for methods named Foo on int. When it doesn't find one, it will throw a runtime binding exception.

  • int19h -

    Great question! This is one issue that we're currently looking into. The consideration is this:

    Explicitily implemented interfaces are (statically) callable only through the interface, and not through the implementing object.

    However, at runtime, there really isn't a notion of interfaces - everything is just a bunch of runtime objects. So from a purely legalistic approach, these guys should never be callable.

    BUT - and this is a big but, and a bit of a spoiler for my next post - dynamic invocations can happen with a STATIC receiver! What happens if that receiver is the correct interface?

    Consider:

    IFoo f = ...;

    dynamic d = ...;

    f.Foo(d);

    Because at compile time, the type of d is dynamic (the second class of dispatches in my post), we cant resolve the call to Foo at compile time, and must do it at runtime. But the receiver is known at compile time to be an explicit interface! That means the runtime binder should dispatch to that interface call, because the call itself was never meant to be dynamic!

    So what do we do?

    We're not sure yet! :) We may make a call and say that if the receiver is dynamic, you cant call explicit methods on it, but if the receiver is static, then you can. I'll definitely let you know more on it when we decide!

    But again, great question!

  • int19h -

    Great question! This is one issue that we're currently looking into. The consideration is this:

    Explicitily implemented interfaces are (statically) callable only through the interface, and not through the implementing object.

    However, at runtime, there really isn't a notion of interfaces - everything is just a bunch of runtime objects. So from a purely legalistic approach, these guys should never be callable.

    BUT - and this is a big but, and a bit of a spoiler for my next post - dynamic invocations can happen with a STATIC receiver! What happens if that receiver is the correct interface?

    Consider:

    IFoo f = ...;

    dynamic d = ...;

    f.Foo(d);

    Because at compile time, the type of d is dynamic (the second class of dispatches in my post), we cant resolve the call to Foo at compile time, and must do it at runtime. But the receiver is known at compile time to be an explicit interface! That means the runtime binder should dispatch to that interface call, because the call itself was never meant to be dynamic!

    So what do we do?

    We're not sure yet! :) We may make a call and say that if the receiver is dynamic, you cant call explicit methods on it, but if the receiver is static, then you can. I'll definitely let you know more on it when we decide!

    But again, great question!

  • > We may make a call and say that if the receiver is dynamic, you cant call explicit methods on it, but if the receiver is static, then you can.

    Given that you seem to be really trying to make runtime resolution as close to compile-time as possible, what you describe seems to be the only sensible approach. It allows for a simple rule of thumb: if receiver is dynamic, overload resolution is exactly the same as if "dynamic" type was replaced by the actual type of the object (and if it's not dynamic, then its declared type is used).

    It's just a pity that, if you go that way, it will mark yet another point on which explicit interface implementations are second-class citizens in C# (another, existing, sore point is that you cannot properly override them with the ability to call base implementation in C#, even though CLR allows for that).

  • Thats exactly the rationale that we've been using when making these decisions, but yes, I agree with you that this is yet another point in which explicit interface implementations are second-class citizens.

    Though, like you say, theres not much we can really do about this one with the current architecture and framework... :)

  • Very good resources for the coming version... Sam Ng Dynamic in C# Part One Dynamic in C# Part Two Chris

  • Last time we dealt with the basics of dynamic binding . This time, we'll add a small twist. First, lets

  • You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • By now, my hope is that you all have a well-rounded view of dynamic. We started this series by introducing

  • Another experiment with C# 4 and the “dynamic type”. I was playing with this code below and wasn’t 100%

  • Excellent! I think I need to go through each post in your blog. Love the stuff. Keep posting.

Page 1 of 1 (15 items)