WyjątkiThe-Hangover-2-Poster-01Nikt nie jest w stanie przewidzieć działań użytkownika. Gdy tworzymy program zwykle jesteśmy przygotowani na scenariusz absolutnie zgodny z zastosowaniem aplikacji ,jednak co się stanie, jeśli w miejscu Y napiszę X.

Ten X nie ma żadnego zastosowania z punktu widzenia programu ,ale nie zmienia to faktu ,że powoduje on jego blokadę. Gdy w textboxie pobieramy liczby musimy być przygotowani na to ,że użytkownik nie podał wartości liczbowej. Gdy otwieramy w aplikacji plik graficzny musimy być przygotowani na to ,że użytkownik wybrał przez przypadek plik tekstowy i tak dalej.

Błędy się zdarzają i każdy dobry program jest idioto odporny.
Pisząc jakikolwiek program nie zdziw się ,że tyle samo czasu musisz poświęcić na przechwytywanie błędów i wyjątków, jakie mogą nastąpić w wyniku losowych działań użytkownika.

Próba przechwycenia wyjątku kaca nie skończyła się pomyślnie…po raz kolejnyChoroba

Try i catch

W niektórych miejscach w kodzie błąd może nastąpić w każdym momencie ,a próba poradzenia sobie z tą sytuacją za pomocą dużej ilości warunków if nie jest dobrym pomysłem. Trzeba by było wtedy przewidzieć to, co jest czasami nieprzewidywalne. Wiemy ,że błąd w takim kodzie może wystąpić ,ale dla konstrukcji if musielibyśmy przewidzieć te błędy w dokładnej specyfikacji.

Na szczęście w C# mamy do dyspozycji blok try i catch.

Wewnątrz bloku try piszemy kod co, do którego nie jesteśmy pewni ,że zadziała dobrze za każdym możliwym razem. Gdy jakiś fragment kodu wewnątrz tego bloku nie wykonał się poprawnie wtedy kod zostaje przerwany i program przeskakuje do bloku catch.

try 
{
     //coś niebezpiecznego 
}
catch 
{
     //przechwytuje każdy wyjątek 
}

Bloków catch może być kilka. Każdy blok catch może przechwytywać określony wyjątek (który otrzymał z bloku try) i w zależności od niego może zareagować w odpowiedni sposób.

Oto przykładowy kod, w którym użytkownik w konsoli musi podać dwie cyfry. Gdy pobierzemy napisy od użytkownika konwertujemy go na typ int by wykonać na nim operacje dzielenia.

Nie przechycony wyjątek przy debugowaniu aplikacji

Co może pójść nie tak w tym kodzie:

  • Napis nie będzie cyfrą
  • Cyfra zawarta w napisie będzie poza zakresem typu int
  • Zostanie wykonane dzielenie przez zero, co dla typu int zwróci wyjątek

W tym przykładzie jak widzicie zastosowałem kilka bloków catch. Najważniejsze jest to, by był przynajmniej jeden blok catch , który przechwytuje wszystkie wyjątki w końcu mamy się spodziewać czegoś nieoczekiwanego. Ten blok catch jest na końcu kodu.

Console.WriteLine("Podaj dwie liczby");
try
{
    string x = Console.ReadLine();
    int xx = int.Parse(x);
    string y = Console.ReadLine();
    int yy = int.Parse(y);
    Console.WriteLine(xx/yy);

}
catch (FormatException fEx)
{
    Console.WriteLine(fEx.Message);    
}
catch (OverflowException OverEx)
{
    Console.WriteLine(OverEx.Message);
}
catch (ArithmeticException ArgEx)
{
    Console.WriteLine(ArgEx.Message);
}
catch (Exception Ex)
{
    Console.WriteLine("Coś poszło nie tak");
}

Przechwytywanie każdego wyjątku oddzielnie też ma sens. Mógłbym dla każdego z nich wyświetlić swoją własną wiadomość dla użytkownika.

Wyjątek FormatExceptionnastąpi, gdy użytkownik podał litery. Wyjątek OverflowException nastąpi, gdy wartość podana w konsoli będzie poza zakresem liczbowym wartości typu int. Wyjątek “ArithmeticException” wystąpi przy próbie dzielenia przez zero.

Przydałoby się jednak wytłumaczyć, co się dzieje, gdy występuje wyjątek w aplikacji w bloku try/catch i jak to jest możliwe ,że każdy wyjątek może być przechwycony od jego typu.

Kiedy wyjątek wystąpi program najpierw porównuje ten wyjątek do wyjątków, które są zawarte w blokach catch w kolejności, w jakiej zostały napisane. Czyli jeśli na początku umieścisz blok catch ,który przechwytuje wszystko pozostałe bloki będą bezużyteczne.

Na szczęście w takim wypadku pomogą Visual Studio. Nikt nie wie dokładnie, który wyjątek może być zawarty, w którym albo, który dziedziczy po którym. OverflowException zawiera się w ArithmeticException.

Visual Studio 2010 catch try
Jak widzisz niektóre wyjątki mogą spełniać wiele bloków catch ,ale i tak wykona się tylko jeden, ten pierwszy. A Visual Studio pilnuje programistę by wszystkie bloki catch były użyteczne i wzajemnie się nie kryły.

Lista wyjątków można odnaleźć w tym moim wpisie.

Używanie bloku Finally

Przy wyjątkach warto pamiętać o tym ,że nasz program nie daje żadnej gwarancji, co do swojego poprawnego przepływu.

Najlepszym tego przykładem jest otwieranie plików. Jeśli plik został otworzony w bloku try, ale w czasie jego odczytywania wyskoczył wyjątek, to fragment kodu, który zamyka ten plik w dalszym bloku try nigdy nie zostanie użyty.

Jest to poważny problem, zwłaszcza jeśli pomyślisz o operacjach baz danych, w których trzeba łączyć się z bazą ,a potem to połączenie zamykać.

Aby mieć absolutną pewność ,że dany kod zostanie uruchomiony niezależnie od tego, czy otrzymaliśmy wyjątek, czy nie, to używamy bloku finally. Oto prosty przykład, w którym go zastosowałem

int aa = 15;
try 
{
    string a = Console.ReadLine();
    aa = int.Parse(a);
}
catch 
{
    Console.WriteLine("Nie zmieniłeś zmiennej- Błąd");
}
finally 
{
    Console.WriteLine(aa);
}

Bez względu na to, czy wyjątek się pojawił, czy nie zmienna “aa” zostanie wyświetlona.

Używanie słów kluczowych checked i unchecked

Muszę przyznać ,że programuje już jakiś czas i jeszcze nie miałem do czynienia ze słowami kluczowymi jak unchecked i checked, co wcale nie znaczy , że mnie nigdy nie interesowało, jaka jest ich rola w kodzie.Puszczam oczko

Jak zapewne już wiesz typ int i jego mniejsi koledzy mają określoną wielkość maksymalną i minimalną , a gdy double i float mają nieskończoność dodatnią i ujemną.

Czyli dla typu int zawsze wyskakuje wyjątek, gdy jego zawartość zostanie przekroczona. Jednak te “zawsze” ma pewną lukę.

Otóż możemy zaznaczyć w kodzie, jak ta określona zawartość int może być obsłużona. Czy wolimy otrzymać wyjątek, czy po prostu błędny wynik?

Dlaczego mamy do dyspozycji taką możliwość? Widocznie sprawdzanie, czy wartość int została przekroczona, to kolejny może zbędny wysiłek dla programu .Możemy mieć tysiące zmiennych int, które coś liczą ,a wiemy ,że wartości te nie muszą być pilnowane "czy są w swoich zakresach" gdy wiemy ,że taka sytuacja nie nastąpi.

Jakby spojrzeć na to w ten sposób, to stosowanie słowa kluczowego “unchecked” może zwiększyć wydajność każdego algorytmu operującego na małych liczbach.

Zobaczmy jak działa słowo kluczowe unchecked w praktyce.

Aby pobrać maksymalną liczbę bądź minimalną z typu int użyj tych właściwości podanych na obrazku poniżej.

int maxvalue i minvalue

Słowo kluczowe unchecked pozwoli nam na przekroczenie granicy wartościowej typu int bez ujrzenia wyjątku “OverflowException.

int max = int.MaxValue;
unchecked
{
    Console.WriteLine(max);
    Console.WriteLine(++max);
    Console.WriteLine(max + 2140000000);
}

Jak widzicie po dodaniu wartości do maksymalnego przedziału zawartość int zmienna “max” odwróciła się w drugą granicę wartości int.

rezultat unchecked code

Słowo kluczowe checked wywołuje naturalne zachowanie kodu C#.

int Min = int.MinValue;
int NiedaSie = checked(Min - 1);


Gdy zakres zmiennej int zostanie przekroczony otrzymam następujący wyjątek.

normalne zachowanie overflow checked

Zapewne zastanawiasz się po co nam słowo kluczowe checked, które wykonuje to samo co kod w C# naturalnie robi.

Sprawa jest taka ,że w opcjach projektu , można ustawić cały kod aby zachowywał się tak, jakby on był pod klauzurą “unchecked”.

Poszukując ikonki projektu w Solution Explorer i klikając na nią prawym przyciskiem myszki oraz wybierając potem z menu “Properties” możesz zobaczyć następujące okno.

Ustawienia projektu

W tym oknie wybierając zakładkę “Bulid” ,a potem przycisk “Advanced’ możesz ustawić na stałe zachowanie aplikacji, w której int-y po przekroczeniu wartości nie wyrzucają wyjątków.

Rzucanie wyjątków

Pisząc swój własny kodu czasami chcesz zaznaczyć ,że coś się nie dzieje tak jakbyś sobie tego życzył.

public static string DzienTygodnia(int liczba)
{
    switch (liczba)
    {
        case 1:
            return "poniedziałek";
            break;
        case 2:
            return "wtorek";
            break;
        case 3:
            return "środa";
            break;
        case 4:
            return "czwartek";
            break;
        case 5:
            return "piątek";
            break;
        case 6:
            throw new System.Exception("Kac Exception");
            break;
        case 7:
            throw new EndOfStreamException
            ("Idź do kościoła zamiast programować");
            break;
        default:
            throw new ArgumentOutOfRangeException
            ("Liczba musi być z przedziału od 1 do 7");   
    }
}

Powiedzmy ,że stworzyłeś metodę, w której użytkownik podaje liczbę od 1 do 7 po czym ta metoda zwraca odpowiedni dzień tygodnia. Jednak co powinna zwrócić metoda, gdy otrzyma wartość 8. Wzorując się na gotowych klasach w platformie .NET powinna ona zwracać wyjątek, w końcu ktoś użył jej niezgodnie z jej przeznaczeniem.

Wraz z wyrzucaniem wyjątku za pomocą słowa kluczowego "throw" możesz określić, jaki wyjątek powinien być wyrzucony.

Użycie metody w kodzie aplikacji konsolowej.

try 
{
    DzienTygodnia(int.Parse(Console.ReadLine()));
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
};

Można też tworzyć własne typy wyjątków (klasy), ale może o tym kiedy indziej.

To nie Koniec Kursu

Na koniec kursu zaprezentuję aplikację konsolową, w której zastosujemy całą wiedzę zawartą w tym kursie.

Taka aplikacja konsolowa może bardzo użytecznym narzędziem Uśmiech