CodeGym /Kursy /C# SELF /Wiele catch i filtry w C#

Wiele catch i filtry w C#

C# SELF
Poziom 13 , Lekcja 4
Dostępny

1. Wprowadzenie

Kiedy program robi się trochę bardziej skomplikowany niż Hello, world!, szybko trafiasz na temat: obsługa różnych typów błędów na różne sposoby. Wyobraź sobie: pracujesz z plikami, siecią, bazą danych — i dla różnych sytuacji z błędami reakcja powinna być inna. Na przykład, jeśli nie ma pliku, możesz zaproponować użytkownikowi wybranie innego, a przy błędzie sieci — powtórzyć operację albo pokazać motywujący komunikat "Sprawdź kabel, może znowu kot go przegryzł".

W C# do tego używa się kilku bloków catch pod rząd. Każdy taki blok specjalizuje się w swoim typie wyjątku (i jego potomkach).

Struktura wielu catch


try
{
    // Tutaj — kod, który może rzucić różne wyjątki
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"Plik nie znaleziony: {ex.Message}");
}
catch (IOException ex) // łapie wszystkie błędy wejścia/wyjścia, jeśli to nie FileNotFoundException
{
    Console.WriteLine($"Błąd wejścia-wyjścia: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"Coś poszło nie tak: {ex.Message}");
}
Wiele bloków catch dla różnych typów błędów

Ważne! Kompilator idzie po blokach od góry do dołu i wybiera pierwszy, który pasuje typem. Jeśli złapie FileNotFoundException, dalej już nie pójdzie.

Ilustracja "łańcucha"

Typ wyjątku Który blok złapie?
FileNotFoundException Pierwszy catch
IOException (inny) Drugi catch
ArgumentException Trzeci (ogólny) catch

Dlaczego nie można mieszać?
Najbardziej "szeroką" pułapkę — na przykład catch (Exception) — zawsze dawaj na końcu, bo inaczej "połknie" wszystkie wyjątki za wcześnie i do wyspecjalizowanych catch-ów wykonanie nie dojdzie.

To jakby wyłączyć wszystkie czujniki pożarowe w budynku, bo jeden z nich zareagował na przypieczony toster: wtedy kolejne pożary system już nie zauważy.

Praktyczny przykład

using System;
using System.IO;

class Program
{
    static void Main()
    {
        try
        {
            double result = CalculateAverageAgeFromFile("users.txt");
            Console.WriteLine($"Średni wiek — {result}");
        }
        catch (FileNotFoundException ex)
        {
            Console.WriteLine("Błąd: plik nie znaleziony. Sprawdź ścieżkę.");
        }
        catch (FormatException ex)
        {
            Console.WriteLine("Błąd danych: nie można odczytać wieku.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Inny błąd: {ex.Message}");
        }
    }

    static double CalculateAverageAgeFromFile(string filePath)
    {
        // (Implementacja: czyta plik, parsuje wiek, liczy średnią)
        // ...
        throw new NotImplementedException();
    }
}

Tutaj wyraźnie rozdzielamy, co robić, jeśli nie ma pliku (FileNotFoundException), a co — jeśli w pliku są jakieś "krzywe" dane (FormatException). Wszystkie inne przypadki łapie "awaryjny" trzeci blok.

2. Filtry catch: łapiemy subtelne niuanse

Wiele bloków catch jest wygodne, ale czasem to za mało. Bywa, że w ramach jednego typu wyjątku chcesz reagować różnie, zależnie od okoliczności.

Na przykład, jeśli sieć nie działa, bo skończył się internet — możemy spróbować połączyć się ponownie. A jeśli serwer po prostu nie odpowiada — może warto pokazać inny komunikat.

I tu wchodzą do gry filtry catch — prawdziwy superfeature C#, który pozwala łapać nie tylko po typie, ale i po dodatkowym warunku.

Składnia filtra when


catch (IOException ex) when (ex.Message.Contains("dysku nie ma"))
{
    Console.WriteLine("Ups! Wygląda na to, że wyciągnąłeś pendrive.");
}
catch (IOException ex)
{
    Console.WriteLine("Inny błąd wejścia/wyjścia: " + ex.Message);
}
Filtr catch when do precyzyjnego warunku przechwycenia

Tutaj pierwszy blok łapie tylko te IOException, których komunikat zawiera frazę "dysku nie ma" — reszta idzie do drugiego bloku.

Zastosowanie w realu

Filtry są szczególnie przydatne przy pracy z błędami sieci, gdy na podstawie właściwości wyjątku trzeba zdecydować: powtarzać próbę czy po prostu zgłosić błąd.

Jeszcze przykład: wyobraź sobie, że w metodzie parsującej plik chcesz nie tylko "krzyczący" FormatException, ale osobno — jeśli chodzi o odczyt wieku (np. jeśli wiek zapisany jest jako "abc" zamiast liczby).

catch (FormatException ex) when (ex.Message.Contains("wiek"))
{
    Console.WriteLine("Błąd: nie udało się odczytać wieku. Sprawdź dane.");
}
catch (FormatException ex)
{
    Console.WriteLine("Błąd formatu danych: " + ex.Message);
}

Filtry a wydajność

Filtry — rzecz wygodna, ale pamiętaj, że warunek w filtrze liczy się przed wejściem do bloku catch, czyli jeśli nie jest spełniony — ciało bloku w ogóle się nie wykona.

A jak filtr sam rzuci wyjątek (np. w twoim wyrażeniu w when będzie dzielenie przez zero) — to taki wyjątek ten blok catch nigdy nie złapie. Trzeba uważać.

3. Łączenie wielu catch i filtrów

Wyobraź sobie, że w twoim projekcie trzeba obsłużyć nie tylko typ wyjątku, ale i jego stan wewnętrzny. Na przykład, przy pracy z IOException chcemy inne zachowanie, jeśli błąd systemu plików dotyczy dostępu, a inne — jeśli zabrakło miejsca na dysku.


try
{
    File.AppendAllText("log.txt", "Nowy wpis\n");
}
catch (IOException ex) when (ex.Message.Contains("Brak miejsca"))
{
    Console.WriteLine("Błąd: Na dysku skończyło się miejsce!");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("Błąd: brak uprawnień do zapisu pliku. Uruchom jako administrator.");
}
catch (IOException ex)
{
    Console.WriteLine("Inny błąd dysku: " + ex.Message);
}
Łączenie filtrów i różnych typów catch

Tutaj używamy i filtra, i rozdzielenia po typach błędów. Taka elastyczność jest szczególnie cenna przy tworzeniu złożonej aplikacji, gdzie ważna jest informatywna reakcja na częste awarie.

4. Cechy i "pułapki" filtrów i wielu catch

  • Filtr catch może używać zmiennej wyjątku (ex), ale nie może jej zmieniać.
  • Jeśli rzucisz wyjątek wewnątrz when, to nie zostanie on "złapany" przez ten sam catch — poleci dalej w górę stosu, jakby filtra nie było.
  • Logika filtra staje się częścią kontraktu: twój kolega z projektu musi wiedzieć, że nie każdy IOException zostanie złapany — tylko jeśli warunek jest spełniony.
  • Jeśli używasz tylko jednego catch na całą metodę, ale w nim filtr, to jest szansa, że "zbędnych" wyjątków w ogóle nie przechwycisz. Więc jeśli masz wątpliwości — używaj jawnych, osobnych bloków.
  • W dużych aplikacjach można stosować filtry do centralnej logiki, np. logowania tylko krytycznych błędów.

5. Scenariusze na rozmowy kwalifikacyjne i do pracy

Znajomość filtrów i wielu catch — to nie tylko dla bajeru czy wysokiej oceny za clean code. To realna umiejętność, której mogą od ciebie wymagać na rozmowie, szczególnie jeśli stanowisko dotyczy wsparcia dużej, złożonej, rozproszonej aplikacji.

Przykłady pytań:

  • Jak w C# obsłużyć różne scenariusze błędów przy czytaniu pliku, żeby dla różnych sytuacji (brak pliku, dysk zajęty, dane uszkodzone) wyświetlać różny komunikat?
  • Jak nie "zagłuszyć" ważnego błędu, jeśli dałeś ogólny blok catch (Exception)?
  • Po co jest filtr catch? Czy może być w nim throw?
1
Ankieta/quiz
Poznajemy wyjątki, poziom 13, lekcja 4
Niedostępny
Poznajemy wyjątki
Stos wywołań i tworzenie własnych wyjątków
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION