[Ez a cikk egy egyetemi beadandóm kapcsán készült. Kicsit hosszúra sikeredett (15 oldalra), emiatt akit érdekel az alábbi címről letöltheti pdf formátumban is: innen. Mellesleg az íromány az alábbi két Jeffrey Richter által írt angol nyelvű értekezés magyar változata: 1, 2. Némileg kibővítve, némileg megcsonkítva 😀]

A mai korszerű programozási nyelvekhez tartozó futtatókörnyezetek többsége tartalmaz valamilyen automatikus szemétgyűjtési mechanizmust a memóriakezelés leegyszerűsítése végett. Ebben a cikkben a C# programozási nyelvhez tartozó .NET CLR futtatókörnyezetben található szemétgyűjtőt (angolul: gargabe collector, vagy röviden csak gc) fogom bemutatni a C++-os memóriakezelési megoldáshoz viszonyítva.

A szemétgyűjtés okai

Arra a kérdésre, hogy miért van szükségünk automatikus szemétgyűjtésre, a válasz egyszerű. Mert könnyebbé teszi a programozó életét, hiszen így jobban tud koncentrálni a tényleges feladatára, ha nem kell külön törődnie a memória kezelésével is. Pontosan ez a lényeg, ugyanis a memóriakezelés kapcsán két olyan tipikus hibát követhetünk le, melyek sokkal alattomosabbak, mint bármilyen más alkalmazásbeli bug, ugyanis ezek hatására a program működése nem lesz megjósolható, vagyis nemdeterminisztikussá válik. Mielőtt még továbbmennénk, nézzük is meg, mi ez a két hibalehetőség:

– Az egyik a memóriaszivárgás (memory leak) esete, amikor a programozó elfelejti felszabadítani az általa lefoglalt tárterületet. Ekkor a memóriában az ilyen objektumok csak foglalják a helyet, de senki se használja őket semmire. Ez ahhoz vezethet, hogy elfogy a szabad memória, ami miatt az alkalmazás elszáll OutOfMemomeryException-nel.

– A másik lehetőség, amikor egy már felszabadított tárterületre hivatkozik a program. Vagyis azon a mutatón keresztül, amelyet az allokációkor kapott, olyan memóriaterületet ér el, mely már nem az övé, és itt bármilyen adat lehet, aminek a következtében kiszámíthatatlanná válik az alkalmazás futása.

Ez két olyan hiba, melyet általában rendkívül nehéz fülön csípni, és az esetek nagy részében a javításuk se feltétlenül triviális feladat. Az automatikus szemétgyűjtés eme két hiba bekövetkezésének valószínűségét hivatott nullára lecsökkenteni.

Egy objektum élete, memóriakezelés szempontjából

Egy erőforrást használó objektum életciklusa során az alábbi 5 tevékenység valamelyikét végezheti:

1) Helyet foglal a szükséges erőforrás számára
2) Inicializálja az erőforrást a kezdeti állapotnak megfelelően, és előkészíti a használatra
3) Eléri és használja az erőforrást az osztálypéldány megfelelő tagján keresztül
4) „Feltakarít” maga utána
5) Felszabadítja a lefoglalt tárterületet

Memóriakezelés szempontjából minket most csak az első, a negyedik és az ötödik tevékenységek érdekelnek. Kezdjük először a két szélsővel, ugyanis ezeket a lépéseket képes automatikusan elvégezni a számunkra a GC! (A negyedik lépeshez a GC-nek ismernie kéne az erőforrás típusát és működését, de mivel ez nem várható el, ezért ez a feladat továbbra is a programozó feladata marad, de erről majd picit később).

C++ esetén, ha a memóriában helyet szeretnénk allokálni egy adott objektumnak, akkor azt úgy tesszük meg, hogy végigmegyünk egy láncolt listán, mely a memória szabad területeit reprezentálja, és az első méretben megfelelő szabad helyet lefoglalja. Ez így szép és jó, viszont ennek a műveletigénye a lineáris keresés műveletigényével, O(n)-nel egyenértékű. A GC ehhez képest O(1) műveletigénnyel képes nyújtani ugyanezt a szolgáltatást. Vajon miként lehetséges ez?

A memória allokáció egy olcsó művelet .NET környezetben

A GC esetén a memóriában a helyfoglalás azért lehet ilyen rendkívül olcsó, mert mindösszesen egy mutató értékének növeléséről van szó. Ez a mutató arra a tárterületre hivatkozik, amelyet a következő allokáció során az újonnan létrehozandó objektum fog majd megkapni, így nincs szükség semmilyen keresésre a helyfoglalás során. Hívjuk ezt a pointert NextObjPtr-nek. A legegyszerűbben ezt úgy tudjuk elképzelni, ha a memóriára, mint egy veremre gondolunk, és a verem legfelső elemére van egy referenciánk. (A továbbiakban erre a „veremre”, mint managed heap-re fogunk hivatkozni.)
Nézzünk egy egyszerű példát. Tegyük fel, hogy van A, B és C objektumunk. Először, amikor az A objektum számára akarunk helyet allokálni, akkor a NextObjPtr a verem legaljára mutat. Helyfoglalást követően ez az érték pontosan annyival nő meg, mint amekkora helyre szüksége volt az A objektumnak. A B és a C objektum helyfoglalása után valahogy így nézhet ki a memória.

Menedzselt Heap

Ahhoz, hogy mindez jól és hatékonyan működhessen, két eléggé erős feltételnek kell teljesülnie: A címtér és a tárhely legyen végtelen. Nos, ezeket a feltételek eléggé nehezen lehet biztosítani, emiatt van szükség néha a memória kitakarítására. Ezt a mechanizmust hívjuk szemétgyűjtésnek.

Hogyan történik a szemét összegyűjtése?

A szemétgyűjtés menete röviden az alábbi: Ha a managed heap megtelt, akkor a szemétgyűjtő összegyűjti azokat az objektumokat, melyekre már nincs szüksége az alkalmazásnak és felszabadítja az általuk foglalt helyet, így ismét lesz szabad tárterület. Ez mind szép és jó, de honnan tudja a GC, hogy mire van szüksége egy alkalmazásnak és mire nincs? Onnan, hogy úgynevezett erős referenciákat, vagyis röviden root-okat használ. Ezek azok a pointerek, melyek például globális vagy statikus objektumokra hivatkoznak, vagy egy szál stack-jének lokális változói, vagy akár a CPU regiszterében lévő mutatók, tehát az alkalmazás élő komponensei. A root-ok hivatkozhatnak egy managed heap-beli tárterültre, vagy akár lehet az értékük null is.

Azok az objektumok, melyre van ilyen root hivatkozás, azok „aktív” objektumok, vagyis az alkalmazás számára elérhetőek. Az összes többi nem elérhető, vagyis szemét. Íme, egy ábra, mely erre mutat egy példát.

Allokált objektumok és ROOT-ok

Maga a GC algoritmus ezek alapján a következőképpen működik. Ha megtelt a managed heap, akkor a GC elkezdi összegyűjteni a szemetet. Abból a feltevésből indul ki, hogy minden, ami a memóriában van, az szemét. Viszont, ha talál rá root hivatkozást, akkor azt elérhetőnek minősíti, és nem fogja törölni. A fenti ábrán jól látszik, hogy nem csak az A, a C, a D, és az F objektumok érhetőek el, hanem a H is. (Indirekt módon a  D-n keresztül.) Azért, hogy a GC ne töröljön olyan objektumokat, melyek, ha direktbe nem is, de elérhetőek, ezért rekurzívan végignézi az összes root-ból elérhető objektumot. A rekurzív bejárás során a GC algoritmus felépít egy elérhetőségi gráfot, és azokat az objektumokat fogja majd csak törölni, amelyeket nem lehet elérni, vagyis amelyek nem szerepelnek a gráfban. A vizsgálat során, ha már egy adott objektum szerepel a gráfban, akkor azt már nem fogja továbbvizsgálni, egyrészt teljesítmény szempontból, másrészt a végtelen körhivatkozások elkerülése érdekében.

A fentebbi ábrán látható példa a GC lefutása után az alábbi módon néz ki.

GC lefutása után

Vagyis a szemétgyűjtés olyan módon valósul meg, hogy a GC végigmegy az összes a root-okból elérhető objektumokon rekurzívan, közben felépít egy gráfot, és azokat az objektumokat, melyek nem szerepelnek a gráfban, törli. A törlés után a managed heap-et ismét konzisztens állapotba hozza, vagyis memcpy-val szépen átmásolgatja az elemeket a felszabadult üres helyeknek megfelelően. Természetesen az átrendezést követően a GC feladatai közé tartozik még a pointerek értekeinek fixálása is az új helyezet alapján.

Ami a fentebbi leírásból egyből kitűnik, hogy egy GC lefutás nem egy olcsó művelet, viszont szerencsére csak akkor fut le, ha megtelt a managed heap. Köztes állapotban ellenben sokkal gyorsabb megoldást nyújt, mint a C++-os megvalósítás. Ami még szintén látszik az algoritmusból az az, hogy a fentebb említett két probléma, itt már nem fordulhat elő. Hiszen ha már nincs az adott objektumra referencia, akkor biztosan fel lesz szabadítva. A másik irány is igaz, vagyis ameddig van rá hivatkozás, addig biztosan nem kerül felszámolás alá. A nagy kérdés már csak az, hogy ha a GC ennyire jó, akkor az ANSI C++ miért nem ezt használja? A kérdésre a válasz a kasztolásban rejlik, ugyanis C++-ban a pointer által mutatott objektumok átkasztolhatóak egyik típusról a másikra, viszont így a rendszer nem tudná kideríteni, hogy a pointer mire is mutat.

Ideje feltakarítani magunk után

Most hogy már tudjuk, miként kezeli a GC az egyes (allokáció) és az ötös (felszabadítás) tevékenységét egy adott objektumnak, nézzük meg a négyes (feltakarítás) lépést is. Ebben az állapotában az objektumnak az általa használt erőforrást fel kell szabadítania, oly módon, hogy visszaállítja eredeti állapotába (pl.: fájl esetén bezárja a kommunikációs csatornát és leveszi róla az írási zárolást). Ezt a GC nem tudja automatikusan megcsinálni, ezért a fejlesztő kap egy olyan nyelvi lehetőséget, amelyen keresztül ezt a lépést megvalósíthatja. Ezt hívják Finalization-nek. Ez egy olyan függvény, melyet akkor hív meg a GC az adott objektumon, miután már szemétnek minősítette, de még mielőtt törölné. C++-os fejlesztők egyből analógiába hoznák ezt a destruktorral, de a valóságban eléggé nagy az eltérés a kettő között (részletesebben kicsit később).

Tehát kapunk a rendszertől egy Finalize elnevezésű metódust, mely segítségével feltakaríthatunk magunk után. Viszont ügyelnünk kell arra, hogy mikor használjuk ezt. Ökölszabályként elmondható, hogy szinte soha, próbáljuk meg kerülni, mert eléggé sok hátrányunk származik belőle. Íme, a teljesség igénye nélkül néhány példa:

– A Finalize metódussal rendelkező objektumok két körben kerülnek felszabadításra. (lásd később)

– Már maga az objektum allokálása is tovább tart, ugyanis speciálisan kell kezelni az objektumot, már az elejétől kezdve. (szintén lásd később)

– Hivatkozhat olyan objektumokra, melyek nem tartalmaznak Finalize-t, de emiatt mégis sokáig élnek.

– Több száz példány esetén már jelentős teljesítménycsökkentést okoz, ha egy osztály példányain egyszerre meg kell hívni a Finalize-t.

– Nincs kontrollunk afölött, hogy pontosan mikor is hívódik meg.

– Ha a program valami miatt terminál, akkor nem fut le a Finalize metódus, kivéve, ha ki nem kényszerítjük belőle (RequestFinalizeOnShutdown).

– Nem garantált a meghívásuk sorrendje, vagyis elképzelhető, hogy előbb a gyerek objektum(ok) Finalize-ja hívódik meg és csak utána a szülőé, ami probléma lehet, ha a szülőben hivatkozunk a gyerek objektum(ok)ra.

Ha ezek ellenére is szükségesnek tartjuk a Finalize metódus megírását, akkor törekedjünk arra, hogy minél gyorsabb és rövidebb legyen. Illetve ügyeljünk arra is, hogy ne tartalmazzon szál szinkronizációt és ne dobjon kivételt!

