Linq Casting Issues in C#

Posted in software by Christopher R. Wirz on Thu Aug 04 2011



Language Integrated Query is a Microsoft .NET Framework component that adds native data querying capabilities to .NET languages, originally released as a major part of .NET Framework 3.5 in 2007. LINQ provides language-level querying capabilities and a higher-order function API to C# and VB as a way to write expressive, declarative code. For a significant chunk of software out in the wild, everything revolves around dealing with data from some source (Databases, JSON, XML, etc). Often this involves learning a new API for each data source, which can be annoying. LINQ simplifies this by abstracting common elements of data access into a query syntax which looks the same no matter which data source you pick.

Note: Syntax may significantly change in later C# versions

While LINQ is a lot more concise, it has specific applications for each of its statements. One common mistake is between Cast and OfType. Cast will cast each element in the collection. OfType filters the collection to return elements of a type of having the base type.

Consider the following example...


using System;
using System.Collections.Generic;
using System.Linq;

// A common base class
public class MyBase {
    public int Id {get; set;} = 0;
}
// Now for the derived classes
public class MyDerived : MyBase {

}
public class MyParallelDerived : MyBase {

}

class Program {
  static void Main() {
    var l = new List<MyBase>();
    for (int i = 0; i < 40; i++){
        switch (i%4){
            case 1:
                l.Add(new MyDerived(){Id = i}); break;
            case 2:
                l.Add(new MyParallelDerived(){Id = i}); break;
            case 3:
                l.Add(null); break;
            case 0:
            default:
                l.Add(new MyBase(){Id = i}); break;
        }
    }
    Console.WriteLine("Full List " + l.Count());
    Console.WriteLine("OfType<MyBase> " + l.OfType<MyDerived>().Count());
    Console.WriteLine("OfType<MyDerived> " + l.OfType<MyDerived>().Count());
    Console.WriteLine("OfType<MyParallelDerived> " + l.OfType<MyParallelDerived>().Count());
    Console.WriteLine("Select(s => s as MyBase) " + l.Select(s => s as MyBase).Count());
    Console.WriteLine("Where(s => s is MyBase) " + l.Where(s => s is MyBase).Count());
    try {
        Console.WriteLine("Cast<MyBase>() " + l.Cast<MyBase>().Count());
    }
    catch (Exception ex){
        Console.WriteLine("Cast<MyBase>() produced error " + ex.Message);
    }
    Console.WriteLine("Select(s => s as MyDerived) " + l.Select(s => s as MyDerived).Count());
    Console.WriteLine("Where(s => s is MyDerived) " + l.Where(s => s is MyDerived).Count());
    try {
        Console.WriteLine("Cast<MyDerived>() " + l.Cast<MyDerived>().Count());
    }
    catch (Exception ex){
        Console.WriteLine("Cast<MyDerived>() produced error " + ex.Message);
    }
  }
}

The intention of this test is to create a collection of a base class, two derived classes, and null values. This is done by creating one list containing 10 of each type.

So let's look at the results:


Full List 40
OfType<MyBase> 10
OfType<MyDerived> 10
OfType<MyParallelDerived> 10
Select(s => s as MyBase) 40
Where(s => s is MyBase) 30
Cast<MyBase>() 40
Select(s => s as MyDerived) 40
Where(s => s is MyDerived) 10
Cast<MyDerived>() produced error Specified cast is not valid.

As seen, the correct number of classes appears in the list. The interesting operator is the cast operator and the Select as statement. The results make sense because if a value won't cast to a type, null is returned. As the collection can contain null values, so can the results.

What's next? Try it yourself.