Po delegátech se budu v tomto díle věnovat další velmi zajímavé vlastnosti .NET frameworku, která se často používá při zpracovávání asynchronních operací v tomto prostředí. K tomu nám v .NET frameworku a tím pádem i v jazyku C# slouží členy třídy zvané události.
Co jsou události?
Události představují v jazyku C# cestu, která umožňuje třídě upozornit jinou třídu nebo třídy, že v ní došlo k něčemu zajímavému, na což by mohla tato třída respektive třídy určitým způsobem zareagovat. Časté použití můžeme nalézt v implementaci grafických uživatelských prostředí pro upozornění, že uživatel provedl nějakou akci, na níž by mohla být implementována reakce. Samozřejmě to není jediné možné použití události. Události se hodí všude tam, kde je potřeba upozornit okolí na to, že se v objektu respektive třídě došlo k nějaké změně stavu. Pro definici metod, které mohou být použity pro reakci na vzniklou událost slouží delegáty, kterými jsem se zaobíral v minulém díle, a proto by bylo vhodné abyste měli v implementaci delegátů jasno.
Deklarace události
Pro deklaraci nové události ve třídě použijeme klíčové slovo event. Musíme také určit typ delegáta, který asociujeme s touto události. To má za následek to, že třídy, které budou chtít na vzniklou událost reagovat, musí delegáta tohoto typu vytvořit a k tomuto delegátu asociovat obslužnou metodu(y).
modifikátory event typ_delegáta identifikátor_události;
Vyvolání události
Tím, že jsme ve třídě deklarovali událost, je možné k ní přistupovat jako ke členu delegáta určeného typu. Pokud tento člen nabývá hodnoty null, znamená to, že žádná jiná třída si tuto událost takzvaně nepředplatila, jinými slovy, že nehodlá na nově vzniklou událost jakýmkoli způsobem reagovat. Proto je vhodné před vyvoláním události vždy zkontrolovat jestli člen události nenabývá hodnoty null. Jelikož se tedy událost vyvolá stejně jako volání delegáta obecný způsob vyvolání vypadá takto:
if (identifikátor_události != null)
identifikátor_události (seznam_případných_parametrů);
Ukázková třída vyvolávající událost
Po lehkém obecném úvodu bych rád demonstroval použití události na konkrétním případu. Na ukázku definujeme třídu Žárovka, která bude, na rozdíl od její v implementace v jednom s prvních dílů tohoto seriálu, při svém rozsvícení či zhasnutí vyvolávat událost oznamující změnu stavu.
//deklarace delagata, ktery bude slouzit jako predpis metody
//predstavujici reakci na udalost (handler)
public delegate void ZmenaStavuHandler();
/// <summary>
/// Trida predstavujici zarovku, ktera vyvolava udalost
/// </summary>
public class Zarovka
{
private int vykon;
private bool sviti;
//deklarace udalosti
public event ZmenaStavuHandler Zmeneno;
public int Vykon
{
get
{
return vykon;
}
set
{
vykon = value;
}
}
public bool Sviti
{
get
{
return sviti;
}
}
public Zarovka(int vykon)
{
this.vykon = vykon;
}
//tato metoda vyvolava udalost
protected void PriZmene()
{
//kontrola, zda existuji klientske objekty
//ktere si udalost predplatitili
if (Zmeneno != null)
//vyvolani udalosti
Zmeneno();
}
public void Rozsvitit()
{
sviti = true;
//zavolani metody, ktera vyvolava udalost
PriZmene();
}
public void Zhasnout()
{
sviti = false;
//zavolani metody, ktera vyvolava udalost
PriZmene();
}
}
Na úrovni jmenného prostoru je deklarován delegát ZmenaStavuHandler, který je použit pro určení typu delegáta události Zmeneno ve třídě Žárovka. Delegát předepisuje, že obslužná metoda nebude vracet hodnotu a bude bez vstupních parametrů. Za zmínku stojí metoda PriZmene, která je určena k vyvolání události Zmeneno. Metoda zkontroluje jestli existuje nějaký klientský objekt, který si předplatil událost a pokud ano, dojde k vyvolání události. Metoda PriZmene je volána metodami Rozsviti a Zhasnout, které mění hodnotu soukromého atributu sviti.
Obsluha událostí
Pokud chceme nastalou událost nějaké instance třídy obsloužit, musíme nějakou třídu učinit takzvaným předplatitelem události. Zvenčí, lze k události třídy nebo její instance přistoupit jako ke členu, ale použití tohoto členu je velmi omezené. Jediné co je .NET frameworkem dovoleno s členy typu událost provést je přidání instance delegáta do seznamu delegátů pro její obsluhu nebo naopak instancí delegáta z tohoto seznamu vyjmout. K přidání instance delegáta slouží operátor += a k jeho vyjmutí operátor -= . Následující kód třídy ZarovkaTest ukazuje způsob obsluhy událostí ve třídě:
public class ZarovkaTest
{
public static void Test()
{
//vytvoreni instance tridy Zarovka
Zarovka mojeZarovka = new Zarovka(100);
//vytvoreni instanci delegatu pro obsluhu
//a jejich nasledne prirazeni do seznamu
//instanci delegatu pro obsluhu udalosti
mojeZarovka.Zmeneno += new ZmenaStavuHandler(StavZarovkyZmenenObsluha);
mojeZarovka.Zmeneno += new ZmenaStavuHandler(StavZarovkyZmenenObsluha2);
//metoda zapricini vyvolani udalosti
mojeZarovka.Rozsvitit();
}
private static void StavZarovkyZmenenObsluha()
{
Console.WriteLine("Stav zarovky byl zmenen");
}
private static void StavZarovkyZmenenObsluha2()
{
Console.WriteLine("Druha metoda obsluhujici nastalou zmenu stavu zarovky");
}
}
Jediné co se v metodě Test této třídy děje, je vytvoření instance třídy žárovka, následné vytvoření instancí delegátů typu ZmenaStavuHandler, které jsou asociovány s obslužnými metodami a ihned připojeny k obsluze události. Potom dochází k volání metody Rozsvit, která zapříčiní vyvolání události. Výstup po zavolání této metody bude tedy vypadat následovně, jak jistě mnozí předpokládají.
Stav zarovky byl zmenen
Druha metoda obsluhujici nastalou zmenu stavu zarovky
Události a dědičnost
Jednou ze specifických vlastností událostí, je že nemohou být přímo vyvolány z žádné jiné třídy, než ve které byly deklarovány, to znamená ani ve třídách odvozených, takže v případě, že bychom se pokusili zkompilovat následující kód, nebyli bychom úspěšní.
public class BarevnaZarovka : Zarovka
{
public BarevnaZarovka(int vykon) : base(vykon){ }
public BarevnaZarovka(int vykon,System.Drawing.Color barva) : base(vykon)
{
this.barva = barva;
}
private System.Drawing.Color barva;
public System.Drawing.Color Barva
{
get
{
return barva;
}
set
{
//chyba - udalost neni mozne v teto
//tride udalost vyvolat
Zmeneno();
barva = value;
}
}
}
Vytvořil jsem třídu reprezentující barevnou žárovku, která je odvozena od třídy Zarovka. Nicméně pokud se v ní pokusím vyvolat událost Zmeneno, překlad programu díky tomu skončí chybou. Řešení v těchto situacích se naskýtá ve formě zavolání metody implementované na bázové třídě, která danou událost vyvolá. Pochopitelně tato metoda musí mít takový specifikátor přístupu, aby k ni bylo možné v odvozené třídě přistoupit. V našem případě se jedná o metodu PriZmene. Takže funkční řešení by vypadalo takto:
public class BarevnaZarovka : Zarovka
{
public BarevnaZarovka(int vykon) : base(vykon){ }
public BarevnaZarovka(int vykon,System.Drawing.Color barva) : base(vykon)
{
this.barva = barva;
}
private System.Drawing.Color barva;
public System.Drawing.Color Barva
{
get
{
return barva;
}
set
{
PriZmene();
barva = value;
}
}
}
Tím, že metodu vyvolávající událost zpřístupníme odvozeným třídam, tedy dosáhneme toho, že odvozené třídy budou pomocí této metody schopny danou událost vyvolat. Odvozeným třídám můžeme nechat i vetší volnost při vyvolávání události a to tím, že metodu vyvolávající událost definujeme jako virtuální a tím pádem budou odvozené třídy tuto metodu překrýt a naimplementovat po svém.
Zdrojové kódy k příkladům naleznete zde.
V příštím díle si ještě prohloubíme znalosti o používání událostí v .NET frameworku.