Visszatérve a Finalize != destruktor problémakörhöz, vizsgáljuk meg kicsit közelebbről a problémát. C++ és C# esetén is a fordító képes arra, hogy a származtatott osztály konstruktorába beleszerkessze az ősosztály konstruktorának hívását. C++ esetén ez a destruktorra is igaz, hiszen ott a sorrendiség garantált.  Ellenben C# esetén, ahol nincs destruktor, csak Finalize, a fordító számára nem egyértelmű, hogy van-e az adott osztálynak, meg kell-e hívnia, stb. Emiatt az ősosztály Finalize metódusának a meghívása a programozó felelőssége. Ilyenkor ügyeljünk arra, hogy az ősosztály Finalize-ának hívása kerüljön a metódus legvégére.

Érdekesség: C# esetén van arra lehetőség, hogy szintaktikailag „destruktort” írjunk (vagyis lehet írni ~Object() metódust), de ez a fordítás során az ősosztály Finalize-nak felüldefiniálására fordul le (protected override void Finalize()). Tehát Finalize != destruktor.

Feltakarítás két lépésben

Azon objektumokat, melyek rendelkeznek Finalize metódussal, már akkor meg kell különböztetni a többitől, amikor lefut a new operátoruk. Ezért a rendszer két segédtáblát használ arra, hogy ezt az információt nyilvántartsa. Az egyik a finalization queue névre hallgat. Ebbe a sorba bekerülnek azokra az objektumokra mutató referenciák, melyek rendelkeznek Finalize metódussal, mint ahogyan azt az alábbi ábra is mutatja.

Menedzselt Heap felépítése a két sorral kiegészítve

Amikor lefut a szemétgyűjtő, akkor megnézni, hogy a szemétnek ítélt objektumok közül, melyekhez tartozik finalization queue-beli bejegyzés. Amelyikhez van, azt innen törli és átteszi a másik segédtáblába, az freachable queue-ba. Tehát itt azok az objektumok vannak, amelyeknek van Finalize metódusa és szemétnek lettek minősítve. A fenti ábrán ezek az E, I és a J. (A B, G és a H objektumok ilyenkor már nincsenek benne a managed heap-ben). Ezt az állapotot szemlélteti az alábbi ábra.

GC lefutása után

Az freachable sor nevének az elején az f a finalizable-re utal, vagyis, hogy van Finalize metódusa az adott objektumnak, a reachable pedig arra, hogy újra van az adott elemre referencia, vagyis újból elérhető (van rá root), emiatt a GC nem törölheti. Azért kell újból elérhetővé tenni, hogy le lehessen futtatni a feltakarítást. Ezt egy külön szál végzi, ami mindaddig alszik, amíg üres a sor. (Emiatt a Finalize-ban kerüljük a szálkezelést!) Amint került a sorba új elem, végigmegy az összes bejegyzésen és szépen meghívogatja mindegyiken a Finalize-t, majd ezután törli a sorból a bejegyzést. Vagyis az objektum ténylegesen majd csak a következő GC lefutásakor fog felszámolódni, addig továbbra is ott lesz a managed heap-en.

Tehát a GC első lefutásakor az adott objektumra kapunk egy root-ot azáltal, hogy átpakoljuk a freachable sorba, így emiatt már nem lesz szemét, majd végrehajtatjuk egy külön szálon a Finalze metódust, töröljük a sorból, így elveszik az elérhetősége, emiatt a következő körben már az objektum szemétnek minősül. Az előző ábra az első GC lefutása utáni állapotot mutatja, az alábbi a második utánit.

GC 2. lefutása után

Amikor a holtak életre kelnek

Ha jobban belegondolunk, hogy mi is történik valójában a Finalize metódus kezeléskor, akkor azt láthatjuk, hogy van egy objektum, ami meghal, majd a Finalize miatt újra él, majd megint (de most már végleg) meghal. Ezt a folyamatot hívjuk feltámadásnak (resurrection-nek).

A második meghalás, azért következik be, mivel a Finalize lefutása után már nincs erős hivatkozásunk az objektumra. De mi van akkor, ha a metóduson belül értékül adjuk az adott objektumot egy globális vagy statikus változónak? Nos, újra élni fog. 🙂 Ami több szempontból sem szerencsés. Hiszen egyrészt már feltakarított maga után, másrészt az általa hivatkozott más objektumok is már halottak lehetnek. Jön a zseniális ötlet: élesszük őket is újra. És mindjárt ott is tartunk, hogy van egy teljes zombi hadseregünk. A zombi itt nagyon találó név, hiszen él, mivel van rá root, de halott, mivel már lefutott a finalize. Így, egy olyan rendszert kapunk, amelynek viselkedése teljesen megjósolhatatlan.

Ha valamilyen okból kifolyólag mégis szükségünk lenne a holtak feltámasztására, akkor azt csináljuk okosan. Egy flag segítségével tároljuk el, hogy egyszer már halálra lett ítélve szerencsétlen, és ez alapján a flag alapján cselekedjünk az objektum metódusain belül.

Sőt még tovább megyek, ha újból feleslegessé válik az objektum, jó lenne, ha ismét lefutna a Finalize metódus. Viszont az objektum nem lett újra allokálva, így nem került bele a finalization queue-ba, vagyis nem fog meghívódni alapból. Ezen úgy tudunk segíteni, hogy meghívjuk a GC ReRegisterForFinalize() metódusát. Ilyenkor egy új bejegyzés kerül be a sorba. Ennek köszönhetően pedig sikerült eljutni a hallhatatlansághoz, hiszen ahányszor meghalunk, annyiszor újra is tudjuk élesztetni magunkat, így megtaláltuk az örök élet elixírét. 🙂 Viccet félretéve, van lehetőség újraélesztésre, de csak óvatosan használjuk! (A ReRegisterForFinalize-t csak egyszer hívjuk meg, ha kell, mert különben annyiszor kerül be egy új bejegyzés a sorba, ahányszor meghívjuk a függvényt, vagyis annyiszor fog lefutni a Finalize metódus.)

Még élőként rendezzük a végrendeletünket

Az előző pár bekezdésben láthattuk a Finalize előnyeit és hátrányait, most nézzünk meg egy segédtechnikát, mely segítségével ki lehet küszöbölni a Finalize hibáinak egy részét. A technika lényege, hogy a feltakarítást ne bízzuk a GC-re, hanem mi magunk végezzük el explicite a programból. Ehhez arra van szükségünk, hogy létrehozzunk egy Close, vagy egy Dispose metódust. Előbbre akkor van szükségünk, ha feltakarítást követően még használható az objektum >> tartozik hozzá Open is. Utóbbit pedig akkor, ha már nincs tovább szükségünk az adott objektumra.

Nézzünk egy egyszerű példát ennek a használatára. Tegyük fel, hogy van egy FileStream objektumunk, amellyel egy fájlba akarunk írni. A jobb teljesítmény érdekében az implementáció soron puffereket használ az objektum, viszont ezek tartalma csak akkor kerül ki ténylegesen az adott fájlba, ha a pufferek megteltek. Ha ezek flush műveletét a Finalize-ban hívnánk meg, akkor mindaddig zárolva lenne a fájl, ameddig fel nem szabadítja a GC a tárhelyet, amire várhatnánk egy ideig. Ezért a FileStream objektumnak van Close metódusa is. Ilyenkor felmerülhet bennünk a következő kérdés: ha meg lett hívva explicite a Close, akkor is meg fog-e hívódni a Finalize, és ha igen, akkor mit fog csinálni?

A válasz az, hogy nem, nem fog meghívódni. Egy flag a háttérben beállítódik a Close meghíváskor, így a Finalize-ban lévő kódot ki is lehet hagyni. Ettől még a Finalize továbbra is meghívódna, hiszen benne van a finalization queue-ban, teljesen feleslegesen. Ennek elkerülése érdekében a GC-nek van egy olyan metódusa, mellyel ki tudjuk innen venni (pontosabban kihagyathatjuk azt a lépést, hogy áttegye az freachable sorba). Ez pedig nem más, mint a SuppressFinalize() metódus.

Természetesen az előző példát még tovább lehet egy kicsit bonyolítani azzal, hogy egy olyan StreamWriter-t használunk, mely egy FileStream-et alkalmaz. Mindkét objektum puffert használ, így a StreamWriter-nél is szükséges a flush meghívása. Ha tehát a StreamWriter Close metódusát meghívjuk explicite, akkor az meghívja a FileStream-ét is, így azt már nem kell külön. Ez így jól is működik, de mi van, ha lemaradt a Close meghívása? Nos, ez esetben jön a GC és a Finalize metódusok. DE, van egy kis bibi. A GC nem garantálja a sorrendet, vagyis előfordulhat, hogy előbb hajtja végre a FileStream Finalize-n belül a puffer flush metódusát és majd csak aztán a StreamWriter Finalize-jának flush metódusát, ami nem túl jó nekünk, hiszen így akár fontos adatok is elveszhetnek.

Erre a Microsoft megoldása a következő: A StreamWriter-nek nincs Finalize metódusa, mivel nem garantálható a sorrend. Viszont van Close metódusa, mely explicit meghívása esetén a rendszer jól működik, meg nem hívása esetén pedig adatvesztés esélye állhat fenn, amelyet a programozó észrevesz, így a probléma feloldható.

Ez még hátha jó lesz valamire

A feltakarítás hátulütőinek tisztázása után most nézzük meg azt, hogy miként lehet egy objektumot „kómába helyezni”. A dolog lényege az, hogy van egy objektumunk, mely jelen pillanatban bevégezte a dolgát, de lehet, hogy a későbbiekben még szükségünk lenne rá, csak nem tudjuk, hogy pontosan mikor, vagy, hogy egyáltalán kell-e még. Tegyük fel továbbá azt is, hogy ezen objektum létrehozása drága memória szempontjából. Emiatt nem szeretnénk még egyszer újrainicializálni, de ha sokáig nem kell, akkor akár törlődhet is. Ehhez az úgynevezett gyenge referenciákra van szükségünk (WeakReference, vagy röviden wr).

Vagyis, ha másfelől közelítjük meg a problémát, azt mondhatjuk, hogy a wr-k segítségével olyan objektumokra adhatunk referenciát, melyek el is érhetőek, és törölhetőek is. Ez mégis hogyan lehetséges? A válasz az időzítésben rejlik. Ha még a GC lefutása előtt a wr-en keresztül létre tudunk hozni az objektumra egy erős referenciát (Strong Reference, vagy röviden sr = root), akkor az objektum elérhető. Ha a GC lefutása után szeretnénk elérni az objektumot, akkor nem fog sikerülni, ugyanis a GC már felszabadította az adott tárterületet.

Nézzük ezt meg egy egyszerű példán keresztül. Tegyük fel, hogy van egy alkalmazásunk, mely két nagy egységből áll, melyek között a felhasználó bármikor átválthat. Az alkalmazás egyik felének el kell érnie a fájlrendszerbeli mappákat. Tegyük tovább fel azt, hogy a mappákról készített gráfot eltároljuk a memóriában a jobb teljesítmény érdekében. Ilyenkor van egy root-unk erre az objektumra. Ha a felhasználó átvált az alkalmazás másik felére, ahol nincs szükségünk a mappaszerkezetre, akkor készíthetünk erről az objektumról egy wr-t. Így, ha a felhasználó nem tér vissza egy újabb GC lefutása előtt, akkor az erőforrás felszabadítható, hiszen csak wr-ünk van az objektumra, sr-ünk nincs. Ebben az esetben sajnos újra fel kell építeni a teljes gráfot, ha a felhasználó visszatér az alkalmazás egyik feléhez. Ellenben, ha a felhasználó még a GC lefutása előtt tér vissza, akkor a wr-n keresztül el tudja úgy ismét érni az objektumot, hogy létrehoz rá egy erős referenciát.

Íme, a gyenge referenciák használatának a pszeudó-kódja:

LargeObject lo = new LargeObject(); //van egy sr-ünk az objektumra
//dolgozunk vele, majd átváltunk az alkalmazás másik felére
WeakReference wr = new WeakReference(lo); //van egy sr-ünk és egy wr-ünk is
lo = null; //már csak egy wr-ünk van az objektumra
// dolgozunk az alkalmazás másik felében, majd visszatérünk
lo = wr.Target;
if(lo == null)
lo = new LargeObject(); //későn értünk vissza, lefutott a GC

