Exploiting contravariance with LINQ to XML
Covariance and contravariance for generic interfaces are new features in C# and VB.NET in Visual Studio 2010 respectively the .NET framework 4.0. Generic interfaces like IEnumerable<T> or IEqualityComparer<T> in the .NET framework 4.0 use these new features. Starting with .NET 4.0 the type parameter T in IEqualityComparer<T> is contravariant. That can make coding with LINQ to XML easier, as the class XNodeEqualityComparer implements IEqualityComparer<XNode> where XNode is a common base class for other LINQ to XML classes like XElement.
Let's look at an example. Assume we have the following XML document
<?xml version="1.0" encoding="utf-8" ?>
<root>
<items>
<item>
<foo>a</foo>
<bar>1</bar>
</item>
<item>
<foo>b</foo>
<bar>2</bar>
</item>
<item>
<foo>a</foo>
<bar>1</bar>
</item>
<item>
<foo>c</foo>
<bar>3</bar>
</item>
<item>
<foo>c</foo>
<bar>3</bar>
</item>
</items>
</root>
and we want to use LINQ to XML to extract distinct items where we use XNodeEqualityComparer to compare the 'item' elements in the XML document.
You could be tempted to try it as follows:
XDocument doc = XDocument.Load("XMLFile1.xml");
var distinctItems =
doc
.Root
.Element("items")
.Elements("item")
.Distinct(new XNodeEqualityComparer())
.Select(i => new { foo = (string)i.Element("foo"), bar = (int)i.Element("bar") });
foreach (var item in distinctItems)
{
Console.WriteLine(item);
}
but with .NET 3.5 that does not compile, complaining "Instance argument: cannot convert from 'System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>' to 'System.Collections.Generic.IEnumerable<System.Xml.Linq.XNode>'" on the Distinct(new XNodeEqualityComparer()) call. That happens because Elements("item") gives us an IEnumerable<XElement> and subsequently the Distinct method wants an IEqualityComparer<XElement> to be passed in while we only pass in an IEqualityComparer<XNode>.
With .NET 3.5 to work around that problem we first have to cast IEnumerable<XElement> up to IEnumerable<XNode> before we call Distinct(new XNodeEqualityComparer()) and then down again after the Distinct() call:
XDocument doc = XDocument.Load("XMLFile1.xml");
var distinctItems =
doc
.Root
.Element("items")
.Elements("item")
.Cast<XNode>()
.Distinct(new XNodeEqualityComparer())
.Cast<XElement>()
.Select(i => new { foo = (string)i.Element("foo"), bar = (int)i.Element("bar") });
foreach (var item in distinctItems)
{
Console.WriteLine(item);
}
That compiles fine and nicely returns only distinct items:
{ foo = a, bar = 1 }
{ foo = b, bar = 2 }
{ foo = c, bar = 3 }
With .NET 4.0 however the type parameter T of IEqualityComparer is contravariant meaning if we have a method expecting an IEqualityComparer<XElement> it suffices to use a base type of XElement like XNode and thus with .NET 4.0 our original attempt compiles and runs fine:
XDocument doc = XDocument.Load("XMLFile1.xml");
var distinctItems =
doc
.Root
.Element("items")
.Elements("item")
.Distinct(new XNodeEqualityComparer())
.Select(i => new { foo = (string)i.Element("foo"), bar = (int)i.Element("bar") });
foreach (var item in distinctItems)
{
Console.WriteLine(item);
}