Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# 4.0: DXLinq, a DynamicObject example

0.00/5 (No votes)
5 Jun 2009 1  
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...

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:

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

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

C#
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:

C#
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:

C#
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 because 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, 
					//let's return the collection
                else
                    result = attributes.FirstOrDefault(); //only one attribute matched, 
							//let's just return it

                return true; // return true because we matched
            }
        }

        //no matches, let's allow the base class to 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here