Как работает 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, а просто предоставить соответствующие методы.