Dynamic in C# 4.0: Creating Wrappers with DynamicObject
In the previous post I showed how you can use the new dynamic feature and the ExpandoObject class to add and remove properties at run time, and how this can make your code more readable and flexible than code written with LINQ to XML syntax.
But there were some obvious flaws in that example: While ExpandoObject provided better syntax, LINQ to XML provided a lot of useful library methods that helped you to work with XML files. So, is it possible to combine those two advantages, to have better syntax and still get all those methods? The answer is yes, but you need another type from the System.Dynamic namespace: DynamicObject .
The DynamicObject class enables you to override operations like getting or setting a member, calling a method, or performing any binary, unary, or type conversion operation. To illustrate the issue, let’s create a very simple object that overrides the “get property” operation, so that whenever you access a property it returns the property’s name as a string. This example has no practical value.
public class SampleObject : DynamicObject
{
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
result = binder.Name;
return true;
}
}
As with ExpandoObject, we must use the dynamic keyword to create an instance of this class.
dynamic obj = new SampleObject();
Console.WriteLine(obj.SampleProperty);
//Prints "SampleProperty".
Let’s see what’s going on in this example. When you call obj.SampleProperty, the dynamic language runtime (DLR) uses the language binder to look for a static definition of this property in the SampleObject class. If there is no such property, the DLR calls the TryGetMember method. This method gets information about what property it was called for through the binder parameter. As you can see, the binder.Name contains the actual name of the property.
The TryGetMember method returns true if the operation is successful. But the actual result of the operation must be assigned to the out parameter result. In this example, TryGetMember returns true, but obj.SampleProperty returns "SampleProperty".
Now let’s move to a more complex example and create a wrapper for the XElement object. Once again, I’ll try to provide better syntax for the following LINQ to XML sample.
XElement contactXML =
new XElement("Contact",
new XElement("Name", "Patrick Hines"),
new XElement("Phone", "206-555-0144"),
new XElement("Address",
new XElement("Street1", "123 Main St"),
new XElement("City", "Mercer Island"),
new XElement("State", "WA"),
new XElement("Postal", "68042")
)
);
First of all, I need to create an analog of ExpandoObject. I still want to be able to dynamically add and remove properties. But since I am essentially creating a wrapper for the XElement type, I’ll use XElement instead of the dictionary to maintain the properties.
public class DynamicXMLNode : DynamicObject
{
XElement node;
public DynamicXMLNode(XElement node)
{
this.node = node;
}
public DynamicXMLNode()
{
}
public DynamicXMLNode(String name)
{
node = new XElement(name);
}
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
XElement setNode = node.Element(binder.Name);
if (setNode != null)
setNode.SetValue(value);
else
{
if (value.GetType() == typeof(DynamicXMLNode))
node.Add(new XElement(binder.Name));
else
node.Add(new XElement(binder.Name, value));
}
return true;
}
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
XElement getNode = node.Element(binder.Name);
if (getNode != null)
{
result = new DynamicXMLNode(getNode);
return true;
}
else
{
result = null;
return false;
}
}
}
And here is how you can use this class.
dynamic contact = new DynamicXMLNode("Contacts");
contact.Name = "Patrick Hines";
contact.Phone = "206-555-0144";
contact.Address = new DynamicXMLNode();
contact.Address.Street = "123 Main St";
contact.Address.City = "Mercer Island";
contact.Address.State = "WA";
contact.Address.Postal = "68402";
Let’s look at the contact object. When this object is created, it initializes its inner XElement. If you set a property value, like in contact.Phone = "206-555-0144", the TrySetMember method checks whether an element with the name Phone exists in its XElement. If it does not exist, the method creates the element.
The next interesting line is contact.Address = new DynamicXMLNode(). Basically, in this particular example this line means that I want to create a node that has subnodes. For this property, the TrySetMember method creates an XElement without a value.
The most complex case here is a line such as contact.Address.State = "WA". First, the TryGetMember method is called for contact.Address and returns a new DynamicXMLNode object, which is initialized by the XElement with the name Address. (Theoretically, I could have returned the XElement itself, but that would make the example more complicated.) Then the TrySetMember method is called. The method looks for the State element in contact.Address. If it doesn’t find one, it creates it.
So I have successfully created the required XML structure. But TryGetMember always returns an instance of DynamicXMLNode. How do I get the actual value of the XML node? For example, I want the following line to work, but now it throws an exception.
String state = contact.Address.State;
I have several options here. I can modify the TryGetMember method to return actual values for leaf nodes, for example. But let’s explore another option: override type conversion. Just add the following method to the DynamicXMLNode class.
public override bool TryConvert(
ConvertBinder binder, out object result)
{
if (binder.Type == typeof(String))
{
result = node.Value;
return true;
}
else
{
result = null;
return false;
}
}
Now whenever I have an explicit or implicit type conversion of the DynamicXMLNode type, the TryConvert method is called. The method checks what type the object is converted to and, if this type is String, the method returns the value of the inner XElement. Otherwise, it returns false, which means that the language should determine what to do next (in most cases it means that you’re going to get a run-time exception).
The last thing I’m going to show is how to get access to the XElement methods. Let’s override the TryInvokeMember method so that it will redirect all the method calls to its XElement object. Of course, I’m using the System.Reflection namespace here.
public override bool TryInvokeMember(
InvokeMemberBinder binder,
object[] args,
out object result)
{
Type xmlType = typeof(XElement);
try
{
result = xmlType.InvokeMember(
binder.Name,
BindingFlags.InvokeMethod |
BindingFlags.Public |
BindingFlags.Instance,
null, node, args);
return true;
}
catch
{
result = null;
return false;
}
}
This method enables you to call XElement methods on any node of the DynamicXMLNode object. The most obvious drawback here is absence of IntelliSense.
I’m not even going to pretend that this example is a ready-to-use wrapper for the LINQ to XML library. It doesn’t support attributes, doesn’t allow you to create a collection of nodes (for example, multiple contacts), and it is probably missing other features. Creating a library is a difficult task, and creating a good wrapper is too. But I hope that after reading this blog post you can create a fully functioning wrapper with DynamicObject yourself.
So, if you routinely use a library with complicated syntax that crawls XML files or works with script objects, or if you are creating such a library yourself, you should probably consider writing a wrapper. Doing this might make you more productive and the syntax of your library much better.
All the examples provided in this blog post work in the just released Visual Studio 2010 Beta 2. If you have any comments or suggestions, you are welcome to post them here or contact the DLR team at https://www.codeplex.com/dlr. You can also send an e-mail to the DLR team at dlr@microsoft.com.
Documentation for DynamicObject is also available on MSDN (check out our new MSDN design and don’t forget to take a look at the lightweight view.) In documentation, you can read about other useful methods of this class, such as TryBinaryOperation, TryUnaryOperation, TrySetIndex, and TryGetIndex.
Anonymous
October 19, 2009
Hi, this is another great post. I read alot about DynamicObject after I read you previous post about ExpandoObject, really with this two new classes, life would be much easier :)Anonymous
October 20, 2009
This is indeed very cool! Creating wrappers for objects is now childs play! :) Just tried doing one for the ever so tedious work of implementing "INotifyPropertyChanged" for all ViewModel objects in a wpf project. works like a charm. I have a question though, it's all well and fine that I can do for instance "Contact.Name" to access a property called "Name", but what if i don't know at compile-time the name of the property that I want to look up (in this case 'Name')? (But maybe at runtime I want to look up some other property, but this is decided via combobox or whatever) Cheers!Anonymous
October 21, 2009
@Dynamic Duo With DynamicObject, you can define methods and properties on the class. Probably, the easiest way for you will be to implement some kind of GetProperty() method in your implementation of DynamicObject. In this case, if you call obj.GetProperty("Name"), the actual GetProperty() method will be called. But if you call something like obj.SampleMethod(), the DLR will check whether such a method exists in your class, and if not, it will call TryInvoke(). This way you can support both simplified syntax such as obj.Name for cases when you know the name of the property at compile-time and standard syntax such as obj.GetProperty("Name") to support your scenario when you get the property name at run-time.Anonymous
October 21, 2009
DynamicDuo: For properties I don't know at compile time, I would use a syntax like obj[propName], where propName is the name of the property determined at runtime. Then I would just implement TrySetIndex/TryGetIndex to do the same thing as TrySetMember/TryGetMember, only the property name would come from the indexes parameter instead of the binder parameter.Anonymous
October 21, 2009
@ Gabe Nice idea. I really like it.Anonymous
October 25, 2009
It's a cool function of .NET 4. But I don't know what kind of scenario I need it?Anonymous
October 26, 2009
Nice article thanksAnonymous
November 14, 2009
Thank you for this article - these new features are an enormous help. This allows C# do things that Django and Rails already do, creating methods on the fly. For example, in Django I can call a method that doesn't exist on an object, such as "Person.orderby_lastname_and_datevisited", and it will create a SQL query with this ordering, done on the fly. These new dynamic features of C# are going to make my life much easier. You're slowly getting to the flexibility of Python and Ruby, though with the static typing. In a year's time, it will be interesting to compare C# 4 and Perl 6.Anonymous
March 03, 2010
The comment has been removedAnonymous
March 04, 2010
@JonB Yes, compiler doesn't warn you in this case. The thing is, if you don't have an XML node "Name" (like in XElement.Element("Name")), the compiler doesn't warn you either. In both cases all you get is a run-time exception. There are techniques that help you to avoid this problems. TDD and unit testing are the most famous ones.Anonymous
March 20, 2010
The comment has been removedAnonymous
April 15, 2010
@JonB Jon, TDD has been around for ages now - relying on static compiler verifications is something of the past...and if the compiler was so thorough why do we even have the concept of runtime errors? These days even static language evangelists push for TDD as a design technique. Try Ruby, try Perl, see the dynamic side of programming for yourself. I'm really glad that C# is moving in this direction. Being involved with MS technologies since VC++ 1.0, I have to say MS is doing the right thing - they won't stand a chance otherwise. What, with all those scripting developments on top of Java (think Groovy, Scala, JRuby) and the Python and Ruby crowd, MS will be losing points big time if they don't play the catch-up game...Anonymous
May 11, 2010
Just getting the feeling C# and .net is just becoming one rather large potential dinosaur. The more things are obscured and different ways are added to do the same thing, the more likely things are to go wrong or be misused by Engineers who just want to try out the next Microsoft language fad. There is a lot to be said for the KISS (Keep It Simple S....) principle and that applies to engineering more than most fields! The above XML example is a perfect example on how to make life difficult - I'm an engineer who has to clean up the mess caused by developers using 'very' clever code - so ok I'm biased :)Anonymous
May 12, 2010
Can we create event on the fly and fire it?Anonymous
July 02, 2010
This is very cool feature wich simplifies developers life in metadata programming era!!! Thanks for this extension - I lacked that since migrated from JavaScript to C#Anonymous
July 28, 2010
Just came across this article while looking for a solution to my problem. Hope you could guide me well. In the process of implementing a wrapper in VB for web sessions (i.e. HttpContext.Current.Session) for my library, I got hit by two problems:
- Having "Option Strict On" vomits while attempting to use the object. e.g. Dim ws As Object = New MyLibrary.WebSessionWrapperClass ws.UserName = "Username" (this throws the compiler to disallow late binding)
- I have to instantiate the class everytime I need to access the session thru my wrapper, thus defeating my intentions to obtain friendly access to the web session contents. Where: WebSessionWrapperClass inherits from DynamicObject What could be the right (politically correct) way of achieving my objective? Thanks / @bhi
- Anonymous
February 06, 2014
Hi, Dynamic parameter breaks edit and continue in visual studio -- can you help to confirm if this has been fixed in VS2013, or I'm just need to adjust some settings thanks