Reputation: 1481
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
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 Id
s and for each Id there is a list of ItemOption
s with the same Id.
Upvotes: 0
Reputation: 7744
Its because class String cannot be inherited, but your class ItemOption can be inherited and also can be casted implicitly.
Upvotes: 1
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