Linq - Casting IQueryable to IList returns null - WHY?

12,819

Solution 1

The problem here is one of covariance.

First, your example is a bit too complicated. I have removed some fluff. Also, I've added some diagnostics that illuminate the problem.

class Program
{
    static void Main(string[] args)
    {
        var names = new[] { "Tom", "Dick", "Harry", "Mary", "Jay" };

        var query = from n in names
                    select new C
                    {
                        S = n
                    };

        //There IS data in the query result
        Debug.Assert(query != null, "result is null");

        //but the conversion here makes it return null
        var list = query.ToList() as IList<I>;
        Console.WriteLine(query.ToList().GetType());

        // this assert fires.
        Debug.Assert(list != null, "Cast List is null");
    }
}

interface I
{
    string S { get; set; }
}

class C : I
{
    public string S { get; set; }
}

The output of this program is:

System.Collections.Generic.List`1[C]

Note that we're trying to cast a List<C> to List<I> which doesn't work in C# 3.0.

In C# 4.0 you should be able to do this, thanks to the new co- and contra-variance of type parameters on generic interfaces.

Also, your original question asked about IQueryable but that's not relevant here: the query expression you supplied creates an IEnumerable<string> not an IQueryable<string>.

EDIT: I want to point out that your "cast" using the as operator is technically not a cast, but is a "type conversion". If you had use a cast, you would have gotten an exception with useful information. If I change to:

    var list = (IList<I>)query.ToList();

I get an InvalidCastException with:

Additional information: Unable to cast object of type 'System.Collections.Generic.List1[C]' to type 'System.Collections.Generic.IList1[I]'.

Solution 2

Try this:

var query = from n in names
            where n.Contains("a")
            select new MyDataClass
            {
              Name = n.ToString()
            } as IMyDataInterface;

Your problem is in this line:

IList<IMyDataInterface> list = query.ToList() as IList<IMyDataInterface>;

This can also be written as:

List<MyDataClass> tmp = query.ToList();
IList<IMyDataInterface> list = tmp as IList<IMyDataInterface>;

Unfortunately, in C# the as operator does not work in the way you want it to. The as operator simply casts the list object as a list of a different type; it does not attempt to go through the list and cast every item. To cast a list to something else, you need to call the Cast extension method on it. E.g.:

IList<IMyDataInterface> list = query.ToList().Cast<IMyDataInterface>();

So, your options are: cast ever item in your query as the interface you want (my first example) or cast the entire list after you've executed your query (my second example).

I suggest the former.

Share:
12,819
jlembke
Author by

jlembke

Consultant developer for a large manufacturing company.

Updated on June 05, 2022

Comments

  • jlembke
    jlembke almost 2 years

    I have to be missing something obvious here. I don't get why this cast of the results of a linq query returns null and not the typed list I'm requesting.

    IList<IMyDataInterface> list = query.ToList() as IList<IMyDataInterface>;
    

    The complete code to run this is below. This is a knowledge gap I need to bridge. I have tried all kinds of permutations of casts to get it to work. I get no Exceptions, just a null. Of note is that the Linq query is selecting its results into instances of my custom "MyDataClass" which implements IMyDataInterface

    class Program
    {
        static void Main(string[] args)
        {
            IMyFunctionalInterface myObject = new MyClass();
    
    
            //myObject.Get() returns null for some reason...
            IList<IMyDataInterface> list = myObject.Get();
    
            Debug.Assert(list != null, "Cast List is null");
        }
    }
    
    public interface IMyFunctionalInterface
    {
        IList<IMyDataInterface> Get();
    }
    
    public class MyClass : IMyFunctionalInterface
    {
        public IList<IMyDataInterface> Get()
        {
            string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
    
            var query = from n in names
                        where n.Contains("a")
                        select new MyDataClass
                        {
                            Name = n.ToString()
                        };
    
            //There IS data in the query result
            Debug.Assert(query != null, "result is null");
            //but the cast here makes it return null
            IList<IMyDataInterface> list = query.ToList() as IList<IMyDataInterface>;
    
            return list;
        }
    
    }
    public interface IMyDataInterface
    {
        string Name { get; set; }
    }
    
    public class MyDataClass : IMyDataInterface
    {
        public string Name { get; set; }
    }
    
  • Admin
    Admin over 15 years
    I'm surprised the cast in approach 1 works. Every time I try anything like that, I get the same problem described in the question. The only solution I've found is the Cast<>() extension method, which can be called either before or after .ToList().
  • Randolpho
    Randolpho over 15 years
    I tested before I posted. The cast works in approach 1 because the select portion can take any valid C# construction expression.
  • Rafa Castaneda
    Rafa Castaneda over 14 years
    Note that in C# 4.0 covariance/contravariance applies only to interfaces and delegates, so the cast of List<C> to List<I> in C# 4.0 won't work either, and that's right.
  • Jan Aagaard
    Jan Aagaard about 14 years
    I am not sure I understand this answer. Should the type conversion / cast work in C# 4.0? (Note: I get the InvalidCastException even though I am using C# 4.0.)
  • Jay Bazuzi
    Jay Bazuzi about 14 years
    (I've been away from C# for a while, so I'm a little fuzzy.) C# 4.0 adds co- and contra-variance for interface type parameters. The NDP annotated a few interfaces for co-and contra-variance. For example, in my Beta 2 build, I see that IEnumerable is now out T, but IList is not. So in the above code I can write query.ToList() as IEnumerable<I> and it works (non-null).