Foreach

Оператор foreach осуществляет перечисление элементов коллекции, выполняя внедренный оператор для каждого элемента коллекции.

оператор_foreach:
foreach ( тип_локальной_переменной идентификатор in выражение ) внедренный_оператор

Тип и идентификатор в каждом операторе foreach объявляют итерационную переменную оператора. Если тип_локальной_переменной задан ключевым словом var, говорят, что это неявно введенная итерационная переменная, и в качестве ее типа принимается тип элементов оператора foreach, как описано ниже. Итерационная переменная соответствует локальной переменной только для чтения, область видимости которой охватывает внедренный оператор. В ходе выполнения оператора foreach итерационная переменная представляет элемент коллекции, для которого в данный момент выполняется итерация. Если внедренный оператор пытается изменить итерационную переменную (путем присваивания или с помощью операторов ++ и ‑‑) или передать ее как параметр ref или out, возникает ошибка времени компиляции.

Во время компиляции оператора foreach сначала определяется тип коллекции, тип перечислителя и тип элементов для выражения. Это происходит следующим образом.

· Если типом X выражения является тип массива, производится неявное преобразование ссылочного типа X в интерфейс System.Collections.IEnumerable (поскольку System.Array реализует этот интерфейс). В качестве типа коллекции принимается интерфейс System.Collections.IEnumerable, в качестве типа перечислителя — интерфейс System.Collections.IEnumerator, и в качестве типа элементов — тип элементов массива типа X.

· В противном случае следует определить, существует ли для типа X соответствующий метод GetEnumerator.

o Выполняется поиск членов для типа X с идентификатором GetEnumerator без использования аргументов типа. Если поиск членов не дает результата, или дает неоднозначный результат, или находит член, не являющийся группой методов, следует проверить наличие перечислимого интерфейса, как описано ниже. Рекомендуется выдавать предупреждение, если результатом поиска членов является что-либо, кроме группы методов или отсутствия совпадений.

o Выполняется разрешение перегрузки с использованием результирующей группы методов и пустого списка аргументов. Если разрешение перегрузки не позволяет получить применимые методы, дает неоднозначный результат или приводит к единственному наилучшему методу, который описан как статический или не открытый, следует проверить наличие перечислимого интерфейса, как описано ниже. Рекомендуется выдавать предупреждение, если результатом разрешения перегрузки является что-либо, кроме однозначно определенного открытого метода экземпляра или отсутствия применимых методов.

o Если тип возвращаемого значения E метода GetEnumerator не является типом класса, структуры или интерфейса, возникает ошибка, и больше никакие действия не выполняются.

o Выполняется поиск членов для типа E с идентификатором Current без использования аргументов типа. Если поиск членов не дает результата, приводит к ошибке или находит что-либо, кроме открытого свойства экземпляра, разрешающего чтение, возникает ошибка, и больше никакие действия не выполняются.

o Выполняется поиск членов для типа E с идентификатором MoveNext без использования аргументов типа. Если поиск членов не дает результата, приводит к ошибке или находит что-либо, кроме группы методов, возникает ошибка, и больше никакие действия не выполняются.

o Выполняется разрешение перегрузки для группы методов с использованием пустого списка аргументов. Если разрешение перегрузки не позволяет получить применимые методы, дает неоднозначный результат или приводит к единственному наилучшему методу, который описан как статический или не открытый либо возвращает значение типа, отличного от bool, возникает ошибка, и больше никакие действия не выполняются.

o В качестве типа коллекции принимается X, в качестве типа перечислителя — E, в качестве типа элементов — тип свойства Current.

· В противном случае проверяется наличие перечислимого интерфейса.

o Если имеется ровно один тип T, для которого существует неявное преобразование типа X в интерфейс System.Collections.Generic.IEnumerable<T>, то в качестве типа коллекции принимается этот интерфейс, в качестве типа перечислителя — интерфейс System.Collections.Generic.IEnumerator<T>, и в качестве типа элементов — T.

o Если таких типов T несколько, возникает ошибка, и больше никакие действия не выполняются.

o Если существует неявное преобразование типа X в интерфейс System.Collections.IEnumerable, в качестве типа коллекции принимается этот интерфейс, в качестве типа перечислителя — интерфейс System.Collections.IEnumerator, и в качестве типа элементов — object.

o В противном случае возникает ошибка, и больше никакие действия не выполняются.

Если описанная выше процедура завершается успешно, она дает однозначно определенный результат: тип коллекции C, тип перечислителя E и тип элементов T. После этого оператор foreach, заданный в виде

foreach (V v in x) внедренный_оператор

развертывается следующим образом:

{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
внедренный_оператор
}
}
finally {
… // Удаление ресурсов e
}
}

Переменная e невидима или недоступна для выражения x, внедренного оператора и любого другого исходного кода программы. Переменная v доступна только чтения во внедренном операторе. Если не определено явное преобразование (§6.2) типа T (типа элементов) в V (тип в операторе foreach), возникает ошибка, и больше никакие действия не выполняются. Если x имеет значение null, во время выполнения генерируется исключение System.NullReferenceException.

Для любого оператора foreach разрешается использовать реализацию, в которой он обрабатывается иначе (например, для повышения быстродействия), при условии сохранения поведения, совместимого с вышеописанным развертыванием.

Тело блока finally строится по следующим правилам.

· Если существует неявное преобразование типа E в интерфейс System.IDisposable, предложение finally развертывается в блок, семантически эквивалентный следующему:

finally {
((System.IDisposable)e).Dispose();
}

Исключение составляют ситуации, когда e является типом значения или параметром типа, экземпляром которого оказывается тип значения; в этих случаях приведение e к типу System.IDisposable не сопровождается упаковкой.

· Если E является запечатанным типом, предложение finally развертывается в пустой блок:

finally {
}

· В противном случае предложение finally развертывается следующим образом:

finally {
System.IDisposable d = e as System.IDisposable;
if (d != null) d.Dispose();
}

Локальная переменная d невидима или недоступна для любого пользовательского кода. В частности, она не вступает в конфликты с другими переменными, чьи области видимости включают блок finally.

Оператор foreach обходит элементы массива в следующем порядке. Элементы одномерных массивов обходятся в порядке возрастания индекса, начиная с индекса 0 и заканчивая индексом Length – 1. Элементы многомерных массивов обходятся сначала по возрастанию индексов самого правого измерения, затем по возрастанию индексов следующего измерения слева и так далее до самого левого измерения.

В следующем примере выводится каждое значение двумерного массива в порядке следования элементов:

using System;

class Test
{
static void Main() {
double[,] values = {
{1.2, 2.3, 3.4, 4.5},
{5.6, 6.7, 7.8, 8.9}
};

foreach (double elementValue in values)
Console.Write("{0} ", elementValue);

Console.WriteLine();
}
}

Вывод выглядит следующим образом:

1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9

В следующем примере

int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);

в качестве типа n неявно принимается int — тип элементов массива numbers.