IEnumerable<IList<object>> 上的 C# foreach 编译但不应该

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

我有以下代码:

IEnumerable<IList<MyClass>> myData = //...getMyData

foreach (MyClass o in myData)
{
    // do something
}

它编译、运行,显然我得到一个 System.InvalidCastException
为什么编译器不抱怨? MyClass 是一个简单的 bean,没有扩展。

编辑 1:
正如 David 所建议的那样,将类型从 IList 切换到 List 编译器会抱怨

编辑 2:
我了解该行为是在 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 中指定的,以便更深入。

foreach 被编译时,它遵循一种模式而不是特定类型(就像 LINQ 和 await 所做的那样)。

foreach 不是在寻找 IEnumerableIEnumerable<T>,而是在寻找具有 GetEnumerator() 方法(IList<T> 有)的类型。并且外部列表中的对象可以是从 MyClass 派生并实现 IList<T>).

的类型

即。编译器进行轻量级 "matches the pattern" 检查而不是完整检查。

请参阅 C#5 语言规范的第 8.8.3 节,其中详细介绍了这一点(你会看到我已经相当简化了上面的内容:甚至 IEnumerator 都没有被检查,只是有一个 MoveNext() 方法和一个 Current 属性).

IList<MyClass> 可转换为 MyClass.

但是如果你实际上 运行 它带有一个非空的枚举,

IEnumerable<IList<MyClass>> myData = new IList<MyClass>[1] { new List<MyClass>() {new MyClass()}};

您收到此错误:

Unable to cast object of type 'System.Collections.Generic.List`1[MyClass]' to type 'MyClass'.

这符合规范:

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.

...

那里IList<MyClass>MyClass的显式转换(尽管它会在运行时失败),所以没有错误出品了。

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.

...

好吧,因为 IList<MyClass> 是一个接口,所以理论上你可以有一个 class 实现了该接口并且派生自 MyClass

如果将其更改为 IEnumerable<List<MyClass>>,它将无法编译。

无论如何,至少我收到了可疑转换的警告,因为解决方案中没有继承自 IList<MyClass>MyClass 的 class。

假设 MyClass 没有实现 IList<MyClass>,可能会有一个 MyClass 的派生类型实现了 IList<MyClass>,那么你的循环将是有效的.

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
}