Не долгое время, прежде чем я обнаружил, что новое ключевое слово dynamic не работает с оператором С# foreach:
using System;
sealed class Foo {
public struct FooEnumerator {
int value;
public bool MoveNext() { return true; }
public int Current { get { return value++; } }
}
public FooEnumerator GetEnumerator() {
return new FooEnumerator();
}
static void Main() {
foreach (int x in new Foo()) {
Console.WriteLine(x);
if (x >= 100) break;
}
foreach (int x in (dynamic)new Foo()) { // :)
Console.WriteLine(x);
if (x >= 100) break;
}
}
}
Я ожидал, что итерация по переменной dynamic должна работать полностью, как если бы тип переменной коллекции был известен во время компиляции. Я обнаружил, что второй цикл на самом деле выглядит так, когда компилируется:
foreach (object x in (IEnumerable) /* dynamic cast */ (object) new Foo()) {
...
}
и каждый доступ к переменной x приводит к динамическому поиску/отбрасыванию, поэтому С# игнорирует то, что я указываю правильный тип x в инструкции foreach - это было немного удивительно для меня... А также, С# компилятор полностью игнорирует, что коллекция из динамически типизированной переменной может реализовывать интерфейс IEnumerable<T>!
Полное поведение оператора foreach описано в спецификации С# 4.0 8.8.4. Статья foreach.
Но... Совершенно возможно реализовать такое же поведение во время выполнения! Можно добавить дополнительный флаг CSharpBinderFlags.ForEachCast, исправить код, который будет выглядеть следующим образом:
foreach (int x in (IEnumerable<int>) /* dynamic cast with the CSharpBinderFlags.ForEachCast flag */ (object) new Foo()) {
...
}
И добавьте дополнительную логику в CSharpConvertBinder:
- Оберните коллекции
IEnumerableиIEnumeratorвIEnumerable<T>/IEnumerator<T>. - Коллекции обложек не реализуют
IEnumerable<T>/IEnumerator<T>для реализации этих интерфейсов.
Итак, оператор foreach сегодня выполняет итерацию по dynamic, полностью отличную от итерации по статически известной переменной коллекции и полностью игнорирует информацию о типе, указанную пользователем. Все, что приводит к различному итерационному поведению (IEnumarble<T> -обновление коллекций, повторяется как только IEnumerable -выполнение) и более чем 150x замедляется при повторении по dynamic. Простое исправление приведет к значительно лучшей производительности:
foreach (int x in (IEnumerable<int>) dynamicVariable) {
Но зачем мне писать такой код?
Очень приятно видеть, что иногда С# 4.0 dynamic работает полностью одинаково, если тип будет известен во время компиляции, но очень грустно видеть, что dynamic работает совершенно по-другому, где IT CAN работает так же, как статически типизированный код.
Итак, мой вопрос: почему foreach over dynamic отличается от foreach чем-либо еще?