views:

55

answers:

3

Working with interfaces, we commonly have a var or an IQueryable that is going to return a set of data objects that we will then cast to the interface and return as a List or IList, like this:

var items =
from t in SomeTable
where t.condition == true
select;
return items.ToList( ).Cast<SomeInterface>( ).ToList( );

NOTE: items.Cast( ).ToList( ) will compile, but will throw an InvalidCastException at run time.

Is there a better way? (I put the ToList/Cast/ToList in an extension method, but that's not really any better...)

return items.CastToList<SomeClass, SomeInterface>( );

Thanks!

+1  A: 

What's the point of the first ToList() call? Why not just items.Cast<SomeInterface>().ToList()?

Alternately, you could do this instead:

var items = from t in SomeTable
            where t.condition == true
            select (SomeInterface) t;
return items.ToList();

If t doesn't implement SomeInterface it will still fail at runtime, though.

This will do the same thing, but won't fail; rather it will give you null if the object doesn't implement the interface:

var items = from t in SomeTable
            where t.condition == true
            select t as SomeInterface;
return items.ToList();
Ian Henry
The question explains the first call to ToList, and there is good reason for it (although it should be AsEnumerable, not ToList). Calling Cast directly on the IQueryable would cause it to be executed on the DB, which can't work
Thomas Levesque
+2  A: 

What you're doing is correct (you can't call Cast directly on the IQueryable), but it's sub-optimal, because you're creating 2 lists when you really only need one. Use AsEnumerable instead of the first ToList :

return items.AsEnumerable().Cast<SomeInterface>().ToList();

Calling AsEnumerable will cause Cast to be evaluated on a IEnumerable, not IQueryable, so the cast will be done on the actual object returned from the DB, rather than directly in the DB.

Thomas Levesque
That's what I was hoping for, to avoid creating both lists.
Kelly
A: 

A few solutions...

Instead of converting data after collecting it, you could consider make a projection while collecting it.

return items.Select(x=>x as SomeInterface).ToList();

If you do this frequently, an extension method may still be helpful. It doesn't have to be Tolist/Cast/ToList as you mentioned though. Cast will take an IEnumerable already, but you can create an overload that accepts an IQueryable, something like this (untested).

public IEnumerable<TResult> Cast<T, TResult>(this IQueryable<T> input)
{
    return input.AsEnumerable().Cast<TResult>();
}

This would whittle your code down to this:

return items.Cast<SomeClass, SomeInterface>.ToList();
Phil Gilmore
We were close! But since there is a cast involved, I needed a <T1> and a <T2>. Thanks!
Kelly
Yep, you're right. Edited the code to reflect this.
Phil Gilmore