Wyjątki

C# jest bardzo mocno związany z platformą .NET, a na tej, obsługa błędów polega na przechwytywaniu wyjątków. Dlatego programując w tym języku nie należy stosować sposobów takich jak zwracanie kodów (bardzo popularne w języku C/C++).

/* Obsługa błędów na zasadzie zwracanych kodów */
/* Język: C */
#include <stdio.h>

int main()
{
    FILE *hFile = fopen("plik","rt");
  
    if (!hFile)
    {
        printf("Wystąpił jakiś błąd.");  
    }
    else
    {
        //kontynuacja odczytu pliku    
    }
  
    return 0;
}

Kod ten działa poprawnie, ale nie gwarantuje, że jeżeli błąd wystąpi, zostanie obsłużony (wystarczy, że programista pominie instrukcje if(!hFile)), ponadto trudno jest uzyskać poprzez ten mechanizm dokładne informacje na temat występującego problemu. Bardziej bezpieczne jest napisanie takiego kodu w oparciu o wyjątki:

using System;
using System.IO;

/* klasa odczytująca plik */
class Reader
{
    public string filename;

    /* konstruktor klasy */
    public Reader(string filename)
    {
        this.filename = filename;
    }

    /* metoda zwracająca zawartość pliku */
    public string Read()
    {
        string s = null;

        /* otwarcie pliku jako strumienia, jeżeli podczas tej operacji 
           wystąpi wyjątek, to zostanie obsłużony przez funkcje wywołującą */
        FileStream fStream = new FileStream(filename, FileMode.Open);

        try
        {
            /* odczyt pliku do samego końca */
            StreamReader sReader = new StreamReader(fStream);

            s = sReader.ReadToEnd();
        }
        finally
        {
            /* instrukcje zawarte w bloku finally zostaną wykonane zawsze, 
               nawet jeżeli wystąpi wyjątek w bloku try */
            fStream.Close();
        }

        return s;
    }
}

class Program
{
    static void Main(string[] args)
    {
        string content = null;
        Reader reader = new Reader("niematakiegopliku");

        try
        {
            content = reader.Read();
        }
        catch (FileNotFoundException)
        {
            /* jeżeli plik nie istnieje, ten blok catch zostanie uznany
               za najbardziej poprawny i obsłuży wyjątek */
            Console.WriteLine("Plik \"{0}\" nie istnieje",reader.filename);
        }
        catch (Exception e)
        {
            /* ten blok obsłuyży wyjątek jeżeli zaden poprzedni blok catch
               nie będzie odpowiedni */
            Console.WriteLine("Wystąpił wyjątek:\n{0}\n\nSzczegóły:\n{1}", e.Message, e);
        }

        Console.WriteLine("Zawartość pliku: {0}", content);
    }
}

Hierarchia wyjątków

Jak widać w powyższym przykładzie obsługa wyjątków polega na umieszczeniu niepewnej instrukcji w bloku try i wychwytywania występujących w niej wyjątków przez instrukcje catch.
Środowisko uruchomieniowe musi wybrać, który blok catch. jest najbardziej odpowiedni - jeżeli plik nie istnieje to blok catch. obsługujący wyjątek FileNotFoundException jest najbardziej odpowiedni. Jeżeli natomiast problem polegałby przykładowo na braku dostępu do pliku, to blok ten nie pasowałby. Musiałby być użyty bardziej ogólny blok np.

catch (Exception e)

Jeżeli nie było by takiego bloku, środowisko szukało by bloku try. w funkcji, która wywołuje powodujący problemy kod (w powyższym przykładzie nie znajdując odpowiedniego bloku catch. w funkcji Read() środowisko przejdzie do funkcji Main() i powtórzy procedurę dopasowywania wyjątku). Jeżeli w funkcjach wywołujących nie można znaleźć odpowiedniego bloku catch, to wyjątek zostanie obsłużony przez mechanizm ostatniej szansy czyli np. komunikat o błędzie i przerwanie pracy programu.

Mechanizm ostatniej szansy - nie obsłużony wyjątek: Image:csharp exception.jpg

Pamiętaj ze bloki catch muszą być uporządkowane od najmniej, do najbardziej ogólnego!

Blok finally

Blok finally zostanie wykonany zawsze. Tutaj należy wpisywać kod który musi się wykonać niezależnie od tego czy wystąpił wyjątek czy też nie, np. zamknięcie pliku, zamknięcie połączenia z bazą danych.

Klasy wyjątków pisane przez użytkownika

Klasą bazową dla wszystkich wyjątków jest System.Exception. Tworząc własne typy wyjątków nie należy dzidziczyć bezpośrednio po klasie System.Exeption! Został przyjęt ogólny podział: wyjątki rzucane przez klasy z biblioteki klas bazowych .NET pochodzą z System.SystemException, zaś nasze wyjątki na poziomie aplikacji powinny być dziedziczone z System.ApplicationException, aby moża było łatwo odróżnić nasze wyjątki od wyjątków systemowych.

Utwórzmy sobie zatem nową klasę wyjątku:

public class MyOwnException : System.ApplicationException
{
     private string myName;
     public MyOwnException() {}
     public MyOwnException(string myName)
     {
           this.myName = myName;
     }

     //przesłonięcie właściwości Exception.Message
     public override string Message
     {
           get
           {
                 return myName + "Exception";
           }
     }
}

Tworząc własne typy wyjątków dobrze jest stosować jednolity system nazewnictwa: nazwy klas wyjątków kończyć słowem Exception, np. CarException, OurDataBaseException.

3 komentarzy

Bardzo dobry artykuł ;)

Widze ze nie zrozumiałes przesłania.. nie chodzi o porównanie języków tylko metod przechwytywania błędów, równie dobrze można by tam wstawić pascala albo coś w tym stylu....

Artykuł bardzo fajny, ale C# trzebaby porownać z C++ a nie C :P