I have the following code:
IEnumerable<IList<MyClass>> myData = //...getMyData
foreach (MyClass o in myData)
{
// do something
}
It compiles, runs and obviously I get an System.InvalidCastException
.
Why does the compiler not complain? MyClass
is a simple bean, no extensions.
Edit 1:
As suggested by David switching the type from IList
to List
the compiler complains
Edit 2:
I've understood that the behaviour is as specified in the C# Language definition. However, I don't understand why such a cast/conversion is allowed, since at runtime I always get an InvalidCastException. I opened this in order to go deeper.
IList<MyClass>
is convertible to MyClass
.
But if you actually run it with a non-empty enumerable,
IEnumerable<IList<MyClass>> myData = new IList<MyClass>[1] { new List<MyClass>() {new MyClass()}};
You get this error:
Unable to cast object of type 'System.Collections.Generic.List`1[MyClass]' to type 'MyClass'.
This is in compliance with the spec:
Section 8.8.4 The foreach statement
... If there is not an explicit conversion (§6.2) from T (the element type) to V (the local-variable-type in the foreach statement), an error is produced and no further steps are taken.
...
There is an explicit conversion from IList<MyClass>
to MyClass
(though it will fail at runtime), so no error is produced.
Section 6.2.4 Explicit reference conversions
The explicit reference conversions are:
- From object and dynamic to any other reference-type.
- From any class-type S to any class-type T, provided S is a base class of T.
- From any class-type S to any interface-type T, provided S is not sealed and provided S does not implement T.
- From any interface-type S to any class-type T, provided T is not sealed or provided T implements S.
...
Well because IList<MyClass>
is an interface so theoretically you could have a class that implemented that interface AND derives from MyClass
.
If you change it to IEnumerable<List<MyClass>>
it will not compile.
In any case, at least I'm getting a warning for suspicious cast, as there is no class in the solution which inherits from both IList<MyClass>
and MyClass
.
When a foreach
is compiled it follows a pattern not specific types (much as LINQ and await
do).
foreach
isn't looking for an IEnumerable
or IEnumerable<T>
but for a type which has a GetEnumerator()
method (which IList<T>
does). And the objects in the outer list could be of a type derived from MyClass
and implementing IList<T>
).
Ie. the compiler does a lightweight "matches the pattern" check not a complete check.
See §8.8.3 of the C#5 Language Specification which covers this in detail (and you'll see I've rather simplified things above: even IEnumerator
isn't checked for, just that there is a MoveNext()
method and a Current
property).
Under the assumption that MyClass
does not implement IList<MyClass>
, there could be a derived type of MyClass
that does implement IList<MyClass>
and then your loop would be valid.
That is,
class MyClass
{
}
class Derived : MyClass, IList<MyClass>
{
// ...
}
// ...
// Here IList<MyClass> is Derived, which is valid because Derived implements IList<MyClass>
IEnumerable<IList<MyClass>> myData = new []{new Derived()};
// Here MyClass is Derived, which is valid because Derived inherits from MyClass
foreach (MyClass o in myData)
{
// do something
}
来源:https://stackoverflow.com/questions/53559287/c-sharp-foreach-on-ienumerableilistobject-compiles-but-shouldnt