C # foreach на IEnumerableкомпилируется, но не должен - PullRequest
0 голосов
/ 30 ноября 2018

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

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

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

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

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

Редактировать 2:Я понял, что поведение такое, как указано в определении C # Language .Однако я не понимаю, почему такое приведение / преобразование разрешено, так как во время выполнения я всегда получаю InvalidCastException.Я открыл этот , чтобы пойти глубже.

Ответы [ 4 ]

0 голосов
/ 30 ноября 2018

В предположении, что 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
}
0 голосов
/ 30 ноября 2018

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 (local-variable-type в выражении foreach), возникает ошибка, и дальнейшие шаги не предпринимаются.

...

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

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

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

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

...

0 голосов
/ 30 ноября 2018

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

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

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

0 голосов
/ 30 ноября 2018

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

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

Т.е.Компилятор выполняет упрощенную проверку «соответствует шаблону», а не полную проверку.

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

...