Sunday, July 26, 2009

Using the DynamicObject class

In this post I'm going to show a little example of using the DynamicObject class from the .NET's Dynamic Language Runtime. This class is an easy way to provide dynamic dispatch to your objects in a DLR language.

Code samples presented here were created using Visual Studio 2010 Beta 1 so there may be differences with the final version.


The experiment



The example is a class that allows access to the properties of JSON objects using the property syntax of a DLR language. In order to do this, a wrapper to JSON.NET's JObject was created. This wrapper is called FSDynJObjectWrapper.

The following code shows an example of using FSDynJObjectWrapper with C# 4.0 :


// Load the document
JsonTextReader reader = new JsonTextReader(GetTwitterPublicTimeLine());
JsonSerializer serializer = new JsonSerializer();
JArray topArray = (JArray)serializer.Deserialize(reader);

// Access the document
dynamic aObj = new FSDynJObjectWrapper((JObject)topArray[0]);
Console.WriteLine("========");
Console.WriteLine(aObj.user.screen_name);
Console.Write("\t'{0}'", aObj.text);


Given that the JSON returned by the Twitter REST api looks like this:


{"in_reply_to_screen_name":null,
"text":"...",
"user": { "following":null,
"description":"...",
"screen_name":"...",
"utc_offset":0,
"followers_count":10,
"time_zone":"...",
"statuses_count":155,
"created_at":"...",
"friends_count":1,
"url":"...",
"name":"...",
"notifications":null,
"protected":false,
"verified":false,
"favourites_count":0,
"location":"...",
"id": ...,
...
},
"truncated":false,
"created_at":"...",
"in_reply_to_status_id":null,
"in_reply_to_user_id":null,
"favorited":false,
"id":...,
"source":"...."
}



DLR and DynamicObject



The Dynamic Language Runtime provides a common infrastructure to build dynamic languages on the CLR. The System.Dynamic.DynamicObject class is an easy way to override the behavior of access to an object from a dynamic language. A partial definition of this class looks like this:


public abstract class DynamicObject : IDynamicMetaObjectProvider {
public virtual bool TryGetMember(GetMemberBinder binder,
out object result)
public virtual bool TrySetMember(SetMemberBinder binder,
object value)
public virtual bool TryDeleteMember(DeleteMemberBinder binder)

public virtual bool TryConvert(ConvertBinder binder,
out object result)
public virtual bool TryUnaryOperation
(UnaryOperationBinder binder, out object result)
public virtual bool TryBinaryOperation
(BinaryOperationBinder binder, object arg,
out object result)
public virtual bool TryInvoke
(InvokeBinder binder, object[] args, out object result)
public virtual bool TryInvokeMember
(InvokeMemberBinder binder, object[] args,
out object result)
...
}


As you can see this class has methods to override things like what happens when a property is access or a method is invoked. A detailed description of this class is available from the Getting Started with the DLR as a Library Author document .

The FSDynJObjectWrapper class



As presented above the FSDynJObjectWrapper inherits from DynamicObject and provides the required functionality. For this example the class is implemented using F# (although any .NET language could be used) and looks like this:


open System.Dynamic
open System.Reflection
open System.Linq.Expressions
open Newtonsoft.Json.Linq
open Newtonsoft.Json


type FSDynJObjectWrapper(theObject:JObject) =
inherit DynamicObject() with
override this.TryGetMember(binder : GetMemberBinder, result : obj byref ) =
match theObject.[binder.Name] with
| null -> false
| :? JObject as aJObject ->
result <- FSDynJObjectWrapper(aJObject)
true
| theValue ->
result <- theValue
true


The TryGetMember will be called when accessing a property. It tries to lookup the name of the requested property in the JObject instance. A new instance of the wrapper is created if the property value is another JObject instance allowing expressions like: aObj.user.screen_name .

For the next posts I'm going to show the use of other DLR classes and examples using other DLR languages.

Code for this post can be found here.