• C# i .NET
  • Goto w C# - kiedy ma sens, a kiedy lepiej go unikać?

Goto w C# - kiedy ma sens, a kiedy lepiej go unikać?

Radosław Krajewski 25 lipca 2026
Przykład użycia `c# goto` do powtórzenia kodu. Instrukcja `goto label 1` przenosi wykonanie do `label 1`.

Spis treści

Czasem trzeba natychmiast opuścić kilka zagnieżdżonych bloków kodu albo przejść do konkretnej gałęzi instrukcji switch. W takich sytuacjach instrukcja goto w C# może być użyteczna, ale tylko wtedy, gdy jej działanie pozostaje oczywiste dla kolejnej osoby czytającej kod. Pokażę składnię, praktyczne przykłady, ograniczenia oraz sytuacje, w których lepiej wybrać pętlę, break, return albo osobną metodę.

Najważniejsze informacje o instrukcji goto w C#

  • goto przenosi wykonanie do nazwanej etykiety w obrębie tej samej metody.
  • W C# dostępne są trzy warianty: skok do etykiety, goto case oraz goto default.
  • Najbardziej uzasadnione zastosowania to wyjście z kilku pętli i współdzielenie logiki w switch.
  • Kompilator nie pozwoli na skok, który omija inicjalizację lokalnej zmiennej albo wchodzi w niedozwolony zakres.
  • W większości kodu aplikacyjnego czytelniejsze będą pętle, warunki, metody i wcześniejszy return.

Czym jest goto i jak działa w C#

goto to instrukcja skoku, która przekazuje sterowanie do wskazanego miejsca oznaczonego etykietą. Etykieta jest nazwą zakończoną dwukropkiem, a jej użycie wygląda tak:

goto koniec;

Console.WriteLine("Ten kod zostanie pominięty.");

koniec:
Console.WriteLine("Wykonanie trafia tutaj.");

W tym przykładzie program przechodzi bezpośrednio do etykiety koniec. Dla mnie najważniejsza zasada brzmi prosto: miejsce docelowe skoku musi być widoczne bez analizowania całej metody. Gdy etykiet zaczyna przybywać, kod szybko traci przewidywalny przepływ.

Etykieta działa tylko w obrębie tej samej metody. Nie można przejść za pomocą goto do innej metody, klasy ani pliku. Skok nie tworzy też nowego zakresu i nie wywołuje żadnej funkcji pomocniczej.

Instrukcja nie jest mechanizmem optymalizacji. Współczesny kompilator C# i środowisko uruchomieniowe .NET potrafią bardzo dobrze optymalizować zwykłe konstrukcje sterujące, dlatego używanie goto wyłącznie z myślą o wydajności zazwyczaj nie ma sensu. Czytelność przepływu powinna być ważniejsza niż hipotetyczna oszczędność kilku instrukcji.

Trzy odmiany skoku, które warto znać

W praktyce słowo kluczowe goto występuje w C# w trzech formach. Pierwsza jest ogólna, a dwie pozostałe zostały zaprojektowane specjalnie do pracy z instrukcją switch.

Skok do własnej etykiety

Najbardziej ogólny zapis to goto nazwaEtykiety;. Etykieta musi znajdować się w tej samej metodzie i wskazywać poprawną instrukcję:

int liczba = 7;

if (liczba > 0)
{
    goto dodatnia;
}

Console.WriteLine("Liczba nie jest dodatnia.");

dodatnia:
Console.WriteLine("Liczba jest dodatnia.");

Technicznie kod działa poprawnie, ale w tak prostym przypadku zwykły if jest lepszym wyborem. Ten przykład pokazuje składnię, nie wzorzec, który warto kopiować do aplikacji.

Przejście do innej gałęzi switch

goto case pozwala przejść do istniejącej gałęzi case. To przydatne, gdy kilka wartości ma wykonać część wspólnej logiki, ale nie chcesz jej kopiować:

int poziom = 3;
int punkty = 0;

switch (poziom)
{
    case 3:
        punkty += 30;
        goto case 2;

    case 2:
        punkty += 20;
        goto case 1;

    case 1:
        punkty += 10;
        break;

    default:
        punkty = 0;
        break;
}

Console.WriteLine(punkty);

Dla wartości 3 program wykona kolejno kod z przypadków 3, 2 i 1. To ważne, ponieważ C# nie pozwala na przypadkowe przechodzenie z jednego niepustego case do następnego, jak zdarza się w niektórych innych językach.

Przeczytaj również: Pętla do-while w C# - składnia, przykłady i pułapki

Przejście do default

W obrębie switch można również użyć goto default;. Taki zapis przekazuje sterowanie do domyślnej gałęzi:

string kod = "X";

switch (kod)
{
    case "A":
        Console.WriteLine("Poprawny kod A.");
        break;

    case "B":
        Console.WriteLine("Poprawny kod B.");
        break;

    default:
        Console.WriteLine("Nieznany kod.");
        break;
}

