Pogledaj jedan post
Old 13.09.2003., 15:35   #15
Delegati ispod "haube"!

Evo, proslo je dosta vremena otkad sam zapocela ovu moju kolumnu. U prvom nastavku govorila sam o delegatima koji nam u .NET-u daju type-safe nacin pozivanja callback funkcija. Takodjer sam obecala da cu u iducem "nastavku" opisati detaljnije kako rade delegati "ispod haube". No, prije nego pocnem, zeljela bih samo napomenuti da mi se u proslom listingu potkrala jedna greska. Naima, u funkciji StatickiCallBackovi umjesto linija

Kod:
cb += new Skup.CallBackFn(App.IspisNaKonzolu);
cb += new Skup.CallBackFn(App.IspisUProzor);
trebale su stajati linije

Kod:
cb += new Skup.CallBackFn(TestDriver.IspisNaKonzolu);
cb += new Skup.CallBackFn(TestDriver.IspisUProzor);
Do ove greske doslo je jer sam naknadno preimenovala klasu App u klasu TestDriver, ali sam zaboravila to ime svugdje promijeniti.

OK, sad idemo dalje. Proucimo li jos jednom primjer iz proslog listinga, vidjet cemo da su delegati zapravo laki za koristenje: jednostavno ih definiramo pomocu kljucne rijeci delegate, pomocu kljucne rijeci new konstruiramo instancu delegata i nakon toga pozivamo callback funkcije pomocu nama bliske "method call" sintakse, samo sto umjesto imena metode koristimo ime varijable koja referencira na delegatski objekt.

No, ono sto se pri svemu tome dogadja interno, nesto je malo slozenije: kompajler i CLR obavljaju dosta skrivenog posla da bi stvar nama izgledala jednostavno. No, sada dolazi trenutak prosvjetljenja:

Pogledajmo liniju koda iz proslog listinga kojom smo bili definirali nas delegatski objekt:

Kod:
public delegate void CallBackFn(Object value, int el, int brElem);
Kada kompajler vidi ovu liniju, on ustvari definira cijelu jednu klasu koja izgleda ovako:

Kod:
public class CallBackFn : System.MulticastDelegate
{
	//konstruktor
	public CallBackFn(Object target, int methodPtr);

	//Metoda s istim prototipom kao sto je zadano u source-kodu
	public void virtual Invoke(Object value, int el, int brElem);

	//Metode koje omogucuju asinkrono pozivanje callback funkcije
	public virtual IAsyncResult BeginInvoke(Object value, int el, int brElem,
AsyncCallback callback, Object object);

	public virtual void EndInvoke(IAsyncResult result);
}
Dakle, klasa koju generira kompajler (CallBackFn) ima 4 metode: konstruktor, Invoke, BeginInvoke i EndInvoke. U nastavku cu se koncentrirati na konstruktor i Invoke metodu.

Nadalje, nasa klasa CallBackFn je public, jer smo delegat definirali kao public. Da smo delegat definirali kao npr. protected ili private, tada bi i klasa CallBackFn bila protected ili private. Buduci da je nas delegat ustvari klasa, mozemo ga definirati ili ugnijezdenog unutar neke druge klase ili u global scope-u.

Primjetimo da je klasa CallBackFn naslijedjena iz bazne klase System.MulticastDelegate.
System.MulticastDelegate je klasa definirana u .NET biblioteci. Svi delegati u .NET-u derivirani su iz ove klase pa nas delegat CallBackFn (kao i svi ostali delegati!) naslijedjuje neke fieldove, propertiese i metode od klase System.MulticastDelegate. Od svih tih member-a najznacajnija su ova tri privatna fielda:

_target (koji je tipa System.Object)
_methodPtr (koji je tipa int)
_prev (koji je tipa System.MulticastDelegate)

Idemo redom:

_target referencira na objekt nad kojim ce se operirati kad se pozove callback metoda. Ovaj field se koristi za pozivanje callback metoda instanci objekata.

_methodPtr je cijeli broj koji CLR koristi da bi identificirao stvarnu callback metodu koju treba pozvati (dakle, _methodPtr ustvari glumi onaj pointer uobicajen u C-u)

_prev referencira na drugi delegatski objekt. Ovaj field je obicno null, ali nekad i ne mora biti!

Primjetimo da svi delegati imaju konstruktor koji uzima dva parametra: referncu na neki objekt i cijeli broj koji (glumeci pointer) referencira na pravu callback metodu. Medjutim, ako pogledamo program iz prvog listinga, vidjet cemo da se tamo, pri konstruiranju delegata pomocu kljucne rijeci new koriste konstrukti poput

new CallBackFn(App.IspisNaKonzolu);

ili

new CallBackFn(testObj.IspisUFile);

Kako je ovo moguce kad konstruktor delegata CallBackFn ocekuje drugacije parametre??? Jer, po ovome nasem se taj kod jednostavno ne bi mogao kompajlirati bez javljanja greske.

