При написании кода на языке C# достаточно часто приходится использовать конструкцию foreach. Ведь так на много удобнее проходить по коллекциям, по сравнению с использованием цикла for. Безусловно, у каждого метода есть свои плюсы и минусы и выбор всегда зависит от конкретной задачи и ситуации. Но на днях меня удивила одни интересная особенность конструкции foreach, а  именно то, как это работает.

Рассмотрим простой пример:

У нас есть некая коллекция элементов и с помощью foreach мы выводим на консоль имена всех элементов.

При этом класс MyCollection выглядит так:

Класс коллекции реализовывает единственный метод GetEnumerator интерфейса IEnumerable для возможности использования класса в конструкции foreach.

Особенности enumerator’а нас сейчас не интересуют, будем считать что он работает так, как ему положено.

Теперь сделаем простую вещь, а именно, поменяем наш класс MyCollection так, чтобы он больше не реализовывал интерфейс IEnumerable, при этом метод GetEnumerator оставим без изменений.

К моему удивлению, код и дальше продолжил компиляцию и правильно работал. Первым делом, посмотрим какой IL-код у нас получился:

Тут хорошо видно, что при создании IL-кода компилятор не обращает внимания на наличие интерфейса IEnumerable, а непосредственно вызывает метод GetEnumerator. На этом моменте мне стало интересно и я пошел читать спецификацию языка C# (Standard ECMA-334 C# Language Specification), которая, к слову, датирована далёким июнем 2006-го года.  В разделе 8.18 Итераторы сказано следующее:

The foreach statement is used to iterate over the elements of an enumerable collection. In order to be
enumerable, a collection shall have a parameterless GetEnumerator method that returns an enumerator.
Generally, enumerators are difficult to implement, but the task is significantly simplified with iterators.

Таким образом, получается что для использования коллекции в конструкции foreach достаточно только метода GetEnumerator, а наличие интерфейса - необязательное условие.

Пример кода доступен по адресу: https://github.com/e0ne/BlogSamples/tree/master/ForeachTest


Comments

ControlFlow Russia

Wednesday, February 9, 2011 2:30 PM

ControlFlow

foreach не только не требует, чтобы класс реализовывал IEnumerable<T>, он ещё и не требует, чтобы возвращаемый GetEnumerator() объект реализовывал IEnumerator<T>/IEnumerator: http://ideone.com/9HxFE
Используется данная техника в классе List<T>, имеющим вложенный тип List<T>.Enumerator - засчёт того, что этот тип является структурой, перебор List<T> через foreach очень быстр: enumerator лежит прямо на стеке, методы get_Current и MoveNext не требуют вызовов через интерфейс и даже инлайнятся в вызываемый код, в конце перебора не вызывается Dispose и весь код перебора списка не убирается в try { } finally { }.

foreach обладает ещё одной интересной особенностью - он умеет даункастить IEnumerable-коллекции (не типизированные) при явном указании типа переменной итерации.

leechdraw Russia

Wednesday, February 9, 2011 2:34 PM

leechdraw

Я думаю, это просто "сахар" платформы .Net - не важно, что за объект мы получили - мы (имеется ввиду компилятор на стадии создания IL-кода) легко может проверить, есть ли у объекта нужный метод, а интерфейсы - просто абстракция удобная программисту/парсеру кода, но по сути ничего не меняющая, в конце концов, указав наследование от определенного интерфейса, мы просто ставим себе нечто вроде зарубки на память - "не забудь реализовать все члены класса", не более того. Или я не прав?

e0ne United States

Wednesday, February 9, 2011 2:45 PM

e0ne

leechdraw, если я правильно понял комментарий, то не согласен что "интерфейсы - это только синтаксический сахор". Они играют дстаточно важную роль в замене множественного наследования и, в терминах ООП, выполняют роль контрактов.

leechdraw Russia

Wednesday, February 9, 2011 3:45 PM

leechdraw

e0ne, ну да, согласен, просто появилась мысль додумать до конца, не сумел Smile Лучше не буду ввязываться в дискуссию, лучше почитаю про интерфейсы внимательнее. Smile

ControlFlow Russia

Wednesday, February 9, 2011 5:15 PM

ControlFlow

@leechdraw:
это не имеет никакого отношения к платформе/рантайму/интерфейсам, это лишь приблуда компилятора C# (в VB.NET сделано так же) лишь для целей оптимизации.

progg.ru

Sunday, February 13, 2011 10:30 AM

trackback

Интересная особенность конструкции foreach в языке C#

Thank you for submitting this cool story - Trackback from progg.ru

vtimashkov Russia

Sunday, February 13, 2011 8:12 PM

vtimashkov

Вообще-то это "приблуда" называется duck typing (http://en.wikipedia.org/wiki/Duck_typing)
и является на редкость нужной и полезной вещью. Не в C# разумеется.

Guide Marrakech France

Monday, November 7, 2011 9:23 AM

Guide Marrakech

Only wanna pronounce that this is very cooperative, Acknowledges for taking your rhythm to communicate this. “We can’t many be heroes so celebrity has to perch on the hinder further applaud as they go by.” by Volition Rogers.

Marrakech France

Sunday, November 13, 2011 8:49 AM

Marrakech

Quality decipher, I ethical passed this onto a ally who was doing quantity inquiry on that. Plus he ethical bought me lunch as I establish it for him grin Thus permit me rephrase that: Thank you for lunch! “They may disregard what you said, still they inclination never disregard how you made them experience.” by Carl W. Buechner.

Comments are closed