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)
{
var elements = BaseElement.Elements(binder.Name).Select
(element => new DXElement(element));
int count = elements.Count();
if (count > 0)
{
if (count > 1)
result = elements;
else
result = elements.FirstOrDefault();
return true;
}
else
{
var attributes =
BaseElement.Attributes(binder.Name).Select(attr => attr.Value);
count = attributes.Count();
if (count > 0)
{
if (count > 1)
result = attributes;
else
result = attributes.FirstOrDefault();
return true;
}
}
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.