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.