How will you make it if you never even try?

July 5, 2006

Generics and XPATH — a beautiful match

Filed under: C# — Tags: , , , — charlieflowers @ 8:19 pm

Generics are sweet. Here’s a simple little example that let me cut down on repetitious code when working with XPath.
If you know a little XPath, then you know that it lets you specify a string that contains an “XPath query”, and that query will return you one or more Xpath nodes that match your query. The nodes might be Xml attributes or an Xml elements (or, of course, any of the other types of Xml nodes), depending on your query.
I found myself needing to write code that obtains a single “required” node in an Xml document. By “required”, I mean that I wanted to do an XPath query for the node, and if no match was found, I wanted to throw an exception saying “The xpath query ‘/whatever’ has no match, but exactly one match was expected.”
And of course, the kinds of Xpath queries I commonly needed were those to get either a required Element or a required Attribute. Without generics, I would have had to do something like this.

public static XmlAttribute GetRequiredXmlAttribute(XmlDocument doc, string xpath)
{
	XmlNode node = doc.SelectSingleNode(xpath);

	if (node == null)
	{
		throw new Exception(“The xpath ‘” + xpath + “’ has no matches, but exactly one match is required.”);
	}

	XmlAttribute attribute = node as XmlAttribute;

	if (attribute == null)
	{
		// There was a match, but it is not an XmlAttribute.
		throw new Exception(“The xpath ‘” + xpath + “’ matches a node of type ‘” + node.GetType().FullName + “’, which is not an XmlAttribute.”);
	}

	return attribute;
}

This code is an absolute POSTER BOY for generics. More than half the battle in learning a new technology is in understanding the motivation for it. If you understand that certain XPath queries will always match an XmlAttribute and other XPath queries will always match an XmlElement, and you know that you normally have to do a lot of type-checking and casting to figure out which kind of Xml node you’ve got, then you are looking at one of the key motivations behind generics.
Here’s the generic version of the code – very nice!

public static T GetRequiredNodeFromSourceNode<T>(XmlNode sourceNode, string requiredXpath) where T : XmlNode
{
	XmlNode node = sourceNode.SelectSingleNode(requiredXpath);

	if (node == null)
	{
		throw new ArgumentException("Tried to extract the path '" + requiredXpath + "', but nothing was found for that xpath.");
	}

	T result = node	as T;

	if (result == null)
	{
		throw new ArgumentException("The xpath you provided points to a node of type " + node.GetType().FullName +
		", which cannot be cast to type " + typeof(T).FullName + ".");
	}

	return result;
}

See, generics lets you express something that you always knew about, but were previously unable to express. You knew that some XPaths returned elements while others returned attributes – but .Net 1.x did not give you a way to express that in your code. Now, generics does.
Here’s some code that uses the above generic method:

XmlDocument doc = new XmlDocument();
doc.LoadXml(@"C:\someFile.xml");

XmlAttribute attribute = GetRequiredNodeFromSourceNode<XmlAttribute>(doc, "/root/@someAttribute");
XmlElement element = GetRequiredNodeFromSourceNode<XmlElement>(doc, "/root/someElement");

// This will give one of our exceptions, because this xpath syntax always returns an element.
XmlAttribute attributeFail = GetRequiredNodeFromSourceNode<XmlAttribute>(doc, "/root");

// This will give one of our exceptions, because this xpath syntax always returns an attribute.
XmlElement elementFail = GetRequiredNodeFromSourceNode<XmlElement>(doc, "/root/@hello");

What it boils down to:
Some XPath queries are “typed” by nature — certain queries always return an XmlAttribute while others always return an XmlElement. However, before generics C# gave you no way to express that fact without resorting to the common base class, XmlNode. Generics addresses this exact problem. So you can write less code and have it cover more ground (for example, this code works for XmlComments, processing instructions, and whatever other kinds of Xml nodes you might need to deal with in the future).
What’s also interesting about this example is that it lets you be strongly typed even when you don’t know what your return type will be. If you’ve programmed in C# for a while, this is probably something you “felt the need for” at one time or another, but it couldn’t be acheived before generics.

Advertisements

Blog at WordPress.com.