Работа цикла foreach, С#

1,00
р.
Как работает foreach, если я кладу в него не просто коллекцию, а метод, который возвращает коллекцию. Метод не будет выполняться на каждой итерации?

Ответ
Нет, вызов Foo будет сделан только один раз. Цикл
foreach (var i in Foo()) { // тело цикла }
внутри заменяется на вот что:
IEnumerable x = Foo() using (var enumerator = x.GetEnumerator()) { while (enumerator.MoveNext()) { var i = enumerator.Current // тело цикла } }
Т. е., как мы видим, метод вызывается однократно.

(Подробное и более точное объяснение в деталях.)
Конструкция foreach -- это синтаксический сахар. При компиляции эта конструкция:
foreach (var i in x) { // тело цикла }
Заменяется примерно на следующий код:
using (var enumerator = x.GetEnumerator()) { while (enumerator.MoveNext()) { var i = enumerator.Current // тело цикла } }
При этом x -- это экземпляр некоторого объекта, который содержит метод GetEnumerator()* или имплементирует интерфейс IEnumerable (или IEnumerable), и может быть задан как в виде переменной, так и в виде выражения.
Если в цикле тип переменной отличается от T:
IEnumerable x = ... foreach (SomeType i in x) { // тело цикла }
То будет добавлено приведение к этому типу. При неудаче будет выброшено исключение:
using (var enumerator = x.GetEnumerator()) { while (enumerator.MoveNext()) { var i = (SomeType)enumerator.Current // тело цикла } }
(Но в цикле вы не можете изменять переменную цикла i.)

Конструкция foreach работает также и для коллекций, имплементирующих необобщенный IEnumerable. При этом код получается несколько другой, поскольку у необобщенного IEnumerator отсутствует метод Dispose():
var enumerator = x.GetEnumerator() while (enumerator.MoveNext()) { var i = enumerator.Current // тело цикла }
Если у переменной цикла явно указан тип (foreach (SomeType i in Foo())), то точно так же добавляется приведение типов:
var i = (SomeType)enumerator.Current
При этом тип i будет SomeType, без указания типа — object.

Дополнительно советую почитать о том, как работают итераторы и IEnumerable/IEnumerable. (Например, в спецификации языка, раздел 8.8.4.)

*При этом возвращаемым типом этого метода должен быть объект, имеющий открытые свойство Current и метод с сигнатурой bool MoveNext(). Это то, что называют «утиная типизация»: можно не имплементировать интерфейс IEnumerable или IEnumerable, а просто предоставить соответствующие методы.