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
}