Programujeme v jazyku C# II. Diel 17. – Udalosti, dokončenie

Michal Čižmár  /  12. 10. 2006, 00:00

Asi v takomto období pre rokom som začal písať druhú sériu o programovacom jazyku C#. Nastal čas poslúchnuť hlasy čitateľov v diskusiách a v e-mailoch a začať s novou sériou. Tento diel ale ešte patrí do 2 série, pretože posledná kapitola zostala nedokončená.

Aby som mal “srdce na mieste“ musím ešte niečo povedať o šírení udalostí v programe pomocou delegátov. Na konci navrhnem, čomu by sme sa mohli venovať v III. sérií. Tento článok bude hlavne o diskusii za ním, pretože som zvedavý, či ste radi, že sa seriál znovu rozbieha a či ste spokojní s tým, o čom by mala byť nová séria.
 
>>Implementácia Udalostí pomocou Delegátov
 
Na začiatku kapitoly o udalostiach sme si vysvetlili ich filozofiu a potom sme sa hrali s delegatmi, ich rôznymi typmi a povedali sme si aj niečo o anonymných metódach, ktoré prináša C# v2. No a od delegátov k udalostiam je naozaj len nepatrný krôčik, ale nestihol som to už vysvetliť. Takže len pre poriadok si to stručne vysvetlime a poďme na príklady.
 
Udalosť sa deklaruje slovíčkom event,  potom sa napíše meno delegáta, ktorým vlastne definujeme, akú hlavičku budú mať metódy, ktoré budú spúšťané danou udalosťou a na koniec napíšeme meno danej udalosti. Hovoríte si: “tomu neporozumiem, kým to neuvidím!“ ? Tak to znamená, že ste na tom tak isto ako ja, takže nech sa páči, príklad:
 
Príklad 17.1
using System;
      //Zadeklarujeme typ delegata
public delegate void JednoduchyDelegat();

class Odoberatel
{
      public static void Main(string[] args)
      {
            //nastavenie odberatelov
            Poskytovatel.HelloWorldEvent+=PrvyOdberatel;
            Poskytovatel.HelloWorldEvent+=DruhyOdberatel;

            //Spustenie udalosti:
            Poskytovatel.ZavolajOdberatelov();
            Console.ReadLine();
      }

     
private static void PrvyOdberatel()
      {
            Console.WriteLine("Hlasi sa 1. odberatel");
      }
  
      private static void DruhyOdberatel()
      {
            Console.WriteLine("Hlasi sa 2. odberatel");
      }
}
//---------------------------------------

class Poskytovatel
{
      //Vytvorime udalost pomocou delegata
      public static event JednoduchyDelegat HelloWorldEvent;
    
      public static void ZavolajOdberatelov()
      {
            Console.WriteLine("Idem zavolatat odberatelov");
            if (HelloWorldEvent!=null) HelloWorldEvent();
      }
}
//---------------------------------------

  Príklad na stiahnutie

 

 

SharpDEvelop ikonka

 

Pre zaujímavosť som Vám spravil ScreenShot, keď SharpDevelop ponúkal možnosti na výber, tak udalosť rozlíšil pomocou ikonky podobnej blesku. Toto je štandardné znázornenie udalostí aj v iných vývojových prostrediach.

 
>>Prečo používať Eventy, keď Delegát robí to isté?
 
Ak ste pozorne čítali predchádzajúce diely o udalostiach, tak si asi hovoríte, že načo robiť vlastne dvojitú robotu pomocou definovania udalostí, keď by sa predchádzajúci príklad dal spraviť rovno na úrovni delegátov.
 
Všimnite si prvý rozdiel, ktorý sa prejavil pri pridávaní odberateľov do zoznamu. Pomocou delegátov sme to v príklade 14.2 pre dvoch odberateľov riešili takto:
 
pozdravenie = new BezParametrov(Pozdrav1);
pozdravenie += new BezParametrov(Pozdrav2);
 
Pomocou udalosti:
HelloWorldEvent+=PrvyOdberatel;
 
Taktiež si všimnite, že odberatelia pre udalosť sa pridávajú len pomocou += (prípadne -=)
 
Druhý rozdiel  z príkladu nie je možno tak priamo vidno. Ale je to veľmi podstatný rozdiel, tak tu dám aj pozor:-)

POZOR!!!
Udalosť môže byť spustená iba v triede, ktorá ju vlastní na rozdiel od delegátov, ktoré keď boli zadeklarované ako public, mohli byť spustené aj inou triedou.
 
Ak by sme teda chceli spustiť externe udalosť HelloWorld() napr. takto:
Poskytovatel.HelloWorldEvent();
 
