Урок 6.2 — Цикл foreach
Безопасный обход коллекций без индексов. Просто и элегантно.
Цикл foreach создан для одной задачи: перебрать все элементы коллекции один за другим. В отличие от for, тебе не нужно думать об индексах, длине коллекции и шаге. Просто: «возьми каждый элемент и сделай с ним что-то». foreach — это синтаксический сахар, который делает код чище и безопаснее.
Синтаксис foreach
foreach (тип переменная in коллекция) { тело }
int[] numbers = { 10, 20, 30, 40, 50 };
foreach (int num in numbers)
{
Console.WriteLine(num);
}
// Вывод: 10, 20, 30, 40, 50 (каждое на новой строке)
Как это читать? «Для каждого int под именем num из коллекции numbers выполни тело цикла».
Переменная цикла num на каждой итерации получает значение очередного элемента. Её тип должен совпадать с типом элементов коллекции (или быть совместимым).
foreach с разными коллекциями
Массивы
string[] names = { "Анна", "Борис", "Вика" };
foreach (string name in names)
{
Console.WriteLine($"Привет, {name}!");
}
Списки List<T>
List<double> prices = new() { 99.9, 149.5, 299.0, 89.5 };
foreach (double price in prices)
{
Console.WriteLine($"Цена: {price} руб.");
}
Строки (перебор символов)
Строка — это коллекция символов char, поэтому её тоже можно перебирать foreach:
string word = "C#";
foreach (char ch in word)
{
Console.WriteLine($"Символ: '{ch}'");
}
// Вывод:
// Символ: 'C'
// Символ: '#'
// Подсчёт букв 'а' в строке
string text = "программирование на C#";
int countA = 0;
foreach (char c in text)
{
if (c == 'а') countA++;
}
Console.WriteLine($"Букв 'а': {countA}"); // 2
HashSet и другие коллекции
HashSet<int> unique = new() { 1, 2, 3, 4, 5 };
foreach (int num in unique)
{
Console.WriteLine(num);
}
// Порядок НЕ гарантируется, но все элементы будут выведены
foreach со словарём Dictionary
Со словарём foreach возвращает пары KeyValuePair<TKey, TValue>:
Dictionary<string, int> ages = new()
{
{ "Анна", 25 },
{ "Борис", 32 },
{ "Вика", 28 }
};
// Полная запись
foreach (KeyValuePair<string, int> kvp in ages)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
// С var (короче)
foreach (var kvp in ages)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
// Деконструкция (C# 7+)
foreach (var (name, age) in ages)
{
Console.WriteLine($"{name}: {age}");
}
// Только ключи
foreach (string name in ages.Keys)
{
Console.WriteLine(name);
}
// Только значения
foreach (int age in ages.Values)
{
Console.WriteLine(age);
}
foreach vs for: безопасность и неизменяемость
foreach нельзя изменять коллекцию во время перебора. Если попытаться — выбросится InvalidOperationException:
List<int> numbers = new() { 1, 2, 3, 4, 5 };
// ❌ ОШИБКА! Нельзя удалять элементы во время foreach
foreach (int num in numbers)
{
if (num % 2 == 0)
numbers.Remove(num); // InvalidOperationException!
}
// ✅ Правильно — использовать for или копию
for (int i = numbers.Count - 1; i >= 0; i--)
{
if (numbers[i] % 2 == 0)
numbers.RemoveAt(i);
}
foreach также не даёт изменить сам элемент, если это value type. Для reference type ты можешь изменить поля объекта, но не заменить его:
int[] numbers = { 1, 2, 3 };
// ❌ НЕ работает — value type, копия!
foreach (int num in numbers)
{
num = num * 2; // Ошибка компиляции! num — read-only
}
// ✅ Работает — for
for (int i = 0; i < numbers.Length; i++)
{
numbers[i] = numbers[i] * 2;
}
// Для ссылочных типов — можно менять поля:
class Person { public string Name; }
List<Person> people = new() { new Person { Name = "Анна" } };
foreach (Person p in people)
{
p.Name = "Борис"; // ✅ OK — меняем поле объекта
// p = new Person(); // ❌ Нельзя заменить сам объект!
}
Когда НЕЛЬЗЯ использовать foreach
Есть несколько ситуаций, когда foreach не подходит и нужно использовать for:
❌ Ситуация 1: Нужен индекс элемента
foreach не предоставляет индекс текущего элемента. Если нужно знать позицию — используй for.
// foreach — не даст индекс
// for — даёт
for (int i = 0; i < arr.Length; i++)
Console.WriteLine($"Элемент {i}: {arr[i]}");
❌ Ситуация 2: Нужно изменить коллекцию
Добавление, удаление, изменение элементов во время foreach — ошибка.
// foreach — InvalidOperationException
// for — можно удалять (особенно с конца)
❌ Ситуация 3: Нужен обратный проход
foreach всегда идёт от начала к концу. Нельзя перебирать с конца.
// for — обратный проход
for (int i = arr.Length - 1; i >= 0; i--)
Console.WriteLine(arr[i]);
❌ Ситуация 4: Нестандартный шаг
foreach перебирает все элементы подряд. Нельзя взять каждый второй или каждый третий.
// for — шаг 2
for (int i = 0; i < arr.Length; i += 2)
Console.WriteLine(arr[i]);
foreach vs for: сравнение
Практические примеры
Пример 1: Поиск элемента
List<int> numbers = new() { 5, 12, 8, 21, 3, 17 };
bool found = false;
foreach (int num in numbers)
{
if (num == 21)
{
found = true;
break; // можно использовать break в foreach
}
}
Console.WriteLine(found ? "Нашли 21!" : "Не нашли");
// break работает и в foreach: прерывает перебор
Пример 2: Подсчёт суммы и среднего
int[] scores = { 85, 92, 78, 95, 88 };
int sum = 0;
int count = 0;
foreach (int score in scores)
{
sum += score;
count++;
}
double average = (double)sum / count;
Console.WriteLine($"Сумма: {sum}, Среднее: {average:F2}");
Пример 3: Фильтрация
List<string> words = new() { "C#", "Java", "Python", "JavaScript", "Go" };
// Выбрать все слова длиннее 3 символов
List<string> longWords = new();
foreach (string word in words)
{
if (word.Length > 3)
{
longWords.Add(word);
}
}
Console.WriteLine(string.Join(", ", longWords)); // "Python, JavaScript"
// То же самое через LINQ (будет в уроке 6.3):
// var longWords = words.Where(w => w.Length > 3).ToList();
Большой пример: анализ текста
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
string text = "C# — это современный язык программирования. "
+ "C# используется для веб-разработки, "
+ "мобильных приложений и игр на Unity.";
// 1. Подсчёт символов
int charCount = 0;
int letterCount = 0;
int digitCount = 0;
foreach (char c in text)
{
charCount++;
if (char.IsLetter(c)) letterCount++;
if (char.IsDigit(c)) digitCount++;
}
Console.WriteLine($"Всего символов: {charCount}");
Console.WriteLine($"Букв: {letterCount}");
Console.WriteLine($"Цифр: {digitCount}");
// 2. Разбиваем на слова и анализируем
string[] words = text.Split(' ', ',', '.', '—');
List<string> cleanWords = new();
foreach (string word in words)
{
string trimmed = word.Trim();
if (trimmed.Length > 0)
cleanWords.Add(trimmed);
}
Console.WriteLine($"\nВсего слов: {cleanWords.Count}");
Console.WriteLine("Слова:");
foreach (string word in cleanWords)
{
Console.WriteLine($" - {word} (длина: {word.Length})");
}
// 3. Самое длинное слово
string longest = "";
foreach (string word in cleanWords)
{
if (word.Length > longest.Length)
longest = word;
}
Console.WriteLine($"\nСамое длинное слово: '{longest}'");
}
}
Что важно запомнить
foreach (int item in collection). Не думай об индексах.foreach (var kvp in dict) или foreach (var (key, value) in dict).Тест: 6.2: Цикл foreach
3 вопроса