CodeGym /Kursy /C# SELF /Składnia try-catch: obsługa błędów

Składnia try-catch: obsługa błędów

C# SELF
Poziom 13, Lekcja 1
Dostępny

1. Wprowadzenie

W idealnym świecie kod powinien być idealny. Ale niestety, nawet najbardziej ogarnięty programista kiedyś natknie się na niespodziewane sytuacje: nie znaleziono pliku, użytkownik wpisał tekst zamiast liczby, sieć nagle padła, albo coś wróciło z bazy danych nie tak, jak się spodziewaliśmy.

Jeśli takich problemów nie łapać — program po prostu się "wywali", czasem z tajemniczym komunikatem o błędzie i stack trace (stosem wywołań). To nie jest to, czego oczekuje użytkownik albo przyszły dział wsparcia. Trzeba nauczyć się "łapać" takie błędy i reagować na nie: spokojnie zakończyć działanie, wyświetlić zrozumiały komunikat albo nawet naprawić sytuację na bieżąco.

try-catch — to sposób, żeby powiedzieć kompilatorowi i środowisku uruchomieniowemu: "Spróbuj wykonać ten kawałek kodu. Jeśli pojawi się problem, nie panikuj, daj mi szansę ogarnąć!"

2. Podstawowa składnia try-catch

Ogólna struktura jest bardzo prosta:


try
{
    // Tutaj piszemy niebezpieczny (albo potencjalnie niebezpieczny) kod
}
catch (ExceptionType variableName)
{
    // Tutaj piszemy, co zrobić, jeśli pojawi się wyjątek typu ExceptionType
}
Podstawowa składnia try-catch
  • Blok try — to "strefa zagrożenia". Wrzucamy tam operacje, które potencjalnie mogą "wystrzelić".
  • Blok catch — łapie wyjątki określonego typu. Jeśli w środku try pojawi się wyjątek, sterowanie od razu przeskoczy do pierwszego pasującego catch, a kod po "problemowym miejscu" w try już się nie wykona.

Przykład: prosta obsługa wyjątku

Załóżmy, że nasz mini-kalkulator z poprzednich wykładów teraz umie dzielić liczby. Ale użytkownik może wpisać zero! Zobaczmy, jak to działa bez obsługi błędów:

int a = 10;
int b = 0;
int result = a / b; // BUM! System.DivideByZeroException
Console.WriteLine(result);

Program zakończy się awaryjnie z błędem DivideByZeroException:

Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.

Teraz "naprawimy" to przy pomocy try-catch:

int a = 10;
int b = 0;

try
{
    int result = a / b; // niebezpieczna operacja
    Console.WriteLine("Wynik dzielenia: " + result);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Ups! Dzielenie przez zero jest niemożliwe: " + ex.Message);
}

Teraz program się nie "wywali". W konsoli pojawi się komunikat o niemożności dzielenia przez zero:

Ups! Dzielenie przez zero jest niemożliwe: Attempted to divide by zero.

3. Jak działa try-catch: krok po kroku

Tak naprawdę w środku try może być dowolnie dużo linii kodu. Jeśli któraś z akcji w bloku wyrzuci wyjątek, sterowanie od razu przeskakuje do najbliższego pasującego catch. Wszystko, co napisane po miejscu błędu w bloku try, nie zostanie wykonane.

Zobaczmy większy przykład:

try
{
    Console.WriteLine("Zaczynamy...");
    int[] numbers = { 1, 2, 3 };
    Console.WriteLine(numbers[1]);    // to ok
    Console.WriteLine(numbers[5]);    // błąd: indeks poza zakresem tablicy
    Console.WriteLine("Ten komunikat już się nie pojawi!");
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Błąd: Próba dostępu do nieistniejącego elementu tablicy");
}

Wynik w konsoli będzie taki:

Zaczynamy...
2
Błąd: Próba dostępu do nieistniejącego elementu tablicy

Process finished with exit code 0.

Jak działa ten program?

  • Pojawia się komunikat "Zaczynamy...".
  • Potem program próbuje wyświetlić element numbers[1] — to 2.
  • Kiedy dochodzi do numbers[5], następuje "awaria", przy której "wyrzuci się" IndexOutOfRangeException.
  • Program od razu przeskakuje do bloku catch, pomijając Console.WriteLine("Ten komunikat już się nie pojawi!");
  • W konsoli pojawi się nasz komunikat z catch.

4. Czy można obsługiwać różne typy błędów na różne sposoby?

Tak! Możemy podać kilka bloków catch, żeby różnie reagować na różne typy wyjątków. To wygodne: na przykład, jeśli jedna akcja dotyczy czytania pliku (może być FileNotFoundException), a inna — dzielenia (może być DivideByZeroException).

try
{
    // Twój niebezpieczny kod
}
catch (DivideByZeroException)
{
    Console.WriteLine("Błąd dzielenia przez zero");
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Błąd: indeks poza zakresem tablicy");
}
catch (Exception ex)
{
    Console.WriteLine("Jakiś nieprzewidziany błąd: " + ex.Message);
}

