Tuesday 18 November 2008

Dynamic Objects, part1

(caveat: all based on the October 2008 public CTP; for all I know, this will be fixed in RTM - but either way, it shows how to work with IDynamicObject)

C# 4.0 / .NET 4.0 introduce DLR concepts into the core framework and language. However, you may-or-may-not know that actually we've had *a* form of dynamic object right from 1.1, in the form of ICustomTypeDescriptor (and TypeDescriptionProvider in .NET 2.0). These are used to provide custom data-binding to .NET objects, and is how (for example) a DataView displays columns in a grid - each column in the DataTable pretends to be a property on the object (each row).

Since C# 4.0 doesn't directly allow you to write dynamic objects, I've been playing with Tobias Hertkorn's dynamic class here.

I was a little surprised to see that this type of custom data-binding doesn't automatically work for dynamic objects (IDynamicObject) - i.e. the following fails:

     dynamic duck = new Duck();
duck.Value = "abc";
Application.Run(new Form { DataBindings = {
{ "Text", duck, "Value" } } });

It can't find a property called "Value" on our duck. Vexing. So; what can we do about this? Well, we could try exposing our dynamic object via the standard data-binding API. Of course, we could extend the Duck class to support ICustomTypeDescriptor directly, but I thought it would be more fun (read: challenging) to look at the more general problem of talking to an unknown dynamic object ;-p

To get data-binding working (on objects, rather than types), we need to use ICustomTypeDescriptor:

  • IDynamicObject - the dynamic object, which will be wrapped with... 
    • ICustomTypeDescriptor - which provides a... 
      • PropertyDescriptorCollection - which (for each named property) can provide a... 
        • PropertyDescriptor - which exposes... 
          • GetValue
          • SetValue

Most of the code involved here isn't very interesting (I will post it when I find somewhere suitable)... but it presents a few challenges. The main trick here is: given an IDynamicObject and a property name as a string, how do you get/set the value? ILDASM to the rescue, and it turns out to be something like the following (where "getter" and "setter" are used to cache the call-site):

   CallSite<Func<CallSite, object, object>> getter;
public override object GetValue(object component)
{
if (getter == null)
{
var binder = Microsoft.CSharp.RuntimeBinder.RuntimeBinder.GetInstance();
var payload = new Microsoft.CSharp.RuntimeBinder.CSharpGetMemberPayload(binder, Name);
getter = CallSite<Func<CallSite, object, object>>.Create(payload);
}
return getter.Target(getter, Unwrap(component));
}
CallSite<Func<CallSite, object, object, object>> setter;
public override void SetValue(object component, object value)
{
if (setter == null)
{
var binder = Microsoft.CSharp.RuntimeBinder.RuntimeBinder.GetInstance();
var payload = new Microsoft.CSharp.RuntimeBinder.CSharpSetMemberPayload(binder, true, null, Name);
setter = CallSite<Func<CallSite, object, object, object>>.Create(payload);
}
setter.Target(setter, Unwrap(component), value);
}

So how do we work with this? I've written a .AsBindable() extension method that wraps an IDynamicObject in my custom type descriptor. We can then bind to the dynamic object as below:

       var duck = new Duck();
dynamic dynamicDuck = duck;
dynamicDuck.Value = "abc";
var bindable = duck.AsBindable();
Application.Run(new Form { DataBindings = {
{ "Text", bindable, "Value" } } });

Cool ;-p

Next time, I might try and reverse things - i.e. rather than an existing IDynamicObject wrapped in a custom descriptor, I'll try and wrap a regular object (with custom 2.0 data-binding) with IDynamicObject to simplify data-access. Of course, HyperDescriptor would still be much quicker! This is just to play with the objects to see how it all works...