Linq - Casting IQueryable to IList returns null - WHY?
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.IList
1[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.
Comments
-
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 over 15 yearsI'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 over 15 yearsI tested before I posted. The cast works in approach 1 because the select portion can take any valid C# construction expression.
-
Rafa Castaneda over 14 yearsNote 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 about 14 yearsI 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 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 nowout T
, butIList
is not. So in the above code I can writequery.ToList() as IEnumerable<I>
and it works (non-null).