Tak kompilátor zakričí toto:
The event 'Poskytovatel.HelloWorldEvent' can only appear on the left hand side of += or -= (except when used from within the type 'Poskytovatel') (CS0070)
 
Preto som v príklade, najpr zavolal metódu Poskytovatel.ZavolajOdberatelov();, z ktorej sa zavolala interná udalosť.  
 
Týmto spôsobom je umožnené perfektne dodržať princípy OOP, hlavne čo sa týka bezpečnosti a zapuzdrenia.
 
>>Vlastný časovač (Timer) pomocou udalostí
Keďže predchádzajúci príklad slúžil hlavne na objasnenie deklarácie udalosti a jej použitia, teraz sa pozrime na trochu názornejší príklad. Ak ste už programovali s vizuálnymi komponentami, tak ste sa určite stretli s komponentou Timer. Podľa mňa je ideálnou ukážkou, na záver kapitoly o udalostiach, si spraviť takýto Timer vlastný. Čo poviete?
 
 
Príklad 17.2
using System;
using System.Threading;
 
public delegate void JednoduchyDelegat();
//--------------------------------------      
class MainClass
{
      public static void Main(string[] args)
      {
            //registrovanie udalosti
            MyTimer.Tick+=PisKazduSekundu;
            MyTimer.Start(1);
           
      }
     
      private static void PisKazduSekundu()
      {
            DateTime dt = DateTime.Now;
            string cas = dt.ToLongTimeString();
           
         Console.WriteLine("Prave je:{0}", cas);             
      }          
 }
//---------------------------------------
class MyTimer
{
      public static event JednoduchyDelegat Tick;
     
      public static void Start(int sek)
      {
            int delay = sek *1000;
            for( ; ; )
            {
                  Thread.Sleep(delay);
                  if (Tick!=null) Tick();                 
            }
      }          
 /---------------------------------------           
 }

  Príklad na stiahnutie
 

Or. k prikl. 17.2

 
Vytvorili sme udalosť Tick, ktorá je volaná z vnútra triedy MyTimer v nekonečnej slučke, pseudoparalelne s vykonávaním ďalších častí programu. Aj keď sme na túto udalosť v príklade zavesili len jednu metódu, nie je problém ich tam dať viac. Ak ste náhodou z tohto príkladu nepochopili ako fungujú udalosti, napíšte prosím dole do diskusie, po ktorý bod  je to ešte jasné.
 
 
>>Štandard pri tvorbe udalostí
V minulých článkoch som spomínal, že udalosti sú dobrý základ na  RAD nástroje, ale aj na tvorbu projektov v tímoch, kde jeden programátor programuje odberateľa a druhý poskytovateľa určitej udalosti. Preto je dobré dodržiavať aj nejaké štandardy pri deklarovaní udalostí, či skôr delegátov. Delegát by nemal mať žiaden návratový typ a mal by mať len dva vstupné parametre, takže niečo takéto:
 
public delegate void EventHandler(object sender, EventArgs e);
 
Parameter sender je odkaz na triedu, ktorá udalosť vyvolala. Toto sa používa, ak je tá istá metóda zaregistrovaná vo viacerých udalostiach a potom podľa typu odosielateľa sa môže zachovať rôznymi spôsobmi, alebo dodatočne ešte spätne volať nejaké jeho metódy. EventArgs je základná trieda, od ktorej môžu byť odvodené triedy, ktoré chceme predávať ako parametre udalosti.
 
>>Finito s udalosťami
Dúfam, že po súvislom prečítaní celej kapitoly o udalostiach, nielen že chápete princípu poskytovateľ - odberateľ, ale vedeli by ste to použiť aj vo vlastných programoch a tým nemyslím, len nastavenie rekcii na stlačenie tlačítka v okne :-)
 
 
>>Tak čo s ďalšou sériou?
Ak sa učíte C# podľa tohto seriálu, tak podľa mňa by bolo logickým krokom pozrieť sa na databázy. Oboznámili by sme sa s ADO.NET a ako testovaci server by sme použili SQL Server 2005 Express, ktorý je zadarmo (neskôr aj Oracle 10g , MySQL ci PostgreSQL).

Určite Vás zaujme, že napr. SQL Server 2005 je tak úzko zviazaný s platformou .NET, že je možné triggre alebo uložené procedúry písať priamo v C#, bez toho aby ste do hĺbky museli ovládať všetky SQL príkazy na ich tvorbu.
 
Uvítam konštruktívne nápady na tému 3 série dole v diskusii, alebo či jednoducho súhlasíte s takýmto zameraním seriálu na najbližšiu dobu.
 
SEE YOU!
 
Michal Čižmár

Neprehliadnite: