Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting generic type in linq query

So I have class which accepts a generic type parameter and does a little special handling if the type parameter is a subclass of a given type.

IEnumerable<T> models = ...

// Special handling of MySpecialModel
if (filterString != null && typeof(MySpecialModel).IsAssignableFrom(typeof(T)))
{
    var filters = filterString.Split(...);
    models = 
        from m in models.Cast<MySpecialModel>()
        where (from t in m.Tags
               from f in filters 
               where t.IndexOf(f, StringComparison.CurrentCultureIgnoreCase) >= 0
               select t)
              .Any()
        select (T)m;
}

But I'm getting an exception on the last line

Cannot convert type 'MySpecialModel' to 'T'

If I change the code to use as instead of casting, I get this error.

The type parameter 'T' cannot be used with the 'as' operator because it does not have a class type constraint nor a 'class' constraint.

What am I missing here?

Update

This class needs can take any type parameter, including structs and built-in types, so a generic constraint would not be a suitable solution in my case.

like image 325
p.s.w.g Avatar asked Mar 11 '13 21:03

p.s.w.g


3 Answers

Do Select(x => (MySpecialModel)x)

The LINQ Cast<T> method will only work for casting elements to that the element already is (such as a base type, derived type, or interface). It is not intended to cast objects that are able to be cast to a target type. (e.g. new List<int>{1,2,3}.Cast<long>() will throw an exception as well.

The above answer wasn't wrong, but it doesn't address the question.

Just because you have proved with reflection that a generic parameter is bound to a given type, doesn't mean that the compiler knows that it is. In order to make this work, you will need to cast your T instance to a common type (e.g. object), then cast it to the specific type. e.g. (changing the last line in your query to select (T)(object)m should do the trick.

like image 55
smartcaveman Avatar answered Nov 16 '22 02:11

smartcaveman


Try the following

select (T)(object)m;

At runtime you've verified that T is a subtype of MySpecialModel but the compiler doesn't have access to this information at compile time. It just sees an attempted conversion between 2 unrelated types: T and MySpecialModel.

To work around this you need to use object as a middle man. The compiler understands how to convert MySpecialModel to object and to go from object to T.

like image 3
JaredPar Avatar answered Nov 16 '22 02:11

JaredPar


The most straightforward fix is to cast to object first before the cast to T:

select (T)(object)m;

The problem is your check occurs at runtime, but the compiler doesn't know that T must be an instance of MySpecialModel within the if statement. Therefore it just sees you are trying to cast to some arbitrary type T from MySpecialModel which is not safe, hence the error.

like image 1
Lee Avatar answered Nov 16 '22 02:11

Lee