IEnumerable <ilist <object >>上的C#foreach会编译,但不应编译

时间:2018-11-30 14:18:52

标签: c# generics nested-generics

我有以下代码:

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

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

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

编辑1:
根据David的建议,将类型从IList切换为List,编译器抱怨

编辑2:
我了解到,该行为与C# Language definition中指定的行为相同。但是,我不明白为什么允许这样的转换/转换,因为在运行时我总是会收到InvalidCastException。我打开this是为了更深入。

4 个答案:

答案 0 :(得分:6)

好吧,因为IList<MyClass>是一个接口,所以从理论上讲,您可以拥有一个实现该接口并从MyClass派生的类。

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

无论如何,至少我收到可疑演员表的警告,因为解决方案中没有从IList<MyClass>MyClass继承的类。

答案 1 :(得分:5)

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

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

即。编译器会进行轻量级的“匹配模式”检查,而不是完全检查。

请参阅C#5语言规范的8.8.3节,其中对此进行了详细介绍(并且您会看到上面我已经做了一些简化:即使IEnumerator都没有检查,只是有MoveNext()方法和Current属性)。

答案 2 :(得分:4)

IList<MyClass> 可转换为MyClass

但是如果您实际上使用非空枚举来运行它,

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

您收到此错误:

  

无法转换类型为'System.Collections.Generic.List`1 [MyClass]'的对象来键入'MyClass'。

这符合规范:

  

8.8.4节foreach语句

     

...   如果没有从T(元素)进行显式转换(第6.2节)   类型)转换为V(foreach语句中的局部变量类型),   错误产生,因此不采取进一步措施。

     

...

IList<MyClass>MyClass的显式转换(尽管它将在运行时失败),因此不会产生任何错误。

  

第6.2.4节显式引用转换

     

显式引用转换为:

     
      
  • 从对象和动态到任何其他引用类型。
  •   
  • 从任何类别类型S到任何类别类型T,只要S是T的基类。
  •   
  • 从任何类类型S到任何接口类型T,只要未密封S并且未实现T。
  •   
  • 从任何接口类型S到任何类类型T,只要未密封T或提供T实现S。
  •   
     

...

答案 3 :(得分:2)

在假设MyClass未实现IList<MyClass>的情况下,可能会有确实实现MyClass的派生类型IList<MyClass>,然后您的循环才有效。 / p>

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
}