2007-12-21

C# delegate-ek


Év vége előtti vincseszter-gyomtalanításnál találtam egy fájlot, amibe a C# delegate-jairól véstem fel pár gondolatot valamikor. Egyébként kicsit bánom hogy nem mentem a Javapolisra. Legközelebb -főleg ha ilyen lehetőség lesz- az asztalra csapok, félreteszem az aktuális (sz*rból kihúzandó) projektet és megcsinálom amit akarok.



A delegate a C-ben és C++-ban megszokott függvénypointerekre hasonlít legjobban.
Magának a szónak a jelentése: kiküldött követ, meghatalmazott, képviselő.



A delegate egy olyan adatstruktúra ami egy vagy több metódusra mutat, példánymetódusok esetében egyben az azokhoz tartozó objektumokra is. Mondhatnánk azt is, hogy a delegate, mint deklaráció egy metódus szignatúráját írja le. Azokkal a metódusokkal kompatibilis, amelyeknek a paraméterlistája és a visszatérési értéke megegyezik a delegate paraméterlistájával és visszatérési értékével.


Delegate-t így kell deklarálni:


delegate int op(int a, int b);


Ha osztályon kívül deklaráljuk akkor a namespace-n belül lehet elérni, de ha osztályon belül deklaráljuk (static kulcszó nem megengedett), akkor az osztály nevén keresztül kell hivatkozni rá. Ilyenkor tehát a delegate nem a példányhoz hanem az osztályhoz tartozik.


A delegate a System.Delegate osztályból származik, viszont ezt az öröklődést csak a rendszer és a compiler-ek használják, mert maga az osztály sealed, tehát a programozáskor mi csak a delegate kulcsszóval találkozunk.


Delegate példányosítása a következőképpen történik:


op x = new op(add);


Ahol az add metódusnak kompatibilisnak kell lennie a delegate-tel, különben fordítási hiba jelentkezik.


Egy delegate-hez újabb metódusokat is hozzá lehet adni:


x += new op(sub);

x = new op(add) + new op(sub);


stb. Ha új értéket rendelünk a delegate-hez, a régi elveszik, mint a referenciáknál megszokott.



A delegate meghívása egyszerűen így történik:


x(1, 2);


Ilyenkor az összes metódus végrehajtódik szépen sorban szinkronban ugyanazokkal a paraméterekkel. Ha egyik metódus változtat valamit, azt a változást a sorban utána következo metódusok mind látni fogják. A legutolsó visszatérési érték fogja képezni a delegate visszatérési értékét. Ha exception következik be a hívások során és az nincs elkapva a híváslistában meghívott metódusban, akkor ez az exception a delegate-et hívó kódban fog jelentkezni, mintha a delegate hívása során keletkezett volna. Az exception keletkezése után a híváslistában szereplő többi metódus nem kerül meghívásra.



A delegate-bol el is lehet venni metódusokat a - és a -= operátorral. Ha olyan metódust próbálunk elvenni belőle ami előzőleg nem volt benne, nem történik változás.


Delegate-ek összehasonlítása



A delegate-ekre alkalmazható az egyenlőséget ill. az egyenlőtlenséget vizsgáló összehasonlító operátor (==,!=). Két delegate példány egyenlőnek tekinthető, ha mindkettő értéke null, vagy egyenlő híváslistával bírnak, tehát a híváslistákban szereplő metódusok ugyanazokra a statikus metódusokra és ugyanazon objektumpéldányok ugyanazon metódusaira mutatnak ugyanabban a sorrendben a két delegate esetében. A delegate-ek típusa nem számít, tehát két különböző típusú delegate (amik persze ugyanazt a szignatúrát képviselik) lehet egyenlő.


Event-ek



A delegate int op(int a, int b) tehát egy delegate-nek a deklarációja és már látható volt, hogyan lehet lokális változóként létrehozni. (Miért van külön event, ezt nem értem...)


public event Click EventHandler;


Így néz ki tehát egy event deklarációja. Az event kulcsszó, aztán az esemény neve (érdemes valami igeként elnevezni) aztán a típusa. Így egy delegate típus könnyen hozzárendelhető többféle eseményhez.


A delegate-ek és event-ek kiválóan használhatóak eseménykezelésre, ahogy ez meg is van valósítva a System.EventHandler esetében. Egy gombhoz például a következőképpen rendelhetünk egy eseménykezelőt:


button.Click += new EventHandler(this.buttonClick);


Ahol a Click egy event amihez EventHandler típusú metódusokat lehet kapcsolni. Az osztályon belül így történik az eseménykezelő hívása:


Click(this, null);


A megfelelő paraméterekkel természetesen.


Java vs. .net



A java 1.4-gyel szemben gyakorlatilag beépítették a nyelvbe az eseménykezelést. A jávás megoldásnak több hátránya is van a C#-hoz képest:


  • Meg kell írni vagy örökölni kell valahonnan a listenereket kezelő metódusokat. (add, remove, notify) Az örökléssel az a probléma hogy kizárja a máshonnan való öröklést, a logika megírása pedig a plusz hibalehetőségekkel járó ujjgyakorlat. Elvileg lehet írni egyedi implementációkat (pl. Chain of Responsibility), de az alkalmazott listener minta nem egy olyan dolog amit túlzottan meg lehetne fűszerezni.

  • C#-ban meg lehet csinálni, hogy az eseménykezelő metódusok nem ugyanabban az osztályban foglalnak helyet. A java-ban a Listener interfészek csokorba gyűjtik a metódusokat, amiket egy adott osztálynak implementálnia kell. (Billentyűeseményeknél lehet hogy csak a leütésre akarok figyelni, de az alkalmazandó interfész miatt a felengedésre is meg kell csinálnom az üres metódust.) Erre be is vezették az Adapter-osztályokat, amikben eleve benne van az összes metódus üres törzzsel, de ezek szintén öröklődést igényelnek.

  • Kissé antipattern az a megoldás, hogy egy meglévő -valamilyen célra már felhasznált osztályt- hirtelen kiegészítünk valami Listener logikával, mert éppen kell. Pl. public class MyForm extends JPanel implements ActionListener. Így az osztályban definiált public void actionPerformed(ActionEvent) metódust boldog boldogtalan hívogathatja. C#-ban egyszerűen lehet csinálni olyan eseménykezelő metódusokat amelyek az adott osztályon kívülről nem hozzáférhetőek. Ehhez jávában befoglalt osztályok kellenek, amik persze hosszabb távon egész jól megszokhatóak.

  • Már meglévő osztályok metódusait is fel lehet használni, anélkül hogy bele kellene nyúlnunk a kódjukba és implementálnunk kellene mégegy interfészt. Ezt java-ban wrapper osztállyal lehet megvalósítani, ami delegálja a metódushívást.


A fentiek ellenére nem hiányolom a delegate-eket a java-ból, főleg a hozzáadásos, összehasonlításos dolgait. Gyakorlatilag kis kerülőúttal mindent meg lehet oldani delegate-ek nélkül, ezenkívül több kérdést felvet, mint amennyit megold.



Mire jó még?



Kb. ugyanarra mint a C-s, függvénypointerek, tehát callback függvények megjelölésére, függvénytáblák létrehozására, stb. Egy példa függvénytábla létrehozására és használatára:


public op[] operations = new operations[10];

...

op[0] = new op(add);

...

operations[0](2, 3);

Nincsenek megjegyzések: