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

Pętla foreach w C# — jak działa pod maską

Pętla foreach służy do wygodnego przejścia po wszystkich elementach tablicy, łańcucha znaków lub kolekcji — bez ręcznego operowania indeksem.

Jak działa foreach

Pętla pobiera kolejno każdy element kolekcji i udostępnia go w zmiennej iteracyjnej. Jej największa zaleta to brak ryzyka przekroczenia indeksu tablicy lub bufora — nie musisz pilnować zakresu ani długości. Działa z każdą kolekcją implementującą IEnumerable<T> (tablice, listy, słowniki itd.) i świetnie sprawdza się z wynikami zapytań LINQ.

Składnia

foreach (typ identyfikator in kolekcja)
{
    // instrukcje wykonywane dla każdego elementu
}
  • kolekcja – tablica lub kolekcja, po której iterujemy (wymagane).
  • typ – typ pojedynczego elementu (możesz użyć var, by kompilator wywnioskował go sam).
  • identyfikator – nazwa zmiennej reprezentującej bieżący element.

Przykłady

// iteracja po znakach łańcucha
string tekst = "Abc";
foreach (char znak in tekst)
{
    Console.WriteLine(znak);   // A, b, c
}

// iteracja po liście
List<string> imiona = new() { "Anna", "Marek", "Ola" };
foreach (string imie in imiona)
{
    Console.WriteLine(imie);
}

Co wolno, a czego nie wolno w foreach

To częste źródło nieporozumień, więc rozdzielmy dwie rzeczy:

  • Nie wolno zmieniać struktury kolekcji w trakcie iteracji — dodawanie czy usuwanie elementów rzuci InvalidOperationException („Collection was modified”).
  • Nie wolno przypisać nowej wartości zmiennej iteracyjnej — jest ona tylko do odczytu.
  • Wolno natomiast modyfikować właściwości elementów, jeśli są to typy referencyjne (obiekty) — zmieniasz wtedy stan obiektu, a nie samą kolekcję.
// ✅ modyfikacja właściwości obiektu (typ referencyjny) — to działa
foreach (var produkt in produkty)
{
    produkt.Cena *= 1.23m;   // zmieniamy stan istniejącego obiektu
}

// ❌ zmiana struktury kolekcji w trakcie iteracji — wyjątek w czasie działania
foreach (var produkt in produkty)
{
    produkty.Remove(produkt);   // InvalidOperationException
}

Jeśli musisz usuwać elementy podczas przechodzenia, użyj pętli for idącej od końca, metody RemoveAll(...) albo iteruj po kopii kolekcji (np. foreach (var p in produkty.ToList())).

Potrzebujesz indeksu? Użyj LINQ

Gdy mimo wszystko potrzebujesz numeru elementu, możesz połączyć foreach z metodą Select:

foreach (var (imie, index) in imiona.Select((wartosc, i) => (wartosc, i)))
{
    Console.WriteLine($"{index + 1}. {imie}");
}

Jak foreach działa pod maską

Kompilator tłumaczy foreach na wywołania metody GetEnumerator() kolekcji oraz pętlę korzystającą z MoveNext() i Current zwróconego enumeratora. Powyższy przykład z listą imion kompiluje się w przybliżeniu do:

using var enumerator = imiona.GetEnumerator();
while (enumerator.MoveNext())
{
    string imie = enumerator.Current;
    Console.WriteLine(imie);
}

To dlatego zmiana struktury kolekcji w trakcie iteracji rzuca wyjątek — większość wbudowanych kolekcji śledzi wersję (licznik modyfikacji) i MoveNext() sprawdza go przy każdym kroku, wykrywając niespójność.

Co ciekawe, List<T> zwraca enumerator jako strukturę (value type), a nie klasę — dzięki temu iterowanie po niej w foreach nie alokuje nic na stercie. Jeśli jednak przypiszesz tę samą kolekcję do zmiennej typu interfejsu IEnumerable<T>, kompilator musi wywoływać metody przez interfejs, co powoduje boxing struktury enumeratora i drobny narzut wydajnościowy. W typowym kodzie biznesowym ta różnica jest pomijalna, ale w gorących ścieżkach wykonania (hot paths) bywa zauważalna.

Kiedy używać foreach?

Wybieraj foreach, gdy chcesz po prostu przejść po wszystkich elementach i indeks nie jest potrzebny — kod jest wtedy czytelniejszy i odporny na błędy zakresu. Gdy potrzebujesz indeksu, iterujesz wstecz lub modyfikujesz kolekcję — sięgnij po for.

💡 W Visual Studio: wpisz foreach i naciśnij dwukrotnie Tab, aby wstawić szkielet pętli.

Jeśli potrzebujesz indeksu iteracji albo pełnej kontroli nad krokiem pętli, zobacz Pętla for w C#. Przegląd wszystkich pętli w C# znajdziesz w Pętle w C#.

FAQ

Dlaczego dostaję InvalidOperationException tylko czasem, gdy modyfikuję kolekcję w foreach?
Bo kolekcja wykrywa modyfikację przy kolejnym wywołaniu MoveNext() — jeśli usuniesz ostatni element w ostatniej iteracji, pętla może się zakończyć zanim licznik wersji zostanie sprawdzony ponownie. To niebezpieczne poleganie na przypadku, nie gwarantowane zachowanie — zawsze traktuj modyfikację kolekcji w foreach jako błąd, niezależnie od tego, czy akurat rzuci wyjątek.

Czy foreach działa na własnych typach?
Tak, o ile typ implementuje IEnumerable/IEnumerable<T> (albo ma publiczną metodę GetEnumerator() — kompilator nie wymaga formalnie interfejsu, tzw. duck typing). Najprostszy sposób to użycie słowa kluczowego yield return we własnej metodzie zwracającej IEnumerable<T>.

👨‍💻
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.

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ę →