C# 4.0 'динамический' и foreach оператор

Не долгое время, прежде чем я обнаружил, настолько новый dynamic ключевое слово не работает хорошо с C# 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 переменным результатам с динамическим поиском/броском так C# игнорирует, что я имею, указывают корректное xвведите в foreach операторе - который был немного удивителен для меня... И также, компилятор C# полностью игнорирует тот набор от переменной с динамическим контролем типов, может реализации IEnumerable<T> взаимодействуйте через интерфейс!

Полное foreach поведение оператора описано в спецификации 8.8.4 C# 4.0 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) {

Но почему я должен написать код как это?

Это должно очень приятно иногда видеть это C# 4.0 dynamic работы полностью то же, если тип будет известен во время компиляции, но это должно очень печально видеть это dynamic работы, полностью отличающиеся, где IT CAN работает то же кодом со статическим контролем типов.

Таким образом, мой вопрос: почему foreach dynamic работы, отличающиеся от foreach по чему-либо еще?

11
задан Yi Jiang 28 August 2010 в 01:39
поделиться

2 ответа

Прежде всего, чтобы объяснить некоторую предысторию читателям, которых смутил вопрос: язык C# на самом деле не требует, чтобы коллекция "foreach" реализовывала IEnumerable. Скорее, он требует, чтобы она реализовала IEnumerable, или чтобы она реализовала IEnumerable, или просто чтобы у нее был метод GetEnumerator (и чтобы метод GetEnumerator возвращал что-то с Current и MoveNext, соответствующее ожидаемому шаблону, и так далее)

Это может показаться странной особенностью для статически типизированного языка, каким является C#. Почему мы должны "соответствовать шаблону"? Почему бы не потребовать, чтобы коллекции реализовывали IEnumerable?

Подумайте о мире до появления generics. Если бы вы хотели создать коллекцию ints, вам пришлось бы использовать IEnumerable. И, следовательно, при каждом вызове Current, коллекция будет содержать int, а затем, конечно же, вызывающая сторона будет немедленно разворачивать ее обратно в int. Что медленно и создает нагрузку на GC. Используя подход, основанный на паттернах, вы можете создавать сильно типизированные коллекции в C# 1.0!

Сейчас, конечно, никто не реализует этот паттерн; если вам нужна сильно типизированная коллекция, вы реализуете IEnumerable и готово. Если бы система общих типов была доступна в C# 1.0, маловероятно, что функция "соответствия шаблону" была бы реализована в первую очередь.

Как вы заметили, вместо поиска шаблона код, сгенерированный для динамической коллекции в foreach ищет динамическое преобразование к IEnumerable (а затем делает преобразование от объекта, возвращаемого Current, к типу переменной цикла, конечно). Так что ваш вопрос, по сути, звучит так: "Почему код, сгенерированный при использовании динамического типа в качестве типа коллекции в foreach, не ищет шаблон во время выполнения?".

Потому что сейчас уже не 1999 год, и даже когда это было так во времена C# 1.0, коллекции, использующие паттерн, почти всегда также реализовывали IEnumerable. Вероятность того, что реальный пользователь будет писать код производственного качества на C# 4.0, который выполняет foreach над коллекцией, реализующей паттерн, но не IEnumerable, крайне мала. Если вы оказались в такой ситуации, что ж, это неожиданно, и мне жаль, что наш дизайн не смог предугадать ваши потребности. Если вы считаете, что ваш сценарий на самом деле распространен, и что мы неправильно оценили, насколько он редок, пожалуйста, опубликуйте более подробную информацию о вашем сценарии, и мы рассмотрим возможность его изменения для гипотетических будущих версий.

Обратите внимание, что преобразование, которое мы генерируем в IEnumerable, является динамическим преобразованием, а не просто проверкой типа. Таким образом, динамический объект может участвовать; если он не реализует IEnumerable, но хочет предложить прокси-объект, который реализует, он может свободно это сделать.

Короче говоря, дизайн "динамического foreach" - это "динамически запрашивать у объекта последовательность IEnumerable", а не "динамически выполнять все операции проверки типов, которые мы бы сделали во время компиляции". Теоретически это тонко нарушает принцип проектирования, согласно которому динамический анализ дает тот же результат, что и статический, но на практике именно так мы ожидаем, что подавляющее большинство коллекций с динамическим доступом будет работать.

23
ответ дан 3 December 2019 в 04:12
поделиться

Но почему я должен писать такой код?

Действительно. И зачем компилятору писать такой код? Вы устранили любую возможность предположить, что цикл можно оптимизировать. Кстати, вы, кажется, неправильно интерпретируете IL, он выполняет повторную привязку для получения IEnumerable.Current, вызов MoveNext () является прямым, а GetEnumerator () вызывается только один раз. Я думаю, что это уместно, следующий элемент может без проблем привести или не привести к типу int. Это может быть набор разных типов, каждый со своей папкой.

2
ответ дан 3 December 2019 в 04:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: