🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Pętle w C# — for, while, foreach

Pętle w języku C# są używane do wielokrotnego wykonywania określonego bloku kodu. Pozwalają na automatyzację powtarzalnych zadań, przetwarzanie kolekcji danych i kontrolowanie przepływu programu w zależności od warunków. C# udostępnia cztery instrukcje iteracyjne: forwhiledo…while i foreach — każda ma osobny, dedykowany artykuł z pełną składnią, przykładami i pułapkami. Tutaj znajdziesz szybkie porównanie oraz tematy, które wykraczają poza pojedynczą pętlę: zagnieżdżanie, własne iteratory, przetwarzanie równoległe i wydajność.

Cztery pętle C# — szybkie porównanie

PętlaWarunek sprawdzanyMin. liczba wykonań ciałaKiedy używać
forprzed iteracją0znasz liczbę powtórzeń lub potrzebujesz indeksu
whileprzed iteracją0liczba powtórzeń zależy od warunku sprawdzanego w locie
do…whilepo iteracji1ciało musi wykonać się co najmniej raz (menu, walidacja)
foreach0 (dla pustej kolekcji)przechodzisz po wszystkich elementach, indeks niepotrzebny

Pełną składnię, krok po kroku działania i typowe pułapki każdej z nich znajdziesz w dedykowanym artykule — linki w tabeli powyżej.

Pętle zagnieżdżone

Pętlę można umieścić wewnątrz innej pętli — to pozwala na bardziej złożone struktury iteracyjne, np. przetwarzanie danych dwuwymiarowych (macierze, plansze gier, tabliczki):

for (int i = 1; i <= 10; i++)
{
    for (int j = 1; j <= 10; j++)
    {
        int wynik = i * j;
        Console.Write($"{wynik,4}");
    }
    Console.WriteLine(); // przejście do nowego wiersza po każdym rzędzie
}

Powyższy kod generuje tabliczkę mnożenia 10×10. Uwaga na złożoność: dwie zagnieżdżone pętle po n elementów to O(n²) — przy dużych zbiorach danych warto rozważyć, czy nie da się tego zrobić wydajniej (np. przez indeksowanie, słownik zamiast zagnieżdżonego przeszukiwania).

Własne iteratory — yield return

Słowo kluczowe yield return pozwala napisać własną metodę, po której można iterować pętlą foreach, bez ręcznego implementowania IEnumerator. Kompilator sam generuje maszynę stanów obsługującą MoveNext():

public class MojKontener
{
    private int[] dane = { 1, 2, 3, 4, 5 };

    public IEnumerable<int> PodwojoneWartosci()
    {
        foreach (int element in dane)
        {
            yield return element * 2;
        }
    }
}

var kontener = new MojKontener();
foreach (int wartosc in kontener.PodwojoneWartosci())
{
    Console.WriteLine(wartosc); // 2, 4, 6, 8, 10
}

Kluczowa różnica względem zwykłej metody zwracającej List<int>yield return generuje elementy leniwie (lazy evaluation) — kolejna wartość liczy się dopiero, gdy pętla `foreach` faktycznie o nią poprosi. To pozwala przetwarzać strumienie danych, które nie zmieściłyby się naraz w pamięci.

Pętle równoległe — Parallel.For i Parallel.ForEach

Gdy masz wiele niezależnych operacji, które można wykonać jednocześnie, biblioteka System.Threading.Tasks.Parallel pozwala rozdzielić iteracje pętli między dostępne rdzenie procesora:

Parallel.For(0, 1000, i =>
{
    PrzetworzElement(i);
});

Parallel.ForEach(produkty, produkt =>
{
    PrzetworzProdukt(produkt);
});

To przyspiesza kod tylko wtedy, gdy iteracje są niezależne i wystarczająco kosztowne, by zrównoważyć narzut na zarządzanie wątkami. Jeśli iteracje modyfikują współdzielony stan (np. tę samą zmienną licznika czy kolekcję) bez synchronizacji, `Parallel.For` wprowadzi race condition zamiast przyspieszenia — to najczęstszy błąd przy pierwszym kontakcie z tą klasą.

LINQ jako alternatywa dla pętli

Wiele pętli filtrujących lub transformujących dane da się zastąpić czytelniejszym zapytaniem LINQ:

List<int> liczby = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// zamiast pętli for + if
var parzyste = liczby.Where(n => n % 2 == 0).ToList();

// zamiast pętli for + nowa lista + Add
var podwojone = liczby.Select(n => n * 2).ToList();

LINQ bywa czytelniejszy dla prostych transformacji, ale ma swój narzut (delegaty, czasem alokacje enumeratorów) — w kodzie wykonywanym miliony razy na sekundę (hot path) zwykła pętla for zwykle wygrywa wydajnością. Wybieraj LINQ dla czytelności w kodzie biznesowym, a jawną pętlę tam, gdzie liczy się każda mikrosekunda.

