Strona główna » Kursy » Kurs C# » Wyjątki
 

Wyjątki

Wstęp

Dobrze napisany program to taki, który działa tak jak programista przewidział. Niestety w każdym programie mogą występować różnego rodzaju błędy. Jeśli nie wynikną one z implementacji algorytmu to mogą dotyczyć nieprawidłowo wprowadzonych danych albo wystąpienia problemu z przyczyn sprzętowych. W celu uniknięcia problemów najprostszym sposobem jest zastosowanie sprawdzania wyjątków w programie.

Wyjątki

Jednym z najprostszych sposobów zapobiegania wyjątkom w programie jest sprawdzenie czy wprowadzane dane są prawidłowe, albo czy plik do zapisu danych istnieje. Takie podejście przyniesie oczekiwany efekt, ale ciągłe sprawdzanie czy nie wystąpi zaraz błąd może spowolnić działanie programu. Z kolei sam kod z bardzo dużą ilością instrukcji warunkowych może nie wyglądać zbyt przejrzyście. Jednak jak w takim razie programista może wyłapać błędy?

C# należy do grona języków programowania, który posiada mechanizm obsługi wyjątków. Służą one do wyłapywania błędu w programi w miejscach określonych przez użytkownika. Choć zastosowanie tego bloku kodu nie pozwala na magiczne przetworzenie błędnych danych to programista może odczytać szereg informacji, które pozwolą znaleźć źródło błędu. Przykładowo można przejrzeć, które funkcje zostały po kolei wywołane i w której linijce kodu błąd wystąpił. Ponadto istnieje wiele różnych rodzajów błędu, więc dla typowych błędów można otrzymać jakiegoś rodzaju wiadomość, a nie tylko, że program niestety przestał działać.

Składnia

Obsługa wyjątków jest możliwa dzięki bloku try .. catch .. finally. Po słowie kluczowym try program przechodzi do instrukcji zawartych w bloku po nim. Jeśli podczas wykonywania instrukcji w programie zostanie wykryty błąd wykonywania to program automatycznie przejdzie do instrukcji w bloku po catch. Jeśli nie wystąpi żaden wyjątek to ten blok zostaje pominięty. Niezależnie od wyjątku zawsze zostanie wywołany blok finally, który zwykle można pominąć. Składnia przedstawia się następująco:

  1. try {
  2.   // instrukcje do wykonania
  3. }
  4. catch (Exception ex) {
  5.   // obsługa wyjątku
  6. }
  7. finally {
  8.   // dalsza część kodu
  9. }

Informacje wyłapane w wyjątku są przetrzymywane w argumencie, który przyjmuje blok catch. Zazwyczaj jest to typ Exception (chociaż może być to wyjątek szczegółowy). Tutaj informacje przechowuje zmienna ex.

Przykład

Wyjątek można zastosować do wyłapywania błędu dzielenia przez zero. Zastąpi to poznany wcześniej sposób wyłapywania dzielenia przez zero poprzez instrukcję warunkową if. Przykładowy zapis kodu wygląda następująco:

  1. static void Main(string[] args) {
  2.   int a = 5;
  3.   int b = 0;
  4.   try {
  5.     Console.WriteLine("{0} / {1} = {2}", a, b, a / b);
  6.   }
  7.   catch (Exception ex) {
  8.     Console.WriteLine(ex.Message);
  9.   }
  10.   Console.ReadKey();

W powyższym przykładzie najpierw (2. - 3.) podajemy dane, które na pewno spowodują wystąpienie błędu dzielenia. Następnie (4.) przechodzimy do wykonywania instrukcji. (5.) Próba wypisania działania oraz wyniku kończy się błędem, ponieważ próbujemy podzielić przez zero. Z tego powodu przechodzimy do bloku catch i na ekran (8.) wypisujemy wiadomość przekazaną w wyjątku. Korzystamy tutaj z przekazanego argumentu i jego właściwości Message. W niej zapisana jest treść wcześniej wbudowana w język C#. Bloka finally został tutaj opuszczony.

Po uruchomieniu powyższego programu z danymi a = 5 oraz b = 0 program wypisze następujący komunikat (komunikat może się różnić w zależności od wybranego języka systemu):

  1. Nastąpiła próba podzielenia przez zero.

Z kolei po korekcie danych na np. a = 6 oraz b = 3 program wypisze:

  1. 6 / 3 = 2

Warto tutaj zwrócić uwagę, że podczas obliczeń dzielenie przez zero nie musi być jedynym wyjątkiem. Gdyby kalkulator obsługiwał bardziej zaawansowane funkcji to mogłoby wystąpić znacznie więcej różnych błędów. Dla programisty oznaczałoby to pisania dodatkowych instrukcji warunkowych if. Tutaj nie ma takiej potrzeby. Jeśli podczas wykonywania instrukcji zostanie zgłoszony wyjątek to programista zawsze się o tym dowie. Należy jednak pamiętać, że nie każdy wyjątek będzie tak szczegółowo wskazywał na przyczynę problemu jak w przypadku dzielenia przez zero.

Rzucanie wyjątku

Rzucenie wyjątku powoduje, że wykonywanie instrukcji zostaje przerwane i zgłoszony wyjątek zostaje przekazany do bloku try, a jeśli takowy nie istnieje to błąd jest zgłaszany poprzez pokazanie błędu użytkownikowi. W celu rzucenia wyjątku wystarczy wpisać:

  1. throw new Exception(..);

W miejsce kropek można podać treść komunikatu czego dotyczy wyjątek oraz opcjonalnie jako drugi argument podać wyjątek, który spowodował ten wyjątek. Do wygenerowanego błędu dołączone zostaną w sposób automatyczny o linijce w której wystąpił błąd oraz informacje o wcześniej wywołanych funkcjach.

Przykład

Spróbujmy teraz napisać własny komunikat błędu dotyczący dzielenia przez zero. W tym celu musimy zastosować instrukcję warunkową, aby błąd wykryć, a potem będzie można rzucić konkretny wyjątek:

  1. static void Main(string[] args) {
  2.   int a = 5;
  3.   int b = 0;
  4.   try {
  5.     if (b == 0)
  6.       throw new Exception("Wiadomość");
  7.     Console.WriteLine("{0} / {1} = {2}", a, b, a / b);
  8.   }
  9.   catch (Exception ex) {
  10.     Console.WriteLine(ex.Message);
  11.   }
  12.   Console.ReadKey();
  13. }

Tym razem w bloku try (5.) sprawdzamy czy wystąpi błąd. Jeśli tak to (6.) zgłaszamy własny wyjątek, który następnie zostaje przekazany do (9.) bloku catch i w tym przypadku (10.) zostaje wypisany komunikat na ekran.

Chociaż podany przykład wygląda nieco sztucznie to należy pamiętać, że programy sięgają nawet kilku tysięcy linijek. W takich kodach obsługa wyjątków pozwala na nie przerywanie ich wykonywania, ale np. zgłoszenia błedu użytkownikowi w przyjaźniejszy sposób niż okienkiem z błędem i zamknięciem programu. Ponadto rzucanie wyjątku pozwala jest przekazywane pomiędzy funkcjami, więc nawet jeśli jest 5 funkcji, które siebie wywołują to jest możliwe w pierszej otrzymanie błędu, że dana część instrukcji nie została wykonana i na tej podstawie program może zdecydować co dalej.

Przekazywanie wyjątku

Może się zdarzyć tak, że wyjątek po obsłużeniu powinien zostać przekazany dalej. Przykładem zastosowanie czegoś takiego jest zapisanie błędu do dziennika zdarzeń, a następnie przekazanie błędu wyżej, aby np. wypisać go użytkownikowi, ale żeby funkcja nadrzędna wiedziała o występieniu błędu. Wyjatek można rzucić dalej poprzez instrukcję throw zapisaną w bloku catch:

  1. catch (Exception ex) {
  2.   // obsługa wyjątku
  3.   throw; // rzuć dalej
  4. }

W bloku catch można spotkać właśnie jedną instrukcję throw oznacza to, że nie chcemy obsługiwać w tym miejscu kodu, a gdzie indziej.

Przykład

Wracając do podanego wcześniej przykładu program może wykryć błąd, aby nie wykonać niepoprawnej operacji, ale możemy chcieć, aby błąd został zgłoszony użytkownikowi. Wtedy w catch dodamy wspomnianą instrukcję throw. Ze względu na to, że wokół obsługi wyjątku nie ma już obsługi wyjątku to błąd zostanie zgłoszony bezpośrednio użytkownikowi, a program - zamknięty tak jakby bloku try nie było.

  1. static void Main(string[] args) {
  2.   int a = 5;
  3.   int b = 0;
  4.   try {
  5.     Console.WriteLine("{0} / {1} = {2}", a, b, a / b);
  6.   }
  7.   catch (Exception ex) {
  8.     Console.WriteLine(ex.Message);
  9.     throw;
  10.   }
  11.   Console.ReadKey();
  12. }

Podsumowanie

Obsługa wyjątków to bardzo wygodny mechanizm obsługi błędów w programie. Należy go stosować wszędzie tam gdzie może wystąpić błąd. Różnorodność błędów jest duża: nieprawidłowe dane wejściowe, błąd obsługi strumienia danych czy brak ustawionej wartości zmiennej. Każdy z nich można wyłapać i w przejrzysty sposób opisać. To z kolei wpływa na szybsze wyłapywanie błędu i jego poprawienie.

Zadania

Zadanie 1

Napisz kalkulator, który wczyta od użytkownika dwie liczby, a następnie wypisze wynik dzielenia pierwszej liczby przez drugą. Jeśli wystąpi błąd podczas wczytywania danych lub obliczeń to odpowiedni komunikat powinien zostać wypisany. Pamiętaj o komunikatach, aby użytkownik wiedział czego oczekuje program.

Przykładowo po wpisaniu do zmiennej zamiast wartości liczbowej to słowo to program powinien wypisać:

  1. Nieprawidłowy format ciągu wejściowego.

Zadanie 2

Napisz kalkulator, który wczyta od użytkownika dwie liczby, a następnie wypisze wynik dzielenia pierwszej liczby przez drugą. Ze względu na to, że chcemy podzielić dwie liczby całkowitoliczbowe to musimy mieć pewność, że po podzieleniu a przez b nie będzie żadnej reszty. Jeśli będzie jakokolwiek reszta to powinien zostać zwrócony błąd "Niepoprawne dzielenie całkowitoliczbowe".

Przykłado dla a = 5 i b = 2 program wypisze:

  1. Niepoprawne dzielenie całkowitoliczbowe