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

对着背影说爱祢 提交于 2019-12-05 05:30:53

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
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!