Zdarzenia stanowią bardzo wygodny mechanizm monitorowania stanu obiektów. W .NET można spotkać je na każdym kroku. Kontrolki zarówno w WinForms jak i WPF, posiadają wiele zdarzeń, często kilkadziesiąt. Niestety każda deklaracja zdarzenia pochłania zasoby. Nie ma to znaczenia gdy obiekt posiada tylko kilka zdarzeń ale może to być zauważalne dla skomplikowanych klas np. kontrolek w WinForms. Każda kontrolka eksponuje dziesiątki zdarzeń a użytkownicy zwykłe korzystają wyłącznie z kilku – rzadko ma miejsce sytuacja w której trzeba do wszystkich się podpiąć. Warto zobaczyć co naprawdę generuje prosta deklaracja zdarzenia:
internal class Program { public event EventHandler SampleEvent; private static void Main(string[] args) { } }
Reflector:
.class private auto ansi beforefieldinit Program extends [mscorlib]System.Object { .event [mscorlib]System.EventHandler SampleEvent { .addon instance void Program::add_SampleEvent(class [mscorlib]System.EventHandler) .removeon instance void Program::remove_SampleEvent(class [mscorlib]System.EventHandler) } .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { } .method private hidebysig static void Main(string[] args) cil managed { .entrypoint } .field private class [mscorlib]System.EventHandler SampleEvent }
Jak wdać, została wygenerowana specjalna właściwość oraz delegata. Pytanie brzmi: po co generować tyle pól delegate, skoro tylko kilka zdarzeń zwykle jest wykorzystywanych?
Klasa EventHandlerList to po prostu kolekcja zdarzeń. Przykład:
class SampleClass { protected EventHandlerList _events = new EventHandlerList(); public event EventHandler SampleEvent1 { add { _events.AddHandler("SampleEvent1", value); } remove { _events.RemoveHandler("SampleEvent1", value); } } public event EventHandler SampleEvent2 { add { _events.AddHandler("SampleEvent2", value); } remove { _events.RemoveHandler("SampleEvent2", value); } } public void TestEvent1() { EventHandler eh = _events["SampleEvent1"] as EventHandler; if (eh != null) { eh(this, null); } } }
EventHandlerList stanowi pojemnik na zdarzenia. Zamiast tworzyć za każdym razem delegate, w powyższym kodzie są one tworzone wyłącznie gdy zajdzie taka potrzeba. Klasa, która ma 50 zdarzeń zużyje pamięć wyłącznie na EventHandlerList i potrzebne zdarzenia. Wywołanie takiego zdarzenia jest proste ponieważ do EventHandler’a można dostać się jak do słownika – poprzez przekazanie klucza. Częstym wzorcem jest użycie klucza jako statyczne pole read-only:
internal class SampleClass { protected EventHandlerList _events = new EventHandlerList(); private static readonly object SampleEventKey = new object(); public event EventHandler SampleEvent1 { add { _events.AddHandler(SampleEventKey, value); } remove { _events.RemoveHandler(SampleEventKey, value); } } public void TestEvent1() { EventHandler eh = _events[SampleEventKey] as EventHandler; if (eh != null) { eh(this, null); } } }
EventHandlerList jest powszechnie wykorzystywany w bibliotekach Microsoft’u. Na przykład każda kontrolka w WinForms ma właściwość Events, która jest typu EventHandlerList. Z tego względu poniższy kod można uznać za anty-wzorzec:
public class CustomButton : Button { public event EventHandler SampleEvent; }
Dużo lepiej jest napisać:
public class CustomButton : Button { private static readonly object SampleEventKey = new object(); public event EventHandler SampleEvent1 { add { Events.AddHandler(SampleEventKey, value); } remove { Events.RemoveHandler(SampleEventKey, value); } } public void TestEvent1() { EventHandler eh = Events[SampleEventKey] as EventHandler; if (eh != null) { eh(this, null); } } }
Mała uwaga do “EventHandler stanowi pojemnik na zdarzenia.” Nie powinno być EventHandlerList?
Pozdrawiam,
Sky
@Skynet:
Tak dokladnie. Dzieki za uwage.