Wyjątki w C# – obsługa błędów

Poznasz mechanizm wyjątków – jak przechwytywać błędy, reagować na nie i tworzyć odporne aplikacje. Nauczysz się używać try/catch/finally oraz rzucać własne wyjątki!

try catch finally throw Exception
01

Problem – program się wysypuje

Co się stanie, gdy użytkownik wpisze literę zamiast liczby? Gdy podzielisz przez zero? Gdy plik nie istnieje? Program się wysypie!

cs/Problem.cs C#
// 😰 Program bez obsługi błędów

Console.Write("Podaj liczbę: ");
string input = Console.ReadLine();

// Co jeśli użytkownik wpisze "abc"?
int liczba = int.Parse(input);  // 💥 CRASH!

Console.WriteLine($"Wpisałeś: {liczba}");

// Wynik dla "abc":
// Unhandled exception. System.FormatException:
// Input string was not in a correct format.
Katastrofa!

Bez obsługi błędów każdy nieprzewidziany przypadek kończy działanie programu. Użytkownik widzi czerwony komunikat błędu i program się zamyka.

Typowe przyczyny błędów

cs/TypoweBłędy.cs C#
// 1. Nieprawidłowy format danych
int x = int.Parse("abc");  // 💥 FormatException

// 2. Dzielenie przez zero
int wynik = 10 / 0;  // 💥 DivideByZeroException

// 3. Brak pliku
string txt = File.ReadAllText("nieistnieje.txt");  // 💥 FileNotFoundException

// 4. Null reference
string s = null;
int len = s.Length;  // 💥 NullReferenceException

// 5. Indeks poza tablicą
int[] arr = { 1, 2, 3 };
int v = arr[10];  // 💥 IndexOutOfRangeException
02

Czym jest wyjątek?

Wyjątek (exception) to obiekt reprezentujący błąd, który wystąpił podczas działania programu. Gdy coś pójdzie nie tak, system „rzuca” wyjątek.

Analogia: Alarm pożarowy 🚨

Wyobraź sobie budynek biurowy:

  • Normalne działanie = ludzie pracują
  • Wyjątek (pożar) = alarm się włącza
  • Obsługa wyjątku = procedura ewakuacji

Bez procedury (catch) – panika! Z procedurą – kontrolowana reakcja.

Analogia: Rzucanie piłki 🏀
  • throw = rzucasz piłkę (zgłaszasz problem)
  • catch = łapiesz piłkę (obsługujesz problem)
  • Jeśli nikt nie złapie – piłka uderza w ziemię (crash!)
Kod
int.Parse(„abc”)
throw
FormatException
catch
Obsługa błędu
Kod
int.Parse(„abc”)
throw
FormatException
Brak catch!
💥 CRASH
03

try/catch – podstawy

Blok try/catch pozwala przechwycić wyjątek i zareagować na błąd zamiast pozwolić programowi się wysypać.

cs/TryCatch.cs C#
Console.Write("Podaj liczbę: ");
string input = Console.ReadLine();

try
{
    // Kod który MOŻE rzucić wyjątek
    int liczba = int.Parse(input);
    Console.WriteLine($"Wpisałeś: {liczba}");
}
catch
{
    // Kod wykonywany GDY wystąpi błąd
    Console.WriteLine("To nie jest poprawna liczba!");
}

Console.WriteLine("Program działa dalej...");

// Dla "abc":
// To nie jest poprawna liczba!
// Program działa dalej...
Jak to działa?
  1. Kod w try jest wykonywany normalnie
  2. Jeśli wystąpi błąd → skok do catch
  3. Jeśli NIE ma błędu → catch jest pomijany
  4. Program kontynuuje po bloku try/catch

Przepływ sterowania

try
Wykonaj kod
błąd?
TAK
→ catch
try
Wykonaj kod
błąd?
NIE
→ pomiń catch
cs/BezBłędu.cs C#
try
{
    int x = int.Parse("42");  // OK!
    Console.WriteLine($"Liczba: {x}");  // Wykonane
}
catch
{
    Console.WriteLine("Błąd!");  // POMINIĘTE
}

// Wynik:
// Liczba: 42
04

Obiekt wyjątku

Wyjątek to obiekt klasy Exception. Możesz go przechwycić i odczytać informacje o błędzie.

cs/ObiektWyjatku.cs C#
try
{
    int x = int.Parse("abc");
}
catch (Exception ex)  // Przechwytujemy obiekt wyjątku
{
    Console.WriteLine($"Typ: {ex.GetType().Name}");
    Console.WriteLine($"Komunikat: {ex.Message}");
    Console.WriteLine($"Stack trace: {ex.StackTrace}");
}

// Wynik:
// Typ: FormatException
// Komunikat: Input string was not in a correct format.
// Stack trace: at System.Number.ThrowOverflowOrFormat...
WłaściwośćOpisPrzykład
MessageOpis błędu„Input string was not in a correct format.”
StackTraceGdzie wystąpił błądLinia kodu, metoda, plik
GetType()Typ wyjątkuFormatException
InnerExceptionWyjątek zagnieżdżonyPrzyczyna głębsza
SourceŹródło błęduNazwa assembly
cs/PraktycznyPrzyklad.cs C#
Console.Write("Podaj wiek: ");
string input = Console.ReadLine();

try
{
    int wiek = int.Parse(input);
    Console.WriteLine($"Za 10 lat będziesz mieć {wiek + 10} lat");
}
catch (Exception ex)
{
    // Przyjazny komunikat dla użytkownika
    Console.WriteLine("❌ Podaj poprawną liczbę całkowitą!");
    
    // Szczegóły dla programisty (np. do logów)
    Console.WriteLine($"[DEBUG] {ex.GetType().Name}: {ex.Message}");
}
05

Typy wyjątków

W .NET istnieje wiele typów wyjątków. Każdy opisuje inny rodzaj błędu.

WyjątekKiedy występujePrzykład
FormatExceptionNieprawidłowy format danychint.Parse("abc")
DivideByZeroExceptionDzielenie przez zero10 / 0
NullReferenceExceptionUżycie nullnull.ToString()
IndexOutOfRangeExceptionIndeks poza zakresemarr[100]
FileNotFoundExceptionBrak plikuFile.ReadAllText("brak.txt")
ArgumentExceptionNieprawidłowy argumentPrzekazano pusty string
ArgumentNullExceptionArgument jest nullPrzekazano null
InvalidOperationExceptionNieprawidłowa operacjaOperacja w złym stanie
OverflowExceptionPrzepełnienieLiczba za duża
IOExceptionBłąd I/OProblem z plikiem/siecią

Hierarchia wyjątków

txt/Hierarchia.txt Schemat
Exception                          ← Klasa bazowa
├── SystemException
│   ├── FormatException
│   ├── DivideByZeroException
│   ├── NullReferenceException
│   ├── IndexOutOfRangeException
│   ├── ArgumentException
│   │   └── ArgumentNullException
│   ├── InvalidOperationException
│   └── OverflowException
├── IOException
│   └── FileNotFoundException
└── ApplicationException           ← Własne wyjątki (stare podejście)
Dziedziczenie wyjątków

Wyjątki tworzą hierarchię klas. catch (Exception) łapie wszystkie wyjątki, bo każdy dziedziczy po Exception.

06

Wiele bloków catch

Możesz obsłużyć różne typy błędów w różny sposób, używając wielu bloków catch.

cs/WieleCatch.cs C#
Console.Write("Podaj dzielnik: ");
string input = Console.ReadLine();

try
{
    int dzielnik = int.Parse(input);
    int wynik = 100 / dzielnik;
    Console.WriteLine($"100 / {dzielnik} = {wynik}");
}
catch (FormatException)
{
    Console.WriteLine("❌ To nie jest liczba!");
}
catch (DivideByZeroException)
{
    Console.WriteLine("❌ Nie można dzielić przez zero!");
}
catch (Exception ex)  // Catch-all na końcu
{
    Console.WriteLine($"❌ Nieznany błąd: {ex.Message}");
}
Kolejność catch ma znaczenie!

Bloki catch są sprawdzane od góry do dołu. Bardziej szczegółowe wyjątki muszą być PRZED ogólnymi!

❌ Źle – ogólny na początku

catch (Exception ex)     // Łapie WSZYSTKO!
{
}
catch (FormatException)  // Nigdy nie wykona!
{
}

✅ Dobrze – szczegółowy najpierw

catch (FormatException)  // Specyficzny
{
}
catch (Exception ex)     // Ogólny na końcu
{
}

Łączenie wyjątków (C# 6+)

cs/WhenFilter.cs C#
try
{
    // kod
}
// Łączenie typów (C# 6+)
catch (Exception ex) when (ex is FormatException or OverflowException)
{
    Console.WriteLine("Problem z liczbą!");
}

// Filtr when
catch (Exception ex) when (ex.Message.Contains("timeout"))
{
    Console.WriteLine("Przekroczono czas!");
}
07

Blok finally

Blok finally wykonuje się ZAWSZE – niezależnie czy wystąpił błąd czy nie. Służy do sprzątania zasobów.

cs/Finally.cs C#
StreamReader reader = null;

try
{
    reader = new StreamReader("dane.txt");
    string content = reader.ReadToEnd();
    Console.WriteLine(content);
}
catch (FileNotFoundException)
{
    Console.WriteLine("Plik nie istnieje!");
}
finally
{
    // ZAWSZE się wykona – zamknij plik!
    if (reader != null)
    {
        reader.Close();
        Console.WriteLine("Plik zamknięty.");
    }
}
Kiedy używać finally?
  • Zamykanie plików
  • Zamykanie połączeń z bazą danych
  • Zwalnianie zasobów sieciowych
  • Czyszczenie obiektów tymczasowych

Przepływ z finally

try
Kod
OK lub catch
finally
ZAWSZE!

try/finally bez catch

cs/TryFinally.cs C#
// Można użyć try/finally BEZ catch
// Wyjątek poleci wyżej, ale finally się wykona

try
{
    // Otwórz zasób
    // Użyj zasobu (może rzucić wyjątek)
}
finally
{
    // Zamknij zasób (ZAWSZE)
}
Alternatywa: using

W C# lepszą praktyką jest używanie using zamiast try/finally dla zasobów:

using (StreamReader reader = new StreamReader("plik.txt"))
{
    // reader zostanie automatycznie zamknięty
}
08

Rzucanie wyjątków (throw)

Możesz sam rzucić wyjątek używając throw. To sposób sygnalizowania błędu w swoim kodzie.

cs/Throw.cs C#
public class Osoba
{
    private int _wiek;
    
    public int Wiek
    {
        get { return _wiek; }
        set
        {
            if (value < 0)
            {
                throw new ArgumentException("Wiek nie może być ujemny!");
            }
            if (value > 150)
            {
                throw new ArgumentException("Wiek nie może przekraczać 150!");
            }
            _wiek = value;
        }
    }
}

// Użycie
try
{
    Osoba o = new Osoba();
    o.Wiek = -5;  // 💥 ArgumentException!
}
catch (ArgumentException ex)
{
    Console.WriteLine(ex.Message);  // "Wiek nie może być ujemny!"
}

Popularne wyjątki do rzucania

WyjątekKiedy rzucaćPrzykład
ArgumentExceptionNieprawidłowy argumentWiek ujemny
ArgumentNullExceptionArgument jest nullNazwa = null
ArgumentOutOfRangeExceptionArgument poza zakresemIndeks < 0
InvalidOperationExceptionNieprawidłowy stanMetoda wywołana w złym momencie
NotImplementedExceptionMetoda niezaimplementowanaTODO
NotSupportedExceptionOperacja niewspieranaTylko do odczytu
cs/Walidacja.cs C#
public class Konto
{
    public decimal Saldo { get; private set; }
    
    public void Wplac(decimal kwota)
    {
        if (kwota <= 0)
            throw new ArgumentException("Kwota musi być dodatnia", nameof(kwota));
        
        Saldo += kwota;
    }
    
    public void Wyplac(decimal kwota)
    {
        if (kwota <= 0)
            throw new ArgumentException("Kwota musi być dodatnia", nameof(kwota));
        
        if (kwota > Saldo)
            throw new InvalidOperationException("Brak wystarczających środków");
        
        Saldo -= kwota;
    }
}
09

Ponowne rzucanie (rethrow)

Możesz przechwycić wyjątek, wykonać akcję (np. logowanie) i rzucić go dalej.

cs/Rethrow.cs C#
public void PrzetworzPlik(string sciezka)
{
    try
    {
        string content = File.ReadAllText(sciezka);
        // przetwarzanie...
    }
    catch (Exception ex)
    {
        // Zaloguj błąd
        Console.WriteLine($"[LOG] Błąd: {ex.Message}");
        
        // Rzuć dalej (zachowuje stack trace!)
        throw;
    }
}

❌ Źle – gubi stack trace

catch (Exception ex)
{
    throw ex;  // Resetuje stack trace!
}

✅ Dobrze – zachowuje stack trace

catch (Exception ex)
{
    throw;  // Zachowuje oryginalny stack trace
}

Opakowywanie wyjątku

cs/Opakowywanie.cs C#
public void ZapiszDane(string dane)
{
    try
    {
        File.WriteAllText("dane.txt", dane);
    }
    catch (IOException ex)
    {
        // Opakuj w własny wyjątek z dodatkowym kontekstem
        throw new InvalidOperationException(
            "Nie udało się zapisać danych", ex);  // ex = InnerException
    }
}
10

Własne wyjątki

Możesz tworzyć własne klasy wyjątków dziedziczące po Exception. Daje to precyzyjniejszą obsługę błędów.

cs/WlasnyWyjatek.cs C#
// Własny wyjątek
public class BrakSrodkowException : Exception
{
    public decimal KwotaBrakujaca { get; }
    
    public BrakSrodkowException(decimal brakuje)
        : base($"Brakuje {brakuje:C} na koncie")
    {
        KwotaBrakujaca = brakuje;
    }
}

// Użycie w klasie
public class Konto
{
    public decimal Saldo { get; private set; } = 100;
    
    public void Wyplac(decimal kwota)
    {
        if (kwota > Saldo)
        {
            decimal brakuje = kwota - Saldo;
            throw new BrakSrodkowException(brakuje);
        }
        Saldo -= kwota;
    }
}

// Obsługa
try
{
    Konto k = new Konto();
    k.Wyplac(500);
}
catch (BrakSrodkowException ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine($"Doładuj konto o: {ex.KwotaBrakujaca:C}");
}
Konwencja nazewnictwa

Nazwa własnego wyjątku powinna kończyć się na Exception:

  • BrakSrodkowException
  • NieznanyUzytkownikException
  • LimitPrzekroczonyException

Pełna implementacja (z konstruktorami)

cs/PelnyWyjatek.cs C#
public class WalidacjaException : Exception
{
    public string NazwaPola { get; }
    
    // Konstruktor domyślny
    public WalidacjaException() : base() { }
    
    // Z komunikatem
    public WalidacjaException(string message) 
        : base(message) { }
    
    // Z komunikatem i nazwą pola
    public WalidacjaException(string message, string nazwaPola) 
        : base(message)
    {
        NazwaPola = nazwaPola;
    }
    
    // Z inner exception
    public WalidacjaException(string message, Exception inner) 
        : base(message, inner) { }
}
11

Wzorce użycia

Wzorzec 1: Pętla z ponawianiem

cs/Ponawianie.cs C#
int liczba;
bool poprawne = false;

while (!poprawne)
{
    Console.Write("Podaj liczbę (1-100): ");
    
    try
    {
        liczba = int.Parse(Console.ReadLine());
        
        if (liczba < 1 || liczba > 100)
            throw new ArgumentOutOfRangeException();
        
        poprawne = true;
    }
    catch (FormatException)
    {
        Console.WriteLine("To nie jest liczba!");
    }
    catch (ArgumentOutOfRangeException)
    {
        Console.WriteLine("Liczba musi być z zakresu 1-100!");
    }
}

Console.WriteLine($"Dziękuję! Wybrałeś: {liczba}");

Wzorzec 2: TryParse zamiast wyjątku

cs/TryParse.cs C#
// ❌ Wolniejsze – wyjątek jest kosztowny
try
{
    int x = int.Parse(input);
}
catch (FormatException) { }

// ✅ Szybsze – bez wyjątku
if (int.TryParse(input, out int x))
{
    Console.WriteLine($"Liczba: {x}");
}
else
{
    Console.WriteLine("To nie jest liczba");
}
TryParse vs Parse
  • int.TryParse() – zwraca true/false, nie rzuca wyjątku
  • int.Parse() – rzuca FormatException przy błędzie
  • Używaj TryParse gdy błąd jest oczekiwany (dane od użytkownika)
  • Używaj Parse gdy błąd jest wyjątkowy

Wzorzec 3: Obsługa plików

cs/ObslugaPlikow.cs C#
public static string OdczytajBezpiecznie(string sciezka)
{
    try
    {
        return File.ReadAllText(sciezka);
    }
    catch (FileNotFoundException)
    {
        Console.WriteLine($"Plik nie istnieje: {sciezka}");
        return null;
    }
    catch (UnauthorizedAccessException)
    {
        Console.WriteLine($"Brak dostępu do pliku: {sciezka}");
        return null;
    }
    catch (IOException ex)
    {
        Console.WriteLine($"Błąd odczytu: {ex.Message}");
        return null;
    }
}
12

Dobre praktyki

✅ Rób❌ Nie rób
Łap konkretne wyjątki Łapać wszystko przez catch (Exception)
Używaj TryParse dla danych użytkownika Używać wyjątków do kontroli przepływu
Loguj szczegóły wyjątku Ignorować wyjątki (pusty catch)
Używaj throw; do ponownego rzucenia Pisać throw ex; – gubi stack trace
Zwalniaj zasoby w finally lub using Zostawiać otwarte pliki/połączenia
Twórz sensowne komunikaty Pisać „Wystąpił błąd”
Wyjątki są KOSZTOWNE!

Rzucanie wyjątku jest wolne – wymaga budowania stack trace. Nie używaj wyjątków do normalnego przepływu programu!

// ❌ ZŁE – wyjątek do kontroli przepływu
try {
    int x = lista[index];
} catch (IndexOutOfRangeException) { }

// ✅ DOBRE – sprawdzenie warunku
if (index >= 0 && index < lista.Count) {
    int x = lista[index];
}
13

Częste błędy

❌ Błąd 1: Pusty catch

❌ Źle – połykanie błędu

try
{
    // kod
}
catch
{
    // Nic nie robimy = ZŁO!
}

✅ Dobrze – reakcja na błąd

try
{
    // kod
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    // lub logowanie
}

❌ Błąd 2: Zbyt ogólny catch

❌ Źle

try
{
    // wiele operacji
}
catch (Exception)
{
    Console.WriteLine("Błąd!");
    // Który? Nie wiadomo!
}

✅ Dobrze

try
{
    // operacje
}
catch (FileNotFoundException)
{
    Console.WriteLine("Brak pliku");
}
catch (FormatException)
{
    Console.WriteLine("Zły format");
}

❌ Błąd 3: throw ex zamiast throw

❌ Źle – resetuje stack trace

catch (Exception ex)
{
    Log(ex);
    throw ex;  // ŹRÓDŁO BŁĘDU ZGUBIONE!
}

✅ Dobrze – zachowuje stack trace

catch (Exception ex)
{
    Log(ex);
    throw;  // Stack trace zachowany
}

❌ Błąd 4: Wyjątek zamiast if

❌ Źle – wolne!

try
{
    int x = tablica[index];
}
catch (IndexOutOfRangeException)
{
    // obsługa
}

✅ Dobrze – szybkie

if (index >= 0 && index < tablica.Length)
{
    int x = tablica[index];
}
else
{
    // obsługa
}
14

Podsumowanie

Kluczowe pojęcia
  • Exception – obiekt reprezentujący błąd
  • try – kod który może rzucić wyjątek
  • catch – obsługa wyjątku
  • finally – wykonuje się ZAWSZE (sprzątanie)
  • throw – rzucanie wyjątku

Schemat składni

cs/Schemat.cs C#
// Pełna składnia try/catch/finally
try
{
    // Kod który może rzucić wyjątek
}
catch (SpecyficznyWyjatek ex)
{
    // Obsługa konkretnego typu
}
catch (Exception ex)
{
    // Catch-all (ostatni)
}
finally
{
    // Zawsze się wykonuje
}

// Rzucanie wyjątku
throw new ArgumentException("komunikat");

// Własny wyjątek
public class MojWyjatek : Exception
{
    public MojWyjatek(string msg) : base(msg) { }
}
WyjątekPrzyczyna
FormatExceptionNieprawidłowy format (Parse)
DivideByZeroExceptionDzielenie przez zero
NullReferenceExceptionUżycie null
IndexOutOfRangeExceptionIndeks poza tablicą
FileNotFoundExceptionBrak pliku
ArgumentExceptionNieprawidłowy argument
InvalidOperationExceptionZły stan obiektu
Zadania

Zadania praktyczne

📝 Zadanie 1: Kalkulator bezpieczny

Napisz prosty kalkulator (dodawanie, odejmowanie, mnożenie, dzielenie) który:

  • Pobiera dwie liczby od użytkownika
  • Obsługuje błędy: niepoprawny format, dzielenie przez zero
  • Pozwala użytkownikowi spróbować ponownie

📝 Zadanie 2: Odczyt pliku konfiguracji

Napisz metodę LoadConfig(string path) która:

  • Odczytuje plik tekstowy
  • Obsługuje FileNotFoundException, UnauthorizedAccessException
  • Zwraca null przy błędzie lub zawartość pliku

📝 Zadanie 3: Walidacja formularza

Utwórz klasę Formularz z metodą Waliduj(string email, int wiek):

  • Email musi zawierać "@" i "." – inaczej ArgumentException
  • Wiek musi być 0-150 – inaczej ArgumentOutOfRangeException
  • Zwraca true jeśli dane są poprawne

⭐ Zadanie 4: Własny wyjątek

Utwórz klasę KontoBankowe z:

  • Własnością Saldo
  • Metodami Wplac() i Wyplac()
  • Własnym wyjątkiem NiewystarczajaceSrodkiException

💡 Wyjątek powinien zawierać właściwość KwotaBrakujaca

⭐⭐ Zadanie 5: Przetwarzanie listy liczb

Napisz program który:

  • Wczytuje liczby od użytkownika (kończy na "koniec")
  • Ignoruje niepoprawne wpisy (z komunikatem)
  • Na końcu wyświetla: sumę, średnią, min, max
  • Obsługuje przypadek pustej listy