A fenti példakód két érdekes részt tartalmaz: az egyik a wr létrehozása, a másik pedig az sr létrehozása wr-ből. Nézzük először az elsőt. Amikor egy gyenge referenciát szeretnénk létrehozni egy adott objektumra, akkor azt úgy tudjuk megtenni, hogy a beépített WeakReference típusból létrehozunk egy új példányt és átadjuk a konstruktornak paraméterként az objektumra mutató referenciát. Ezek után pedig töröljük az erős referenciát, hiszen ha ez továbbra is megmaradna, akkor az egésznek nem lenne semmi értelme.

A gyenge referenciáknak két fajtája van, a rövid és a hosszú életű. A rövid gyenge referenciák (short weak reference) olyanok, hogy nem figyelik, hogy újra lettek-e élesztve. Míg a hosszú életű gyenge referenciákat (long weak reference) igen. Ez utóbbit úgy tudjuk létrehozni, hogy a konstruktor egy másik túlterhelt változatát használjuk, amelyik vár egy bool paramétert is. Ha long wr-t akarunk, akkor ezt állítsuk be true-ra. Mellesleg megjegyezném, hogy kerüljük a long wr-eket, mert az általuk mutatott objektumok feltámadása után az objektumok kiszámíthatatlan viselkedést produkálnak!

A gyenge referenciából erős referencia készítés oly módon valósul meg, hogy a wr Target tulajdonságán keresztül elérhető objektumot értékül adjuk egy root-nak, vagy egy abból elérhető referenciának. Ha ez az érték null, akkor elkéstünk, lefutott a GC, nincs mit tenni, újra létre kell hozni az objektumot. Viszont, ha az értéke nem null, akkor minden további nélkül tovább tudjuk használni.

A gyenge referenciákkal kapcsolatban felmerülhet bennünk egy érdekes kérdés. Hogyan tudunk úgy létrehozni gyenge referenciát, hogy közben nem hozunk létre egyben erős referenciát is? Másképp megfogalmazva a kérdést, ha az objektum csak a WeakReference-n keresztül elérhető, akkor tulajdonképpen elérhető, vagy mégsem?

A gyenge referenciák misztériuma

Hogy tudunk úgy létrehozni egy objektumra referenciát, hogy az valójában ne legyen referencia? A válasz nem is annyira egyszerű. Amikor a WeakReference beépített típus konstruktorának átpasszoljuk az objektumra mutató pointert, akkor valójában nem történik a managed heap-en allokáció. De ha nincs allokáció, nem jön létre egy új objektum, mely hivatkozna rá, akkor mégis, hogy tudjuk őt elérni a későbbiekben? Nos, egy segédtábla segítségével. Pontosabban kettővel, ugyanis, amikor létrehozunk egy WeakReference-t, akkor egy bejegyzés bekerül a short weak reference táblába vagy a long weak reference táblába a referencia típusától függően.

Ezekben a táblákban a managed heap-en lévő memóriacímre vannak hivatkozások, de ezek nem tekintendőek root-oknak! Ezek alapján tekintsük meg a GC algoritmus gyenge referenciák kezelésével kibővített változatát:

1) A GC felépíti az elérhetőségi gráfot a managed heap alapján

2) Megnézi a short weak reference táblában, van-e olyan hivatkozás, mely szemétre mutat. Ha van, akkor ennek a bejegyzésnek az értékét null-ra állítja.

3) Megnézi a finalization sort, hogy van-e olyan hivatkozás benne, mely szemétre mutat. Ha igen, akkor áthelyezi a mutatót a freachable sorba, és hozzáadja az objektumot a gráfhoz, így újból elérhetővé válik az.

4) Megnézi a long weak reference táblát van-e benne olyan hivatkozás, mely olyan objektumra mutat, mely nincs a gráfban. (Ilyenkor már a gráf részét képezik az freachable objektumai is!). Ha talál ilyet, akkor ennek az értékét null-ra állítja.

5) Törli a szemetet és eltünteti a lyukakat, illetve frissíti a referenciákat.

A short és a long wr-k közötti igazi különbség a következő. Short wr esetén, ha az objektum szemétnek lett minősítve, akkor egyből ezután törlődik is a short weak reference táblából róla a bejegyzés. Viszont, ha ennek az objektumnak van Finalize-ja, ami még nem futott le, és el szeretnénk érni az objektumot, akkor azt már nem tudja, hiszen már nincsen rá mutató, pedig még mindig ott van a managed heap-en. Ezzel szemben a long wr-nál csak akkor törlődik a bejegyzés a long weak reference táblából, ha az objektum tárhelye fel lett szabadítva, vagyis, ha újra is élesztik, akkor is megmarad rá a hivatkozás.

Különböző generációk, különböző szokások

A cikk hátralévő részében néhány GC optimalizálási technikát fogok bemutatni. Ezek közül az egyik legfontosabb a generációk kezelése. Amikor több különböző generációval dolgozunk egyszerre, akkor az alábbi négy feltevés mindegyike igaz kell, hogy legyen:

1) Az új objektumok, rövid ideig fognak élni

2) A régi objektumok, sokáig fognak élni

3) Az új objektumok között erős kapocs van, és elérésük gyakran azonos időben történik

4) A heap egy adott részének a kezelése gyorsabb, mint a teljes heap menedzselése

Tanulmányok állítják, hogy ezek az állítások megállják a helyüket a manapság használt szoftverek nagy részénél. Nézzük, ezek a feltevések hogyan segítik a GC-t a teljesítményének a javításában.
Kezdetben egy üres managed heap-pel indulunk. Létrehozunk objektumot, majd megtelik a memóriánk. Az ilyenkor a memóriában lévő objektumok a 0. generáció részét képezik. Ezek azok az új objektumok, melyeket a GC még nem vizsgált meg, mint ahogyan az az alábbi ábrán is látható.

0. Generáció

A rózsaszínnel jelölt objektumokat a GC szemétnek minősítette, ezért felszabadítja az általuk lefoglalt helyet. A megmaradt objektumokat ezután 1. generációsaknak nevezzük, és ezek feltehetően nem is nagyon fognak változni. A GC lefutása után érkező újonnan allokált objektumok fogják képezni az új 0. generációt, mint ahogyan az alábbi ábrán is látszik. A GC az esetek többségében csak a 0. generációs objektumokat vizsgálja.

0. + 1. Generáció

Az M törlése után a 1. generációból 2. generáció lesz, a 0. generációból pedig 1. (A legmagasabb generációs szint a 2.). Az újonnan érkező objektumok lesznek az új 0. generáció, melyet a heap betelésekor a GC-nek meg kell vizsgálnia. Ezt szemlélteti az alábbi ábra.

0. + 1. + 2. Generáció

Tehát összefoglalva a generációk kezelését, a rendszer úgy viselkedik, hogy mindig csak a legutóbbi GC óta létrejött objektumokat vizsgálja meg, a régieket békén hagyja. Ilyenkor két kérdés merülhet fel bennünk:

– Mi van akkor, ha a 0. generációs takarítás után sincsen elég szabad hely?

– Mi van akkor, ha nem egy 0. generációs objektum valamely hivatkozása frissül egy újonnan létrehozott objektumra?

Az első kérdésre a válasz, hogy ebben az esetben a GC a 0. és az 1. generációt kezdi el vizsgálni. Ha ez se hozná meg a kellő sikert, akkor a 0., 1. és a 2. generáció elemeit fogja megvizsgálni.

A második kérdésre a válasz kicsit összetettebb. Alapból a régebbi generációk olyan elemeinek belső hivatkozásait, melyre van root, a GC nem vizsgálja a fa építésekor. Viszont ezek idővel változhatnak is, emiatt a GC igénybe veszi a rendszer write-watch támogatását, ami a kernel32.dll-beli GetWriteWatch metódushívást jelenti. Ez által megtudhatja, hogy a legutóbbi GC lefutás óta történt-e referenciafrissítés.

A kezdeti 4 felvetésünk közül eddig még csak hármat használtunk fel. Most nézzük meg a 3-as sorszámú állításból milyen előnyünk származik. Ha C++-ban új elemeket folyamatosan allokálgatunk, akkor azok nagy valószínűséggel nem fognak egymás mellé kerülni, hiszen az első méretben is megfelelő szabad helyet fogják megkapni. DotNET esetén a veremszerű szerkezet itt előnyt jelent, hiszen így biztosan közel lesznek egymáshoz az újonnan allokált elemek. Mivel ezek sűrűn lépnek kapcsolatba egymással, emiatt a folytonos elhelyezés előnyös, hiszen nagyon valószínű, hogy sok ilyen objektum befér a processzor cache-ébe, ami gyorsabb elérést biztosít, mint a Ram-ból történő adatkinyerés.

Többszálúság kezelése

Utolsó blokként még néhány szót ejtenék a többszálú programoknál lévő optimalizációs lehetőségekről. De mielőtt még belemennénk a mechanizmusokba, nézzük azt, milyen kockázatot is hordoz magában a többszálúság a GC szempontjából.

Ha van egy többszálú alkalmazásunk, akkor azt meg kell gátolnunk, hogy ezek a szálak hozzáférjenek a managed heap-hez mindaddig, amíg a GC tevékenykedik. Hiszen az átrendezéskor az egyes mutatók értekei is megváltoznak. Tehát amíg a GC egy külön szálon fut, addig a több szál működését fel kell függeszteni, és az overhead-et minimálisra kell csökkenteni. Erre sokféle mechanizmus létezik, a teljesség igénye nélkül, íme, néhány:

–  Teljesen megszakítható kód: Amikor a GC elkezd dolgozni, akkor az összes többi szál felfüggesztődik. Viszont ahhoz, hogy utána folytatni lehessen őket a JIT (Just-In-Time) fordító által kezelt táblákban el kell tárolnia azt, hogy az adott szál éppen hol tartott egy adott metódusban, mely objektumokat használta és azok hogyan voltak elérhetőek (változó, regiszter, stb.). Ha ezek az infók megvannak, akkor a GC lefutása után adatvesztés nélkül folytatható tovább az alkalmazás.

Szál-eltérítés (Hijacking): Mivel a GC eléri és módosítani is tudja a szál stack-jét, ezért az éppen aktuálisan végrehajtott függvény visszatérési pontját átirányíthatja egy speciális függvényre. Ha az éppen futó metódus végére ér a szál, meghívódik a speciális függvény, mely felfüggeszti a szál tevékenységét a GC idejére. A takarítás után pedig visszaadja a vezérlést a szálnak.

Érdekesség: Hijacking esetén unmanaged code futtatása lehetséges párhuzamosan a GC futtatásával mindaddig, míg nem akar managed objektumhoz hozzáférni.

Mentési-pontok: A JIT fordító képes elhelyezni a metódus belsején belül olyan speciális függvényhívásokat, melyek azt ellenőrzik, hogy a GC nem várakozik-e. Ha igen, akkor altatja a szálat, majd a lefutása után felébreszti. Azokat a helyeket, ahova beszúrja ezeket a speciális függvényhívásokat a JIT, mentési pontnak nevezzük.

További lehetőségek többprocesszoros rendszerek esetén:

Szinkronizáció-mentes allokálás: Többprocesszoros rendszerek esetén a managed heap 0. generációja felosztható annyi részre, ahány processzor található az adott rendszerben. Ebben az esetben ezek szabadon, párhuzamosan végezhetnek allokációt, mindenféle szinkronizáció és zárolás nélkül.

Skálázható szemétgyűjtés: Többprocesszoros szerverek esetén nem csak a managed heap-et lehet megosztani, hanem magát a GC-t is. Ez azt jelenti, hogy mindegyik CPU a saját kis memória részét maga kezeli, és azon futtatgatja időről időre a nagytakarítást. Ebben az esetben a GC egy speciális változatáról beszélünk, mely a SrvGC névre hallgat és az MSCorSrv.dll-ben található. A „normál” változat mellesleg a WksGC nevet kapta és az MSCorWks.dll-ben lakik.

Összefoglalás