Operacje asynchroniczne w pętli

W kodzie async/await częstym błędem jest czekanie (await) na każdą operację po kolei wewnątrz pętli — to wykonuje zadania sekwencyjnie, mimo że mogłyby biec równolegle. Lepszy wzorzec: uruchom wszystkie zadania, a potem poczekaj na komplet naraz:

var tasks = new List<Task<int>>();

for (int i = 0; i < 10; i++)
{
    tasks.Add(ObliczCosAsync(i)); // uruchamia zadanie, nie czeka na nie od razu
}

int[] wyniki = await Task.WhenAll(tasks);

foreach (var wynik in wyniki)
{
    Console.WriteLine(wynik);
}

Gdybyś zamiast tego napisał foreach (...) { var wynik = await ObliczCosAsync(i); }, każde wywołanie czekałoby na poprzednie — dla dziesięciu operacji trwających po sekundę to różnica między ~1 sekundą (równolegle) a ~10 sekundami (sekwencyjnie).

Anulowanie pętli tokenem

W długo trwających pętlach warto dać możliwość eleganckiego przerwania z zewnątrz (np. gdy użytkownik anuluje operację) za pomocą CancellationToken, zamiast flag boolowskich ustawianych ręcznie:

void PrzetworzDane(CancellationToken token)
{
    for (int i = 0; i < 1_000_000; i++)
    {
        token.ThrowIfCancellationRequested(); // rzuca OperationCanceledException, gdy poproszono o anulowanie
        // przetwarzanie elementu i
    }
}

Wyjątki w pętlach

Wyjątek rzucony wewnątrz pętli przerywa ją całkowicie, chyba że złapiesz go w try/catch wewnątrz ciała pętli — wtedy pętla może kontynuować kolejne iteracje mimo błędu w jednej z nich:

foreach (var plik in pliki)
{
    try
    {
        PrzetworzPlik(plik);
    }
    catch (IOException ex)
    {
        Console.WriteLine($"Pominięto {plik}: {ex.Message}");
        // pętla kontynuuje z kolejnym plikiem, zamiast się przerwać
    }
}

Wydajność pętli — praktyczne zasady

  • Unikaj alokacji pamięci wewnątrz pętli (np. tworzenia nowych list czy stringów przy każdej iteracji) — wynoś je poza pętlę, gdy to możliwe.
  • Wartości stałe niezależne od iteracji licz raz, przed pętlą, zamiast przeliczać je w każdym obiegu.
  • Dla dużych kolekcji preferuj foreach nad List<T> — enumerator jest strukturą i nie alokuje niczego na stercie (więcej w artykule o foreach).
  • Zanim sięgniesz po Parallel.For, zmierz — narzut na wątki bywa większy niż zysk dla małych, tanich iteracji.

Podsumowanie

Pętle są niezbędnym narzędziem w programowaniu — pozwalają na wielokrotne wykonywanie kodu, przetwarzanie kolekcji i automatyzację powtarzalnych czynności. Wybór właściwej pętli (forwhiledo…while czy foreach) to głównie kwestia czytelności intencji, a nie wydajności — szczegóły każdej z nich znajdziesz w dedykowanych artykułach linkowanych w tabeli na początku tego wpisu.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

7 comments

  1. Świetny przegląd pętli w C#! Dobra robota! Szkoda tylko, że nie wspomniałeś o tym, że pętle foreach mogą działać także na niestandardowych kolekcjach implementujących interfejs IEnumerable. To dla mnie zawsze było fascynujące.

  2. Bardzo pomocny przewodnik! Cenię sobie szczególnie fragment dotyczący optymalizacji pętli. Unikanie zbędnych operacji i stosowanie pętli foreach tam, gdzie to możliwe, naprawdę może wpłynąć na wydajność kodu. Dzięki za podzielenie się tymi praktykami!

  3. Dla mnie, jako nowicjusza, te przykłady są naprawdę pomocne! Szczególnie podoba mi się sposób przedstawiania składni pętli for i while. Czy mogę prosić o więcej przykładów zastosowania pętli do…while? To dla mnie nadal trochę zagadkowe.

  4. Super, że wspomniałeś o pętlach równoległych! W analizie danych często mamy do czynienia z dużymi zestawami danych, więc korzystanie z Parallel.ForEach potrafi naprawdę przyspieszyć przetwarzanie. Dzięki za przypomnienie!

  5. Fajne wskazówki dotyczące użycia Task w pętlach! W asynchronicznym kodzie zawsze zastanawiałem się, jak efektywnie zarządzać wieloma operacjami jednocześnie. Teraz to wygląda znacznie bardziej jasno. Czekam na więcej artykułów o programowaniu asynchronicznym!

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →