In my last post I showed how the new contravariance feature in .NET 4.0/Visual Studio 2010 for type parameters of generic interfaces makes coding with LINQ to XML easier and more straightforward. In this post I will show how the covariance of the type parameter T of IEnumerable<T> also allows us to write LINQ to XML queries in a more straightforward way.
Let's assume we have the following XML document:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<!-- comment 1 -->
<foo>foo 1</foo>
<bar>bar 1</bar>
<!-- comment 2 -->
<foo>foo 2</foo>
<bar>2</bar>
<!-- comment 3 -->
</root>
and we want to transform that document into a second one with the same root element having the same child nodes, except where all 'bar' child elements of the 'root' element node have been removed. So the result should look as follows:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<!-- comment 1 -->
<foo>foo 1</foo>
<!-- comment 2 -->
<foo>foo 2</foo>
<!-- comment 3 -->
</root>
A first attempt to achieve that with LINQ to XML could look as follows:
XDocument doc1 = XDocument.Load(@"XMLFile1.xml");
XDocument doc2 =
new XDocument(
new XElement(doc1.Root.Name,
doc1
.Root
.Nodes()
.Except(
doc1
.Root
.Elements("bar"))));
doc2.Save(Console.Out);
So we create a new XDocument with a new root XElement having the same name as the Root of the first XDocument where all child nodes except of the 'bar' child elements are copied.
Looks nice and straightforward only if you try to compile that with Visual Studio 2008/.NET 3.5 you get the following error: "Argument '2': cannot convert from 'System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>' to 'System.Collections.Generic.IEnumerable<System.Xml.Linq.XNode>'".
The problem is that the Nodes() call returns an IEnumerable<XNode> and then the following Except() call also needs an IEnumerable<XNode> as its argument while Elements("bar") gives us an IEnumerable<XElement>. With generic interfaces being invariant in .NET 3.5 we can't pass that IEnumerable<XElement> in for an IEnumerable<XNode>, although XElement is a class derived from XNode.
As a workaround we can first cast the IEnumerable<XElement> to an IEnumerable<XNode>:
XDocument doc1 = XDocument.Load(@"XMLFile1.xml");
XDocument doc2 =
new XDocument(
new XElement(doc1.Root.Name,
doc1
.Root
.Nodes()
.Except(
doc1
.Root
.Elements("bar")
.Cast<XNode>())));
doc2.Save(Console.Out);
That way it compiles fine and produces the wanted result with .NET 3.5, only it seems desirable that you would not need that Cast<XNode>() call.
The good news is that starting with .NET 4.0 the type parameter T of IEnumerable<T> is covariant meaning where an IEnumerable<T> of a certain type T is expected we can always pass in an IEnumerable<T2> where T2 is type derived from T, as in our example where XElement is a subclass of XNode (or subsubclass to be precise).
Thus with .NET 4.0 the following compiles and works fine:
XDocument doc1 = XDocument.Load(@"XMLFile1.xml");
XDocument doc2 =
new XDocument(
new XElement(doc1.Root.Name,
doc1
.Root
.Nodes()
.Except(
doc1
.Root
.Elements("bar"))));
doc2.Save(Console.Out);