jlembke
jlembke

Reputation: 13517

Linq - Casting IQueryable to IList returns null - WHY?

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

Upvotes: 2

Views: 13018

Answers (2)

Jay Bazuzi
Jay Bazuzi

Reputation: 46496

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]'.

Upvotes: 13

Randolpho
Randolpho

Reputation: 56391

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.

Upvotes: 5

Related Questions