IndigoDelta
IndigoDelta

Reputation: 1481

ForEach giving Invalid Cast at Runtime

I have the following:

 foreach (ItemOption itemOption in p.Items.Select(e => e.ItemOption).GroupBy(e => e.Id))
  { // do some work on itemoptions }

This compiles. However at runtime I get an Invalid Cast exception:

Unable to cast object of type 'Grouping[System.String,MyNameSpace.ItemOption]' to type 'MyNameSpace.ItemOption'.

If I change the code to, e.g. a String as the type of the item :

 foreach (String itemOption in p.Items.Select(e => e.ItemOption).GroupBy(e => e.Id))
  { // do some work on itemoptions }

Then the compiler tells me the types are incompatible.

Why doesn't the compiler flag the type incompatibility in the first code block?


I did some further investigation and found that, given the following code:

var foo = p.Items.Select(e => e.ItemOption).GroupBy(e => e.Id));
Type singleElementType = foo.ElementAt(0).GetType();

singleElementType is:

System.Linq.Lookup`2+Grouping[System.String, MyNamespace.ItemOption]


UPDATE Following on from the answers I have put together a simpler case to demonstrate the issue

Given the objects:

interface IMyObj
{
    string Id;
}

class MyObj : IMyObj
{
    public string Id;
    public MyObj2 cg;
}

class MyObj2
{
}

This will fail at compile time

IEnumerable<MyObj> compileTimeFailList = new List<MyObj>()
foreach (MyObj2 myObj2 in compileTimeFailList.Where(x => x.Id != null))
{

and this will fail at run time

IEnumerable<IMyObj> runtimeFailList = new List<IMyObj>();
foreach (MyObj2 myObj2 in runtimeFailList.Where(x => x.Id != null))
{ 

The reason being that the objects in runtimeFailList may extend MyObj2 and this cannot be determined by the compiler.

Upvotes: 3

Views: 961

Answers (3)

Hans Kesting
Hans Kesting

Reputation: 39284

About why the cast fails:

foreach (ItemOption itemOption in p.Items.Select(e => e.ItemOption).GroupBy(e => e.Id))

Your .GroupBy doesn't return a list of ItemOptions, bunched by id. Rather it returns a list-of-lists: the first list contains all separate Ids and for each Id there is a list of ItemOptions with the same Id.

Upvotes: 0

Sergey Vedernikov
Sergey Vedernikov

Reputation: 7744

Its because class String cannot be inherited, but your class ItemOption can be inherited and also can be casted implicitly.

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1500375

ItemOption is presumably not a sealed class (unlike System.String) so it's possible that the result of p.Items.Select(...).GroupBy(...) would be implementations of IGrouping<...> which were also ItemOption values. The compiler can't know, so it inserts an implicit cast. As string doesn't implement IGrouping<...> and is sealed, the compiler can spot that that's definitely a mistake.

foreach has always included a cast where necessary. It's a bit nasty, because it's hidden... but without it, foreach would have been very painful to use pre-generics.

Now as for why it's actually wrong... each item in the result is going to be a grouping rather than an individual item. Let me know if you need more help working with the results.

Upvotes: 3

Related Questions