Не долгое время, прежде чем я обнаружил, что новое ключевое слово 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
чем-либо еще?