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.
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); }
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);
}
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.