W tym przykładzie nie ma potrzeby stosowania goto default, ale wariant bywa przydatny w bardziej złożonych regułach. Kompilator sprawdza, czy wskazany case rzeczywiście istnieje i czy jego wartość pasuje do typu użytego przez switch.

Praktyczne zastosowania, w których goto może mieć sens

Najbardziej przekonującym przypadkiem jest przerwanie kilku zagnieżdżonych pętli po znalezieniu wyniku. Zwykły break opuszcza tylko jedną, najbliższą pętlę, więc bezpośredni skok może uprościć kod:

int[,] macierz =
{
    { 4, 8, 2 },
    { 9, 1, 6 },
    { 7, 3, 5 }
};

int szukanaWartosc = 1;
bool znaleziono = false;

for (int wiersz = 0; wiersz < macierz.GetLength(0); wiersz++)
{
    for (int kolumna = 0; kolumna < macierz.GetLength(1); kolumna++)
    {
        if (macierz[wiersz, kolumna] == szukanaWartosc)
        {
            znaleziono = true;
            goto koniecWyszukiwania;
        }
    }
}

koniecWyszukiwania:
Console.WriteLine($"Znaleziono: {znaleziono}");

Ten wzorzec jest czytelny, jeśli etykieta znajduje się tuż za pętlami, a skok ma jeden jasno opisany cel. W aplikacji produkcyjnej rozważyłbym też wydzielenie wyszukiwania do metody i użycie return. Często daje to jeszcze lepszy interfejs, bo metoda może od razu zwrócić współrzędne znalezionego elementu.

Drugim sensownym miejscem jest współdzielenie obsługi w switch. Zamiast powielać te same instrukcje dla kilku wartości, można skierować wykonanie do jednego case. To szczególnie przydatne przy prostych mapowaniach poziomów, statusów lub kodów liczbowych.

goto można spotkać także w kodzie generowanym automatycznie, parserach i niskopoziomowych fragmentach bibliotek. Tam przepływ bywa celowo projektowany jako maszyna stanów. Nie oznacza to jednak, że ręczne kopiowanie takiego stylu do warstwy biznesowej będzie dobrym pomysłem.

Ograniczenia i błędy, o których łatwo zapomnieć

Najczęstszy błąd polega na próbie przeskoczenia do miejsca, w którym zmienna lokalna nie została jeszcze poprawnie zainicjalizowana. C# pilnuje, aby skok nie omijał obowiązkowego przygotowania danych:

goto etykieta;

int wynik = 10;

etykieta:
Console.WriteLine(wynik);

Taki kod nie przejdzie kompilacji, ponieważ wykonanie może dotrzeć do etykiety bez wykonania deklaracji i inicjalizacji wynik. Nie da się użyć goto do obchodzenia reguł dotyczących zakresu i definite assignment, czyli pewności, że zmienna ma przypisaną wartość.

Problemem jest również skakanie do środka bloku, w którym obowiązują lokalne zasady zakresu. Kompilator może odrzucić przejście, jeśli prowadzi ono do miejsca zależnego od pominiętej deklaracji, inicjalizatora albo innej konstrukcji sterującej.

Warto pamiętać o kilku praktycznych konsekwencjach:

  • etykieta musi znajdować się w tej samej metodzie,
  • skok wstecz może łatwo stworzyć nieskończoną pętlę,
  • duża liczba etykiet utrudnia debugowanie i analizę przepływu,
  • goto nie zastępuje obsługi wyjątków ani walidacji danych,
  • opuszczenie bloku try nadal respektuje działanie finally.

Ostatni punkt ma znaczenie przy sprzątaniu zasobów. Skok nie jest magicznym wyjściem omijającym wszystkie reguły języka, ale nadal komplikuje czytanie kodu, jeśli miesza się go z wyjątkami, blokadami i wieloma poziomami zagnieżdżenia.

Goto czy inna konstrukcja sterująca

Zanim użyję bezpośredniego skoku, sprawdzam, czy ten sam zamiar da się wyrazić konstrukcją, która sama opisuje kierunek przepływu. Tabela poniżej dobrze pokazuje praktyczne rozróżnienie.

Sytuacja Najczęściej lepszy wybór Dlaczego
Pominięcie bieżącej iteracji continue Od razu komunikuje, że pętla ma przejść do kolejnego elementu.
Wyjście z jednej pętli break Nie wymaga etykiety i ma jednoznaczny zakres działania.
Wyjście z metody z wynikiem return Zamyka metodę wprost i ogranicza liczbę zmiennych pomocniczych.
Powtarzanie operacji do spełnienia warunku while lub for Warunek i granice powtórzeń są widoczne w jednym miejscu.
Wspólna logika kilku gałęzi switch goto case lub wydzielona metoda Skok może uniknąć duplikacji, a metoda sprawdzi się przy większej logice.
Obsługa błędu wykonania try, catch i finally Wyjątki opisują problem, którego nie powinno się maskować skokiem.

