开发者

Linq - InvalidCastException - Why does "where" not filter the invalid types

Had a problem in a complex linq query so I simplified it in LINQPad:

void Main()
{
    List<basetype> items = new Li开发者_Go百科st<basetype>()
    {
        new typeA() { baseproperty = "1", extendedproperty = 1 },
        new typeB() { baseproperty = "2", extendedproperty = 1.1 },
        new typeA() { baseproperty = "3", extendedproperty = 1 },
    };

    items.Dump();

    (from typeA item in items
     where item is typeA
     select item).Dump();
}

public abstract class basetype
{
    public string baseproperty { get; set; }
    public string type { get; set; }
}

public class typeA : basetype
{
    public int extendedproperty { get; set; }
    public typeA() { type = "A"; }
}

public class typeB : basetype
{
    public double extendedproperty { get; set; }
    public typeB() { type = "B"; }
}

The first Dump works fine and returns:

extendedproperty baseproperty type 
1                1            A
1.1              2            B
1                3            A

However the second Dump errors with:

InInvalidCastException: Unable to cast object of type 'typeB' to type 'typeA'.

I can fix this by just removing the "typeA" but I wouldn't want to do that in the original statement as I would have to cast the type all over the place:

from item in items

Interestingly enough, moving the where also fixes this though you might agree that's a bit ugly:

from typeA item in items.Where(i => i is typeA)

My question is: why is the original where not filtering out the invalid item before the cast is evaluated?


Reason #1:

The cast to the type happens before the filter because it comes to the left. In C# it is almost always the case that the thing to the left happens before the thing to the right.

Reason #2:

Suppose we did it your way. You have a List<object> and you say

from string s in myObjects where s.Length > 100

and you get an error saying that object doesn't have a Length property - because of course with your way the cast to string happens after the filter, and therefore the filter cannot depend upon the invariant determined by the cast. Presumably you put the cast in there because you want to use the properties of the target type. But you can't have it both ways; either the left operation runs first or the right operation runs first. They can't both run first.

Reason #3:

There already is a way to do what you want:

... from foos.OfType<Bar>() ...

That is equivalent to filtering first and then providing a sequence of just the filtered values of the right type.


why is the original where not filtering out the invalid item before the cast is evaluated?

Before the where runs, the from must run.

(from typeA item in items 

You have inadvertently cast in your from expression. Remove the TypeA (it is optional) from from and you'll be all set. This is the same as the implicit cast in a foreach statement:

foreach(TypeA item in items) //will throw for same reason
{
  if (item is TypeA)

I can fix this by just removing the "typeA" but I wouldn't want to do that in the original statement as I would have to cast the type all over the place:

You can use either of these solutions:

(items.OfType<TypeA>()).Dump();

(from item in items
where item is TypeA
let itemA = item as TypeA
select itemA).Dump();


…in addition to the other answers, note that you can cast the result set in one shot (instead of "all over the place" using

.Cast<typeA>()

Example:

class Program
{
    static void Main(string[] args)
    {
        var list = new List<BaseType> {new TypeA(), new TypeB()};
        IEnumerable<TypeA> results = list.Where(x => x is TypeA).Cast<TypeA>();
        Console.WriteLine("Succeeded. Press any key to quit.");
        Console.ReadKey();
    }

    public class BaseType{}
    public class TypeA : BaseType {}
    public class TypeB : BaseType {}
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