Medjutim, kompajler pri kompajliranju zna da se u tim nasim new naredbama konstruira delegat, pa prolazi kroz nas source-kod kako bi odredio na koji objekt i na koju metodu se nas delegat odnosi. Tada kompajler generira kod koji na "klasican" nacin poziva konstruktor od CallBackFn objekta proslijedjujuci mu kao parametar target referencu na objekt ciju callback funkciju zelimo pozvati (ili null, ako je metoda staticka!), a kao parametar methodPtr proslijedjuje mu token (adresu) zeljene metode.

Sto radi konstruktor delegata? Po njegovom pozivu, najprije se field _prev postavlja na null. Field _prev koristi se za kreiranje vezane liste objekata tipa MulticastDelegate. Za sada cu ignorirati ovo polje, ali u nekom od iducih nastavaka govorit cu o tzv. lancima delegata, pa ce tamo biti rijeci i o njemu.

Dakle, svaki delegat je u osnovi wrapper oko neke metode. Delegat je zapravo objekt nad kojim baratamo kada pozivamo callback metodu (ili vise njih).

Klasa MulticastDelegate definira i dva read-only propertyja: Target i Method.
Ako imamo referencu na neki delegatski objekt, tada mozemo procitati sto je zapisano u ova dva propertyja. Target nam vraca referencu na objekt nad kojim se poziva callback metoda. Ako je callbac metoda staticka, tada Target vraca null. Metod nam vraca objekt tipa System.Reflection.MethodInfo koji identificira callback metodu.

Ove informacije mozemo upotrijebiti na vise nacina. Npr. mozemo provjeriti referencira li neki delegatski objekt na metodu objekta nekog sasvim specificnog tipa:

Kod:
Boolean DelegatReferenciraNaMetoduZadanogTipa(MulticastDelegate d, Type type)
{
	return ((d.Target != null) && d.Target.GetType() == type);
}
Takodjer, mogli bismo npr. napisati kod koji provjerava ima li nasa callback metoda neko unaprijed zadano ime (npr. IspisUProzor):

Kod:
Boolean DelegatReferenciraNaMetoduZadanogImena(MulticastDelegate d, string methodName)
{
	return (d.Method.Name == methodName);
}
Naravno, ne treba ni reci da je ovako nesto u Javi nemoguce izvesti! No, pustimo to smece i idemo dalje!

Sad kad smo vidjeli kako se delegati konstruiraju, pogledajmo kako se pozivaju callback metode. U onom prvom listingu imali smo npr. ovakav programski isjecak:

Kod:
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);
         	}
      	}
}
Odmah nakon linije komentara dolazi linija u kojem se poziva callback metoda. Na prvi pogled, izgleda kao da pozivamo funkciju cije ime je fn kojoj prosljedjujemo tri parametra. Medjutim, funkcija s imenom fn ne postoji. Sto se onda dogadja???

Ponovo, zbog toga sto kompajler zna da je fn varijabla koja referencira na delegatski objekt, on generira kod koji poziva delegatovu Invoke metodu. Drugim rijecima, kad komajler vidi ovo:

Kod:
fn(elementi[el], el + 1, elementi.Length);
on generira kod kao da je pisalo ovo:

Kod:
fn.Invoke(elementi[el], el + 1, elementi.Length);
Da je ovo stvarno istina mozete provjeriti ako pomocu ILDasm dissasemblera dissasemblirate kod naseg programa. Da smo npr. iz C# koda eksplicitno pozvali Invoke, C# kompajler bi generirao ovu gresku: "error CS1533: Invoke cannot be called directly on a delegate". Dakle, C# kompajler ne dopusta da eksplicitno pozovemo Invoke; medjutim, drugi kompajleri bi mogli dopustiti da u svrhu pozivanja nase callback metode pozovemo Invoke. Zapravo, Visual Basic.NET upravo trazi od nas da eksplicitno pozovemo Invoke.

Prisjetimo se, na kraju da je kompajler definirao metodu Invoke onda kad je definirao klasu CallBackFn. Kad se pozove Invoke, ta metoda koristi privatne fieldove _target i _methodPtr u svrhu pozivanja zeljene callback metode nad zeljenim objektom. Primjetimo da je signatura metode Invoke ista kao i signatura koju smo upotrijebili pri definiranju delegata; tj. zbog toga sto je delegat CallBackFn uzimao tri parametra i vracao vrijednost void, zato i metoda Invoke uzima ista tri parametra i isto vraca vrijednost void.

Nadam se da sam sada malo rasvijetlila interni rad delegata (iako ne jos sasvim, jer ostaju jos i pozivanje vise callback funkcija zaredom, kao i asinkroni delegati, itd... ali, sve u svoje vrijeme!) Nego, ima li uopce nekoga tko ovo cita i razumije. Nadam se da ima... javite se stoga i recite svoje misljenje o ovoj mojoj maloj "kolumni".

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