A cikk elején megismerkedhettünk a memóriakezelés szemétgyűjtést nem használó módszereinél leggyakrabban előforduló hibákkal, majd megnéztük, hogy a GC ezeket hogyan képes orvosolni. Ezt követően utánajártunk annak, hogy milyen feltakarítási mechanizmusok léteznek és azoknak mik az előnyei, illetve a hátrányai. A cikk második felében pedig jó néhány trükköt megnéztünk arra, hogy miként lehet kicselezni a GC-t, például újraélesztés, gyenge referenciák, stb. segítségével. Végezetül pedig betekintést nyerhettünk abba is, hogy a GC-t miként lehet optimalizálni a jobb teljesítmény elérése érdekében.

C# dokumentációs kommentek

Posted: 2011. február 11. in C#

/Ez a cikk egy idei egyetemi beadandóm kapcsán készült! /

A programozók alapjában véve lusták, és nem az önkifejezés nagy mesterei. Emiatt nem is igazán rajonganak a dokumentációkészítésért. Mindemellett még az is igaz, hogy a dokumentáció írásakor nem a jól bevált fejlesztő környezetükben kell dolgozniuk, hanem egy olyan helyen, ahol például nincsen az eszköztáron Build gomb (szövegszerkesztő). Emiatt mindig az utolsó utáni pillanatra halasztják a szoftver dokumentációjának elkészítését.

Nos, a dokumentációs kommentek ezen a helyzeten próbálnak meg javítani. Minden programozó használ többé-kevésbé kommenteket, hogy az általa előállított kódrészlet a későbbiekben is dekódolható legyen, vagy mások számára (itt fejlesztőre kell gondolni, nem átlag halandóra) is olvasható, érhető legyen. Miért ne lehetne ezeket felhasználni a dokumentációkészítéshez? A válasz nagyon egyszerű, mivel mindenki másképp csinálna. Ezért a Microsoft ezen úgy próbál meg segíteni, hogy ad egy ajánlást arra, hogy szerinte minkét kéne kinéznie a kommenteknek ahhoz, hogy utána lehessen belőlük dokumentációt generáltatni. Ehhez
egy speciális formátumra van szükség, amelyet a szakirodalomban dokumentációs kommenteknek neveznek.

A kommentektől a kész dokumentációig

A dokumentációs komment feladata egyfelől az egységes kinézet/felület biztosítása a kommentekhez, másfelől pedig a hierarchiába szervezhetőség. Utóbbit nézzük meg részletesebben.

Általában amikor egy osztályt felkommentezzünk, akkor megadunk az osztályról egy általános leírást, amely röviden összefoglalja az általa megvalósított funkcionalitást. Másrészről az egyes függvényekhez, illetve metódusokhoz is meg szokás adni egy-egy részletesebb meghatározást. Vagyis az objektumok és a tagjaik közötti kapcsolatot célszerű a kommentek szintjén is eltárolni.

Egy másik fontos kapcsolat, amely szintén megjelenik a kommentek szintjén is, az az objektumok közötti hierarchia. Például hivatkozunk egy másik osztályra, amely használja ezt (akár példakód megadásával is), vagy fordítva, egy olyanra hivatkozunk, amelyet ebben szeretnénk felhasználni. Tehát ezeket a kapcsolatokat is célszerű eltárolni, az egymásra történő hivatkozhatóság miatt.

A Microsoft ahhoz, hogy a fentebb említett két fontos tulajdonságot (vagyis az egységes felületet, és a hierarchiába szerverhetőséget) be tudja vezetni a kommentek szintjére, úgy gondolta, hogy az XML nyelv használatára van itt is szükség. Ugyanis az XML-nél az egységes felületet könnyen lehet garantálni séma vagy definíciós fájlok segítségével, illetve az XML fájlok alapból hierarchikus felépítésűek.

Az XML nem csak a kommentek tárolásánál jelenik meg, hanem már forráskódban is (vagyis magunkban a kommentekben is). Ugyanis így könnyen lehet garantálni az egységes kinézetet már kód szinten is, másrészt pedig a forráskód elemező is így egyszerűbben elő tudja állítani a kommentekből a megfelelő XML fájlt. A Microsoft részéről itt véget is ér a sztori. Vagyis a kezünk be ad egy reprezentációs modellt (hogyan kommentáljunk), illetve egy a modellnek megfelelő leírást (a generált XML fájlt). Az, hogy ezzel mi ezek után mit kezdünk, az már a mi dolgunk – állítja az MS.

Formálisabban megfogalmazva az előző bekezdés végét: a rendszer biztosítja számunkra a dokumentációs kommenteket, illetve a dokumentáció generátort, de a dokumentáció megjelentő már a fejlesztő feladata. Erre az álláspontra (érhetetlen okból) a .NET 2.0 és a Visual Studio 2005 megjelenésekor tértek át. Ugyanis azelőtt az 1.X-es verzióknál az IDE-ben még volt támogatás dokumentáció
megjelenítésre, úgynevezett web report formátumban. Sajnos ezt a 2005-ös változatból már kihagyták.

