다음을 통해 공유


Dynamic in C#

The other day I was playing around with some office code, and I found myself writing a lot of code much like the following sample that Anders used at his PDC talk:

 static void Main(string[] args)
{
    var xl = new Excel.Application();

    ((Excel.Range)xl.Cells[1, 1]).Value2 = "Process Name";
    ((Excel.Range)xl.Cells[1, 2]).Value2 = "Memory Usage";
}

As you can imagine, it very quickly became tiresome assigning the results of each call to a local variable, debugging and finding out what type it returns for my scenarios, making the cast to the strong type so that I can call certain methods on it, rinse, and repeat.

This pattern is common in dynamic APIs, and cause a lot of excess code to be written that essentially is used just to make the type system happy. Wouldn't it be nice if instead, we could write something like this?

 static void Main(string[] args)
{
    var xl = new Excel.Application();

    xl.Cells[1, 1].Value2 = "Process Name";
    xl.Cells[1, 2].Value2 = "Memory Usage";
}

Well, in C# 4.0, we now allow you to write exactly that. One of the main features that we're working on in C# 4.0 is the dynamic late binding feature. This feature allows you to tell the compiler that the thing that I'm returning really ought to be treated like a dynamic type, and that any dispatch on it should be done dynamically. The runtime will then do the binding for you based on the runtime type of the object instead of the static compile time return type, and if the binding succeeds, then the code will succeed. This gives us exactly what we want.

So how does this feature work?

Firstly, we've introduced a dynamic type into the type system. This type indicates to the compiler that all operations based on the type should be bound dynamically and not at compile time. Secondly, we've created a C# runtime binder which does the late binding for you. Lastly, we've baked in the usage of the DLR and are making full use of their caching and dynamic dispatch capabilities, so that you can interop with dynamic objects (objects created from Iron Python for instance).

The dynamic type

In order to start using the dynamic binding, we've got to have some way to signify to the compiler that we want our object or expression to be bound dynamically. Enter the dynamic type.

The dynamic type is just a regular type that you can use in your code to denote local variables, fields, method return values etc. It tells the compiler that everything to do with that object or expression should be done dynamically. Consider the following example:

 static void Main(string[] args)
{
    dynamic d = SomeInitializingStatement;

    d.Foo(1, 2, 3); // (1)
    d.Prop = 10; // (2)
    var x = d + 10; // (3)
    int y = d; // (4)
    string y = (string)d; // (5)
    Console.WriteLine(d); // (6)
}

In this example, each of the statements has some element of the dynamic type flowing through it, and is therefore dispatched dynamically. Lets consider each of them.

  1. Since the receiver in this example is typed dynamic, the compiler will indicate to the runtime that it needs to bind some method named "Foo" on whatever the runtime type of d happens to be, with the arguments {1, 2, 3} applied to it.
  2. This example also has a dynamic receiver, and so the compiler indicates to the runtime that it needs to bind a property-looking-thing (could be a field or property) named "Prop", and that it wants to set the value to 10.
  3. In this example, the operator "+" becomes a dynamically bound operation because one of its arguments is dynamically typed. The runtime then does the normal operator overload resolution rules for "+", finding any user-defined operators named "+" on the runtime type of d, and considering that along with the regular predefined binary operators for int.
  4. In this example, we have an implicit conversion from the runtime type of d to int. The compiler signifies to the runtime that it should consider all implicit conversions on int and on the runtime type of d, and determine if there is a conversion to int.
  5. This example highlights an explicit conversion to string. The compiler encodes this cast and tells the runtime to consider explicit casts to string.
  6. In this example, despite the fact that we're calling a statically known method at compile time, we have dynamic arguments. As such, we cannot perform overload resolution correctly at compile time, and so the dynamic-ness of d flows out to its containing call, and we end up dispatching Console.WriteLine dynamically as well.

There are several other scenarios that dynamic flows out to, but I've listed these to give you a general idea of what the dynamic type's implications are.

Now, we should note that the dynamic type is really just syntactic sugar to signify to the compiler that it should treat bindings dynamically. In metadata, dynamic is just object with an attribute signifying its dynamicity (if that's even a word... I don't think it is though!).

What happens at compile time?

For each dynamic operation, the compiler generates calls into the DLR, and takes advantage of its call sites. For more information about the DLR and what call sites are, my colleague Jim Hugunin gave an excellent talk at PDC about it - you can view his blog here, and watch his talk here.

The DLR call site takes a set of standard actions which indicate what type of dynamic action we want to take. The C# compiler emits a subclass of these standard actions, annotated with some C# specific details, and emits invocations of the call sites in place of the call that the user makes. For instance, this code sample gets translated into something like the following pseudocode:

 // This code...
static void Main(string[] args)
{
    dynamic d = SomeInitializingStatement;
    d.Foo(1, 2, d);
}

// transforms into this code.
static void Main(string[] args)
{
    dynamic d = SomeInitializingStatement;
    _csharpCallAction = new CSharpCallAction("Foo");
    _dlrSite<T> = new Site<T>(_csharpCallAction); // Create the site. 
    _dlrSite.Target(1, 2, d); // Invoke the delegate. 
}

If you want more information on this, my colleague Chris Burrows has written an excellent blog on the dynamic type and what the compiler generates.

Note that the site creation pseudocode specifies a generic argument, T. This argument is a delegate type that represents the signature of the call. So in our example, our call takes 2 integer arguments and a dynamic argument, and has a dynamic receiver. T would then be a delegate that represents that.

Invoking that delegate invokes the C# runtime binder, which binds the expression based on the runtime types of the arguments and the receiver.

What happens at runtime?