Tutaj ostatni blok catch z typem Exception — to taki "uniwersalny łapacz". Złapie wszystkie inne wyjątki, które nie pasowały do poprzednich bloków. Ale pamiętaj: jeśli napiszesz go jako pierwszy, reszta już się nie wykona! Zawsze dawaj "szerokie" catch na końcu.

5. Jak wygląda obiekt wyjątku?

W środku bloku catch możemy opcjonalnie podać zmienną — na przykład, catch (Exception ex). Ona zawiera wszystkie info o błędzie: komunikat, typ błędu, stack trace, zagnieżdżone wyjątki.

Oto mały przykład:

try
{
    string? text = null;
    Console.WriteLine(text.Length); // Opa! NullReferenceException
}
catch (NullReferenceException ex)
{
    Console.WriteLine("Złapano wyjątek: " + ex.Message);
    Console.WriteLine("Stack trace: " + ex.StackTrace);
}

Takie podejście — to zbawienie przy debugowaniu trudnych błędów: zawsze możesz zobaczyć, gdzie i dlaczego program się "wywrócił".

6. Łapiemy błąd i lecimy dalej

W prawdziwych, zwłaszcza użytkowych, aplikacjach błąd — to nie koniec świata. No ktoś wpisał złą ścieżkę do pliku, zdarza się. To jeszcze nie powód, żeby program wpadał w panikę. Zamiast tego spokojnie prosimy go, żeby spróbował jeszcze raz.

Poniższy przykład pokazuje prosty, ale skuteczny sposób: opakowujemy czytanie pliku w pętlę, i jeśli coś pójdzie nie tak, po prostu informujemy o błędzie i prosimy o ponowną próbę. Bez paniki, bez czerwonych ekranów.

bool success = false;
while (!success)
{
    Console.Write("Podaj nazwę pliku: ");
    string fileName = Console.ReadLine() ?? "";

    try
    {
        string content = File.ReadAllText(fileName);
        Console.WriteLine("Plik został pomyślnie odczytany!");
        success = true; // HURA!
    }
    catch (FileNotFoundException)
    {
        Console.WriteLine("Błąd: plik nie znaleziony. Spróbuj jeszcze raz.");
    }
}

Tutaj kluczowa idea — nie tylko złapać wyjątek, ale ogarnąć go z głową: nie pozwalamy programowi się wywalić, tylko przełączamy go w tryb "spróbuj jeszcze raz, ale poprawnie". Użytkownik dostaje jasną podpowiedź, program — drugą szansę, a ty — wdzięczność i szacunek.

7. Typowe błędy przy pracy z try-catch

Błąd nr 1: Opakowywanie całego kodu w jeden uniwersalny try-catch.
To wygląda kusząco — jeden try, jeden catch (Exception), i niby "wszystko chronione". Ale w praktyce tracisz kontrolę nad sytuacją. Nie wiadomo, gdzie dokładnie był problem i na jaki błąd tak naprawdę reagujesz. W efekcie, zamiast niezawodności — totalna utrata diagnostyki.

Błąd nr 2: Łapanie wyjątków i nic z nimi nie robienie.
Przykład z życia:

try
{
    // coś potencjalnie niebezpiecznego
}
catch
{
    // cisza...
}
Taki kod dosłownie mówi: "Coś poszło nie tak... no i trudno". To niebezpieczne: program może działać dalej w złym stanie, a ty nawet nie wiesz, że był błąd. Zawsze przynajmniej wyświetl komunikat albo zaloguj problem. Żadnych pustych catch!

Błąd nr 3: Łapanie zbyt ogólnego typu wyjątku.
Jeśli łapiesz Exception, chociaż wiesz, że może się pojawić na przykład FormatException albo FileNotFoundException, — odbierasz sobie możliwość sensownej reakcji na konkretny problem. Im węższy i dokładniejszy twój catch, tym bardziej przewidywalnie i sensownie zachowuje się program w nietypowej sytuacji.

8. Do czego NIE zaleca się używać wyjątków

Czasem początkujący zaczynają używać wyjątków... jako zwykłego mechanizmu sterowania logiką, na przykład:

try
{
    if (x < 0) throw new Exception("x musi być >= 0");
}
catch (Exception)
{
    // cofnięcie stanu, kontynuacja pracy...
}

Tak nie rób! Wyjątki — to narzędzie do nieoczekiwanych, rzadkich sytuacji (błąd odczytu pliku, padnięcie sieci itd.), a nie do zwykłych sprawdzeń poprawności danych użytkownika. Do takich rzeczy używaj instrukcji warunkowych (if, else) i zwracaj specjalne wartości, jeśli coś jest nie tak.

2
Zadanie
C# SELF, poziom 13, lekcja 1
Niedostępne
Dzielenie z obsługą błędu
Dzielenie z obsługą błędu
2
Zadanie
C# SELF, poziom 13, lekcja 1
Niedostępne
Obsługa niepoprawnych danych wejściowych
Obsługa niepoprawnych danych wejściowych
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION