2010-05-30 2 views
11

Не задолго до того, я обнаружил, что новый dynamic ключевое слово не очень хорошо работает с C# 's foreach заявление:C# 4.0 «динамический» и Еогеасп заявление

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()) { 
    ... 
} 

и каждый доступ к й результатам переменных с динамическим поиском/отливать так C# игнорирует, что я указать правильный x ' s в предложении foreach - это было для меня немного неожиданно ... А также компилятор C# полностью игнорирует эту коллекцию из динамически типизированной переменной, которая может реализовать интерфейс IEnumerable<T>!

Полное описание поведения описано в спецификации C# 4.0. 8.8.4. Заявление foreach.

Но ... Вполне возможно реализовать такое же поведение во время выполнения! Можно добавить дополнительный CSharpBinderFlags.ForEachCast флаг, исправить emmited код выглядит как:

foreach (int x in (IEnumerable<int>) /* dynamic cast with the CSharpBinderFlags.ForEachCast flag */ (object) new Foo()) { 
    ... 
} 

И добавить некоторую дополнительную логику CSharpConvertBinder:

  • Wrap 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 работает совершенно другой, где она может работает так же, как статически типизированный код ,

Так что мой вопрос: почему foreach более dynamic работает отличным от foreach чем-нибудь еще?

+0

Меня волнует поведение, совершенно отличное от статического 'foreach'! Проблема производительности - это результат неправильного поведения. Зачем использовать динамическое преобразование 'IEnumerable' +, где статически известно, что' forech' будет перебирать целочисленную последовательность? – ControlFlow

ответ

23

Прежде всего, чтобы объяснить некоторый фон читателям, которые смущены вопросом: язык C# фактически не требует, чтобы сборник «foreach» реализовал IEnumerable.Скорее, это требует либо того, что он реализует IEnumerable, либо реализует IEnumerable<T>, или просто имеет метод GetEnumerator (и что метод GetEnumerator возвращает что-то с Current и MoveNext, которое соответствует ожидаемому шаблону и т. Д.).

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

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

В настоящее время, конечно, никто не реализует этот образец; если вы хотите получить строго типизированную коллекцию, вы реализуете IEnumerable<T>, и все готово. Если для C# 1.0 была доступна система общего типа, маловероятно, что функция «соответствовать шаблону» была бы реализована в первую очередь.

Как вы уже отметили, вместо того, чтобы искать шаблон, сгенерированный код для динамического сбора в Еогеасп выглядит для динамического преобразования в IEnumerable (а затем выполняет преобразование из объекта, возвращенного тока к тип переменной цикла, конечно.) Итак, ваш вопрос в основном заключается в «почему код, созданный с использованием динамического типа в качестве типа коллекции foreach, не может найти шаблон во время выполнения?»

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

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

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

+0

1. Спасибо за ваш ответ, Эрик! :) 2. Функция «соответствовать шаблону» предназначена не только для «мира до дженериков», но и используется в настоящее время, например, таким фундаментальным ** общим типом, как тип «Список » (и причины это то же самое - преимущество в производительности). 3. Я понимаю, что функция «соответствовать шаблону» очень редко используется в настоящее время и на самом деле не хочет, чтобы C# поддерживала это, в первую очередь, это вопрос о сопоставлении динамического и статического поведения. – ControlFlow

+0

4. Я забочусь о полном игнорировании 'IEnumarable ' в наши дни! Я предпочту, что дизайн «динамического foreach» будет «динамически запрашивать объект для последовательности IEnumerable , если оператор foreach определяет тип T, задающий последовательность IEnumerable иначе». Это будет намного ближе к статическому поведению и приведет к лучшей производительности. Единственная проблема - коллекции, которые реализуют только «IEnumerable», который редко встречается и может обрабатываться 'CSharpConvertBinder'. – ControlFlow

+0

@ControlFlow Список не использует "соответствует шаблону", как это реализовать IEnumerable непосредственно: Список общественного класса : IList , ICollection , IEnumerable , IList, ICollection, IEnumerable –

2

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

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