When the DLR delegate gets invoked, it does a couple of cool things that I'll describe briefly. For more information, check out Jim Hugunin's blog.

  1. The DLR checks a cache to see if the given action has already been bound against the current set of arguments. So in our example, we would do a type match based on 1, 2, and the runtime type of d. If we have a cache hit, then we return the cached result.

  2. If we do not have a cache hit, then the DLR checks to see if the receiver is an IDynamicObject. These guys are essentially objects which know how to take care of their own binding, such as COM IDispatch objects, real dynamic objects such as Ruby or Python ones, or some .NET object that implements the IDynamicObject interface. If it is any of these, then the DLR calls off to the IDO and asks it to bind the action.

    Note that the result of invoking the IDO to bind is an expression tree that represents the result of the binding.

  3. If it is not an IDO, then the DLR calls into the language binder (in our case, the C# runtime binder) to bind the operation. The C# runtime binder will bind the action, and will return an expression tree representing the result of the bind.

  4. Once step 2 or 3 have happened, the resulting expression tree is merged into the caching mechanism so that any subsequent calls can run against the cache instead of being rebound.

Jim gives a great description of points 1, 2, and 4, which deal with the DLR specifics, so I'm going to elaborate on what happens in step 3.

The C# runtime binder

The C# runtime binder uses Reflection to populate its internal symbol table to determine what to bind to. Each of the C# specific actions encodes the type of the binding, along with extra information that allows us to determine how to bind the action.

For example, if the argument is known at compile time to have a static type, then that type will be marked in the C# action, and will be used as the type of the argument during runtime binding. If it is known at compile time to be typed dynamic (ie it is a variable of type dynamic, or is an expression that returns dynamic), then the runtime binder will use reflection to determine its runtime type and use that type as the type of the argument.

The runtime binder populates its symbol table as needed. For instance, in our example, we were calling the method Foo. The runtime binder will load all members named Foo on the type of the receiver into the symbol table.

It then populates the necessary conversions for each of the argument types. Since we may need to coerce the arguments to types that match the method calls (using user-defined conversions as necessary), the binder loads those conversions into the symbol table as well.

It then performs overload resolution exactly like the static compiler does. That means that we get the exact same semantics as the static compiler. It also means that we get the same error semantics and messages - a failed binding at runtime results in an exception being thrown, which encapsulates the error message that you would have gotten at compile time.

It then takes the result of overload resolution and generates an expression tree that represents the result, and returns that back to the DLR.

A summary

So that's a brief summary of what the dynamic pipeline looks like. Of course, I've glossed over a lot of the details, but I'll be covering those details in my future posts. Until next time, some questions to ponder:

What happens when the receiver is known statically but the arguments are dynamic? What happens if the methods we're trying to bind against are private? What about operators - how does resolution work on them?

These, and more, I'll aim to address in my subsequent posts.

As always, happy coding!

kick it on DotNetKicks.com

Comments

  • Anonymous
    October 29, 2008
    I didn't see anything about this, so I was wondering whether one could use 'dynamic' values for multimethods.

  • Anonymous
    October 29, 2008
    The comment has been removed

  • Anonymous
    October 30, 2008
    Please tell me something akin to "option strict" will exist.

  • Anonymous
    October 30, 2008
    The comment has been removed

  • Anonymous
    October 30, 2008
    The comment has been removed

  • Anonymous
    October 30, 2008
    Tanveer - We're debating whether or not to put an "option strict"-like thing on dynamic. Still in the process of that - will let you guys know when we've decided!

  • Anonymous
    October 30, 2008
    The comment has been removed

  • Anonymous
    October 30, 2008
    The comment has been removed

  • Anonymous
    October 31, 2008
    int19h - You've hit it dead on - the one place that dynamic can "leak" into your app is when you call someone else that returns a dynamic. This seems like a good place for IDE tools to help us figure out what calls are dynamic and which arent!

  • Anonymous
    October 31, 2008
    The comment has been removed

  • Anonymous
    November 02, 2008
    The comment has been removed

  • Anonymous
    November 03, 2008
    Exactly. Note that we COULD have gotten this particular scenario to work - recognize that its an assignment conversion and that we have a delegate type, so perform the conversion. But that wouldn't work in more general scenarios, such as passing a methodgroup to a method call, and performing overload resolution on the outer method first in order to determine the delegate type.

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

  • Anonymous
    November 03, 2008
    can yu tag this post with c# 4.0

  • Anonymous
    November 08, 2008
    C# 4.0 Dynamic Lookup I really like the way the C# team tackled bring dynamic programming to the language

  • Anonymous
    December 15, 2008
    Very good resources for the coming version... Sam Ng Dynamic in C# Part One Dynamic in C# Part Two Chris

  • Anonymous
    December 15, 2008
    As I mentioned last time, there are a few gotchas that we'll need to look at in order to get a full understanding

  • Anonymous
    December 16, 2008
    Last time , we began to dive into dynamic binding in C# and what happens through the pipeline. This time,

  • Anonymous
    December 16, 2008
    You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • Anonymous
    December 16, 2008
    http://blogs.msdn.com/samng/archive/2008/10/29/dynamic-in-c.aspx Subscribe in a reader Join my blog network

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

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

  • Anonymous
    May 06, 2010
    Taking Dynamics a bit further!   Another interesting article on .NET 4.0 dynamics can be seen here (http://nightowlcoders.blogspot.com/2010/05/building-better-dynamic-net-40.html)  This outlines how to take advantage of string based property access across a dynamic object.  This is a really powerful combination when put along side other features using Dynamics. Feel free to re-link, follow, and comment!