CodeGym /Kursy /C# SELF /Blok finally i operator throw

Blok finally i operator throw

C# SELF
Poziom 13 , Lekcja 2
Dostępny

1. Poznajemy blok finally

Czyszczenie i zwalnianie zasobów

Wyobraź sobie: robisz eksperymenty w laboratorium chemicznym i kiedy już skończysz (albo nawet jak wysadzisz parę kolbek), i tak musisz posprzątać stanowisko, zmyć chemikalia i zgasić światło. Właśnie do tego służy blok finally — on odpala się zawsze, po try i catch, niezależnie od tego, czy był wyjątek, czy nie.


try
{
    // Tutaj piszemy "niebezpieczny" kod, który może rzucić wyjątek
}
catch
{
    // Tutaj łapiemy i obsługujemy wyjątki
}
finally
{
    // Ten kod wykona się zawsze, był wyjątek czy nie
}
Struktura try-catch-finally w C#

Po co to wszystko? Przede wszystkim, żeby mieć pewność, że zasoby zostaną zwolnione: zamknąć plik, połączenie z bazą danych, odblokować drzwi do serwerowni... Gdyby nie było finally, po błędzie niektóre zasoby mogłyby zostać "zawieszone" — a to już prawdziwy problem. Na przykład plik będzie zablokowany i nawet admin go nie otworzy.

Dlaczego nie wszystko w catch?

Moglibyśmy po prostu zwolnić zasób w catch? Teoretycznie tak. Ale jeśli wszystko pójdzie dobrze, catch się nie wykona. Jeśli chcemy mieć pewność, że zwalnianie zasobu nastąpi zawsze, potrzebujemy finally.

W prawdziwym życiu często są takie zadania, gdzie nie ważne, czy był sukces czy porażka — sprzątanie musi być. Właśnie po to wymyślono finally.

2. Cechy bloku finally

Kiedy finally NIE działa?

Podchwytliwe pytanie! finally wykonuje się zawsze. Nawet jeśli w bloku try jest return (wczesne wyjście z metody), albo rzucany jest nowy wyjątek, finally i tak się wykona.


static void Test()
{
    try
    {
        Console.WriteLine("Przed return");
        return;
    }
    finally
    {
        Console.WriteLine("finally i tak się wykona!");
    }
}

//Wywołanie Test()
Test();
Wypisze:

// Przed return
// finally i tak się wykona!
    

Jednak jeśli nagle Twoja aplikacja zaliczy "twardy crash" (np. wyłączą komputer, proces zostanie zabity albo padnie całe CLR), blok finally się nie wykona. Ale na to już nic nie poradzisz.

Operator finally i stos wywołań

Stos wywołań omówimy szerzej na następnej lekcji, a teraz tylko krótko: to jak stos wywołanych metod, po którym program "schodzi w dół", jeśli nie znajdzie pasującego catch.

Jeszcze ważna rzecz: jeśli w try pojawi się wyjątek, a w catch nie zostanie przechwycony (np. nie ma odpowiedniego handlera), to program opuszcza bieżącą metodę i idzie dalej po stosie wywołań, aż znajdzie pasujący catch. Ale przed tym zawsze wykona się blok finally na każdym poziomie stosu.

To gwarantuje, że zasoby są zwalniane poprawnie, nawet przy dziwnych błędach.

3. Operator throw: jak samemu rzucać wyjątki

Co to jest throw i po co go używać

Czasem samo przechwycenie wyjątku to za mało — bywa, że trzeba stworzyć swój błąd i "wyrzucić" go na zewnątrz. Właśnie do tego jest operator throw.


throw new Exception("To mój specjalny błąd!");
Tworzenie i rzucanie własnego wyjątku

throw dosłownie mówi CLR: "Zauważyłem tu coś bardzo złego, rzucam swój Exception, niech się tym zajmie ten, kto wywołał ten kod".

throw bez tworzenia nowego wyjątku

Możesz użyć throw; wewnątrz bloku catch, żeby ponownie rzucić właśnie złapany wyjątek — na przykład jeśli obsłużyłeś część błędu, ale dalej chcesz przekazać odpowiedzialność wyżej.


try
{
    DangerousOperation();
}
catch (Exception ex)
{
    LogError(ex);
    throw; // ponownie rzuca bieżący wyjątek, stos wywołań zostaje zachowany
}
Ponowne rzucenie bieżącego wyjątku z zachowaniem stosu wywołań

Gdybyśmy napisali throw ex;, informacja o stosie wywołań by przepadła — to zła praktyka.

4. Jak finally działa razem z throw?

finally działa nawet przy rzucaniu wyjątku

Sprawdźmy, co się stanie, jeśli w try pojawi się throw, ale mamy też finally:


try
{
    Console.WriteLine("Przed błędem...");
    throw new Exception("Błąd podczas try!");
}
catch
{
    Console.WriteLine("Catch łapie błąd.");
}
finally
{
    Console.WriteLine("Finally się wykonał.");
}
finally wykonuje się nawet przy rzucaniu wyjątku

Rezultat:


Przed błędem...
Catch łapie błąd.
Finally się wykonał.

I jeśli nie ma pasującego catch, finally i tak się wykona zanim program się wywali.

Niuanse: co się stanie, jeśli w finally też rzucisz throw?

Jeśli w finally pojawi się throw, to ten wyjątek zastąpi poprzedni. Czyli informacja o tym, co się stało w try/catch, przepadnie. Dlatego nie zaleca się rzucać throw w finally, jeśli wcześniej już był wyjątek.


try
{
    throw new Exception("Błąd w try");
}
finally
{
    throw new Exception("Błąd w finally");
}
// Efekt: na zewnątrz wyjdzie "Błąd w finally"
Wyjątek z finally zastępuje poprzedni

5. Praktyczne wskazówki i typowe błędy

Co najczęściej zapominają początkujący

  • Nie używają bloku finally przy zwalnianiu zasobów, polegając tylko na catch.
  • Umieszczają kod, który może rzucać nowe wyjątki, wewnątrz finally — to prowadzi do nieoczekiwanych błędów.
  • Zapominają, że return w try nie "omija" finally — on zawsze się wykona.

Alternatywa dla finally: co wybrać?

Z pojawieniem się konstrukcji using (którą omówimy szerzej później), zwalnianie zasobów stało się jeszcze wygodniejsze, ale w gruncie rzeczy using pod spodem używa tego samego finally. Do wszelkich niestandardowych sytuacji (np. odblokowanie albo wysłanie info o błędzie) — i tak trzeba użyć finally.

Za co lubią finally na rozmowach kwalifikacyjnych

Każdy rekruter, który choć raz pisał serwer pod dużym obciążeniem, lubi zadawać pytania o finally. Zwykle pytają: "Co się stanie, jeśli w try jest return, a w finallythrow?" albo "Czy zwalnianie zasobów jest gwarantowane przy wyjątkach?". I teraz masz nie tylko odpowiedzi, ale i zrozumienie. Potrafisz nie tylko opowiedzieć, jak działa finally, ale też wyjaśnić, po co jest, kiedy go używać i dlaczego bez niego żaden poważny kod się nie obejdzie.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION