Delegaty i zdarzenia. Obsługa wyjątków
Przegląd zagadnień
Pierwsza część zajęć będzie dotyczyć delegatów - mechanizmu który odpowiada znanym chociażby z języka C++ wskaźnikom do funkcji.
Omówione zostanie tworzenie delegatów, pojawią się także przykłady wykorzystania delegatów w obsłudze zdarzeń.
W drugiej części zajęć dominować będzie tematyka obsługi błędów.
Zajęcia zakończą się samodzielnym wykonaniem kilku zadań programistycznych.
Delegaty i zdarzenia (1)
Na etapie projektowania nowej aplikacji nie zawsze potrafimy powiedzieć, jakie obiekty lub metody będą obsługiwały daną czynność.
Możliwe jest jednak przygotowanie konstrukcji, która zajmie się taką właśnie obsługą.
Częstym zastosowaniem delegatów jest wykorzystanie ich do obsługi zdarzeń - elementów potrafiących poinformować o zmianie stanu obiektu.
Delegaty i zdarzenia (2)
Delegaty są obiektami typu referencyjnego, klasą pochodną klasy System.Delegate.
W składni występuje słowo kluczowe delegate, zaraz po nim podajemy typ zwracany, a następnie nazwę delegata mogącego zawierać dowolną metodę przyjmującą jako parametry dwa obiekty.
Delegaty - przykład 1
W pierwszej linii kodu zadeklarowaliśmy delegat Delegacik, który jest referencją do funkcji bezparametrowej, zwracającej void. Następnie tworzymy klasę Testowa oraz deklarujemy metodę jeden, która jest zgodna z deklaracją delegata.
W kolejnym kroku powstaje nowy obiekt klasy Delegacik wskazujący na metodę jeden klasy Testowa, a następnie wywołujemy metodę jeden().
Delegaty - przykład 2
Głównym zastosowaniem delegatów jest obsługa zdarzeń - i tego właśnie tematu dotyczy drugi przykład.
Na początku deklarujemy delegata i zmienną typu delegatowego jako procedurę obsługi zdarzenia (onClick).
Następnie deklarujemy metodę zgodną z deklaracją delegatu - obiekt klasy Button i procedurę obsługi delegatu wskazującego na funkcję Przyc1_Click.
Operacja podpięcia procedury realizowana jest za pomocą operatora +=, odłączenie procedury za pomocą -=.
Obsługa wyjątków
Wyjątek jest obiektem przekazującym informacje o niezwykłych zdarzeniach w programie. Może to być na przykład nie znalezienie pliku czy niemożność użycia jakiegoś elementu.
Należy odróżniać błędy programisty i użytkownika od wyjątków - tym pierwszym powinno się zapobiegać dopracowując kod, natomiast sposobem pozbycia się błędów użytkownika może być sprawdzenie poprawności danych.
Wyjątkom nie da się zapobiec, można je za to obsłużyć, by nie powodowały zakończenia pracy programu.
Warto zapamiętać, że w języku C# wszystkie wyjątki muszą być typu System.Exception lub pochodnych - sytuacja taka nie miała miejsca na przykład w C++, gdzie możliwe było zgłoszenie w zasadzie dowolnego wyjątku.
Obsługa wyjątków - podejście obiektowe
Pierwszą rzeczą, na którą należy zwrócić uwagę, jest odejście od sposobu wyłapywania wyjątków przy pomocy instrukcji warunkowych typu na przykład if - else. Powodują one szybkie skomplikowanie kodu programu.
Idea, na której bazuje mechanizm wychwytywania wyjątków w języku C# opiera się na odseparowaniu części programu odpowiadającej za zwykłe działanie programu od fragmentów opisujących zachowanie w razie wystąpienia wyjątku.
Podstawowa konstrukcja jest dwuczłonowa - po słowie try następuje ciąg instrukcji, który może potencjalnie zawierać wyjątek, natomiast po słowie catch umieszcza się obsługę tegoż wyjątku (czyli na przykład stosowny komunikat do wyświetlenia na ekranie).
Obsługa wyjątków - przykład 1
W powyższym przykładzie w bloku try pomiędzy metodami wypisującymi fragmenty tekstu na ekranie znajduje się również kod odpowiedzialny za wyrzucenie wyjątku - rozpoczyna się on instrukcją throw.
Po wyrzuceniu wyjątku program przechodzi natychmiast do wykonania instrukcji zawartych w bloku catch, a następnie tych znajdujących się po niej (o ile są takie).
W naszym przypadku w efekcie wykonania programu na ekranie pojawią się kolejno komunikaty „Początek metody Main...” oraz „Wyjątek przechwycony i obsłużony”.
Synchronizacja - przykład blokady
Po wykonaniu programu uzyskamy komunikat „Wyjątek DivideByZeroException”
Metoda Dv() nie pozwala na dzielenie przez zero oraz na dzielenie zera przez dowolną liczbę, w pozostałych przypadkach wykonuje się zwykłe dzielenie.
Zmiana wartości parametrów a i b spowoduje w zależności od ich doboru albo wyrzucenie drugiego wyjątku albo podanie na ekranie monitora wyniku dzielenia.
Wyjątki zawsze powinno się obsługiwać w hierarchii od najbardziej wyspecjalizowanego do najogólniejszego (w przeciwnym wypadku już na starcie otrzymalibyśmy najogólniejszy wyjątek).
Obsługa wyjątków - dodatkowe informacje
Kilkakrotne wykorzystanie instrukcji catch umożliwia przygotowanie wyspecjalizowanej obsługi wyjątków. Pozwoli to na uzyskanie stosunkowo najdokładniejszych informacji na temat przyczyny wystąpienia wyjątku.
Obowiązuje tu jedna zasada - zawsze należy zaczynać od wyjątków najbardziej wyspecjalizowanych, a kończyć na tych najogólniejszych.
Przydatną opcją jest możliwość przygotowania kodu, który wykona się niezależnie od tego, czy wystąpi wyjątek. Rozwiązanie to pozwala na przykład na zwolnienie zajmowanych zasobów czy zamknięcie otwartego wcześniej pliku. W efekcie dużo łatwiejsze jest zapanowanie nad środowiskiem pracy.
Podsumowanie
Obsługa wyjątków zarezerwowana jest dla specjalnych sytuacji, w których nie jest możliwe zastosowanie zwyczajnych mechanizmów zabezpieczających czy stosowne skorygowanie kodu.
Z różnych względów najbardziej eleganckim i skutecznym rozwiązaniem jest tworzenie i wyrzucanie elementów klas pochodnych od klasy Exception.
Celem stosowania obsługi wyjątków jest nie tylko umożliwienie programowi dalszej pracy, ale i poinformowanie użytkownika o przyczynie wystąpienia wyjątku w możliwie najbardziej wyczerpujący sposób - z tego właśnie powodu warto przygotowywać dokładne komunikaty dotyczące obsługi wyjątków.
Pytania sprawdzające
Do czego służą delegaty?
W jaki sposób delegaty mogą obsłużyć zdarzenia?
Podaj podstawowe różnice pomiędzy błędem programisty a wyjątkiem.
Czy w programie można umieścić kod, który wykona się niezależnie od wystąpienia wyjątku? W jaki sposób?
Laboratorium
Napisz program, który będzie obsługiwać czujnik ruchu przy włączniku światła. Wykorzystaj w programie delegaty.
Zmodyfikuj kod poprzedniego programu tak, by możliwe było włączenie telewizora po zarejestrowaniu przez czujnik ruchu.
Przygotuj własną klasę wyjątków (na przykład dziedziczącą po System.ApplicationException) do obsługi działań pierwiastkowania i dzielenia, a następnie napisz aplikację znajdującą wartości tych działań dla podanych argumentów z obsługą wyjątków zdefiniowanej uprzednio klasy.
L
L