É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áltpublic 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:
Megjegyzés küldése