Moja praktyczna reguła jest następująca: jeśli kierunek przejścia można nazwać konstrukcją języka, wybieram tę konstrukcję zamiast goto. return mówi „kończę metodę”, break mówi „kończę pętlę”, a continue mówi „pomijam tę iterację”. Samo goto mówi tylko „idź tam”, więc resztę trzeba odczytać z kodu.

Nie oznacza to, że każda obecność goto jest błędem. Pojedynczy skok do etykiety kończącej kilka pętli może być prostszy niż rozbudowany zestaw flag i warunków. Granica przebiega tam, gdzie kod przestaje być lokalny i trzeba śledzić wiele możliwych dróg wykonania.

Jak ocenić, czy użycie goto jest uzasadnione

Przed zatwierdzeniem takiego kodu zadaję sobie trzy pytania. Po pierwsze, czy etykieta ma jedno oczywiste znaczenie, na przykład „koniec wyszukiwania”? Po drugie, czy bez goto powstałoby więcej pomocniczych flag, zagnieżdżeń albo duplikacji? Po trzecie, czy osoba utrzymująca kod za miesiąc zrozumie przepływ bez uruchamiania debuggera?

Jeśli odpowiedź na któreś z tych pytań brzmi „nie”, lepiej wydzielić metodę albo przebudować warunki. Szczególnie ostrożnie podchodzę do skoków wstecznych, wielu etykiet w jednej metodzie oraz konstrukcji przypominających ręcznie napisany automat stanów.

W kodzie C# preferuję jawny przepływ i małe metody. goto zostawiam dla wąskich, dobrze opisanych przypadków, przede wszystkim dla przejść w switch i wyjścia z kilku poziomów pętli. Taka powściągliwość nie wynika z zakazu językowego, tylko z kosztu, jaki ponosi później czytelność.

Jedna zasada, która dobrze porządkuje użycie goto

Instrukcja goto jest legalnym i czasem przydatnym elementem C#, ale nie powinna być domyślnym sposobem budowania logiki. Najlepiej sprawdza się wtedy, gdy ma jeden krótki skok, jedną dobrze nazwaną etykietę i wyraźną korzyść w postaci mniejszej liczby zagnieżdżeń lub uniknięcia powielania kodu.

Jeżeli skok wymaga komentarza dłuższego niż sama metoda, to zwykle sygnał, że problem lepiej rozwiązać przez pętlę, return, osobną funkcję albo prostszy model danych. W C# nie chodzi o to, by nigdy nie używać goto, lecz by każdy taki skok miał łatwy do obrony powód.

FAQ - Najczęstsze pytania

Może być uzasadnione, gdy po znalezieniu wyniku trzeba natychmiast opuścić kilka poziomów pętli. Etykieta powinna znajdować się bezpośrednio za pętlami i mieć jednoznaczną nazwę, na przykład koniecWyszukiwania. Wydzielenie logiki do osobnej metody z użyciem return często będzie jeszcze czytelniejsze.

Można wykonać skok do własnej etykiety za pomocą goto nazwaEtykiety;, przejść do konkretnej gałęzi switch przez goto case albo skierować wykonanie do gałęzi domyślnej za pomocą goto default;. Wszystkie etykiety i wskazywane miejsca muszą znajdować się w tej samej metodzie.

Kompilator blokuje skok, który mógłby doprowadzić do użycia zmiennej bez wykonania jej deklaracji i inicjalizacji. Dotyczy to także przejść do niedozwolonego zakresu lub środka bloku zależnego od pominiętej deklaracji. Reguły te chronią pewność przypisania wartości do zmiennych.

continue służy do pominięcia bieżącej iteracji, break kończy najbliższą pętlę, a return zamyka metodę i może zwrócić wynik. Konstrukcje for i while lepiej opisują powtarzanie operacji, natomiast try, catch i finally służą do obsługi wyjątków, których nie należy zastępować skokiem.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

switch
etykiety
goto
pętle
przepływ sterowania
Autor Radosław Krajewski
Radosław Krajewski
Nazywam się Radosław Krajewski i od 6 lat zgłębiam tajniki programowania .NET, chmury Azure oraz sztucznej inteligencji. Moja przygoda z tymi technologiami zaczęła się od fascynacji tym, jak złożone problemy można rozwiązywać za pomocą kodu i innowacyjnych narzędzi. Staram się przekazywać tę wiedzę w sposób zrozumiały, dzieląc się swoimi doświadczeniami i spostrzeżeniami na kursdotnet.pl. W moich artykułach skupiam się na praktycznych aspektach, porównuję różne rozwiązania i analizuję najnowsze trendy, aby dostarczyć Wam rzetelne i aktualne informacje, które pomogą Wam rozwijać się w tej dynamicznie zmieniającej się dziedzinie.

Udostępnij artykuł

Napisz komentarz