Цикл в C# — это конструкция, которая повторяет блок кода, пока выполняется условие или пока не перебраны все элементы коллекции. В C# четыре оператора цикла: for (со счетчиком), while (с предусловием), do-while (с постусловием) и foreach (перебор коллекции, у него есть асинхронная форма await foreach с C# 8). Управлять ходом цикла помогают break и continue.
Содержание
Ниже — один рабочий пример со всеми четырьмя циклами, таблица выбора, разбор break/continue и типичные ошибки с настоящим выводом компилятора и среды. Все примеры проверены на .NET SDK 10.0.401 (C# 14) 24 сентября 2026 года.
Четыре цикла на одном примере
Код ниже можно сохранить в файл loops.cs и запустить командой dotnet run loops.cs (в .NET 10 файл без проекта запускается напрямую). В обычном консольном проекте тот же код работает в Program.cs.
int[] prices = { 120, 45, 300, 80 };
// for: нужен индекс или шаг
int total = 0;
for (int i = 0; i < prices.Length; i++)
{
total += prices[i];
}
Console.WriteLine($"for: сумма = {total}");
// foreach: нужны только элементы
int max = int.MinValue;
foreach (int p in prices)
{
if (p > max) max = p;
}
Console.WriteLine($"foreach: максимум = {max}");
// while: число повторов заранее неизвестно
int balance = 500;
int months = 0;
while (balance >= 120)
{
balance -= 120;
months++;
}
Console.WriteLine($"while: подписка на {months} мес., остаток {balance}");
// do-while: тело выполнится хотя бы один раз
string[] inputs = { "abc", "", "42" }; // имитация ввода пользователя
int step = 0;
int number;
do
{
Console.WriteLine($"do-while: ввод \"{inputs[step]}\"");
step++;
} while (!int.TryParse(inputs[step - 1], out number));
Console.WriteLine($"do-while: число {number} получено с попытки {step}");
Вывод:
for: сумма = 545
foreach: максимум = 300
while: подписка на 4 мес., остаток 20
do-while: ввод "abc"
do-while: ввод ""
do-while: ввод "42"
do-while: число 42 получено с попытки 3
Как устроен каждый цикл
for — цикл со счетчиком
Заголовок for (инициализация; условие; итерация) состоит из трех частей. Порядок такой: один раз выполняется инициализация (int i = 0), затем перед каждым проходом проверяется условие (i < prices.Length), если оно истинно — выполняется тело, после тела — итерация (i++), и снова проверка условия.
Прогон руками для массива из 4 элементов: i = 0, 1, 2, 3 — условие истинно, тело выполняется; при i = 4 условие ложно, цикл завершается. Переменная i объявлена в заголовке и после цикла недоступна.
Все три части необязательны: for (;;) — бесконечный цикл, который завершают через break или return. Шаг может быть любым: i += 2 для каждого второго элемента, i-- для обратного прохода.
while — цикл с предусловием
while (условие) { ... } проверяет условие до каждого прохода. Если оно ложно с самого начала, тело не выполнится ни разу. Подходит, когда число повторов заранее неизвестно: читать, пока есть данные; уменьшать баланс, пока хватает денег.
do-while — цикл с постусловием
do { ... } while (условие); сначала выполняет тело, потом проверяет условие. Поэтому тело выполняется минимум один раз. Типичный сценарий — запросить ввод и повторять, пока он некорректен. Обратите внимание на точку с запятой после while (...) — в do-while она обязательна.
foreach — перебор коллекции
foreach (var элемент in коллекция) проходит по массиву, списку, строке (по символам) и любому типу с подходящим методом GetEnumerator() (его результат должен иметь MoveNext() и Current). Индекс здесь не нужен, выйти за границы массива нельзя. Ограничения: переменную цикла нельзя присвоить, а коллекцию нельзя менять во время перебора (оба случая разобраны ниже). Подробно устройство foreach и интерфейса IEnumerable — тема отдельного разбора, здесь достаточно правил использования.
Условие — только bool
В отличие от C и C++, в C# условие цикла обязано иметь тип bool. Код while (n), где n — целое, не компилируется:
error CS0029: Cannot implicitly convert type 'int' to 'bool'
Исправление — явное сравнение: while (n != 0). Как те же циклы выглядят в C и C++, где число в условии допустимо, разобрано в статье о циклах в C и C++.
Какой цикл выбрать
| Задача | Цикл | Почему |
|---|---|---|
| Пройти все элементы, индекс не нужен | foreach |
Короче и без риска выйти за границы |
| Нужен индекс, шаг, обратный порядок, соседние элементы | for |
Счетчик под полным контролем |
| Повторять, пока выполняется условие, число шагов неизвестно | while |
Проверка до первого прохода |
| Тело должно выполниться хотя бы раз (ввод, меню, повтор запроса) | do-while |
Проверка после прохода |
| Удалять элементы списка по условию | for с конца или RemoveAll |
foreach не разрешает менять коллекцию |
Упрощение: любой цикл можно переписать через while, так что таблица говорит о читаемости и типичном риске ошибок, а не о возможностях.
break и continue
continue пропускает остаток текущего прохода и переходит к следующему. break завершает цикл целиком.
int[] data = { 4, -1, 7, 0, 9, 3 };
// continue: пропустить итерацию
int sum = 0;
foreach (int x in data)
{
if (x <= 0) continue; // отрицательные и ноль пропускаем
sum += x;
}
Console.WriteLine($"сумма положительных = {sum}");
// break: выйти из цикла при первой находке
int index = -1;
for (int i = 0; i < data.Length; i++)
{
if (data[i] > 5)
{
index = i;
break;
}
}
Console.WriteLine($"первое число больше 5 - индекс {index}");
// break во вложенном цикле выходит только из внутреннего
int[,] grid = { { 1, 2, 3 }, { 4, 99, 6 }, { 7, 8, 9 } };
Console.WriteLine($"позиция 99: {Find(grid, 99)}");
static string Find(int[,] g, int target)
{
for (int r = 0; r < g.GetLength(0); r++)
for (int c = 0; c < g.GetLength(1); c++)
if (g[r, c] == target)
return $"строка {r}, столбец {c}"; // return выходит из обоих циклов
return "не найдено";
}
Вывод:
сумма положительных = 23
первое число больше 5 - индекс 2
позиция 99: строка 1, столбец 1
Во вложенных циклах break завершает только ближайший цикл. Чтобы выйти из обоих, удобнее всего вынести циклы в метод и использовать return, как в Find. Другие варианты — флаг, который проверяет внешний цикл, или goto на метку после циклов (работает, но читается хуже).
Типичные ошибки в циклах C
Выход за границы массива
int[] a = { 10, 20, 30 };
for (int i = 0; i <= a.Length; i++)
{
Console.WriteLine(a[i]);
}
Результат: три числа, затем исключение на i = 3:
10
20
30
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
Индексы массива из 3 элементов — 0, 1, 2. Исправление: условие i < a.Length или foreach, если индекс не нужен.
Изменение коллекции внутри foreach
var list = new List<int> { 1, 2, 3, 4 };
foreach (int x in list)
{
if (x % 2 == 0) list.Remove(x);
}
Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
Исправление — обратный for (при удалении с конца индексы еще не пройденных элементов не сдвигаются) или готовый метод:
var list = new List<int> { 1, 2, 3, 4 };
for (int i = list.Count - 1; i >= 0; i--)
{
if (list[i] % 2 == 0) list.RemoveAt(i);
}
Console.WriteLine(string.Join(", ", list)); // 1, 3
var list2 = new List<int> { 1, 2, 3, 4 };
list2.RemoveAll(x => x % 2 == 0);
Console.WriteLine(string.Join(", ", list2)); // 1, 3
Присваивание переменной foreach
Код foreach (int x in a) { x = x * 2; } не компилируется:
error CS1656: Cannot assign to 'x' because it is a 'foreach iteration variable'
Чтобы изменить элементы массива, нужен for с индексом: a[i] = a[i] * 2.
Точка с запятой после while
int i = 0;
while (i < 3);
{
Console.WriteLine(i);
i++;
}
Точка с запятой становится пустым телом цикла, а блок в скобках — обычным кодом после него. Условие i < 3 никогда не меняется, программа зависает без вывода. Компилятор предупреждает, но сборку не останавливает:
warning CS0642: Possible mistaken empty statement
Исправление — убрать ; после while (i < 3). Предупреждения при сборке стоит читать, а не пропускать.
Беззнаковый счетчик в обратном цикле
int n = 0;
for (uint i = 3; i >= 0; i--)
{
Console.WriteLine(i);
if (++n == 6) break; // страховка, иначе цикл бесконечный
}
3
2
1
0
4294967295
4294967294
Тип uint не бывает отрицательным: после 0 значение переходит к максимуму, и i >= 0 истинно всегда. В этом прогоне компилятор предупреждения не выдал. Исправление — счетчик типа int.
Сравнение дробного счетчика на равенство
Цикл for (double x = 0; x != 1.0; x += 0.1) не останавливается: из-за двоичного представления после десяти шагов x не равен ровно 1.0. В прогоне со страховкой на 20 шагов цикл проскочил единицу и дошел до x = 2.0000000000000004. Исправление — считать шаги целым счетчиком (for (int t = 0; t <= 10; t++), а x = t / 10.0) или сравнивать через <.
Лямбда захватывает переменную for
var fromFor = new List<Action>();
for (int i = 0; i < 3; i++)
fromFor.Add(() => Console.Write(i + " "));
foreach (var f in fromFor) f();
Console.WriteLine("<- for");
var fromForeach = new List<Action>();
foreach (int n in new[] { 0, 1, 2 })
fromForeach.Add(() => Console.Write(n + " "));
foreach (var f in fromForeach) f();
Console.WriteLine("<- foreach");
3 3 3 <- for
0 1 2 <- foreach
В for переменная i одна на весь цикл, и все лямбды видят ее последнее значение 3. В foreach с C# 5 переменная создается заново на каждом проходе. Исправление для for — копия внутри тела: int copy = i; и захват copy.
Если цикл работает не так
- Программа зависла без вывода — проверьте
;послеwhile (...), меняется ли переменная условия в теле, нет лиuintв обратном цикле или сравненияdoubleчерез!=. - IndexOutOfRangeException — условие
<=вместо<или обращение кa[i + 1]на последнем шаге. - InvalidOperationException в foreach — коллекция меняется во время перебора, нужен обратный
forилиRemoveAll. - Цикл не выполнился ни разу — условие
while/forложно с самого начала; если тело нужно хотя бы один раз, используйтеdo-while.
Выводы
- В C# четыре цикла:
forсо счетчиком,whileс предусловием,do-whileс постусловием (тело выполняется минимум раз) иforeachдля перебора коллекций. foreach— выбор по умолчанию для обхода всех элементов;for— когда нужны индекс, шаг или изменение коллекции.breakвыходит только из ближайшего цикла; из вложенных удобнее выходить черезreturnиз вынесенного метода.- Условие цикла в C# — только
bool, а переменнуюforeachи саму коллекцию во время перебора менять нельзя. - Частые ошибки:
<=вместо<,;послеwhile,uintв обратном цикле,!=дляdouble, захват переменнойforв лямбде.
Где применяется / связь с практикой
Циклы в C# встречаются в любом типе проекта: обработка данных и файлов, игровой цикл в Unity, повторные попытки запроса к API, обход результатов из базы. Уверенное владение циклами и коллекциями — база, на которой строятся LINQ, асинхронный код и работа с потоками данных.
Освойте тему на практике
Системно пройти синтаксис C#, коллекции и ООП с практикой и ревью кода можно на курсе по основам C#. Чтобы посмотреть формат обучения, подойдут бесплатные открытые уроки Otus.
FAQ
Что быстрее: for или foreach?
Для массивов компилятор обычно превращает foreach в проход по индексу, и разница незаметна. Для List<T> и других коллекций она зависит от типа и версии среды, поэтому решать стоит по читаемости, а скорость измерять на своем коде (например, BenchmarkDotNet).
Можно ли в foreach узнать номер элемента?
Отдельной переменной-индекса у foreach нет. Можно завести счетчик до цикла и увеличивать его в теле, а в .NET 9 и новее — перебирать list.Index(), который возвращает пары (индекс, элемент).
Чем цикл отличается от рекурсии?
Цикл повторяет код внутри одного вызова метода, рекурсия — через повторные вызовы метода самого себя. Для простого перебора цикл проще и не расходует стек вызовов.



