Как работает foreach, если я кладу в него не просто коллекцию, а метод, который возвращает коллекцию. Метод не будет выполняться на каждой итерации?
Ответы
Ответ 1
Нет, вызов 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, а просто предоставить соответствующие методы.
Комментариев нет:
Отправить комментарий