Pogledaj jedan post
Old 24.08.2003., 23:49   #1
Programiranje u .NET-u i C#-u: moja redovna "kolumna" :-)

Ovim postom zapocinjem moju redovitu forumsku "kolumnu" u kojoj cu se baviti programiranjem u C#-u i .NET-u. Za citanje kolumne potrebno je solidno poznavanje objektno-orijentiranog programiranja npr. u C++-u ili Javi. U svojim tekstovima nastojat cu obuhvatiti one stvari koje su specificne za C# i .NET, i koje se ne pojavljuju (ili su nesto drugacije) u ostalim OO jezicima.
Danasnja tema bit ce delegati:

Callback funkcije spadaju u vrlo korisne programske konstrukte. Sjetimo se npr. standardne C-funkcije qsort. Ova funkcija kao jedan od ulaznih parametara prima pointer na funkciju kojom usporedjujemo elemente polja kojeg sortiramo. To je vrlo korisna mogucnost, jer time rad funkcije qsort poopcavamo, tako da ona ispravno radi ne samo sa standardnim tipovima podataka (poput int, float...), nego je mozemo prilagoditi nasim potrebama i sortirati neki korisnicki-definiran tip podataka po kriteriju kojeg sami odaberemo.

I u Windows-programiranju cesta je upotreba callback funkcija: koriste se za windows-procedure, za razne hook-ove, za asinkrono pozivanje funkcija, itd.

Callback funkcije koriste se, naravno i u Microsoft .NET-u, i to za vrlo razlicite stvari: npr. mozemo registrirati callback metodu koja ce se pozvati onda kad se neki assembly ucita, kada se promijeni stanje nekog prozora, kad se izabere neka opcija u meniju, kada s radom zavrsi neka asinkrona operacija, itd, itd.

Adresa neke funkcije u C-u i u C++-u je samo broj (tj. adresa memorijske lokacije prvog bajta asemblerkog koda te funkcije). Ta adresa u sebi ne nosi nikakve druge podatke kao npr. broj parametara koji funkcija prima, tipove tih ulaznih parametara ili tip kojeg funkcija vraca kao rezultat svog rada. Ukratko, C/C++ callback funkcije nisu type-safe.

U .NET frameworku je drugacije: umjesto obicne adrese callback funkcije, koristimo type-safe mehanizam koji zovemo delegati. Sljedeci program daje nam jedan primjer koristenja delegata:

Kod:
using System;
using System.Windows.Forms;
using System.IO;

class Skup
{
   private Object[] elementi;

   public Skup(int brElem)
   {
      elementi = new Object[brElem];
      for (int i = 0; i < brElem; i++)
         elementi[i] = i;
   }

   // Definiramo tip CallBackFn 
   // (UOCI: ovaj tip je ugnjezden unutar klase Skup)
   public delegate void CallBackFn(Object value, int el, int brElem);


   public void ProsetajPoPoljuIZoviFunkciju(CallBackFn fn)
   {
      for (int el = 0; el < elementi.Length; el++)
      {
         if (fn != null)
         {
            // ako je zadana bar jedna callback funkcija, pozovi je
            fn(elementi[el], el + 1, elementi.Length);
         }
      }
   }
}


class TestDriver
{
   static void Main()
   {
      StatickiCallBackovi();
      CallBackoviInstance();
   }


   static void StatickiCallBackovi()
   {
      // Kreiraj skup od 5 elemenata
      Skup skupElemenata = new Skup(5);

      // Prosetaj po elementima skupa bez ikakve callback funkcije
      skupElemenata.ProsetajPoPoljuIZoviFunkciju(null);
      Console.WriteLine();

      // Prosetaj po elementima skupa i nad svakim elementom pozovi funkciju NaKonzolu
      skupElemenata.ProsetajPoPoljuIZoviFunkciju(
new Skup.CallBackFn(TestDriver.IspisNaKonzolu));
      Console.WriteLine();

      // Prosetaj po elementima skupa i nad svakim elementom pozovi funkciju UProzor
      skupElemenata.ProsetajPoPoljuIZoviFunkciju(
new Skup.CallBackFn(TestDriver.IspisUProzor));
      Console.WriteLine();

      // Prosetaj po elementima skupa i nad svakim elementom 
      // pozovi i funkciju NaKonzolu i funkciju UProzor
      Skup.CallBackFn cb = null;
      cb += new Skup.CallBackFn(App.IspisNaKonzolu);
      cb += new Skup.CallBackFn(App.IspisUProzor);
      skupElemenata.ProsetajPoPoljuIZoviFunkciju(cb);
      Console.WriteLine();
   }

   static void IspisNaKonzolu(Object value, int el, int brElem)
   {
      Console.WriteLine("Vrijednost elementa {0} od {1}: {2}.", el, brElem, value);
   }

   static void IspisUProzor(Object value, int el, int brElem)
   {
      MessageBox.Show(String.Format("Vrijednost elementa {0} od {1}: {2}.",
 el, brElem, value));
   }


   static void CallBackoviInstance()
   {
      // Kreiraj skup od 5 elemenata
      Skup skupElemenata = new Skup(5);

      // Prosetaj po elementima skupa i nad svakim elementom pozovi IspisUFile
      TestDriver testObj = new TestDriver();
      skupElemenata.ProsetajPoPoljuIZoviFunkciju(
new Skup.CallBackFn(testObj.IspisUFile));
      Console.WriteLine();
   }

