C# "dynamic," Part II

Yesterday, I made an attempt to introduce the C# dynamic feature by describing what change there has been to the language, what scenarios we hope to affect by that change, and some overview of what dynamic operations look like in C# 4 as it stands in the preview distributed at PDC 2008.

One thing that I did not mention was this: that the language changes are entirely focused around consumption of dynamic types, not definition of those types. This is not to say that you cannot define dynamic types--just that the C# language has not change in any way so as to provide you a shortcut. We have not added a "method_missing," nor are any of the regular types you define in C# capable of somehow dynamically acquiring properties. And you can't say something like "dynamic class C" to define such a class.

If you want to define a new type that accepts dynamic operations, then it's easy to do so, and you don't even need C# 4. You just implement IDynamicObject IDynamicMetaObjectProvider, which is the interface that tells the DLR, "I know how to dispatch operations on myself." It's a simple interface, in the spirit of IQueryable, with a single method that returns a "MetaObject" "DynamicMetaObject" to do the real heavy lifting.

I want to make clear at this point that I am about to provide an example of how to use the DLR, but that I am only doing so in order to demonstrate how these objects play with the C# 4 language features. There are other blogs that are certainly better sources for information about the DLR.

So, here's an implementation:

 public class MyDynamicObject : IDynamicMetaObjectProvider
{
    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new MyMetaObject(parameter, this);
    }
}

Simple enough! Here's the MyMetaObject definition. The MetaObject DynamicMetaObject "knows how" to respond to a variety of actions including method calls, property sets/gets, etc. I'll just handle those (no best practices here; this is a minimal implementation):

 public class MyMetaObject : DynamicMetaObject
{
    public MyMetaObject(Expression parameter, object value)
        : base(parameter, BindingRestrictions.Empty, value)
    {
    }

    public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
    {
        return this.PrintAndReturnIdentity("InvokeMember of method {0}", binder.Name);
    }

    public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
    {
        return this.PrintAndReturnIdentity("SetMember of property {0}", binder.Name);
    }

    public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
    {
        return this.PrintAndReturnIdentity("GetMember of property {0}", binder.Name);
    }

    private DynamicMetaObject PrintAndReturnIdentity(string message, string name)
    {
        Console.WriteLine(String.Format(message, name));
        return new DynamicMetaObject(
            Expression,
            BindingRestrictions.GetTypeRestriction(
                Expression,
                typeof(MyDynamicObject)));
    }
}

This is exactly what, say, IronPython might do to implement the dictionary lookup that it needs to do for its members. And when I say "exactly," I mean "sort of" because of course IronPython's implementation is considerably more complex and robust. But this gets the job done. Suppose I want to now use C# to invoke these things using the new syntax. That's the easy part! Check this out:

 public class Program
{
    static void Main(string[] args)
    {
        dynamic d = new MyDynamicObject();

        d.P3 = d.M1(d.P1, d.M2(d.P2));
    }
}

So I take my MyDynamicObject that I defined above, and then I get a few properties, call a few methods, and set a property for good measure. If you compile this, you get the following output:

 GetMember of property P1
GetMember of property P2
Call of method M2
Call of method M1
SetMember of property P3

I think that's pretty cool.

Next time I'll talk more about the dynamic type again, in the C# language, and how it behaves. I just wanted to take a little diversion to clear up the fact that we've really done a lot of cool work on the consumption side, and your code ought to look better for it when you're consuming these things. If you're defining them, then you're in DLR-land, and you might even be writing in python or ruby or some other language. I'll provide pointers to those issues as I get them.

Previous posts in this series: C# "dynamic"