C# události pro callback funkce
Události (event) v .NET framework jsou efektivní a velmi jednoduchý způsob, jak implementovat návrhový vzor observer. Klíčové slovo event zná asi každý, kdo s C# přišel do styku. Při práci na .NET wrapperu pro SQLite jsem ale narazil na zajímavý problém – jak zajistit, aby callback funkce vyvolávající událost byla zaregistrována, jen pokud na událost čeká alespoń jeden klient?
V duchu ostatních komponent v .NET framework by bylo očekavatelné, že by pro třídu reprezentující událost byly definované události AddDelegate a RemoveDelegate – tím bychom ale objekt definovali rekursivně tak, že každá jeho instance by obsahovala instanci objektu stejné třídy. Což pochopitelně může vést při implementaci k nekonečným problémům s nekonečnými rekursivními strukturami.
Jak se překládají události
class PozarniAlarm {
public event EventHandler Hori;
}
Při pohledu na kód vygenerovaný překladačem pro událost je vidět, že pro událost je překladačem vygenerovaný delegát se stejnou signaturou, jakou má událost a se stejným jménem jako má událost; dále pro ni jsou vygenerovány metody add_Hori(EventHandler) a remove_Hori(EventHandler).
Pokud v kódu pracujeme s událostí pomocí jejího názvu, až na operátory += a -= pracujeme právě s tímto delegátem. Zmíněné operátory se pochopitelně přeloží na volání odpovídající metody add_Hori, nebo remove_Hori.
Události, které by zachycovaly přidání nebo odebrání klienta události neexistují, ale nabízí se otázka, jestli by nebylo možné nějakým způsobem přetížit ony metody pro += a -=. A jak zní odpověď? Ale samozřejmě…
Vlastní add a remove
Podobně jako se u properties zadávají bloky kódu pro přečtení a zápis hodnoty, zadávají se i bloky kódu pro přidání a odebrání handleru, celková struktura je properties velmi podobná.
class PozarniAlarm {
public event EventHandler Hori {
add {
// ... kód pro přidání obsluhy ...
}
remove {
// ... kód pro odebrání obsluhy ...
}
}
}
Pokud části add a remove neuvedete, překladač automaticky za vás vygeneruje kód analogický tomuto pro přidávání
Hori = (EventHandler)EventHandler.Combine(Hori, value);
a tomuto pro odebírání
Hori = (EventHandler)EventHandler.Remove(Hori, value);
Hori v těchto blocích kódu samozřejmě vystupuje ve významu delegáta, ve kterém se akumulují všechny delegáty přidané k události pomocí operátoru +=.
Pokud naopak bloky add a remove zadáme, překladač automaticky negeneruje proměnnou akumulující přidané delegáty a je nutné se o ni postarat. A tato proměnná, kterou pro událost vytvoříme ručně bohužel nesmí mít stejné jméno jako událost. Škoda…
Spojení s callback funkcí
V tomto bodě už je jednoduché do procesu vstoupit a ve vhodnou chvíli zaregistrovat svou callback funkci, která událost vyvolává.
class PozarniAlarm {
public event EventHandler Hori {
add {
// zjednodušená syntaxe pro spojování delegátů
m_HoriListeners += value;
// pokud se k události přihlásil první čekatel, zaregistruj
// callback funkci
if(m_HoriListeners.GetInvocationList().Length == 1)
RegisterCallback();
}
remove {
// pokud se odhlašuje poslední čekatel, odregistruj
callback funkci
if(m_HoriListeners.GetInvocationList().Length == 1)
UnregisterCallback();
m_HoriListeners -= value;
}
}
// pro události, kde jsou zadané add a remove se proměnná
// udržující seznam přidaných delegátů nevygeneruje
// automaticky, musíme si jí vyrobit sami
private EventHandler m_HoriListeners;
private void RegisterCallback() {
// ... proveď registraci callback funkce vyvolávající
// událost Hori ...
}
private void UnregisterCallback() {
// ... zruš registraci callback funkce vyvolávající
// událost Hori ...
}
}