   void IspisUFile(Object value, int el, int brElem)
   {

      StreamWriter sw = new StreamWriter("Datoteka.dat", true);
      sw.WriteLine("Vrijednost elementa {0} od {1}: {2}.", el, brElem, value);
      sw.Close();
   }
}
Uocimo klasu Skup na vrhu gornjeg programa. Zamislimo da klasa Skup sadrzi skup elemenata koje zelimo procesuirati jedan po jedan. Kada kreiramo objekt tipa Skup, njegovom konstruktoru proslijedjujemo broj elemenata od kojih se skup sastoji. Konstruktor tada kreira polje Objekata i inicijalizira da svaki objekt bude jedan cijeli broj.

Klasa Skup takodjer definira i tzv. delegat. Delegatom zadajemo signaturu callback metode. U nasem primjeru, delegat CallBackFn zadaje signaturu callback metode koja prima tri parametra (jedan Object, i dva int-a) i vraca vrijednost void. Na neki nacin, delegati su vrlo slicni typedef-ovima u C-u koji reprezentiraju adresu neke funkcije.
U nastavku, u klasi Skup definirali smo jos i funkciju ProsetajPoPoljuIZoviFunkciju. Ova metoda prima jedan ulazni parametar, fn, koji je ustvari referenca na CallBackFn delegat-objekt. ProsetajPoPoljuIZoviFunkciju "sece" po svim elementima polja i nad svakim elementom poziva metodu zadanu u varijabli fn. Toj metodi proslijedjuje se vrijednost elementa kojeg upravo procesiramo, redni broj tog elementa i ukupan broj elemenata u polju. Poanta je u tome da callback metoda moze procesirati elemente na koji god nacin zeli!

Metoda StatickiCallBackovi demonstrira razlicite nacine uporabe callback delegata. Najprije se konstruira objekt tipa Skup od 5 elemenata. Zatim se poziva ProsetajPoPoljuIZoviFunkciju sa ulaznim parametrom null. Ovo je prvi primjer upotrebe delegata. Prisjetimo se: ProsetajPoPoljuIZoviFunkciju je metoda koja nad svakim elementom u Skupu izvodi neku akciju. No, kako je CallBackFn parametar koji smo proslijedili ovoj funkciji bio null, svaki element ce se procesuirati bez pozivanja bilo kakve callback metode.

Iduca dva slucaja su ocita: prvi ce ispisati elemente skupa na konzolu, a drugi u MessageBox prozore, jer smo prvi put pozvali metodu ProsetajPoPoljuIZoviFunkciju s ulaznim parametrom TestDriver.IspisNaKonzolu, a drugi put s parametrom TestDriver.IspisUProzor. Primjetimo da smo upotrijebili kljucnu rijec new (npr. new Skup.CallBackFn(TestDriver.IspisNaKonzolu) ). Time smo u biti kreirali delegat koji u biti predstavlja tzv. tape-safe "wrapper" za funkciju IspisNaKonzolu.

Posljednja dva primjera pokazuju kako se delegati mogu medjusobno povezati u svojevrsni lanac. Najprije smo kreirali reference-varijablu cb na CallBackFn delegat objekt i inicijalizirali je na vrijednost null. Ta varijabla u biti pokazuje na zaglavlje vezane liste delegata. Vrijednost null naznacuje da u toj vezanoj listi zasad nemamo nijedan cvor. U nastavku programa konstruira se delegat-objekt koji predstavlja type-safe "wrapper" za TestDriver-ovu metodu IspisNaKonzolu. Nadalje, koristimo operator += , cime u vezanu listu delegata na koju pokazuje varijabla cb dodajemo netom konstruirani delegat. Nakon toga, ponovo pomocu operatora += u istu vezanu listu dodajemo jos jedan delegat, koji je, ovog puta, "wrapper" za metodu IspisUProzor.

Sada, kada se pozove funkcija ProsetajPoPoljuIZoviFunkciju sa ulaznim parametrom cb (koji je, podsjetimo se, zaglavlje vezane liste delegata), sto ce se dogoditi??? Unutar funkcije ProsetajPoPoljuIZoviFunkciju, u liniji u kojoj se poziva callback metoda ustvari ce se redom pozvati sve callback metode koje su bile "wrappane" u vezanoj listi delegata! Drugim rijecima, za svaki element najprije ce se pozvati IspisNaKonzolu, a zatim IspisUProzor.

Vazno je uociti da je sve u gornjem primjeru bilo type-safe: npr. kada konstruiramo neki CallBackFn delegat, kompajler pazi na to da funkcije koje "wrappamo" imaju signaturu identicnu onoj koju smo naveli pri deklariranju delegata. Tj. u nasem slucaju obje metode moraju uzimati tri ulazna parametra (object, int i int) i obje metode moraju vracati void. Ako ovo ne bi bio slucaj, kompajler bi javio gresku.

U programu smo do sada delegate koristili samo za pozive statickih metoda. Medjutim, delegate mozemo koristiti i za pozivanje metoda instanci objekata. Jedino, moramo tada navesti instancu objekta nad kojim zelimo pozvati metodu.
Kad ovo uocimo, nastavak programa lako cemo razumjeti.

Eto, sada smo vidjeli otprilike kako se koristimo delegatima, cemu oni sluze, itd. No, kako su delegati interno, u CLR-u definirani??? Kako stvar funkcionira ispod "haube"??? O tome cete doznati vise u nekom od mojih sljedecih tekstova!

pozdrav svima od
math_baby
__________________
"Open source is an intellectual-property destroyer. I'm an American, I believe in the American Way. I worry if the government encourages open source and I don't think we've done enough education of policymakers to understand the threat."" -Jim Allchin, Microsoft operating system chief
math_baby is offline  
Odgovori s citatom