Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# foreach on IEnumerable<IList<object>> compiles but shouldn't

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.

like image 524
Emaborsa Avatar asked Nov 30 '18 14:11

Emaborsa


Video Answer


4 Answers

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.

like image 122
Pinx0 Avatar answered Nov 01 '22 09:11

Pinx0


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).

like image 37
Richard Avatar answered Nov 01 '22 09:11

Richard


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.

...

like image 31
Sweeper Avatar answered Nov 01 '22 08:11

Sweeper


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
}
like image 20
Theraot Avatar answered Nov 01 '22 10:11

Theraot