Tore Vestues blogs

On a quest for the silver bullet...

Creating a dynamic xml reader with C# 4.0

"The static type dynamic" was the catchphrase at PDC'08 when talking about what's new in C# 4.0. The dynamic type seems to be introduced mainly to simplify the code you write when doing Com interop. But many also see this as a step towards the dynamic languages for C#. I like C#. I also like dynamic languages like python. In addition I am a big fan of the static language Boo that actually feels quite dynamic. This has given me a special interest in the dynamic type in C#.

One of the nice things (there are several others!) about dynamic languages like Python is that the code feels much cleaner. It is not bloated with type declarations all over. This clean feel was also the goal of the language Boo, and even though it is a static language, Boo really gives you that dynamic feel.

The question is the of course: Is the C# getting more dynamic?

Well to be honest I do not think so. Having to declare a type at all (even if the declaration is "dynamic") feels quite static, and the code bloats like static languages. Most of the time. I think it is wrong to think that C# is a more dynamic language due to the dynamic keyword, and I feel you are not using the right tools if you try to use C# as a dynamic language. If you want to write dynamic code, use a dynamic language.

But when we get the dynamic keyword, lets find things to use it for. When I am introduced to new features I am not that interested in all the fancy things you can do with it, I want real value. And I think I have found an example of real value in the dynamic keyword. I'll give you an example, other than in com-interop, where the dynamic keyword will help you keep your code nice and clean, where otherwise you'd have to write a whole lot of ugly code.

The Quick Xml Reader

The funny thing is that I have already implemented a nice xmlreader in Boo (using the IQuackFoo-interface). When the dynamic keyword was introduced in C# 4 and you've got the IDynamicObject from the DLR, I figured I can do the exact same thing in C#. That's just cool!

So what did I do? Here's the comment from the Boo code file:

class XmlReader(IQuackFu):
"""
Represents an xmldocument using "dynamic typing". 
Can be used to retrieve single values from the xml-document.
There is no support for retrieving lists.

Language: 
 Element: Both nodes and leaves
 Node: an element containing sub-elements
 Leaf: An element that represents a value (in xml this is an attribute or a node with nodetype text)

use method names to retrieve nodes, and properties to retrieve leaves
example:
<myXml stat="ok">
    <user id="123" nsid="123">
        <username>Tore</username>
        <password>foo</password>
    </user>
</myXml>

myXml as duck = XmlReader(xmlString)
myXml //will fail since "myXml" is no leaf
myXml() // will return a new XmlReaderrepresenting the rsp-node with children
myXml().stat // will return "ok"
myXml().user().username // will return "Tore"
"""

How cool is that? This is dynamic, resolved at run time, and it can now be done in C# as well. The point with parsing xml is that you won't catch errors in the xml structure at compile time anyway. So why not just do the code dynamic?

So, our Xml reader let's you easily get values from an xml structure without having to declare all the types and without having to worry about all the details of the xml-parsing that normally is a bit complex and frankly, boring. In addition the code is much more readable than normal xml-parsing code.

Xml reading in C#

So, how do we do this in C#? Our XmlReader is a class where you can use any method name or property name on it, and it will still compile. For example, This will compile:

dynamic ourReader = new XmlReader(xmlString);
ourReader.WhateverMethodNameYouLike();

To do this, the XmlReader needs to inherit the IDynamicObject, and it has to implement the method GetMetaObject that returns a MetaObject.

Simplifying the MetaObject

To implement a MetaObject and do some useful stuff you actually have to understand how to build a Linq expression tree that describes the action to be taken. If people are going to use this, it should be as simple as possible. Building Linq expression trees is not as simple as possible IMHO. So, I'll make a generic base class that hides the whole MetaObject with its Linq expression trees, and let you override some methods in your XmlReader class instead.

I will not go into details on how I have done this, but if you're interested, you can look at the source (there's a link at the end). The 'magic' is within the MetaBaseDynamicObject. Together with the BaseDynamicObject these can be reused for different tasks that needs on the fly interpreting of property and method calls.

All you have to do is to inherit the BaseDynamicObject, and override any of these methods:

public class BaseDynamicObject:IDynamicObject
{
    /// <summary>
    /// If a method on this object is called run time, this method will be called.
    /// </summary>
    /// <param name="action">Information on the method that has been called</param>
    /// <param name="args">parameters on the method call</param>
    /// <returns>The return value for the originally called method</returns>
    public virtual Object Call(CallAction action, object[] args)

    /// <summary>
    /// If a property is set on this object run time, this method will be called.
    /// </summary>
    /// <param name="action">Information on the property which value is attemted set</param>
    /// <param name="value">the value that the property is attemted set with</param>    
    public virtual void SetMember(SetMemberAction action, object value)

    /// <summary>
    /// If a property is called on this object run time, this method will be called.
    /// </summary>
    /// <param name="action">Information on the property which is being called</param>
    /// <returns>The return value for the property that has been called</returns>
    public virtual Object GetMember(GetMemberAction action) //, MetaObject[] args)
}

Implementing the XmlReader

So what we have to do here is to create a class (XmlReader), inherit the BaseDynamicObject, and override Call and GetMember. We will not bother with SetMember now, as we do not want to support updating the xml. This is what it looks like:

public class XmlReader:BaseDynamicObject
{
    private readonly XmlElement element;

    public XmlReader(string xmlText) {
        var doc = new XmlDocument();
        doc.LoadXml(xmlText);
        element = doc.DocumentElement;
    }

    public XmlReader(XmlElement xmlelement)
    {
        element = xmlelement;
    }

    public override object Call(CallAction action, object[] args)
    {
        var elements = element.SelectNodes(action.Name);
        if (elements.Count == 0) 
            throw new Exception("Element '" + action.Name + "' doesn't exist in xml");
        if (IsLeaf((XmlElement)elements[0]))
            throw new Exception("Element '" + action.Name + "' is a Leaf and cannot be fetched as a Node");
        return new XmlReader((XmlElement)elements[0]);
    }

    public override object GetMember(GetMemberAction action)
    {
        var elements = element.SelectNodes(action.Name);
        if (elements.Count > 0) {
            if (IsLeaf((XmlElement)elements[0]))
                return elements[0].InnerText;
            else
                throw new Exception("Element '"+action.Name+"' is not a Leaf and cannot be fetched one");
        }

        var attribute = element.GetAttributeNode(action.Name);
        if (attribute == null)
            throw new Exception("Element '"+action.Name+"' doesn't exist in xml");
        return attribute.Value;
    }

    private bool IsLeaf(XmlElement leafElement)
    {
        return leafElement.ChildNodes.Count == 1 &&
            leafElement.ChildNodes[0].NodeType == XmlNodeType.Text;
    }
}

This class now does all the xml-handling. So that means you can do stuff like this:

string xmlString = 
    "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
    "<myXml stat=\"ok\">" +
        "<user id=\"321\" nsid=\"123\">" +
            "<username>Tore</username>" +
            "<password>foo</password>" +
        "</user>" +
    "</myXml>";
dynamic myXml = new XmlReader(xmlString);
Console.WriteLine(myXml.stat); // "ok"
Console.WriteLine(myXml.user().username); // "Tore"
Console.WriteLine(myXml.user().nsid); // "123"

This is a real declarative and easy way to handle xml, and since xml will only fail run time anyway, there is no argument that you lose compile time checking either.

I hope that has triggered you a bit on some exciting possibilities on using the dynamic type.

Here's the source. Please remember you will need C# 4.0 to make this work.

Thanks to this and this blog for giving me information and ideas.

- Tore Vestues

(This post have been migrated from my old blog, so sadly the old comments are gone)

blog comments powered by Disqus