3 Delegacje Delegacja to specjalny typ danych, który przechowuje referencję (adres) do procedury lub funkcji. W środowisku.net delegacja jest odpowiednikiem wskaźnika (pointer) do funkcji znanego z języka C/C++. W małych projektach można się obyć z używania delegacji, w większych jest to praktycznie niemożliwe. Środowisko.NET korzysta z delegacji powszechnie, a ich dobrym przykładem jest obsługa zdarzeń z późnym wiązaniem (z wykorzystaniem instrukcji AddHandler). 3.1 Tworzenie delegacji Przed wykorzystaniem delegacji lub wykorzystaniem delegacji wbudowanej w.net musimy ją utworzyć za pomocą instrukcji: Specyfikator Delegate Sub/Function Nazwa([parametry]) As Type Specyfikator określa dostępność delegacji, może tu wystąpić Public, Private, Protected czy Friend. Nazwa delegacji powinna być zgodna z regułami tworzenia nazw w.net. Delegacja może zawierać (ale nie musi) parametry, w przypadku zbudowania delegacji do funkcji obowiązkowo musi być określony zwracany typ danych. Delegacja może wskazywać adres tylko tej procedury czy funkcji, której sygnatura (zestaw parametrów, zwracany typ danych) jest zgodna z sygnaturą delegacji. 3.2 Skojarzenie delegacji z procedurą czy funkcją Przed wykorzystaniem delegacji musimy utworzyć procedury czy funkcje, które będziemy chcieli kojarzyć z delegacją. Obowiązuje zasada, że z delegacją można skojarzyć dowolną procedurę czy funkcję pod jednym warunkiem: zgodności sygnatur. Poniżej przykład definicji delegacji, dwóch procedur, instrukcji tworzącej instancję delegacji, przypisanie do instancji delegacji adresu procedury. ' utworzenie delegacji do procedury z jednym parametrem typu String Delegate Sub MessageDelegate(ByVal jg As String) ' utworzenie dwóch procedur, które będą wywoływane via instancja delegacji Private Sub SendToMessageBox(ByVal txt As String) MessageBox.Show(txt, Me.Text) End Sub 54
Private Sub SendToEventLog(ByVal txt As String) ' utworzenie logu aplikacji Dim AppLog As New EventLog AppLog.Source = Me.Text AppLog.WriteEntry(txt) End Sub ' utworzenie instancji delegacji z jednym parametrem typu String Dim SendMessage As MessageDelegate ' utworzenie instancji delegacji typu MessageDelegate wskazującej adres procedury SendToMessageBox SendMessage = New MessageDelegate(AddressOf SendToMessageBox) ' utworzenie instancji delegacji typu MessageDelegate wskazującej adres procedury SendToEventLog SendMessage = New MessageDelegate(AddressOf SendToEventLog) Jeżeli instancja delegacji już istnieje, to można zmienić adres procedury czy funkcji w prosty sposób: SendMessage = AddressOf SendToEventLog 3.3 Wywołanie delegacji W podanym wyżej przykładzie zbudowano instancję delegacji o nazwie SendMessage, która wskazuje jedną z dwóch procedur SendToMessageBox lub SendToEventLog. Jej wykorzystanie jest proste: SendMessage( to jest argument ) 3.4 Aplikacja TestDelegacji W celu prześledzenia przykładów użycia delegacji przygotowałem prostą aplikację o nazwie TestDelegacji. Aplikację tworzy jeden formularz o nazwie frmtestaplikacji, plik AppConfig wprowadzony w związku z wykorzystaniem logu aplikacji i klasy CDelegacje. 3.4.1 Delegacja pojedyncza W formularzu umieszczono formant typu TabControl z dwoma zakładkami, pierwsza zawiera formanty związane z przykładami wykorzystania delegacji do wywołania procedury z jednym argumentem. Poza sytuacją, w której delegacja zawiera referencję do pojedynczej procedury będzie pokazany przykład zbudowania i wykorzystania delegacji zbiorowej. 55
Przykład ten pokazuje, jak można wykorzystać delegacje do podjęcia decyzji na etapie wykonania programu którą procedurę trzeba wywołać w danej sytuacji. Do pola txtkomunikat opisanego etykietą Wpisz komunikat wpisujemy dowolny tekst. W zależności od tego, który z dwóch formantów typu RadioButton będzie wybrany klik lewego przycisku polecenia Wyślij komunikat ma go wysłać albo do wiadomości albo do logu aplikacji. Rozwiązanie pokazane jest niżej. 56
W kolejnym kroku piszemy procedury obsługujące zdarzenia CheckedChange obu przycisków radiowych. Procedura obsługi zdarzenia Click pierwszego z przycisków kończy rozwiązanie tego problemu. 3.4.2 Delegacja zbiorowa Można teraz stworzyć inną sytuację, w której komunikat może być wysłany do jednego z odbiorców lub do obu jednocześnie. Dla prześledzenia takiej sytuacji w drugiej ramce umieszczono pola wyboru (CheckBox) w miejsce RadioButton. W kodzie będziemy musieli wykorzystać delegacje zbiorową, która różni się tym od zwykłej delegacji, że zapisuje referencje na wewnętrznej liście. Tym samym musi istnieć możliwość dodania referencji do listy, jej usunięcia oraz wywołania delegacji zbiorowej z wykonaniem wszystkich referencji z jej listy. Pokazana niżej procedura dodaje lub usuwa referencję do procedury SendToMessage do instancji SendMessage zależnie od tego, czy pole wyboru jest zaznaczone czy nie. Nazwa Delegate jest w nawiasach klamrowych dla zaznaczenia, że chodzi o klasę. 57
W analogiczny sposób tworzymy procedurę zdarzenia CheckedChange drugiego z pól wyboru (dodajemy lub usuwamy delegację do procedury SendToEvenLog). Pozostało już tylko napisanie procedury zdarzenia Click drugiego z przycisków. Przed wykorzystaniem logu aplikacji musimy zmodyfikować plik app.config, w którym w sekcji <listeners> musi się znaleźć wpis <add name= EventLog />., a w sekcji <sharedlisteners> pokazany niżej wpis. 58
W sekcji <sharedlisteners> dodajemy pokazany niżej wpis (klucz intializedate podaje nazwę węzła w pliku logów). Poniżej dwa zrzuty ekranowe ilustrujące działanie delegacji pojedynczych i zbiorowych. W celu sprawdzenia wpisów w logu aplikacji korzystamy z widoku ServerExplorer, w którym musimy rozwinąć gałąź Event Logs i dalej Aplikacja. Odszukujemy pozycję TestDelegacji i jako pierwszą pozycję mamy interesujący nas komunikat. 59
3.4.3 Delegacje do funkcji W tym podrozdziale zajmiemy się zagadnieniem utworzenia i wykorzystania delegacji do funkcji. Wykorzystamy w tym celu formanty zakładki Funkcje i klasy, w której umieszczono następujące formanty: txtpromien (pole tekstowe do podania promienia okręgu), nudz (Formant typu NumericUpDn do wyboru dokładności zaokrąglenia), cztery pola tekstowe do zwrócenia wyników pola okręgu, obwodu okręgu, objętości kuli i jej powierzchni, są to: txtpole, txtobwod, txtobjetosc i txtpowkuli; btnoblicz przycisk polecenia uruchamiający obliczenie tych czterech wielkości. Dodatkowo zdublowano przycisk polecenia oraz cztery pola tekstowe na potrzeby innego, bardziej zaawansowanego rozwiązania w którym wykorzystamy delegację do przekazania funkcji do procedury. Poniżej widok formularza otwartego na zakładce Funkcje i klasy. W kodzie klasy formularza musimy utworzyć obiekty i funkcje, które będą obsługiwać nasz przykład. 60
Zaczynamy od utworzenia delegacji do funkcji o nazwie Okrag, która przyjmuje jeden argument typu Double i zwraca wartość typu Double. Tworzymy także delegację do procedury DelegateMsg, która nie potrzebuje żadnego parametru oraz dwie jej instancje, które wskazują na adresy procedur Msg1 oraz Msg2. Kolejny krok to utworzenie czterech procedur obliczających pole okręgu, jego obwód, objętość kuli o zadanym promieniu oraz jej powierzchnię. Musimy napisać dwie procedury zgłaszające komunikaty w sytuacji, gdy będzie źle podany promień okręgu. Procedury te wykorzystują dwie stałe o nazwach t1 i t2 (zadeklarowane w sekcji deklaracji klasy formularza). Pozostaje teraz napisanie procedury zdarzenia Click przycisku btnoblicz. Procedura ta musi odebrać z pola tekstowego txtpromien jego zawartość (tekstową) i ją skonwertować na wartość typu Double. Musi także odebrać z formantu nudz parametr zaokrąglenia typu Integer. Konwersja tych danych może spowodować błąd czasu wykonania, stąd wykorzystamy blok Try-Catch w celu przechwycenia ewentualnego błędu. 61
Jeżeli podany promień jest liczbą większą od zera, to tworzone są cztery instancje delegacji Okrag wskazujące na odpowiednią funkcję. Kolejna grupa czterech instrukcji wywołuje instancje za pomocą słowa kluczowego Invoke z przekazaniem promienia r. Zwrócony wynik jest pierwszym argumentem funkcji Round (poprzedza ją przestrzeń nazw Math, ponieważ nie zaimportowaliśmy tej przestrzeni), drugim jest wielkość zaokrąglenia d. Wynik działania funkcji Round musi być przekształcony na typ String, robimy to wywołując metodę ToString. W przypadku, gdyby podany promień był liczbą nie większą od zera wywoływana jest delegacja del1, a w przypadku problemu z konwersją promienia na Double delegacja del2. Przejdziemy teraz do innego rozwiązania naszego problemu, wykorzystamy w tym celu delegację do przekazania do metody funkcji jako argumentu. Dodatkowo istotne dla tego rozwiązania procedury, funkcje i delegację umieścimy w dedykowanej klasie o nazwie CDelegacje. Poniżej kod tej klasy. Zaczyna on się deklaracją zmiennej prywatnej mr typu Double, zmienna ta będzie przechowywać promień okręgu. Konstruktor New ustawia wartość tej zmiennej w momencie utworzenia instancji klasy. Kolejna instrukcja tworzy publiczną delegację do funkcji o nazwie JG, która przyjmuje argument typu Double i zwraca wartość typu Double. 62
W kolejnym kroku tworzona jest publiczna funkcja Oblicz zwracająca wartość typu Double, która jako argument przyjmuje obiekt sqd typu JG, czyli funkcję. Funkcja ta zwraca rezultat wywołania funkcji sqd z parametrem mr pomnożony przez wartość Pi. Kolejno utworzone są cztery funkcje obliczeniowe o nazwach sqd1 do sqd4, których adresy będą mogły być przekazane do funkcji Oblicz (i tu tkwi siła tego rozwiązania funkcja Oblicz może pracować na dowolnej liczbie funkcji, których sygnatura jest zgodna z JG). Możemy już wrócić do kodu formularza i napisać procedury związane z wykorzystaniem klasy CDelegacje. Zaczynamy od deklaracji obiektu w jako instancji klasy CDelegacje oraz od napisania pomocniczej, prywatnej procedury pp, która przyjmuje argument typu TextBox jako miejsce zwrócenia wyniku praz argument będący referencją (czyli delegacją) do potrzebnej funkcji obliczeniowej. 63
Pozostaje już tylko napisanie procedury zdarzenia Click przycisku btnoblicz1. Użycie procedury pp pozwoliło na zapisanie powyższej procedury w sposób jednocześnie zwarty i przejrzysty, i o to chodzi w programowaniu obiektowym. Poniżej widok formularza z wynikami obu metodami. 64