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: for, while, do…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ętla | Warunek sprawdzany | Min. liczba wykonań ciała | Kiedy używać |
|---|---|---|---|
for | przed iteracją | 0 | znasz liczbę powtórzeń lub potrzebujesz indeksu |
while | przed iteracją | 0 | liczba powtórzeń zależy od warunku sprawdzanego w locie |
do…while | po iteracji | 1 | ciało musi wykonać się co najmniej raz (menu, walidacja) |
foreach | — | 0 (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
foreachnadList<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 (for, while, do…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.
🚀 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.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
7 comments
Dodaj komentarz
Musisz się zalogować, aby móc dodać komentarz.
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ę →
Ś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.
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!
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.
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!
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!