Delegaty i zdarzenia

Zdarzenia są wykorzystywane w wielu miejscach .NET. Obsługują komunikaty Windows w kontrolkach oraz formach (także Web Forms) i są wspaniałym mechanizmem, który pozwala obiektowi poinformować o zmianie stanu czy wywołać funkcjonalność, która będzie znana dopiero po utworzeniu instancji innego obiektu.

Zdarzenia są wykorzystywane w wielu miejscach .NET. Obsługują komunikaty Windows w kontrolkach oraz formach (także Web Forms) i są wspaniałym mechanizmem, który pozwala obiektowi poinformować o zmianie stanu czy wywołać funkcjonalność, która będzie znana dopiero po utworzeniu instancji innego obiektu.

Jedną z istotnych cech .NET jest mechanizm delegatówi możliwa dzięki temu obsługa zdarzeń. Mówiąc w dużym skrócie, mechanizm ten pozwala wywołać określoną funkcję za pośrednictwem zmiennej, wskazującej na dany kod, przy czym delegaty są zupełnie bezpieczne w użyciu (w odróżnieniu od wskaźników w C+ +). Klasyczny przykład zastosowania zdarzeń jest pokazany w artykule „Pierwszy program i podstawy IDE". Aby określić, jaką akcję spowoduje naciśnięcie przycisku w formie (Win albo Web Forms), przypisujemy odpowiednią procedurę obsługi zdarzeń.

Załóżmy, że mamy klasę monitorującą stan fragmentu aplikacji. Gdy następuje pewne zdarzenie (np. skomplikowany algorytm zaczął działać nieprawidłowo), chcemy, żeby ta informacja została zarejestrowana. Problemy są dwa - załóżmy, że nie wiemy, w jaki sposób ma być to rejestrowane - to może być plik, dziennik zdarzeń w systemie albo komunikat SNMP. Poza tym klasa usługowa nic nie wie o sposobie rejestrowania - wie tylko, że gdzieś ma przekazać komunikat.

Właśnie w takich sytuacjach bardzo przydaje się mechanizm delegatów. Sposób rejestrowania określa sygnatura:

public delegate bool

LogInformationDelegate(string message);

Mówi ona tyle, że funkcja, która będzie używana do rejestrowania, przyjmuje jako parametr łańcuch znaków i zwraca wartość logiczną. Przykładowe funkcje implementujące proces tworzenia dziennika mogą mieć postać:

public class MiscFunctions {

public bool

RealLogInformation(string msg) {

Console.WriteLine("RealLogInformation:" + msg);

return true;

}

public static bool

StaticRealLogInformation(string msg) {

Console.WriteLine("StaticRealLogInformation:" + msg);

return true;

}

public bool

RealLogInformationExp(string msg) {

throw new ApplicationException( "Wyjątek na życzenie");

}

}

(Oczywiście są to tylko przykładowe implementacje - wysyłają informacje na konsolę.)

Klasa monitorująca ma pole - delegat (del), któremu przypisywane będą funkcje mające rejestrować informacje:

public class Monitoring {

public LogInformationDelegate del;

public void Signal(string msg) {

if (del == null) return;

del(msg);

}

private bool MyLog(string msg) {

Console.WriteLine("Prywatna funkcja MyLog: " + msg);

return true;

}

public void AttachPrivate() {

del = MyLog;

}

}

Funkcja Signal powoduje, że wywoływane są funkcje, które zostały przypisane do delegata. Oczywiście - trzeba najpierw sprawdzić, czy został zainicjowany.

Po zdefiniowaniu wszystkich elementów można zainicjować klasę „monitorującą":

1 Monitoring m = new Monitoring();

2 m.Signal("To się nie wyświetli - brak delegata");

3 MiscFunctions f=new MiscFunctions();

4 m.del = new LogInformationDelegate(f.RealLogInformation);

5 m.del = f.RealLogInformation;

6 m.Signal("Komunikat1");

Pierwsze wywołanie funkcji Signal niczego nie spowoduje (wiersz 2) - nie ma jeszcze zainicjowanego delegata. Dopiero gdy inicjujemy go w wierszu 4, klasa monitorująca wie, gdzie ma przesłać komunikat. Warto dodać, że z punktu widzenia MSIL wiersze 4 i 5 są równoważne.

Do delegata można dodawać funkcje. Wtedy wywołanie Signal spowoduje wywołanie dwóch funkcji (w tym wypadku dwukrotnie tej samej):

m.del += f.RealLogInformation;

m.Signal("2 razy ten sam komunikat");

Delegat jest też klasą MulticastDelegate (w wersji beta 2 - nie działa w tym wypadku mechanizm podpowiadania IntelliSense). Jedną z metod jest pobranie listy przypisanych adresów funkcji (GetInvocationList):

foreach

(Delegate d in m.del.GetInvocationList()) {

Console.WriteLine(d.Target + "," + d.Method);

}

Takie wyliczenie ma ważne zastosowanie. Załóżmy, że na liście przypisanej do delegata jedna funkcja (pierwsza) spowoduje wyjątek:

m.del = f.RealLogInformationExp;

m.del += f.RealLogInformation;

Wtedy wywołanie:

try {

m.Signal("Ajj"); //Nic się nie wyświetli

} catch {}

nie spowoduje żadnej akcji, bo po zgłoszeniu wyjątku wywołanie dalszych funkcji będzie wstrzymane. Ale jeżeli funkcje będą wywoływane w taki sposób:

foreach

(Delegate d in m.del.GetInvocationList()) {

LogInformationDelegate loc = (LogInformationDelegate)d;

try {

loc("to się wyświetli raz");

} catch {

}

}

gdzie po kolei zostaną wyliczone kolejne elementy przypisane do delegata, to możemy kontrolować, co się dzieje, gdy jedna z funkcji, na którą wskazuje delegat, zgłosi wyjątek. Oczywiście - od delegata można odjąć wskaźnik do funkcji:

m.del -= f.RealLogInformationExp;

m.Signal("tylko raz komunikat");


Zobacz również