C# 4.0: DXLinq, a DynamicObject example

System.Xml.Linq is a great library, it lets us very easily manipulate XML, in very close fashion to how we would interact with other data sources. My only issue is that it tends to seem a little redundant,  given this XML:

<A>
  <B X="1"/>
  <B X="2"/>
</A>

We would do the following to get the value of both the X attributes:

var root = XElement.Load("Sample.xml");

foreach (var b in root.Elements("B"))
    Console.WriteLine(b.Attribute("X").Value);

That’s not exactly the cleanest code ever, so what if we could do this instead:

var root = XElement.Load("Sample.xml");

dynamic xml = new DXElement(root);

foreach (var b in xml.B)
    Console.WriteLine(b.X);

Using the combination of C# 4.0’s new dynamic type, and the DynamicObject class, getting this working is actually pretty easy. What we need is a dynamic that will map properties to child elements and attributes. The DynamicObject base class provides an overloadable TryGetMember method that is called whenever the code tries to get a property off of a dynamic. For example, when xml.B is used above, DynamicObject.TryGetMember is called, asking the DynamicObject to return the value of the member. So the DXElement gets to decide what type and value to return, at runtime. Here is my DXElement class, in its entirety:

public class DXElement : DynamicObject
{
    public XElement BaseElement { get; set; }

    public DXElement(XElement baseElement)
    {
        this.BaseElement = baseElement;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        //Go ahead and try to fetch all of the elements matching the member name, and wrap them
        var elements = BaseElement.Elements(binder.Name).Select(element => new DXElement(element));

        //Get the count now, so we don't have to call it twice
        int count = elements.Count();
        if (count > 0)
        {
            if (count > 1)
                result = elements; //We have more than one matching element, so let's return the collection
            else
                result = elements.FirstOrDefault(); //There is only one matching element, so let's just return it

            return true; //return true cuz we matched
        }
        else
        {
            //Ok, so no elements matched, so lets try attributes
            var attributes = BaseElement.Attributes(binder.Name).Select(attr => attr.Value);
            count = attributes.Count();

            if (count > 0)
            {
                if (count > 1)
                    result = attributes; //more than one attribute matched, lets return the collection
                else
                    result = attributes.FirstOrDefault(); //only one attribute matched, lets just return it

                return true; // return true cuz we matched
            }
        }

        //no matches, let's let the base class handle this
        return base.TryGetMember(binder, out result);
    }
}

Not too bad right? Now, it makes a couple of assumptions about the input XML, which are most likely false, but for examples sake, we’ll just ignore that fact ;). As you can see, there isn’t much code needed, just the TryGetMemeber overload using XLinq against the BaseElement to find what we want to return as the result.

Advertisements

5 comments so far

  1. EDavis on

    That’s pretty cool. Thanks for posting.

  2. TJones on

    Why is it the cool stuff you want today is one step ahead of you. Then when it is here, you want the next thing a step ahead.

    • scmccart on

      b/c ppl wouldn’t stay interested w/o a dangling carrot 😉

  3. Lucie-Alfons on

    Great idea, but will this work over the long run?

    • scmccart on

      Well, it was just meant as an example, but I don’t think it would take much to flesh it out the rest of the way. Do you have any suggestions?


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: