$ sudo teach IT
Модуль 6 · Цикл for

Урок 6.2 — Цикл foreach

Безопасный обход коллекций без индексов. Просто и элегантно.

Цикл foreach создан для одной задачи: перебрать все элементы коллекции один за другим. В отличие от for, тебе не нужно думать об индексах, длине коллекции и шаге. Просто: «возьми каждый элемент и сделай с ним что-то». foreach — это синтаксический сахар, который делает код чище и безопаснее.

📝

Синтаксис foreach

foreach (тип переменная in коллекция) { тело }

C# · foreach с массивом
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 с разными коллекциями

Массивы

C# · Массив
string[] names = { "Анна", "Борис", "Вика" };

foreach (string name in names)
{
    Console.WriteLine($"Привет, {name}!");
}

Списки List<T>

C# · List
List<double> prices = new() { 99.9, 149.5, 299.0, 89.5 };

foreach (double price in prices)
{
    Console.WriteLine($"Цена: {price} руб.");
}

Строки (перебор символов)

Строка — это коллекция символов char, поэтому её тоже можно перебирать foreach:

C# · Строка
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 и другие коллекции

C# · HashSet
HashSet<int> unique = new() { 1, 2, 3, 4, 5 };

foreach (int num in unique)
{
    Console.WriteLine(num);
}
// Порядок НЕ гарантируется, но все элементы будут выведены
🔑

foreach со словарём Dictionary

Со словарём foreach возвращает пары KeyValuePair<TKey, TValue>:

C# · Dictionary
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:

C# · Ошибка изменения
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 ты можешь изменить поля объекта, но не заменить его:

C# · Изменение элементов
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: сравнение

foreach
for
Читаемый и безопасный
Гибкий, но многословный
Нет доступа к индексу
Есть доступ к индексу
Нельзя изменять коллекцию
Можно изменять коллекцию
Только прямой проход
Любой порядок и шаг
Элемент — read-only (для value)
Элемент — изменяемый
Работает с любой коллекцией
Только по индексу (IList)
🧩

Практические примеры

Пример 1: Поиск элемента

C# · Поиск
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: Подсчёт суммы и среднего

C# · Сумма
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: Фильтрация

C# · Фильтрация
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();
🧩

Большой пример: анализ текста

C# · Анализ текста
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}'");
    }
}
📌

Что важно запомнить

1️⃣
foreach — безопасный и читаемый перебор всех элементов. foreach (int item in collection). Не думай об индексах.
2️⃣
Нельзя изменять коллекцию во время foreach (добавлять/удалять). Используй for для модификации.
3️⃣
foreach не даёт индекс. Если нужен индекс, обратный проход или нестандартный шаг — используй for.
4️⃣
Со словарём перебирай пары: foreach (var kvp in dict) или foreach (var (key, value) in dict).
5️⃣
break и continue работают и в foreach. Используй break для досрочного выхода.

Тест: 6.2: Цикл foreach

3 вопроса

Вложенные циклы

Premium