All the LINQ operations we’ve seen so far have worked on lists of type IEnumerable<T>, where T is the data type of the objects in the list. This is fine for most of the current data types in C#, such as the generic List<T> and the C# array. However, some older data types, such as the ArrayList, do not implement IEnumerable<T>; rather they implement the older, non-generic IEnumerable interface. If we want to use these older data types with LINQ, we must convert them to IEnumerable<T>.
There are two methods that can be used to do this: Cast<T> and OfType<T>. Let’s look at Cast<T> first.
Using our list of Canadian prime ministers, we can call the method that returns an ArrayList instead of an array. To apply LINQ operators to this list, we need to cast it first:
ArrayList pmArrayList01 = PrimeMinisters.GetPrimeMinistersArrayList(); var sorted = pmArrayList01.Cast<PrimeMinisters>().OrderBy(pm => pm.lastName); Console.WriteLine("*** cast ArrayList"); foreach (var pm in sorted) { Console.WriteLine("{0}, {1}", pm.lastName, pm.firstName); }
If we tried to call OrderBy() directly on pmArrayList01, we would find that the code wouldn’t compile. If you’re using Visual Studio’s Intellisense, you’ll also notice that most of the LINQ functions don’t show up in the list anyway. The problem is that the ArrayList is not an IEnumerable<T>.
We call Cast<PrimeMinisters> on this list first, followed by a call to OrderBy() to sort the list by last name. Thus the general rule is that the object calling Cast<T> must implement IEnumerable, and the output from Cast<T> is of type IEnumerable<T>.
With this code, we get the expected output:
Abbott, John Bennett, Richard Borden, Robert Bowell, Mackenzie Campbell, Kim Chrétien, Jean Clark, Joe Diefenbaker, John Harper, Stephen Laurier, Wilfrid Macdonald, John Mackenzie, Alexander Mackenzie King, William Martin, Paul Meighen, Arthur Mulroney, Brian Pearson, Lester St. Laurent, Louis Thompson, John Trudeau, Pierre Tupper, Charles Turner, John
Now, an ArrayList can store items of any data type (it’s defined to accept the generic ‘object’ type), so we could mix things up a bit and add some ordinary strings onto the end of the list of prime ministers. That is, we could try something like adding this code after that above:
pmArrayList01.Add("A string item"); pmArrayList01.Add("Isn't this interesting?"); pmArrayList01.Add("End of list"); sorted = pmArrayList01.Cast<PrimeMinisters>().OrderBy(pm => pm.lastName); foreach (var pm in sorted) { Console.WriteLine("{0}, {1}", pm.lastName, pm.firstName); }
There’s an obvious problem in that the three strings we’ve added at the end don’t have a firstName and lastName field, so we wouldn’t expect the code to run anyway. However, we find that code does in fact compile without errors. If we try to run it, we get the following error:
Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.String' to type 'LinqObjects01.PrimeMinisters'.
The problem is that the Cast<PrimeMinisters> method requires that all elements in the list passed to it are of type PrimeMinisters, and it throws an InvalidCastException if any elements in the input list aren’t of the correct type.
There is one important point about Cast<T>: remember that it is a deferred operator, so it isn’t actually executed until an attempt is made to enumerate its output. That is, if we omit the foreach loop in the above code, but retain the (erroneous) call to Cast<PrimeMinisters>, the code will compile and run, seemingly without errors, since we haven’t attempted to enumerate the ‘sorted’ object. The actual exception is thrown only in the foreach loop when we try to enumerate the elements of ‘sorted’.
If we want to handle lists that contain mixed types, we can use the OfType<T> method instead. This method accepts input IEnumerable objects containing any mixture of types, and looks for those of type T. It will add these objects to its output list and ignore any objects that aren’t of type T. So we can try the following on our mixed ArrayList:
pmArrayList01.Add("A string item"); pmArrayList01.Add("Isn't this interesting?"); pmArrayList01.Add("End of list"); var sorted = pmArrayList01.OfType<PrimeMinisters>().OrderBy(pm => pm.lastName); foreach (var pm in sorted) { Console.WriteLine("{0}, {1}", pm.lastName, pm.firstName); } var sortedStrings = pmArrayList01.OfType<string>().OrderBy(pm => pm); foreach (var pm in sortedStrings) { Console.WriteLine(pm); }
After adding the strings, we first call OfType<PrimeMinisters> and pass the result to OrderBy. The OfType call will look only for elements in the ArrayList of type PrimeMinisters, and ignore the string objects. Thus the list passed to OrderBy contains only the correct type, and the ordering and subsequent foreach loop both work properly. The results of this foreach loop are the same as with our original Cast above.
In the last bit of code, we use OfType<string>, which throws away all the PrimeMinisters objects and saves the three strings. Of course, we have to change the predicate in OrderBy so it operates on a simple string rather than a PrimeMinisters object, and similarly for the WriteLine() in the foreach loop. The output of this final loop is:
A string item End of list Isn't this interesting?
The Cast and OfType operators can also be applied to IEnumerable lists. Cast isn’t much use in this regard, since if we start off with an IEnumerable, we don’t need to convert it to the same list. However, OfType is useful as a filter, since it can be used to create a list of a specific data type from a more generic starting list.
For example, if we create an (somewhat contrived, admittedly) array of type ‘object’ which contains both PrimeMinisters objects and strings, by putting the following method in our PrimeMinisters class:
public static object[] GetObjectArray() { object[] pmArray = new object[GetPrimeMinistersArrayList().Count + 3]; object[] temp = (object[])GetPrimeMinistersArrayList().ToArray(typeof(PrimeMinisters)); for (int i = 0; i < temp.Count(); i++) { pmArray[i] = temp[i]; } pmArray[pmArray.Count() - 3] = "String 1"; pmArray[pmArray.Count() - 2] = "String 2"; pmArray[pmArray.Count() - 1] = "String 3"; return pmArray; }
We can isolate the PrimeMinisters objects by using OfType on the object[] array (remember that a C# array is an IEnumerable<T>).
object[] pmArray01 = PrimeMinisters.GetObjectArray(); var sortedArray = pmArray01.OfType<PrimeMinisters>().OrderBy(pm => pm.lastName); foreach (var pm in sortedArray) { Console.WriteLine("{0}, {1}", pm.lastName, pm.firstName); }
Finally, it’s worth noting that there is a third conversion operator called AsEnumerable<T> which does take an IEnumerable<T> as input and produces another IEnumerable<T> as output. Although this may seem pointless, it’s actually essential when we deal with databases. But we’ll leave that until we consider the use of LINQ with databases.