LINQ: Cast and OfType

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.

Advertisements
Post a comment or leave a trackback: Trackback URL.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: