15. Wyjątki - część I

15. Wyjątki - część I

 Paweł Kruczkowski
Paweł Kruczkowski
00:00
02.01.2007
66931 wyświetlenie

Dzisiaj opowiemy sobie o wyjątkach. Każdy programista pisząc program skazany jest na ich występowanie w mniejszym bądź większym stopniu. Wyjątek (ang. exception) to taki obiekt, który potrafi informować nas o niezwykłych, nienormalnych zdarzeniach w naszym programie. Na szczęście język C# 2.0 zapewnia mechanizm obsługi takich wyjątków. Niniejszy artykuł przybliży nam ten mechanizm, poznamy nowe słowa kluczowe jak: try, catch oraz finally. W oparciu o przykłady, zdefiniujemy sobie również instrukcję throw.

Będąc programistą musimy uświadomić sobie fakt, że w nowo tworzonym przez nas programie mogą wystąpić aż trzy rodzaje błędów. Ważne, aby odróżnić od siebie błędy programisty, błędy użytkownika oraz właśnie wyjątki. Ten pierwszy rodzaj błędu to sytuacja wytworzona przez programistę piszącego kod. Innymi słowy, to wszelakie błędy w kodzie, które można i należy natychmiast poprawić zanim nasz program trafi do klienta. Takimi błędami są na przykład: złe rzutowanie, czy nieprawidłowe używanie jakiegoś obiektu. Oczywiście takie nieprawidłowości będą natychmiast wychwytywane i zgłaszane przez mechanizm obsługi wyjątków, niemniej jednak należy ich unikać i je naprawiać.

Drugi rodzaj błędów – to błędy użytkownika. Są one spowodowane przez osobę, która używa naszego programu. Przykładem takiego błędu może być wpisanie jakieś litery w miejscu, gdzie nasza aplikacja oczekuje liczby. Oczywiście takich błędów można i należy unikać. W naszym przypadku wystarczyłoby sprawdzić w kodzie poprawność wpisywanych danych. Podobnie jak błędy programisty, tak i błędy użytkownika są zgłaszane przez mechanizm obsługi wyjątków, niemniej jednak podobnie jak w poprzednim przypadku musimy ich unikać i przestrzegać nasz program przed ich pojawianiem się.

Jednak są w naszych programach takie sytuacje, że nawet jeśli uporamy się z błędami programistów oraz użytkowników (chociaż doświadczenie podpowiada, że to jest równoznaczne z cudem to i tak nasz program może natrafić na takie sytuacje, których wcześniej nie można było przewidzieć. Takie sytuacje to nasze tytułowe wyjątki. Żeby ich unikać należy je obsłużyć, aby nie były przyczyną zatrzymania się wykonywania naszego programu. 

Wyobraźmy sobie sytuację z wyjątkiem w naszym programie. A więc wygląda ona następująco: gdy jakaś metoda aktualnie się wykonująca nie obsługuje wyjątków a zgłosi takowy, wówczas wyjątek ten jest przekazywany przez nią do metody bezpośrednio ją wywołującej. Jeśli żadna z metod nie obsługuje wyjątku, zostaje on „obsłużony” przez środowisko CLR (środowisko .Netowe, które tworzy obiekty, przydziela dla nich pamięć, sprawdza bezpieczeństwo, wykonuje polecenia etc.).

W języku obiektowym C# 2.0 do obsługi wyjątków służy specjalny blok. Blok ten używa instrukcji catch, która to odpowiada za obsługę wyjątków w naszym programie. Poniżej przedstawiamy ogólną  formę bloku obsługi wyjątków:

try
            {
                //blok kodu, w ktorym moze wystąpić wyjątek
            }
            catch (TypWyjatku1 exp1)
            {
                //obsluga wyjatku dla TypWyjatku1
            }
 
            catch (TypWyjatku2 exp2)
            {
                //obsluga wyjatku dla TypWyjatku2
            }
 
            finally
            {
                //blok kodu do wykonania, zanim skonczy sie blok try{}
            }

Blok ten składa się z instrukcji try, w której umieszczamy monitorowany kod. Następnie tworzymy instrukcję catch, która jest odpowiedzialna za obsługę wyjątków. Wyjątki generowane przez środowisko CLR są wyśtwietlane automatycznie. Jest jednak możliwość ich ręcznego zgłoszenia. Aby tak zrobić należy właśnie w instrukcji catch użyć słowa kluczowego throw (o tym w dalszej części artykułu). Blok obsługi wyjątków może zawierać kilka instrukcji catch, które w swojej definicji mogą zawierać typy wyjątków (czyli typ wyjątku, który miał miejsce w naszym kodzie – w instrukji try). Na końcu znajduje się finally, czyli blok w którym umieszczamy kod wykonujący się zawsze (niezależnie, czy został zgłoszony wyjątek, czy też nie).

Idealnym dla nas rozwiązaniem jest przechwycenie wyjątku, obsłużenie go, a następnie kontynuowanie działania naszego programu. Po tych operacjach idealnie jest wyświetlić obsłużony przez nas wyjątek za pomocą eleganckiego komunikatu o błędzie.

Prześledźmy pierwszy prosty programik:

public class Wyjatki
{
    static void Main()
    {
        int a = 0;
        int iloraz = 20 / a;
 
        System.Console.WriteLine("Wynik dzielenia wynosi: {0}", iloraz);
    }
}

W powyższym przykładzie próbujemy uzyskać wynik dzieląc liczbę 20 przez 0. Oczywiście taka operacja nie jest dozwolona i w trakcie takiej próby otrzymamy informację o wyjątku:

W momencie wystąpienia w naszym programiku nieobsłużonego wyjątku, środowisko CLR natychmiast kończy działanie programu i zgłasza nam jego istnienie. Aby elegancko napisać powyższy przykład, wprowadźmy blok obsługi błędów w następujący sposób:

public class Wyjatki
{
    static void Main()
    {
        try
        {
            int a = 0;
            int iloraz = 20 / a;
            System.Console.WriteLine("Napis nigdy nie wyświetlony!");
        }
        catch (ArithmeticException e)
        {
            System.Console.WriteLine("Niedozwolone dzielenie przez zero!!!") ;
        }
        finally
        {
            System.Console.WriteLine("Napis po instrukcji catch {}");
        }
    }
}

W instrukcji try umieśćiliśmy fragment kodu, który chcemy monitorować. Bezpośrednio po tym bloku dołączylismy klauzulę catch, która wyszczególnia typ wyjątku, który chcemy przechwycić. W naszym przykładzie tym wyjątkiem jest ArithmeticException, który “nadzoruje” wszelakie błędne operacje arytmetyczne. Jeśli więc w bloku try wystąpi tego typu błąd to blok catch obsłuży ten błąd, a środowisko CLR tym razem nie zakończy działania naszego programu. Po bloku catch nie wracamy do klauzuli try (a więc nigdy nie zostanie wyświetlony napis z bloku try) tylko przechodzimy do bloku finally (stąd wyświetlony napis z tego bloku):

W języku C# 2.0 wyjątki możemy zgłaszać jedynie w postaci obiektów klasy Exception lub szeregu obiektów pochodnych od tej klasy. Klasa Exception znajduje się w przestrzeni nazw System. Przestrzeń ta zawiera liczne typy wyjątków, które możemy wykorzystywać w programach (między innymi ArithmeticException, który użyliśmy w powyższym przykładzie czy też InvalidCastException lub ArgumentNullException).

Jak już zostało wyżej napisane, obok przechwytywania wyjątków w bloku catch, możemy również w nim zgłaszać wyjątki. Do tego służy słowo kluczowe: throw:

                        throw new Exception();

W powyższym fragmencie kodu utworzyliśmy egzemplarz klasy Exception i obiekt ten następnie zgłaszamy za pomocą throw. Zgłoszenie takiego wyjątku powoduje natychmiastowe zatrzymanie działania naszego programu, a środowisko CLR rozpoczyna wyszukiwanie bloku obsługi takiego wyjątku. Jeśli aktualnie wykonywana metoda nie posiada takiej obsługi, to CLR sprawdza czy jest on w metodzie wywołującej idt. Jeśli okaże się, że zgłoszony wyjątek nie jest obsłużony wówczas – jak już doskonale wiemy – CLR kończy działanie programu.

Poniższy przykład prezentuje sposób użycia w instrukcji catch słowa kluczowego throw:

public class Dzielenie
{
    int a, b;
    public Dzielenie(int a, int b)
    {
        this.a = a;
        this.b = b;
    }
 
    public int Podziel()
    {
        return a / b;
    }
}
 
   
public class Wyjatki
{
    static void Main()
    {
        try
        {
            Dzielenie d = new Dzielenie(25, 5);
            int wynik = d.Podziel();
            Console.WriteLine("Wynik dzielenia wynosi: {0}.", wynik);
            d = new Dzielenie(wynik, 0);
            wynik = d.Podziel();
            Console.WriteLine("Wynik dzielenia wynosi: {0}.", wynik);
        }
        catch
        {
            throw new Exception();
        }
        finally
        {
            Console.WriteLine("Koniec metody Podziel()");
        }
    }
}

W powyższym przykładzie w bloku catch utworzyliśmy obiekt typu Exception a następnie za pomocą słowa kluczowego throw zgłaszamy ten obiekt. Środowisko CLR zostanie poinformowane o tym obiekcie w momencie wystapienia niezwykłej sytuacji w programie (w naszym przypadku, gdy będziemy wywoływać za drugim razem metodę Podziel() za pomocą obiektu d – gdyż będzie to niedozwolona próba dzielenia przez 0). Po uruchomieniu programu uzyskamy informację, że w programie wystąpił wyjątek, co widzimy poniżej:

Aby jednak mieć jakiś pożytek z powyższego programu, przechwyćmy zgłoszony wyjątek w następujący sposób:

public class Dzielenie
{
    int a, b;
    public Dzielenie(int a, int b)
    {
        this.a = a;
        this.b = b;
    }
 
    public int Podziel()
    {
        try
        {
            return a / b;
        }
        catch (Exception e)
        {
            Console.WriteLine("Błąd! Nie wolno dzielić przez 0!!!");
            return 0;
        }
    }
}
public class Wyjatki
{
    static void Main()
    {
        try
        {
            Dzielenie d = new Dzielenie(25, 5);
            int wynik = d.Podziel();
            Console.WriteLine("Wynik dzielenia wynosi: {0}.", wynik);
            d = new Dzielenie(wynik, 0);
            wynik = d.Podziel();
            Console.WriteLine("Wynik dzielenia wynosi: {0}.", wynik);
        }
        catch
        {
            throw new Exception();
        }
        finally
        {
            Console.WriteLine("Koniec metody Podziel()");
        }
    }
}

Po uruchomieniu naszego przykładu uzyskamy nastepujące wyniki:

To prosty przykład prezentujący sposób zgłaszania oraz przechwytywania wyjątków. Wynik, jaki uzyskaliśmy nie wymaga komentarza, gdyż każdy z nas powinien po dzisiejszej lekturze zrozumieć dlaczego otrzymaliśmy taki a nie inny końcowy rezultat. Za tydzień będziemy kontynuować temat wyjątków w języku C#. Powiemy sobie między innymi o możliwości tworzenia własnych wyjątków, a także poszerzymy sobie wiedzę na temat instrukcji catch.


Spodobał Ci się ten artykuł? Podziel się z innymi!

Źródło:

Polecamy również w kategorii Kurs C#, cz. II