De hogy még se maradjunk megjelenítő eszköz nélkül a Microsoft nem sokkal a 2005-ös fejlesztő környezet megjelenés után nyilvánossá tette az általuk belsőleg is használt dokumentáció megjelenítőt, amely a SandCastle névre hallgat. Ezzel az eszközzel részletesebben is fogunk még foglalkozni a későbbiekben, ezért előjáróban csak annyit, hogy nem egyszerű használni. Ezért megjelentek a piacon olyan eszközök,
amelyek teljes körű dokumentációkezelési támogatást nyújtottak, ilyen volt például a DoxyGen. (Ez így nem teljesen igaz, ugyanis a DoxyGen már előbb jelen volt, csak azelőtt a C# nyelvet nem támogatta).

Alap dokumentációs kommentek

Ennyi bevezető után, lássuk hogyan is néznek ki ezek a dokumentációs kommentek. Az egysoros dokumentációs komment három darab jobbra dőlő perjellel kezdődik, míg a többsoros változata ennek egy jobbra dőlő perjellel és két csillaggal kezdődik és egy csillag + egy perjel párossal záródik. Nézzünk ezekre egy egyszerű példát:

/// Egysoros dokumentációs komment
/**
* Többsoros dokumentációs
* komment
*/

Utóbbit ritkán szokás használni, inkább az egysoros változat kezdő szimbólumait szokták ismételgetni. Amit ezekről még fontos megjegyezni itt az elején az az, hogy úgy kell őket használni, mint az attribútumokat, vagyis mindig az adott osztály, metódus, stb. fölé kell elhelyezni. Íme, egy konkrét példa:

/// <summary>
/// Ezt az osztályt 2D-s pontok leírására használjuk
/// </summary>
public class Point
{

Mint, ahogyan azt már fentebb is említettem, illetve, ahogyan azt a kódrészlet is mutatja, XML elemek segítségével történik a dokumentációs kommentek megadása. A summary az egyik legegyszerűbb, amely egy általános szöveges összefoglalás megfogalmazását teszi lehetővé.

Két dolgot jegyeznék még meg, mielőtt belekezdenék az ajánlott dokumentációs kommentek felsorolásába. Az egyik, hogy mivel XML alapú a rendszer, ezért szabadon bővíthető. A másik dolog szintén az XML alapúságból fakad: a generikus típusoknál különös elővigyázatossággal kell eljárni, ugyanis az xml elemek leírásához használt forma megegyezik a generikus paraméterek megadásának módjával. Ennek kivédésével a megfelelő dokumentációs kommentek résznél fogunk foglalkozni.

A dokumentációs kommenteket általában két nagy csoportba szokás osztani. Az egyiket magyar fordításban elsődleges, a másikat pedig másodlagos csoportnak lehetne nevezni. Az elsődleges csoportba olyan XML tag-ek tartoznak bele, amelyek a komment szintjén használhatóak. A másodlagos csoportba pedig azok kaptak helyet, akiket nem lehet önmagunkban használni, hanem be kell őket csomagolni, ágyazni egy elődleges kommentbe. Eme csoportosítás alapján a 22 alap dokumentációs komment:

– Elsődleges csoport: <example>, <exception>, <include>, <param>, <permission>, <remarks>, <returns>, <seealso>, <summary>, <typeparam>

– Másodlagos csoport: <c>, <code>, <description>, <item>, <list>, <listheader>, <term>, <typeparamref>, <para>, <paramref>, <see>, <value>

Egy másfajta szemléletmód szerint történő kategorizálása a kommenteknek a funkció szerinti csoportosítás (természetesen egy komment akár több funkcionális csoportba is beletartozhat). Ezek alapján az alábbi kategóriákat lehet megkülönböztetni: szövegformázó tag-ek, lista/táblázat készítő tag-ek, hivatkozás leíró tag-ek, általános leíró tag-ek, paraméter leíró tag-ek, és függvény leíró tag-ek. Íme, ezek alapján a 22 alap dokumentációs komment csoportosítása:

– Szövegformázó tag-ek: <c>, <code>, <param>

– Lista/Táblázat készítő tag-ek: <list>, <listheader>, <item>, <term>, <description>

– Hivatkozás leíró tag-ek: <see>, <seealso>, (<include>)

– Általános leíró tag-ek: <summary>, <remarks>, <value>, <example>

– Paraméter leíró tag-ek: <param>, <paramref>, <typeparam>, <typeparamref>

– Függvény leíró tag-ek: <returns>, <exception>, <permission>

A dokumentációs kommenteket a funkcionális csoportosítás szerint haladva fogom bemutatni.

<c>

Ez egy olyan formázó tag, amely segítségével inline vagy egysoros (tehát viszonylag rövid) kódrészlet stílusú szöveget lehet elhelyezni a kommentben. Vagyis a nyitó és záró tag-ek közé elhelyezett szöveg olyan betűtípust (Courier New-t) fog használni, mint amilyen a forráskód betűtípusa. Ezt általában osztály vagy metódus nevek formázására, illetve egysoros utasítások beszúrására szokták használni a kommenteken belül. Íme,egy egyszerű példa:

/// <remarks>
/// Ez az osztály a <c>Singleton</c> osztályból van származtatva
/// </remarks>
public class DataAccessLayer: Singleton

(A remarks tag osztályok általános leírására szolgáló tag, lásd lentebb bővebben.)

<code>

Ez a formázó tag nagyon hasonlít a c tag-re, a kettő között az a különbség, hogy a code-t többsoros forráskódok idézésére is használhatjuk. Általában az example (példaadásra használt leíró) tag-gel együtt szokott előfordulni a kommentekben. Íme, egy egyszerű példa a használatára:

/// <example> Mivel ez egy egyke osztály, ezért az elérése az alábbi módon történik:
/// <code>
/// Product p = DataAccessLayer.GetInstance().GetProductById(1);
/// DataAccessLayer.GetInstance().UpdateProduct(originalproduct, modifiedproduct);
/// </code>
/// Tehát nincs szükség külön példányosításra!
/// </example>

(Természetesen az example tag-ek köré is kell tenni egy-egy remark tag-et.)

<para>

Ez egy olyan formázó tag, amely hosszú leírások esetén használatos, ugyanis ennek segítségével lehet paragrafusokat létrehozni. A html-es p tag-re hasonlít leginkább. Általában a remark és a summary tag-eken belül szokás használni. Íme, egy egyszerű példa:

/// <remarks>
/// Ez az osztály az alábbi funkcionalitásért felelős:
/// <para> Termékek elérése. Ide az alábbi fgv-ek tartoznak: …</para>
/// <para> Termékek módosítása. Ide az alábbi fgv-ek tartoznak: …</para>
/// <para> Új termék felvitele, melyet a <c>CreateProduct</c> meghívásával lehet végrehajtatni.</para>
/// …
/// </remarks>

<list>

Ezen lista/táblázat készítő tag segítségével számozást (számozott listát) vagy felsorolást (pontozott listát) lehet létrehozni, illetve akár táblázatot is. Azt, hogy éppen melyikre van szükségünk azt a type attribútumon keresztül tudjuk beállítani, amely az alábbi értékek valamelyikét veheti fel: bullet, number, table. Az egyes elemeket az item tag-ek segítségével lehet leírni, amelyek általában egy term és egy description tag-ből állnak. Van lehetőség címke (listákhoz) vagy fejlécsor (táblázathoz) megadására is a listheader tag segítségével, melynek felépítése megegyezik az item-éval. A list tag segítségével tehát úgy nevezett definíciós listát/táblázatot lehet létrehozni, vagyis megmondhatjuk, hogy mi (term), mit jelent (description). Íme, egy egyszerű példa (az előző példa táblázatos változata):

/// <list type=”table”>
/// <listheader>
/// <term>Művelet típusa (CRUD)</term>
/// <description>A konkrét műveletek nevei</description>
/// </listheader>
/// <item>
/// <term>Create</term>
/// <description>CreateProduct, CreateProducts</description>
/// </item>
/// <item>
/// <term>Retrieve</term>
/// …
/// </list>

<listheader>

Ezen tag segítségével a táblázatokhoz fejlécsort, vagy listákhoz címkét/fejlécet lehet megadni. A html table objektumának th tag-jével hozható analógiába. Fontos: csak a list-tel együtt használható. Példa: lásd list
példa.

<item>

Ez a tag arra szolgál, hogy egy lista egy elemét vagy egy táblázat egy sorát leírjuk vele. Alapból két alkotó elemből tevődik össze egy term-ből (mi) és egy description-ből (mit jelent). De van arra is lehetőség, hogy csak a description használjuk! A html table objektumának tr tag-jével hozható párhuzamba. Példa: lásd list példa.

<term>

Ezen tag megadása nem kötelező, de ha megadjuk, akkor azt mondja meg, hogy mihez találunk leírást a description tag-ben. Példa: lásd list példa.

<description>

Ezen tag egy táblázat- vagy egy listabeli elem leírását tartalmazza. Használható a term-mel együtt, illetve nélküle is. Mindig egy item tag-en belül kell lennie. Példa: lásd list példa.

<see>

Ez a tag a hivatkozások leírására szolgál. Egy másik osztályra, vagy egy metódusra történő hivatkozáskor, a cref attribútumon keresztül kell megadnunk annak a nevét. A tag manifesztációja a dokumentációban egy hiperlink lesz. Íme, egy egyszerű példa:

/// <summary>
/// A fgv. ellentett párja: <see cref=”ConvertBack” />
/// </summary>
public object ConvertTo(object value, Type …

/// <summary>
/// A fgv. ellentett párja: <see cref=”ConvertTo” />
/// </summary>
public object ConvertBack(object value, Type …

<seealso>

Ezzel a tag-gel a dokumentáció „See also” (~„Lásd bővebben”) részébe tudunk elhelyezni linkeket. A felépítése teljesen megegyezik a see tag-ével.

/// <seealso cref=”UpdateProduct” />
/// <seealso cref=”CreateProduct” />
public void CreateOrUpdateProduct …

<include>

Ez a tag is a hivatkozás leíró tag-ek csoportjába sorolandó, de itt a hivatkozás alatt kicsit mást értünk, mint a see, vagy a seealso esetében. Ezzel egy XML dokumentumra (vagy annak részlétére) tudunk hivatkozni, amelyet a dokumentáció generálásakor a fordító behelyettesít a megfelelő xml tartalommal. A hivatkozás szintaktikája a következő: a file attribútum szolgál az xml dokumentum relatív elérési útjának megadására, a path opcionális attribútum pedig egy XPATH lekérdezést tartalmazhat. Íme, egy egyszerű példa:

/// <summary> Az alábbi adatbázisok valamelyikéhez kapcsolódhat (teljes connstring-ek!)
/// <code>
/// <include file=”web.config” path=’configuration/connectionstrings/*’ />
/// </code>
/// </summary>
public void Connect2ADataBase()

<summary>

Ez az egyik legáltalánosabb célú dokumentációs tag. Használható önmagában is, illetve konténerként is. A Microsoft ajánlása szerint a függvényeket és a metódusokat ezzel a tag-gel célszerű ellátni, és itt röviden összefoglalni az általuk megvalósított funkcionalitást. Példa: korábbi példák közül jó néhány.

<remarks>

Ez a tag nagyon hasonlít a summary-ra, a különbség a kettő között az, hogy míg a summary függvényekhez és metódusokhoz lett kitalálva, addig a remarks osztályoknál és interfészeknél használandó. A Visual Studio mindenhol alapból a summary-t ajánlja fel (a /// beírása után automatikusan beszúrja a forráskódba), ezért ezt kézzel át kell írni típusok kommentezése esetén. Példa: korábbi példák közül jó néhány.

<value>

Ez a tag az osztály tulajdonságainak felkommentezésére használható. Itt célszerű röviden összefoglalni, hogy mit és miért tárolunk el ezekben a tulajdonságokban, illetve adott esetben a módosíthatósági megkötésekre is fontos felhívni a figyelmet (pl.: csak olvasható). Íme, egy egyszerű példa:

/// <value>A csatlakozott kliensek számát adja meg. Csak lekérdezhető.
/// Új kliens felvételekor ez a szám automatikusan megnő eggyel. (<c>AddClient</c>)</value>
public int Clients { get; private set;}
public void AddClient (Client c)

<example>

Ez a tag általában a code-dal együtt használatos. Eme dokumentációs kommenttel példakódot adhatunk az adott osztály vagy függvény használatára, elérésére. (Az example tag alapból a tartalmát nem forráskód stílusban jeleníti meg, ezért van szükség a code tag-re). Példa: lásd code példa.

<param>

Ez a tag a függvény paramétereinek kommentezésére szolgál. Itt azt szokás megadni, hogy mit reprezentál az adott paraméter, módosítjuk-e a paraméter értéket a metódusban, illetve van-e a bemenő értékre valamilyen megkötés. Íme, egy egyszerű példa

/// <param name=”originalproduct”>Az eredeti termék entitás</param>
/// <param name=”modifiedproduct”>A módosított termék entitás</param>
public void UpdateProduct(Product originalproduct, Product modifiedproduct)

<paramref>

Ezt a tag-et akkor szokás használni, ha egy függvény paraméterére szeretnénk hivatkozni valahol a kommentben (például valahol a summary-n belül). A hivatkozott paraméter nevét a name attribútumon keresztül lehet megadni. Íme, egyszerű példa (az előző példa kibővítése):

/// <summary>
/// Először összehasonlítja az <paramref name=”originalproduct” /> és a <paramref name=”modifiedproduct” /> értékeket, és ezek alapján csak a deltát küldi fel a szervernek.
/// </summary>
/// <param name=”originalproduct”>Az eredeti termék entitás</param>
/// <param name=”modifiedproduct”> …

<typeparam>

Ez a tag a generikus paraméterekhez történő megjegyzésfűzésre használható. Itt célszerű leírni, hogy az adott paraméterrel mit szeretnénk csinálni, illetve van-e rá valamilyen megkötés. A typeparam egyaránt használható generikus függvényeknél és generikus osztályoknál is. Íme, egyszerű példa:

/// <typeparam name=”T”>A pár első elemének típusa</typeparam>
/// <typeparam name=”U”>A pár második elemének típusa</typeparam>
pubic class Pair<T, U>

<typeparamref>

Ezen tag segítségével lehet kommentben generikus paraméterre hivatkozni. A hivatkozást a name attribútumon keresztül kell megadni. Fontos, ha magára a generikus osztályra vagy függvényre szeretnénk a kommentben utalni, akkor nem használhatjuk a <,> jeleket, mivel az összezavarná az XML értelmezőt. Emiatt használjunk inkább a {} zárójeleket, vagy a html-nél is használt &lt; &gt; párost. Íme,egy egyszerű példa:

/// <summary>
/// A <typeparamref name=”T” /> típusparaméter alapján először meghatározza a táblát, majd utána lekéri belőle az összes adatot, végül pedig egy listában adja őket vissza.
/// </summary>
/// <typeparam name=”T”>Az entitás neve</typeparam>
public List<T> RetriveAllData<T>()

<returns>

Ezzel a tag-gel a függvények visszatérési értékéhez lehet kommentet fűzni. Úgy is fogalmazhatunk, hogy a param ellentett párja, csak itt nincs returnsref. Általában itt a visszaadott típusban történő reprezentációról szokás egy-két szót ejteni. Íme, egy egyszerű példa:

///<returns>Egy termék szöveges reprezentációja kulcs-érték párok felsorolásával, pontosvesszőkkel elválasztva.</returns>
public override string ToString()

<exception>

A Java-val ellentétben, ahol kötelező kiírni a függvény által le nem kezelt kivételek teljes listáját a szignatúránál, C#-ban erre nincsen se mód, se lehetőség. Viszont ez egy eléggé hasznos dolog, ezért célszerű mindenesetben (még ha nem is készül dokumentáció a kommentekből, akkor is) az exception tag-et használni. (Illetve a lekezelt kivételekre is érdemes így felhívni a figyelmet). A cref attribútumon keresztül kell megadni a lehetséges Exception osztály nevét. A nyitó és a záró tag-ek közé pedig 2 információt ajánlott leírni: mikor keletkezhet ez a kivétel, és levan-e kezelve. Íme, egy egyszerű példa:

/// <exception cref=”System.DivideByZeroException”>
/// Ha az <paramref name=”y” /> értéke 0, akkor ilyen kivétel dobódik, de le van kezelve.
/// </exception>
/// <param name=”x”>Osztandó</param>
/// <param name=”y”>Osztó</param>
public double Divide (int x, int y)

<permission>

Ezzel a kommenttel azt írhatjuk le, hogy egy adott függvény milyen jogok mellett hívható meg, vagy egy adott típus milyen jogok mellett használható. Ha van rá bármilyen megkötés csak akkor van értelme ennek a kommentnek. Íme, egy értelmetlen példa:

/// <permisson cref=”System.Security.PermissionSet”>
/// Ezt a függvényt bárki meghívhatja</permission>
public void PublicMethod()

XML generálás

A dokumentációs kommentekkel való ismerkedés után folytassuk tovább az utunkat a dokumentációgenerálásával. Ez a név kicsit félrevezető lehet, ugyanis itt nem arról van szó, hogy egy kész chm, vagy html alapú dokumentációt generáltatunk le a rendszerrel, hanem ebben a terminológiában azt értjük ez alatt, hogy a dokumentációs kommentekből egy xml dokumentum generálódik, amely egy helyen és egységes ábrázolja a kommenteket. Vagyis ez a dokumentáció modellje, ha úgy vesszük, ahonnan a dokumentáció megjelenítő majd kinyeri az adatokat.

Tehát ebben a részben az XML dokumentum előállításával és felépítésével fogunk foglalkozni. A dokumentációs kommentekből az XML fájl előállításához mindösszesen a C# fordítóra van szükségünk. Ennek van egy olyan speciális kapcsolója, amellyel az adott cs forrásállományból képes előállítani a megfelelő dokumentációs modellt. Ez a kapcsoló a /doc. Íme, egy egyszerű példa, amelyet a VS Command Promt-ból lehet kiadni:

csc Pair.cs /doc: PairDocumentation.xml

Ennek persze van egy egyszerűbb és kényelmesebb módja is. Visual Studio-ban a projekt-en jobb egér gombbal kell kattintatni és ott a helyi menüből a Properties lehetőséget kell kiválasztani. Az új ablakban a Build fülre kell átváltani, majd ennek az Output szekciójában meg kell keresni az XML documentation file jelölőnégyzetet. Ezt pipálja be! Ilyenkor alapból a lefordított dll mellé helyezi el az xml fájlt, vagyis bin/debug
mappába.

Ezek után nézzük meg, hogy mit is tartalmaz egy ilyen XML fájl. Íme, egy egyszerű példa:

A forrásállomány: Pair.cs

/// <summary>
/// Ezen osztály segítségével egy párt lehet reprezentálni
/// </summary>
/// <typeparam name=”T”>A pár első elemének típusa</typeparam>
/// <typeparam name=”U”>A pár második elemének a típusa</typeparam>
class Pair<T, U>
{
    public Pair(T t, U u)
    {
        First = t;
        Second = u;
    }
    public T First { get; set; }
    public U Second { get; set; }
}

A belőle generált modell: PairDocumentation.xml

<?xml version=”1.0″?>
<doc>
    <assembly>
        <name>DocComDemo</name>
    </assembly>
    <members>
        <member name=”T:DocComDemo.Pair`2″>
            <summary>
            Ezen osztály segítségével egy párt lehet reprezentálni
            </summary>
            <typeparam name=”T”>A pár első elemének típusa</typeparam>
            <typeparam name=”U”>A pár második elemének típusa</typeparam>
        </member>
    </members>
</doc>

A generált XML állományban minden dokumentációs komment kap egy egyedi azonosítót, egyfelől az egymásra történő hivatkozás megkönnyítése végett, másrészt pedig a dokumentáció megjelenítőnek is szüksége van erre. Ennek az egyedi azonosítónak az előállítása egy viszonylag hosszú algoritmus alapján történik. Ezért az algoritmussal nem untatom a nagyérdeműt (akit esetleg érdekel, az megtalálhatja az MSDN-en), viszont a fentebbi kódrészletbeli példán keresztül megmutatom, mennyi információ tárol el a rendszer csak az azonosítókban.

A T:DocComDemo.Pair’2 részei:

T: A dokumentációs komment egy típushoz tartozik. Ha például egy metódushoz tartozna, akkor M lenne, vagy ha egy eseményhez, akkor pedig E.

DocComDemo.Pair: A teljes neve, annak az objektumnak, amelyhez a kommentet fűztük. (Metódusok esetén az esetleges paraméterek típusa is fel lenne itt sorolva.)

’2: 2 generikus paramétere van az osztálynak.

XML értelmezése, avagy a Dokumentáció megjelenítő

Végezetül elérkeztünk a dokumentáció megjelenítőhöz, amely értelmet ad a modellben tárolt adatoknak. Mint, ahogy azt a bevezetőben is említettem, alapból nem tartozik ilyen eszköz a Visual Studio-hoz, ezért ezt külön kell beszerezni. A Microsoft ajánlása erre a saját fejlesztésű SandCastle nevezetű program, amelyet az alábbi címről lehet letölteni: http://sandcastle.codeplex.com/.

A szoftver feltelepítése után úgy tudjuk használni a programot, hogy elindítjuk a hozzá tartozó grafikus felületet. Sajnos ez alapból nem jelenik meg a Start menüben, ezért nekünk kell megkeresünk és futtatnunk adminisztrátori jogokkal. Alapból az alábbi elérési úton keresztül érhetjük el: C:\Program Files\Sandcastle\Examples\Generic\SandcastleGui.exe.

A felületen keresztül meg kell adni az dokumentációs kommentekkel ellátott alkalmazás, vagy osztály könyvtár lefordított exe-jét, vagy dll-jét. Ezenkívül a C# fordító által generált XML fájlt is meg kell adni, illetve az esetleges + osztálykönyvtárakat is, amelyeket külön adtunk a projekthez hozzá. Ezek után be kell még állítanunk a dokumentáció nevét, a megjelenítési stílusát (alapból vs2005, de a prototype is egész jó), illetve a leendő formátumát (chm, sima html, vagy hxs). Végezetül már csak rá kell kattintani a Build gombra, ahol először el kell menteni a projektet és csak utána kezdődhet meg a tényleges munka. A végleges dokumentáció elkészítésének ideje nagyban függ a projekt méretéttől, de az általánosságban elmondható róla, hogy nem sieti el a dolgot.

Fordítás közben töménytelen log információt kapunk, amelyek viszont nem túl sok hasznos információval szolgál. Ha elsőre nem generálja le rendesen a dokumentációt, akkor célszerű letölteni a SandCastle-höz készült kis segédalkalmazást, amely egyrészt a rengeteg log-ot képes érhető, emészthető formába átkonvertálni, illetve nagyobb testreszabhatóságot is biztosít. Az alábbi címről tölthető le a SandCastle Help
File Builder
nevezetű program: http://shfb.codeplex.com/.

Javaslat

A dokumentációs kommentek használata mindenféleképpen javaslott, ugyanis így átláthatóbb lesz tőle a programkód, illetve lehet belőle dokumentációt gyártani és még az IntelliSence is fel tudja használni!

Örömmel jelentem, hogy végre megjelent a nagy magyar Silverlight könyv! 

Silverlight könyv

A könyv 5 szerző és 1 szakmai lektor keze munkáját dicséri: Árvai Zoltán, Fár Attila Gergő, Novák István, Reiter István, Tóth László, és Jómagam. Ezúton is szeretnék köszönetet mondani Novák Istvánnak a fáradhatatlan szerkesztői és lektori munkájáért!

Nem kis munka volt elkészíteni, de reméljük megérte. 🙂 A könyv az alábbi fejezetekből tevődik össze:

1. Silverlight és XAML alapok
2. Layout Management
3. Alapvető vezérlők
4. Animáció és média
5. Stílusok és testreszabhatóság
6. Adatok kezelése
7. Saját vezérlők készítése
8. Kommunikáció a kliens és a szerver között
9. A Silverlight rejtett képességei
10. Üzleti alkalmazások fejlesztése Silverlightban
11. Összetett adatok megjelenítése és kezelése
12. Moduláris alkalmazások fejlesztése

És az alábbi címről tölthető le:

http://devportal.hu/Fajlok/Default.aspx?shareid=1&path=Konyvek%5cSilverlight+4+-+A+technol%c3%b3gia%2c+%c3%a9s+ami+m%c3%b6g%c3%b6tte+van

Észrevételeket, megjegyzéseket örömmel fogadunk.

Utolsó gondolatként még annyit jegyeznék meg, hogy mellesleg ez a 200. blogbejegyzésem és azt hiszem keresve se találhattam volna jobb témát. 😉

Kéthétnyi kihagyás után, mely nagyrészt beadandó, könyv és hibajavító kódok írásával telt, ismét jelentkezem egy újabb (remélhetőleg) érdekes és hasznos cikkel. Ebben a részben Chris Diaz (Visual Studio-s csapatnál group program manager) Tips&Tricks: Visual Studio 2010 IDE & Extensions nevet viselő előadásán elhangzottakat szeretném Veletek megosztani.

Elsőre kicsit szkeptikusan álltam hozzá, mert az ilyen előadások ritkán szoktak hasznos információval szolgálni, de végül pozitívan kellett csalódnom. Sok okosság hangozott el (étvágygerjesztőként: a VS 20 millió kódsorból áll; kommentek, whitespacek nélküli is, több mint 4 millió) és nem csak egy másfélórás shortkey bemutató volt az egész. Nézzük sorjában a fontosabbakat:

– A Visual Studio kezdőlapját teljesen áttervezték. Jár is érte a piros pont a VS csapatnak. De a legjobb az egészben, hogy bárki átszerkesztheti, sőt akár fejleszthet is bele új modulokat (mármint a kezdőlapba). Ehhez annyit kell tenni, hogy az online template-k között rá kell keresni a Custom Start Page Project Template-re. (A háttérben a Visual Studio Gallery oldal taralmában keresünk.) Ehhez előtte persze fel kell telepíteni a Visual Studio SDK-t. Ha ez megvan, akkor a letöltött start page template-t egyből tesztre is szabhatjuk. Build után a Visual Studio-ban úgy tudjuk beállítani az új kezdőlapot, hogy elnavigálunk a Tools >> Options >> Startup és itt a Customize Start Page legördülő listából ki kell választani a sajátot.

Magyarított egyedi VS kezdőoldal

– Egy másik hasznos dolog a Productivity Power Tools  kiegészítés a Visual Studio-hoz, amelyet az MS fejlesztett (igazándiból azt nem értem, ez miért nincs alapból benne). Ez valójában egy tonna cool szolgáltatást tartalmaz (pl.: Solution Navigator, Quick Access) és egy jó pár olyat, ami nélkül azért lehet élni (pl.: tripla kattintás, ctrl + klikk >> go to definition, stb.). Én most csak a Solution Navigator-t és a Quick Access-t mutatnám be. A Solution Navigator-nek két nézete van, az egyik a Solution Explorer és a Class View összedolgozása igazándiból, a másik egy inline referencia és hívás kereső. Ezek eléggé elő tudják segíteni egy új rendszer megismerését, vagy egy komplex rendszer átlátását. (Persze ez nem helyettesíti az architecture explorer és a függőségi gráf használatát.)

Solution Navigator 

Inline Solution Navigator

A Quick Access ablak lényege, hogy egy adott fogalomhoz kapcsolódó összes Visual Studio-s szolgáltatásra, menüpontra, beállításra, stb. rá lehet keresni. Tehát ha például a debug beállításait nem találjuk az IDE-ben, akkor egyszerűen le kell ütnünk az CTRL + 3-at és be kell írni a keresőbe, hogy debug, a többit a VS megoldja. Ez egy eléggé hasznos és gyakran használható kis ablak szerintem.

– A harmadik bekezdésbe néhány kódszerkesztésnél hasznos feature-t és billentyűkombinációt gyűjtöttem össze. Néha elfordul olyan, hogy az IntelliSence és a VS túlbuzgó, például ha egy még meg nem írt metódust akarunk meghívni egy osztály példányon, akkor ő a hozzá legjobban hasonlító szóra fogja lecserélni az általunk beírt függvény nevét. Jó hír, le lehet ezt tiltani ideiglenesen az IntelliSence-nél (egy úgynevezett Suggestion Mode-ba lehet átváltani) a Ctrl + Alt + Space segítségével. Persze ne felejtsük el utána vissza bekapcsolni! Egy másik hasznos feature a több sor egyidejű módosítása, pl.: hozzáférési szint átállításkor. Ilyenkor az Alt + egér vagy az Alt + Shift és a le,föl,jobbra,balra gombok segítségével ki kell jelölni a szerkesztendő sorokat, majd utána már gépelhetjük is a kódot egyszerre több sorba.

 Utolsó dologként a blokkmozgatás technikát mutatnám meg. Jelöljük ki a mozgatni kívánt szöveget és az Alt + le,föl gombok segítségével helyezzük át a megfelelő helyre. Néhány esetben valamivel kényelmesebb, mint a Ctrl+C, Crtl+V páros.
Utolsó gondolatként csak annyit fűznék még hozzá ehhez a bejegyzéshez, hogy érdemes megnézni az előadásról készült videót, mert tényleg sok hasznos dolgot, technikát lehet belőle tanulni.

A sorozat következő részében az SQL Server 2008 R2 egy új szolgáltatásával fogunk foglalkozni. (Változás jogát fenntartom!)

A Tech-Ed-es sorozatom első szakmai cikkének Christian Wenz által tartott Web Applications in Danger: Attacks Against Web Sites and RIAs interaktív szekciójának taglalását választottam. Bár a címből nem látszik, de ez valójában egy kétrészes előadássorozat első fele, melynek a folytatása a Usability, SEO, Security: Common RIA and Ajax Mistakes (and Fixes) nevet kapta. A kettő együtt alkotja a mostani blogbejegyzésem témáját.

 Webes Támadások

A biztonságról (kifejezetten a webes alkalmazásoknál, melyeket bárki elérhet, emiatt több lehet köztük a potenciális rosszakaró) órákon és órákon keresztül lehetne (és kéne is) beszélni, de sokan csak úgy gondolnak rá (rendkívül helytelenül), hogy ez csak egy extra szolgáltatás. NEM, az alkalmazásoknak ugyanúgy a szerves részét képezi a biztonság, mint például az adatelérési réteg. Ebben a bejegyzésben nem a téma létjogosultságáról és az alapjairól szeretnék értekezni, hanem (mint ahogyan az előadásokon is) néhány érdekesebb és mostanság „divatos” támadási technikát és ellenszert (countermeasure-t) szeretnék Nektek bemutatni, Veletek megosztani.

Támadások célpontjai

Az SQL Injection-ről (1,2,3) és a Cross-Site Scripting-ről (1,2,3) (remélhetőleg) már mindenki hallott és tudja, miként kell ellenük védekezni. Viszont a Cross-Site Request Forgery-ről (1,2,3,4) vagy a Clickjacking-ről (1,2,3,4) már annál kevesebben. Kezdjük először a CSRF kivesézésével kérdez-felelek formában.

– Mi is az a Cross-Site Request Forgery?
– Ez egy olyan webes támadási technika, amelynek az alapja pont az XSS alapjának ellentéte. Míg ott a felhasználó bízik meg az adott oldal tartalmában, addig itt a weboldal fogadja bizalmába a klienst és hajt végre a nevében a nem kívánt műveletet. A CSRF-et szokás még XSRF, illetve session riding néven is emlegetni.

– Hogyan működik a CSRF?
– Az áldozatnak be kell jelentkezve lennie egy adott rendszerbe, ahol az authentikációs információk egy részét a sütiben tárolják. Ezeket az értékes adatokat a böngésző egy GET http utasítás ellenében minden további nélkül rendelkezésére bocsájtja a rendszernek. Az URL-be be lehet csomagolni GET paramétereket, amelyek segítségével az oldalon meg lehet hívni bizonyos műveleteket. Íme, egy egyszerű példa:

https://ourbank.com/withdraw?from=you&to=me&amount=1000000

Ha mindezt mondjuk egy kép src attribútumába helyezzük el, akkor az áldozat számára az egész művelet mind addig láthatatlan marad, amíg meg nem érkezik a hó végi számlakivonata a banktól.

Cross Site Request Forgery működése

– Miként lehet ellene védekezni?
– Kliensként javaslott kijelentkezni a rendszerekből, ha már nem használjuk őket és elkerülni az „emlékezzen rám” lehetőségeket. Fejlesztőként használjuk a Synchronizer Token Pattern-t, melynek lényege, hogy egy extra véletlen generált token-t is utaztatunk az oldalon, melyet a sessionid-val együtt mindig megvizsgálunk oldalkéréskor. További lehetőségek az alábbi oldalon találhatóak.

 

– Mi is az a Clickjacking, vagy klikkeltérítés?
– Ez is egy olyan webes támadási technika, mely a weboldal felhasználó iránti bizalmára épül. Itt a kliens a tudta nélkül kattint egy olyan gombra, mely olyan http üzeneteket küld el, melyekkel saját magáról adhat ki információkat, vagy rosszabb esetben akár a gép fölötti irányítást ruházza át másra. Szokás ezt a támadást még UI redressing-nek is hívni.

– Hogyan működik?
– A technika lényege, hogy a támadó rávegye az áldozatot arra, hogy rákattintson egy olyan linkre, amire esze ágába nem klikkelne, ha tudna róla. Általában egy iframe segítségével két réteget hoznak létre egymás fölött, ahol a felső réteg nem feltétlenül fedi teljesen az alsót. Amikor a felhasználó azt hiszi, hogy rákattint a felső rétegen egy gombra, akkor valójában az alsó rétegen lévőre kattint és annak a kódját hajtja végre a rendszer. Az elmúlt pár évben ez eléggé nagy port kavart, ugyanis ily módon a flash webkamera funkcióját is be lehetett aktiválni. Néhány érdekes cikk a témával kapcsolatban(1,2,3,4).

Clickjacking működése

– Miként lehet ellene védekezni?
– Kliensként egyrészt ne kattintgassunk mindenféle reklámra, mely valamilyen banális dolgot kínálnak számunkra INGYEN. Másrészt használjuk a FireFox-os NoScript kiegészítést, mely CleanClick-nek elnevezett funkciója meggátolja, hogy clickjacking áldozattá váljunk. Fejlesztőként használjuk a framekiller nevezetű js kódot, mely meggátolja, hogy az oldalunkat mások beágyazhassák frame-ekbe, vagy használjuk az X-Frame-Option header tag-et.

 

– További webes támadásokról és ellenszereikről, hol olvashatok bővebben?
– Egyrészt az alábbi MSPress könyvben: Developing More-Secure ASP.NET 2.0 Applications, másrészt pedig az OWASP és a Seven Deadliest WebAttacks oldalakon.

– A jövőben szívesen olvasnál a blogon hasonló témájú cikkeket?
– Ha igen, akkor kérlek, jelezd valamilyen úton-módon.

A Tech-Ed cikksorozat második részében magát a konferenciát szeretném Nektek bemutatni.  Ez a több mint 3000 fejlesztőt és üzemeltetőt Németország szívébe csalogató öt napos konferencia az európai színhelye a minden évben 3 kontinensen megrendezésre kerülő Tech-Ed franchise-nak.  Az amerikai változatot bonyolítja le a leghamarabb még májusban, majd utána jön az európai hadszíntér, végül pedig Kína decemberben, illetve Dubai márciusban.

A Tech-Ed oly módon van felépítve, mint a PDC vagy a MIX, vagyis van egy plenáris ülés, amit Keynote-nak hívnak, utána pedig több teremben folynak párhuzamosan az előadások, melyek közül a hallgatóság tetszése szerint válogathat. Az előadásokat alapjában véve kétféle szempont szerint csoportosítják: szint (level {100, 200, 300, 400}), illetve tartalom (pl.: architektúra, adatbázis, menedzsment, webfejlesztés, mobilfejlesztés, stb.). Persze a szint sokszor becsapós, ugyanis level 300-as előadások is néha eléggé bevezető szintűre sikerülnek. Minden előadás 1 óra hosszú (rövid), ezért van egy jó pár olyan témakör, amikből emiatt előadássorozatokat szoktak inkább tartani (pl.: software testing, performance tuning). Vannak úgy nevezett interactive session-ök is (interaktív szekciók), melyek nem 300-500 fős előadótermekben kaptak helyet, hanem a kiállítási részlegen belül lekerített kb. 30-40 fős pavilonokban. Mint ahogyan a nevéből és a létszámból is adódik, itt van lehetőség beleszólni, belekérdezni az előadásba. Összességében több mint 150 előadó közel 600 előadással szórakoztatta kb. 3,5 napon keresztül a nagyérdeműt.

Az alapozó után nézzük meg mennyivel tudott többet/kevesebbet nyújtani az idei berlini Tech-Ed a két évvel ezelőtti barcelonaihoz képest.

Barcelona VS. Berlin (TechEd)

Nos, Barcelona a földrajzi adottságai miatt előnyt élvez, ugyanis ott a mediterrán időjárás és a tengerpart közelsége, melyek rendkívül kellemes élményt nyújtanak. Berlinről sajnos ez nem mondható sőt, sajnos szinte mindennap esett az eső és eléggé hideg is volt az ott létünk alatt.
A konferenciának helyet adó épületek közül nekem személy szerint a barcelonai sokkal szimpatikusabb volt. Ott egyetlen hatalmas épület volt, amiben nehezen lehetett eltévedni. Míg a berlini Messe Berlin csarnok valójában egy épületegyüttes, melyben nagyon egyszerű elveszíteni a fonalat, hogy éppen merre is van az arra. Két előadás közötti szünetek nagy része mindig azzal telt el, hogy kerestük a következő termet és elkóboroltunk valahogy oda negyedóra alatt.
Sajnos az idei büdzsé valamivel kisebb volt, mint anno két éve. Ez nagyon meglátszott azon, hogy összevonták az üzemeletető és fejlesztő konferenciát (60/40 arányban voltak az előadások, sajnos), illetőleg a regisztrációkor szokássá vált Tech-Ed táskákat is leváltották szatyrokra. Sajnos idén Company Store se volt jelen, ahol lehetett volna cool MS cuccokat beszerezni vagy akár MsPress könyveket.
A berlini Tech-Ed egyik pozitívuma az volt, hogy sok konferencia utáni (este 7 utáni) eseményt szerveztek. Volt Windows Phone este, volt Magyar est, volt Student Partner est, stb..

Összességében azt mondanám, hogy a rendezvény színvonala sajnos kicsit esett az elmúlt években, de a szakmai értéke cseppet se csorbult. Bár az előadásokat összeválogatni (úgy, hogy főleg cloud és wp7 előadások voltak a developer szekciókban) és rájuk bejutni eléggé nehézkes feladatnak bizonyult, de ha egyszer sikerült bekerülni, akkor sok-sok hasznos információval gazdagodhattunk.

Utolsó utáni gondolatként egy pletykát hadd osszak meg Veletek, mi szerint lehet, hogy jövőre nem biztos, hogy lesz Tech-Ed Europe, ugyanis a franciák nem tudnak megegyezni a németekkel, hogy ki tartsa jövőre a rendezvényt. Hogy ennek mennyi igazság tartalma azt nem tudom, de mindenesetre érdekes lenne, ha 2011-ben Párizs adna otthont a konferenciának.

Ebben a most induló cikksorozatban az elmúlt egy hetes berlini Tech-Ed élményeimet szeretném Veletek megosztani.  A cikkek vagy pontosabban blog-bejegyzések, nem lesznek túl hosszúak, maximum 1 oldalasak. Elsőként magát Berlint szeretném bemutatni Nektek, majd a következő részben a Tech-Ed-et im allgemeinen (overall) + összehasonlítva a két évvel ezelőtti barcelonai változattal, ezek után pedig az érdekesebb előadásokról ejtenék majd néhány szót.

Berlin egy nagyon érdekes város melyet, nagyrészt a történelemi múltjának köszönhet. Alapból két reptérrel rendelkezik, és nem azért mert egy nem lett volna elég a légi forgalom kiszolgálásához, hanem mert ugyebár egy jó ideig két külön országhoz tartozott. Egy másik fontos dolog, amit szintén a világháborúnak köszönhet Berlin az a tágas terek, a jól szervezett közlekedés, illetve a régi és modern épületek kontrasztja. A lebombázás következményeképpen a teljesen újjá épített és újraszervezett város könnyen ki tudta küszöbölni azokat a hibákat, amelyek a mai nagy városokat túlzsúfolttá teszik. A rendkívül hatékonyan megszervezett tömegközlekedésnek köszönhetően sokan használják a BVG-t (az ottani BKV-t), így kevesebben járnak autókkal az utakon. Ennek következtében ritkán fordulnak elő dugok, és sok utat át tudtak alakítani sétálóutcákká. (Mindemellett sok-sok park és tér is tarkítja Berlin térképét.)  Szinte mindenhol van kerékpár sáv is, illetve biciklitároló helyek is találhatóak szerte a városban. A tömegközlekedés oly annyira jól megoldott, hogy az egy hetes kinn tartózkodásunk alatt mindösszesen kétszer használtunk buszt (akkor is a reptér miatt), a többit meg tudtuk oldani S-Bahn-nal (itteni HÉV megfelelője), illetve U-Bahn-nal (a metró ottani neve).
A közlekedési lámpák is egyediek Berlinben ugyanis itt kalapos emberkék jelzik a gyalogosok számára a szabad átkelést vagy éppen a várakozást.

Közlekedési lámpa Kelet-Berlinben

A berliniek ezt annyira magukénak érzik, hogy a szuvenír boltok nagy része ilyen ábrákkal ellátott bóvlikat árul. A másik nagyon menő figura a medve, ami onnan jön, hogy a Berlin-t úgy is le lehet írni, hogy Bärlin (der Bär – németül a medve).

Barlin

Építészet ügyileg a német fővárosban látszólag jól megférnek egymás mellett a régi stílusú múzeumok, templomok és az ultramodern irodaházak, plázák. Számomra viszont teljesen kiábrándító volt látni, hogy a Reichtag-tól (~ Magyarországon a Parlament) kb. 100 méterre egy hatalmas több száz méteres épület komplexum foglalja el a patak túl partját.

 Reichstag és a modern épület komplexum

Egy másik dolog, amiben sajnos csalódnom kellett: a germán nők szépsége.  Ennek persze nagyon sok köze van ahhoz, hogy a németek egy eléggé öreg társadalom, kb. 40-45 év lehet az átlag életkor az általunk vett minta alapján. Plussz az elvétve néha-néha felbukkanó fiatal mädchen-ek sem voltak éppen a korcsoportjuk kiemelkedően hübsch példányai.

Utolsó gondolatként még a német konyhaművészetről emlékeznék meg pár szóban: íztelen és mindemellett wc-re gyakran csalogató ételeik vannak, ellenben nagyon sok finom sörük is van. 

Bár a városról és a német életvitelről még nagyon sok mindent lehetne írni, de akkor sajnos nem férnék bele az 1 oldalas méretkorlátba és ezt különben is látni kell. Én tényleg csak ajánlani tudom mindeninek, mert pénzügyileg sem egy vészes dolog: a repülőjegyeket 30 ezer alatt sikerült beszereznünk (és nem fű alatt) a szállásunk is egy 3 csillagos szállodában volt és 6 napra 2 főre 200 euró környéki összegtől szabadították meg a bankkártyánkat.

Az október 11.-én megrendezésre került MiniMix konferencián szerény személyem is gazdagította az előadók hadát, melynek főattrakciója minden kétséget kizárólag a vizezett sör és a virsli + kolbász páros volt. 😉

Azok, akik esetleg nem tudtak eljönni a MiniMix-re, vagy nem tudtak beülni az összes őket érdeklő előadásra (pl.: időbeni ütközés miatt) azok számára a devportal.hu/tv oldalon (a legördülő listában a Minimix konferencia 2010. október 11. legyen kiválasztva) elérhetőek az előadásokról készült felvételek.

Az előadásomban említettem, hogy elérhetővé teszem a diasort, illetve a demók teljes forráskódját. Hát, most jött el ez a pillanat, íme, a  kb. 200 Mb-nyi adat.

MiniMix 2010 - Adat vizualizáció Silverlight 4-ben

Diasor 

Itt érhető el 

Demó I. – Óra

 Kezdő projekt

Kész projekt

Demó II. – Diagram

 Kezdő projekt

Kész projekt

Demó III. – Pivot

 Forrás I, II

Kezdő projekt I, II

Kész projekt I, II

 

Az összes anyag az alábbi SkyDrive mappán keresztül elérhető: http://cid-8dcaf3b0da4fb828.office.live.com/browse.aspx/MiniMix%20konferencia%20anyagok

Hát ezzel is megvolnék: tartottam diákoknak Silverlight haladó tanfolyamot, olyan témakörökről és technológiákról, melyek egy része rosszul dokumentált.

Ebben a blogbejegyzésben szeretném Veletek megosztani a tapasztalataimat a tanfolyam előkészületeitől egészen a pénteki euforikus érzésig (vége-vége-vége).

 
A történet ott kezdődött, hogy július közepén a számítógépem alaplapja és tápegysége rendkívül intim viszonyba került egymással, konkrétan olyan forróság járta át őket, hogy összeégtek. 

A képhez nem fűznék kommentárt, a lényeg a lényeg, hogy tönkrement a gépem. Már egy ideje gondolkodtam azon, hogy kéne vennem egy notit, ezért úgy láttam éppen itt az ideje, eme beruházás materializálásának. Meg is rendeltem a gépet, amelyre azt mondták, hogy azt követő hét elejére meg is lesz… hát nem így lett… végül is augusztus 3.-ától tudtam használatba venni az én kis DRÁGASZÁGom. Ennek köszönhetően egyik géptől a másikig folyton cápáztam itthon a család számítógépei között, amíg meg nem lett az új gépem. Így át tudtam írni az SL kezdőknek tanfolyam demóit és diáit SL 4.0-ásra. Szerencsére Bálinttól kaptam egy kölcsön notit július legvégén emiatt úgy, ahogy tudtam dolgozgatni. Persze én naivan azt gondoltam, hogy a délelőtti kezdőknek szóló tanfolyam után lesz még annyi lelkierőm délutánonként, hogy dolgozni tudok a haladóknak szóló tanfolyamon. Hát nem így lett…  Így maradt egyetlen hetem arra, hogy összedobjam azt a 6 diasort és azt a több mint 30 demót, amelyet a múlt héten nagyrészt bemutattam a nagyérdeműnek.

Szóval úgy kezdtem el dolgozni AZ ANYAGON, hogy kevés időm volt és egy vadi új gépem. Már az első nap rájöttem, hogy amit kiadtam előzetesen tematikát az át kell gondolnom, mivel ennyit nem lett volna időm normálisan kidolgozni. Így hát kiválogattam a szerintem érdekesebb és valamilyen szinten nehéz témaköröket, majd átírtam a tanfolyam címét. Erre azért volt szükség, mivel nem egy Silverlight dig deep into tanfolyamot készültem összeállítani (pl.: Adatkötés alfától omegáig és tovább), hanem egy olyat, ahol olyan technológiákat mutatok be, melyek Silverlight alapúak és az üzleti alkalmazások fejlesztésénél nyújtanak nagy segítséget. Ezért lett a tanfolyam új neve: Silverlight-os Technológiák haladóknak.

Ezek után jött a témakörök szintjeinek meghatározása. Mivel tudtam, hogy az SL kezdőknek tanfolyamról is fognak jönni egy jó páran (őket hívtam a tanfolyamon „A bátraknak”), ezért nem mehettem túlságosan mély szintre. (Bár alapból nem is terveztem, de ez így jó kifogásnak tűnt  ) Emiatt megpróbáltam belőni a LEVEL 300-at, vagyis bevezető előadást akartam tartani az egyes technológiákról, de úgy, hogy azért néhány dolgot feltételezek a hallgatóságról. Konkrétan minimális SL (pl.:  ha azt mondom, hogy kétirányú adatkötés, akkor ne pislogjanak) és szoftvertechnológiai alapismeretek. Utóbbira azért volt szükség, mert eléggé sok ott használt fogalmat szerettem volna felhasználni a tanfolyam során. (pl.: Design Pattern, Inversion Of Control, N-tier, MVC, szoftver életciklus, stb.) Végülis úgy döntöttem ezeket is bele veszem az anyagba, mert nem árt, ha össze vannak foglalva egy-egy dián. Továbbá az anyag összeállítása során fontosnak tartottam, hogy legyenek az egyes témakörök végén egyéni munkák, hogy a hallgatók el tudják mélyíteni a tudásukat, így a neten eléggé sokat keresgéltem megfelelő step-by-step-ek után (amiket aztán a tanfolyamon skip-eltünk , de erre majd mindjárt visszatérek).

Most, hogy már minden megvolt az előkészületek közül, így neki is vágtam a demók és diák gyártásának. (Ekkor már megvoltak a témakörök, de a sorrend még nem). Elsőként a MEF-hez készítettem el a demókat, amivel eléggé gyorsan haladtam, mivel semmilyen problémába nem futottam bele. Utána jött a diakészítés, aminél a legnagyobb fejtörést, mint mindig, az adott technológia bevezetése okozta. Így a slideshare oldaláról sok-sok diasort leszedve válogattam össze a megfelelő ábrákat. Bár tudom eléggé hihetetlennek hangzik, de nem rendelkezek túl nagy önbizalommal, vagy pontosabban fogalmazva szeretem, ha megerősítenek abban, amit csinálok, hogy az jó. Ezért Árvai Zoli barátomat megkértem, hogy néhány diasoromat legyen szíves nézze át. Ez utónis köszönöm a sok-sok javaslatot és véleményt, amellyel elősegítette, hogy a diasorok teljesebbek és precízebbek legyenek.

A többi diasor és demóalkalmazás elkészítése nagyrészt gördülékenyen ment kivéve az MVVM és a Dynamic Languages  (dinamikus nyelvek) részt. Ezekkel a fő probléma az, hogy nincsen egy hivatalos forrás, ahonnan meg lehet őket jól tanulni, így nincs egy tematika, ami alapján érdemes lehet végig venni az egyes feature-öket. Emiatt az MVVM-nél én inkább magára a DI-re és a többi technológiával való együttműködésre fektettem a nagyobb hangsúlyt, nem pedig pl.: a ViewModel-ek között kommunikációra, a validációra, stb.. „Ahány előadó, annyiféle megközelítés”.
A dinamikus nyelvekkel kapcsolatban a legnagyobb problémát az okozta, hogy meghatározzam, melyik nyelvből, mennyit kéne ahhoz bemutatni, hogy se ne legyen túl felületes, se ne legyen túl mély. (Eléggé nehéz megtalálni, ezt az arany középutat.) Ja és persze a legjobb az egészben, hogy 2 nyelv alapjait kell bemutatni + dinamikus nyelvi koncepciókat + mindez, hogyan illik bele az SL programozási modellbe és milyen előnyeink származnak ebből. Szóval summázva, nem egy egyszerű dolog. (Dynamic Langauges-re még a blogbejegyzés végén visszatérek).

Augusztus 8.-án befejeztem a diagyártást és félve bár, de úgy saccoltam, hogy az összeállított anyagokat kb. 4 nap alatt le tudom adni. Hát itt is sikerült eléggé alul becsülnöm magam , ugyanis az első három nap alatt vettük végig az első két témakört (WCF Duplex Communication, WCF Data Services (+ REST)). A maradék két napra maradt 4 diasor, amit végül lecsökkentettem háromra (Dynamic Languages-t kivettem a repertoárból).  Ennek persze egyrészt én vagyok az oka, hiszen eléggé sokat meséltem mindenféle silverlight-os és szoftver technológiai dolgokról, hogy még tisztább legyen a kép egy-egy adott technológia kapcsán. Másrészről viszont a hallgatóság is közrejátszott a tanfolyam haladási sebességének alakulásában. Voltak egy páran, akik még nem érezték teljesen magukénak a XAML szintaxisát vagy a WCF-es proxy használatát, így eléggé sok elírás és rossz helyre kattintás miatt, sok időt kellett eltöltenem a problémák kibogozásával. Ne értsétek félre ez nem probléma, csak ha a résztvevőknek nagyobb tapasztalata lett volna, és nagyobb biztonsággal tudtak volna mozogni a Silverlight-ban, akkor egy kicsit gyorsabban is haladhattunk volna. (Illetőleg, akik értettek hozzá azok nem unatkoztak volna). A kicsit lassabb haladás miatt sajnos ki kellett vennem az egyéni laborgyakorlatokat, ami szerintem fontos, de végülis, aki akar vele a későbbiekben foglalkozni úgy is végig fog csinálni egy jó pár ilyet…

Összességében nekem azt sikerült levonnom a tanfolyamból az anyagokat illetően, hogyha a WCF Duplex kommunikációt kihagyom és esetleg a WCF Data Services elején kevesebbet foglalkozok a REST-es résszel, akkor az 5 napba ilyen tempóval belefér az 5 témakör (-duplex, -rest, + dynamic). Bár nehezen mondok le a Duplex-ről, mert én eléggé sokat foglalkoztam vele és (aki olvasgatja a blog-omat, az tudja, miről beszélek), de végülis a többi technológia sokkal gyakrabban előfordul az üzleti alkalmazásoknál, mint ez, ezért ezt a jövőben lehet csak a kispadon tartom.

Ami magát a tanfolyam egészét illeti, nekem nagyon pozitív benyomásom van róla, ugyanis sok értelmes ember verődött ott össze, akik közül sokan mertek kérdezni is . A végén kitöltetett értékelő lap alapján nagyon jó visszajelzéseket kaptam, amelyet köszönök szépen.  Szóval én úgy értékelném a tanfolyamot, hogy jól sikerült, és remélhetőleg a résztvevők is sokat tanultak belőle.

Ami pedig a jövőt illeti. Egyrészt úgy tervezem, hogy a kimaradt dinamikus nyelvekből, majd készítek valamikor egy rövid screencast sorozatot, amely bemutatja az alapjait az egésznek. (Lehet, hogy a videók angol nyelvűek lesznek, de egyelőre még nem döntöttem el. Szerintetek milyen nyelvű legyen? Mivel nincs túl sok ilyen témájú angol nyelvű videó, ezért én eléggé erőteljesen hajlok afelé, hogy ilyet készítsek, így széles körben is tudom terjeszteni az igét ). Másrészt pedig a jövőben előfordulhat, hogy több magasabb szintű (level 300-400) tanfolyamot vagy előadását fog tartani az MS. Remélem a többi előadó is látja ennek a szükségességét .
 

Akinek bármilyen véleménye, megjegyzése, esetleg ötlete van a tanfolyammal, a diasorokkal, a demókkal kapcsolatban kérem, ossza meg velem, hogy a jövőben még jobbá tudjam alakítani őket. Előre is köszönöm a segítségeteket.

A mai nap anyagait a SkyDrive-on a 5. nap mappában találhatjátok meg.

Mai nap anyagai

1. diasor – Managed Extensibility (teljes)

Prezentáció

Demo 2 – ImportMany

Demo 3 – Catalogs

Demo 4 – Recomposition

Demo 5 – MetaData + Lazy-loading

Demo 6 – Dynamic Loading

 

2. diasor – Model View ViewModel

Prezentáció

Demo 1 – Code-Behind kód átírása MVVM stílusúra

Demo 2 – MVVM + WCF-es webszolgáltatás

Demo 3 – Command használata

Demo 4 – MVVM Light Toolkit

Demo 5 – Unit Testing

Demo 6 – WCF RIA Services + MVVM + MEF

 

A holnapi nap összedobok egy rövid blog bejegyzés a tapasztalaimról, illetve a jövővel kapcsolatban!