C# foreach на IEnumerable<IList <object >> компилируется, но не должен

У меня есть следующий код:

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

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

Он компилируется, запускается и, очевидно, я получаю System.InvalidCastException,
Почему компилятор не жалуется? MyClass это простой боб, без расширений.

Изменить 1:
Как предложил Дэвид, переключив тип с IList в List компилятор жалуется

4 ответа

Ну потому что IList<MyClass> это интерфейс, так что теоретически вы можете иметь класс, который реализовал этот интерфейс и является производным от MyClass,

Если вы измените его на IEnumerable<List<MyClass>> это не скомпилируется.

В любом случае, по крайней мере, я получаю предупреждение за подозрительное приведение, поскольку в решении нет класса, который наследовал бы от обоих IList<MyClass> а также MyClass,

Когда foreach компилируется по шаблону, а не по конкретным типам (так же как LINQ и await делать).

foreach не ищет IEnumerable или же IEnumerable<T> но для типа, который имеет GetEnumerator() метод (который IList<T> делает). И объекты во внешнем списке могут иметь тип, производный от MyClass и внедрение IList<T>).

То есть. компилятор выполняет легкую проверку "соответствует шаблону", а не полную проверку.

См. §8.8.3 Спецификации языка C#5, в которой это подробно описано (и вы увидите, что я несколько упростил вещи выше: даже IEnumerator не проверяется, просто есть MoveNext() метод и Current имущество).

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

... Если нет явного преобразования (§6.2) из ​​T (тип элемента) в V (тип локальной переменной в операторе foreach), выдается ошибка и дальнейшие шаги не предпринимаются.

...

Существует явное преобразование из IList<MyClass> в MyClass (хотя он потерпит неудачу во время выполнения), поэтому ошибка не возникает.

Раздел 6.2.4 Явные преобразования ссылок

Явные ссылки преобразования:

  • От объекта и динамического до любого другого ссылочного типа.
  • Из любого типа класса S в любой тип класса T, если S является базовым классом T.
  • От любого типа класса S к любому типу интерфейса T при условии, что S не запечатан, и при условии, что S не реализует T.
  • От любого типа интерфейса S к любому типу класса T при условии, что T не запечатан, или при условии, что T реализует S.

...

При условии, что 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
}
Другие вопросы по тегам