C# 4 added support for the new dynamic type, which uses a version of the DLR to dispatch calls at runtime, allowing objects to change their behaviour quite dramatically.

At the C# level, it is relatively easy to implement a dynamic object by inheriting from the DynamicObject class. This convenience class makes it really easy to implement dynamic objects, though to really see what’s going on one needs to look at the IDynamicMetaObjectProvider interface.

We’ll use the following test program to drive the creation of the dynamic objects.

object[] targets = { new MyObject1(), new MyObject1(), new MyObject1(), new MyObject2(), new MyObject1(), new MyObject2(), new MyObject3(), new MyObject4(), new MyObject5(), new MyObject6(), new MyObject7(), new MyObject8(), new MyObject9(), new MyObject10(), new MyObject1() }; foreach (dynamic target in targets) { dynamic result = target.Foo; }

Using Reflector, with the optimization set at 2.0, we see how the dynamic call is implemented. The foreach loop in the above translates to the following code.

I wanted to look at the dynamic behaviour, so the next thing to do was to ensure that the assembly containing the interesting code is unoptimised so that I could step through it using Reflector Pro. We can do this by uninstalling the default optimised ngen version.

If you use Reflector to follow the code from CallSite<.>.Create, you see that the Target property holds a delegate that calls UpdateAndExecute1. I therefore decompiled System.Core using Reflector Pro and navigated to the UpdateDelegates class where I put a breakpoint on UpdateAndExecute1. On the first call to target.Foo, with target bound to MyObject1, we enter this method.

For a given call site, the system maintains some caches that hold dispatches that we’ve made in the past at this call site. On the first call, we drop through to

which is code that is going to compile a test and fast dispatch to a given set of typed arguments.

The code passes through a set of methods until it eventually reaches the Compile method in System.Linq.Expression.Expression<T>. Grabbing the expression tree, by going to the locals window and using "Make ObjectId", we can later look at the code to determine that it is compiling the following delegate. The debugger has a view for exposing Expression Trees in the textual format seen below.

The cool thing is that the Target delegate of the callsite is set to point to the compiled version of this expression tree. Hence the next time we call target.Foo on an instance of MyObject1, we’ll dispatch really quickly into the TryGetMember code, and will continue to do so until we try to dispatch on something that isn’t of type MyObject1. On the way out, we add the compiled expression tree to the Rules associated with the CallSite. We can store at most 10 such rules, and we’ll see where they come in to play in a little while.

When we make a call on MyObject2, the code above misses the fast path and we go back in to UpdateAndexecute1. The code in this method looks at the rules that we have previously compiled and tries to find something that works. On this pass it doesn’t find anything, so we go back into the compile code and compile the following ExpressionTree.

This is the same as the first expression tree, apart from the change to the guard type. Again, this compiled delegate is set into the Target property of the CallSite so we’ll quickly dispatch to the right code for future calls to objects of type MyObject2 at this call site, and it is added to the rules for the callsite.

In our test program, we now dispatch to an instance of MyObject1. This causes the fast path to miss, and we get back into the UpdateAndexecute1 method.

The system gets the Rules for the CallSite, and in this case there are two that are applicable. One matches the MyObject1 case and the other matches the MyObject2 case. We pull the MyObject1 dispatcher from the rules cache, and set it into the Target property. We dispatch the call, notice it has been successful, and before we return the result, we call UpdateRules to move the rule that was successful earlier in the Rules array. This means that when new rules come into effect, it will take longer for it to be flushed out of the 10 item cache. We have also set the Target property to the matching rule, so we’ll dispatch to it quickly if we make another call on the same type.

The test program then goes on to make lots of calls on different types of target.

The Rules cache for the CallSite holds at most 10 items – roughly, the ten most recently used types at that call site. The BindCore method also maintains a cache at the level of the Binder for the call site, so that all call sites sharing the same binder can share the compiled results.

This cache is also bounded, but can hold a lot more items which are more globally applicable. This cache is searched only after the site specific cache has been fully tried. This is a clever way to exploit the observation that for many programs, we tend to dispatch for large amounts of time on the same type at a given call site.

and then run under windbg with the sos extension loaded. The first three calls are to a target of type MyObject1… during the first call the cache is set up, but the subsequent two calls don’t go through the setup code. The fourth call targets a different object type and therefore goes back through the cache setup code.