• C# i .NET
  • Stos Stack w C# - kiedy używać Pop, Peek i TryPop

Stos Stack w C# - kiedy używać Pop, Peek i TryPop

Przemysław Kwiatkowski • 7 listopada 2026
Schematyczne przedstawienie struktury danych stos. Pokazuje operacje "push" (dodawanie) i "pop" (usuwanie) elementów.

Spis treści

Przywracasz poprzedni stan edytora albo obsługujesz nawiasy w wyrażeniu i potrzebujesz pobrać ostatnio dodany element. W C# zwykle sięgasz wtedy po stos typu LIFO, choć wybór metody i sposób obsługi pustej kolekcji zależą od tego, co program ma zrobić dalej.

LIFO diagram Push Pop Peek

Stos oddaje najpierw element dodany jako ostatni

  • Push dodaje element na wierzch, a Pop usuwa i zwraca ten element.
  • Peek pozwala podejrzeć wierzch bez zmiany kolekcji.
  • TryPop zwraca informację, czy udało się pobrać element, zamiast zgłaszać wyjątek dla pustego stosu.
  • Stack nie synchronizuje dostępu między wątkami; przy współbieżnych operacjach rozważ ConcurrentStack.

Push i Pop odwracają kolejność pobierania

Wyobraź sobie historię zmian w edytorze. Na stos trafiają kolejne stany dokumentu, a cofnięcie powinno przywrócić ten zapisany jako ostatni. Właśnie tak działa zasada LIFO: „ostatni wszedł, pierwszy wyszedł”.

W .NET gotową implementacją jest Stack z przestrzeni nazw System.Collections.Generic. Push dodaje element, Pop usuwa go z wierzchu, a Peek odczytuje wierzch bez usuwania. Prawdę mówiąc, łatwo pomylić ostatnie dwie operacje, a różnica staje się widoczna dopiero przy następnym odczycie.

var history = new Stack();

history.Push("Dokument A");
history.Push("Dokument B");

string current = history.Peek(); // Dokument B, stos bez zmian
string previous = history.Pop(); // Dokument B, teraz usunięty

Operacje na wierzchu są szybkie. Dokumentacja .NET opisuje Pop jako operację O(1); Push zwykle także ma koszt stały, ale gdy zabraknie miejsca w wewnętrznej tablicy, kolekcja musi ją powiększyć i skopiować elementy. Dla typowego użycia nie trzeba temu zapobiegać, lecz przy znanej liczbie elementów można ustawić pojemność konstruktorem.

TryPop wybiera bezpieczną ścieżkę dla pustego stosu

Gdy historia zmian może być pusta, wywołanie Pop bez sprawdzenia kończy się InvalidOperationException. Można wcześniej sprawdzić Count, ale gdy celem jest po prostu pobranie elementu, TryPop łączy próbę z obsługą wyniku.

if (history.TryPop(out string? previous))
{
    Console.WriteLine($"Przywracam: {previous}");
}
else
{
    Console.WriteLine("Brak wcześniejszego stanu.");
}

Prosta reguła pomaga dobrać metodę: jeśli pusty stos oznacza błąd programu, użyj Pop i pozwól, by błąd był widoczny; jeśli brak elementu jest normalnym wynikiem, wybierz TryPop. Podobnie TryPeek sprawdza wierzch bez usuwania go.

Sytuacja Skutek Co zrobić
Stos może być pusty, a brak elementu jest normalny Pop zgłosi wyjątek Użyj TryPop i obsłuż wynik false
Brak elementu wskazuje na błąd logiki Warunek może ukryć nieoczekiwany stan Użyj Pop i potraktuj wyjątek jako sygnał błędu
Chcesz sprawdzić następny element i zachować go w kolekcji Pop zmieni stan stosu Użyj Peek albo TryPeek

Enumerowanie pokazuje elementy od wierzchu i nie opróżnia stosu

Popularne nieporozumienie brzmi: przejście pętlą po stosie pobiera z niego elementy. W Stack enumeracja nie usuwa danych, a kolejność przejścia zaczyna się od wierzchu, czyli od elementu dodanego najpóźniej.

var stack = new Stack();
stack.Push(10);
stack.Push(20);
stack.Push(30);

foreach (int item in stack)
{
    Console.WriteLine(item); // 30, 20, 10
}

Console.WriteLine(stack.Count); // 3

Microsoft Learn opisuje enumerator jako nieważny po zmianie kolekcji: jeśli dodasz lub usuniesz element w trakcie pętli, kolejne przejście może zgłosić wyjątek. Gdy naprawdę chcesz opróżnić stos, pobieraj elementy przez TryPop lub Pop, zamiast polegać na samym foreach.

