1.10. Using LINQ with Collections That Don't Support IEnumerable<T>

Problem

There are a whole bunch of collections that don't support the generic versions of IEnumerable or ICollection but that do support the original nongeneric versions of the IEnumerable or ICollection interfaces, and you would like to be able to query those collections using LINQ.

Solution

The type cannot be inferred from the original IEnumeration or ICollection interfaces, so it must be provided using either the OfType<T> or Cast<T> extension methods or by specifying the type in the from clause, which inserts a Cast<T> for you. The first example uses Cast<XmlNode> to let LINQ know that the elements in the XmlNodeList returned from XmlDocument.SelectNodes are of type XmlNode. For an example of how to use the OfType<T> extension method, see the Discussion section:

	// Make some XML with some types that you can use with LINQ
	// that don't support IEnumerable<T> directly
	XElement xmlFragment = new XElement("NonGenericLinqableTypes",
	                        new XElement("IEnumerable",
	                            new XElement("System.Collections",
	                                new XElement("ArrayList"),
	                                new XElement("BitArray"),
	                                new XElement("Hashtable"),
	                                new XElement("Queue"),
	                                new XElement("SortedList"),
	                                new XElement("Stack")),*
	                            new XElement("System.Net",
	                                new XElement("CredentialCache")),
	                            new XElement("System.Xml",
	                                new XElement("XmlNodeList")),
	                            new XElement("System.Xml.XPath",
	                                new XElement("XPathNodeIterator"))),
	                        new XElement("ICollection",
	                            new XElement("System.Diagnostics",
	                                new XElement("EventLogEntryCollection")),
	                            new XElement("System.Net",
	                                new XElement("CookieCollection")),
	                            new XElement("System.Security.AccessControl",
	                                new XElement("GenericAcl")),
	                            new XElement("System.Security",
	                                new XElement("PermissionSet"))));

	XmlDocument doc = new XmlDocument();
	doc.LoadXml(xmlFragment.ToString());

	// Select the names of the nodes under IEnumerable that have children and are
	// named System.Collections and contain a capital S and return that list in
	descending order
	var query = from node in doc.SelectNodes("/NonGenericLinqableTypes/IEnumerable/*").
	Cast<XmlNode>()
	            where node.HasChildNodes &&
	                node.Name == "System.Collections"
	            from XmlNode xmlNode in node.ChildNodes
	            where xmlNode.Name.Contains('S')
	            orderby xmlNode.Name descending
	            select xmlNode.Name;

	foreach (string name in query)
	{
	    Console.WriteLine(name);
	}

The second example works against the Application event log and retrieves the errors that occurred in the last 6 hours. The type of the element in the collection (EventLogEntry) is provided next to the from keyword, which allows LINQ to infer the rest of the information it needs about the collection element type:

	EventLog log = new EventLog("Application");
	var query = from EventLogEntry entry in log.Entries
	            where entry.EntryType == EventLogEntryType.Error &&
	                  entry.TimeGenerated > DateTime.Now.Subtract(new TimeSpan(6, 0, 0))
	            select entry.Message;

	Console.WriteLine("There were " + query.Count<string>() +
	    " Application Event Log error messages in the last 6 hours!");
	foreach (string message in query)
	{
	    Console.WriteLine(message);
	}

Discussion

Cast<T> will transform the IEnumerable into IEnumerable<T> so that LINQ can access each of the items in the collection in a strongly typed manner. Before using Cast<T>, it would behoove you to check that all elements of the collection really are of type T, or you will get an InvalidCastException if the type of the element is not convertible to the type T specified, because all elements will be cast using the type. Placing the type of the element next to the from keyword acts just like a Cast<T>:

	ArrayList stuff = new ArrayList();
	stuff.Add(DateTime.Now);
	stuff.Add(DateTime.Now);
	stuff.Add(1);
	stuff.Add(DateTime.Now);

	var expr = from item in stuff.Cast<DateTime>()
	           select item;
	// attempting to cast the third element throws InvalidCastException
	foreach (DateTime item in expr)
	{
	    Console.WriteLine(item);
	}

Tip

Note that again because of the deferred execution semantics that the exception that occurs with Cast<T> or from only happens once that element has been iterated to.

Another way to approach this issue would be to use OfType<T>, as it will only return the elements of a specific type and not try to cast elements from one type to another:

	var expr = from item in stuff.OfType<DateTime>()
	           select item;
	// only three elements, all DateTime returned. No exceptions
	foreach (DateTime item in expr)
	{
	    Console.WriteLine(item);
	}

See Also

The "OfType<TResult> method" and "Cast<TResult> method" topics in the MSDN documentation.

Get C# 3.0 Cookbook, 3rd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.