Queue i ConcurrentStack rozwiązują inne problemy

Stos pasuje do cofania ostatniej zmiany, śledzenia nawiasów czy przechodzenia grafu w głąb. Jeśli zadania mają być obsługiwane w kolejności nadejścia, potrzebujesz kolejki FIFO, czyli Queue: dodajesz przez Enqueue, pobierasz przez Dequeue. Wybór nie zależy od szybkości, tylko od kolejności, której wymaga logika.

Jest jeszcze przypadek wielowątkowy. Zwykły Stack nie synchronizuje równoczesnych odczytów i zapisów, więc współdzielona kolekcja wymaga blokady albo typu ConcurrentStack. Microsoft Learn wskazuje, że jego publiczne i chronione składowe są bezpieczne przy współbieżnym użyciu; to właściwy punkt wyjścia, gdy wiele wątków ma niezależnie dodawać lub pobierać elementy.

Uważam, że najczęstszy błąd przy wyborze stosu nie dotyczy samej implementacji, lecz pomylenia „ostatniego” z „najstarszym” elementem. Jeśli chcesz cofać historię lub wracać z najgłębszego miejsca algorytmu, LIFO jest naturalne; jeśli obsługujesz kolejkę żądań, wybierz FIFO.

Przed dodaniem elementu ustal, kto go później pobierze

W stosie szczegóły kolejności są częścią działania algorytmu, a nie ozdobą kolekcji. Przy cofaniu zmian zapisuj stan przed modyfikacją dokumentu, a przy przetwarzaniu nawiasów wkładaj na stos otwierające znaki i zdejmuj je po napotkaniu pasującego zamknięcia.

Jeśli elementów może być wiele, rozważ konstruktor z początkową pojemnością, by ograniczyć powiększanie tablicy. Gdy natomiast kolekcję współdzieli kilka wątków, zdecyduj o synchronizacji przed wdrożeniem operacji, bo samo zastąpienie pętli przez foreach nie czyni dostępu bezpiecznym.

FAQ - Najczęstsze pytania

Pop usuwa element z wierzchu stosu i go zwraca. Peek zwraca ten sam element, ale pozostawia go w kolekcji, więc przy kolejnym odczycie nadal będzie na wierzchu.

Użyj TryPop, które zwraca false, gdy stos jest pusty, zamiast zgłaszać wyjątek. Jeśli pobranie jest możliwe, metoda zwraca true i przekazuje element przez parametr out. Zwykłe Pop na pustym stosie zgłasza InvalidOperationException.

Enumeracja zaczyna się od wierzchu stosu, czyli od elementu dodanego najpóźniej, i nie usuwa danych. Dla stosu, na który kolejno dodano 10, 20 i 30, pętla wypisze 30, 20, 10, a liczba elementów pozostanie równa 3. Zmiana kolekcji w trakcie enumeracji może unieważnić enumerator.

Wybierz Stack<T>, gdy potrzebujesz pobierać najnowszy element jako pierwszy, na przykład przy cofaniu zmian lub przechodzeniu grafu w głąb. Użyj Queue<T>, gdy elementy mają być obsługiwane w kolejności nadejścia: dodawane przez Enqueue i pobierane przez Dequeue.

Zwykły Stack<T> nie synchronizuje równoczesnych odczytów i zapisów. Przy współbieżnym dostępie użyj blokady albo rozważ ConcurrentStack<T>, którego publiczne i chronione składowe są bezpieczne przy współbieżnym użyciu.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

lifo
enumeracja
współbieżność
stos
Autor Przemysław Kwiatkowski
Przemysław Kwiatkowski
Jestem Przemysław i od 15 lat zajmuję się programowaniem .NET, chmurą Azure oraz sztuczną inteligencją. Moja przygoda z tymi technologiami zaczęła się od fascynacji możliwościami, jakie dają, a z czasem przerodziła się w pasję do tworzenia rozwiązań, które realnie wpływają na pracę i życie ludzi. Na kursdotnet.pl staram się dzielić się swoją wiedzą w sposób przystępny, tłumacząc złożone zagadnienia i pomagając zrozumieć, jak te dynamicznie rozwijające się obszary IT mogą być wykorzystane w praktyce. Dokładam wszelkich starań, aby prezentowane przeze mnie materiały były rzetelne, aktualne i oparte na sprawdzonych źródłach, a także aby uporządkować wiedzę w sposób ułatwiający jej przyswojenie.

Udostępnij artykuł

Napisz komentarz