You are on page 1of 288

A knyv nyomtatott verzija megvsrolhat a knyvesboltokban, s a kiad webruhzban: www.joskiado.

hu

Reiter Istvn

C# programozs lpsrl lpsre

JEDLIK OKTATSI STDI Budapest, 2012

Minden jog fenntartva. A szerz s a kiad a knyv rsa sorn trekedtek arra, hogy a lert tartalom a lehet legpontosabb s napraksz legyen. Ennek ellenre elfordulhatnak hibk, vagy bizonyos informcik elavultt vlhattak. A pldkat s a mdszereket mindenki csak sajt felelssgre alkalmazhatja. Javasoljuk, hogy felhasznls eltt prblja ki s dntse el sajt maga, hogy megfelel-e a cljainak. A knyvben foglalt informcik felhasznlsbl fakad esetleges krokrt sem a szerz, sem a kiad nem vonhat felelssgre. Az oldalakon elfordul mrka- valamint kereskedelmi vdjegyek bejegyzjk tulajdonban llnak.

Reiter Istvn, 2012

Bort: Varga Tams Anyanyelvi lektor: Dr. Bonhardtn Hoffmann Ildik

Kiad: Jedlik Oktatsi Stdi Kft. 1215 Budapest, v u. 8-12. Internet: http://www.jos.hu E-mail: jos@jos.hu Felels kiad: a Jedlik Oktatsi Stdi Kft. gyvezetje

Nyomta: LAGrade Kft. Felels vezet: Szutter Lnrd

ISBN: 978-615-5012-17-4 Raktri szm: JO-0340

ELSZ
Amikor 2008. augusztusban elkszltem ennek a knyvnek az els vltozatval mg problmt okozott tlpni a bvs 100 oldalas hatrt. Ha jl emlkszem, vgl 108 oldal lett, szerencsre mr csak az Internet egy stt sarkban lehet megtallni. rdekes mdon ezttal is az oldalszm okozott gondot, igaz, fordult a kocka: valamivel tbb mint 300 oldalt kellett kitltenem, nmi kompromisszum rn sikerrel is jrtam. Ha jobban belegondolok, az elmlt 4 vem alapjaiban hatrozta meg ez a knyv. Rengeteget ksznhetek neki, de szintn szlva nha gylltem is. Valjban most is ezt teszem, hiszen jjel 11 van s lmos vagyok. 4 v alatt elkpeszt vltozson esett t az informatika vilga. Ebben a pillanatban is jabb csodk szletnek. Borzalmasan nehz lpst tartani a technolgia vgtelen evolcijval, szinte lehetetlennek ltsz feladat. Ennek a knyvnek a clja, hogy olyan alapokat nyjtson, amelyre nyugodtan tmaszkodhatunk a kalandos ton, amelyet szoftverfejlesztsnek hvnak. Tbb mint 40000 olvas tbbszz jtancsnak, visszajelzsnek eredmnye az amit most a kezedben tartasz! Nem fogok hazudni, ez nem egy mindentud lexikon s nem is csodalmpa, amit megdrzslve azonnal fejedbe szll a tuds - br prblkozni lehet. Tekintsd trsnak, jbartnak, aki utat mutat, ha elakadtl. Remlem, pp annyi rmet okoz majd Neked, mint nekem tette! Ktelessgem ksznetet mondani mindazoknak, akik hozzjrultak a knyv ltrejtthez! Sajnos annyi helyem nincs, hogy mindenki elfrjen, mgis szeretnm kiemelni nhnyukat: Kovcs Gyula s Balzs Gbor tanr uraknak, a gymri Teleki Lszl Gimnzium s Informatikai Szakkzp iskolbl. Lipp Szabolcsnak s a Microsoft Magyarorszgnak, ksznm a lehetsget, remlem hamarosan elrjk a hatszmjegy letltsszmot is! Ecsegi Szandrnak, hogy vgig mellettem llt, rmutatott olyan dolgokra, amik nekem eszembe sem jutottak. Ksznm. A knyv s a hozztartoz programozsi pldk a weben is elrhetek a Devportal.hu oldalon.

Talms, 2012. augusztus

Reiter Istvn

TARTALOMJEGYZK
MICROSOFT .NET FRAMEWORK ___________________________________________________________ 15
A .NET platform ___________________________________________________________________________ 15 MSIL/CIL _________________________________________________________________________________ 15 Fordts s futtats ________________________________________________________________________ 16 BCL _____________________________________________________________________________________ 16 A C# programozsi nyelv ____________________________________________________________________ 16 Alternatv megoldsok _____________________________________________________________________ 16

ISMERKEDNK A NYELVVEL _______________________________________________________________ 18


Visual Studio _____________________________________________________________________________ 18 Hello World! ______________________________________________________________________________ 18 A C# szintaktikja __________________________________________________________________________ 21 Kulcsszavak ______________________________________________________________________________ 22 Megjegyzsek_____________________________________________________________________________ 22 Nvterek _________________________________________________________________________________ 23

VLTOZK ____________________________________________________________________________________ 24
Deklarci s definci______________________________________________________________________ 24 Tpusok __________________________________________________________________________________ 24 Loklis s globlis vltozk __________________________________________________________________ 25 Referencia- s rtktpusok _________________________________________________________________ 25 Referencik ______________________________________________________________________________ 27 Boxing s unboxing ________________________________________________________________________ 28 Konstansok _______________________________________________________________________________ 30 A felsorolt tpus ___________________________________________________________________________ 30 Null tpusok ______________________________________________________________________________ 32 A dinamikus tpus _________________________________________________________________________ 33

OPERTOROK ________________________________________________________________________________ 35
Opertor precedencia ______________________________________________________________________ 35 rtkad opertor _________________________________________________________________________ 36 Matematikai opertorok ____________________________________________________________________ 36 Relcis opertorok________________________________________________________________________ 37 Logikai s feltteles opertorok ______________________________________________________________ 37 Bit opertorok ____________________________________________________________________________ 40 Rvid forma ______________________________________________________________________________ 43 Egyb opertorok _________________________________________________________________________ 43

VEZRLSI SZERKEZETEK__________________________________________________________________ 46
Szekvencia _______________________________________________________________________________ 46 Elgazs _________________________________________________________________________________ 46 Ciklus ___________________________________________________________________________________ 51

GYAKORL FELADATOK____________________________________________________________________ 57
Szorztbla _______________________________________________________________________________ 57 Szmolgp ______________________________________________________________________________ 60 K Papr Oll___________________________________________________________________________ 62 Szmkitall jtk _________________________________________________________________________ 64

TPUSKONVERZIK _________________________________________________________________________ 68
Ellenrztt konverzik______________________________________________________________________ 68 Is s as __________________________________________________________________________________ 69 Karakterkonverzik ________________________________________________________________________ 70

TMBK ______________________________________________________________________________________ 71
Tbbdimenzis tmbk _____________________________________________________________________ 72

STRINGEK _____________________________________________________________________________________ 75
Metdusok _______________________________________________________________________________ 76 StringBuilder _____________________________________________________________________________ 77

Regulris kifejezsek _______________________________________________________________________ 78

GYAKORL FELADATOK II. ________________________________________________________________ 81


Minimum- s maximumkeress ______________________________________________________________ 81 Szigetek _________________________________________________________________________________ 81 tlaghmrsklet _________________________________________________________________________ 82 Buborkrendezs __________________________________________________________________________ 83

OBJEKTUM-ORIENTLT PROGRAMOZS - ELMLET __________________________________ 85


UML ____________________________________________________________________________________ 85 Osztly __________________________________________________________________________________ 85 Adattag s metdus________________________________________________________________________ 85 Lthatsg _______________________________________________________________________________ 86 Egysgbezrs ____________________________________________________________________________ 86 rklds ________________________________________________________________________________ 86

OSZTLYOK __________________________________________________________________________________ 88
Konstruktorok ____________________________________________________________________________ 89 Adattagok ________________________________________________________________________________ 91 Lthatsgi mdostk _____________________________________________________________________ 92 Parcilis osztlyok _________________________________________________________________________ 92 Begyazott osztlyok _______________________________________________________________________ 94 Objektuminicializlk ______________________________________________________________________ 95 Destruktorok _____________________________________________________________________________ 95

METDUSOK ________________________________________________________________________________ 103


Paramterek ____________________________________________________________________________ 105 Visszatrsi rtk _________________________________________________________________________ 110 Kiterjesztett metdusok ___________________________________________________________________ 111

TULAJDONSGOK___________________________________________________________________________ 113 INDEXELK __________________________________________________________________________________ 115 9

STATIKUS TAGOK __________________________________________________________________________ 117


Statikus adattag __________________________________________________________________________ 117 Statikus konstruktor ______________________________________________________________________ 118 Statikus metdus _________________________________________________________________________ 119 Statikus tulajdonsg ______________________________________________________________________ 120 Statikus osztly __________________________________________________________________________ 120

STRUKTRK _______________________________________________________________________________ 121


Konstruktor _____________________________________________________________________________ 121 Destruktor ______________________________________________________________________________ 122 Adattagok _______________________________________________________________________________ 123 Hozzrendels ___________________________________________________________________________ 123 rklds _______________________________________________________________________________ 125

OSZTLYKNYVTRAK ___________________________________________________________________ 126 GYAKORL FELADATOK III. ______________________________________________________________ 130


Faktorilis s hatvny _____________________________________________________________________ 130 Gyorsrendezs ___________________________________________________________________________ 131 Lncolt lista _____________________________________________________________________________ 133 Binris keresfa __________________________________________________________________________ 134

RKLDS _________________________________________________________________________________ 138


Virtulis metdusok ______________________________________________________________________ 139 Polimorfizmus ___________________________________________________________________________ 141 Lezrt osztlyok s metdusok ______________________________________________________________ 142 Absztrakt osztlyok _______________________________________________________________________ 142

INTERFSZEK _______________________________________________________________________________ 145


Explicit interfszimplementci _____________________________________________________________ 147 Virtulis tagok ___________________________________________________________________________ 148

10

OPERTOR KITERJESZTS ________________________________________________________________ 150


Egyenlsg opertorok ____________________________________________________________________ 151 A ++/-- opertorok ________________________________________________________________________ 152 Relcis opertorok_______________________________________________________________________ 153 Konverzis opertorok ____________________________________________________________________ 153

KIVTELKEZELS ___________________________________________________________________________ 154


Kivtel hierarchia _________________________________________________________________________ 156 Kivtel ksztse _________________________________________________________________________ 156 Kivtelek tovbbadsa ____________________________________________________________________ 157 Finally blokk _____________________________________________________________________________ 157

GYAKORL FELADATOK IV. _______________________________________________________________ 159


IEnumerator s IEnumerable _______________________________________________________________ 159 IComparable s IComparer _________________________________________________________________ 160 Mtrix tpus _____________________________________________________________________________ 162

DELEGATE ___________________________________________________________________________________ 164


Paramter s visszatrsi rtk _____________________________________________________________ 167 Nvtelen metdusok ______________________________________________________________________ 168

GENERIKUSOK ______________________________________________________________________________ 174


Generikus metdusok _____________________________________________________________________ 174 Generikus osztlyok_______________________________________________________________________ 175 Generikus megszortsok __________________________________________________________________ 177 rklds _______________________________________________________________________________ 178 Statikus tagok ___________________________________________________________________________ 179 Generikus gyjtemnyek___________________________________________________________________ 179 Generikus interfszek, delegateek s esemnyek ______________________________________________ 184 Kovariancia s kontravariancia ______________________________________________________________ 184

11

LAMBDA KIFEJEZSEK _____________________________________________________________________ 187


Generikus kifejezsek _____________________________________________________________________ 187 Kifejezsfk _____________________________________________________________________________ 189 Lambda kifejezsek vltozinak hatkre _____________________________________________________ 189 Nvtelen metdusok kivltsa lambda kifejezsekkel ___________________________________________ 190

UNSAFE KD ________________________________________________________________________________ 192


Fix objektumok __________________________________________________________________________ 194 Natv DLL kezels _________________________________________________________________________ 195

TBBSZL ALKALMAZSOK ____________________________________________________________ 196


Application Domain -ek ____________________________________________________________________ 198 Szlak __________________________________________________________________________________ 198 Aszinkron delegate-ek _____________________________________________________________________ 199 Szlak ltrehozsa ________________________________________________________________________ 202 Foreground s background szlak____________________________________________________________ 204 Szinkronizci ___________________________________________________________________________ 205 ThreadPool ______________________________________________________________________________ 208

PRHUZAMOS PROGRAMOZS TASK PARALLEL LIBRARY ________________________ 210


Tbbszlsg vs. Prhuzamossg ____________________________________________________________ 210 Teljestmny _____________________________________________________________________________ 210 Prhuzamos ciklusok ______________________________________________________________________ 211 Parallel.Invoke ___________________________________________________________________________ 215 Task____________________________________________________________________________________ 217 Async/Await _____________________________________________________________________________ 219

REFLECTION _________________________________________________________________________________ 220 LLOMNYKEZELS _______________________________________________________________________ 221


Olvass/rs fjlbl/fjlba __________________________________________________________________ 221 Knyvtrstruktra kezelse ________________________________________________________________ 224

12

Inmemory streamek _____________________________________________________________________ 226 XML____________________________________________________________________________________ 226 XML DOM _______________________________________________________________________________ 229 XML szerializci _________________________________________________________________________ 231

KONFIGURCIS FJL HASZNLATA ____________________________________________________ 233


Konfigurci-szekci ksztse ______________________________________________________________ 234

HLZATI PROGRAMOZS _______________________________________________________________ 237


Socket __________________________________________________________________________________ 237 Blokk elkerlse __________________________________________________________________________ 244 Tbb kliens kezelse ______________________________________________________________________ 246 TCP s UDP ______________________________________________________________________________ 251

LINQ TO OBJECTS ___________________________________________________________________________ 252


Nyelvi eszkzk __________________________________________________________________________ 252 Kivlaszts ______________________________________________________________________________ 253 Szrs __________________________________________________________________________________ 257 Rendezs _______________________________________________________________________________ 259 Csoportosts ____________________________________________________________________________ 260 Listk sszekapcsolsa_____________________________________________________________________ 263 Outer join _______________________________________________________________________________ 265 Konverzis opertorok ____________________________________________________________________ 265 Element opertorok _____________________________________________________________________ 267 Halmaz opertorok _______________________________________________________________________ 268 Aggregt opertorok ______________________________________________________________________ 269 PLINQ Prhuzamos vgrehajts ____________________________________________________________ 270

GRAFIKUS FELLET ALKALMAZSOK WINDOWS FORMS ________________________ 274


Hello Windows Forms ___________________________________________________________________ 274 Vezrlk ________________________________________________________________________________ 278

13

GYAKORL FELADATOK V.________________________________________________________________ 286


Szmolgp _____________________________________________________________________________ 286 Szvegszerkeszt _________________________________________________________________________ 287

14

MICROSOFT .NET FRAMEWORK


A kilencvenes vek kzepn a Sun MicroSystems kiadta a Java platform els nyilvnos vltozatt. Az addigi programnyelvek/platformok klnbz okokbl nem tudtk felvenni a Java-val a versenyt, gy szmtalan fejleszt dnttt gy, hogy a knyelmesebb s sokoldalbb Java-t vlasztja. Vlaszkpp a Microsoft a kilencvenes vek vgn elindtotta a Next Generation Windows Services fednev projektet, amelybl aztn megszletett a .NET, ami a kiss elavult s nehzkesen programozhat COM platformot hivatott levltani (ettl fggetlenl a COM ma is ltez, viszonylag npszer eszkz ez fleg a hatalmas szoftverbzisnak ksznhet, minden Windows rendszernek rszt kpezi, s szmos .NET knyvtr is pt r). Az vek folyamn a .NET Framework szinte teljesen tvette az uralmat a Microsoft fejleszti platformpalettjn. Ott van a hagyomnyos asztali alkalmazsokban (Windows Forms, WPF), a weben (ASP.NET, ASP.NET MVC, Silverlight) s az okostelefonokon is (Windows Phone). Nem hagyhatjuk sz nlkl azt sem, hogy az egyes technolgik kztt meglehetsen egyszer az tjrs, elg csak a WPF-Silverlight-Windows Phone triumvirtusra gondolni.

A .NET PLATFORM
Maga a .NET platform a Microsoft, a Hewlett Packard, az Intel s msok kzremkdsvel megfogalmazott CLI (Common Language Infrastructure) egy implementcija. A CLI egy szablyrendszer, amely maga is tbb rszre oszlik: A CTS (Common Type System) az adatok kezelst, a memriban val megjelenst, az egymssal val interakcit stb. rja le. A CLS (Common Language Specification) a CLI kompatibilis nyelvekkel kapcsolatos elvrsokat tartalmazza. A VES (Virtual Execution System) a futsi krnyezetet specifiklja, nevezik CLR -nek (Common Language Runtime) is.

ltalnos tvhit, hogy a VES/CLR t virtulis gpknt azonostjk. Ez abbl a szintn tves elkpzelsbl alakult ki, hogy a .NET ugyanaz, mint a Java, csak Microsoft kntsben. A valsgban nincs .NET virtulis gp, helyette n. felgyelt (vagy managed) kdot hasznl, vagyis a program teljes mrtkben natv mdon, kzvetlenl a processzoron fut, mellette pedig ott a keretrendszer, amely felels pl. a memriafoglalsrt vagy a kivtelek kezelsrt. A .NET nem programozsi nyelv, hanem krnyezet. Gyakorlatilag brmelyik programozsi nyelvnek lehet .NET implementcija. Jelenleg kb. 50 nyelvnek ltezik hivatalosan .NET megfelelje, nem beszlve a szmtalan hobbifejlesztsrl.

MSIL/CIL
A hagyomnyos programnyelveken mint pl. a C++ megrt programok n. natv kdra fordulnak le, vagyis a processzor szmra kis tlzssal azonnal rtelmezhetek. A .NET (akrcsak a Java) ms ton jr, a fordt elszr egy kztes nyelvre (Intermediate Language) fordtja le a forrskdot. Ez a nyelv a .NET vilgban az MSIL, illetve a szabvnyosts utn a CIL (MICROSOFT/CommonIL) klnbsg csak az elnevezsben van. Jogos a krds, hogy a kt mdszer kzl melyik a jobb? Ha nagy ltalnossgban beszlnk, akkor a vlasz az, hogy nincs kztk klnbsg. Igaz, hogy a natv nyelvek hardver-kzelibbek s emiatt gyorsabbak tudnak lenni, viszont ez tbb hibalehetsggel is jr, amelyek elkerlse a felgyelt krnyezetben kiegyenlti az eslyeket. Bizonyos terleteken viszont egyik vagy msik megkzelts jelents eltrst eredmnyezhet. J plda a szmtgpes grafika, ahol a natv nyelvek vannak elnyben pont azrt, mert az ilyen szmtsignyes

15

feladathoz minden csepp erforrst ki kell prselni a hardverbl. Msfell a felgyelt krnyezet a hatkonyabb memriakezels miatt jobban teljest olyan helyzetekben, ahol nagy mennyisg adatot mozgatunk a memrin bell (pl. szmos rendez algoritmus ilyen).

FORDTS S FUTTATS
A natv programok n. gpi kdra fordulnak le, mg a .NET forrskdokbl egy CIL nyelv futtathat llomny keletkezik. Ez a kd a felteleptett .NET Framework nek szl utastsokat tartalmaz. Amikor futtatjuk ezeket az llomnyokat, elszr az n. JIT (JustInTime) fordt veszi kezelsbe, s lefordtja ket gpi kdra, amit a processzor mr kpes kezelni. Amikor elszr fordtjuk le a programunkat, akkor egy n. Assembly (vagy szerelvny) keletkezik. Ez tartalmazza a felhasznlt, illetve megvalstott tpusok adatait (ez az n. Metadata), amelyek a futtat krnyezetnek szolglnak informcival (pl. osztlyok szerkezete, metdusai, stb.). Egy Assembly egy vagy tbb fjlbl is llhat, tipikusan .exe (futtathat llomny) vagy .dll (osztlyknyvtr) kiterjesztssel.

BCL
A .NET Framework teleptsvel a szmtgpre kerl tbbek kztt a BCL (Base Class Library), ami az alapvet feladatok (fjl olvass/ rs, adatbzis-kezels, adatszerkezetek stb) elvgzshez szksges eszkzket tartalmazza. Az sszes tbbi knyvtr (ADO.NET, WCF, stb) ezekre pl.

A C# PROGRAMOZSI NYELV
A C# (ejtsd: sz-srp) a Visual Basic mellett a .NET f programozsi nyelve. 1999 ben Anders Hejlsberg vezetsvel kezdtk meg a fejlesztst. A C# tisztn objektumorientlt, tpusbiztos, ltalnos felhasznls nyelv. A tervezsnl a lehet legnagyobb produktivits elrst tartottk szem eltt. A nyelv elmletileg platform fggetlen (ltezik Linux s Mac fordt is), de napjainkban a legnagyobb hatkonysgot a Microsoft implementcija biztostja.

ALTERNATV MEGOLDSOK
A Microsoft .NET Framework jelen pillanatban csak s kizrlag Microsoft Windows opercis rendszerek alatt rhet el. Ugyanakkor a szabvnyosts utn a CLI specifikci nyilvnos s brki szmra elrhet lett, ezen ismeretek birtokban pedig tbb fggetlen csapat vagy cg is ltrehozta a sajt CLI implementcijt, br eddig mg nem sikerlt teljes mrtkben reproduklni az eredetit. Ezen cljukat nehezti, hogy a Microsoft idkzben szmos, a specifikciban nem szerepl vltoztatst vgzett a keretrendszeren.
A hivatalosnak ECMA szabvny nem felttlenl tekinthet tkletes tmutatnak a keretrendszer megrtshez, nhol jelents eltrsek vannak a valsghoz kpest. Ehelyett ajnlott a C# nyelv fejleszti ltal ksztett C# referencia, amely br nem elssorban a .NET hez kszlt rtkes informcikat tartalmaz. SSCLI Az SSCLI (Shared Source Common Language Infrastructure) vagy korbbi nevn Rotor a Microsoft ltal fejlesztett nylt forrs, keresztplatformos vltozata a .NET Frameworknek (teht nem az eredeti lebuttott vltozata). Az SSCLI Windows, FreeBSD s Mac OSX rendszereken is fut.

16

Az SSCLIt kimondottan tanulsi clra ksztette a Microsoft, ezrt a licence engedlyez mindenfajta mdostst, egyedl a piaci rtkestst tiltja meg. Ez a rendszer nem szolgltatja az eredeti keretrendszer teljes funkcionalitst, jelen pillanatban valamivel a .NET 2.0 mgtt jr. Az SSCLI projektet mr nem fejleszti tovbb a Microsoft, ettl fggetlenl a forrskd s a hozz tartoz dokumentcik rendelkezsre llnak, letlthetek a kvetkez webhelyrl: http://www.microsoft.com/downloads/details.aspx?FamilyId=8C09FD61-3F26-4555-AE173121B4F51D4D&displaylang=en Mono A Mono projekt szlatyja Miguel de Icaza, 2000ben kezdte meg a fejlesztst, s egy vvel ksbb mutatta be az els kezdetleges C# fordtt. A Ximian (amelyet Icaza s Nat Friedman alaptott) felkarolta az tletet, s 2001 jliusban hivatalosan is elkezddtt a Mono fejlesztse. 2003 ban a Novell felvsrolta a Ximian t, az 1.0 verzi mr Novell termkknt kszlt el egy vvel ksbb. 2011-ben az Attachmate felvsrolta a Novell-t, ezzel egytt pedig tbb szz fejleszttl belertve a Mono csapatot is megvltak. A Mono jvje ekkor ktsges volt, de Icaza alig egy hnapon bell j cget alaptott Xamarin nven, amely tvette a projektet. A Mono pillanatnyilag a 2.10.8 verziszmnl jr, tmogatja a C# 4.0 nyelvi elemeit, illetve megvalstja a Microsoft-fle BCL nagy rszt. A Mono elrhet Windows, Linux, UNIX, BSD, Mac OSX s Solaris rendszereken is. Napjainkban a Mono mutatja a leggretesebb fejldst, mint a Microsoft .NET jvbeli ellenfele, illetve keresztplatformos trsa. A Mono emblmja egy majmot brzol, a sz ugyanis spanyolul majmot jelent. A Mono hivatalos oldala: http://www.mono-project.com/Main_Page DotGNU A DotGNU a GNU projekt rsze, amelynek clja egy ingyenes s nylt alternatvt nyjtani a Microsoft implementci helyett. Ez a projekt szemben a Mono val nem a Microsoft BCL lel val kompatibilitst helyezi eltrbe, hanem az eredeti szabvny pontos s tkletes implementcijnak a ltrehozst. A DotGNU sajt CLI megvalstsnak a Portable .NET nevet adta. A DotGNU projektet mr nem fejlesztik tovbb. A projekt hivatalos oldala: http://www.gnu.org/software/dotgnu/

17

ISMERKEDNK A NYELVVEL
Mieltt elkezdennk hossz utunkat, fel kell fegyverkeznnk a megfelel fejleszteszkzzel! A .NET programozshoz a legjobb vlaszts a Microsoft sajt termke, a Visual Studio. A nagy fizets vltozatok (Professional, Ultimate) mellett ltezik az ingyenes Express csald is, amelyek szinte teljes kr szolgltatst nyjtanak. Az Express vltozatok klnlegessge, hogy a nagy vltozatokkal ellenttben csak egy-egy rszterletre koncentrlnak, kln vltozat van a webes s asztali alkalmazsok ksztshez.

VISUAL STUDIO
A jegyzetben a Visual C# Express 2010-et hasznljuk, br jelen pillanatban mr ltezik a Visual Studio 11 is, igaz, csak bta vltozatban. Nem kell azonban aggdni, a Visual Studio kifejezetten konzisztens felpts maradt az elmlt vekben, aki az egyik vltozatot tudja hasznlni, az a tbbivel is elboldogul. A Visual C# Express 2010 az albbi webhelyrl tlthet le: http://www.microsoft.com/visualstudio/en-us/products/2010-editions/visual-csharp-express A Visual C# Express a telepts utn 30 napig hasznlhat, utna regisztrcis kdot fog krni. Ehhez mindssze egy Live ID szksges (a megadott e-mail cm), ezutn kapunk kdot, amelyet berva immr tovbbi elfelttelek nlkl tetszs szerint hasznlhatjuk a programot. A Visual Studio elindtsakor tegyk a kvetkezt: 1. 2. Tools men -> Settings A Basic Settings helyett vlasszuk az Expert Settings belltst!

Ez az egyszer lps a ksbbiekben mg hasznunkra vlik majd.

HELLO WORLD!
Ksztsk el els C# nyelven rt programunkat! A hres Hello World! program elsknt Dennis Ritchie s Brian Kernighan A C programozsi nyelv cm knyvben jelent meg, s azta szinte hagyomny, hogy egy programozsi nyelv bevezetjeknt ezt a programot mutatjk be. A File menben kattintsunk a New Project menpontra, ekkor a megjelen ablakban kivlaszthatjuk a projekt tpust. A jegyzetben nagyrszt Console Application sablont fogunk hasznlni, ezzel egy parancssorban fut programot hozhatunk ltre.

18

Az OK gombra kattintva elkszl az els projektnk, a forrskd gy nz ki: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestApp { class Program { static void Main(string[] args) { } } } Meglepen sok mindent ltunk, ahhoz kpest, hogy ez a program a vilgon semmit nem csinl. Menjnk sorjban, egyelre klnsebb magyarzat nlkl, a kvetkez fejezetekben mindenre fny derl. Az els ngy sorral azokat a nvtereket jelltk ki, amelyeket hasznlni szeretnnk. Ebben az esetben a Visual Studio egy kicsit tlbuzg volt, egyelre az els sor is elg lett volna, de nyugodtan hagyjunk mindent, ahogy van, mg nincs jelentsge. A kvetkez lpsben megadjuk a programunk nvtert, ez alaprtelmezs szerint az lesz, amit a New Project ablakban a Name mezben megadtunk. A class Program sor az, ami egy kicsit ijeszt lehet a kezd programoz szmra. A C# tisztn objektumorientlt nyelv, ami egyelre azt jelenti szmunkra, hogy brmit tesznk, azt csakis egy osztlyon bell egy fggvnyben vagy metdusban tehetjk. Egy osztlyt a class kulcsszval vezetnk be, amely utn az osztly nevt kell rnunk. Ebben a pillanatban mg nem kell ennl tbbet tudnunk az objektumorientlt programozsrl, de nemsokra ennek is eljn az ideje.

19

Nem maradt ms htra, mint a Main fggvny. Ez nagyon fontos sszetevje minden C# programnak, hiszen ez az alkalmazsunk belpsi pontja, itt kezddik el a futsa. Minden C# programnak tartalmaznia kell egy Main nev fggvnyt, ellenkez esetben le sem fordul. Vizsgljuk meg egy kicsit a fejleszteszkzt is!

Bal oldalon a forrskdot lthatjuk, mg a jobb oldalt az n. Solution Explorer foglalja el. Minden esetben, amikor egy projektet ksztnk, egy n. Solution jn ltre. Egy Solution tbb projektet is tartalmazhat, jobb gombbal kattintva hozhatjuk el a hozz tartoz helyi ment, amely segtsgvel jabb projektet kszthetnk. Ugyangy minden projekthez is tartozik ilyen men, amellyel pl. j elemeket adhatunk hozz. Lthat, hogy egy faszer szerkezetrl van sz. A flkvr betvel kiemelt projekt a StartUp elem, ez fog elindulni, ha futtatjuk a Solution-t. Tetszs szerint megvltoztathatjuk ezt a belltst a kvnt projekt helyi menjben (Set as StartUp project). A projekteket lenyitva a hozz tartoz fjlokat talljuk, illetve kt mappt, amelyek specilis clt szolglnak. A Properties mappa a programrl tartalmaz metaadatokat, pldul a kszt nevt, az aktulis verziszmot stb. A References mappa pedig a projektben hasznlt osztlyknyvtrakat sorolja fel. Ebbl rgtn ktfle is van, a BCL-hez tartoz knyvtrak (alapbellts szerint) nem msoldnak be a program kimeneti mappjba, mg a kls knyvtrak igen. j projekt ltrehozsakor is tbb knyvtrat tallunk, ezek kzl egyelre a System s System.Core nlklzhetetlen. Ahhoz, hogy a programunkat futtassuk, elszr le kell fordtanunk. Ezt a Build menben tallhat Build Solution paranccsal tehetjk meg. Ha tbb projektnk is van, egyenknt is fordthatunk a helyi menbl. Amennyiben nem kapunk hibazenetet, illetve a bal als sarokban megjelenik a Build Succeeded felirat, akkor a programunk szintaktikailag helyes, megprblhatjuk futtatni. Ehhez a Debug men Start Debugging illetve Start Without Debugging parancsait kell hasznlnunk. Ha a projekt a legutbbi fordts ta megvltozott, akkor automatikusan le is fordtja a programunkat a Visual Studio. Hasznlhatunk gyorsbillentyket is, F6 a fordts, mg F5 a futtats (s fordts, ha szksges). Vgezetl szintn elrhetjk a clunk, ha a mensorban tallhat kis zld fektetett hromszgre kattintunk. Lpjnk tovbb, egyelre a Main fggvnyen bell fogunk tevkenykedni. Ahhoz, hogy elkszthessk a programunkat, tudnunk kell, hogyan kezelhetjk a parancssort a programunkbl. A .NET Framework BCL-e erre is knl megoldst, a Console osztly lesz segtsgnkre:

20

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestApp { class Program { static void Main(string[] args) { Console.WriteLine("Hello World"); Console.ReadKey(); } } } rmmel tapasztalhatjuk, hogy a Console sz utn tett pont elhozza a vlaszthat fggvnyek listjt. Ezt a Visual Studio IntelliSense-nek hvja. Ha nem ajnlja fel a listt a fejleszteszkz, akkor elkpzelhet, hogy a kdkiegszts nincs bekapcsolva. A visszakapcsolshoz a kvetkezket kell tenni: 1. 2. 3. Tools men -> Options Ha nincs bejellve a Show All Settings ngyzet, akkor kapcsoljuk be! Text Editor -> C# -> IntelliSense -> Legyen bejellve a Show completion list after a character is typed felirat ngyzet!

A WriteLine fggvny kirja a parancssorba a paramterknt kapott szveget, majd j sort nyit. Amennyiben szeretnnk ugyanabban a sorban maradni, hasznljuk a Console.Write fggvnyt! A Console.ReadKey fggvny feladata, hogy vr egy billentyletst a felhasznltl, mieltt tovbblpne a program. Mirt van erre szksg? A legegyszerbb, ha kiprbljuk a programot nlkle. Amit ltni fogunk, az egy villans, hiszen a programunk csak annyit tesz, hogy kir egy szveget s kilp. Ez utbbi miatt kellett valami, amivel megszakthatjuk futst, s itt jn a kpbe a ReadKey.

A C# SZINTAKTIKJA
Amikor egy programozsi nyelv szintaktikjrl beszlnk, akkor azokra a szablyokra gondolunk, amelyek megszabjk a forrskd felptst. Ez azrt fontos, mert az egyes fordtprogramok csak ezekkel a szablyokkal ltrehozott kdot tudjk rtelmezni. Ha a forrskd szintaxisa nem megfelel, a program nem fordul le. A C# gynevezett C-stlus szintaxissal rendelkezik (azaz a C programozsi nyelv szintaxist veszi alapul), ez hrom fontos szablyt von maga utn: Az egyes utastsok vgn pontosvessz - ; - ll. A kis- s nagybetk klnbz jelentsggel brnak, azaz a program s Program azonostk klnbznek. Ha a fenti kdban Console.WriteLine helyett console.writeline t rnnk, akkor a program nem fordulna le. A program egysgeit (osztlyok, metdusok stb.) n. blokkokkal jelljk ki, kapcsos zrjelek ({ s }) segtsgvel.

21

KULCSSZAVAK
Szinte minden programnyelv definil kulcsszavakat, amelyek specilis jelentsggel brnak a fordt szmra. Ezeket az azonostkat a sajt meghatrozott jelentskn kvl nem lehet msra hasznlni, ellenkez esetben a fordt hibt jelez. Vegynk pldul egy vltozt, aminek az int nevet akarjuk adni! Az int nv is beptett tpusra utal, azaz kulcssz, teht nem fog lefordulni a program. int int; //hiba A legtbb fejleszteszkz (gy a Visual Studio is) megsznezi a kulcsszavakat, ezrt knny elkerlni a fenti hibt. A C# 5.0 mr 77 kulcsszt ismer: abstract as base bool break byte case catch char checked class const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto If implicit In int interface internal Is lock long namespace new null object operator out override params private protected public readonly ref return sbyte sealed short Sizeof stackalloc Static String Struct Switch This Throw True Try Typeof Uint Ulong unchecked unsafe ushort using virtual volatile void while

Ezeken kvl ltezik mg 23 azonost, amelyeket a nyelv nem tart fenn specilis hasznlatra, de klnleges jelentssel brnak. Amennyiben lehetsges, kerljk a hasznlatukat hagyomnyos vltozk, osztlyok ltrehozsnl: add ascending by descending equals from get global group in into join let on orderby partial Remove Select Set Value var where yield

Nhnyuk a krnyezettl fggen ms-ms jelentssel is brhat, a megfelel fejezet bvebb informcit ad majd ezekrl az esetekrl.

MEGJEGYZSEK
A forrskdba megjegyzseket tehetnk. Ezzel egyrszt zeneteket hagyhatunk (pl. egy metdus lersa) magunknak vagy a tbbi fejlesztnek, msrszt a kommentek segtsgvel dokumentcit tudunk generlni, ami szintn az els clt szolglja, csak ppen lvezhetbb formban. Megjegyzseket a kvetkezkppen hagyhatunk: static void Main(string[] args) { Console.WriteLine("Hello World"); // Ez egy egysoros komment Console.ReadKey(); /* Ez egy

22

tbbsoros komment */ } Az egysoros komment a sajt sora legvgig tart, mg a tbbsoros a /* s */ prokon bell rvnyes. Utbbiakat nem lehet egymsba gyazni: /* /* */ */ Ez a kd nem fordul le. A kommenteket a fordt nem veszi figyelembe, tulajdonkppen a fordtprogram els lpse, hogy a forrskdbl eltvolt minden megjegyzst.

NVTEREK
A .NET Framework osztlyknyvtrai szerny becsls szerint is legalbb tzezer nevet, azonostt tartalmaznak. Ilyen nagysgrenddel elkerlhetetlen, hogy a nevek ne ismtldjenek. Ekkor egyrszt nehz eligazodni kzttk, msrszt a fordt sem tudn, mikor mire gondolunk. Ennek a problmnak a kikszblsre hoztk ltre a nvterek fogalmt. Egy nvtr tulajdonkppen egy virtulis doboz, amelyben a logikailag sszefgg osztlyok, metdusok stb. vannak. Nyilvn knnyebb megtallni az adatbzis-kezelshez szksges osztlyokat, ha valamilyen kifejez nev nvtrben vannak (pl. System.Data). Nvteret magunk is definilhatunk a namespace kulcsszval: namespace MyNameSpace { } Ezutn a nvtrre vagy a program elejn a using kulcsszval, vagy az azonost el rt teljes elrssel hivatkozhatunk: using MyNameSpace; //vagy MyNameSpace.Valami A jegyzet els felben fleg a System nvteret fogjuk hasznlni.

23

VLTOZK
Amikor programot runk, akkor szksg lehet trolkra, ahov az adatainkat ideiglenesen eltroljuk. Ezeket a trolkat vltozknak nevezzk. A vltozk a memria egy (vagy tbb) celljra hivatkoz lerk. Egy vltozt a kvetkez mdon hozhatunk ltre C# nyelven: Tpus vltoznv; A vltoznv els karaktere csak bet vagy alulvons jel (_) lehet, a tbbi karakter szm is. Lehetleg kerljk az kezetes karakterek hasznlatt (br technikailag nem okoz problmt, nem akadlyozza a fordtst)! Konvenci szerint a vltoznevek kisbetvel kezddnek. Amennyiben a vltoznv tbb szbl ll, akkor clszer azokat a szhatrnl nagybetvel elvlasztani (pl. pirosAlma, vanSapkaRajta stb.).

DEKLARCI S DEFINCI
Egy vltoz (illetve lnyegben minden objektum) letciklusban megklnbztetnk deklarcit s defincit. A deklarcinak tartalmaznia kell a tpust s azonostt, a definciban pedig megadjuk az objektum rtkt. rtelemszeren a deklarci s a definci egyszerre is megtrtnhet. int x; // deklarci x = 10; // definci int y = 11; // deklarci s definci

TPUSOK
A C# ersen (statikusan) tpusos nyelv, ami azt jelenti, hogy minden egyes vltoz tpusnak ismertnek kell lennie fordtsi idben, ezzel biztostva, hogy a program csakis olyan mveletet hajthat vgre, amire valban kpes. A tpus hatrozza meg, hogy egy vltoz milyen rtkeket tartalmazhat, illetve mekkora helyet foglal a memriban. A kvetkez tblzat a C# beptett tpusait tartalmazza, mellettk ott a .NET megfeleljk, a mretk s egy rvid lers: C# tpus byte char bool sbyte short ushort int uint float double decimal long .NET tpus System.Byte System.Char System.Boolean System.SByte System.Int16 System.Uint16 System.Int32 System.Uint32 System.Single System.Double System.Decimal System.Int64 Mret (byte) 1 2 1 1 2 2 4 4 4 8 16 8 Lers Eljel nlkli 8 bites egsz szm (0..255) Egy Unicode karakter Logikai tpus, rtke igaz(1 vagy true) vagy hamis(0 vagy false) Eljeles, 8 bites egsz szm (-128..127) Eljeles, 16 bites egsz szm (-32768..32767 Eljel nlkli, 16 bites egsz szm (0..65535) Eljeles, 32 bites egsz szm (2147483648.. 2147483647). Eljel nlkli, 32 bites egsz szm (0..4294967295) Egyszeres pontossg lebegpontos szm Ktszeres pontossg lebegpontos szm Fix pontossg 28+1 jegy szm Eljeles, 64 bites egsz szm

24

ulong string object

System.Uint64 System.String System.Object

8 N/A N/A

Eljel nlkli, 64 bites egsz szm Unicode karakterek szekvencija Minden ms tpus se

A forrskdban teljesen mindegy, hogy a rendes vagy a .NET nven hivatkozunk egy tpusra. Alaktsuk t a Hello World programot gy, hogy a kirand szveget egy vltozba tesszk: using System; namespace TestApp { class Program { static void Main(string[] args) { string message = "Hello World"; Console.WriteLine(message); Console.ReadKey(); } } } A C# 3.0-tl kezdve mr lehetsges egy metdus hatkrben deklarlt vltoz tpusnak meghatrozst a fordtra bzni. ltalban olyankor tesszk ezt, amikor hossz tpusnvrl van sz, vagy nehz meghatrozni a tpust. Ezt az akcit a var kulcsszval kivitelezhetjk. Ez termszetesen nem jelenti azt, hogy gy hasznlhatjuk a nyelvet, mint egy tpustalan krnyezetet! Abban a pillanatban, amikor rtket rendeltnk a vltozhoz (radsul ezt azonnal meg is kell tennnk), az gy fog viselkedni, mint az ekvivalens tpus. Az ilyen vltozk tpusa nem vltoztathat meg, de a megfelel tpuskonverzik vgrehajthatak. int x = 10; // int tpus vltoz var z = 10; // int tpus vltoz z = "string"; // fordtsi hiba var w; //fordtsi hiba

LOKLIS S GLOBLIS VLTOZK


Egy blokkon bell deklarlt vltoz loklis lesz a blokkjra nzve, vagyis a program tbbi rszbl nem lthat (gy is mondhatjuk, hogy a vltoz hatkre a blokkjra terjed ki). A fenti pldban a message egy loklis vltoz, ha egy msik fggvnybl vagy osztlybl prblnnk meg elrni, akkor a program nem fordulna le. Ebben az esetben az IntelliSense sem ajnlan fel, ez az eszkz teht erre is segtsgnkre van. Globlis vltoznak azokat az objektumokat nevezzk, amelyek a program brmely rszbl elrhetek. A C# nem rendelkezik a ms nyelvekbl ismers globlis vltozval, mivel deklarcit csak osztlyon bell vgezhetnk. thidalhatjuk a helyzetet statikus vltozk hasznlatval, errl ksbb sz lesz.

REFERENCIA- S RTKTPUSOK
A .NET minden tpusa direkt vagy indirekt mdon a System.Object nev tpusbl szrmazik, s ezen bell sztoszlik rtk- s referencia-tpusokra (egyetlen kivtel a pointer tpus, amelynek semmifle kze sincs a System.Object-hez). A kett kztti klnbsg leginkbb a memriban val elhelyezkedsben jelenik meg.

25

A CLR kt helyre tud adatokat pakolni, az egyik a verem (stack), a msik a halom (heap). A stack egy n. LIFO (last-in-first-out) adattr, vagyis a legutoljra berakott elem lesz a tetejn, kivenni pedig csak a mindenkori legfels elemet tudjuk. A heap nem adatszerkezet, hanem a program ltal lefoglalt nyers memria, amit a CLR tetszs szerint hasznlhat. Minden mvelet a stack-et hasznlja, pl. ha ssze akarunk adni kt szmot, akkor a CLR lerakja mindkettt a stack-be s meghvja a megfelel utastst. Ezutn kiveszi a verem legfels kt elemt, sszeadja ket, majd a vgeredmnyt visszahelyezi: int x = 10; int y = 11; x+y A verem: |11| |10| --sszeads mvelet--|21| A referenciatpusok minden esetben a halomban jnnek ltre, mert ezek sszetett adatszerkezetek, s gy hatkony a kezelsk. Az rtktpusok vagy a stack-ben vagy a heap-ben vannak attl fggen, hogy hol deklarltuk ket. Metduson bell, loklisan deklarlt rtktpusok a verembe kerlnek, a referenciatpuson bell adattagknt deklarlt rtktpusok pedig a halomban foglalnak helyet. Nzznk nhny pldt! using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 10; } } } Ebben a programban x-et loklisan deklarltuk egy metduson bell, ezrt biztosak lehetnk benne, hogy a verembe fog kerlni. class MyClass { private int x = 10; } Most x egy referenciatpuson (esetnkben egy osztlyon) belli adattag, ezrt a halomban foglal majd helyet. class MyClass { private int x = 10; public void MyMethod() { int y = 10; } }

26

Ezttal egy kicsit bonyolultabb a helyzet. Az y nev vltozt egy referenciatpuson bell, de egy metdusban, loklisan deklarltuk, gy a veremben fog troldni, x pedig mg mindig adattag, ezrt marad a halomban. Meg kell emlteni ugyanakkor, hogy a programoz szemszgbl a vilgon semmi jelentsge nincs, hogy mi hol troldik. Vgl nzzk meg, hogy mi lesz rtk- s mi referenciatpus! rtktpus lesz az sszes olyan objektum, amelyeket a kvetkez tpusokkal deklarlunk: Az sszes beptett numerikus tpus (int, byte, double, stb.) A felsorolt tpus (enum) Logikai tpus (bool) Karakter tpus (char) Struktrk (struct)

Referenciatpusok lesznek a kvetkezk: Osztlyok (class) Interfsz tpusok (interface) Delegate tpusok (delegate) Stringek Minden olyan tpus, amely kzvetlen mdon szrmazik a System.Objectbl vagy brmely class kulcsszval bevezetett szerkezetbl.

REFERENCIK
Az rtk- illetve referenciatpusok kztti klnbsg egy msik aspektusa az, ahogyan a forrskdban hivatkozunk rjuk. Vegyk a kvetkez kdot: int x = 10; int y = x; Az els sorban ltrehoztuk az x nev vltozt, a msodikban pedig egy j vltoznak adtuk rtkl xet. A krds az, hogy y hova mutat a memriban: oda, ahol x van, vagy egy teljesen ms terletre? Amikor egy rtktpusra hivatkozunk, akkor tnylegesen az rtkt hasznljuk fel, vagyis a krdsnkre a vlasz az, hogy a kt vltoz rtke egyenl lesz, de nem ugyanazon a memriaterleten helyezkednek el, teht y mshova mutat, teljesen nll vltoz.

10 Msol

10

A helyzet ms lesz referenciatpusok esetben. Mivel k sszetett tpusok, ezrt fizikailag lehetetlen lenne az rtkeikkel dolgozni, gy egy referenciatpusknt ltrehozott vltozban tulajdonkppen csak olyan informcik vannak, amelyek segtsgvel a CLR kpes meghatrozni, hogy hol keresse az igazi objektumot a memriban. Viszont vigyzzunk, a referencik nem mutatk (mint pldul a C++-beli pointerek), teht azon kvl, hogy elhzzuk a kvnt objektumot, nincs hatsunk a memrira. Az egyszersg kedvrt a tovbbiakban is a memriaterletre mutat kifejezst hasznljuk. Nzzk meg ezt kzelebbrl:

27

using System; namespace TestApp { class MyClass { public int x; } class Program { static void Main(string[] args) { MyClass s = new MyClass(); s.x = 10; MyClass p = s; p.x = 14; Console.WriteLine(s.x); Console.ReadKey(); } } } Vajon mit fog kirni a program? Kezdjk az elejrl! Hasonl a fellls, mint az elz forrskdnl, viszont amikor a msodik vltoznak rtkl adjuk az elst, akkor az trtnik, hogy a p nev referencia ugyanarra a memriaterletre hivatkozik majd, mint s, vagyis tulajdonkppen annak egy lneve (alias) lesz. rtelemszeren, ha p mdosul, akkor s is gy tesz, ezrt a fenti program kimenete 14 lesz.

x = 10

BOXING S UNBOXING
Boxingnak (bedobozols) azt a folyamatot nevezzk, amely megengedi egy rtktpusnak, hogy gy viselkedjen, mint egy referenciatpus. Korbban azt mondtuk, hogy minden tpus kzvetlenl vagy indirekt mdon a System.Object bl szrmazik. Az rtktpusok esetben az utbbi teljesl, ami egy igen specilis helyzetet jelent. Az rtktpusok alapveten nem szrmaznak az Objectbl, mivel gy hatkony a kezelsk, nem tartozik hozzjuk semmifle tlsly (elmletileg akr az is elfordulhatna ilyenkor, hogy a referenciatpusokhoz adott extrk (sync blokk, metdustbla stb.) tbb helyet foglalnnak, mint a tnyleges adat). Hogy mirt van ez gy, azt nagyon egyszer kitallni: az rtktpusok egyszer tpusok, amelyek kis mennyisg adatot tartalmaznak, ezenkvl ezeket a tpusokat klnsen gyakran fogjuk hasznlni, ezrt elengedhetetlen, hogy a lehet leggyorsabban kezelhessk ket. A problma az, hogy az rtktpusoknak a fentiektl fggetlenl illeszkednik kell a tpusrendszerbe, vagyis tudnunk kell gy kezelni ket, mint egy referenciatpust, s itt jn kpbe a boxing mvelet. Nzznk egy pldt! Az eddig hasznlt Console.WriteLine metdus deklarcija gy nz ki:

28

public static void WriteLine( Object value ) Lthat, hogy a paramter tpusa object, ami a System.Object rvidebb elnevezse, vagyis egy referenciatpus. Mi trtnik vajon, ha egy int tpus vltozt (egy rtktpust) akarunk gy kirni? A WriteLine metdus minden tpust gy r ki, hogy meghvja rajtuk a ToString metdust, amely visszaadja az adott tpus string-alakjt. A baj az, hogy a ToStringet a System.Object deklarlja, ilyen mdon a referenciatpusok mind rendelkeznek vele, de az rtktpusok mr nem. Mg nagyobb baj, hogy a ToString hvshoz a sima objectknt meghatrozott vltozknak el kell kertenik a trolt objektum valdi tpust, ami a GetType metdussal trtnik amelyet szintn a System.Object deklarl , ami nem is lenne nmagban problma, de az rtktpusok nem trolnak magukrl tpusinformcit pp a kis mret miatt. A megoldst a boxing mvelet jelenti, ami a kvetkezkppen mkdik: a rendszer elkszt a halmon egy az rtktpus valdi tpusnak megfelel keretet (dobozt), amely tartalmazza az eredeti vltoz adatait, illetve az sszes szksges informcit ahhoz, hogy referencia-tpusknt tudjon mkdni lnyegben az is lesz. Els rnzsre azt gondoln az ember, hogy a dobozols rendkvl drga mulatsg, de ez nem felttlenl van gy. A valsgban a fordt kpes gy optimalizlni a vgeredmnyt, hogy nagyon kevs htrnyunk szrmazzon ebbl a mveletbl, nhny esetben pedig nagyjbl ugyanazt a teljestmnyt rjk el, mint referenciatpusok esetben. Vegyk szre, hogy az eddigi WriteLine hvsoknl a konverzi krs nlkl azaz implicit mdon mkdtt annak ellenre, hogy rtk- s referenciatpusok kztt nincs szoros relci! Az ilyen kapcsolatot implicit konverzbilis kapcsolatnak nevezzk s nem tvesztend ssze a polimorfizmussal (hamarosan), br nagyon hasonlnak ltszanak. A kvetkez forrskd azt mutatja, hogy miknt tudunk kzzel dobozolni: int x = 10; object boxObject = x; // bedobozolva Console.WriteLine("X rtke: {0}", boxObject); Itt ugyanaz trtnik, mintha rgtn az x vltozt adnnk t a metdusnak, csak ppen egy lpssel hamarabb elksztettk x referenciatpus klnjt. Az unboxing (vagy kidobozols) a boxing ellentte, vagyis a bedobozolt rtktpusunkbl kinyerjk az eredeti rtkt: int x = 0; object obj = x; // bedobozolva int y = (int)obj; // kidobozolva Az object tpus vltozn explicit tpuskonverzit hajtottunk vgre (errl hamarosan), gy visszakaptuk az eredeti rtket. A kidobozols szintn rdekes folyamat: logikusan gondolkodva azt hinnnk, hogy most minden fordtva trtnik, mint a bedobozolsnl, vagyis a vermen elksztnk egy j rtktpust s tmsoljuk az rtkeket. Ez majdnem teljesen igaz, egyetlen apr kivtellel: amikor vissza akarjuk kapni a bedobozolt rtktpusunkat, akkor valjban az unbox IL (Intermediate Language) utastst hvjuk meg, amely egy n. value-type-pointert ad vissza, amely a halomra msolt s bedobozolt rtktpusra mutat. Ezt a cmet azonban nem hasznlhatjuk kzvetlenl a verembe msolshoz, ehelyett az adatok egy ideiglenes vermen ltrehozott objektumba msoldnak, majd onnan egy jabb msols mvelettel a szmra kijellt helyre vndorolnak. A ketts msols pazarlsnak tnhet, de ez egyrszt megkerlhetetlen szably, msrszt a JIT ezt is kpes gy optimalizlni, hogy ne legyen nagy a teljestmnyvesztesg. Fontos mg megjegyezni, hogy a bedobozols utn teljesen j objektum keletkezik, amelynek semmi kze az eredetihez:

29

int x = 10; object z = x; z = (int)z + 10; Console.WriteLine(x); // 10 Console.WriteLine(z); // 20 A kimenet 10 illetve 20 lesz. Vegyk szre azt is, hogy z-n konverzit kellett vgrehajtanunk az sszeadshoz, de az rtkadshoz nem (elszr kidobozoltuk, sszeadtuk a kt szmot, majd az eredmnyt visszadobozoltuk).

KONSTANSOK
A const tpusmdost kulcssz segtsgvel egy objektumot konstanss, megvltoztathatatlann tehetnk. A konstansoknak egyetlenegyszer adhatunk (s ekkor ktelez is adnunk) rtket, mgpedig a deklarcinl. Brmely ksbbi prblkozs fordtsi hibt okoz. const int x; // Hiba const int y = 10; // Ez j x = 11; // Hiba A konstans vltozknak adott rtket/kifejezst fordtsi idben ki kell tudnia rtkelni a fordtnak. A kvetkez forrskd ppen ezrt nem is fog lefordulni: Console.WriteLine("Adjon meg egy szmot: "); const int x = int.Parse(Console.ReadLine()); A Console.ReadLine metdus egy sort olvas be a standard bemenetrl (ez alaprtelmezs szerint a konzol lesz, de megvltoztathat), amelyet terminl karakterrel (amely a sor vgt jelzi, pl. Carriage Return, Line Feed stb.), pl. az Enter-rel zrunk. A metdus egy string tpus rtkkel tr vissza, amelybl ki kell nyernnk a felhasznl ltal megadott szmot. Erre fogjuk hasznlni az int.Parse metdust, ami paramterknt egy karaktersorozatot vr, s egsz szmot ad vissza. A paramterknt megadott karaktersor nem tartalmazhat numerikus karakteren kvl mst, ellenkez esetben a program kivtelt dob. Ennek kikszblsre rendelkezsnkre llnak a TryParse fggvnyek is, ezek logikai rtket adnak vissza, amellyel jelzik, hogy a konverzi sikerlt vagy sem, utbbi esetben pedig nem dobnak kivtelt.

A FELSOROLT TPUS
A felsorolt tpus olyan adatszerkezet, amely meghatrozott rtkek nvvel elltott halmazt kpviseli. Felsorolt tpust az enum kulcssz segtsgvel deklarlhatunk: using System; namespace TestApp { class Program { enum Animal { Cat, Dog, Tiger, Wolf }; static void Main(string[] args) { Console.ReadKey(); }

30

} } Enum tpust csakis metduson kvl (osztlyon bell vagy nll tpusknt) deklarlhatunk, ellenkez esetben a program nem fordul le. Ezutn gy hasznlhatjuk: using System; namespace TestApp { class Program { enum Animal { Cat, Dog, Tiger, Wolf }; static void Main(string[] args) { Animal b = Animal.Tiger; if (b == Animal.Tiger) // Ha b egy tigris { Console.WriteLine("b egy tigris..."); } Console.ReadKey(); } } } A felsorols minden tagjnak megfeleltethetnk egy egsz (numerikus) rtket. Ha mst nem adunk meg, akkor alaprtelmezs szerint a szmozs nulltl kezddik, s deklarci szerinti sorrendben (rtsd: balrl jobbra) eggyel nvekszik. Ezen a mdon az enum objektumokon explicit konverzit hajthatunk vgre a megfelel numerikus rtkre: Animal a = Animal.Cat; int x = (int)a; // x == 0 a = Animal.Wolf; x = (int)a; // x == 3 A tagok rtkei alaprtelmezetten int tpusak, de ezen vltoztathatunk: enum Animal : byte { Cat, Dog, Tiger, Wolf }; Termszetesen ez egytt jr azzal, hogy a tagok rtknek az adott tpus rtkhatrai kztt kell maradniuk, vagyis a pldban egy tag rtke nem lehet tbb mint 255. Ilyen mdon csakis a beptett egsz numerikus tpusokat hasznlhatjuk (pl. byte, long, uint stb.). Azok a nevek amelyekhez nem rendeltnk rtket implicit mdon, az ket megelz nv rtktl szmtva kapjk meg azt, nvekv sorrendben. gy a lenti pldban Tiger vltoz rtke ngy lesz: using System; namespace TestApp { class Program { enum Animal { Cat = 1, Dog = 3, Tiger, Wolf }

31

static void Main(string[] args) { Animal a = Animal.Tiger; Console.WriteLine((int)a); // 4 Console.ReadKey(); } } } Az Enum.TryParse metdussal string rtkekbl gyrthatunk felsorolt rtkeket: using System; namespace TestApp { class Program { enum Animal { Cat = 1, Dog = 3, Tiger, Wolf } static void Main(string[] args) { string s1 = "1"; string s2 = "Dog"; Animal a1, a2; Enum.TryParse(s1, true, out a1); Enum.TryParse(s2, true, out a2); Console.ReadKey(); } } }

NULL TPUSOK
A referenciatpusok az inicializls eltt automatikusan null rtket vesznek fel, illetve mi magunk is megjellhetjk ket belltatlannak: using System; namespace TestApp { class MyClass { } class Program { static void Main(string[] args) { MyClass mc = null; Console.ReadKey(); } } }

32

Ugyanez az rtktpusoknl mr nem alkalmazhat: int x = null; // ez nem j Azt mr lttuk, hogy a referenciatpusokra referencikkal, azaz a nekik megfelel memriacmmel mutatunk, ezrt lehetsges null rtket megadni nekik. Az rtktpusok pedig az ltaluk trolt adatot reprezentljk, ezrt k nem vehetnek fel null rtket, mivel az ilyen tpusokon ez a fogalom nincs rtelmezve. Minden ilyen tpus a deklarcija pillanatban (nhny kivteltl eltekintve) automatikusan a megfelel nulla rtkre inicializldik. Azonban elfordulhat, hogy mgis szeretnnk, ha ezek a tpusok is rendelkeznnek egy nem definilt llapottal, s ebben lesznek segtsgnkre a nullable tpusok. Ezek ugyan rtktpusok, mgis kaphatnak null rtket. A nullable tpusokat a rendes tpus utn rt krdjellel jelezzk: int? x = null; // ez mr j Egy nullable tpusra val konverzi implicit mdon (kln krs nlkl) megy vgbe, mg az ellenkez irnyban explicit konverzira lesz szksgnk (vagyis ezt tudatnunk kell a fordtval): int y = 10; int? x = y; // implicit konverzi y = (int)x; // explicit konverzi

A DINAMIKUS TPUS
Ennek a fejezetnek a teljes megrtshez szksg van az osztlyok, illetve metdusok fogalmra, ezeket egy ksbbi fejezetben tallja meg az olvas. A C# 3.0 verzijig bezrlag minden vltoz s objektum statikusan tpusos volt, vagyis egyrszt a tpust fordtskor meg kellett tudnia hatrozni a fordtnak, msrszt ez futsi id alatt nem vltozhatott meg. A C# 4.0 bevezeti a dynamic kulcsszt, amely hasznlatval dinamikusan tpusoss tehetnk objektumokat. Mit is jelent ez a gyakorlatban? Lnyegben azt, hogy minden dynamic kulcsszval jellt objektum brmit megtehet fordtsi idben, mg olyan dolgokat is, amelyek futsidej hibt okozhatnnak. Ezenkvl az sszes ilyen objektum futsidben megvltoztathatja a tpust is: using System; namespace TestApp { class Program { static void Main(string[] args) { dynamic x = 10; Console.WriteLine(x); // x most 10 x = "szalmi"; Console.WriteLine(x); // x most szalmi Console.ReadKey(); } } }

33

Vegyk a kvetkez osztlyt: class Test { public void Method(string s) { } } Ha a fenti metdust meg akarjuk hvni, akkor meg kell adnunk szmra egy string tpus paramtert is. Kivve, ha a dynamic-ot hasznljuk: dynamic t = new Test(); t.Method(); // ez lefordul A fenti forrskd minden tovbbi nlkl lefordul, viszont futni nem fog. A konstruktorok nem tartoznak az tverhet metdusok kz, akr hasznltuk a deklarcinl a dynamicot, akr nem. A paramtereket minden esetben ktelez megadnunk, ellenkez esetben a program nem fordul le. Br a fenti tesztek szrakoztatak, valjban nem tl hasznosak. A dynamic hagyomnyos objektumokon val hasznlata lnyegben nemcsak tlthatatlann teszi a kdot, de komoly teljestmnyproblmkat is okozhat, ezrt mindenkppen kerljk el az ilyen szitucikat! A dinamikus tpusok igazi haszna a ms programnyelvekkel klnsen a script alap nyelvekkel val egyttmkdsben rejlik. A dynamic kulcssz mgtt egy komoly platform, a Dynamic Language Runtime (DLR) ll (termszetesen a dynamic mellett j nhny osztly is helyet kapott a csomagban). A DLR olyan tpustalan, azaz gyengn tpusos nyelvekkel tud egyttmkdni, mint a Lua, JavaScript, PHP, Python vagy Ruby.

34

OPERTOROK
Amikor programozunk, utastsokat adunk a szmtgpnek. Ezek az utastsok kifejezsekbl llnak, a kifejezsek pedig opertorokbl s operandusokbl, illetve ezek kombincijbl jnnek ltre: i = x + y; Ebben az utastsban inek rtkl adjuk x s y sszegt. Kt kifejezs is van az utastsban: 1. lps: x + y > ezt az rtket jelljk * -gal 2. lps: i = * > i rtkl adjuk a * -ot

Az els esetben x s y operandusok, a + jel pedig az sszeads mvelet opertora. Ugyangy a msodik pontban i s * (vagyis x + y) az operandusok, az rtkads mvelet (=) pedig az opertor. Egy opertornak nemcsak kt operandusa lehet. A C# nyelv egy- (unris) s hrom-operandus (ternris) opertorokkal is rendelkezik. A kvetkez fejezetekben megismerkednk nhny opertorral, de nem az sszessel. Ennek oka az, hogy bizonyos opertorok nmagukban nem hordoznak jelentst, egy-egy specilis rszterlet kapcsoldik hozzjuk. Ezrt ezeket az opertorokat majd a megfelel helyen vizsgljuk meg (pl. az indexel opertor most kimarad, elsknt a tmbknl tallkozhat majd vele a kedves olvas).

OPERTOR-PRECEDENCIA
Amikor tbb opertor is szerepel egy kifejezsben, a fordtnak muszj valamilyen sorrendet (n. precedencit) fellltani kzttk, hiszen az eredmny ettl is fgghet. Pldul: 10 * 5 + 1 Ennl a kifejezsnl - sorrendtl fggen - az eredmny lehet 51 vagy 60. A j megolds az elbbi, az opertorok vgrehajtsnak sorrendjben a szorzs s az oszts elnyt lvez (termszetesen rvnyeslnek a matematikai szablyok). A legels sorrendi helyen szerepelnek pl. a zrjeles kifejezsek, utolsn pedig az rtkad opertor. Ha bizonytalanok vagyunk a vgrehajts sorrendjben, akkor mindig hasznljunk zrjeleket, ez a vgleges programra nzve semmilyen hatssal nincs (s a forrskd olvashatsgt is javtja). A fenti kifejezs gy nzne ki helyesen zrjelezve: (10 * 5) + 1 A C# nyelv precedencia szerint 14 kategriba sorolja az opertorokat (a kisebb sorszmt rtkeli ki hamarabb a fordt): 1. 2. 3. 4. 5. 6. 7. 8. Zrjel, adattag hozzfrs (pont (.) opertor), metdushvs, postfix inkrementl s dekrementl opertorok, a new opertor, typeof, sizeof, checked s unchecked Pozitv s negatv opertorok (x = -5), logikai s binris tagads, prefix inkrementl s dekrementl opertorok, explicit tpuskonverzi Szorzs, maradkos s maradk nlkli oszts sszeads, kivons Bit-eltol (>> s <<) opertorok Kisebb (vagy egyenl), nagyobb (vagy egyenl), as, is Egyenl s nem egyenl opertorok Logikai S

35

9. 10. 11. 12. 13. 14.

Logikai XOR Logikai VAGY Feltteles S Feltteles VAGY Feltteles opertor ( ? : ) rtkad opertor, illetve a rvid formban hasznlt opertorok (pl: x +=y)

RTKAD OPERTOR
Az egyik legltalnosabb mvelet, amit elvgezhetnk az, hogy egy vltoznak rtket adunk. A C# nyelvben ezt az egyenlsgjel segtsgvel tehetjk meg: int x = 10; Ltrehoztunk egy int tpus vltozt, elneveztk xnek, majd kezdrtknek tzet adtunk. Termszetesen nem ktelez a deklarcinl megadni a defincit (amikor meghatrozzuk, hogy a vltoz milyen rtket kapjon), ezt el lehet halasztani: int x; x = 10; Ennek ellenre ajnlott akkor rtket adni egy vltoznak, amikor deklarljuk. Egy vltoznak nemcsak konstans rtket, de egy msik vltozt is rtkl adhatunk, de csakis abban az esetben, ha a kt vltoz azonos tpus, illetve ha ltezik megfelel konverzi (a tpuskonverzikkal egy ksbbi fejezet foglalkozik). int x = 10; int y = x; // y rtke most 10

MATEMATIKAI OPERTOROK
A kvetkez pldban a matematikai opertorok hasznlatt vizsgljuk meg: using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 10; int y = 3; int z = x + y; // sszeads: z = 10 + 3 Console.WriteLine(z); // Kirja az eredmnyt: 13 z = x - y; // Kivons: z = 10 - 3 Console.WriteLine(z); // 7 z = x * y; //Szorzs: z = 10 * 3 Console.WriteLine(z);//30 z = x / y; // Maradk nlkli oszts: z = 10 / 3; Console.WriteLine(z); // 3 z = x % y; // Maradkos oszts: z = 10 % 3

36

Console.WriteLine(z); // Az oszts maradkt rja ki: 1 Console.ReadKey(); //Vr egy billenty letsre } } }

RELCIS OPERTOROK
A relcis opertorok segtsgvel egy adott rtkkszlet elemei kztti viszonyt tudjuk lekrdezni. Relcis opertort hasznl mveletek eredmnye vagy igaz (true) vagy hamis (false) lesz. A numerikus tpusokon rtelmezve van egy rendezs relci: using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 10; int y = 23; Console.WriteLine(x > y); // Kirja az eredmnyt: false Console.WriteLine(x == y); // false Console.WriteLine(x != y); // x nem egyenl y nal: true Console.WriteLine(x <= y); // x kisebb-egyenl mint y: true Console.ReadKey(); } } } Az els sor egyrtelm, a msodikban az egyenlsget vizsgljuk a ketts egyenlsgjellel. Ilyen esetekben figyelni kell, mert egy elts is nehezen kiderthet hibt okoz, amikor egyenlsg helyett az rtkad opertort hasznljuk. Az esetek tbbsgben ugyanis gy is lefordul a program, mkdni viszont valsznleg rosszul fog. A relcis opertorok sszefoglalsa: x>y x >= y x<y x <= y x == y x != y x nagyobb, mint y x nagyobb vagy egyenl, mint y x kisebb, mint y x kisebb vagy egyenl, mint y x egyenl y-nal x nem egyenl y-nal

LOGIKAI S FELTTELES OPERTOROK


A C# logikai tpusa (bool) kt rtket kpes felvenni: igaz (true) s hamis (false). Ennek a tpusnak a segtsgvel elvgezhetek a szoksos logikai mveletek, ezeket nzzk most meg. using System;

37

namespace TestApp { class Program { static void Main(string[] args) { bool l = true; bool k = false; if (l == true && k == false) { Console.WriteLine("Igaz"); } Console.ReadKey(); } } } A fenti pldban elszr felvettnk kt logikai vltozt, az elsnek igaz, a msodiknak hamis rtket adtunk. Ezutn egy elgazs kvetkezik, errl bvebben egy ksbbi fejezetben lehet olvasni, a lnyeg az, hogy ha a zrjelben megadott felttel igaz, akkor vgrehajtja a blokkjban lv utasts(oka)t. A pldban az S (&&) opertort hasznltuk, ez kt operandust vr, s akkor ad vissza igaz rtket, ha mindkett igaz rtket kpvisel. Ebbl kvetkezik az is, hogy akr az elz fejezetben megismert relcis opertorokbl felptett kifejezsek vagy matematikai formulk is lehetnek operandusok. A program nem sok mindent tesz, csak kirja, hogy Igaz. Nzzk az S igazsgtblzatt: A hamis hamis igaz igaz B hamis igaz hamis igaz Eredmny hamis hamis hamis igaz

A fenti forrskd j gyakorls az opertor-precedencihoz, az elgazs felttelben elszr az egyenlsget fogjuk vizsglni (a hetes szm kategria) s csak utna a feltteles S t (tizenegyes kategria). A msodik opertor a VAGY (||): using System; namespace TestApp { class Program { static void Main(string[] args) { bool l = true; bool k = false; if (l == true || k == true) { Console.WriteLine("Igaz"); } Console.ReadKey();

38

} } } A VAGY (||) opertor akkor trt vissza igaz rtket, ha az operandusai kzl legalbb az egyik igaz. Ez a program is ugyanazt csinlja, mint az elz, a klnbsg a felttelben van. Lthat, hogy k biztosan nem igaz, hiszen ppen eltte kapott hamis rtket. A VAGY igazsgtblzata: A hamis hamis igaz igaz B hamis igaz hamis igaz Eredmny hamis igaz igaz igaz

Az eredmny kirtkelse az n. lusta kirtkels (vagy rvidzr) mdszervel trtnik, azaz a program csak addig vizsglja a felttelt, amg muszj. Tudni kell azt is, hogy a kirtkels mindig balrl jobbra halad, ezrt pl. a fenti pldban k soha nem fog kirtkeldni, mert l van az els helyen, s mivel igaz rtket kpvisel, ezrt a felttel is biztosan teljesl. A harmadik a tagads vagy negci (!): using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 10; if (!(x == 11)) { Console.WriteLine("X nem egyenl 11 -gyel!"); } Console.ReadKey(); } } } Ennek az opertornak egy operandusa van (ez a pldban a zrjelben lv kifejezs), s akkor ad vissza igaz rtket, ha az operandusban megfogalmazott felttel hamis. A tagads igazsgtblja: A hamis igaz Eredmny igaz hamis

Ez a hrom opertor n. feltteles opertor, kzlk pedig az S s a VAGY opertoroknak ltezik csonkolt logikai prja is. A klnbsg annyi, hogy a logikai opertorok az eredmnytl fggetlenl kirtkelik a teljes kifejezst, nem lnek a lusta kirtkelssel. A logikai VAGY mvelet: if (l == true | k == true) { Console.WriteLine("Igaz");

39

} A logikai S: if (l == true & k == false) { Console.WriteLine("Igaz"); } A logikai opertorok csaldjhoz tartozik (ha nem is szorosan) a feltteles opertor. Ez az egyetlen hromoperandus (ternris) opertor, s a kvetkezkppen mkdik: felttel ? igaz-g : hamis-g; using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 10; int y = 10; Console.WriteLine((x == y) ? "Egyenl" : "Nem egyenl"); Console.ReadKey(); } } } Az opertor gy mkdik, hogy a krdjel eltti kifejezst kirtkeli, majd megnzi, hogy a kifejezs igaz vagy hamis. Ha igaz, akkor a krdjel utni rtk lesz a teljes kifejezsnk rtke, ha pedig hamis, akkor a kettspont utni. Termszetesen a kt kifejezs ugyanazt a tpust kell, hogy kpviselje, ellenkez esetben nem fordul el a program. Egyszerbb if-else (errl ksbb) gakat lehet ltala kivltani, sok gpelst megsprolhatunk vele s a kdunk is kompaktabb, ttekinthetbb lesz tle.

BIT OPERTOROK
Az elz fejezetben emltett logikai opertorok bitenknti mveletek elvgzsre is alkalmasak. A szmtgp az adatokat kettes szmrendszerbeli alakban trolja, gy pldul ha van egy byte tpus vltoznk (ami egy byte, azaz 8 bit hosszsg), aminek a 2 rtket adjuk, akkor az a kvetkezkppen jelenik meg a memriban: 2 00000010 A bit opertorok ezzel a formval dolgoznak. Az eddig megismert kett mell jn ngy msik opertor is. A mveletek: Bitenknti S: veszi a kt operandus binris alakjt, s a megfelel bitprokon elvgzi az S mveletet, azaz ha mindkt bit 1 llsban van, akkor az adott helyen az eredmnyben is az lesz, egybknt pedig 0: 01101101

40

00010001 AND 00000001 Elg egyrtelm, hogy az S igazsgtblt hasznltuk az eredmny kiszmolshoz. Plda: using System; namespace TestApp { class Program { static void Main(string[] args) { Console.WriteLine(10 & 2); // 1010 & 0010 = 0010 = 2 Console.ReadKey(); } } } A programot futtatva ltni fogjuk, hogy a Console.WriteLine a mvelet eredmnyt tzes szmrendszerben rja majd ki. A bitenknti VAGY hasonlan mkdik, mint az S, de a vgeredmnyben egy bit rtke akkor lesz 1, ha a kt operandus adott bitje kzl valamelyik legalbb az: 01101101 00010001 OR 01111101 Plda a VAGY-ra: using System; namespace TestApp { class Program { static void Main(string[] args) { Console.WriteLine(10 | 2); // 1010 | 0010 = 1010 = 10 Console.ReadKey(); } } } Biteltols balra: a kettes szmrendszerbeli alak fels bitjt eltoljuk, majd a jobb oldalon keletkez res bitet nullra lltjuk. Az opertor jele: <<. 10001111 LEFT SHIFT 100011110 Plda:

41

using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 143; Console.WriteLine(x << 1); // 10001111 (=143) << 1 = 100011110 = 286 Console.ReadKey(); } } } Amikor biteltolst vgznk, figyelnnk kell arra, hogy a mvelet vgeredmnye minden esetben 32 bites, eljeles szm (int) lesz. Ennek nagyon egyszer oka az, hogy gy biztostja a .NET, hogy az eredmny elfrjen (a fenti pldban hasznlt 143 pl. pontosan 8 biten felrhat szm, azaz egy byte tpusban mr nem frne el az eltols utn, hiszen akkor 9 bitre lenne szksgnk). Biteltols jobbra: most az als bitet toljuk el, s fell ptoljuk a hinyt. Az opertor: >>. Plda: using System; namespace TestApp { class Program { static void Main(string[] args) { byte x = 143; Console.WriteLine(x >> 1); // 10001111 (=143) >> 1 = 01000111 = 71 Console.ReadKey(); } } } Vegyk szre, hogy amg a balra val eltols tnylegesen fizikailag hozztett az eredeti szmunkhoz, addig a jobbra tols elvesz belle, hiszen a fellre rkez nulla bitek nem hasznosulnak az eredmny szempontjbl! rtelemszeren a balra tols ezrt mindig nvelni, a jobbra tols pedig mindig cskkenteni fogja az eredmnyt. Ennl is tovbb mehetnk, felfedezve a biteltolsok valdi hasznt: egy n bittel balra tols megfelel az alapszm 2 az n-edik hatvnyval val szorzsnak: 143 << 1 = 143 * (2^1) = 286 143 << 2 = 143 * (2^2) = 572 Ugyangy a jobbra tols ugyanazzal a hatvnnyal oszt (nullra kerektssel): 143 >> 1 = 143 / (2^1) =71 143 >> 2 = 143 / (2^2) = 35

42

Amikor olyan programot ksztnk, amely ersen pt kettvel vagy hatvnyaival val szorzsra/osztsra, akkor ajnlott bitmveleteket hasznlni, mivel ezeket a processzor sokkal gyorsabban vgzi el, mint a hagyomnyos szorzst (tulajdonkppen a szorzs a processzor egyik leglassabb mvelete).

RVID FORMA
Vegyk a kvetkez pldt: x = x + 10; Az x nev vltozt megnveltk tzzel. Csakhogy ez a megolds nem tl hatkony. Mi trtnik valjban? Elsknt rtelmezni kell a jobb oldalt, azaz ki kell rtkelni xet, hozz kell adni tzet, s eltrolni a veremben. Ezutn ismt kirtkeljk xet, ezttal a bal oldalon. Szerencsre van megolds, mgpedig az n. rvid forma. A fenti sorbl ez lesz: x + = 10; Rvidebb, szebb s hatkonyabb. Az sszes aritmetikai opertornak ltezik rvid formja. Az igazsghoz azrt az is hozztartozik, hogy a fordtprogram elvileg felismeri a fent felvzolt szitucit, s a rvid formval egyenrtk IL t kszt belle (ms krds, hogy a forrskd gy viszont szebb s olvashatbb). A problma ugyanaz, de a megolds ms a kvetkez esetben: x = x + 1; Szemmel lthatan ugyanaz a baj, azonban az eggyel val nvelsre/cskkentsre van nll opertorunk: ++x s --x x++ s x-Ebbl az opertorbl rgtn kt verzit is kapunk, prefixes (++/-- ell) s postfixes (++/-- htul) formt. A prefixes alak pontosan azt teszi, amit elvrunk tle, azaz megnveli (vagy cskkenti) az operandust eggyel. A postfixes forma egy kicsit bonyolultabb, elsknt ltrehoz egy tmeneti vltozt, amiben eltrolja az operandusa rtkt, majd megnveli eggyel az operandust, vgl visszaadja az tmeneti vltozt. Ez elsre taln nem tnik hasznosnak, de vannak helyzetek, amikor lnyegesen megknnyti az letnket a hasznlata. Attl fggen, hogy nveljk vagy cskkentjk az operandust, inkrementl illetve dekrementl opertorrl beszlnk. Ez az opertor hasznlhat az sszes beptett numerikus tpuson, valamint a char illetve enum tpusokon is, mivel ezeknek van numerikus megfeleljk.

EGYB OPERTOROK
Unris (+ s -): az adott szm pozitv illetve negatv rtkt jelezzk vele: using System; namespace TestApp { class Program { static void Main(string[] args)

43

{ int x = 10; int y = 10 + (-x); Console.WriteLine(y); Console.ReadKey(); } } } Ez a program nullt fog kirni (termszetesen rvnyeslnek a matematikai szablyok). Ezeket az opertorokat csakis eljeles tpusokon hasznlhatjuk, mivel az opertor int tpussal tr vissza (akkor is, ha pl. byte tpusra alkalmaztuk). A kvetkez kt sor fordtsi hibt okoz, mivel az int tpus nem fr el a byte tpusban: byte x = 10; byte y = -x; A typeof az operandusa tpust adja vissza: using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 143; if (typeof(int) == x.GetType()) { Console.WriteLine("x tpusa int"); } Console.ReadKey(); } } } A vltozn meghvott GetType metdus a vltoz tpust adja vissza (ez egy System.Objecthez tartoz metdus, gy a hasznlathoz dobozolni kell az objektumot). A sizeof opertor a paramtereknt megadott rtktpus mrett adja vissza byte-ban. Ez az opertor kizrlag unsafe (ld. Unsafe md fejezet) mdban hasznlhat s csakis rtktpusokon (illetve pointer tpusokon): using System; namespace TestApp { class Program { static void Main(string[] args) { unsafe {

44

Console.WriteLine(sizeof(int)); } Console.ReadKey(); } } } Ez a program ngyet fog kirni, hiszen az int tpus 32 bites, azaz 4 byte mret tpus. A programot az unsafe kapcsolval kell lefordtanunk parancssorbl val fordtskor: csc /unsafe main.cs Illetve ha Visual Studio-val dolgozunk, akkor kattintsunk jobb gombbal a Solution Explorerben a projekten, s vlasszuk a Properties elemet:

Ezutn a megnyl oldalban a Build fln jelljk be az Allow unsafe code ngyzetet:

45

VEZRLSI SZERKEZETEK
Vezrlsi szerkezetnek a program utastsainak sorrendisgt szablyoz konstrukcikat nevezzk.

SZEKVENCIA
A legegyszerbb vezrlsi szerkezet a szekvencia. Ez tulajdonkppen egyms utn megszabott sorrendben vgrehajtott utastsokbl ll. A szekvencia minden ms vezrlsi szerkezet ptkve.

ELGAZS
Gyakran elfordul, hogy meg kell vizsglnunk egy lltst, s attl fggen, hogy igaz vagy hamis, a programnak ms-ms utastst kell vgrehajtania. Ilyen esetekben elgazst hasznlunk: using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 10; if (x == 10) // Ha x egyenl 10-zel { Console.WriteLine("x rtke 10"); } Console.ReadKey(); } } } Az elgazs felttelben az egyenlsg opertort hasznltuk, s ide kapcsoldik egy viszonylag gyakori hiba: ha vletlenl csak sima egyenlsgjelet runk, az fordtsi hibnak minsl: if (x = 10) // hiba { } Vannak nyelvek, amelyek ezt a formt is megengedik, ezzel automatikusan rtket adva a felttelben szerepl vltoznak, de a C# nem ilyen. A felttelben szerepl kifejezsnek minden esetben logikai rtket kell visszaadnia, vagy pedig az eredmnynek konvertlhatnak kell lennie bool tpusra. A felttelbe nem rhatunk numerikus rtket visszaad kifejezst, illetve null rtket sem vizsglhatunk pusztn a referencia nevnek megadsval:

46

using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 11; if (x) // ez nem fordul le { } if (x > 0) // ez mr j { } Program p = null; if (!p) // ez sem fordul le { } if (p == null) // ez mr j { } Console.ReadKey(); } } } Termszetes az igny arra is, hogy azt a helyzetet is kezelni tudjuk, amikor x rtke nem tz. Ilyenkor hasznljuk az else gat: using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 11; if (x == 10) // Ha x egyenl 10-zel { Console.WriteLine("x rtke 10"); } else // Ha pedig nem { Console.WriteLine("x rtke nem 10"); } Console.ReadKey();

47

} } } Az else szerkezet akkor lp mkdsbe, ha a hozz kapcsold felttel(ek) nem igaz(ak). nmagban else g nem llhat, nem is lenne sok rtelme. A fenti helyzetben rhattuk volna ezt is: using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 11; if (x == 10) // Ha x egyenl 10-zel { Console.WriteLine("x rtke 10"); } if (x != 10) // Ha pedig x nem 10 { Console.WriteLine("x rtke nem 10"); }

Console.ReadKey(); } } } Ez a program pontosan ugyanazt csinlja, mint az elz, de van egy nagy klnbsg kzttk: mindkt felttelt ki kell rtkelnie a programnak, hiszen kt klnbz szerkezetrl beszlnk (ez egyttal azzal is jr, hogy a feltteltl fggen mindkt llts lehet igaz). Arra is van lehetsgnk, hogy tbb felttelt is megvizsgljunk, ekkor else-if szerkezetet hasznlunk:
using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 13; if (x == 10) // ha x rtke 10 { Console.WriteLine("x rtke 10"); } else if (x == 12) // vagy 12 { Console.WriteLine("x rtke 12"); }

48

else // ha pedig valami ms { Console.WriteLine("x rtke nem 10 vagy 12"); } Console.ReadKey(); } } }

A program az els olyan gat hajtja vgre, amelynek a felttele teljesl (vagy ha egyik felttel sem bizonyult igaznak, akkor az else gat ha adtunk meg ilyet). Egy elgazsban pontosan egy darab if, brmennyi else-if s pontosan egy else g lehet. Egy elgazson bell is rhatunk elgazst. Az utols pldban olyan vltozt vizsgltunk, amely nagyon sokfle rtket vehet fel. Nyilvn ilyenkor nem tudunk minden egyes llapothoz felttelt rni (pontosabban tudunk, csak az nem lesz szp). Ilyen esetekben azonban van egy egyszerbb s elegnsabb megolds, mgpedig a switch-case szerkezet. Ezt akkor hasznljuk, ha egy vltoz tbb lehetsges llapott akarjuk vizsglni:
using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 11; switch (x) { case 10: Console.WriteLine("x rtke 10"); break; case 11: Console.WriteLine("x rtke 11"); break; } Console.ReadKey(); } } }

A switch szerkezeten bell megadhatjuk azokat az llapotokat, amelyekre reaglni szeretnnk. Az egyes esetek utastsai utn meg kell adnunk, hogy mi trtnjen ezutn. Az gak a kijellt feladatuk vgrehajtsa utn a break utastssal kilpnek a szerkezetbl. Termszetesen nem kell minden lehetsges llapotot megvizsglnunk, csak azokat, amelyek szmunkra rdekesek. A fenti pldban elg nagy bajban is lennnk, ha minden egyes szmot kln akarnnk vizsglni. Erre a clra hasznlhatjuk a default gat, amely gyakorlatilag az else gnak felel meg. Ha nincs olyan g, amely kezeln az ppen aktulis rtket, akkor vagy a default g kapja meg a vezrlst, vagy ha nem rtunk ilyet, akkor a switch szerkezetbl kilpve folytatdik a program futsa.

using System;

49

namespace TestApp { class Program { enum Animal { TIGER, WOLF, CAT, DOG }; static void Main(string[] args) { Animal animal = Animal.DOG; switch (animal) { case Animal.TIGER: Console.WriteLine("Tigris"); break; default: Console.WriteLine("Nem ismerem ezt az llatot!"); break; } Console.ReadKey(); } } }

A C++ nyelvtl eltren a C# nem engedlyezi, hogy break utasts hinyban egyik llapotbl tcssszunk egy msikba. Ez all a szably all egyetlen kivtel van, ha az adott g nem tartalmaz semmilyen utastst:
using System; namespace TestApp { class Program { enum Animal { TIGER, WOLF, CAT, DOG }; static void Main(string[] args) { Animal animal = Animal.DOG; switch (animal) { case Animal.TIGER: case Animal.DOG: default: Console.WriteLine("Ez egy llat!"); break; } Console.ReadKey(); } } }

A break utastson kvl hasznlhatjuk a goto-t is, ekkor tugrunk a megadott gra:

using System;

50

namespace TestApp { class Program { enum Animal { TIGER, WOLF, CAT, DOG }; static void Main(string[] args) { Animal animal = Animal.DOG; switch (animal) { case Animal.TIGER: goto default; case Animal.DOG: goto default; default: Console.WriteLine("Ez egy llat!"); break; }

Console.ReadKey(); } } }

CIKLUS
Amikor egy adott utastssorozatot egyms utn tbbszr kell vgrehajtanunk, akkor ciklust hasznlunk. A C# ngyfle ciklust biztost szmunkra. For ciklus Az els az n. szmlls ciklus (nevezzk for-ciklusnak). Nzzk a kvetkez programot:
using System; namespace TestApp { class Program { static void Main(string[] args) { for (int i = 0; i < 10; ++i) { Console.WriteLine(i); } Console.ReadKey(); } } }

Vajon mit r ki a program? Mieltt ezt megmondanm, elszr inkbb nzzk meg azt, hogy mit csinl: a for utni zrjelben talljuk az n. ciklusfelttelt, ez minden ciklus rsze lesz, s azt adjuk meg benne, hogy milyen felttele van a ciklus futsnak. A szmlls ciklus felttele els rnzsre elgg sszetett, de ez ne tvesszen meg minket, valjban nem az. Mindssze hrom krdsre kell vlaszt adnunk: Honnan? Meddig? Hogyan?

51

Menjnk sorjban: a Honnan?-ra adott vlaszban megmondjuk azt, hogy milyen tpust hasznlunk a szmolshoz s azt, hogy honnan kezdjk a szmolst. Tulajdonkppen ebben a lpsben adjuk meg az n. ciklusvltozt, amelyre a ciklusfelttel pl. A fenti pldban egy int tpus ciklusvltozt hoztunk ltre a ciklusfelttelen bell, s nulla kezdrtket adtunk neki. A ciklusvltoz neve konvenci szerint i lesz az angol iterate ismtel szbl. Tbb ciklusvltoz hasznlatakor ltalban i, j, k, ... sorrendet kvetnk. Mivel a ciklusfelttel utn blokkot nyitunk, azt hinn az ember, hogy a ciklusvltoz loklis lesz a ciklus blokkjra (a for utn kvetkez kapcsos zrjelekkel hatrolt rszre) nzve, de ez nem fedi a valsgot. A ciklusfelttelen bell deklarlt ciklusvltoz loklis lesz a ciklust tartalmaz blokkra (vagyis ebben az esetben a teljes Main fggvnyre) nzve. pp ezrt a kvetkez forrskd nem fordulna le, hiszen i mr ltezik:
using System; namespace TestApp { class Program { static void Main(string[] args) { for (int i = 0; i < 10; ++i) { Console.WriteLine(i); } int i = 10; // itt a hiba, i mr ltezik Console.ReadKey(); } } }

Kvetkezzen a Meddig?! Most azt kell megvlaszolnunk, hogy a ciklusvltoz mely rtke tesz eleget a ciklusfelttelnek. Ebben a pldban i-nek kisebbnek kell lennie tznl, vagyis kilenc mg j, de ha ennl nagyobb, akkor a ciklust be kell fejezni. Termszetesen bonyolultabb kifejezst is megadhatunk:
using System; namespace TestApp { class Program { static void Main(string[] args) { for (int i = 1; i < 10 && i != 4; ++i) { Console.WriteLine(i); } Console.ReadKey(); } } }

52

Persze ennek a programnak klnsebb rtelme nincs, de a ciklusfelttel rdekesebb. Addig megy a ciklus, amg i kisebb tznl s nem egyenl nggyel. rtelemszeren csak hromig fogja kirni a szmokat, hiszen mire a ngyhez r, a ciklusfelttel mr nem lesz igaz. Utoljra a Hogyan? krdsre adjuk meg a vlaszt, vagyis azt, hogy milyen mdon vltoztatjuk a ciklusvltoz rtkt. A leggyakoribb mdszer a pldban is lthat inkrementl (vagy dekrementl) opertor hasznlata, de itt is megadhatunk sszetett kifejezst:
using System; namespace TestApp { class Program { static void Main(string[] args) { for (int i = 0; i < 10; i += 2) { Console.WriteLine(i); } Console.ReadKey(); } } }

Ebben a kdban kettesvel nveljk a ciklusvltozt, vagyis a pros szmokat ratjuk ki a kpernyre. Most mr eleget tudunk ahhoz, hogy vlaszt adjunk arra, hogy az els programunk mit csinl: nulltl kilencig kirja a szmokat. A for ciklusbl tetszs szerint elhagyhatjuk a ciklusfej brmely rszt akr az egszet is, ekkor vgtelen ciklust kszthetnk. Vgtelen ciklusnak nevezzk azt a ciklust, amely soha nem r vget. Ilyen ciklus szlethet programozsi hibbl, de szndkosan is, mivel nha erre is szksgnk lesz.
using System; namespace TestApp { class Program { static void Main(string[] args) { for (;;) { Console.WriteLine("Vgtelen ciklus"); } } } }

A program futst a Ctrl+C billentykombincival llthatjuk le, ha parancssorbl futtattuk. While ciklus Msodik kliensnk az ell-tesztels ciklus (mostantl hvjuk while-ciklusnak), amely onnan kapta a nevt, hogy a ciklusmag vgrehajtsa eltt ellenrzi a ciklusfelttelt, ezrt elfordulhat az is, hogy a ciklustrzs egyszer sem fut le:

53

using System; namespace TestApp { class Program { static void Main(string[] args) { int i = 0; // ciklusvltoz deklarci while (i < 10) // ciklusfelttel: fuss amg i kisebb, mint 10 { Console.WriteLine("i rtke: {0}", i); ++i; // ciklusvltoz nvelse } Console.ReadKey(); } } }

A program ugyanazt csinlja, mint a szmlls ciklusnl bemutatott plda, viszont itt jl lthatan elklnlnek a ciklusfelttelrt felels utastsok (kezdrtk, ciklusfelttel, nvel/cskkent). Mkdst tekintve az ell-tesztels ciklus hasonlt a szmllsra (mindkett elszr a ciklusfelttelt ellenrzi), de az elbbi sokkal rugalmasabb, mivel tbb lehetsgnk van a ciklusfelttel megvlasztsra. A vltoz rtknek kiratsnl a Console.WriteLine egy msik verzijt hasznltuk, amely n. formtumsztringet kap paramterl. Az els paramterben a kapcsos zrjelek kztt megadhatjuk, hogy a tovbbi paramterek kzl melyiket helyettestse be a helyre (nulltl szmozva). Do-While ciklus A harmadik versenyz kvetkezik, t htul-tesztels ciklusnak hvjk (legyen do-while), nem nehz kitallni, hogy azrt kapta ezt a nevet, mert a ciklusmag vgrehajtsa utn ellenrzi a ciklusfelttelt, gy legalbb egyszer biztosan lefut:
using System; namespace TestApp { class Program { static void Main(string[] args) { int i = 0; do { Console.WriteLine("i rtke: {0}", i); ++i; } while (i < 10); Console.ReadKey(); } } }

54

Foreach ciklus Vgl de nem utolssorban a foreach (neki nincs kln neve) ciklus kvetkezik. Ezzel a ciklussal vgigiterlhatunk egy tmbn vagy gyjtemnyen, illetve minden olyan objektumon, ami megvalstja az IEnumerable s IEnumerator interfszeket (interfszekrl egy ksbbi fejezet fog beszmolni, ott lesz sz errl a kettrl is). A pldnk most nem a mr megszokott szmoljunk el kilencig lesz, helyette vgigmegynk egy string objektumon:
using System; namespace TestApp { class Program { static void Main(string[] args) { string str = "abcdefghijklmnopqrstuvwxyz"; foreach (char ch in str) { Console.Write(ch); } Console.ReadKey(); } } }

A ciklusfejben felvesznk egy char tpus vltozt (egy string karakterekbl ll), utna az in kulcssz kvetkezik, amivel kijelljk azt a listt, amelynek elemein vgig szeretnnk menni. A pldban hasznlt ch vltoz nem ciklusvltoz, hanem n. itercis vltoz, amely felveszi az iterlt gyjtemny aktulis elemnek rtkt. Gyakorlatilag itt egy referencirl van sz (legalbbis referenciatpus elemek esetn), de a foreach ciklus nem mdosthatja egy gyjtemny elemeit (le sem fordulna ebben az esetben a program). Ez a ciklus ktfle mdban kpes mkdni: ha a lista, amin alkalmazzuk, megvalstja az IEnumerable s IEnumerator interfszeket, akkor azokat fogja hasznlni, ha nem, akkor pedig hasonl lesz a vgeredmny, mint egy szmlls ciklus esetben (leszmtva az itercis vltozt, az mindenkppen megmarad). A foreach pontos mkdsvel az interfszekrl szl fejezet foglalkozik majd, ahol tbbek kztt megvalstunk egy osztlyt, amelyen kpes vgigiterlni (azaz megvalstjuk az IEnumerable s IEnumerator interfszeket). Yield A yield kifejezs lehetv teszi, hogy egy ciklusbl olyan osztlyt generljon a fordt, amely megvalstja az IEnumerable interfszt, s ezltal hasznlhat legyen pl. a foreach ciklussal. A yield hasznlathoz szksg van a System.Collections nvtrre. Amennyiben ez nem szerepel a felvett nvterek kztt, az IntelliSense nem nyjt segtsget, illetve fordtsi hibt is okoz. Ha mr bertuk az IEnumerable kifejezst, s a Visual Studio alhzza pirossal (mivel nem adtuk meg a nvteret), akkor kt lehetsgnk van ennek a problmnak a megoldsra (valjban hrom, ha kzzel berjuk a nvteret, de ht a programozk lustk): 1. 2. Jobb klikk rajta, s a helyi menbl vlasszuk a Resolve->Using System.Collections; sort! Ctrl + . billentykombinci

55

using System; using System.Collections; namespace TestApp { class Program { static public IEnumerable EnumerableMethod(int max) { for (int i = 0; i < max; ++i) { yield return i; } } static void Main(string[] args) { foreach (int i in EnumerableMethod(10)) { Console.Write(i); } Console.ReadKey(); } } }

A yield mkdsi elve a kvetkez: a legels metdushvsnl a ciklus megtesz egy lpst, ezutn kilpnk a metdusbl de annak llapott megrizzk, azaz a kvetkez hvsnl nem jraindul a ciklus, hanem onnan folytatja, ahol legutbb abbahagytuk.

56

GYAKORL FELADATOK
SZORZTBLA
Ksztsnk szorztblt! A program vagy a parancssori paramterknt kapott szmot hasznlja, vagy ha ilyet nem adtunk meg, akkor generljon egy vletlen szmot. Megolds (7/Mtable.cs) szrevehettk mr, hogy a Main fggvny rendelkezik egy paramterrel, ami egy string tpus elemekbl ll tmb (tmbkrl a kvetkez fejezetek adnak tbb tjkoztatst, most ez nem annyira lesz fontos). Ebben a tmbben lesznek az n. parancssori paramtereink. De mi is az a parancssori paramter? Egy nagyon egyszer pldt nzznk meg: fordtsunk le egy C# nyelv forrskdot: csc main.cs Ebben az esetben a csc a fordtprogram neve, mg a forrskdot tartalmaz fjl neve a paramter. Ha ezt vesszk alapul, akkor az args tmb egyetlen elemet tartalmaz a csc-re nzve, mgpedig a main.cs t. Lssuk, hogy hogyan fog ez kinzni a mi programunkban (feltesszk, hogy mul.exe lesz a neve): mul.exe 12 Ebben az esetben a 12 es szorztblt szeretnnk ltni. Eddig parancssoros futtatsrl beszltnk, de ehhez valjban nem kell elhagyni a Visual Studio-t. Nyissuk meg a Properties ablakot (jobb klikk a projekten s Properties), majd a Debug fln a Command Line Arguments szvegdobozba be is rhatjuk a paramtereket. Ha tbb is van, szkzzel vlasszuk el ket!

A kvetkez lpsben fejlesszk tovbb a programot, hogy rja ki a paramterknt megadott szm ktszerest! Ehhez mg szksgnk van arra is, hogy szmtpuss alaktsuk a paramtert, hiszen azt stringknt kapjuk meg. Erre a feladatra az int.Parse metdust hasznljuk majd, amely szmm konvertlja a paramtereknt kapott szveget (persze csak akkor, ha ez lehetsges, egybknt kivtelt dob). A forrskd most gy alakul:
using System; namespace TestApp {

57

class Program { static void Main(string[] args) { int number = int.Parse(args[0]); Console.WriteLine(number * 2); Console.ReadKey(); } } }

Mivel a tmbket mindig nulltl kezdve indexeljk, ezrt az els parancssori paramter a megadott szm a nulladik helyen lesz. A program futtatsakor megkapjuk az eredmnyt, ami 20 lesz. Egyetlen problma van, a program sszeomlik, ha nem adunk meg paramtert. Most mdostsuk gy, hogy figyelmeztesse a felhasznlt, hogy meg kell adnia egy szmot is! Ezt gy fogjuk megoldani, hogy lekrdezzk a paramtereket tartalmaz tmb hosszt, s ha ez az rtk nulla, akkor kirjuk az utastst:
using System; namespace TestApp { class Program { static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("Adjon meg egy paramtert!"); } else { int number = int.Parse(args[0]); Console.WriteLine(number * 2); } Console.ReadKey(); } } }

Egy kicsit szebb lesz a forrskd, ha az else g hasznlata helyett az if gba tesznk egy return utastst, amely visszaadja a vezrlst annak a rendszernek, amely az t tartalmaz metdust hvta (ez a metdus jelen esetben a Main, t pedig mi vagyis inkbb az opercis rendszer hvta, azaz a program befejezi a futst):
using System; namespace TestApp { class Program { static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("Adj meg egy paramtert!"); Console.ReadKey(); return;

58

} int number = int.Parse(args[0]); Console.WriteLine(number * 2); Console.ReadKey(); } } }

A kvetkez lpsben ahelyett, hogy kilpnk, ha nincs paramter, inkbb generlunk egy vletlen szmot. Ehhez szksgnk lesz egy Random tpus objektumra. A forrskd most ilyen lesz:
using System; namespace TestApp { class Program { static void Main(string[] args) { int number; if (args.Length == 0) { Random r = new Random(); number = r.Next(100); } else { number = int.Parse(args[0]); } Console.WriteLine(number * 2); Console.ReadKey(); } } }

Vletlenszmot a Next metdussal generltunk, a fenti formjban 0 s 100 kztt generl egy szmot, de tetszleges rtket is berhatunk. Mr nincs ms dolgunk, mint megrni a feladat lnyegt, a szorztblt:
using System; namespace TestApp { class Program { static void Main(string[] args) { int number; if (args.Length == 0) { Random r = new Random(); number = r.Next(100); } else {

59

number = int.Parse(args[0]); } for (int i = 1; i <= 10; ++i) { Console.WriteLine("{0} x {1} = {2}", i, number, i * number); } Console.ReadKey(); } } }

SZMOLGP
Ksztsnk egy egyszer szmolgpet! A program indtsakor krjen be kt szmot s egy mveleti jelet, majd rja ki az eredmnyt! Ezutn bvtsk ki a programot, hogy a kt szmot illetve a mveleti jelet parancssori paramterknt is megadhassuk (ekkor nincs kln mveletvlaszt men, hanem rjuk ki rgtn az eredmnyt): main.exe 12 23 + (az eredmny pedig 35 lesz). Megolds (7/Calculator.cs) Most is kt rszbl ll a programunk, elszr hozz kell jutnunk a szmokhoz s az opertorhoz, majd elvgezzk a megfelel mveletet s kirjuk az eredmnyt. Ezttal is a szksges vltozk deklarcijval kezdjk a programrst, hrom darab kell, kt numerikus (legyen most int) s egy karaktertpus. Ezutn bekrjk a felhasznltl a szksges adatokat, vagy pedig felhasznljuk a paramtereket. Ez utbbi esetben vgezznk egy kis hibaellenrzst, vizsgljuk meg, hogy pontosan hrom paramtert kaptunke! A forrskd eddig gy nz ki:
using System; namespace TestApp { class Program { static void Main(string[] args) { int x, y; char op; if (args.Length == 0) { Console.WriteLine("Az els szm: "); x = int.Parse(Console.ReadLine()); Console.WriteLine("A msodik szm: "); y = int.Parse(Console.ReadLine()); Console.WriteLine("A mvelet(+, -, *, /): "); op = Convert.ToChar(Console.Read()); } else

60

{ if (args.Length != 3) { Console.WriteLine("Tl sok/kevs paramter!"); return; } else { x = int.Parse(args[0]); y = int.Parse(args[1]); op = Convert.ToChar(args[2]); } } Console.ReadKey(); } } }

Az opertor megszerzshez egyrszt a Console.Read fggvnyt hasznltuk (mivel csak egyetlen karakterre van szksg), msrszt ennek a metdusnak a visszatrsi rtkt amely egy egsz szm t kellett konvertlnunk karaktertpuss, ehhez a Convert.ToChar metdus nyjtott segtsget. Most mr nagyon egyszer dolgunk van, mindssze ki kell szmolnunk az eredmnyt. Ezt nyilvn a beolvasott opertor alapjn fogjuk megtenni, ebben a helyzetben pedig a legkzenfekvbb, ha a switch szerkezetet hasznljuk:
int result = 0; switch (op) { case '+': result break; case '-': result break; case '*': result break; case '/': result break; }

= x + y;

= x - y;

= x * y;

= x / y;

Console.WriteLine("A mvelet eredmnye: {0}", result);

Amire figyelni kell, az az, hogy a result vltoz kapjon kezdrtket, ellenkez esetben ugyanis nem fordul le a program (uninitialized variable kezdet hibazenetet kapunk). Ez azrt van gy, mert a vltoz-deklarci s az utols sorban lev kirats kztt nem biztos, hogy megtrtnik a vltoz-definci. Persze mi tudjuk, hogy az rtkads megtrtnik, a fordt viszont nem! Ugyan az rtktpusok bizonyos helyzetekben automatikusan nulla rtket kapnak, de ez nem minden esetben igaz, s loklis vltozk esetn pp ez a helyzet. Ezrt minden olyan esetben, amikor egy loklis vltoz deklarcija s defincija kztt hasznlni akarjuk a vltozt, hibazenetet fogunk kapni. A fenti esetben megoldst jelenthet az is, ha bevezetnk a switch szerkezetben egy default cmkt, amivel minden esetben megtrtnik - valamilyen formban az rtkads, ezzel pedig megnyugtatjuk a fordtt.

61

K PAPR OLL
Ksztsnk k-papr-oll jtkot! Ebben az esetben is hasznljuk a vletlenszm-genertort. A jtk folyamatos legyen, vagyis addig tart, amg a felhasznl kilp (neheztskppen lehet egy karakter, amelyre a jtk vget r)! Tartsuk nyilvn a jtk llst, s minden fordul utn rjuk ki az aktulis eredmnyt! Megolds (7/SPS.cs) A programunk lnyegben hrom rszbl fog llni: elsknt megkrdezzk a felhasznltl, hogy mit vlasztott, majd sorsolunk a gpnek is valamit, vgl pedig kirtkeljk az eredmnyt. Els dolgunk legyen, hogy deklarlunk t darab vltozt, egy Random objektumot, kt stringet (ezekben troljuk, hogy mit vlasztottak a versenyzk), s kt byte tpust (ezekben pedig az eredmnyeket tartjuk szmon, itt elg lesz a byte is, mert feltesszk, hogy senki nem fog tbb szzszor jtszani egyms utn)! Szksgnk lesz mg egy logikai tpusra is, ezzel fogjuk jelezni a programnak, hogy akarunk e mg jtszani. Ksztsk el a program vzt a vltoz-deklarcikkal, illetve a fciklussal (a ciklustrzsben krdezznk r, hogy akarunk-e mg jtszani):
using System; namespace TestApp { class Program { static void Main(string[] args) { Random r = new Random(); string compChoice = ""; string playerChoice = ""; int compScore = 0; int playerScore = 0; bool l = true; do { Console.WriteLine("Akarsz mg jtszani? i/n"); if (Console.ReadKey(true).KeyChar == 'n') l = false; } while (l); } } }

A Console.ReadKey metdussal egyetlen karaktert olvasunk be a standard bemenetrl, de ezt az adatot nem hasznlhatjuk fel azonnal, mivel nem char tpust, hanem ConsoleKeyInfo objektumot ad vissza. Ez utbbi KeyChar tulajdonsgval kapjuk vissza a bert karaktert. A ReadKey most kapott egy paramtert is, amellyel azt jeleztk, hogy az adott karakter ne jelenjen meg a konzolon. szrevehetjk, hogy az elgazsnl most elhagytuk a blokkot kijell kapcsos zrjeleket. Amennyiben az elgazs (vagy ciklus) trzse egyetlen sorbl ll, akkor nem ktelez kirakni a blokkhatrolkat. Most krjk be az adatokat:
Console.WriteLine("Mit vlasztasz? (k/p/o)"); switch (Console.ReadKey(true).KeyChar)

62

{ case 'k': playerChoice = "k"; break; case 'p': playerChoice = "papr"; break; case 'o': playerChoice = "oll"; break; } switch (r.Next(0, 3)) { case 0: compChoice = "k"; break; case 1: compChoice = "papr"; break; case 2: compChoice = "oll"; break; }

Ez a kdrszlet termszetesen a ciklustrzsben a krds el kerl. Az egyes lehetsgeket a k/p/o billentykre lltottuk. Mr csak az eredmny kirtkelse van htra:
if ( (playerChoice == "k" && compChoice == "papr") || (playerChoice == "papr" && compChoice == "oll") || (playerChoice == "oll" && compChoice == "k") ) { Console.WriteLine("Vesztettl! Az lls:\nSzmtgp: {0}\nJtkos:{1}", ++compScore, playerScore); } else if (playerChoice == compChoice) { Console.WriteLine("Dntetlen! Az lls:\nSzmtgp: {0}\nJtkos:{1}", compScore, playerScore); } else { Console.WriteLine("Nyertl! Az lls:\nSzmtgp: {0}\nJtkos:{1}", compScore, ++playerScore); }

rdekesebb megolds bitmveletekkel elkszteni a kirtkelst, ehhez egy kis segtsg: jelljk a hrom lehetsget (k/p/o) a kvetkezkppen: 100, 010, 001! Nyilvnval, hogy egy bitenknti VAGY mvelettel ellenrizni tudjuk a dntetlen eredmnyt, mivel ekkor 100 | 100 = 100, vagyis ugyanazt kapjuk vissza. Ha az eredmny nem dntetlen, akkor a VAGY hromfle eredmnyt adhat vissza (KP, KO, OP): 110, 101 s 011, amelyeket pl. egy switch szerkezettel dolgozhatunk fel.

63

SZMKITALL JTK
Ksztsnk szmkitall jtkot, amely lehetsget ad kivlasztani, hogy a felhasznl prblja kitallni a program ltal kisorsolt szmot, vagy fordtva. A kitallt szm legyen 1 s 100 kztt. t prblkozsa lehet a jtkosnak, minden tipp utn rjuk ki, hogy a tippelt szm nagyobb vagy kisebb-e, mint a kitallt szm! Ha a gpen van a sor, akkor hasznljunk vletlenszm-genertort a szm ltrehozsra. A gpi jtkos gy tallja ki a szmot, hogy mindig felezi az intervallumot (pl. elszr 50et tippel, ha a kitallt szm nagyobb, akkor 75 jn, s gy tovbb). A felhasznltl a jtk vgn krdezzk meg, hogy akare ismt jtszani! Megolds (7/Number.cs) Ennek a feladatnak a legnagyobb kihvsa, hogy olyan programszerkezetet rakjunk ssze, amely tlthat s knnyen mdosthat. Legyen az alaptlet az, hogy elgazsokat hasznlunk! Nzzk meg gy a program vzt:
static void Main(string[] args) { // Itt kivlasztjuk, hogy ki vlaszt szmot if(/* A jtkos vlaszt */) { // A szmtgp megprblja kitallni a szmot } else// A szmtgp vlaszt { // A jtkos megprblja kitallni a szmot } // Megkrdezzk a jtkost, hogy akar-e mg jtszani }

Nem nz ki rosszul, de a problma az, hogy a kt felttel blokkja nagyon el fog hzni, emiatt pedig kevsb lesz olvashat a forrskd. Ez persze nem olyan nagy baj, de ahhoz elg, hogy valami mst prbljunk ki. A procedurlis s alacsony szint nyelvek az ilyen feladatokat ugr utastsokkal oldjk meg, vagyis a forrskdban elhelyezett cmkk kztt ugrlnak (tulajdonkppen a magas szint nyelveknl is ez trtnik, csak ezt mi nem ltjuk mivel, az if/switch/stb. elfedi ellnk). Ez a mdszer a magas szint nyelvek esetn nem igazn ajnlott (fleg, mert megvannak az eszkzk az ugrls kikerlsre), de jelenleg nem is hasznljuk ki teljesen a nyelv adta lehetsgeket, ezrt most szabad rosszalkodnunk. rjuk t a fenti vzat egy kicsit:
using System; namespace TestApp { class Program { static void Main(string[] args) { START: Console.WriteLine("Vlassz jtkmdot!"); Console.WriteLine("1 - Te gondolsz egy szmra"); Console.WriteLine("2 - A szmtgp gondol egy szmra"); switch (Console.ReadKey(true).KeyChar) { case '1': goto PLAYER; case '2': goto COMPUTER; }

64

PLAYER: goto END; COMPUTER: goto END; END: Console.WriteLine("\nAkarsz mg jtszani? i/n"); switch (Console.ReadKey(true).KeyChar) { case 'i': goto START; case 'n': break; } } } }

Kzben ki is egsztettk a kdot egy kicsit, lehet vele ksrletezni, amg el nem ksztjk a lnyeget. Jl lthat, hogy a cmkkkel kellemesen olvashatv s rthetv vlt a kd (persze ennl nagyobb terjedelm forrsnl mr problmsabb lehet). Mr elksztettk a programrszt, amely megkrdezi a jtkost, hogy szeretne-e mg jtszani. Egy apr hibja van, mgpedig az, hogy ha i vagy n helyett ms billentyt nyomunk le, akkor a program vget r. Ezt knnyen kijavthatjuk, ha egy kicsit gondolkodunk. Nyilvn a default cmkt kell hasznlni, s ott egy ugr utastssal a vezrlst a megfelel helyre tenni:
END: Console.WriteLine("\nAkarsz mg jtszani? i/n"); switch (Console.ReadKey(true).KeyChar) { case 'i': goto START; case 'n': break; default: goto END; }

Most kvetkezik a program lnyege: a jtk elksztse. Elsknt azt a szitucit implementljuk, amikor a jtkos prblja kitallni a szmot, mivel ez az egyszerbb. Szksgnk lesz termszetesen vltozkra is, de rdemes tgondolni, hogy gy vegyk fel ket, hogy mindkt programrsz hasznlhassa ket. Ami biztosan mindkt esetben kell, az a vletlenszm-genertor, valamint kt int tpus vltoz az egyikben a jtkos s a szmtgp tippjeit troljuk, a msik pedig a ciklusvltoz lesz, neki adjunk azonnal nulla rtket. Ezt a kettt deklarljuk egyelre, rgtn a START cmke eltt! Ksztsk is el a programot, nem lesz nehz dolgunk, mindssze egy ciklusra lesz szksgnk:
COMPUTER: int number = r.Next(100); i = 0; while(i < 5) { Console.WriteLine("\nA tipped: "); x = int.Parse(Console.ReadLine()); if(x < number) { Console.WriteLine("A szm ennl nagyobb!"); } else if(x > number) { Console.WriteLine("A szm ennl kisebb!"); } else

65

{ Console.WriteLine("Nyertl!"); goto END; } ++i; } Console.WriteLine("\nVesztettl, a szm {0} volt.", number); goto END;

Ennek megrtse nem okozhat gondot, lphetnk a kvetkez llomsra, ami viszont kicsit nehezebb lesz. Ahhoz, hogy megfelel stratgit ksztsnk a szmtgp szmra, magunknak is tisztban kell lennnk azzal, hogy hogyan lehet megnyerni ezt a jtkot. A legltalnosabb mdszer, hogy mindig felezzk az intervallumot, gy az utols tippre mr elg szk lesz az a szmhalmaz, amibl vlaszthatunk (persze gy egy kicsit szerencsejtk is lesz). Nzznk meg egy pldt: a gondolt szm legyen a 87, s tudjuk, hogy a szm egy s szz kztt van! Az els tippnk 50 lesz, amire termszetesen azt a vlaszt kapjuk, hogy a szm ennl nagyobb. Mr csak 50 lehetsges szm maradt, ismt feleznk, a kvetkez tippnk gy a 75 lesz. Ismt azt kapjuk vissza, hogy ez nem elg. Ismt feleznk, mghozz maradk nlkl, vagyis tizenkettt adunk hozz a hetventhz, s gy ki is talltuk a gondolt szmot. Most mr knnyen fel tudjuk rni, hogy mit kell tennie a szmtgpnek: az els ngy ksrletnl felezzk az intervallumot, az utols krben pedig tippelnk. Nzzk a ksz kdot:
PLAYER: Console.WriteLine("Gondolj egy szmra! (1 - 100)"); Console.ReadLine(); x = 50; int min = 0; int max = 100; while (i < 5) { Console.WriteLine("A szmtgp szerint a szm {0}", x); Console.WriteLine("Szerinted? (k/n/e)"); switch (Console.ReadKey(true).KeyChar) { case 'k': if (i == 3) x = r.Next(min, x); else { max = x; x -= (max - min) / 2; } break; case 'n': if (i == 3) x = r.Next(x + 1, max); else { min = x; x += (max - min) / 2; } break; case 'e': Console.WriteLine("A szmtgp nyert!"); goto END; } ++i;

66

} Console.WriteLine("A szmtgp nem tudta kitallni a szmot."); goto END;

A min illetve max vltozkkal tartjuk szmon az intervallum als, illetve fels hatrt. Az x vltozban troljuk az aktulis tippet, neki meg is adtuk a kezdrtket. Egy pldn keresztl nzzk meg, hogy hogyan mkdik a kdunk! Legyen a gondolt szm ismt 87! A szmtgp els tippje 50, mi erre azt mondjuk, hogy a szm ennl nagyobb, ezrt a switch n ga fog beindulni. Az intervallum als hatra ezutn x (vagyis 50) lesz, mivel tudjuk, hogy a szm ennl biztosan nagyobb. A fels hatr nyilvn nem vltozik, mr csak az j xet kell kiszmolni, vagyis hozz kell adni a fels s als hatrok klnbsgnek a felt: (100 50) / 2 = 25 (ellenkez esetben pedig nyilvn le kellene vonni ugyanezt). Amirl mg nem beszltnk, az az a felttel, amelyben x egyenlsgt vizsgljuk hrommal (vagyis azt akarjuk tudni, hogy eljtte az utols tipp ideje). Ez az elgazs fogja visszaadni az utols tippet a vletlenszmgenertorral a megfelelen leszktett intervallumban.

67

TPUSKONVERZIK
Azt mr megtanultuk, hogy az egyes tpusok msknt jelennek meg a memriban, azonban gyakran kerlnk olyan helyzetbe, hogy egy adott tpusnak gy kellene viselkednie, mint egy msiknak. Ilyen helyzetekben tpuskonverzit (castolst) kell elvgeznnk. Ktflekppen konvertlhatunk: implicit s explicit mdon. Az elbbi esetben nem kell semmit tennnk, a fordt krs nlkl elvgzi helyettnk. Implicit konverzi ltalban hasonl tpusokon mkdik, szinte minden esetben a szkebb tpusrl a tgabbra:
int x = 10; long y = x; // y == 10, implicit konverzi

Ebben az esetben a long s int mindketten egsz, numerikus tpusok, s mivel a long tgabb (az int 32 bites, mg a long 64 bites egsz szm), ezrt a konverzi gond nlkl vgbemegy. Egy implicit konverzi minden esetben sikeres, s nem jr adatvesztssel. Egy explicit konverzi nem felttlenl fog mkdni, s ha mgis, akkor adatveszts is fellphet. Vegyk a kvetkez pldt:
int x = 300; byte y = (byte)x; // explicit konverzi, y == ??

A byte szkebb tpus, mint az int (8 illetve 32 bitesek), ezrt explicit konverzit hajtottunk vgre, ezt a vltoz eltti zrjelbe rt tpussal jelltk. Ha lehetsges a konverzi, akkor vgbemegy, egybknt a fordt figyelmeztetni fog. Vajon mennyi most az y vltoz rtke? A vlasz elsre meglep lehet: 44. A magyarzat: a 300 egy kilenc biten felrhat szm (100101100), persze az int kapacitsa ennl nagyobb, de most csak a hasznos rszre van szksg. A byte viszont (ahogy a nevben is benne van) egy nyolcbites rtket trolhat (vagyis a maximum rtke 255 lehet), ezrt a 300nak csak az els 8 bitjt (00101100) adhatjuk t ynak, ami pont 44.

ELLENRZTT KONVERZIK
A programfejleszts alatt hasznos lehet tudnunk, hogy minden konverzi gond nlkl lezajlott-e vagy sem. Ennek ellenrzsre n. ellenrztt konverzit fogunk hasznlni, amely kivtelt dob (errl hamarosan), ha a forrs nem fr el a clvltozban:
checked { int x = 300; byte y = (byte)x; }

Ez a kifejezs kivtelt (System.OverflowException) fog dobni, ha elindtjuk a lefordtott programot, hibazenet fogad majd. Figyeljnk arra, hogy ilyen esetekben csak a blokkon bell deklarlt, statikus s tagvltozkat vizsglhatjuk. Elfordul, hogy csak egy-egy konverzit szeretnnk vizsglni, amihez nincs szksg egy egsz blokkra:
int x = 300; byte y = checked((byte)x);

Az ellenrzs kikapcsolst is megtehetjk az unchecked hasznlatval:


int x = 300; byte y = unchecked((byte)x);

68

Az ajnls szerint ellenrztt konverzikat csak a fejleszts ideje alatt (tesztelsre) hasznljunk, mivel nmi teljestmnyvesztssel jr!

IS S AS
Az is opertort futsidej tpus-lekrdezsre hasznljuk:
using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 10; if (x is int) // ha x egy int { Console.WriteLine("x tpusa int"); } Console.ReadKey(); } } }

Ez a program lefordul, de figyelmeztetst kapunk, mivel a fordt felismeri, hogy a felttel mindig igaz lesz. Ennek az opertornak a leggyakoribb felhasznlsi terlete az interfsz-megvalsts lekrdezse (errl ksbb). Prja, az as az ellenrzs mellett egy explicit tpuskonverzit is vgrehajt. Ezzel az opertorral csakis referenciatpusra konvertlhatunk, rtktpusra nem (ekkor le sem fordul a program). Az as opertort csakis akkor hasznljuk tpusellenrzsre, ha egyttal konverzit is szeretnnk vgezni, pusztn vizsglat cljbl az is opertort kell alkalmazni. Nzznk egy pldt:
using System; namespace TestApp { class Program { static void Main(string[] args) { object a = "123"; object b = "Hello"; object c = 10; string aa = a as string; Console.WriteLine(aa == null ? "NULL" : aa); // 123 string bb = b as string; Console.WriteLine(bb == null ? "NULL" : bb); // Hello string cc = c as string; Console.WriteLine(cc == null ? "NULL" : cc); // NULL Console.ReadKey(); }

69

} }

Amennyiben ez a konverzi nem hajthat vgre, a clvltozhoz null rtk rendeldik (ezrt is van a referenciatpusokra korltozva ez az opertor).

KARAKTERKONVERZIK
A char tpust implicit mdon tudjuk numerikus tpusra konvertlni, ekkor a karakter Unicode rtkt kapjuk vissza:
using System; namespace TestApp { class Program { static void Main(string[] args) { for (char ch = 'a'; ch <= 'z'; ++ch) { Console.WriteLine((int)ch); } Console.ReadKey(); } } }

Erre a kimeneten a kvetkez szmok jelennek meg: 97, 98, 99, 100, ... A kis a bet hexadecimlis Unicode szma 0061h, ami a 97 decimlis szmnak felel meg, teht a konverzi a tzes szmrendszerbeli rtket adja vissza. Az Unicode nemzetkzi karakter kdolsi szabvny, amely a vilg rsrendszereinek nagy rszt tartalmazza (jelenleg tbb mint 110000 karaktert tartalmaz).

70

TMBK
Gyakran van szksgnk arra, hogy tbb azonos tpus objektumot troljunk el, ilyenkor knyelmetlen lenne mindegyiknek kln vltozt foglalnunk (kpzeljnk el 30 darab int tpus vltozt, mg lerni is egy rkkvalsg lenne). De ezt nem is kell megtennnk, hiszen rendelkezsnkre ll a tmb adatszerkezet. A tmb meghatrozott szm, azonos tpus elemek halmaza. Minden elemre egyrtelmen mutat egy index (egsz szm). A tmbk referenciatpusok. A C# mindig folytonos memriablokkokban helyezi el egy tmb elemeit. Tmbt a kvetkezkppen deklarlhatunk:
int[] array = new int[10];

Ez a tmb tz darab int tpus elem trolsra alkalmas. A tmb deklarcija utn az egyes indexeken lv elemek automatikusan a megfelel null rtkre inicializldnak (ebben az esetben 10 darab nullt fog tartalmazni a tmbnk). Ez a szably referenciatpusoknl kiss mshogy mkdik, mivel ekkor a tmbelemek null-ra inicializldnak. Ez nagy klnbsg, mivel rtktpusok esetben szimpla nullt kapnnk vissza az ltalunk nem belltott indexre hivatkozva (vagyis ez egy teljesen szablyos mvelet), mg referenciatpusoknl ugyanez NullReferenceException tpus kivtelt fog generlni. Az egyes elemekre az indexel opertorral (szgletes zrjelek: [ ]) s az elem indexvel (sorszmval) hivatkozunk. A szmozs mindig nulltl kezddik, gy a legutols elem indexe: az elemek szma mnusz egy. A kvetkez pldban feltltnk egy tmbt vletlen szmokkal s kiratjuk a tartalmt:
using System; namespace TestApp { class Program { static void Main(string[] args) { int[] array = new int[10]; Random r = new Random(); for (int i = 0; i < array.Length; ++i) { array[i] = r.Next(0, 100); } foreach (int item in array) { Console.WriteLine(item); } Console.ReadKey(); } } }

A pldban a ciklusfelttel megadsakor a tmb Length nev tulajdonsgt hasznltuk, amely visszaadja a tmb hosszt. Lthat az indexel-opertor hasznlata is, az array[i] a tmb iedik elemt jelenti. Az indexelssel vigyzni kell, ugyanis a fordt nem ellenrzi fordtsi idben az indexek helyessgt, viszont helytelen indexels esetn futs idben IndexOutOfRangeException kivtelt fog dobni a program. A tmb adott elemt az rtkadssal egy idben is kirhattuk volna, a pldban csak szemlltets cljbl hasznltunk ktfle ciklust. Egy tmbt akr a deklarci pillanatban is feltlthetnk a neknk megfelel rtkekkel:
char[] charArray = new char[] { 'b', 'd', 'a', 'c' };

71

Egy tmb ltal trolhat elemek szmt a deklarcival azonnal meghatrozzuk, ezen a ksbbiekben nem lehet vltoztatni. Dinamikusan bvthet adatszerkezetekrl a Gyjtemnyek cm fejezet szl. Minden tmb a System.Array osztlybl szrmazik, ezrt nhny hasznos mvelet azonnal rendelkezsnkre ll (pl. rendezhetnk egy tmbt a Sort metdussal):
chararray.Sort(); // tmb rendezse

TBBDIMENZIS TMBK
Eddig az n. egydimenzis tmbt (vektort) hasznltuk. Lehetsgnk van azonban tbbdimenzis tmbk ltrehozsra is, ekkor nem egy indexszel hivatkozunk egy elemre, hanem annyival, ahny dimenzis a tmb. Vegyk pldul a matematikbl mr ismert mtrixot: 12, 23, 2 A = [ 13, 67, 52 ] 45, 55, 1 Ez egy ktdimenzis tmbnek (mtrix) felel meg, az egyes elemekre kt indexszel hivatkozunk, els helyen a sor ll s utna az oszlop. gy a 45 indexe: [2, 0] (ne feledjk, mg mindig nulltl indexelnk). Multidimenzis tmbt a kvetkez mdon hozunk ltre C# nyelven:
int[,] matrix = new int[3, 3];

Ez itt egy 3x3as mtrix, olyan, mint a fent lthat. Itt is sszekthetjk az elemek megadst a deklarcival, br egy kicsit trkksebb a dolog:
int[,] matrix = { {12, 23, {13, 67, {45, 55, }; new int[,] 2}, 52}, 1}

Az elemszm most is meghatrozott, nem vltoztathat. Nyilvn nem akarjuk mindig kzzel feltlteni a tmbket, viszont ezttal nem olyan egyszer a dolgunk, hiszen egy ciklus biztosan nem lesz elg ehhez, vagyis gondolkodnunk kell: az index els tagja a sort, a msodik az oszlopot adja meg, pontosabban az adott sorban elfoglalt indext. Ez alapjn pedig j tletnek tnik, ha egyszerre csak egy dologgal foglalkozunk, azaz szpen vgig kell valahogyan mennnk minden soron egyesvel. Erre a megoldst az n. egymsba gyazott ciklusok jelentik: a kls ciklus a sorokon megy t, a bels pedig a sorok elemein:
using System; namespace TestApp { class Program { static void Main(string[] args) { int[,] matrix = new int[3, 3]; Random r = new Random(); for (int i = 0; i < matrix.GetLength(0); ++i) // sorok

72

{ for (int j = 0; j < matrix.GetLength(1); ++j) // oszlopok { matrix[i, j] = r.Next(0, 100); } } Console.ReadKey(); } } }

Most nem rjuk ki a szmokat, ez mr nem okozhat gondot az olvasnak. A tmbk GetLength metdusa a paramterknt megadott dimenzi hosszt adja vissza (nulltl szmozva), teht a pldban az els esetben a sor, a msodikban az oszlop hosszt adjuk meg a ciklusfelttelben. A tbbdimenzis tmbk egy varinsa az n. egyenetlen (jagged) tmb. Ekkor legalbb egy dimenzi hosszt meg kell adnunk, ez konstans marad, viszont a bels tmbk hossza tetszs szerint megadhat:
int[][] jarray = new int[3][];

Ksztettnk egy hrom sorral rendelkez tmbt, azonban a sorok hosszt (az egyes sorok maguk is nll vektorok) rrnk ksbb megadni, s nem kell ugyanolyan hossznak lennik.
using System; namespace TestApp { class Program { static void Main(string[] args) { int[][] jarray = new int[3][]; Random r = new Random(); for (int i = 0; i < 3; ++i) { jarray[i] = new int[r.Next(1, 5)]; for (int j = 0; j < jarray[i].Length; ++j) { jarray[i][j] = i + j; Console.Write("{0}, ", jarray[i][j]); } Console.WriteLine(); } Console.ReadKey(); } } }

Vletlenszm-genertorral adtuk meg a bels tmbk hosszt, persze rtelmes kereteken bell. A bels ciklusban jl lthat, hogy a tmb elemei valban tmbk, hiszen hasznltuk a Length tulajdonsgot (persze a hagyomnyos tbbdimenzis tmbk esetben is ez a helyzet, de ott nem lenne rtelme kln elrhetv tenni az egyes sorokat). Az inicializls a kvetkezkppen alakul ebben az esetben:

73

int[][] jarray = new int[][] { new int[]{ 1, 2, 3, 4, 5 }, new int[]{ 1, 2, 3 }, new int[]{ 1 } };

74

STRINGEK
A C# beptett karaktertpusa (char) egy Unicode karaktert kpes trolni kt byte-on. A szintn beptett string tpus ilyen karakterekbl ll (teht az egyes betket char pldnyknt kezelhetjk).
using System; namespace TestApp { class Program { static void Main(string[] args) { string s = "ezegystring"; Console.WriteLine(s); Console.ReadKey(); } } }

A ltszat ellenre a string referenciatpus, viszont nem ktelez hasznlnunk a new opertort a deklarcijakor. Egy string egyes karaktereire az indexel opertorral hivatkozhatunk (vagyis minden stringet kezelhetnk tmbknt is):
using System; namespace TestApp { class Program { static void Main(string[] args) { string s = "ezegystring"; Console.WriteLine(s[0]); // e Console.ReadKey(); } } }

Ekkor a visszaadott objektum tpusa char lesz. A foreach ciklussal indexel opertor nlkl is vgigiterlhatunk a karaktersorozaton:
foreach (char ch in s) { Console.WriteLine(ch); }

Az indexel opertort nemcsak vltozkon, de nyers szvegen is alkalmazhatjuk:


Console.WriteLine("ezegystring"[4]); // y

Ilyenkor egy nvtelen vltozt kszt a fordt, s azt hasznlja. Nagyon fontos tudni, hogy mikor egy ltez string objektumnak j rtket adunk, akkor nem az eredeti pldny mdosul, hanem egy teljesen j objektum keletkezik a memriban (vagyis a string n. immutable megvltoztathatatlan tpus). Ez a viselkeds fleg akkor okozhat (teljestmny)problmt, ha sokszor van szksgnk ilyen mveletekre.

75

METDUSOK
A .NET szmos hasznos metdust biztost a stringek hatkony kezelshez. Most megvizsglunk nhnyat, de tudni kell, hogy a van, amelyiknek szmos vltozata is lehet, most a leggyakrabban hasznltakat nzzk meg: sszehasonlts:
using System; namespace TestApp { class Program { static void Main(string[] args) { string a = "egyik"; string b = "msik"; int x = String.Compare(a, b); if (x == 0) { Console.WriteLine("A kt string egyenl"); } else if (x < 0) { Console.WriteLine("Az 'a' a kisebb"); } else { Console.WriteLine("A 'b' a kisebb"); } Console.ReadKey(); } } }

A String.Compare metdus nullt ad vissza, ha a kt string egyenl, s nullnl kisebbet/nagyobbat, ha nem (pontosabban, ha lexikografikusan lnyegben bcsorrend szerint kisebb/nagyobb). Keress:
using System; namespace TestApp { class Program { static void Main(string[] args) { string s = "verylonglongstring"; char[] chs = new char[] { 'y', 'z', 'o' }; Console.WriteLine(s.IndexOf('r')); // 2 Console.WriteLine(s.IndexOfAny(chs)); // 3 Console.WriteLine(s.LastIndexOf('n')); // 16 Console.WriteLine(s.LastIndexOfAny(chs)); // 9 Console.WriteLine(s.Contains("long")); // true

76

Console.ReadKey(); } } }

Az IndexOf s LastIndexOf metdusok egy karakter vagy karaktersorozat els illetve utols elfordulsi indext (utbbi esetn a kezdindexet) adjk vissza. Ha nincs tallat, akkor a visszaadott rtk -1 lesz. A kt metdus Anyre vgzd vltozata egy karaktertmbt fogad paramtereknt, s az abban tallhat sszes karaktert prblja megtallni. A Contains igaz rtkkel tr vissza, ha a paramtereknt megadott karakter(sorozat) benne van a stringben. Mdosts:
using System; namespace TestApp { class Program { static void Main(string[] args) { string s = "smallstring"; char[] chs = new char[] { 's', 'g' }; Console.WriteLine(s.Replace('s', 'l')); // lmallltring Console.WriteLine(s.Trim(chs)); // mallstrin Console.WriteLine(s.Insert(0, "one")); // onesmallstring Console.WriteLine(s.Remove(0, 2)); // allstring Console.WriteLine(s.Substring(0, 3)); // sma Console.WriteLine(s.ToUpper()); // SMALLSTRING Console.WriteLine(s.ToLower()); // smallstring Console.ReadKey(); } } }

A Replace metdus az els paramternek megfelel karaktereket lecserli a msodik paramterre. A Trim a string elejn s vgn lv karaktereket vgja le, a Substring pedig kivg egy karaktersorozatot, paramterei a kezd s vgindexek (van egyparamteres vltozata is, ekkor csak a kezdindexet adjuk meg s a vgig megy). Az Insert/Remove metdusok hozzadnak, illetve elvesznek a stringbl (a megadott indexeken). Vgl a ToLower s ToUpper metdusok kis- illetve nagybetss alaktjk az eredeti stringet. Fontos megjegyezni, hogy ezek a metdusok soha nem az eredeti stringen vgzik a mdostsokat, hanem egy j pldnyt hoznak ltre, s azt adjk vissza.

STRINGBUILDER
Azt mr tudjuk, hogy amikor mdostunk egy stringet, akkor automatikusan egy j pldny jn ltre a memriban, ez pedig nem felttlenl olcs mvelet. Ha sokszor (legalbb 10+ alkalom) van szksgnk erre, akkor hasznljuk inkbb a StringBuilder tpust, ez automatikusan lefoglal egy nagyobb darab memrit, s ha ez sem elg, akkor allokl egy megfelel mret terletet, s tmsolja magt oda. A StringBuilder a System.Text nvtrben tallhat. Plda a StringBuilder hasznlatra:
using System; using System.Text; namespace TestApp {

77

class Program { static void Main(string[] args) { StringBuilder sb = new StringBuilder(50); for (char ch = 'a'; ch <= 'z'; ++ch) { sb.Append(ch); } Console.WriteLine(sb); Console.ReadKey(); } } }

A StringBuilder fenti konstruktora (van tbb is) helyet foglal tven karakter szmra (ltezik alaprtelmezett konstruktora is, ekkor tizenhat karakternek foglal helyet). Az Append metdussal tudunk karaktereket (vagy egsz stringeket) hozzfzni.

REGULRIS KIFEJEZSEK
Regulris kifejezsek segtsgvel vizsglhatjuk, hogy egy karaktersorozat megfelel-e egy adott mintnak. Nzznk is egy pldt: ksztsnk olyan regulris kifejezst, amely termszetes szmokra illik! A pldban a nullt nem soroljuk a termszetes szmok kz, vagyis a minta a kvetkez lesz: az els szmjegy egy s kilenc kztti lesz, ezutn pedig brmennyi nulla s kilenc kztti szmjegy jhet. A forrskd:
using System; using System.Text.RegularExpressions; namespace TestApp { class Program { static void Main(string[] args) { Regex pattern = new Regex("^[1-9][0-9]*"); string s1 = "012345"; string s2 = "112345"; string s3 = "2"; Console.WriteLine("{0} : {1}", s1, (pattern.IsMatch(s1) ? "termszetes szm" : "nem termszetes szm")); Console.WriteLine("{0} : {1}", s2, (pattern.IsMatch(s2) ? "termszetes szm" : "nem termszetes szm")); Console.WriteLine("{0} : {1}", s3, (pattern.IsMatch(s3) ? "termszetes szm" : "nem termszetes szm")); Console.ReadKey(); } } }

A regulris kifejezst egy Regex pldnynak adjuk t, lssuk, hogy hogyan pl fel:

78

^: ezzel a karakterrel (Alt Gr + 3) megmondjuk, hogy a mintaillesztst a string elejtl kell kezdeni. [1-9]: a string elejn egy darab numerikus karakter ll kivve a nullt. [0-9]*: a csillag(*) nulla vagy tbb elfordulst jelent, vagyis az els szmjegy utn nulla vagy tbb nulla s kilenc kztti szm llhat. A programunk a kvetkez kimenetet produklja: 012345 : nem termszetes szm 112345 : termszetes szm 2 : termszetes szm A kvetkez pldnk egy kicsit bonyolultabb lesz, a regulris kifejezs pedig mr-mr ijeszt. Kpzeljk el, hogy a vilgon uniformizljk a telefonszmokat, a kvetkez mdon: minden szm az orszg elhvjval kezddik, amelyet egy + jel elz meg, az elhvszm kt vagy hrom szmjegybl ll, az els szm nem lehet nulla. Ezutn egy szkz jn, amelyet a krzetszm kvet, ami szintn kt vagy hrom szmjegy, az els helyen itt sem llhat nulla. Vgl kvetkezik a telefonszm, ami a krzetszmot szkzzel elvlasztva kveti. Minden telefonszm hrom darab hrom szmjegybl ll blokkbl ll, ezeket ktjel vlasztja el. Egy plda: +36 30 123-456-789 Lssuk a forrskdot:
string s = @"^(\+)[1-9][0-9]{1,2}\s[1-9][0-9]{1,2}\s(\d{3}(-)){2}\d{3}$"; Regex pattern = new Regex(s); string s1 = "+36 30 661-345-612"; string s2 = "+3630 661-567-233"; string s3 = "+56 30 667-876-987-456"; Console.WriteLine(pattern.IsMatch(s1)); // true Console.WriteLine(pattern.IsMatch(s2)); // false Console.WriteLine(pattern.IsMatch(s3)); // false

A regulris kifejezs el ktelezen oda kell tennnk a @ jelet, mivel specilis karaktereket is tartalmaz (erre a fordt is figyelmeztet). A kifejezsnk kt rszbl ll: ^(\+)[1-9][0-9]{1,2}\s: ez a minta lesz az els kt helyen s hivatott az elhvt illetve a krzetszmot ellenrizni: a mr ismert ^-vel az illesztst a karaktersor elejtl kezdjk, az ezt kvet (\+) pedig megmondja, hogy az els helyen egy + jel van (ez persze nem lesz ott a krzetszm eltt). A + el \ t kell tennnk, mivel ennek a jelnek nll jelentse is van regulris kifejezsben. A zrjelek kz nyers karaktereket (is) rhatunk. Ezutn ismers kvetkezik: az [1-9][0-9] et mr nem okozhat gondot megrteni, az utna kvetkez {1,2} t mr inkbb. Ezzel azt kzltk, hogy az eltte lv meghatrozs ([0-9]) legalbb egy, legfeljebb kt alkalommal szerepel egyms utn. Vgl a \s egy szkzt jell. (\d{3}(-)){2}\d{3}$: ez a kifejezs a ktjellel elvlasztott szmblokkokat jelli. A vgn lv $ jel jelzi, hogy a karaktersorozat vgig kell illesztenie, vagyis az egszet ellenrizze (msklnben a 345-345-345-456 sorozat rvnyes lenne, hiszen benne van az, amit kerestnk). Haladjunk tovbbra is a vgrl: a {3}rl mr tudjuk, hogy azt jelenti: az eltte lv kifejezsnek pontosan hromszor kell szerepelnie, ez jelen esetben azt jelenti, hogy a minta vgn hrom szmjegy ll, amelyet a \d vel (d mint digit) jellnk. A \d eltt szerepl {2} az egsz zrjelben lv kifejezsre vonatkozik, ahol megmondtuk, hogy hrom szmjegy utn egy ktjel kvetkezik.

79

A .NET regulris kifejezsei meglepen sokrtek, rengeteg lehetsgnk van, a fenti kt plda csak zelt volt. Tovbbi informcit tallhatunk az MSDN megfelel oldaln: http://msdn.microsoft.com/en-us/library/az24scfc(v=VS.90).aspx

80

GYAKORL FELADATOK II.


MINIMUM- S MAXIMUMKERESS
Keressk ki egy tmb legnagyobb, illetve legkisebb elemt s ezek indexeit (ha tbb ilyen elem van, akkor elg az els elforduls)! Megolds (11/MinMax.cs) A mininum/maximum-keress a legalapvetbb algoritmusok egyike. Az alapelv rendkvl egyszer, vgigmegynk a tmb elemein, s minden elemet sszehasonltunk az aktulis legkisebbel/legnagyobbal. Nzzk is meg a forrskdot (csak a lnyeg szerepel itt, a tmb feltltse nem okozhat gondot):
int int int int min = 1000; max = -1; minIdx = 0; maxIdx = 0;

for (int i = 0; i < 30; ++i) { if (array[i] < min) { min = array[i]; minIdx = i; } if (array[i] > max) { max = array[i]; maxIdx = i; } }

A min s max vltozknak kezdrtket is adtunk, ezeket gy kell megvlasztani, hogy ezek biztosan kisebbek illetve nagyobbak legyenek, mint a tmb elemei (feltesszk, hogy nullnl kisebb s ezernl nagyobb szm nincs a tmbben). rtelemszeren, mivel minden elemet ktelezen meg kell vizsglnunk, ez az algoritmus nem tl gyors nagy elemszm esetn, viszont nagyon jl mkdik prhuzamos krnyezetben a tmb egyes rszeit kln feldolgozegysg kapja meg.

SZIGETEK
Egy szigetcsoport fltt elreplve bizonyos idkznknt megnztk, hogy pp hol vagyunk. Ha sziget (szrazfld) fltt, akkor lertunk egy egyest, ha tenger fltt, akkor nullt. A programunk ezt az adatot dolgozza fl, amelyet vagy a billentyzetrl, vagy - ha van - a parancssori paramterbl kap meg. A feladatok: Adjuk meg a leghosszabb egybefgg szrazfld hosszt! Adjuk meg, hogy hny szigetet talltunk!

81

Megolds (11/Islands.cs) A megoldsban csak az adatok feldolgozst nzzk meg, a beolvassuk nem okozhat mostanra gondot. A szigeteken vgzett mrseket a data nev, string tpus vltozban troltuk el. A kt feladatot egyszerre fogjuk megoldani, mivel a programunk a kvetkez elven alapul: a data stringen fogunk keresztlmenni egy ciklus segtsgvel. Ha a string adott indexn egyest tallunk, akkor elindtunk egy msik ciklust, amely attl az indextl megy egszen addig, amg nullhoz nem r. Ekkor egyrszt megnveljk eggyel a szigetek szmt trol vltozt, msrszt tudni fogjuk, hogy milyen hossz volt a sziget, s sszehasonlthatjuk az eddigi eredmnyekkel. Nzzk is meg a forrskdot:
int islandCount = 0; int maxIslandLength = 0; int i = 0; while (i < data.Length) { if (data[i] == '1') { ++islandCount; int j = i; int tmp = 0; while (j < data.Length && data[j] == '1') { ++j; ++tmp; } i = j; if (tmp > maxIslandLength) { maxIslandLength = tmp; } } else { ++i; } }

A kdban kt rdekes dolog is van. Az els a bels ciklus felttele: ellenrizzk, hogy mg a szigetet mrjk s azt is, hogy nem rtnk-e a string vgre. Ami fontos, az a felttelek sorrendje: mivel tudjuk, hogy a felttelek kirtkelse balrl jobbra halad, ezrt elszr azt kell vizsglnunk, hogy helyes indexet hasznlunk-e, ellenkez esetben ugyanis kivtelt kapnnk (hiszen ha a string vge utn vagyunk, ott mr nincs semmi). A msik rdekessg a kt ciklusvltoz. Amikor befejezzk a bels ciklust, akkor a kls ciklusvltozt j pozciba kell helyeznnk, mghozz oda, ahol a bels ciklus abbahagyta a vizsglatot.

TLAGHMRSKLET
Az v minden napjn megmrtk az tlaghmrskletet, az eredmnyeket pedig egy mtrixban troltuk (az egyszersg kedvrt tegyk fel, hogy minden hnap harminc napos, az eredmnyeket pedig vletlenszmgenertorral (sszer kereteken bell) sorsoljuk ki). Keressk meg az v legmelegebb s leghidegebb napjt! Adjuk meg az v legmelegebb s leghidegebb hnapjt! Volte egymst kvet t nap (egy hnapon bell), amikor mnusz fokot mrtnk?

82

Megolds (11/Temperature.cs) Ehhez a feladathoz nem tartozik rsos megolds, tulajdonkppen egy minimum- s maximum-kivlasztsrl van sz, csak ppen ktdimenzis tmbben (viszont a jegyzethez csatolva van egy lehetsges megolds).

BUBORKRENDEZS
Valstsuk meg egy tmbn a buborkrendezst! Megolds (11/BubbleSort.cs) A buborkos rendezs egy rendezsi algoritmus, amelynek alapelve, hogy a kisebb elemek bubork mdjra felszivrognak, mg a nagyobb elemek lesllyednek. Ennek az algoritmusnak tbbfle implementcija is ltezik, mi most kt vltozatt is megvizsgljuk. Az els gy nz ki:
for (int i = 0; i < array.Length - 1; ++i) { for (int j = array.Length - 1; j > i; --j) { if (array[j - 1] > array[j]) { int tmp = array[j]; array[j] = array[j - 1]; array[j - 1] = tmp; } } }

Kezdjk a bels ciklussal! Ez a tmb vgrl fog visszafel menni s cserlgeti az elemeket, hogy a legkisebbet vigye tovbb magval. Legyen pl. a tmb utols nhny eleme: 10 34 5 Fogjuk az 5 t (array[j]) s sszehasonltjuk az eltte lev elemmel, ami a 34 (array[j-1]). Mivel nagyobb nla, ezrt megcserljk a kettt: 10 5 34 Ezutn cskkentjk a ciklusvltozt, ami most megint az eddigi legkisebb elemre, az 5-re fog mutatni, s cserlhetjk tovbb. Termszetesen, ha kisebb elemet tallunk, akkor ezutn t fogjuk tovbb vinni, egszen addig, amg a legkisebb elem elfoglalja a tmb els indext. Itt jn kpbe a kls ciklus, ami azt biztostja, hogy a rendezett elemeket mr ne vizsgljuk, hiszen a bels ciklus minden futsakor a tmb elejre tesszk az aktulis legkisebb elemet. Nzznk meg egy msik megoldst is:
for (int i = 1; i < array.Length; ++i) { int y = array[i]; int j = i - 1; while (j > -1 && y < array[j]) { array[j + 1] = array[j]; --j; } array[j + 1] = y; }

83

Itt lnyegben ugyanarrl van sz, csak most ellrl vizsgljuk az elemeket. Nem rt tudni, hogy a buborkos rendezs csak kis elemszm esetben hatkony, nagyjbl O(n^2) nagysgrend. Az O() (n. nagy ord) jellst hasznljuk egy algoritmus futsidejnek megbecslsre (illetve hasznljk a matematika ms terletein is).

84

OBJEKTUMORIENTLT PROGRAMOZSELMLET
A korai programozsi nyelvek nem az adatokra, hanem a mveletekre helyeztk a hangslyt, mert akkoriban mg fleg matematikai szmtsokat vgeztek a szmtgpekkel. Ahogy aztn a szmtgpek szles krben elterjedtek, megvltoztak az ignyek, az adatok pedig tl komplexekk vltak ahhoz, hogy a procedurlis mdszerrel knyelmesen s hatkonyan kezelni lehessen ket. Az els objektumorientlt programozsi nyelv a Simula 67 volt. Tervezi (Ole-Johan Dahl s Kristen Nygaard) hajk viselkedst szimulltk s ekkor jtt az tlet, hogy a klnbz hajtpusok adatait egy egysgknt kezeljk, gy egyszerstve a munkt. Az OOP mr nem a mveleteket helyezi a kzppontba, hanem az egyes adatokat (adatszerkezeteket) s a kzttk lev kapcsolatokat (hierarchit). Ebben a fejezetben az OOP elmleti oldalval foglalkozunk, a cl a paradigma megrtse, gyakorlati pldkkal a kvetkez rszekben tallkozhatunk (szintn a kvetkez rszekben tallhat meg nhny elmleti fogalom is, amelyek gyakorlati pldkon keresztl rthetbben megfogalmazhatk, ezrt ezeket csak ksbb trgyaljuk, pl. polimorfizmus).

UML
Az OOP tervezs elsegtsre hoztk ltre az UML t (Unified Modelling Language). Ez egy ltalnos tervezeszkz, a clja, hogy egy minden fejleszt ltal ismert kzs jelrendszert valstson meg. A kvetkezkben az UML eszkzeit fogjuk felhasznlni az adatok kztti relcik grafikus brzolshoz.

OSZTLY
Az OOP vilgban egy osztly olyan adatok s mveletek sszessge, amellyel lerhatjuk egy modell (vagy entits) tulajdonsgait s mkdst. Legyen pldul a modellnk a kutya llatfaj! Egy kutynak vannak tulajdonsgai (pl. letkor, sly stb.) s van meghatrozott viselkedse (pl. csvlja a farkt, jtszik stb.). Az UML a kvetkezkppen jell egy osztlyt:

Kutya
Amikor programot runk, akkor az adott osztlybl (osztlyokbl) ltre kell hoznunk egy (vagy tbb) pldnyt, ezt pldnyostsnak nevezzk. Az osztly s pldny kztti klnbsgre j plda a recept (osztly) s a stemny (pldny). A pldnyokat objektumnak is nevezik.

ADATTAG S METDUS
Egy objektumnak az letciklusa sorn megvltozhat az llapota, tulajdonsgai. Ezt az llapotot valahogy el kell tudnunk trolni, illetve biztostani kell a szksges mveleteket a tulajdonsgok megvltoztatshoz (pl. a kutya eszik (ez egy mvelet), ekkor megvltozik a jllakottsg tulajdonsga/llapota). A tulajdonsgokat trol vltozkat adattagoknak (vagy meznek), a mveleteket metdusoknak nevezzk. A mveletek sszessgt felletnek is hvjuk. Mdostsuk a diagramunkat: Kutya jollak : int eszik() : void Az adattagokat nv: tpus alakban brzoljuk, a metdusokat pedig nv(paramterlista): visszatrsi_rtk formban. Ezekkel a fogalmakkal egy ksbbi fejezet foglalkozik majd.

85

LTHATSG
Az egyes tulajdonsgokat s metdusokat nem felttlenl kell kzszemlre bocstani. Az OOP egyik alapelve, hogy a felhasznl csak annyi adatot kapjon meg, amennyi felttlenl szksges. A kutys pldban az eszik() mvelet magba foglalja a rgst, nyelst, emsztst is, de errl nem fontos tudniuk, csak az evs tnye szmt. Ugyangy egy tulajdonsg (adattag) esetben sem j, ha mindenki hozzjuk fr (az elfogadhat, ha a kzvetlen csald hozzfr a szmlmhoz, de idegenekkel nem akarom megosztani). Az s-OOP szablyai hromfle lthatsgot fogalmaznak meg (ez nyelvtl fggen bvlhet), a C# lthatsgairl a kvetkez rszekben lesz sz. A hromfle lthatsg: Public: mindenki lthatja (UML jells: +). Private: csakis az osztlyon bell elrhet, a leszrmazott osztlyok nem lthatjk s nem is mdosthatjk (a szrmaztats s rklds hamarosan jn) (UML jells: -). Protected: ugyanaz, mint a private, de a leszrmazott osztlyok mdosthatjk is (UML jells: #).

A Kutya osztly most gy nz ki: Kutya -jollak : int +eszik() : void

EGYSGBE ZRS
A hagyomnyos, nem OO programnyelvek (pl. a C) az adatokat s a rajtuk vgezhet mveleteket a program kln rszeiknt kezelik. Bevett szoks ezeket elklnteni egy nll forrsfjlba, de ez mg mindig nem elg biztonsgos. A kett kztt nincs sszerendels, ezrt ms programozk gond nlkl trhatjk egyiket vagy msikat, illetve hozzfrnek a struktrkhoz, s nem megfelelen hasznlhatjk fel azokat. Az OO paradigma egysgbe zrja az adatokat s a hozzjuk tartoz felletet, ez az n. egysgbe zrs (encapsulation vagy information hiding). Ennek egyik nagy elnye, hogy egy adott osztly bels szerkezett gond nlkl megvltoztathatjuk, mindssze arra kell figyelni, hogy a fellet ne vltozzon (pl. egy autt biztosan tudunk kormnyozni, attl fggetlenl, hogy az egy szemlyaut, traktor vagy Forma-1es gp).

RKLDS
Az rklds vagy szrmaztats az j osztlyok ltrehozsnak egy mdja. Egy (vagy tbb) mr ltez osztlybl hozunk ltre egy jat gy, hogy az minden szljnek tulajdonsgt rkli, vagy tfogalmazza azokat. A legegyszerbben egy pldn keresztl rthet meg. Legyen egy llat osztlyunk! Ez elgg tg fogalom, ezrt szkthetjk a krt, mondjuk a Gerinces llatokra. Ezen bell megklnbztethetnk Emls t vagy Hll t. Az Emls osztly egy leszrmazottja lehet a Kutya s gy tovbb. Az rkldst specializlsnak is nevezik. A specializls sorn az osztlyok kztt n. az-egy (is-a) relci ll fenn. gy amikor azt mondjuk, hogy a Kutya az egy llat akkor arra gondolunk, hogy a Kutya egy specializltabb forma, amelynek megvan a sajt karakterisztikja, de vgeredmnyben egy llat. Ennek a gondolatmenetnek a gyakorlati felhasznls sorn lesz jelentsge.

86

A diagram a fenti pldt brzolja. UML-l az rkldst res nyllal jelljk, amely a specializlt osztly fell mutat az ltalnosabbra. Az osztlyok kztt fennll kapcsolatok sszessgt hierarchinak nevezzk. Elfordul, hogy nem fontos szmunkra a bels szerkezet, csak a felletet szeretnnk trkteni, hogy az osztlyunkat fel tudja hasznlni a programunk egy msik rsze (ilyen pldul a mr emltett foreach ciklus). Ilyenkor nem egy igazi osztlyrl, hanem egy interfszrl felletrl beszlnk, amelynek nincsenek adattagjai, csakis a mveleteket deklarlja. A C# rendelkezik nll interfszekkel, de ez nem minden programnyelvre igaz, ezrt k egy hasonl szerkezetet, n. absztrakt osztlyokat hasznlnak. Ezekben elfordulnak adattagok is, de leginkbb a fellet definilsra koncentrlnak. A C# nyelvi szinten tmogat absztrakt osztlyokat is, a kett kztt ugyanis lnyegi klnbsg van. Az interfszek az osztlytl fggetlenek, csakis felletet biztostanak (pldul az IEnumerable s IEnumerator a foreachnek (is) biztostanak felletet, az nem lnyeges, hogy milyen osztlyrl van sz). Az absztrakt osztlyok viszont egy shz ktik az utdokat (erre az esetre plda az llat osztly, amelyben megadhatunk egy absztrakt evs metdust, amit az utdok megvalstanak - egy krokodil mshogy eszik, mint egy hangya, de az evs az llatokhoz kthet valami, ezrt kzs).

87

OSZTLYOK
Osztlyt a class kulcssz segtsgvel deklarlhatunk:
using System; namespace TestApp { class Dog { } class Program { static void Main(string[] args) { Console.ReadKey(); } } }

Lthat, hogy a fprogram s a sajt osztlyunk elklnl. Konvenci szerint az osztlynv mindig nagybetvel kezddik. Felmerlhet a krds, hogy honnan tudja a fordt, hogy melyik a f osztly? A helyzet az, hogy a Main specilis fggvny, ezt a fordt automatikusan felismeri s megjelli, mint a program belpsi pontjt. Igazbl lehetsges tbb Main nev metdust is ltrehozni, ekkor a fordtprogramnak meg kell adni (a /main kapcsol segtsgvel), hogy melyik az igazi belpsi pont. Ettl fggetlenl ezt a megoldst, ha csak lehet, kerljk el! Nzznk egy pldt:
using System; class Program1 { static public void Main() { Console.WriteLine("Program1"); } } class Program2 { static public void Main() { Console.WriteLine("Program2"); } }

Ezt a forrskdot gy fordthajuk le: csc /main:Program1 main.cs Futtatskor a Program1 szveget fogja kirni a program. A fenti pldban a lehet legegyszerbb osztlyt hoztuk ltre. A C++ nyelvet ismerk figyeljenek arra, hogy az osztly-deklarci vgn nincs ktelez pontosvessz, ugyanakkor a fordt nem szl rte, ha odarjuk!

88

Az osztlyunkbl a new opertor segtsgvel tudunk kszteni egy pldnyt.


Dog d = new Dog();

A new hvsakor lefut a konstruktor, megfelel nagysg hely foglaldik a memriban, ezutn pedig megtrtnik az adattagok inicializlsa is.

KONSTRUKTOROK
Minden esetben, amikor egy osztlyt pldnyostunk, egy specilis metdus a konstruktor fut le, melynek feladata, hogy belltsa az osztly rtkeit. Br a fenti osztlyunkban nem definiltunk semmi ilyesmit, ettl fggetlenl rendelkezik alaprtelmezett (azaz paramter nlkli) konstruktorral. Ez igaz minden olyan osztlyra, amelynek nincs konstruktora (amennyiben brmilyen konstruktort ltrehoztunk, akkor ez a lehetsg megsznik). Az alaprtelmezett konstruktor legelszr meghvja a sajt sosztlya alaprtelmezett konstruktort. Ha nem szrmaztattunk direkt mdon (mint a fenti programban), akkor ez a System.Object konstruktora lesz (tulajdonkppen ez elbb vagy utbb mindenkppen meghvdik, hiszen az sosztly konstruktora is meghvja a sajt st s gy tovbb). Abban az esetben, ha az sosztly nem tartalmaz alaprtelmezett konstruktort (mert van neki paramteres), akkor valamely msik konstruktort explicit mdon hvni kell a leszrmazott osztly konstruktorbl a base metdussal, minden ms esetben fordtsi hiba az eredmny.
class Base { public Base(string s) { } } class Derived : Base { }

Ez a forrskd(rszlet) nem fordul le, mivel a Base osztly csakis paramteres konstruktorral rendelkezik.
class Base { public Base(string s) { } } class Derived : Base { public Derived() : base("abc") { } }

gy mr viszont mkdni fog. A base nem sszekeverend a Base nev osztllyal, ez egy nll metdus, amely minden esetben az sosztly valamely konstruktort hvja. Az alaprtelmezett konstruktor valamilyen formban minden esetben lefut, akkor is, ha az osztlyban deklarltunk paramterest, hiszen tovbbra is ez felel a memriafoglalsrt. Egy osztly pldnyostshoz a pldnyostst vgz programrsz szmra lthat kell legyen a pldnyostand osztly konstruktora.

89

Az adattagok ha vannak automatikusan a nekik megfelel nullrtkre inicializldnak(pl: int-> 0, bool>false, referencia- s nullable tpusok ->null). A C++ nyelvet ismerk vigyzzanak, mivel itt csak alaprtelmezett konstruktort kapunk automatikusan, rtkad opertort illetve msol konstruktort nem! Ugyanakkor minden osztly a System.Objectbl szrmazik (mg akkor is, ha erre nem utal semmi), ezrt nhny metdust (pldul a tpus lekrdezshez) a konstruktorhoz hasonlan azonnal hasznlhatunk. Jelen pillanatban az osztlyunkat semmire nem tudjuk hasznlni, ezrt ksztsnk hozz nhny adattagot s egy konstruktort:
using System; namespace TestApp { class Dog { private string name; private int age; public Dog(string name, int age) { this.name = name; this.age = age; } } class Program { static void Main(string[] args) { Dog d = new Dog("Fli", 2); Console.ReadKey(); } } }

A konstruktor neve meg kell, hogy egyezzen az osztly nevvel, s semmilyen visszatrsi rtke nem lehet. A mi konstruktorunk kt paramtert vr, a nevet s a kort (metdusokkal s paramtereikkel a kvetkez rsz foglalkozik bvebben). Egy osztlynak paramterlisttl fggen brmennyi konstruktora lehet, s egy konstruktorbl hvhatunk egy msikat a thisszel:
class Test { public Test() : this(10) { } public Test(int x) { } }

Ha tbb konstruktor is van, akkor a paramter tpushoz leginkbb illeszked fut le. A pldban a konstruktor trzsben rtket adtunk a mezknek a this hivatkozssal, amely mindig arra a pldnyra mutat, amelyen meghvtk (a this kifejezs gy minden olyan helyen hasznlhat, ahol az osztlypldnyra van szksg). Nem ktelez hasznlni, ugyanakkor hasznos lehet, hogyha sok adattag/metdus van, illetve ha a paramterek neve megegyezik az adattagokval. A fordtprogram automatikusan odakpzeli magnak a fordts sorn, gy mindig tudja, mivel dolgozik.

90

Az adattagok private elrsek (ld. elmleti rsz), azaz most csakis az osztlyon bell hasznlhatjuk s mdosthatjuk ket, pldul a konstruktorban. Nemcsak a konstruktorban adhatunk rtket a mezknek, hanem hasznlhatunk n. inicializlkat is:
class Dog { private string name = "Rex"; private int age = 5; public Dog(string name, int age) { this.name = name; this.age = age; } }

Az inicializls mindig a konstruktor eltt fut le, ez egyben azt is jelenti, hogy az utbbi fellbrlhatja. Ha a Dog osztlynak ezt a mdostott vltozatt hasznltuk volna fentebb, akkor a pldnyosts sorn minden esetben fellrnnk a kutya alaprtelmezetten megadott - kort. Az inicializls sorrendje megegyezik a deklarls sorrendjvel (fellrl lefel halad). A konstruktorok egy specilis vltozata az n. msol- vagy copy-konstruktor. Ez paramtereknt egy sajt magval megegyez tpus objektumot kap, s annak rtkeivel inicializlja magt. Msol konstruktort ltalban az rtkad opertorral szoktak implementlni, de az opertor-kiterjeszts egy msik fejezet tmja, gy most egyszerbben oldjuk meg:
class Dog { private string _name = "Rex"; private int _age = 5; public Dog(string name, int age) { this.name = name; this.age = age; } public Dog(Dog otherDog) : this(otherDog.name, otherDog.age) { } }

A program els rnzsre furcsa lehet, mivel privt elrhetsg tagokat hasznlunk, de ezt minden gond nlkl megtehetjk, mivel a C# a privt elrhetsget csak osztlyon kvl rvnyesti, ugyanolyan tpus objektumok ltjk egymst. Most mr hasznlhatjuk is az j konstruktort:
Dog d = new Dog("Fli", 2);; Dog e = new Dog(d);

ADATTAGOK
Az adattagok vagy mezk olyan vltozk, amelyeket egy osztlyon (vagy struktrn) bell deklarltunk. Az adattagok az osztlypldnyhoz tartoznak (azaz minden egyes pldny klnll adattagokkal rendelkezik),

91

vele szletnek s halnak is meg. Az eddigi pldinkban is hasznltunk mr adattagokat, ilyenek voltak a Dog osztlyon belli name s age vltozk. Az adattagokon hasznlhatjuk a const tpusmdostt is, ekkor a deklarcinl rtket kell adnunk a meznek, hasonlan az elz fejezetben emltett inicializlshoz. Ezek a mezk pontosan ugyangy viselkednek, mint a hagyomnyos konstansok. Egy konstans mezt nem lehet statikusnak (statikus tagokrl hamarosan) jellni, mivel a fordt egybknt is gy fogja kezelni (ha egy adat minden objektumban vltozatlan, felesleges minden alkalommal kln pldnyt kszteni belle), vagyis minden konstans adattagbl globlisan minden pldnyra vonatkozan egy darab van. A mezkn alkalmazhat a readonly mdost is, ez kt dologban klnbzik a konstansoktl: az rtkads elhalaszthat a konstruktorig, s az rtkl adott kifejezs eredmnynek nem szksges ismertnek lennie fordtsi idben.

LTHATSGI MDOSTK
Korbban beszltnk az OOP lthatsgairl. A C# az alap hromhoz mg kettt hozztesz: public: az osztlyon/struktrn kvl s bell teljes mrtkben hozzfrhet. private: csakis a tartalmaz osztlyon bell osztlyok/struktrk esetben az alaprtelmezs. lthat, a leszrmazottak sem lthatjk,

protected: csakis a tartalmaz osztlyon s leszrmazottain bell lthat. internal: csakis a tartalmaz (s a bart) assembly(ke)n bell lthat. protected internal: a protected s internal keverke.

Ezek kzl leggyakrabban az els hrmat fogjuk hasznlni.

PARCILIS OSZTLYOK
C# nyelvben ltrehozhatunk n. parcilis (darab, tredk) osztlyokat (partial class), ha egy osztlydeklarciban hasznljuk a partial kulcsszt (ezt minden darabnl meg kell tennnk). Egy parcilis osztly defincija tbb rszbl (tipikusan tbb forrsfjlbl) llhat. Egy parcilis osztly minden tredknek ugyanazzal a lthatsgi mdostval kell rendelkeznie, valamint az egyik rsznl alkalmazott egyb mdostk (pl. abstract), illetve az sosztly deklarci a teljes osztlyra (rtsd: minden tredkre) rvnyes lesz (ebbl kvetkezik, hogy ezeket nem ktelez feltntetni minden darabnl). Ugyanakkor ennl az utols felttelnl figyelni kell arra, hogy ne adjunk meg egymsnak ellentmond mdostkat (pl. egy osztly nem kaphat abstract s sealed mdostkat egy idben). Nzznk egy pldt:
// main.cs using System; partial class PClass { } class Program { static public void Main()

92

{ PClass p = new PClass(); p.Do(); } }

Lthat, hogy egy olyan metdust hvtunk, amelynek hinyzik a deklarcija. Ksztsnk egy msik forrsfjlt is:
// partial.cs using System; partial class PClass { public void Do() { Console.WriteLine("Hello!"); } }

A kt fjlt gy tudjuk fordtani: csc main.cs partial.cs A .NET a parcilis osztlyokat fknt olyan esetekben hasznlja, amikor az osztly egy rszt a fordt generlja (pl. a grafikus fellet alkalmazsoknl a kezdeti belltsokat az InitializeComponent metdus vgzi, ezt teljes egszben a fordt kszti el). Ennek a megoldsnak az a nagy elnye, hogy knnyen ki tudjuk egszteni ezeket a generlt osztlyokat. Brmelyik osztly (teht a nem-parcilis is) tartalmazhat begyazott parcilis osztlyt, ekkor rtelemszeren a tredkek a tartalmaz osztlyon bell kell legyenek (ugyanez nem vonatkozik a parcilis osztlyon bell lv parcilis osztlyokra, ott a begyazott osztlyok tredkei sztoszolhatnak a tartalmaz osztly darabjai kztt). Egy parcilis osztly darabjainak ugyanabban az assemblyben kell lennik. A C# 3.0 mr engedlyezi parcilis metdusok hasznlatt is, ekkor a metdus deklarcija s defincija sztoszlik:
partial class PClass { partial void Do(); } partial class PClass { partial void Do() { Console.WriteLine("Hello!"); } }

Parcilis metdusnak nem lehet elrhetsgi mdostja (pp ezrt minden esetben private elrs lesz) valamint void-dal kell visszatrnie. A partial kulcsszt ilyenkor is ki kell tenni minden elfordulsnl. Csakis parcilis osztly tartalmazhat parcilis metdust.

93

BEGYAZOTT OSZTLYOK
Egy osztly tartalmazhat metdusokat, adattagokat s ms osztlyokat is. Ezeket a bels osztlyokat begyazott (nested) osztlynak nevezzk. Egy ilyen osztlyt ltalban elrejtnk, de ha mgis publikus elrsnek deklarljuk, akkor a kls osztlyon keresztl rhetjk el. A begyazott osztlyok alaprtelmezs szerint privt elrsek.
class Outer { class Inner { // a begyazott osztly nem lthat } } class Outer { public class Inner { // gy mr ltszik } } // ... Outer.Inner x = new Outer.Inner(); // pldnyosts

Egy begyazott osztly hozzfr az t tartalmaz osztlypldny minden tagjhoz (belertve a private elrs tagokat s ms begyazott osztlyokat is), de csakis akkor, ha a begyazott osztly trol egy, a kls osztlyra hivatkoz referencit:
class Outer { private int value = 11; private Inner child; public Outer() { child = new Inner(this); } public void Do() { child.Do(); } class Inner { Outer parent; public Inner(Outer o) { parent = o; } public void Do() { Console.WriteLine(parent.value); } } }

94

OBJEKTUMINICIALIZLK
A C# 3.0 objektumok pldnyostsnak egy rdekesebb formjt is tartalmazza:
using System; namespace TestApp { class Person { public Person() { } private string name; public string Name { get { return name; } set { name = value; } } } class Program { static void Main(string[] args) { Person p = new Person() { Name = "Istvn" }; Console.WriteLine(p.Name); Console.ReadKey(); } } }

Ilyen esetekben vagy egy nyilvnos tagra, vagy egy tulajdonsgra hivatkozunk (ez utbbit hasznltuk). Termszetesen, ha ltezik paramteres konstruktor, akkor is hasznlhatjuk ezt a belltsi mdot.

DESTRUKTOROK
A destruktorok a konstruktorokhoz hasonl specilis metdusok, amelyek az osztly ltal hasznlt erforrsok felszabadtsrt felelsek. A .NET n. automatikus szemtgyjt (garbage collector) rendszert hasznl, amelynek lnyege, hogy a hivatkozs nlkli objektumokat (nincs rjuk mutat rvnyes referencia) a keretrendszer automatikusan felszabadtja.
Person p = new Person(); // mc egy Person objektumra mutat p = null; // az objektumra mr nem mutat semmi, felszabadthat

Objektumok alatt ebben a fejezetben csak s kizrlag referenciatpusokat rtnk, az rtktpusokat nem a GC kezeli. A szemtgyjt mkdse nem determinisztikus, azaz elre nem tudjuk megmondani, hogy mikor fut le, ugyanakkor kzzel is meghvhat, de ez nem ajnlott. A kvetkez pldban foglalunk nmi memrit, majd megvizsgljuk, hogy mi trtnik felszabadts eltt s utn:

95

using System; namespace TestApp { class Program { static void Main(string[] args) { Console.WriteLine("Foglalt memria: {0}", GC.GetTotalMemory(false)); for (int i = 0; i < 10; ++i) { int[] x = new int[1000]; } Console.WriteLine("Foglalt memria: {0}", GC.GetTotalMemory(false)); GC.Collect(); // meghvjuk a szemtgyjtt Console.WriteLine("Foglalt memria: {0}", GC.GetTotalMemory(false)); Console.ReadKey(); } } }

A GC osztly GetTotalMemory fggvnye a program ltal lefoglalt byte-ok szmt adja vissza, paramterknt pedig megadhatjuk, hogy meg szeretnnk-e hvni a szemtgyjtt. A fenti program kimenete valami ilyesmi lesz, az rtkek persze vltozhatnak: Foglalt memria: 21060 Foglalt memria: 70212 Foglalt memria: 27144 Vegyk szre, hogy a ciklusban ltrehozott tmbk minden ciklus vgn eltntethetek, mivel nincs tbb rjuk hivatkoz referencia (hiszen a loklis objektumok hatkre ott vget r)! De hogyan is mkdik a szemtgyjt? A .NET n. genercis garbage collector-t hasznl, amely abbl a feltevsbl indul ki, hogy a legfrissebben ltrehozott objektumok lesznek leghamarabb felszabadthatak (ez az n. genercis hipotzis) (gondoljunk csak a loklis vltozkra, amelyeket viszonylag sokat hasznlunk). Ez alapjn a kvetkez trtnik: minden friss objektum a nulladik legfiatalabb generciba kerl. Amikor eljn a szemtgyjts ideje, a GC elszr ezt a genercit vizsglja meg, s ha tall hivatkozs nlkli objektumot azt trli (pontosabban az elfoglalt memriaterletet szabadnak jelli), a maradkot pedig trakja az els generciba. Ezutn sorban tvizsglja a tbbi genercit (mg kett van), s elvgzi a megfelel mdostsokat. rtelemszeren a msodik genercis objektumok akkor trldnek, ha a program megll (illetve elfogyhat a memria is, ekkor OutOfMemoryException kivtel keletkezik). Az egyes genercik sszefgg memriaterleten vannak, gy kevesebbet kell dolgoznia a gyjtnek. Az is rdekes krds, hogy honnan tudja a GC, hogy melyik objektumok feleslegesek. Ehhez be kell vezetnnk kt fogalmat, a gyenge illetve ers referencik (weak- s strong-reference) intzmnyt: Minden olyan objektumra, amelyet a new opertorral hozunk ltre, ers referencival hivatkozunk, ezek normlis objektumok, amelyek akkor s csakis akkor kerlnek hatkrn kvlre (s takarthatak el a GC ltal), ha nincs rjuk hivatkoz rvnyes referencia.

96

A gyenge referencik ennek pp ellenkezjt nyjtjk, brmikor trlhet az ltaluk mutatott objektum, ha nincs elg memria akkor is, ha ltezik r mutat rvnyes gyenge hivatkozs.

A .NET nyelvi szinten tmogatja a gyenge referencikat:


int[][] array = new int[10][]; for (int i = 0; i < 10; ++i) { array[i] = new int[1000]; } WeakReference wr = new WeakReference(array); array = null;

Ebben a pldban miutn az eredeti tmbhivatkozst nullra lltottuk, a tmb objektumokra mr csak gyenge referencia mutat, azaz brmikor eltakarthat. Egy WeakReference objektumbl visszanyerhet az eredeti ers referencia a Target tulajdonsg segtsgvel, viszont ne felejtsk el ellenrizni ennek nullrtkt, mert lehet, hogy mr tment rajta a GC (s konvertlnunk is kell, mivel object tpussal tr vissza):
WeakReference wr = new WeakReference(array); array = null; if (wr.Target != null) { int[][] array = (int[][])wr.Target; }

A msik fogalom, amelyet ismernnk kell, az az n. application root objektumok. Ezek olyan objektumok, amelyekrl felttelezhetjk, hogy elrhetek (ilyen objektumok lesznek pl. az sszes loklis s globlis vltoz). A GC mindig a root objektumokat vizsglja meg elszr, s rajtuk keresztl pti fel a memriatrkpet. Most mr tisztban vagyunk az alapokkal, vizsgljuk meg, hogy mi trtnik valjban! Azt mondtuk, hogy a GC tvizsglja a genercikat, ennek azonban van egy kis htrnya, mgpedig az, hogy lass. Ahhoz, hogy a takarts valban hatkony legyen, fel kell fggeszteni a program futst, vagy azzal prhuzamosan dolgozni. Mindkt esetben rosszul jrunk, hiszen vagy lefagy a program egy idre, vagy kevesebb erforrshoz jut (ugyanakkor azt is szmtsba kell venni, hogy a GC a lehet legjobb idben rtsd: akkor, amikor a program a legkevesebb erforrst hasznlja fog beindulni, teht nem felttlenl fog igazn nagy gondot jelenteni az alkalmazs szempontjbl). Nyilvn tbbmagos processzorral szerelt PC knl jobb a helyzet, de attl mg fennll a hatkonysg problmja. pp ezrt, ahelyett, hogy minden alkalommal teljes vizsglatot vgezne a GC, bevezettk a rszleges takarts fogalmt, amely a kvetkez feltevsre pl: az egyes illetve kettes genercikban lv objektumok nagy valsznsggel nem mdosultak, vagyis feltehetjk, hogy van rjuk hivatkoz referencia (gondoljunk arra, hogy pl. a loklis vltozk szinte soha nem fognak tkerlni mg az egyes generciba sem, vagyis az egyes s kettes generci tagjai tipikusan hossz let objektumok lesznek). Termszetesen ezt nem tudhatjuk biztosan, ezrt minden .NET alkalmazshoz automatikusan ltrejn egy adatszerkezet (kpzeljk el tmbknt), amelynek egyes indexei a memria egy bizonyos nagysg terletnek llapott mutatjk (nem az objektumokt!). Teht eljn a GC ideje, tvlogatja a nulladik genercit, majd fogja a fenti adatszerkezetet (n. card table), s megvizsgl minden olyan objektumot, amely olyan memriaterleten fekszik, amelyet a card table mdostottnak jellt. Ez drmaian megnveli a GC hatkonysgt, hiszen a teljes felhasznlt memrinak csak tredkt kell megvizsglnia. A GC a nevvel ellenttben nem csak ennyit tesz, valjban az dolga az objektumok teljes letciklusnak a kezelse s a memria megfelel szervezse is. Amikor elindtunk egy .NET programot, akkor a GC elsknt szabad memrit kr az opercis rendszertl (a .NET n. szegmensekre osztja a memrit, minden szegmens 16 MB mret), mgpedig ktszegmensnyit: egyet a hagyomnyos objektumoknak (GC Heap) s egyet a nagymret (100+ kilobyte) objektumoknak (LOH

97

Large Object Heap, ezt csakis teljes takartsnl vizsglja a GC). Ezutn nyugodtan kszthetnk objektumokat, mert ha elfogy a hely, a GC automatikusan j szegmenseket fog ignyelni. Azt gondoln az ember, hogy ennyi az egsz, de minden objektum letben eljn a pillanat, amikor visszaadja a lelkt a teremtjnek, nevezetesen a GCnek. Ilyenkor r hrul az a rendkvl fontos feladat is, hogy rendbe rakja a memrit. Mit is rtnk ez alatt? Hatkonysg szempontjbl az a legjobb, ha az osztlypldnyok egymshoz kzel lehetleg egyms mellett vannak a memriban. pp ezrt a GC minden (f)gyjtciklus (teht teljes takarts) alkalmval tmozgatja az objektumokat, hogy a lehet leghatkonyabban kezelhessk ket. Ennek a megoldsnak egy htultje, hogy ilyen mdon nem hasznlhatunk unmanaged kdot, mivel ez teljes mrtkben megakadlyozza a pointermveleteket. A megoldst az objektumok rgztse (n. pinning) jelenti, errl egy ksbbi fejezet szmol be. A managelt kd ppen a fenti tnyek miatt tudja felvenni a versenyt a natv programokkal. St, olyan alkalmazsok esetben, ahol sokszor foglalunk s szabadtunk fel memrit, a natv kd htrnyba is kerl(het). sszessgben azt mondhatjuk, hogy natv s managelt program kztt nincs nagy klnbg sebessg tekintetben. A GC hromfle mdban tud mkdni: A GC prhuzamosan fut az alkalmazssal A GC felfggesztheti az alkalmazst Szerver md

Nzzk az elst: az objektumok alloklst egy klnll szl vgzi, ha szksg van tiszttsra, akkor a program tbbi szlt csak nagyon rvid ideig fggeszti fel, s a takartssal egyidejleg a program tovbbra is helyet foglalhat a memriban, kivve, ha az tlpte a maximlisan kiszabott keretet. Ez a limitls a nulladik genercira vonatkozik, teht azt szabja meg, hogy mennyi memrit hasznlhat fel egyszerre a G0 (amennyiben ezt az rtket elrjk, a GC beindul). Ezt a mdszert olyan alkalmazsoknl hasznljuk, amikor fontos, hogy felhasznli fellet reszponzv maradjon. Ez az alaprtelmezett md. Hasonlan mkdik a msodik is, viszont teljes mrtkben lelltja az alkalmazst (n. Stop-The-World mdszer) a tisztts idejre. Ez a md sokkal kisebb G0 kerettel rendelkezik. Szerver mdban minden egyes processzor kln heappel s GCvel rendelkezik. Ha egy alkalmazs kifut a memribl, szl a GCnek, amely felfggeszti a program futst a tisztts idejre. Ha meg akarjuk vltoztatni egy program GC mdjt, szksgnk lesz egy konfigurcis fjlra (errl egy ksbbi fejezetben), amely a kvetkezket tartalmazza (a pldban kikapcsoljuk a prhuzamos futst): <configuration> <runtime> <gcConcurrent enabled="false"/> </runtime> </configuration> Vagy: <configuration> <runtime> <gcServer enabled="true"/> </runtime> </configuration> Ezzel pedig a szervermdot lltottuk be.

98

Most pedig megnzzk, hogy hogyan hasznlhatjuk a GC t a gyakorlatban. A konstruktor(ok) mellett egy msik specilis metdust is kaphat minden referenciatpus, ez pedig a destruktor. Ugyanakkor a konstruktortl eltren destruktor nem jn ltre automatikusan, csakis ha magunk definiltuk. A GC megsemmists eltt az objektumokon meghvja a hozzjuk tartoz destruktort, ms nven Finalizer-t. Ennek a metdusnak a feladata, hogy felszabadtsa az osztly ltal hasznlt erforrsokat (pl., hogy lezrja a hlzati kapcsolatokat, bezrjon minden egyes megnyitott fjlt stb.). Vegyk a kvetkez kdot:
using System; namespace TestApp { class DestructableClass { public DestructableClass() { Console.WriteLine("Konstruktor"); } ~DestructableClass() { Console.WriteLine("Destruktor"); } } class Program { static void Main(string[] args) { DestructableClass dc = new DestructableClass(); Console.ReadKey(); } } }

A destruktor neve tilde jellel (~) kezddik, neve megegyezik az osztlyval, s nem lehet semmilyen mdostja vagy paramtere (rtelemszeren egy destruktor mindig privt elrhetsg lesz, vagyis kzvetlenl soha nem hvhatjuk, ez csakis a GC eljoga). Soha ne ksztsnk res destruktort, mivel a GC minden destruktorrl bejegyzst kszt, s mindenkppen meghvja mindegyiket akkor is, ha res, vagyis ez felesleges metdushvs lenne. Ha lefordtjuk ezt a kdot s elindtjuk a programot, elszr a Konstruktor szt fogjuk ltni, majd egy gomb lenyomsa utn megjelenik a prja is. Ha ltni is akarjuk, nem rt parancssorbl futtatni, de egy villansnyi idre Visual Studio-bl futtatva is lthat. A fenti kd valjban a kvetkez formban ltezik:
class DestructableClass { public DestructableClass() { Console.WriteLine("Konstruktor"); } protected override void Finalize() { try { Console.WriteLine("Destruktor"); } finally {

99

base.Finalize(); } } }

Ez a forrskd csak plda, nem fordul le, mivel a Finalize metdust nem definilhatjuk fell, erre val a destruktor. A Finalize-t minden referenciatpus rkli a System.Objecttl. Elszr felszabadtja az osztly erforrsait (a destruktorban ltalunk megszabott mdon), azutn meghvja az sosztly Finalize metdust (ez legalbb a System.Object destruktora lesz) s gy tovbb, amg a lnc vgre nem r:
using System; namespace TestApp { class Base { ~Base() { Console.WriteLine("Base"); } } class Derived : Base { ~Derived() { Console.WriteLine("Derived"); } }

class Program { static void Main(string[] args) { Derived d = new Derived(); Console.ReadKey(); } } }

A destruktorokra vonatkozik nhny szably, ezek a kvetkezk: Egy osztlynak csak egy destruktora lehet A destruktor nem rklhet A destruktort nem lehet direkt hvni, a hvs mindig automatikusan trtnik Destruktora csakis osztlynak lehet, struktrnak nem

Legtbbszr felesleges destruktort kszteni, ez csak nhny specilis esetben szksges, pl. amikor valamilyen unmanaged erforrst (memria, fjl stb...) hasznlunk. IDisposable Az IDisposable interfsz segtsgvel egy osztly ltal hasznlt erforrsok felszabadtsa kzzel elre meghatrozott idpontban is megtrtnhet, teht nem kell a GC re vrni.
using System;

100

namespace TestApp { class DisposableClass : IDisposable { public void Dispose() { // Takartunk GC.SuppressFinalize(this); } } class Program { static void Main(string[] args) { DisposableClass dc = new DisposableClass(); Console.ReadKey(); } } }

Az interfsz ltal deklarlt Dispose metdusban meg kell hvnunk a GC.SuppressFinalize metdust, hogy jelezzk a GCnek, hogy ez az osztly mr felszabadtotta az erforrsait, s nem kell destruktort hvnia. A fenti kdban egy kicsit csaltunk: a SuppressFinalize csak akkor kell, ha valban definiltunk destruktort, egybknt felesleges. Destruktorok hasznlata helyett ltalban az n. Dispose tervezsi mintt alkalmazzuk, amely megvalstshoz nyilvn az IDisposable interfsz lesz segtsgnkre.
class DisposableClass : IDisposable { private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if(!disposed) { if(disposing) { // managed erforrsok felszabadtsa } // unmanaged erforrsok felszabadtsa disposed = true; } } ~DisposableClass() { Dispose(false); } }

101

Ebben a forrskdban kt Dispose metdust ksztettnk, az els paramter nlkli az, amit az interfsztl kaptunk, mg a msik arra szolgl, hogy sszehangoljuk a GC munkjt a kzi erforrs-felszabadtssal. Ha a msodik metdus paramtere true rtk a hvskor, akkor tudjuk, hogy kzzel hvtuk a Dispose metdust, vagyis mind a menedzselt, mind a natv erforrsok felszabadthatak. Ha a paramter rtke false, akkor pedig a hvs a destruktorbl szrmazik, vagyis csak az unmanaged erforrsokkal kell trdnnk. Az IDisposable interfszt megvalst osztlyok hasznlhatak n. using blokkban, ami azt jelenti, hogy a blokk hatkrn kvlre rve a Dispose metdus automatikusan meghvdik:
using System; namespace TestApp { class DisposableClass : IDisposable { public void Dispose() { Console.WriteLine("Takartunk..."); GC.SuppressFinalize(this); } } class Program { static void Main(string[] args) { using (DisposableClass dc = new DisposableClass()) { } Console.ReadKey(); } } }

A program kirja a Takartunk szveget, ahogy elhagyja a vezrls a using blokkot. A legtbb I/O mvelettel (fjl- s hlzatkezels) kapcsolatos osztly megvalstja az IDisposable t, ezrt ezeket ajnlott mindig using blokkban hasznlni.

102

METDUSOK
Az objektumorientlt programozsban egy metdus olyan programrsz, amely vagy egy objektumhoz, vagy egy osztlyhoz kthet. Elbbi az n. osztly metdus, utbbi pedig a statikus metdus. Ebben a fejezetben az osztly (instance) metdusokrl lesz sz. Egy metdussal megvltoztathatjuk egy objektum llapott, vagy informcit kaphatunk annak adatairl. Optimlis esetben egy adattaghoz csakis metdusokon keresztl frhetnk hozz (ez akkor is igaz, ha ltszlag nem gy trtnik, pl. minden opertor valjban metdus formjban ltezik, ezt majd ltni fogjuk). Bizonyra szeretnnk, ha a korbban elksztett kutya osztlyunk nemcsak lgna a semmiben, hanem tenne is valamit. Ksztsnk nhny metdust a legfontosabb mveletekhez: az evshez s az alvshoz:
using System; namespace TestApp { class Dog { string name; int age; public Dog(string name, int age) { this.name = name; this.age = age; } public void Eat() { Console.WriteLine("A kutya eszik..."); } public void Sleep() { Console.WriteLine("A kutya alszik..."); } } class Program { static void Main(string[] args) { Dog d = new Dog("Fli", 4); d.Eat(); d.Sleep(); Console.ReadKey(); } } }

Nzzk meg, hogyan pl fel egy metdus! Elsknt megadjuk a lthatsgot, s itt is rvnyes a szably, hogy ennek hinyban az alaprtelmezett privt elrs lesz rvnyben. Ezutn a visszatrsi rtk tpusa ll, jelen esetben a voiddal jeleztk, hogy nem vrunk ilyesmit. Kvetkezik a metdus neve, ez konvenci szerint nagybetvel kezddik, vgl a sort a paramterlista zrja. Egy metdust az objektum neve utn rt pont opertorral hvhatunk meg (ugyanez rvnyes a publikus adattagokra, tulajdonsgokra stb. is).

103

A hagyomnyos procedurlis programozs (pl. a C vagy Pascal nyelv) a metdusokhoz hasonl, de filozfijban ms eszkzket hasznl, ezek a fggvny (function) s az eljrs (procedure). Mi a klnbsg? Azt mondtuk, hogy a metdusok egy osztlyhoz kthetek, annak letciklusban jtszanak szerepet. Nzznk egy pldt:
using System; namespace TestApp { class NewString1 { private string aString; public NewString1(string s) { this.aString = s; } public void PrintUpper() { Console.WriteLine(this.aString.ToUpper()); } } class NewString2 { public void PrintUpper(string s) { Console.WriteLine(s.ToUpper()); } } class Program { static void Main(string[] args) { NewString1 ns1 = new NewString1("baba"); NewString2 ns2 = new NewString2(); ns1.PrintUpper(); ns2.PrintUpper("baba"); Console.ReadKey(); } } }

Pontosan ugyanaz trtnik mindkt esetben, de van egy nagy klnbsg. Az els osztly valdi osztly: adattaggal, konstruktorral stb. Van llapota, vgezhetnk rajta mveleteket. A msodik osztly nem igazi osztly, csak egy doboz, amelyben egy teljesen nll, egyedl is letkpes szerkezet van, mindssze azrt kell az osztly-definci, mert egybknt nem fordulna le a program (ebben az esetben egy statikus metdust kellett volna ksztennk, errl hamarosan). Az els osztlyban metdust definiltunk, a msodikban eljrst (eljrs s fggvny kztt a lnyegi klnbsg, hogy utbbinak van visszatrsi rtke).

104

PARAMTEREK
Az objektummal val kommunikci rdekben kpesnek kell lennnk kvlrl megadni adatokat, vagyis paramtereket. A paramterek szmt s tpusait a metdus deklarcijban, vesszvel elvlasztva adjuk meg. Egy metdusnak gyakorlatilag brmennyi paramtere lehet, de klszably, hogy kt paramternl tbbet csak kivteles esetben hasznljunk. A metdus nevt s paramterlistjt alrsnak, szignatrnak vagy prototpusnak nevezzk. Egy osztly brmennyi azonos nev metdust tartalmazhat, ameddig a paramterlistjuk klnbzik. A paramterek a metduson bell loklis vltozkknt viselkednek, s a paramter nevvel hivatkozunk rjuk.
using System; namespace TestApp { class Test { public void Method(string param) { Console.WriteLine("A paramter: {0}", param); } } class Program { static void Main(string[] args) { Test t = new Test(); t.Method("Paramter"); Console.ReadKey(); } } }

A C# nyelvben paramtereket tadhatunk rtk s cm szerint is. Elbbi esetben teljesen j pldny jn ltre az adott osztlybl, amelynek rtkei megegyeznek az eredetivel. A msik esetben egy az objektumra mutat referencia addik t, teht az eredeti objektummal dolgozunk. Az rtk- s referenciatpusok klnbzen viselkednek az tads szempontjbl. Az rtktpusok alaprtelmezetten rtk szerint addnak t, mg a referenciatpusoknl a cm szerinti tads az elre meghatrozott viselkeds. Utbbi esetben van azonban egy kivtel, mgpedig az, hogy mg a referenciatpus rtkeit megvltoztathatjuk (s ez az eredeti objektumra is hat), addig magt a referencit mr nem, teht nem kszthetnk j pldnyt, amelyre az tadott referencia mutat. Ha ezt mgis megtesszk, az nem eredmnyez fordtsi hibt, de a vltozs csakis a metduson bell lesz szlelhet. Erre a magyarzat nagyon egyszer: mr emltettk, hogy egy metdusparamter loklis vltozknt viselkedik, vagyis ebben az esetben egyszeren egy loklis referencival dolgoznnk.
using System; namespace TestApp { class Test { public int x = 10; public void TestMethod(Test t) { t = new Test(); t.x = 11; }

105

} class Program { static void Main(string[] args) { Test t = new Test(); Console.WriteLine(t.x); // 10; t.TestMethod(t); Console.WriteLine(t.x); // 10 Console.ReadKey(); } } }

Ha mgis mdostani akarjuk egy referenciatpus referencijt, akkor kln jeleznnk kell azt, hogy valdi referenciaknt akarjuk tadni. Ktflekppen adhatunk t paramtert referencia szerint. Az els esetben az tadott objektumnak inicializltnak kell lennie (teht mindenkppen mutatnia kell valahov, hasznlnunk kellett a new opertort). Ha ezt nem tettk meg, attl a program mg lefordul, de a metdus hvsakor kivtelt fogunk kapni (NullReferenceException). A referencia szerinti tadst a forrskdban is jellni kell, mind a metdus prototpusnl, mind a hvs helyn a ref mdostval. Referenciatpust gyakorlatilag soha nem kell ilyen mdon tadnunk (persze nincs megtiltva, de gondos tervezssel elkerlhet), kivtelt kpez, ha ezt valamilyen .NETen kvli eszkz megkveteli (a lnyeg, hogy mr alloklt objektumra mutat referencit optimlis esetben nem lltunk mshov). A ref rtktpusok esetben mr sokkal hasznosabb, nzzk a kvetkez forrskdot:
using System; namespace TestApp { class Test { public void Swap(int x, int y) { int tmp = x; x = y; y = tmp; } } class Program { static void Main(string[] args) { int x = 10; int y = 20; Test t = new Test(); t.Swap(x, y); Console.WriteLine("x = {0}, y = {1}", x, y); Console.ReadKey(); } } }

106

A Swap eljrssal megprbljuk felcserlni x s y rtkeit. Azrt csak prbljuk, mert int tpusok (mivel rtktpusrl van sz) rtk szerint addnak t, vagyis a metdus belsejben teljesen j vltozkkal dolgozunk. rjuk t egy kicsit a forrst:
using System; namespace TestApp { class Test { public void Swap(ref int x, ref int y) { int tmp = x; x = y; y = tmp; } } class Program { static void Main(string[] args) { int x = 10; int y = 20; Test t = new Test(); t.Swap(ref x, ref y); Console.WriteLine("x = {0}, y = {1}", x, y); Console.ReadKey(); } } }

Most mr az trtnik, amit szeretnnk: x s y rtke megcserldtt. Egy rdekesebb mdszer kt szm megcserlsre: hasznljuk a kizr vagy opertort, ami akkor ad vissza igaz rtket, ha a kt operandusa kzl pontosan az egyik igaz! Nzzk elszr a kdot:
public void Swap(ref int x, ref int y) { if (x != y) { x ^= y; y ^= x; x ^= y; } }

A kt szmot rjuk fel kettes szmrendszerben: x (= 10) = 01010 s y (= 20) =10100! Most lssuk, hogy mi trtnik! Az els sor: 01010 10100 XOR --------11110 (ez lesz most x) A msodik sor: 10100

107

11110 XOR -------01010 (ez most y, ez az rtk a helyn van) Vgl a harmadik sor: 11110 01010 XOR --------10100 (ksz vagyunk) Hogy ez a mdszer mirt mkdik, azt mindenki gondolja t maga, egy kis segtsg azrt jr: felhasznljuk a XOR kvetkez tulajdonsgait: Kommutatv: A XOR B = B XOR A Asszociatv: (A XOR B) XOR C = A XOR (B XOR C) Ltezik neutrlis elem (jelljk NE vel): A XOR NE = A Minden elem sajt maga inverze: A XOR A = 0 (ez az llts az oka annak, hogy ellenriznnk kell, hogy x s y ne legyen egyenl) Br ez az eljrs hatkonyabbnak tnik, igazbl ez egy hagyomnyos PC n mg lassbb is lehet, mint az tmeneti vltozt hasznl trsa. A cm szerinti tads msik formjban nem inicializlt paramtert is tadhatunk, de ekkor felttel, hogy a metduson bell lltsuk be (tadhatunk gy mr inicializlt paramtert is, de ekkor is felttel, hogy j objektumot ksztsnk). A hasznlata megegyezik a reffel, azaz a szignatrban s a hvsnl is jelezni kell a szndkunkat. A hasznland kulcssz az out:
using System; namespace TestApp { class Init { public void TestInit(out Test t) { t = new Test() { s = "Hello!" }; } } class Test { public string s = null; } class Program { static void Main(string[] args) { Test t = null; Init i = new Init(); i.TestInit(out t); Console.WriteLine(t.s); // Hello! Console.ReadKey(); } } }

108

A fenti programokban pontosan tudtuk, hogy hny paramtere van egy metdusnak. Elfordul viszont, hogy ezt nem tudjuk egyrtelmen megmondani, ekkor n. paramtertmbket kell hasznlnunk. Ha ezt tesszk, akkor az adott metdus paramter-listjban a paramtertmbnek kell az utols helyen llnia, illetve egy paramterlistban csak egyszer hasznlhat ez a szerkezet.
using System; namespace TestApp { class Test { public void PrintElements(params object[] list) { foreach (var item in list) { Console.WriteLine(item); } } }

class Program { static void Main(string[] args) { Test t = new Test(); t.PrintElements("alma", "krte", 4, 1, "di"); t.PrintElements(); // ez is mkdik Console.ReadKey(); } } }

A paramtertmbt a params kulcsszval vezetjk be, ezutn a metdus belsejben pontosan gy viselkedik, mint egy normlis tmb. Paramtertmbknt tadhatunk megfelel tpus tmbket is. Alaprtelmezett paramterek A C# 4.0 bevezeti az alaprtelmezett paramtereket, amelyek lehetv teszik, hogy paramtereknek alaprtelmezett rtkeket adjunk, ezltal nem kell ktelezen megadnunk minden paramtert a metdus hvsakor. Nzzk a kvetkez pldt:
class Person { public Person(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } public Person(string firstName, string lastName, string job) : this(firstName, lastName) { Job = job; } public string FirstName {get; private set; } public string LastName {get; private set; } public string Job {get; private set; } }

109

Mivel nem tudunk biztosan minden emberhez munkahelyet rendelni, ezrt kt konstruktort kellett ksztennk. Ez alapveten nem nagy problma, viszont az gondot okozhat, ha valaki csak az els konstruktort hasznlja, majd megprbl hozzfrni a munka tulajdonsghoz. Ezt sem nagy gond megoldani, de mirt fradnnk, ha rendelkezsnkre llnak az alaprtelmezett paramterek? rjuk t a forrskdot:
class Person { public Person(string firstName, string lastName, string job = "N/A") { FirstName = firstName; LastName = lastName; Job = job; } public string FirstName { get; private set; } public string LastName { get; private set; } public string Job { get; private set; } }

A job paramterhez most alaprtelmezett rtket rendeltnk, gy biztosak lehetnk benne, hogy minden adattag megfelelen inicializlt. Az osztlyt most gy tudjuk hasznlni:
Person p1 = new Person("Istvn", "Reiter"); Person p2 = new Person("Istvn", "Reiter", "brtnr");

Nevestett paramterek A C# 4.0 az alaprtelmezett paramterek mellett bevezeti a nevestett paramter (named parameter) fogalmt, amely segtsgvel explicit megadhatjuk, hogy melyik paramternek adunk rtket. Nzzk az elz fejezet Person osztlynak konstruktort:
Person p = new Person(firstName: "Istvn", lastName: "Reiter");

Mivel tudatjuk a fordtval, hogy pontosan melyik paramterre gondolunk, ezrt nem kell betartanunk az eredeti metdus-deklarciban elrt sorrendet:
Person p = new Person(lastName: "Reiter", firstName: "Istvn");

VISSZATRSI RTK
Az objektumainkon nemcsak mveleteket vgznk, de szeretnnk lekrdezni az llapotukat is s felhasznlni ezeket az rtkeket. Ezenkvl szeretnnk olyan fggvnyeket is kszteni, amelyek nem kapcsoldnak kzvetlenl egy osztlyhoz, de hasznosak lehetnek (pl. az Int.Parse fggvny ilyen). Ksztsnk egy egyszer fggvnyt, amely sszead kt szmot, az eredmnyt pedig visszatrsi rtkknt kapjuk meg:
using System; namespace TestApp { class Test { public int Add(int x, int y) { return x + y; } }

110

class Program { static void Main(string[] args) { Test t = new Test(); int result = t.Add(10, 11); Console.ReadKey(); } } }

A vgeredmnyt a return utastssal adhatjuk vissza. A metdus-deklarcinl meg kell adnunk a visszatrsi rtk tpust is. Amennyiben ezt megtettk, a metdusnak mindenkppen tartalmaznia kell egy return utastst a megfelel tpus elemmel (ez lehet null is referencia- s nullable tpusok esetben) s ennek az utastsnak mindenkppen le kell futnia:
public int Add(int x, int y) { if (x != 0 && y != 0) { return x + y; } }

Ez a metdus nem fordul le, mivel nem lesz minden krlmnyek kztt visszatrsi rtk, hiszen nem biztos, hogy x s y is nulltl klnbz rtket kap. A visszatrtett rtk tpusnak vagy egyeznie kell a visszatrsi rtk tpusval, vagy a kett kztt lteznie kell implicit tpuskonverzinak:
public int Add(int x, int y) { return (byte)(x + y); // ez mkdik, br nincs sok rtelme } public int Add(int x, int y) { return (long)(x + y);// ez le sem fordul }

Visszatrsi rtkkel rendelkez metdust hasznlhatunk minden olyan helyen, ahol a program valamilyen tpust vr (rtkads, logikai kifejezsek, metdus paramterei, ciklusfelttel stb.).

KITERJESZTETT METDUSOK
A C# 3.0 lehetsget ad arra, hogy egy mr ltez tpushoz j metdusokat adjunk, anlkl, hogy azt kzvetlenl mdostannk, vagy szrmaztatnnk belle. Egy kiterjesztett metdus (extension method) minden esetben egy statikus osztly statikus metdusa kell, hogy legyen (errl a kvetkez fejezetben). Egsztsk ki a string tpust egy metdussal, ami kirja a kpernyre az adott karaktersorozatot:
using System; namespace TestApp { static public class StringHelper

111

{ static public void Print(this string s) { Console.WriteLine(s); } } class Program { static void Main(string[] args) { string s = "ezegystring"; s.Print(); StringHelper.Print(s); // gy is hasznlhatjuk Console.ReadKey(); } } }

A this mdost utn a paramter tpusa kvetkezik, amely meghatrozza a kiterjesztett osztly tpust. A fenti pldban lthat, hogy rendes statikus metdusknt is hasznlhat egy extension method. Ha kt kiterjesztett metdus ugyanazzal a szignatrval rendelkezik, akkor a hagyomnyos, statikus ton kell hvnunk ket. Ha nem gy tesznk, akkor a specilisabb (szkebb tpus) paramter metdus fog meghvdni. Kiterjesztett metdust nem definilhatunk begyazott osztlyban.

112

TULAJDONSGOK
A tulajdonsgokat (property) a mezk kzvetlen mdostsra hasznljuk, anlkl, hogy megsrtennk az egysgbe zrs elvt. A tulajdonsgok kvlrl nzve pontosan ugyanolyanok, mint a hagyomnyos vltozk, de valjban ezek specilis metdusok. Minden tulajdonsg rendelkezhet n. getter s setter blokkal, elbbi a property mgtt lv mez rtkt adja vissza, utbbi pedig rtket ad neki:
using System; namespace TestApp { class Person { public Person(string name) { this.name = name; } string name; public string Name { get { return this.name; } set { this.name = value; } } } class Program { static void Main(string[] args) { Person p = new Person("Istvn"); Console.WriteLine(p.Name); Console.ReadKey(); } } }

Lthatjuk, hogy egy property deklarci hasonlan pl fel, mint a metdusok, azzal a kivtellel, hogy nincs paramterlista. Vegyk szre, hogy a setterben egy ismeretlen, value nev vltozt hasznltunk. Ez egy specilis elem, azt az rtket tartalmazza, amelyet hozzrendeltnk a setter-hez:
Person p = new Person("Istvn"); p.Name = "Bla"; // value == "Bla"

A getter s setter elrhetsgnek nem muszj megegyeznie, de a getternek minden esetben publikusnak kell lennie:
public string Name { private get { return this.name; } // ez nem mkdik set { this.name = value; } } public string Name { get { return this.name; } private set { this.name = value; }//ez viszont j }

113

Arra is van lehetsg, hogy csak az egyiket hasznljuk, ekkor csak rhat/olvashat tulajdonsgokrl beszlnk:
public string Name { get { return this. name; } }

Egyik esetben sem vagyunk rknyszertve, hogy azonnal visszaadjuk/beolvassuk az adattag rtkt, tetszs szerint vgezhetnk mveleteket is rajtuk:
public string Name { get { return "Mr. " + this.name; } }

A C# 3.0 rendelkezik egy nagyon rdekes jtssal, az n. automatikus tulajdonsgokkal. Nem kell ltrehoznunk sem az adattagot, sem a teljes tulajdonsgot, a fordt mindkettt legenerlja neknk:
public string Name { get; set; }

A fordt automatikusan ltrehoz egy private elrs, string tpus, name nev adattagot, s elkszti hozz a getter-t/setter-t is. Van azonban egy problma, mghozz az, hogy a fordts pillanatban ez a vltoz mg nem ltezik, vagyis kzvetlenl nem hivatkozhatunk r pl. a konstruktorban. Ilyenkor a setter-en keresztl kell rtket adnunk.
class Person { public Person(string name) { this.Name = name; } public string Name { get; set; } }

114

INDEXELK
Az indexelk hasonlak a tulajdonsgokhoz, azzal a klnbsggel, hogy nem nvvel, hanem egy indexszel frnk hozz az adott informcihoz. ltalban olyan esetekben hasznljk, amikor az osztly/struktra tartalmaz egy tmbt vagy valamilyen gyjtemnyt (vagy olyan objektumot, amely maga is megvalst egy indexelt). Egy indexelt gy implementlhatunk:
using System; using System.Collections; namespace TestApp { class Names { private ArrayList nameList; public Names() { nameList = new ArrayList(); nameList.Add("Istvn"); nameList.Add("Szandra"); nameList.Add("Bla"); nameList.Add("Balzs"); } public int Count { get { return nameList.Count; } } public string this[int idx] { get { if (idx >= 0 && idx < nameList.Count) { return nameList[idx].ToString(); } return null; } } } class Program { static void Main(string[] args) { Names n = new Names(); for (int i = 0; i < n.Count; ++i) { Console.WriteLine(n[i]); } Console.ReadKey(); } } }

115

Ez gyakorlatilag egy nvtelen tulajdonsg, a this mutat az aktulis objektumra, amin az indexelt definiltuk. Tbb indexet is megadhatunk, amelyek klnbz tpus indexszel vagy visszatrsi rtkkel rendelkezhetnek. Nem csak egy paramterrel hivatkozhatunk egy indexelvel, pldul ha a bels adatszerkezet egy ktdimenzis tmb, akkor ennek megfelel mennyisg paramterrel dolgozhatunk:
public int this[int idxx, int idxy] { get { /*...*/ } }

116

STATIKUS TAGOK
A hagyomnyos adattagok s metdusok objektumszinten lteznek, azaz minden objektum minden adattagjbl sajt pldnnyal rendelkezik. Gyakran van azonban szksgnk objektumtl fggetlen mezkre/metdusokra, pl. ha meg szeretnnk szmolni, hogy hny objektumot hoztunk ltre. Erre a clra szolglnak az n. statikus tagok, amelyekbl osztlyszinten sszesen egy darab ltezik. Statikus tagokat akkor is hasznlhatunk, ha az osztlybl nem kszlt pldny. A statikus tagok jelentsge a C# nyelv tisztn objektum orientlt mivoltban rejlik, ugyanis nem definilhatunk globlis (mindenki szmra egyformn elrhet) tagokat. Ezt (is) vltjk ki a statikus adattagok s metdusok.

STATIKUS ADATTAG
Statikus tagot a static kulcssz segtsgvel hozhatunk ltre:
using System; namespace TestApp { class Animal { static public int AnimalCounter = 0; public Animal() { ++Animal.AnimalCounter; } ~Animal() { --Animal.AnimalCounter; } } class Program { static void Main(string[] args) { Animal a = new Animal(); Console.WriteLine(Animal.AnimalCounter); Console.ReadKey(); } } }

A pldban a statikus adattag rtkt minden alkalommal megnveljk eggyel, amikor meghvjuk a konstruktort, s cskkentjk, amikor az objektum elpusztul, vagyis az aktv pldnyok szmt tartjuk szmon vele. A statikus tagokhoz az osztly nevn (s nem egy pldnyn) keresztl frnk hozz (a statikus tag gazdaosztlybl az osztly neve nlkl is hivatkozhatunk rjuk, de ez nem ajnlott, mivel rontja az olvashatsgot). A statikus tagok ha az osztlynak nincs statikus konstruktora rgtn a program elejn inicializldnak. Az olyan osztlyok statikus tagjai, amelyek rendelkeznek statikus konstruktorral, az inicializlst elhalasztjk addig a pontig, amikor elszr hasznljuk az adott osztly egy pldnyt. Konvenci szerint minden statikus tag (adattagok is) neve nagybetvel kezddik. A statikus s lthatsgi mdost megadsnak sorrendje tetszleges.

117

STATIKUS KONSTRUKTOR
A statikus konstruktor a statikus tagok belltsrt felel. Kzvetlenl azeltt fut le, hogy egy pldny keletkezik az adott osztlybl, vagy hozzfrtek valamely tagjhoz. A statikus konstruktornak nem lehet lthatsgot adni, illetve nincsenek paramterei sem. Nem frhet hozz pldnytagokhoz sem.
using System; namespace TestApp { class Test { static public int Var = Test.Init(); static public int Init() { Console.WriteLine("Var = 10"); return 10; } static Test() { Console.WriteLine("Statikus konstruktor"); } public Test() { Console.WriteLine("Konstruktor"); } } class Program { static void Main(string[] args) { Console.WriteLine("Start..."); Test t = new Test(); Console.ReadKey(); } } }

Ha elindtjuk a programot, a kvetkez kimenetet kapjuk: Start... Var = 10 Statikus konstruktor Konstruktor A statikus konstruktoroknak van azonban egy hatalmas hibjuk, amelyet a kvetkez pldban lthatunk:
static class A1 { static public int x = 10; } static class A2

118

{ static public int x; static A2() { x = 10; } }

A kt osztly ltszlag ugyanazt teszi, mgis risi teljestmnyklnbsg van kztk:


class Program { static void Main(string[] args) { Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < 10000000; ++i) { int x = A1.x; } Console.WriteLine("Eltelt id: {0}ms", sw.ElapsedMilliseconds); sw = Stopwatch.StartNew(); for (int i = 0; i < 10000000; ++i) { int x = A2.x; } Console.WriteLine("Eltelt id: {0}ms", sw.ElapsedMilliseconds); Console.ReadKey(); } }

A Stopwatch osztly a System.Diagnostics nvtrben van, s ahogyan ltszik is idt tudunk mrni vele. Mindktszer tzmilli alkalommal krtk el a statikus tag rtkt, lssuk az eredmnyt: Eltelt id: 12ms Eltelt id: 88ms A klnbsg elkpeszten nagy, az ok pedig a kvetkez: ha definiltunk statikus konstruktort, akkor a rendszer minden egyes alkalommal, amikor statikus taghoz prblunk hozzfrni, ellenrzi, hogy meghvdott-e mr, ez pedig a fenti teljestmnyvesztesget eredmnyezi.

STATIKUS METDUS
Statikus metdust a hagyomnyos metdusokhoz hasonlan ksztnk, mindssze a static kulcsszra van szksgnk. Ilyen metdus volt az elz pldban az Init metdus is. A statikus konstruktortl eltren r nem vonatkozik, hogy nem lehetnek paramterei. Statikus metdusok nem frnek hozz az osztly normlis tagjaihoz, legalbbis direkt mdon nem (az minden tovbbi nlkl mkdik, ha egy pldny referencijt adjuk t neki). Statikus metdust ltalban akkor hasznlunk, ha nem egy pldny llapotnak a megvltoztatsa a cl, hanem egy osztlyhoz kapcsold mvelet elvgzse. Ilyen metdus pldul az Int32 osztlyhoz tartoz Parse statikus metdus is.

119

A leghresebb statikus metdus a Main.

STATIKUS TULAJDONSG
A statikus tulajdonsgok a C# egy viszonylag ritkn hasznlt lehetsge. ltalban osztlyokhoz kapcsold konstans rtkek lekrdezsre hasznljuk (lnyegben a statikus metdusok egy olvashatbb verzija).
class Math { static public double PI { get { return 3.14; } } }

STATIKUS OSZTLY
Egy osztlyt statikusnak jellhetnk, ha csak s kizrlag statikus tagjai vannak. Egy statikus osztlybl nem hozhat ltre pldny, nem lehet pldny-konstruktora (de statikus igen), s mindig lezrt (ld. rklds). A fordt minden esetben ellenrzi ezeknek a feltteleknek a teljeslst.
static class MathHelper { static public double PI { get { return 3.14; } } static public double Cos(double x) { return Math.Cos(x); } }

120

STRUKTRK
A struktrk szerkezetket tekintve hasonlak az osztlyokhoz, viszont azoktl eltren nem referencia-, hanem rtktpusok. Minden struktra indirekt mdon a System.ValueType osztlybl szrmazik. Ez egy specilis tpus, amely lehetsget biztost rtktpusok szmra, hogy referenciatpusknt viselkedjenek (lsd: Boxing). A struktrk kzvetlenl tartalmazzk a sajt rtkeiket, mg az osztlyok csak referencikat trolnak. pp ezrt struktrt ltalban akkor hasznlunk, ha egyszer adatokkal kell dolgoznunk, de nincs szksgnk egy osztly minden szolgltatsra.

KONSTRUKTOR
Minden struktra alaprtelmezetten rendelkezik egy konstruktorszersggel (vigyzat, nem igazi konstruktor), amely elvgzi a tagok nullra inicializlst (lnyegben nullkkal tlti fel az adott memriaterletet). Ez a lehetsg mindig l, nem rejthet el.
using System; namespace TestApp { struct Test { public int x; } class Program { static void Main(string[] args) { Test t = new Test(); Console.WriteLine(t.x); // x == 0 Console.ReadKey(); } } }

Nem ktelez hasznlni a new opertort, de ha gy tesznk, akkor a struktra tagjainak hasznlata eltt definilni kell az rtkket, ellenkez esetben a program nem fordul le:
using System; namespace TestApp { struct Test { public int x; } class Program { static void Main(string[] args) { Test t; Console.WriteLine(t.x); // nem j, x inicializlatlan

121

Console.ReadKey(); } } }

Kszthetnk sajt konstruktort, de ekkor minden mez rtkadsrl gondoskodnunk kell. Egy struktra mezit nem inicializlhatjuk:
struct Test { int x = 10; // ez nem j int y; public Test(int x, int y) { this.y = y;// ez sem j, x nem kap rtket } }

Struktrban csakis paramteres konstruktort definilhatunk, paramter nlkli alaprtelmezettet nem. Viszont ha ezt megtettk, attl az alaprtelmezett konstruktor mg hasznlhat marad:
using System; namespace TestApp { struct Test { int x; int y; public Test(int x, int y) { this.y = y; this.x = x; } }

class Program { static void Main(string[] args) { Test t1 = new Test(10, 11); Test t2 = new Test(); //ez is mkdik Console.ReadKey(); } } }

DESTRUKTOR
Struktrk nem rendelkezhetnek destruktorral. Egy struktra kt helyen lehet a memriban: a stack-ben s a heap-ben (ha egy referenciatpus tagja). Ahhoz, hogy megrtsk, hogy mirt nincs destruktor, szksgnk van a kvetkezre: egy struktrban lv referenciatpusnak csak a referencijt troljuk. Ha a veremben van a struktra, akkor elbb vagy utbb kikerl

122

onnan, s mivel gy a benne lv referenciatpusra mr nem mutat referencia (legalbbis a struktrbl nem), ezrt eltakarthat. Ugyanez a trtnet akkor is, ha a struktra pldny egy referenciatpusban foglal helyet.

ADATTAGOK
A struktrk az adattagokat kzvetlenl troljk (mg osztlyok esetben mindig referencikat tartunk szmon). Egy struktra minden adattagja amennyiben a konstruktorban nem adunk meg mst automatikusan a megfelel nulla rtkre inicializldik. Struktra nem tartalmazhat sajt magval megegyez tpus adattagot. Ugyangy, egy struktra nem tartalmazhat olyan tpus tagot, amely tpus hivatkozik az eredeti struktrra:
struct Test { Test t; } struct Test1 { Test2 t; } struct Test2 { Test1 t; }

Mindhrom struktra hibs. Az ok nagyon egyszer: mivel a struktrk direkt mdon nem referencikon keresztl troljk az adattagjaikat, valamint mivel a struktrk nem vehetnek fel null rtket, a fenti szerkezetek mind vgtelen hurkot (s vgtelen memriafoglalst) okoznnak (Test1 struktra, amiben Test2, amiben Test1 s gy tovbb) (lsd: tranzitv lezrt).

HOZZRENDELS
Amikor egy struktra pldnynak egy msik pldnyt adunk rtkl, akkor egy teljesen j objektum keletkezik:
using System; namespace TestApp { struct Test { public int x; } class Program { static void Main(string[] args) { Test t1 = new Test(); Test t2 = t1; t2.x = 10; Console.WriteLine("t1.x = {0}, t2.x = {1}", t1.x, t2.x); Console.ReadKey(); } } }

123

Ha lefordtjuk ezt a kdot, azt fogjuk ltni, hogy t1.x rtke nem vltozott, teht nem referencit adtunk t t2 nek. Most nzznk meg egy nagyon gyakori hibt, amibe belefuthatunk! Adott a kvetkez programkd:
using System; namespace TestApp { struct Point { int x; public int X { get { return x; } set { x = value; } } int y; public int Y { get { return y; } set { y = value; } } public Point(int x, int y) { this.x = x; this.y = y; } } struct Line { Point a; public Point A { get { return a; } set { a = value; } } Point b; public Point B { get { return b; } set { b = value; } } } class Program { static void Main(string[] args) { Line l = new Line(); l.A = new Point(10, 10); l.B = new Point(20, 20); Console.ReadKey(); } } }

124

Teljesen szablyos forrs, le is fordul. Lthat, hogy a Point struktra publikus tulajdonsgokkal br, vagyis jogosnak tnik, hogy a Line struktrn keresztl mdostani tudjuk a koordintkat. Egsztsk ki a kdot:
Line l = new Line(); l.A = new Point(10, 10); l.B = new Point(20, 20); l.A.X = 5;

Ez a forrskd nem fog lefordulni, mivel nem vltoznak akarunk rtket adni. Mi lehet a hiba oka? A problma ott van, hogy rosszul rtelmeztk ezt a kifejezst. Az l.A valjban a getter-t hvja meg, ami az eredeti struktra egy msolatval tr vissza, amelynek a tagjait mdostani viszont nincs rtelme. Ilyen esetekben mindig j struktrt kell ksztennk:
Line l = new Line(); l.A = new Point(10, 10); l.B = new Point(20, 20); l.A = new Point(5, 10);

RKLDS
Struktrk szmra az rklds tiltott, minden struktra automatikusan sealed mdostt kap. Ilyen mdon egy struktra nem lehet absztrakt, tagjainak elrhetsge nem lehet protected/protected internal, metdusai nem lehetnek virtulisak, illetve csak a System.ValueType metdusait definilhatja t. Ez utbbi esetben a metdushvsok nem jrnak bedobozolssal:
struct Test { public int x; public override string ToString() { return "X == " + x.ToString(); } }

125

OSZTLYKNYVTRAK
Eddig mindig egyetlen projektben dolgoztunk, ennek viszont megvan az a htrnya, hogy a program nvekedsvel egytt a forrskd is tbb helyet foglal. Hozz kell tennnk azt is, hogy ez a fajta megkzelts a kd-jrafelhasznlst is rontja, hiszen az ltalnosabb programrszeket minden j projektbe ismt be kellene msolni. Sokkal egyszerbb lenne a dolgunk, ha az jrahasznostand forrskdot kln tudnnk vlasztani a tnyleges programtl, ezzel idt s helyet takartva meg. Erre a clra talltk ki a shared library (~megosztott knyvtr) fogalmt, amely a Windows alap rendszereken a DLL -t (Dynamic Link Library) jelenti. A DLL knyvtrak felptse a hasznlt fejleszti platformtl fgg, teht egy COM DLL nem hasznlhat kzvetlenl egy .NET programban. .Net krnyezetben egy ilyen knyvtrat assemblynek neveznk. Ksztsk el az els osztlyknyvtrunkat! Feltesszk, hogy Visaul Studio-ban mr elksztettnk egy Console Application tpus projektet. Kattintsunk jobb gombbal a Solution-n, s az Add menbl vlasszuk a New Project elemet:

A megjelen ablakban a Class Library sablont kell hasznlnunk:

126

Az OK gombra kattintva elkszl az j projekt. Egyelre csak egy osztlyt tartalmaz, viszont Main fggvnyt nem, gy ezt a projektet nem lehet futtatni, csakis egy msik projektbl hvhatjuk meg a szolgltatsait. Ksztsnk egy egyszer osztlyt, akr tnevezve a mr ltezt, akr jat ltrehozva!
using System; namespace MyLibrary { public class ClassLib { static public void PrintHello() { Console.WriteLine("n egy osztlyknyvtrban vagyok!"); } } }

A kvetkez lpsben tudatnunk kell a f projekttel, hogy van egy osztlyknyvtrunk. A projekten bell ott van egy References nev knyvtr, amely a felhasznlt knyvtrakat trolja. Kattintsunk rajta jobb gombbal, s vlasszuk az Add Reference pontot:

127

Az Add Reference ablakban, a Projects fln megjelennek a Solution-ben szerepl projektek, vlasszuk az elbb elksztett knyvtrat:

Az OK gombra kattintva a projectben felhasznlhatjuk az osztlyknyvtr szolgltatsait. A hivatkozott knyvtr a fordts utn a clknyvtr futtathat llomnya mell kerl .dll kiterjesztssel, ha tovbb akarjuk adni programunkat, azt is mell kell msolnunk. A forrskdban a nvtr megadsa utn azonnal kapunk Intellisense tmogatst is. Ksztsk egy programot, amely felhasznlja az osztlyknyvtrunkat:

128

using System; using MyLibrary; // az osztlyknyvtr nvtere namespace TestApp { class Program { static public void Main(string[] args) { ClassLib.PrintHello(); Console.ReadKey(); } } }

129

GYAKORL FELADATOK III.


FAKTORILIS S HATVNY
Ksztsnk rekurzv faktorilis s hatvnyt szmt fggvnyeket! Megolds (19/RecFact.cs s 19/RecPow.cs) Mi is az a rekurzv fggvny? Egy fggvny, amely nmagt hvja. Rengeteg olyan problma van, amelyeket tbb, lnyegben azonos feladatot vgrehajt rszre lehet osztani. Vegyk pl. a hatvnyozst: semmi mst nem tesznk, mint meghatrozott szm szorzst vgznk, mghozz ugyanazzal a szmmal. rhatunk persze egy egyszer ciklust is, de ez egy kicsit atombombval egrre tpus megolds lenne. Nzzk meg a hatvnyozs rekurzv megfeleljt:
using System; namespace TestApp { class Program { static public double Pow(double x, int y) { if (y == 0) { return 1.0; } else return x * Pow(x, y - 1); } static void Main(string[] args) { double result = Pow(2, 10); Console.WriteLine(result); // 1024 Console.ReadKey(); } } }

Lthat, hogy xet (az alapot) rintetlenl hagyjuk, mg a kitevt (y) a fggvny minden hvsakor eggyel cskkentjk, egszen addig, amg rtke nulla nem lesz. Ekkor befejezzk az rdgi krt, s visszaadjuk az eredmnyt. Hasonlkppen kszthetjk el a faktorilist szmol programot is:
using System; namespace TestApp { class Program { static public int Fact(int x) { if (x == 0) { return 1; } else return x * Fact(x - 1); } static void Main(string[] args) { int result = Fact(10);

130

Console.WriteLine(result); Console.ReadKey(); } } }

GYORSRENDEZS
Valstsuk meg a gyorsrendezst! Megolds (19/QuickSort.cs) A gyorsrendezs a leggyorsabb rendez algoritmus, nagy elemszm esetn O(n*logn) nagysgrend tlaggal. Az algoritmus lnyege, hogy a rendezend elemek kzl kivlaszt egy n. pivot elemet, amely el a nla nagyobb, mg pedig a nla kisebb elemeket teszi, majd az gy kapott kt csoportra ismt meghvja a gyorsrendezst (teht egy rekurzv algoritmusrl beszlnk). Lssuk, hogy hogyan is nz ez ki a gyakorlatban! Nagy elemszmnl ugyan jl teljest ez az algoritmus, de kevs elem esetn fordul a kocka. ppen ezrt amikor a rendezsre tadott tmbrszlet kellen kicsi, akkor egy ltalnosabb rendezst, pl. buborkrendezst rdemes hasznlni. A legtbb programozsi nyelv beptett rendezsei ltalban a gyorsrendezs egy varicijt hasznljk.
class Array { private int[] array; public Array(int length) { array = new int[length]; } public int this[int idx] { get { return array[idx]; } set { array[idx] = value; } } public int Length { get { return array.Length; } } public void Sort() { QuickSort(0, array.Length - 1); } private void QuickSort(int left, int right) { // rendezs... } }

Ksztettnk egy osztlyt, amely reprezentlja a rendezend tmbt. A Sort metdussal fogjuk meghvni a tnyleges rendez metdust, amely kt paramtert kap, a rendezsre kivlasztott tmbrszlet als s fels indext. A metdus implementcija a kvetkezkppen nz ki:

131

private { int int int

void QuickSort(int left, int right) pivot = array[left]; lhold = left; rhold = right;

while (left < right) { while (array[right] >= pivot && left < right) { --right; } if (left != right) { array[left] = array[right]; ++left; } while (array[left] <= pivot && left < right) { ++left; } if (left != right) { array[right] = array[left]; --right; } } array[left] = pivot; pivot = left; left = lhold; right = rhold; if (left < pivot) { QuickSort(left, pivot - 1); } if (right > pivot) { QuickSort(pivot + 1, right); } }

A tmb mindkt oldalrl behatroljuk a kisebb/nagyobb elemeket, majd tovbbhvjuk a rendezst. Nzzk meg az algoritmus mkdst egy pldn keresztl! A rendezend szmsorozat legyen: 3, 9, 4, 6, 8, 11 Left s right 0 s 5 (ugye hat elem van a tmbben, s nulltl indexelnk), ezeket az rtkeket eltroljuk, mivel az rtkk mdosulni fog, de a metdus vgn szksg van az eredetiekre. Az els ciklus mivel nem fog a left indexen lv szmnl (3) kisebbet tallni gy vgzdik, hogy right rtke 0 lesz. Az elgazsba mivel right s left egyenl nem megynk bele, ugyangy, ahogyan a msodik ciklusba sem, mivel a hrmasnl kisebb elem nincs, a nla nagyobbak pedig utna helyezkednek el. A kvetkez elgazs szintn kimarad, s nekillhatunk kiszmolni, hogy miknt hvjuk meg jra a metdust. A tmb vltozatlan marad, a pivot vltoz nulla rtket kap, right s left pedig visszakapjk az eredeti rtkket.

132

Ezutn a msodik elgazst fogjuk hasznlni (ne feledjk, hogy right rtke ismt 5), s meghvjuk a QuickSort metdust 1 illetve 5 paramterekkel, vagyis az els elemet (3) mivel mr a helyn van tugorjuk. Kvetkezik a msodik fordul, a pivot vltoz rtke most kilenc lesz, mg left s right rtke 1 s 5. Az els ciklus egyszer fog lefutni, hiszen a tmb negyedik indexn l nyolcas szm mr kisebb mint a pivot elem. Left nem egyenl right tal ezrt a kvetkez elgazsba is bemegynk, s a left indexre helyezzk a right indexen lv nyolcast (a pivot elem pedig pont az itt mr nem lv kilencest trolja). Left elrelp eggyel, hogy a tmb msodik indexre (4) mutasson. A msodik ciklus is lefut, egszen addig fogjuk nvelni left rtkt, amg elri a right ltal mutatott nyolcast, hiszen ott a ciklus felttel msodik fele srl. Most left s right rtke egyenl: 4. ppen ezrt a msodik elgazst kihagyjuk s tovbblpnk. A left ltal mutatott indexre behelyezzk a pivotban lv kilencest, amivel helyre is ll a rend. Pivot rtke ezutn ngy lesz s a kt msik vltoz is visszakapja az rtkt. Left kisebb most, mint a pivot s right pedig nagyobb nla, gy mindkt elgazs felttele teljesl, vagyis most mindkt oldalra hvjuk a metdust. Az ezutni esemnyek tgondolsa pedig az olvas feladata.

LNCOLT LISTA
Valstsuk meg a lncolt lista adatszerkezetet! Megolds (19/LinkedList.cs) Amikor tmbkkel dolgozunk, akkor a tmb elemeit indexekkel rjk el. A lncolt lista olyan adatszerkezet, amelynek elemei a soron kvetkez elemre hivatkoz referencit tartalmaznak. A lncolt listt az els fej- vagy gykrelemen keresztl rjk el. Ha az elemek csak a kvetkez tagra mutatnak, akkor egyszeresen, ha a megelz elemre is, akkor ktszeresen lncolt listrl beszlnk: vagy Elsknt valstsuk meg az elemeket jelkpez osztlyt:
class Node { public Node(int value) { this.Value = value; } public int Value{ get; set; } public Node Next{ get; set; } public Node Previous { get; set; } }

Most pedig jjjn a lncolt lista osztly:


class LinkedList { public LinkedList() { } public LinkedList(int[] values) { foreach (int value in values) { this.Add(value); }

133

} public void Add(int value) { if (Root == null) { Root = new Node(value); } else { Node current = Root; while (current.Next != null) { current = current.Next; } current.Next = new Node(value); current.Next.Previous = current; } } public Node Root { get; private set; } }

Az Add metdus az, amelyik szmunkra rdekes. Elsknt megvizsgljuk, hogy ltezik-e gykrelem, ha nem, akkor ltrehozzuk, s nincs is ms dolgunk (ugye ilyenkor mg nincs se elz, se rkvetkez elem). Ms a helyzet, ha van mr nhny elem a listban, ekkor meg kell keresnnk a legutols elemet, s utnafzni az jat (megvalsthattuk volna gy is a listt, hogy trolunk egy referencit az utols elemre, ez lnyegesen gyorsabb lenne de kevsb rdekes). Ahhoz, hogy megkeressk az utols elemet, szksgnk lesz egy tmeneti referencira, amely mindig az aktulis elemet mutatja majd. A ciklust addig kell futtatni, ameddig az aktulis elem rkvetkezje null rtkre nem mutat, ekkor belltjuk a Next s Previous rtkeket is.

BINRIS KERESFA
Ksztsnk binris keresft! Megolds (19/BinaryTree.cs) A fa tpus olyan adatszerkezet, amelynek elemei nulla vagy tbb gyermekelemmel s maximum egy szlelemmel rendelkeznek: A

A kpen az A elem gyermekei B illetve C, akiknek termszetesen A lesz a kzs szlelemk. A fa tpus egy specilis esete a binris fa, amely minden elemnek pontosan egy szl s maximum kett gyermek eleme lehet. A binris fa specilis esete pedig a binris keresfa, amelynek jellemzje, hogy egy szl elem bal oldali rszfjban a szlelemnl kisebb, jobb oldali rszfjban pedig a szlelemnl nagyobb elemek vannak, ezltal egyrtelmen meghatrozhat, hogy egy elem benne van-e a fban vagy nincs (rtelemszeren a rszfk is keresfk, vagyis rjuk is ugyanez vonatkozik). A binris keresfa minden elemnek egyedi kulccsal

134

kell rendelkeznie, vagyis ugyanaz az elem ktszer nem szerepelhet a fban. A keress mvelet O(logn) nagysgrend. Ahogyan az elz feladatban, most is kezdjk a fa cscsait jelkpez osztllyal:
class TreeNode { public TreeNode(int value) { this.Value = value; } public int Value { get; set; } public TreeNode Parent { get; set; } public TreeNode Left { get; set; } public TreeNode Right { get; set; } }

Most pedig ksztsk el a fa osztlyt!


class BinaryTree { public BinaryTree() { } public BinaryTree(int[] values) { foreach (int value in values) { this.Insert(value); } } public void Insert(int value) { } public TreeNode Root { get; private set; } }

Az osztly vza hasonlt a lncolt listhoz, itt is szksgnk van egy gykrelemre, ez tulajdonkppen a legels beszrt cscs lesz. rjuk meg a hinyz Insert metdust:
public void Insert(int value) { if (Root == null) { Root = new TreeNode(value); } else { TreeNode current = Root; while (current != null) { if (current.Value > value) { if (current.Left == null) { current.Left = new TreeNode(value);

135

current.Left.Parent = current; return; } else { current = current.Left; } } else if (current.Value < value) { if (current.Right == null) { current.Right = new TreeNode(value); current.Right.Parent = current; return; } else { current = current.Right; } } else return; } } }

Ha a gykrelem null rtken ll, akkor ksztnk egy j TreeNode objektumot, egybknt megkeressk az j elem helyt oly mdon, hogy minden cscsnl a megfelel irnyba fordulunk. Amennyiben az adott rtk mr szerepel a fban, egyszeren elhagyjuk a ciklust. Tegyk fel, hogy az elemek a kvetkez sorrendben rkeznek: 10, 1, 4, 6, 6, 3, 9, 12 Ekkor a binris keresfa gy fog kinzni: 10

12

A kvetkez feladatunk, hogy kirjuk a fa elemeit a konzolra. Persze ez nem is olyan egyszer, hiszen megfelel algoritmusra lesz szksgnk ahhoz, hogy a fa elemeit bejrhassuk. Egy rekurzv algoritmust fogunk hasznlni,

136

amely stratgitl fggen az egyes cscsok rszfit, majd magt a cscsot ltogatja meg. Hromfle stratgit ismernk: preorder, inorder s postorder. A preorder elsknt a cscsot, majd a bal s jobb oldali rszft veszi kezelsbe. Inorder mdon a bal oldali rszfa, a cscs s a jobb oldali rszfa lesz a sorrend, vgl pedig a postorder bejrs sorrendje a bal oldali rszfa, a jobb oldali rszfa, vgl pedig a cscs. Nzzk meg, hogyan is mkdik mindez a gyakorlatban! A fent felptett fn fogunk inorder mdon vgigmenni. Az algoritmust a gykrelemre (10) fogjuk meghvni, amely elsknt a bal oldali rszfa cscst (1) fogja megltogatni. Mivel neki nincsen bal oldali rszfja, ezrt kirjuk az egyes szmot, s lpnk a jobb oldali rszfra (4). Neki mr van bal oldali ga, ezrt t vizsgljuk a tovbbiakban. Itt nincs gyermekelem, ezrt a kvetkez szm, amit kirhatunk, a hrom. Visszalpnk a szlelemre, s kirjuk t (4), majd lpnk jobbra. A hatos cscsnak sincs bal oldali fja, ezrt kirjuk, majd jn a jobb fban a kilences, amit megint csak kirunk, hiszen nem rendelkezik gyermekelemmel. Ezen a ponton vgeztnk a gykrelem bal oldali fjval, ezrt t jelentjk meg, ezutn pedig mr csak egyetlen elem marad. A vgs sorrend teht: 1, 3, 4, 6, 9, 10, 12 A forrskdban ez az algoritmus meglehetsen egyszeren jelenik meg, kvetkezzen az inorder bejrs:
public void InOrder(Action<int> action) { _inOrder(Root, action); } private void _inOrder(TreeNode root, Action<int> action) { if (root == null) { return; } _inOrder(root.Left, action); action(root.Value); _inOrder(root.Right, action); }

Az Action<T> osztlyrl a Lambda kifejezsek c. fejezetben olvashat tbbet az olvas.

137

RKLDS
rkldssel egy mr ltez tpust terjeszthetnk ki vagy bvthetjk tetszleges szolgltatssal. A C# csakis egyszeres rkldst engedlyez, vagyis minden osztly egyetlen sosztlybl szrmazhat (leszmtva a System.Objectet), ugyanakkor megengedi tbb interfsz implementlst (interfszekrl hamarosan). Ksztsk el az elmleti rsz pldjt (llat-Kutya-Krokodil) C# nyelven! Az egyszersg kedvrt hagyjuk ki az llat s Kutya kzti specilisabb osztlyokat:
class Animal { } class Dog : Animal { } class Crocodile : Animal { }

Az sosztlyt az osztlydeklarci utn rt kettspont mg kell rni, szintn itt lesznek majd az osztly ltal megvalstott interfszek is. A Kutya s Krokodil osztlyok egyarnt megvalstjk az sosztly (egyelre szegnyes) funkcionalitst. Bvtsk ht ki:
class Animal { public Animal(string name) { this.Name = name; } public string Name { get; set; } public void Eat() { Console.WriteLine("Hamm - Hamm"); } }

Vegyk szre, hogy paramteres konstruktort ksztettnk az sosztlynak, vagyis t kell gondolnunk a pldnyostst! Az els vltozatot (az alaprtelmezett konstruktorral) gy hasznlhattuk:
Dog d = new Dog(); Crocodile c = new Crocodile();

Ha ezt az j Animal osztllyal prbljuk meg akkor meglepets fog rni, mivel nem fordul le a program. Ahhoz, hogy ki tudjuk javtani a hibt, tudnunk kell, hogy a leszrmazott osztlyok elszr mindig a kzvetlen sosztly konstruktort hvjk meg, vagyis ha nem adunk meg mst az alaprtelmezett konstruktort. A problma az, hogy az sosztlynak mr nincs ilyenje, ezrt a leszrmazott osztlyokban explicit mdon hvni kell a megfelel konstruktort:

138

class Dog : Animal { public Dog(string name) : base(name) { } } class Crocodile : Animal { public Crocodile(string name) : base(name) { } }

Ezutn gy pldnyostunk:
Dog d = new Dog("Fli"); Crocodile c = new Crocodile("Aladr");

Ugyangy hasznlhatjuk az sosztly metdust is:


Dog d = new Dog("Fli"); Crocodile c = new Crocodile("Aladr"); d.Eat(); c.Eat();

Honnan tudja vajon a fordt, hogy egy sosztlybeli metdust kell meghvnia? A referenciatpusok specilis mdon jelennek meg a memriban, rendelkeznek tbbek kzt egy n. metdus-tblval, ami mutatja, hogy az egyes metdushvsoknl melyik metdust kell meghvni. Persze ezt is meg kell hatrozni valahogy, ez nagy vonalakban gy trtnik, hogy a fordt a fordts pillanatban megkapja a metdus nevt, s elindul visszafel az osztlyhierarchia mentn. A fenti pldban a hv osztly nem rendelkezik Eat nev metdussal, s nem is definilja t annak a viselkedst (errl hamarosan), ezrt az eggyel feljebbi st kell megnznnk. Ez egszen a lehet legjabb metdusdefinciig megy, s amikor megtallja a megfelel implementcit, bejegyzi azt a metdustblba.

VIRTULIS METDUSOK
Az sosztlyban deklarlt virtulis (vagy polimorfikus) metdusok viselkedst a leszrmazottak tdefinilhatjk. Virtulis metdust a szignatra el rt virtual kulcssz segtsgvel deklarlhatunk:
using System; namespace TestApp { class Animal { public virtual void Eat() { Console.WriteLine("Egy llat eszik..."); } } class Dog : Animal

139

{ public override void Eat() { Console.WriteLine("Kutya csontot rg..."); } } class Crocodile : Animal { public override void Eat() { Console.WriteLine("Krokodil embert rg..."); } } class Program { static void Main(string[] args) { Animal a = new Animal(); Dog d = new Dog(); Crocodile c = new Crocodile(); a.Eat(); d.Eat(); c.Eat(); Console.ReadKey(); } } }

A leszrmazott osztlyokban az override kulcsszval mondjuk meg a fordtnak, hogy szndkosan hoztunk ltre az sosztlyval azonos szignatrj metdust, s a leszrmazott osztlyon ezt kvnjuk hasznlni mostantl. Egy override-dal jellt metdus automatikusan virtulis is lesz, gy az leszrmazottai is tdefinilhatjk a mkdst:
class Crocodile : Animal { public override void Eat() { Console.WriteLine("Krokodil embert rg..."); } } class BigEvilCrocodile : Crocodile { public override void Eat() { Console.WriteLine("Krokodil cenjrt rg..."); } }

Az utdosztly metdusnak szignatrja s lthatsga meg kell egyezzen azzal, amit t akarunk definilni. Tegyk fel, hogy nem ismerjk az sosztly fellett, s a hagyomnyos mdon deklarljuk az Eat metdust (ugye nem tudjuk, hogy mr ltezik)! Ekkor a program ugyan lefordul, de a fordt figyelmeztet minket, hogy eltakarjuk az rkltt metdust. s valban, ha meghvnnk, akkor az j metdus futna le. Ezt a jelensget rnykolsnak (shadow) nevezik.

140

Termszetesen mi azt szeretnnk, ha a fordts hiba nlkl menne vgbe, gy tjkoztatnunk kell a fordtt, hogy szndkosan takarjuk el az eredeti implementcit. Ezt a new kulcsszval tehetjk meg:
class Animal { public virtual void Eat() { Console.WriteLine("Egy llat eszik..."); } } class Dog : Animal { public new void Eat() { Console.WriteLine("Kutya csontot rg..."); } }

Ezutn a Dog utdjai mr nem ltjk az eredeti Eat metdust. Viszont kszthetnk belle virtulis metdust, amelyet az utdai mr kedvkre hasznlhatnak. Azaz, a new mdostval elltott metdus j sort kezd, amikor a fordt felpti a metdustblt, vagyis a new virtual kulcsszavakkal elltott metdus lesz az j metdussorozat gykere.
class Dog : Animal { public new virtual void Eat() { Console.WriteLine("Kutya csontot rg..."); } }

Nem jellhetnk virtulisnak statikus, absztrakt s overridedal jellt tagokat (az utols kett egybknt virtulis is lesz, de ezt nem kell kln jellni).

POLIMORFIZMUS
Korbban mr beszltnk arrl, hogy az s s leszrmazottak kzt az-egy (is-a) relci ll fenn. Ez a gyakorlatban azt jelenti, hogy minden olyan helyen, ahol egy stpust hasznlunk, ott hasznlhatunk leszrmazottat is (pl. egy llatkertben llatok vannak, de az llatok helyre (nyilvn) behelyettesthetek egy specilisabb fajt). Pldul gond nlkl rhatom a kvetkezt:
Animal d = new Dog("Fli");

A new opertor meghvsa utn d gy fog viselkedni, mint a Dog osztly egy pldnya (elvgre az is lesz), hasznlhatja annak metdusait, adattagjait. Arra azonban figyeljnk, hogy ez visszafel nem mkdik, a fordt hibt jelezne! Abban az esetben ugyanis, ha a fordt engedn a visszafel konverzit, az n. leszeletelds (slicing) effektus lpne fel, azaz az adott objektum elveszten a specilisabb osztlyra jellemz karakterisztikjt. A C++ nyelvben sokszor jelent gondot ez a problma, mivel ott egy pointeren keresztl megtehet a lebutts. Szerencsre a C# nyelvben ezt megoldottk, gy nem kell aggdnunk miatta. Mi trtnik vajon a kvetkez esetben:
Animal[] animalArray = new Animal[2];

141

animalArray[0] = new Animal(); animalArray[1] = new Dog(); animalArray[0].Eat(); animalArray[1].Eat();

Amit a fordt lt az az, hogy ksztettnk egy Animal tpus elemekbl ll tmbt s hogy az elemein meghvtuk az Eat metdust. Csakhogy az Eat virtulis metdus, radsul van leszrmazottbeli implementcija is, amely tdefinilja az eredeti viselkedst, s ezt explicit jelltk is az override kulcsszval. gy a fordt fel tudja ismerni a futsidej tpust, s ezltal temezi a metdushvsokat. Ez az n. ksi kts (late binding). A kimenet gy mr nem lehet ktsges. Mr beszltnk arrl, hogyan pl fel a metdustbla, a fordt megkeresi a legkorbbi implementcit, s most mr azt is tudjuk, hogy az els ilyen implementci egy virtulis metdus lesz, azaz a keress legksbb az els virtulis vltozatnl megll.

LEZRT OSZTLYOK S METDUSOK


Egy osztlyt lezrhatunk, azaz megtilthatjuk, hogy j osztlyt szrmaztassunk belle:
sealed class Dobermann : Dog { } class MyDobermann : Dobermann // ez nem j { }

Ebben az esetben az IntelliSense eleve fel sem ajnlja az osztlyt. Egy metdust is deklarlhatunk lezrtknt, ekkor a leszrmazottak mr nem definilhatjk t a mkdst:
class Dog : Animal { public sealed override void Eat() { Console.WriteLine("Vau - Vau - Hamm - Hamm"); } } sealed class Dobermann : Dog { public override void Eat() // ez sem j { } }

ABSZTRAKT OSZTLYOK
Egy absztrakt osztlyt nem lehet pldnyostani. A ltrehozsnak clja az, hogy kzs felletet biztostsunk a leszrmazottainak:

142

using System; namespace TestApp { abstract class Animal { abstract public void Eat(); } class Dog : Animal { public override void Eat() { Console.WriteLine("Kutya eszik..."); } } class Program { static void Main(string[] args) { // Animal a = new Animal(); //ez nem fordul le Dog d = new Dog(); d.Eat(); Console.ReadKey(); } } }

Lthat, hogy mind az osztly, mind a metdus absztraktknt lett deklarlva, ugyanakkor a metdus (ltszlag) nem virtulis s nincs defincija. Egy absztrakt osztly csak a fordts kzben absztrakt, a lefordtott kdban teljesen normlis osztlyknt szerepel, virtulis metdusokkal. A fordt feladata az, hogy betartassa a r vonatkoz szablyokat, amelyek a kvetkezk: absztrakt osztlyt nem lehet pldnyostani, absztrakt metdusnak nem lehet defincija, a leszrmazottaknak definilnia kell az rkltt absztrakt metdusokat.

Absztrakt osztly tartalmazhat nem absztrakt metdusokat is, ezek pont gy viselkednek, mint a hagyomnyos nem-virtulis trsaik. Az rkltt absztrakt metdusokat az override kulcssz segtsgvel tudjuk definilni (hiszen virtulisak, mg ha nem is ltszik). Amennyiben egy osztlynak van legalbb egy absztrakt metdusa, az osztlyt is absztraktknt kell jellni. Annak ellenre, hogy egy absztrakt osztlyt nem pldnyosthatunk, mg lehet konstruktora, mgpedig azrt, hogy bellthassuk vele az adattagokat:
abstract class Animal { public Animal(string name) { this.Name = name; } public string Name { get; set; } abstract public void Eat(); }

143

class Dog : Animal { public Dog(string name) : base(name) { } public override void Eat() { Console.WriteLine("Kutya eszik..."); } }

Vajon hogyan mkdik a kvetkez pldban a polimorfizmus elve? :


Animal[] animalArray = new Animal[2]; animalArray[0] = new Dog("Fli"); animalArray[1] = new Crocodile("Aladr");

Ennek a kdnak hiba nlkl kell fordulnia, hiszen tnylegesen egyszer sem pldnyostottuk az absztrakt sosztlyt. A fordt csak azt fogja megvizsglni, hogy mi van a new opertor jobb oldaln, az alaposztly nem rdekli. Termszetesen a kvetkez esetben nem fordulna le:
animalArray[0] = new Animal("Animal");

144

INTERFSZEK
Az interfszek hasonlak az absztrakt osztlyokhoz, abban az rtelemben, hogy meghatrozzk egy osztly viselkedst, fellett. A nagy klnbsg a kett kzt az, hogy mg elbbi eleve meghatroz egy osztlyhierarchit, egy interfsz nem kthet kzvetlenl egy osztlyhoz, mindssze elr egy mintt, amely megvalstsra vr. Egy msik elnye az interfszek hasznlatnak, hogy mg egy osztlynak csak egy se lehet, addig brmennyi interfszt megvalsthat. Ezenfell interfszt hasznlhatunk struktrk esetben is. A kvetkez pldban trjuk az Animal sosztlyt interfszre:
using System; namespace TestApp { interface IAnimal { void Eat(); } class Dog : IAnimal { public void Eat() { Console.WriteLine("Kutya eszik..."); } } class Program { static void Main(string[] args) { Dog d = new Dog(); d.Eat(); Console.ReadKey(); } } }

Az interfsz nevt konvenci szerint nagy I betvel kezdjk. Lthat, hogy a metdusokhoz nem tartozik definci, csak deklarci. A megvalst osztly dolga lesz majd implementlni a tagjait. Egy interfsz a kvetkezket tartalmazhatja: metdusok, tulajdonsgok, indexelk s esemnyek (errl hamarosan). A tagoknak nincs kln megadott lthatsguk, mindannyiuk elrhetsge publikus. Magnak az interfsznek az elrhetsge alapesetben publikus, illetve jellhetjk internalknt, msfle lthatsgot nem adhatunk meg (illetve osztlyon bell deklarlt (begyazott) interfsz elrhetsge lehet privt). Egy interfszt implementl osztlynak meg kell valstania az interfsz metdusait, egyetlen kivtellel: ha a szban forg osztly egy absztrakt osztly. Ekkor az interfsz metdusait abszraktknt jellve elhalaszthatjuk a metdusdefincit az absztrakt osztly leszrmazottainak implementlsig:
interface IAnimal { void Eat(); } abstract class AbstractAnimal : IAnimal { public abstract void Eat(); }

145

Fontos, hogy amennyiben egy osztlybl is szrmaztatunk, akkor a felsorolsnl az sosztly nevt kell elrevenni, utna jnnek az interfszek:
class Base { } interface IFace { } class Derived : IFace, Base { } // ez nem fordul le

Egy interfszt szrmaztathatunk ms interfszekbl:


using System; namespace TestApp { interface IAnimal { void Eat(); } interface IDog : IAnimal { void Vau(); } class Dog : IDog { public void Eat() { Console.WriteLine("Kutya eszik..."); } public void Vau() { Console.WriteLine("Vau - Vau"); } } class Program { static void Main(string[] args) { Dog d = new Dog(); d.Eat(); d.Vau(); Console.ReadKey(); } } }

Ekkor termszetesen az sszes interfszt meg kell valstanunk. Egy adott interfszt megvalst objektumot implicit mdon tkonvertlhatunk az interfsz tpusra:
Dog d = new Dog(); IAnimal ia = d; IDog id = d; ia.Eat(); id.Vau();

146

Az is s as opertorokkal pedig azt is megtudhatjuk, hogy egy adott osztly megvalste egy interfszt:
Dog d = new Dog(); IAnimal ia = d as IAnimal; if (ia != null) { Console.WriteLine("Az objektum megvalstja az IAnimal -t"); } if (d is IDog) { Console.WriteLine("Az objektum megvalstja az IDog -ot"); }

EXPLICIT INTERFSZIMPLEMENTCI
Ha tbb interfszt implementlunk, az nvtkzshez is vezethet. Ennek kikszblsre explicit mdon megadhatjuk a megvalstani kvnt funkcit:
using System; namespace TestApp { interface IOne { void Method(); } interface ITwo { void Method(); } class Test : IOne, ITwo { public void Method() { Console.WriteLine("Method!"); } } class Program { static void Main(string[] args) { Test t = new Test(); t.Method(); Console.ReadKey(); } } }

Ez a forrskd lefordul, s a metdust is meg tudjuk hvni. A problma ott van, hogy kt metdust kellene implementlnunk, de csak egy van viszont a program mkdik. Persze nem ez az elvrt viselkeds, ezrt ilyen esetekben explicit mdon meg kell mondanunk, hogy melyik metdus/tulajdonsg/stb. melyik interfszhez tartozik. rjuk t a fenti kdot:

147

class Test : IOne, ITwo { void IOne.Method() { Console.WriteLine("IOne Method!"); } void ITwo.Method() { Console.WriteLine("ITwo Method!"); } }

Vegyk szre, hogy nem hasznltunk lthatsgi mdostt, ilyenkor az interfsz lthatsga rvnyes ezekre a tagokra. jabb problmnk van, mghozz az, hogy hogyan fogjuk meghvni a metdusokat. Most fogjuk kihasznlni, hogy egy osztly konvertlhat a megvalstott interfszek tpusra:
Test t = new Test(); ((IOne)t).Method(); // ez mkdik ITwo it = t; it.Method(); // ez is mkdik

VIRTULIS TAGOK
Egy interfsz tagjai alaprtelmezs szerint lezrtak, de a megvalstsnl jellhetjk ket virtulisnak. Ezutn az osztly leszrmazottjai tetszs szerint mdosthatjk a defincit, a mr ismert override kulcsszval:
class Dog : IDog, IAnimal { public void Eat() { Console.WriteLine("Kutya eszik..."); } public virtual void Vau() { Console.WriteLine("Vau - Vau"); } } class BigDog : Dog { public override void Vau() { Console.WriteLine("VAU-VAU"); } }

Egy leszrmazott jraimplementlhatja az adott interfszt, amennyiben nemcsak az snl, de az utdnl is jelljk a megvalstst:
class BigDog : Dog, IAnimal { public new void Eat() {

148

Console.WriteLine("Kutya nagyon eszik..."); } public override void Vau() { Console.WriteLine("VAU-VAU"); } }

Ez esetben hasznlnunk kell a new kulcsszt annak jellsre, hogy eltakarjuk az s megvalstst.

149

OPERTOR KITERJESZTS
Nyilvn szeretnnk, hogy az ltalunk ksztett tpusok hasonl funkcionalitssal rendelkezzenek, mint a beptett tpusok (int, string stb.). Vegyk pl. azt a pldt, amikor egy mtrix tpust valstunk meg! J lenne, ha az sszeads, kivons, szorzs stb. mveleteket gy tudnnk vgrehajtani, mint egy egsz szm esetben, nem pedig metdushvsokkal. Szerencsre a C# ezt is lehetv teszi szmunkra, ugyanis engedi az opertorok kiterjesztst (operator overloading), vagyis egy adott opertort tetszs szerinti funkcival ruhzhatunk fel az osztlyunkra vonatkoztatva.
Matrix m1 = new Matrix(20, 20); Matrix m2 = new Matrix(20, 20); //ez is j m1.Add(m2); //de ez mg jobb lenne m1 += m2;

A kiterjeszthet opertorok listja: +(unris) -% >> >= -(unris) + & == <= ! | != ~ * ^ > ++ / << <

A C# nyelvben az opertorok valjban statikus metdusok, paramtereik az operandusok, visszatrsi rtkk pedig az eredmny. Egy egyszer plda:
class MyInt { public MyInt(int value) { this.Value = value; } public int Value { get; private set; } static public MyInt operator +(MyInt lhs, MyInt rhs) { return new MyInt(lhs.Value + rhs.Value); } }

A + opertor mkdst fogalmaztuk t. A paramterek (operandusok) nevei konvenci szerint lhs (left-handside) s rhs (right-hand-side), utalva a jobb s bal oldali operandusra. Teht most mr nyugodtan rhatom a kvetkezt:
MyInt x = new MyInt(10); MyInt y = new MyInt(20); MyInt result = x + y; Console.WriteLine(result.Value); // 30

150

Mivel definiltunk az osztlyunkon egy sajt opertort, gy a fordt tudni fogja, hogy azt hasznlja, s talaktja a mveletet az albb lthat mdon. Ezt azonban csakis a fordt teheti meg, neknk a rendes formt kell hasznlnunk.
MyInt result = MyInt.operator+(x, y);

EGYENLSG OPERTOROK
A .NET megfogalmaz nhny szablyt az opertor-kiterjesztssel kapcsolatban. Ezek egyike az, hogy ha tlterheljk az egyenlsg opertort (==), akkor definilnunk kell a nem-egyenl (!=) opertort is:
class MyInt { public MyInt(int value) { this.Value = value; } public int Value { get; private set; } static public MyInt operator +(MyInt lhs, MyInt rhs) { return new MyInt(lhs.Value + rhs.Value); } static public bool operator ==(MyInt lhs, MyInt rhs) { return lhs.Value == rhs.Value; } static public bool operator !=(MyInt lhs, MyInt rhs) { return !(lhs == rhs); } }

A nem-egyenl opertor esetben a sajt egyenlsg opertort hasznltuk fel. A megvalsts elve nem felttlenl vilgos, elsknt megvizsgljuk, hogy a kt elem egyenle, de mi a nem-egyenlsgre vagyunk kvncsiak, ezrt tagadjuk az eredmnyt, ami pontosan ezt a vlaszt adja meg. Ezekhez az opertorokhoz tartozik a System.Object tpustl rklt virtulis Equals metdus is, ami a CLS kompatibilitst hivatott megrizni, errl ksbb mg lesz sz. A fenti esetben ezt a metdust is illik megvalstani. Az Equals azonban egy kicsit klnbzik, egyetlen object tpus paramtert vr, ezrt meg kell majd gyzdnnk arrl, hogy valban a sajt objektumunkkal van-e dolgunk:
public override bool Equals(object rhs) { if (!(rhs is MyInt)) { return false; } return this == (MyInt)rhs; }

Mivel ez egy pldny tag, ezrt a thist hasznljuk az objektum jellsre, amin meghvtuk a metdust.

151

Ha az Equals t megvalstottuk, akkor ezt kell tennnk a szintn az object tl rkltt GetHashCode metdussal is, gy az osztly hasznlhat lesz gyjtemnyekkel s a HashTable tpussal is. A legegyszerbb implementci visszaad egy szmot az adattag(ok)bl szmolva (pl.: hatvnyozs, biteltols stb):
public override int GetHashCode() { return this.Value << 2; }

A ++/-- OPERTOROK
Ez a kt opertor elg nagy fejfjst tud okozni, nzzk meg a kvetkez kdot:
using System; namespace TestApp { class MyInt { public MyInt(int value) { this.Value = value; } public int Value { get; private set; } static public MyInt operator ++(MyInt rhs) { ++rhs.Value; return rhs; } } class Program { static void Main(string[] args) { MyInt x = new MyInt(10); Console.WriteLine(x.Value); ++x; Console.WriteLine(x.Value); MyInt y = x++; Console.WriteLine(x.Value); Console.WriteLine(y.Value); Console.ReadKey(); } } }

// 10 // 11 // 12 // 12 (!)

Nzzk az utols sort! Mivel y a postfixes formban kapott rtket, ezrt 11et kellene tartalmaznia, ehelyett x++ esetben pontosan ugyanaz trtnik, mint ha ++x-et rtunk volna. A problma, hogy kifejezetten postfixes opertort nem tudunk definilni, az ltalunk ksztett kd minden esetben a prefix opertort jelenti. Viszont meghvhatjuk az ltalunk definilt opertorokat postfixes alakban, ekkor az adott objektumot eltrolja a rendszer, meghvja az rtket nvel kdrszletet, s visszadja az els lpsben flrerakott rtket. Ugye ez rtktpusok esetben tkletesen mkdik, de referenciatpusoknl az els lpsben nem az rtket, hanem a referencit troljuk, vagyis a ksbbi vltozs itt is rvnyben lesz.

152

Ugyanakkor ltezik mdszer arra, hogy mgis megoldjuk a fenti problmt: az opertornak egy teljesen j objektumot kell visszaadnia, ekkor erre mr nem mutat korbbi referencia, vagyis biztonsgosan hasznlhat, viszont az j mvelet (az objektum ltrehozsa) miatt a teljestmnyre negatv hatssal lehet (ez persze nha elfogadhat).

RELCIS OPERTOROK
Hasonlan a logikai opertorokhoz, a relcis opertorokat is csak prban lehet elkszteni, vagyis (<, >) s (<=, >=):
static public bool operator <(MyInt lhs, MyInt rhs) { return lhs.Value < rhs.Value; } static public bool operator >(MyInt lhs, MyInt rhs) { return lhs.Value > rhs.Value; }

Ebben az esetben az IComparable s IComparable<T> interfszek megvalstsa is szksges lehet a klnbz gyjtemnyekkel val egyttmkds rdekben. Ezekkel hamarosan megismerkednk.

KONVERZIS OPERTOROK
A C# a szkebbrl tgabbra konverzikat implicit mdon (azaz klnsebb jells nlkl), mg a tgabbrl szkebbre konvertlst explicite (ezt jellnnk kell) vgzi. Termszetesen szeretnnk, hogy a sajt tpusunk is kpes legyen ilyesmire, s bizony erre is ltezik opertor. Ezeknl az opertoroknl az implicit illetve explicit kulcsszavakkal fogjuk jellni a konverzi tpust:
static public implicit operator MyInt(int rhs) { return new MyInt(rhs); } static public explicit operator MyInt(string rhs) { return new MyInt(int.Parse(rhs)); }

Ezeket most gy hasznlhatjuk:


MyInt x = 10; // implicit konverzi MyInt y = (MyInt)"20"; //explicit konverzi

Fontos, hogy a konverzis opertorok mindig statikusak.

153

KIVTELKEZELS
Vannak esetek, amikor az alkalmazsunk, gond nlkl lefordul, mgsem gy mkdni, ahogy elkpzeltk. Az ilyen abnormlis mkds kezelsre talltk ki a kivtelkezelst. Amikor az alkalmazsunk rossz llapotba kerl, akkor egy n. kivtelt fog dobni, ilyennel mr tallkoztunk a tmbknl, amikor tlindexeltnk:
using System; namespace TestApp { class Program { static void Main(string[] args) { int[] array = new int[2]; array[2] = 10; } } }

Itt az utols rvnyes index az 1 lenne, gy kivtelt kapunk, mgpedig egy System.IndexOutOfRangeExceptio-t. Ezutn a program lell. Termszetesen mi azt szeretnnk, hogy valahogy kijavthassuk ezt a hibt, ezrt el fogjuk kapni a kivtelt. Ehhez a mvelethez hrom dologra van szksgnk: kijellni azt a programrszt, ami dobhat kivtelt, elkapni azt, s vgl kezelni a hibt:
using System; namespace TestApp { class Program { static void Main(string[] args) { int[] array = new int[2]; try { array[2] = 10; } catch (System.IndexOutOfRangeException e) { Console.WriteLine(e.Message); } Console.ReadKey(); } } }

A try blokk jelli ki a lehetsges hibaforrst, a catch pedig elkapja a megfelel kivtelt (arra figyeljnk, hogy ezek is blokkok, azaz a blokkon bell deklarlt vltozk a blokkon kvl nem lthatak). A fenti programra a kvetkez lesz a kimenet: Index was outside the bounds of the array Lthat, hogy a kivtel egy objektum formjban ltezik. Minden kivtel se a System.Exception osztly, gy ha nem specilisan egy kivtelt akarunk elkapni, akkor rhattuk volna ezt is:

154

try { array[2] = 10; } catch (System.Exception e) { Console.WriteLine(e.Message); }

Ekkor minden kivtelt el fog kapni a catch blokk. A System.Exception tulajdonsgai kzl kettt kell megemltennk: Message: ez egy olvashatbb formja a kivtel oknak. InnerException: ez alaprtelmezetten null rtkkel rendelkezik, akkor kap rtket, ha tbb kivtel is trtnik. rtelemszeren ekkor a legjabb kivtelt kaphatjuk el, s az InnerException-n keresztl kvethetjk vissza az eredeti kivtelig.

Nzzk meg, hogyan mkdnek a kivtelek! Kivtel kt mdon keletkezhet: vagy a throw utastssal szndkosan mi magunk idzzk el, vagy az alkalmazs hibs mkdse miatt. Abban a pillanatban, amikor a kivtel megszletik, a rendszer azonnal elkezdi keresni a legkzelebbi megfelel catch blokkot, elsknt abban a metdusban, amelyben a kivtel keletkezett, majd ha ott nem volt sikeres, akkor abban, amely ezt a metdust hvta (s gy tovbb, amg nem tall olyat, amely kezeln). A keress kzben kt dolog is trtnhet. Ha egy statikus tag vagy konstruktor inicializlsa trtnik, az ekkor szintn kivtellel jr, mghozz egy System.TypeInitializationExceptionnel, amely kivtelobjektum InnerException tulajdonsgba kerl az eredeti kivtel. A msik lehetsg, hogy nem tall megfelel catch blokkot, ekkor a program futsa hibazenet trsasgban lell. Ha mgis tallt hasznlhat catch blokkot, akkor a kivtel helyrl a vezrls a tallt catch re kerl. Ha tbb egymsba gyazott kivtelrl van sz, akkor a megelz catch blokkhoz tartoz finally blokk fut le, majd ezutn kvetkezik a catch blokk. Kivtelt a throw utastssal dobhatunk:
using System; namespace TestApp { class Program { static void Main(string[] args) { try { throw new System.Exception("Kivtel. Hurr!"); } catch (Exception e) { Console.WriteLine(e.Message); } Console.ReadKey(); } } }

A catchnek nem ktelez megadni a kivtel tpust, ekkor minden kivtelt elkap:

155

try { throw new System.Exception(); } catch { Console.WriteLine("Kivtel. Hurr!"); }

Ilyenkor viszont nem hasznlhatjuk a kivtelobjektumot.

KIVTEL HIERARCHIA
Amikor kivtel dobdik, akkor a vezrlst az els alkalmas catch blokk veszi t. Mivel az sszes kivtel a System.Exception osztlybl szrmazik, gy ha ezt adjuk meg a catchnl, akkor az sszes lehetsges kivtelt el fogjuk kapni vele. Egyszerre tbb catch is llhat egyms utn, de ha van olyan, amelyik az s kivtelt kapja el, akkor a program csak akkor fog lefordulni, ha az az utols helyen ll, hiszen a tbbinek eslye sem lenne.
try { int[] array = new int[2]; array[3] = 10; } catch (System.IndexOutOfRangeException) { Console.WriteLine("OutOfRange"); } catch (System.Exception) { Console.WriteLine("Exception"); }

Lthat, hogy a catchnek elg csak a kivtel tpust megadni, persze ekkor nem hasznlhatjuk a kivtelobjektumot.

KIVTEL KSZTSE
Mi magunk is kszthetnk kivtelt, a System.Exception-bl szrmaztatva:
using System; namespace TestApp { class MyException : System.Exception { public MyException() { } public MyException(string message) : base(message) { } public MyException(string message, Exception inner) : base(message, inner) { }

156

} class Program { static void Main(string[] args) { try { throw new MyException("Kivtel. Hurr!"); } catch (MyException e) { Console.WriteLine(e.Message); } Console.ReadKey(); } } }

KIVTELEK TOVBBADSA
Egy kivtelt az elkapsa utn ismt eldobhatunk. Ez hasznos olyan esetekben, amikor feljegyzst akarunk kszteni, illetve ha egy specifikusabb kivtelkezelnek akarjuk tadni a kivtelt:
try { } catch (System.ArgumentException e) { throw; //tovbbadjuk throw (new System.ArgumentNullException()); //vagy egy jat dobunk }

Ilyenkor bellthatjuk az Exception InnerException tulajdonsgt is:


try { throw new Exception(); } catch (System.ArgumentException e) { throw (new System.ArgumentNullException("Tovbb", e)); }

Itt az Exception osztly harmadik konstruktort hasznltuk, az j kivtel mr tartalmazni fogja a rgit is.

FINALLY BLOKK
A kivtelkezels egy problmja, hogy a kivtel keletkezse utn az ppen vgrehajtott programrsz futsa megszakad, gy elfordulhat, hogy nem szabadulnak fel idben az erforrsok (megnyitott fjl, hlzati kapcsolat stb.), illetve objektumok olyan formban maradnak meg a memriban, amely hibt okozhat. Megoldst a finallyblokk hasznlata jelent, amely fggetlenl attl, hogy trtnt-e kivtel, mindig lefut:

157

using System; namespace TestApp { class Program { static void Main(string[] args) { int x = 10; try { Console.WriteLine("x rtke a kivtel eltt: {0}", x); throw new Exception(); } catch (Exception) { Console.WriteLine("Kivtel. Hurr!"); } finally { Console.WriteLine("Finally blokk"); x = 11; } Console.WriteLine("x rtke a kivtel utn {0}", x);

Console.ReadKey(); } } }

Valdi erforrsok kezelsekor knyelmesebb a usingblokk hasznlata (ld. IDisposable fejezet), mivel az automatikusan lezrja azokat. Lnyegben a using-blokkal hasznlt erforrsok fordts utn a megfelel trycatch-finally blokkokk alakulnak.

158

GYAKORL FELADATOK IV.


IENUMERATOR S IENUMERABLE
Ksztsnk osztlyt, amely megvalstja az IEnumerator s IEnumerable interfszeket! Megolds Korbban mr tallkoztunk a foreach ciklussal, s mr tudjuk, hogy csak olyan osztlyokon kpes vgigiterlni, amelyek megvalstjk az IEnumerator s IEnumerable interfszeket. Mindkett a System.Collections nvtrben tallhat. Elsknt nzzk az IEnumerable interfszt:
public interface IEnumerable { IEnumerator GetEnumerator(); }

Ez a foreach-nek fogja szolgltatni a megfelel felletet, ugyanis a ciklus meghvja a metdust, s annak vissza kell adnia az osztlyt IEnumerator-knt (ld. implicit konverzi). Ezrt kell megvalstani egyttal az IEnumerator interfszt is, ami gy nz ki:
public interface IEnumerator { bool MoveNext(); void Reset(); object Current { get; } }

A MoveNext a kvetkez elemre mozgatja a mutatt, ha tudja, ellenkez esetben (vagyis ha a lista vgre rt) false rtkkel tr vissza. A Reset alaprtelmezsre lltja a mutatt, azaz -1 re. Vgl a Current (read-only) tulajdonsg az aktulis pozciban lv elemet adja vissza. Ennek object tpussal kell visszatrnie, hiszen minden tpusra mkdnie kell (ltezik generikus vltozata is, de errl ksbb). Hasznljuk az Animal osztlyunk egy kiss mdostott vltozatt:
public class Animal { public Animal(string name) { this.Name = name; } public string Name { get; private set; } }

Most ksztsnk egy osztlyt, amelyen megvalstjuk a kt interfszt, s ami tartalmaz egy Animal objektumokbl ll listt:
public class AnimalContainer : IEnumerable, IEnumerator { private ArrayList container = new ArrayList(); private int currPosition = -1;

159

public AnimalContainer() { container.Add(new Animal("Fli")); container.Add(new Animal("Bunds")); container.Add(new Animal("Parizer")); } }

Ez persze mg nem az egsz osztly, felvettnk egy ArrayListet, amiben eltroljuk az objektumokat, illetve deklarltunk egy egsz szmot, ami az aktulis pozcit trolja el, s kezdrtkl -1et adtunk (ld. Reset). Ksztsk el az IEnumerator ltal ignyelt metdusokat:
public bool MoveNext() { return (++currPosition < container.Count); } public object Current { get { return container[currPosition]; } } public void Reset() { currPosition = -1; }

Vgl az IEnumerable interfszt valstjuk meg:


public IEnumerator GetEnumerator() { return (IEnumerator)this; }

Ezutn hasznlhatjuk is az osztlyt:


AnimalContainer ac = new AnimalContainer(); foreach (Animal animal in ac) { Console.WriteLine(animal.Name); }

Amennyiben a foreach-en kvl akarjuk hasznlni az osztlyt, pl. ha ksztettnk indexelt is, akkor gondoskodnunk kell a megfelel konverzirl is (a foreach kivtelt kpez, mivel ezt megteszi helyettnk).

ICOMPARABLE S ICOMPARER
Valstsuk meg az IComparable illetve IComparer interfszt! Megolds A msodik gyakorlati pldnkban az IComparable interfszt fogjuk megvalstani, amelyre gyakran van szksgnk. Ez az interfsz ltalban olyan adatszerkezeteknl kvetelmny, amelyek az elemeiken megvalstanak valamilyen rendezst. A generikus List tpusnak is van rendez metdusa (Sort), amely ezzel a metdussal dolgozik. Az IComparable egyetlen metdussal, a CompareTo-val rendelkezik, amely egy object tpust kap paramterl:

160

class ComparableClass : IComparable { public ComparableClass(int value) { this.Value = value; } public int Value { get; private set; } public int CompareTo(object o) { if (o is ComparableClass) { ComparableClass c = (ComparableClass)o; return Value.CompareTo(c.Value); } else throw (new Exception("Nem megfelel objektum...")); } }

Az osztlyban a beptett tpusok CompareTo metdust hasznltuk, hiszen k mind megvalstjk ezt az interfszt. Ez a metdus -1et ad vissza, ha a hv fl kisebb, 0t, ha egyenl s 1et, ha nagyobb. A hasznlata:
List<ComparableClass> list = new List<ComparableClass>(); Random r = new Random(); for (int i = 0; i < 10; ++i) { list.Add(new ComparableClass(r.Next(1000))); } foreach (ComparableClass c in list) { Console.Write("{0} ", c.Value); } Console.WriteLine("\nA rendezett lista:"); list.Sort(); foreach (ComparableClass c in list) { Console.Write("{0} ", c.Value); }

A List<T> tpus hasznlathoz szksg van a System.Collections.Generic nvtrre, bvebben a Generikusok cm fejezetben olvashatunk rla. Hasonl feladatot lt el, de jval rugalmasabb az IComparer interfsz. Az IComparer osztlyok nem rszei az eredeti osztlyoknak, gy olyan osztlyok esetn is hasznlhatunk ilyet, amelyek implementcijhoz nem frnk hozz. Pldul a List<T> rendezsnl megadhatunk egy sszehasonlt osztlyt, amely megvalstja az IComparer interfszt, gy tetszleges rendezst valsthatunk meg anlkl, hogy ki kellene egszteni magt a List<T> -t. Most is csak egy metdust kell elksztennk, ez a Compare, amely kt object tpust vr paramtereknt (illetve felhasznltuk az elzleg elksztett ComparableClass osztlyt):
class ComparableClassComparer : IComparer {

161

public int Compare(object x, object y) { if(x is ComparableClass && y is ComparableClass) { ComparableClass _x = (ComparableClass)x; ComparableClass _y = (ComparableClass)y; return _x.CompareTo(_y); } else throw(new Exception("Nem megfelel paramter..."); } }

A metdus elksztsnl az egyszersg miatt feltteleztk, hogy az sszehasonltott osztly megvalstja az IComparable interfszt (a beptett tpusok ilyenek), termszetesen mi magunk is megrhatjuk az eredmnyt elllt programrszt. Ezutn a kvetkezkppen rendezhetjk a listt:
list.Sort(new ComparableClassComparer());

Az IComparer elnye, hogy nem ktdik szorosan az osztlyhoz (akr anlkl is megrhatjuk, hogy ismernnk a bels szerkezett), gy tbbfle megvalsts is lehetsges.

MTRIX TPUS
Ksztsk el a mtrix tpust, s valstsuk meg rajta az sszeads mveletet! Az egyszersg kedvrt tegyk fel, hogy a mtrix csak egsz szmokat trol! Megolds
class Matrix { int[,] matrix; public Matrix(int n, int m) { matrix = new int[n, m]; } public int N { get { return matrix.GetLength(0); } } public int M { get { return matrix.GetLength(1); } } public int this[int idxn, int idxm] { get { return matrix[idxn, idxm]; } set { matrix[idxn, idxm] = value; } } static public Matrix operator +(Matrix lhs, Matrix rhs) { if (lhs.N != rhs.N || lhs.M != rhs.M) return null; Matrix result = new Matrix(lhs.N, lhs.M);

162

for (int i = 0; i < lhs.N; ++i) { for (int j = 0; j < lhs.M; ++j) { result[i, j] = lhs[i, j] + rhs[i, j]; } } return result; } }

Mtrixokat gy adunk ssze, hogy az azonos indexeken lv rtkeket sszeadjuk: 123 145 1+1 2+4 3+5 456 + 532 = (stb.) 789 211 Az sszeads mveletet csakis azonos nagysg dimenzik mellett lehet elvgezni (3x3as mtrixhoz nem lehet hozzadni egy 4x4 eset). Ezt ellenriztk is az opertor megvalstsnl. Most csak az sszeads mveletet valstottuk meg, a tbbi a kedves olvasra vr. Plusz feladatknt indexellenrzst is lehet vgezni az indexelnl.

163

DELEGATE
A delegate olyan tpus, amely egy vagy tbb metdusra hivatkozik. Minden delegate klnll objektum, amely egy listt trol a meghvand metdusokrl (rtelemszeren ez egyttal ers referencia is lesz a metdust szolgltat osztlyra). Nemcsak pldny-, hanem statikus metdusokra is mutathat. Egy delegate deklarcijnl megadjuk, hogy milyen szignatrval rendelkez metdusok megfelelek:
delegate int TestDelegate(int x);

Delegate nem deklarlhat blokkon bell, csakis osztlyon bell tagknt, illetve osztlyokon kvl (hasonlan az enum tpusokhoz). Ez a delegate olyan metdusra mutathat, amelynek visszatrsi rtke int tpus s egyetlen int paramtere van, pl.:
static public int Pow(int x) { return (x * x); }

A hasznlata:
TestDelegate dlgt = Pow; int result = dlgt(10);

A delegate-ekhez egynl tbb metdust is hozzadhatunk a += s + opertorokkal, valamint elvehetjk ket a = s opertorokkal. A delegate hvsakor a listjn lv sszes metdust meghvja a megadott paramterre.
class Test { public delegate void TestDelegate(string msg); private TestDelegate handler; public Test() { handler += Test.StaticMethod; handler += this.InstanceMethod; } static public void StaticMethod(string msg) { Console.WriteLine(msg); } public void InstanceMethod(string msg) { Console.WriteLine(msg); } public void CallDelegate(string msg) { handler(msg); } }

A delegateek legnagyobb haszna, hogy nem kell elre megadott metdusokat hasznlnunk, ehelyett ksbb tetszs szerint adhatjuk meg az elvgzend mveletet:

164

class Array { public delegate void Transformer(ref int item); private int[] array; public Array(int length) { Length = length; array = new int[Length]; } public int Length { get; set; } public int this[int idx] { get { return array[idx]; } set { array[idx] = value; } } public void Transform(Transformer t) { for (int i = 0; i < array.Length; ++i) { t(ref array[i]); } } }

A Transform metdus egy delegateet kap paramterl, amely elvgzi a vltoztatsokat a tmbn. Pl.:
class Program { static public void TransformerMethod(ref int item) { item *= item; } static void Main(string[] args) { Array array = new Array(10); for (int i = 0; i < array.Length; ++i) { array[i] = i; } array.Transform(Program.TransformerMethod); for (int i = 0; i < array.Length; ++i) { Console.WriteLine(array[i]); } Console.ReadKey(); } }

Kt delegate szerkezetileg nem egyenl, mg akkor sem ha a szignatrjuk megegyezik:

165

using System; namespace TestApp { class Program { public delegate void Dlgt1(); public delegate void Dlgt2(); static public void Method() { } static void Main(string[] args) { Dlgt1 d1 = Program.Method; Dlgt2 d2 = d1; // ez hibs } } }

Ugyanakkor ugyanazon delegate tpus pldnyai kztt hasznlhatjuk az == s != opertorokat. Kt delegate egyenl, ha mindkett rtke null, illetve ha a hvslistjukon ugyanazon objektumok ugyanazon metdusai szerepelnek (vagy ugyanazok a statikus metdusok):
using System; namespace TestApp { class Test { public void Method() { } } class Program { public delegate void TestDelegate(); static public void Method1() { } static public void Method2() { } static void Main(string[] args) { TestDelegate t1 = null; TestDelegate t2 = null; Console.WriteLine(t1 == t2); // True t1 = Program.Method1; t2 = Program.Method1; Console.WriteLine(t1 == t2); // True t1 += Program.Method2; Console.WriteLine(t1 == t2); // False t1 -= Program.Method2; Test x = new Test(); Test y = new Test(); t1 += x.Method; t2 += y.Method;

166

Console.WriteLine(t1 == t2); // False Console.ReadKey(); } } }

PARAMTER S VISSZATRSI RTK


Egy delegatenek tadott metdus paramterei lehetnek olyan tpusok, amelyek az eredeti paramternl ltalnosabbak:
using System; namespace TestApp { class Animal { } class Dog : Animal { } class Cat : Animal { } class Program { public delegate void DogDelegate(Dog d); static public void AnimalMethod(Animal a) { } static void Main(string[] args) { DogDelegate d = AnimalMethod; Console.ReadKey(); } } }

Ez az n. kontravarins (contravariant) viselkeds. Ennek a fordtottja igaz a visszatrsi rtkre, azaz az tadott metdus visszatrsi rtke lehet specifikusabb az eredetinl:
using System; namespace TestApp { class Animal { } class Dog : Animal { } class Cat : Animal { } class Program { public delegate Animal GetAnimal(); static public Animal AnimalMethod() { return new Animal(); } static public Dog DogMethod() { return new Dog(); } static public Cat CatMethod() { return new Cat(); }

167

static void Main(string[] args) { GetAnimal ga = AnimalMethod; Animal a = ga(); ga = DogMethod; Dog d = (Dog)ga(); ga = CatMethod; Cat c = (Cat)ga(); Console.WriteLine("{0}, {1}, {2}", a.GetType(), d.GetType(), c.GetType()); Console.ReadKey(); } } }

Ezt pedig kovarins (covariant) viselkedsnek nevezzk.

NVTELEN METDUSOK
Egy delegate szmra nem ktelez ltez metdust megadnunk, lehetsgnk van helyben kifejteni egyet. Termszetesen ez a nvtelen metdus a program tbbi rszbl kzvetlenl nem hvhat, csak a delegateen keresztl.
using System; namespace TestApp { class Program { public delegate void Test(int x); static void Main(string[] args) { Test t = delegate(int x) { Console.WriteLine(x); }; t(10); Console.ReadKey(); } } }

Egy nvtelen metdus elri az t trol blokk loklis vltozit, s mdosthatja is ket:
using System; namespace TestApp { class Program { public delegate void Test();

168

static void Main(string[] args) { int x = 10; Test t = delegate() { x = 11; Console.WriteLine(x); }; t(); Console.ReadKey(); } } }

Ilyenkor figyelni kell arra, hogy a kls vltozra a delegate is ers referencival mutat, vagyis a vltoz akkor vlik eltakarthatv, ha a delegate maga is rvnyt veszti. Nvtelen metdus nem hasznlhat semmilyen ugr utastst (pl.: goto, break stb.).

169

ESEMNYEK
Egy osztly esemnyeket (event) hasznlhat, hogy a sajt llapota megvltozsakor rtestsen ms osztlyokat. Ehhez a megfigyel osztlyoknak fel kell iratkozni a megfigyelt osztly esemnyre azltal, hogy az elbbiek rendelkeznek egy, az esemnynek megfelel szignatrj metdussal, n. esemnykezelvel. Az esemny megtrtntekor ezek a metdusok fognak lefutni. Eddig ez nagyon gy hangzik, mintha a delegateekrl beszltnk volna, s valban egy esemny tulajdonkppen egy specilis delegate, mindssze hrom dologban klnbznek: Esemny lehet rsze interfsznek, mg delegate nem. Egy esemnyt csakis az az osztly hvhat meg, amely deklarlta. Egy esemny rendelkezik add s remove metdusokkal, amelyek fellbrlhatak.

Egy esemny deklarcijban meg kell adnunk azt a delegate-et, amely az esemnyhez szksges szignatrt definilja. Nzznk egy egyszer pldt:
class Test { public delegate void EventHandlerDelegate(string message); public event EventHandlerDelegate TestStatusChange; private int data = 10; public int Data { get { return data; } set { data = value; this.OnStatusChange(); } } private void OnStatusChange() { if (TestStatusChange != null) { TestStatusChange("Az osztly llapota megvltozott!"); } } }

Nem felttlenl kell delegateet deklarlnunk, mivel rendelkezsnkre ll a beptett ltalnos EventHandler delegate, amely kt paramterrel (errl hamarosan) rendelkezik, s void visszatrsi tpussal br. Az esemny akkor fog beindulni, amikor a data mez rtke megvltozik. Ekkor meghvjuk az OnStatusChanged metdust, amely elsknt megvizsglja, hogy az esemnyre feliratkoztake vagy sem - utbbi esetben a hvs kivtelt vltana ki. Ezt az osztly gy hasznlhatjuk:
class Program { static public void Handler(string message) { Console.WriteLine(message); } static void Main(string[] args) { Test t = new Test();

170

t.TestStatusChange += Program.Handler; t.Data = 11; Console.ReadKey(); } }

A fenti kdban a TestStatusChange gyakorlatilag delegate-knt mkdik, vagyis egy sajt listt tart fenn a meghvand metdusokrl (esemnykezelkrl). Az esemnyekhez rendelt esemnykezelknek konvenci szerint (ettl eltrhetnk, de a Framework esemnyei mind ilyenek) kt paramtere van, az els az az objektum, amely kivltotta az esemnyt, a msodik pedig az esemnyhez kapcsold informcik. A msodik paramter ekkor olyan tpus lehet, amely az EventArgs osztlybl szrmazik. Mdostsuk ennek megfelelen a fenti programot! Elsknt ksztnk egy EventArgs osztlybl szrmaz j osztlyt, amely kpes trolni az esemnyhez kapcsold zenetet (az EventArgs alaprtelmezetten nem rendelkezik ilyesmivel, csak egy alaposztly a specializlt esemnyekhez):
class TestEventArgs : EventArgs { public TestEventArgs(string message) : base() { this.Message = message; } public string Message { get; set; } }

Ezutn mr csak mdostani kell a delegateet s az esemny kivltst:


public delegate void EventHandlerDelegate(object sender, TestEventArgs e); public event EventHandlerDelegate TestStatusChange; private void OnStatusChange() { if (TestStatusChange != null) { TestStatusChange(this, new TestEventArgs("Az osztly llapota megvltozott")); } }

A sender paramternek a thisszel adjuk meg az rtkt, ezutn explicit konverzival visszakaphatjuk belle a kld pldnyt (az els paramter szintn konvenci szerint minden esetben object tpus lesz, mivel ugyanazt az esemnyt hasznlhatjuk klnbz osztlyokkal). Mg mdostsuk az esemnykezelt is:
static public void Handler(object sender, TestEventArgs e) { Console.WriteLine(e.Message); }

A kvetkez pldban az esemnyek valdi hasznt fogjuk ltni. Ksztsnk egy egyszer kliens-szerver alkalmazst (persze nem a hlzaton, csak szimulljuk)! A kliensek csatlakozhatnak a szerverhez (ezutn pedig kilphetnek). A feladat, hogy minden ilyen esemnyrl kldjnk rtestst az sszes csatlakozott kliensnek. Normlis esetben a szerver osztlynak trolnia kellene a kliensek hivatkozsait pl. egy tmbben. A problma, hogy ez azrt nem olyan egyszer, hiszen gondoskodni kell arrl, hogy a kilpett klienseket trljk a listbl, illetve a lista mrete is gondot jelenthet. Esemnyek alkalmazsval viszont nagyon egyszer lesz a dolgunk. Ksztsnk egy EventArgs osztlyt, amely segtsgnkre lesz az esemnykezelk rtestsben:

171

class ServerEventArgs : EventArgs { public ServerEventArgs(string message) : base() { this.Message = message; } public string Message { get; set; } }

Most pedig a szervert ksztjk el:


class Server { public delegate void ServerEvent(object sender, ServerEventArgs e); public event ServerEvent ServerChange; public Server() { } public void Connect(Client client) { this.ServerChange += client.ServerMessageHandler; OnServerChange(string.Format("Felhasznl <{0}> csatlakozott!", client.Name)); } public void Disconnect(Client client) { OnServerChange(string.Format("Felhasznl <{0}> kilpett!", client.Name)); this.ServerChange -= client.ServerMessageHandler; } protected void OnServerChange(string message) { if (ServerChange != null) { ServerChange(this, new ServerEventArgs(message)); } } }

Lthat, hogy a kliensek kezelse nagyon egyszer, mindssze egy mveletet kell elvgeznnk az esemnyekre val feliratkozsrt/leiratkozsrt. Nzzk meg a kliens osztlyt:
class Client { public Client(string name) { Name = name; } public string Name { get; set; } public void ServerMessageHandler(object sender, ServerEventArgs e) { Console.WriteLine("{0} zenetet kapott: {1}", this.Name, e.Message); } }

172

Vgl a Main:
static void Main(string[] args) { Server server = new Server(); Client c1 = new Client("Jzsi"); Client c2 = new Client("Bla"); Client c3 = new Client("Tomi"); server.Connect(c1); server.Connect(c2); server.Disconnect(c1); server.Connect(c3); Console.ReadKey(); }

173

GENERIKUSOK
Az objektum-orientlt programozs egyik alapkve a kd-jrafelhasznls, vagyis, hogy egy adott kdrszletet elg ltalnosra rjunk meg ahhoz, hogy minl tbbszr felhasznlhassuk. Ennek megvalstsra kt eszkz ll rendelkezsnkre, az egyik az rklds, a msik pedig jelen fejezet trgya, a generikusok.

GENERIKUS METDUSOK
Vegyk a kvetkez metdust:
static public void Swap(ref int x, ref int y) { int tmp = x; x = y; y = tmp; }

Ha szeretnnk, hogy ez a metdus ms tpusokkal is mkdjn, akkor bizony sokat kell gpelnnk. Kivve, ha runk egy generikus metdust:
static public void Swap<T>(ref T x, ref T y) { T tmp = x; x = y; y = tmp; }

A T fogja jelkpezni az aktulis tpust (lehet ms nevet is adni neki, eredetileg a Template szbl jtt), ezt generikus paramternek hvjk. Generikus paramtere csakis osztlynak, interfsznek vagy metdusnak lehet, s ebbl tbbet is hasznlhatnak. Ezutn a metdust a hagyomnyos ton hasznlhatjuk, a fordt felismeri, hogy melyik tpust hasznljuk (ezt megadhatjuk mi magunk is explicite):
int x = 10; int y = 20; Program.Swap<int>(ref x, ref y); Console.WriteLine("x == {0} s y == {1}", x, y); string s1 = "alma"; string s2 = "di"; Program.Swap<string>(ref s1, ref s2); Console.WriteLine("s1 == {0} s s2 == {1}", s1, s2);

A C# generikusai hasonltanak a C++ sablonjaira, de annl kevsb hatkonyak, cserben sokkal biztonsgosabb a hasznlatuk. Kt fontos klnbsg van a kett kzt: mg a C++ fordtsi idben kszti el a specializlt metdusokat/osztlyokat, addig a C# ezt a mveletet futsi idben vgzi el. A msik eltrs az elsbl kvetkezik, mivel a C++ fordtskor ki tudja szrni azokat az eseteket, amelyek hibsak, pl. sszeadunk kt tpust a sablonmetdusban, amelyeken nincs rtelmezve sszeads. A C# ezzel szemben knytelen az ilyen problmkat megelzni, a fordt csakis olyan mveletek elvgzst fogja engedlyezni, amelyek mindenkppen mkdni fognak. A kvetkez plda nem fog lefordulni:

174

static public T Sum<T>(T x, T y) { return x + y; }

Fordtskor csak azt tudja ellenrizni, hogy ltez tpust adtunke meg, gy nem tudhatja a fordt, hogy sikeres lesze a vgrehajts, ezrt a fenti kd az sszeads miatt (nem felttlenl valstja meg minden tpus) rossz.

GENERIKUS OSZTLYOK
Kpzeljk el, hogy azt a feladatot kaptuk, hogy ksztsnk egy verem tpust, amely brmely tpusra alkalmazhat! Azt is kpzeljk el, hogy mg nem hallottunk generikusokrl! gy a legkzenfekvbb megolds, ha az elemeket egy object tpus tmbben troljuk:
class Stack { object[] t; int pointer; readonly int size; public Stack(int capacity) { t = new object[capacity]; size = capacity; pointer = 0; } public void Push(object item) { if (pointer >= size) { throw (new StackOverflowException("Tele van...")); } t[pointer++] = item; } public object Pop() { if (pointer-- >= 0) { return t[pointer]; } pointer = 0; throw (new InvalidOperationException("res...")); } }

Ezt most a kvetkezkppen hasznlhatjuk:


static void Main(string[] args) { Stack s = new Stack(10); for (int i = 0; i < 10; ++i) { s.Push(i); } for (int i = 0; i < 10; ++i) {

175

Console.WriteLine((int)s.Pop()); } Console.ReadKey(); }

Mkdni mkdik, de se nem hatkony, se nem knyelmes. A hatkonysg az rtk/referenciatpusok miatt cskken jelentsen (ld. boxing/unboxing), a knyelem pedig amiatt, hogy mindig figyelni kell, pp milyen tpussal dolgozunk, nehogy olyan kasztolssal ljnk, ami kivtelt dob. Ezeket a problmkat knnyen kikszblhetjk, ha generikus osztlyt ksztnk:
class Stack<T> { T[] t; int pointer; readonly int size; public Stack(int capacity) { t = new T[capacity]; size = capacity; pointer = 0; } public void Push(T item) { if (pointer >= size) { throw (new StackOverflowException("Tele van...")); } t[pointer++] = item; } public object Pop() { if (pointer-- >= 0) { return t[pointer]; } pointer = 0; throw (new InvalidOperationException("res...")); } }

Ezutn akrmelyik tpuson knnyen hasznlhatjuk:


static void Main(string[] args) { Stack<int> s = new Stack<int>(10); for (int i = 0; i < 10; ++i) { s.Push(i); } for (int i = 0; i < 10; ++i) { Console.WriteLine(s.Pop()); }

176

Console.ReadKey(); }

GENERIKUS MEGSZORTSOK
Alaprtelmezetten egy generikus paramter brmely tpust jelkpezhet. A deklarcinl azonban kikthetnk megszortsokat a paramterre. Ezeket a where kulcsszval vezetjk be:
where where where where where where T T T T T T : : : : : : alaposztly interfsz osztly struktra new() U

Az utols kt sor magyarzatra szorul. A new() megszorts olyan osztlyra utal, amely rendelkezik alaprtelmezett konstruktorral. Az U pedig ebben az esetben egy msik generikus paramtert jell, vagyis T olyan tpusnak felel meg, amely vagy U bl szrmazik, vagy egyenl vele. Nzznk nhny pldt:
class Test<T> where T : class { }

Ezt az osztlyt csak referenciatpus generikus paramterrel pldnyosthatjuk, minden ms esetben fordtsi hibt kapunk.
class Test<T> where T : struct { }

Ez pedig pp az ellenkezje, rtktpusra van szksg.


class Test<T> where T : IEnumerable { }

Most csakis IEnumerable interfszt megvalst tpussal pldnyosthatjuk az osztlyt.


class Base { } class Derived : Base { } class Test<T> where T : Base { }

177

Ez mr rdekesebb. Ez a megszorts a generikus paramter sosztlyra vonatkozik, vagyis pldnyosthatunk a Base s a Derived tpussal is.
class DefConst { public DefConst() { } } class Test<T> where T : new() { }

Itt olyan tpusra van szksgnk, amely rendelkezik alaprtelmezett konstruktorral, a DefConst osztly is ilyen.
class Base { } class Derived : Base { } class Test<T, U> where T : U { }

Most T tpusnak olyannak kell lennie, amely implicit mdon konvertlhat U tpusra, vagyis T vagy megegyezik Uval, vagy belle szrmazik:
Test<Derived, Base> t1 = new Test<Derived, Base>(); // ez j Test<Base, Derived> t2 = new Test<Base, Derived>(); // ez nem j

rtelemszeren rhattunk volna <Base, Base>-t vagy <Derived, Derived>-et is.

RKLDS
Generikus osztlybl szrmaztathatunk is, ekkor vagy az sosztly egy specializlt vltozatt vesszk alapul, vagy a nyers generikus osztlyt:
class Base<T> { } class Derived<T> : Base<T> { } //vagy class IntDerived : Base<int> { }

178

STATIKUS TAGOK
Generikus tpusok esetben minden tpushoz kln statikus tag tartozik:
using System; namespace TestApp { class Test<T> { static public int Value; } class Program { static void Main(string[] args) { Test<int>.Value = 10; Test<string>.Value = 20; Console.WriteLine(Test<int>.Value); // 10 Console.WriteLine(Test<string>.Value); // 20 Console.ReadKey(); } } }

GENERIKUS GYJTEMNYEK
A C# 2.0 bevezetett nhny hasznos generikus adatszerkezetet, tbbek kzt listt s vermet. A kvetkezkben megvizsglunk kzlk nhnyat. Ezek a szerkezetek a System.Collections.Generic nvtrben tallhatak. List<T> A List<T> az ArrayList generikus, ersen tpusos megfelelje. A legtbb esetben a List<T> hatkonyabb lesz az ArrayListnl, emellett pedig tpusbiztos is. Amennyiben rtktpussal hasznljuk a List<T>-t, az alaprtelmezetten nem ignyel bedobozolst, de rendelkezik nhny olyan mvelettel, amely viszont igen, ezek fleg a keresssel kapcsolatosak. Azrt, hogy az ebbl kvetkez teljestmnyromlst elkerljk, a hasznlt rtktpusnak meg kell valstania az IComparable s az IEquatable interfszeket (a legtbb beptett egyszer (rtk)tpus ezt meg is teszi) (ezeket az interfszeket az sszes tbbi gyjtemny is ignyli).
using System; using System.Collections.Generic; namespace TestApp { class Program { static void Main(string[] args) { List<int> list = new List<int>(); for (int i = 0; i < 10; ++i) { list.Add(i);

179

} foreach (int item in list) { Console.WriteLine(item); } Console.ReadKey(); } } }

Az Add metdus a lista vghez adja hozz a paramterknt megadott elemet, hasonlan az ArrayListhez. Hasznlhatjuk rajta az indexel opertort is. A lista elemeit knnyen rendezhetjk a Sort metdussal (ez a metdus ignyli, hogy a lista tpusa megvalstsa az IComparable interfszt):
using System; using System.Collections.Generic; namespace TestApp { class Program { static void Main(string[] args) { List<int> list = new List<int>(); Random r = new Random(); for (int i = 0; i < 10; ++i) { list.Add(r.Next(100)); Console.Write("{0}, ", list[i]); // rendezetlen elemek } Console.WriteLine(); list.Sort(); foreach (int item in list) { Console.Write("{0}, ", item); // rendezett elemek } Console.ReadKey(); } } }

Kereshetnk is az elemek kztt a BinarySearch metdussal, amely a keresett objektum indext adja vissza:
using System; using System.Collections.Generic; namespace TestApp { class Program {

180

static void Main(string[] args) { List<string> list = new List<string>() { "alma", "di", "krte", "barack" }; Console.WriteLine(list[list.BinarySearch("krte")]); Console.ReadKey(); } } }

Megkereshetjk az sszes olyan elemet is, amely eleget tesz egy felttelnek a Find s FindAll metdusokkal. Elbbi az els, utbbi az sszes megfelel pldnyt adja vissza egy List<T> szerkezetben:
using System; using System.Collections.Generic; namespace TestApp { class Program { static void Main(string[] args) { List<int> list = new List<int>(); Random r = new Random(); for (int i = 0; i < 100; ++i) { list.Add(r.Next(1000)); } Console.WriteLine("Az elso pros szm a listban: {0}", list.Find(delegate(int item) { return item % 2 == 0; })); List<int> evenList = list.FindAll(delegate(int item) { return item % 2 == 0; }); Console.WriteLine("Az sszes pros elem a listban:"); evenList.ForEach(delegate(int item) { Console.WriteLine(item); }); Console.ReadKey(); } } }

A felttelek megadsnl s a pros szmok listjnak kiratsnl nvtelen metdusokat hasznltunk. jdonsgot jelent a listn metdusknt hvott foreach ciklus. Ezt a C# 3.0 vezette be, s az sszes generikus adatszerkezet rendelkezik vele, lnyegben teljesen ugyangy mkdik, mint egy igazi foreach.

181

Paramtereknt egy void Method(T item) szignatrj metdust (vagy nvtelen metdust) vr, ahol T a lista elemeinek tpusa. SortedList<T, U> s SortedDictionary<T, U> A SortedList<T, U> kulcsrtk prokat trol el, s a kulcs alapjn rendezi is ket:
using System; using System.Collections.Generic; namespace TestApp { class Program { static void Main(string[] args) { SortedList<string, int> list = new SortedList<string, int>(); list.Add("egy", 1); list.Add("kett", 2); list.Add("hrom", 3); Console.ReadKey(); } } }

A lista elemei tulajdonkppen nem a megadott rtkek, hanem a kulcsrtk prokat reprezentl KeyValuePair<T, U> objektumok. A lista elemeinek elrshez is hasznlhatjuk ezeket:
foreach (KeyValuePair<string, int> item in list) { Console.WriteLine("Kulcs == {0}, rtk == {1}", item.Key, item.Value); }

A lista kulcsai csakis olyan tpusok lehetnek, amelyek megvalstjk az IComparable interfszt, hiszen ez alapjn trtnik a rendezs. Ha ez nincs gy, akkor mi magunk is definilhatunk ilyet, rszletekrt ld. az Interfszek fejezetet. A listban minden kulcsnak egyedinek kell lennie (ellenkez esetben kivtelt kapunk), illetve kulcs helyn nem llhat null rtk (ugyanez viszont nem igaz az rtkekre). A SortedDictionary<T, U> hasznlata gyakorlatilag megegyezik a SortedList<T, U>-val, a klnbsg a teljestmnyben s a bels szerkezetben van. A SD j (rendezetlen) elemek beszrst gyorsabban vgzi, mint a SL (O(Log n) s O(n)). Elre rendezett elemek beszrsnl pont fordtott a helyzet. Az elemek kzti keress mindkt szerkezetben O(Log n). Ezenkvl a SL kevesebb memrit hasznl fel. Dictionary<T, U> A SortedDictionary<T, U> rendezetlen prja a Dictionary<T, U>:
using System; using System.Collections.Generic; namespace TestApp { class Program { static void Main(string[] args)

182

{ Dictionary<string, int> list = new Dictionary<string, int>(); list.Add("egy", 1); list.Add("kett", 2); list.Add("hrom", 3); foreach (KeyValuePair<string, int> item in list) { Console.WriteLine("Kulcs == {0}, rtk == {1}", item.Key, item.Value); } Console.ReadKey(); } } }

Teljestmny szempontjbl a Dictionary<T, U> mindig jobb eredmnyt fog elrni (egy elem keresse kulcs alapjn O(1)), ezrt ha nem fontos szempont a rendezettsg, akkor hasznljuk ezt! LinkedList<T> A LinkedList<T> egy ktirny lncolt lista. Egy elem beillesztse illetve eltvoltsa O(1) nagysgrend mvelet. A lista minden tagja klnll objektum, egy-egy LinkedListNode<T> pldny. A LinkedListLNode<T> Next s Previous tulajdonsgai a megelz illetve a kvetkez elemre mutatnak. A lista First tulajdonsga az els, Last tulajdonsga pedig az utols tagra mutat. Elemeket az AddFirst (els helyre szr be) s AddLast (utols helyre tesz) metdusokkal tudunk beilleszteni.
using System; using System.Collections.Generic; namespace TestApp { class Program { static void Main(string[] args) { LinkedList<string> list = new LinkedList<string>(); list.AddLast("alma"); list.AddLast("di"); list.AddLast("krte"); list.AddFirst("narancs"); LinkedListNode<string> current = list.First; while (current != null) { Console.WriteLine(current.Value); current = current.Next; } Console.ReadKey(); } } }

Ebben a pldban bejrunk egy lncolt listt, a kimeneten a narancs elemet ltjuk majd els helyen, mivel t az AddFirst metdussal helyeztk be.

183

ReadOnlyCollection<T> Ahogy a nevbl ltszik, ez az adatszerkezet az elemeit csak olvassra adja oda. A listhoz nem adhatunk j elemet sem (ezt nem is tmogatja), csakis a konstruktorban tlthetjk fel.
using System; using System.Collections.Generic; using System.Collections.ObjectModel; // ez is kell!! namespace TestApp { class Program { static void Main(string[] args) { List<string> list = new List<string>() { "alma", "krte", "di" }; ReadOnlyCollection<string> roc = new ReadOnlyCollection<string>(list); foreach (string item in roc) { Console.WriteLine(item); } Console.ReadKey(); } } }

GENERIKUS INTERFSZEK, DELEGATEEK S ESEMNYEK


A legtbb hagyomnyos interfsznek ltezik generikus vltozata is. Pldul az IEnumerable s IEnumerator is ilyen:
class MyClass<T> : IEnumerable<T>, IEnumerator<T> { }

Ekkor a megvalsts teljesen ugyangy mkdik, mint a hagyomnyos esetben, csak pp hasznlnunk kell a generikus paramter(eke)t. A generikus adatszerkezetek (tbbek kztt) a generikus ICollection, IList s IDictionary interfszeken alapulnak, gy ezeket megvalstva akr mi magunk is ltrehozhatunk ilyet. Az interfszekhez hasonlan a delegateek s esemnyek is lehetnek generikusak. Ez az esetkben egyltaln nem jr semmilyen extra ktelezettsggel.

KOVARIANCIA S KONTRAVARIANCIA
Nzzk a kvetkez osztlyhierarchit:
class Person { public int Age;

184

} class Student : Person { public string Name { get; set; } }

A polimorfizmus elve miatt minden olyan helyen, ahol egy Person objektum hasznlhat, ott egy Student objektum is megfelel, legalbbis elvileg. Lssuk, a kvetkez kdot:
List<Student> studentList = new List<Student>(); List<Person> personList = studentList;

A fenti kt sor, pontosabban a msodik nem fordul le, mivel a .NET nem tekinti egyenlnek a generikus paramtereket, mg akkor sem, ha azok kompatibilisak lennnek. Azt mondjuk, hogy a generikus paramterek nem kovarinsak (covariance). A dolog fordtottja is igaz, vagyis nincs kompatibilits az ltalnosabb tpusrl a szkebbre sem, nem kontravarinsak (contravariance). Mirt van ez gy? Kpzeljk el azt a helyzetet, amikor a fenti osztlyokat kiegsztjk mg egy Teacher osztllyal, amely szintn a Person osztlybl szrmazik! Ha a generikus paramterek kovarinsan viselkednnek, akkor lehetsges lenne Student s Teacher objektumokat is egy listba tenni, ez pedig azzal a problmval jr, hogy lehetsges lenne egy elem olyan tulajdonsgt mdostani, amellyel nem rendelkezik, ez pedig nyilvn hibt okoz (persze tpusellenrzssel ez is thidalhat, de ezzel az egsz generikus adatszerkezet rtelmt veszten). A .NET 4.0 bevezeti a kovarins s kontravarins tpusparamtereket, gy oldva meg a fent vzolt problmt, hogy a krdses tpusok csak olvashatak, illetve csak rhatak lesznek. A kvetkez pldban egy generikus delegate segtsgvel nzzk meg az j lehetsgeket (j listaszerkezetet rni bonyolultabb lenne) (a plda megrtshez szksg van a lambda kifejezsek ismeretre):
class Program { delegate void Method<T>(); static void Main(string[] args) { Method<Student> m1 = () => new Student(); Method<Person> m2 = m1; Console.ReadKey(); } }

A fenti kd nem fordul le, mdostsuk a delegate deklarcijt:


delegate void Method<out T>();

Most viszont minden mkdik, hiszen biztostottuk, hogy minden tpust megfelelen kezeljnk. A .NET beptett generikus tpusai mr fel vannak ksztve a fenti helyzetre, szval rhatjuk a kvetkezt:
IEnumerable<Student> sie = new List<Student>() { new Student() { Name = "Szandra", Age = 22 }, new Student() { Name = "Istvn", Age = 26 }, }; IEnumerable<Person> pie = sie; foreach (Person person in pie) {

185

Console.WriteLine(person.Age); }

Most lssuk a kontravariancit! Itt mr lnyegesen termszetesebb dologrl van sz, hiszen hasonlt lthattunk, amikor rkldssel foglalkoztunk.
class Program { delegate void Method<in T>(T t); static void Main(string[] args) { Method<Person> m1 = (person) => Console.WriteLine(person.Age); Method<Student> m2 = m1; Student s = new Student() { Age = 22, Name = "Szandra" }; m1(s); // 22 m2(s); // 22 Console.ReadKey(); } }

186

LAMBDA KIFEJEZSEK
A C# 3.0 bevezeti a lambda kifejezseket. Egy lambda kifejezs gyakorlatilag megfelel egy nvtelen metdus civilizltabb, elegnsabb vltozatnak (ugyanakkor els rnzsre taln ijesztbb, de ha megszokta az ember, sokkal olvashatbb kdot eredmnyez). Minden lambda kifejezs tartalmazza az n. lambda opertort (=>), ennek jelentse nagyjbl annyi, hogy legyen. Az opertor bal oldaln a bemen vltozk, jobb oldaln pedig a bemenetre alkalmazott kifejezs ll. Mivel nvtelen metdus, ezrt egy lambda kifejezs llhat egy delegate rtkadsban is, elsknt ezt nzzk meg:
using System; class Program { public delegate int IntFunc(int x); static void Main(string[] args) { IntFunc func = (x) => (x * x); Console.WriteLine(func(10)); // 100 Console.ReadKey(); } }

Egy olyan metdusra van teht szksg, amely egy int tpus bemen paramtert vr, s ugyanilyen tpust ad vissza. A lambda kifejezs bal oldaln a bemen paramter (x), jobb oldaln pedig a visszadott rtkrl gondoskod kifejezs (x * x) ll. A bemen paramternl nem kell (de lehet) explicit mdon jeleznnk a tpust, azt a fordt magtl kitallja (a legtbb esetre ez igaz, de nha szksg lesz r, hogy jelljk a tpust). Termszetesen nemcsak egy bemen paramtert hasznlhatunk, a kvetkez pldban sszeszorozzuk a lambda kifejezs kt paramtert:

using System; class Program { public delegate int IntFunc2(int x, int y); static void Main(string[] args) { IntFunc2 func = (x, y) => (x * y); Console.WriteLine(func(10, 2)); // 20 Console.ReadKey(); } }

GENERIKUS KIFEJEZSEK
Generikus kifejezseknek (tulajdonkppen ezek generikus delegateek) is megadhatunk lambda kifejezseket, amelyek nem ignylik egy elzleg definilt delegate jelenltt, ezzel nll lambda kifejezseket hozva ltre (ugyanakkor a generikus kifejezsek kaphatnak nvtelen metdusokat is rtkl). Ktfle generikus kifejezs

187

ltezik: a Func, amely adhat visszatrsi rtket s az Action, amely nem (void) (lsd: fggvny s eljrs). Elsknt a Func-ot vizsgljuk meg:
using System; class Program { static void Main(string[] args) { Func<int, int> func = (x) => (x * x); Console.WriteLine(func(10)); // 100 Console.ReadKey(); } }

A generikus paramterek kztt (balrl jobbra) utols helyen mindig a visszatrsi rtk ll, eltte pedig a bemen paramterek (maximum ngy) kapnak helyet.
using System; class Program { static void Main(string[] args) { Func<int, int, bool> func = (x, y) => (x > y); Console.WriteLine(func(10, 5)); // True Console.ReadKey(); } }

Most megnztk, hogy az els paramter nagyobb-e a msodiknl. rtelemszeren a lambda opertor jobb oldaln lv kifejezsnek megfelel tpust (bool) kell eredmnyeznie, ezt a fordt ellenrzi. A Func minden esetben rendelkezik legalbb egy paramterrel, mgpedig a visszatrsi rtk tpusval, ez biztostja, hogy mindig legyen visszatrsi rtk.
using System; class Program { static void Main(string[] args) { Func<bool> func = () => true; Console.WriteLine(func()); // True Console.ReadKey(); } }

A Func prja az Action, amely szintn maximum ngy bemen paramtert kaphat, de nem lehet visszatrsi rtke:

188

using System; class Program { static void Main(string[] args) { Action<int> act = (x) => Console.WriteLine(x); act(10); Console.ReadKey(); } }

KIFEJEZSFK
Generikus kifejezsek segtsgvel felpthetnk kifejezsfkat, amelyek olyan formban troljk a kifejezsben szerepl adatokat s mveleteket, hogy futsi idben a CLR ki tudja azt rtkelni. Egy kifejezsfa egy generikus kifejezst kap generikus paramterknt:
using System; using System.Linq.Expressions; // ez a nvtr kell class Program { static void Main(string[] args) { Expression<Func<int, int, bool>> expression = (x, y) => (x > y); Console.WriteLine(expression.Compile().Invoke(10, 2)); // True Console.ReadKey(); } }

A programban elszr IL kdra kell fordtani (Compile), csak azutn hvhatjuk meg.

LAMBDA KIFEJEZSEK VLTOZINAK HATKRE


Egy lambda kifejezsben hivatkozhatunk annak a metdusnak a paramtereire s loklis vltozira, amelyben definiltuk. A kls vltozk akkor rtkeldnek ki, amikor a delegate tnylegesen meghvdik, nem pedig a deklarlskor, vagyis az adott vltoz legutols rtkadsa szmt majd. A felhasznlt vltozkat inicializlni kell, mieltt hasznlnnk egy lambda kifejezsben. A lambda kifejezs fenntart magnak egy msolatot a loklis vltozbl/paramterbl, mg akkor is, ha az idkzben kifut a sajt hatkrbl:
using System; class Test { public Action<int> act; public void Method() { int local = 11; act = (x) => Console.WriteLine(x * local); local = 100; }

189

} class Program { static void Main(string[] args) { Test t = new Test(); t.Method(); t.act(100); Console.ReadKey(); } }

Ez a program 10000-et fog kirni, vagyis valban a loklis vltoz legutols rtkt hasznlta a lambda kifejezs. A loklis vltozk s paramterek mdosthatak egy lambda kifejezsben. A lambda kifejezsben ltrehozott vltozk ugyangy viselkednek, mint a hagyomnyos loklis vltozk, a delegate minden hvsakor j pldny jn ltre bellk.

NVTELEN METDUSOK KIVLTSA LAMBDA KIFEJEZSEKKEL


Lambda kifejezst hasznlhatunk minden olyan helyen, ahol nvtelen metdus llhat. Nzzk meg pl., hogy hogyan hasznlhatjuk gy a List<T> tpust:
using System; using System.Collections.Generic; class Program { static void Main(string[] args) { List<int> list = new List<int>(); for (int i = 1; i < 10; ++i) { list.Add(i); } int result = list.Find((item) => (item % 2 == 0)); Console.WriteLine("Az els pros szm: {0}", result); List<int> oddList = list.FindAll((item) => (item % 2 != 0)); Console.WriteLine("Az sszes pratlan szm:"); oddList.ForEach((item) => Console.WriteLine(item)); Console.ReadKey(); } }

Esemnykezelt is rhatunk gy:

190

class Test { public event EventHandler TestEvent; public void OnTestEvent() { if (TestEvent != null) { TestEvent(this, null); } } }

Az EventHandler ltalnos delegate-et hasznltuk az esemny deklarcijnl. Az esemny elindtsnl nincs szksgnk most EventArgs objektumra, ezrt itt nyugodtan hasznlhatunk null rtket. Most nzzk a programot:
static void Main(string[] args) { Test t = new Test(); t.TestEvent += (sender, e) => { Console.WriteLine("Esemnykezel!"); }; t.OnTestEvent(); Console.ReadKey(); }

Lambda kifejezs helyett n. lambda lltst (ezt blokkal jelljk ki) rtunk, gy akr tbbsoros utastsokat is adhatunk.

191

UNSAFE KD
A .NET platform legnagyobb eltrse a natv nyelvektl a memria kezelsben rejlik. A menedzselt kd nem enged kzvetlen hozzfrst a memrihoz, vagyis annyi a dolgunk, hogy megmondjuk, hogy szeretnnk egy ilyen s ilyen tpus objektumot, a rendszer elkszti neknk, s kapunk hozz egy referencit, amelyen keresztl elrjk. Nem fogjuk tudni, hogy a memriban hol van s nem is tudjuk thelyezni. pp ezrt a menedzselt kd biztonsgosabb, mint a natv, mivel a fentiek miatt egsz sor hibalehetsg egsz egyszeren eltnik. Ennek azonban ra van, mghozz a sebessg, de ezt behozzuk a memria gyorsabb elrsvel/kezelsvel, ezrt a kt mdszer kztt lnyegben nincs teljestmnybeli klnbsg. Vannak azonban helyzetek, amikor igenis fontos, hogy kzvetlenl elrjk a memrit: A lehet legjobb teljestmnyt szeretnnk elrni egy rendkvl szmtsignyes feladathoz (pl.: szmtgpes grafika). .NETen kvli osztlyknyvtrakat akarunk hasznlni (pl.: Windows API hvsok).

A C# a memria direkt elrst mutatkon (pointer) keresztl teszi lehetv. Ahhoz, hogy mutatkat hasznlhassunk, az adott metdust, osztlyt, adattagot vagy blokkot az unsafe kulcsszval kell jellnnk (ez az n. unsafe context). Egy osztlyon bell egy adattagot vagy metdust jellhetnk unsafe mdostval, de ez nem jelenti azt, hogy maga az osztly is unsafe lenne. Nzzk a kvetkez pldt:
using System; class Test { public unsafe int* x; } class Program { static void Main(string[] args) { unsafe { Test t = new Test(); int y = 10; t.x = &y; Console.WriteLine(*t.x); } Console.ReadKey(); } }

Elszr deklarltunk egy unsafe adattagot, mghozz egy int tpusra mutat pointert. A pointer tpus az rtks referenciatpusok mellett a harmadik tpuskategria. A pointerek nem szrmaznak a System.Objectbl s konverzis kapcsolat sincs kzttk (br az egyszer numerikus tpusokrl ltezik explicit konverzi). rtelemszeren boxing/unboxing sem alkalmazhat rajtuk. Egy pointer mindig egy memriacmet hordoz, amely memriaterleten egy teljesen normlis objektum van. Ebbl kvetkezen a fenti deklarciban nem adhatok azonnal rtket az unsafe pointernek, mivel numerikus rtkads esetn nem fordul le a program (hiszen nem egy int objektumrl van sz), ms objektum memriacmt viszont nem tudom. Sebaj, erre val az n. cmeopertor (&), amellyel tadhatom egy hagyomnyos objektum cmt. A programban ki akarjuk rni a memriaterleten lv objektum rtkt, ezt a dereference opertorral (*) tehetjk meg. Ez visszaadja a mutatott rtket, mg ha magt a vltozt hasznljuk, az csak a memriacmet. A memriacm a memria egy adott byte-jra mutat (vagyis a pointer nvelse/cskkentse a pointer tpusnak megfelel mennyisg byte-tal rakja odbb a mutatt, teht egy int pointer esetn ez ngy byte

192

lesz), amely az adott objektum kezdcme. A pointer gy tudja visszaadni az rtkt, hogy tudja, mekkora mret az objektum (pl. egy int pointer egy 32 bites 4 byte mret terletet vesz majd el). A programot parancssorbl a /unsafe kapcsolval fordthatjuk le, Visual Studio esetn jobb klikk a projecten, Properties ablak, s a Build fln jelljk meg az Allow unsafe code ngyzetet. csc /unsafe main.cs A megfelel explicit konverzival a memriacmet is lekrhetjk:
using System; class Program { static void Main(string[] args) { unsafe { int x = 10; int* y = &x; Console.WriteLine((int)y); } Console.ReadKey(); } }

Pointer csakis a beptett numerikus tpusokra (belertve a char is), logikai tpusokra, felsorolt tpusokra, ms pointerekre, illetve minden olyan ltalunk ksztett struktrra hivatkozhat, amely nem tartalmaz az eddig felsoroltakon kvl mst. Ezeket a tpusokat sszefoglal nven unmanaged tpusoknak nevezzk. Explicit konverzi ltezik szkebb pointer tpusrl tgabb tpusra, fordtott eset fordtsi hibt okoz. Ugyanakkor az elbbi eset elvileg definilatlan eredmnnyel jrhat, a gyakorlatban azrt mkdkpes.
int x = 10; byte y = 20; int* p1 = &x; // ez j p1 = (int*)&y; // ez nem biztos, hogy j, de mkdik p1 = (byte*)&x; // ez mr le sem fordul

Implicit konverzi van viszont brmely pointer tpusrl a void* univerzlis pointer tpusra. A void*-on nem hasznlhat a dereference opertor:
int x = 10; void* p1 = &x; Console.WriteLine(*((int*)p1)); // 10

Egy struktrra is hivatkozhatunk pointerrel, ekkor a tagjait ktflekppen rhetjk el: vagy a mutatn keresztl, vagy a nyl (->) opertorral, amely tulajdonkppen az elbbi rvidtse:
using System; struct Test { public int x; }

193

class Program { static void Main(string[] args) { unsafe { Test t = new Test(); t.x = 10; Test* p = &t; Console.WriteLine((*p).x); // 10 Console.WriteLine(p->x); // 10 } Console.ReadKey(); } }

FIX OBJEKTUMOK
Normlis esetben a szemtgyjt a memria tredezettsgmentestse rdekben mozgatja az objektumokat a memriban. Egy pointer azonban mindig egy fix helyre mutat, hiszen a mutatott objektum cmvel dolgozunk, ami pedig nem fog frisslni. Ez nhny esetben gondot okozhat (pl. amikor hosszabb ideig van a memriban a mutatott adat, pl. valamilyen erforrs). Ha szeretnnk, hogy az objektumok a helykn maradjanak, akkor a fixed kulcsszt kell hasznlnunk. Mieltt jobban megnznnk a fixed-et, vegyk szemgyre a kvetkez forrskdot:
int[] array = new int[] { 1, 3, 4, 6, 7 }; int* p = &array;

Ez a kt sor fordtsi hibval jr. Br a tmbk referenciatpusok, mgis kivtelt kpeznek, mivel hasznlhatunk rajtuk pointert, igaz nem az eddig ltott mdon (valamint a tmb elemeinek unmanaged tpusnak kell lennik). Referenciatpuson bell deklarlt rtktpusok esetn (ilyenek a tmbelemek is) fixlni kell az objektum helyzett, hogy a GC ne mozdthassa el. A kvetkez kd mr mkdni fog:
using System; class Program { static void Main(string[] args) { unsafe { int[] array = new int[] { 1, 3, 4, 6, 7 }; fixed (int* p = array) for (int i = 0; i < 5; ++i) { Console.WriteLine(*(p + i)); } } Console.ReadKey(); } }

194

NATV DLL KEZELS


Elfordul, hogy olyan metdust akarunk hvni, amelyet egy natv (pl. C++ nyelven rt) kls knyvtr tartalmaz. A kvetkez pldban egy Windows API metdust hvunk meg, amely megjelent egy MessageBoxot. Ahhoz, hogy ezt meg tudjuk tenni, elssorban ismernnk kell az eredeti metdus szignatrjt, ehhez szksgnk lesz pl. a Win32 Programmers Reference cm dokumentcira, amelyet letlthetnk: http://www.winasm.net/win32hlp.html Keressk ki a MessageBox metdust, s a kvetkezt fogjuk ltni:
int MessageBox( HWND hWnd, // handle of owner window LPCTSTR lpText, // address of text in message box LPCTSTR lpCaption, // address of title of message box UINT uType // style of message box );

Ezenkvl mg tudnunk kell azt is, hogy melyik DLL trolja az adott fggvnyt, ez pedig jelen esetben a user32.dll lesz. Mindent tudunk, ksztsk el a programot! A fggvny importlshoz a DllImport attribtumot fogjuk hasznlni:
using System; using System.Runtime.InteropServices; // ez a nvtr szksges public class API { [DllImport("user32.dll")] public static extern int MessageBox(int hWnd, string text, string caption, uint type); } class Program { static void Main(string[] args) { API.MessageBox(0, "Hello!", "Nincs", 0); } }

Az attribtumnak meg kell adnunk a knyvtr nevt (user32.dll), valamint a metdus-deklarcit az extern kulcsszval kell jellnnk, hogy tudassuk a fordtval, hogy a fggvnyt egy kld forrsbl kell elkertenie. A forrskdot a szoksos mdon fordtjuk, s ha elindtjuk, akkor a kvetkezt kell ltnunk:

195

TBBSZL ALKALMAZSOK
Egy Windows alap opercis rendszerben minden futtathat llomny indtsakor egy n. process jn ltre, amely teljes mrtkben elklnl az sszes tbbitl. Egy processt az azonostja (PID Process ID) alapjn klnbztetnk meg a tbbitl. Minden egyes process rendelkezik egy n. f szllal (primary- vagy main thread), amely a program belpsi pontja (ld. Main). Azokat az alkalmazsokat, amelyek csak f szllal rendelkeznek, thread-safe alkalmazsoknak nevezzk, mivel csak egy szl fr hozz az sszes erforrshoz. Ugyanakkor ezek az alkalmazsok hajlamosak elaludni, ha egy komplexebb feladatot hajtanak vgre, hiszen a f szl ekkor nem tud figyelni a felhasznltl rkez akcikra (pl. billentylenyoms). Az ilyen helyzetek elkerlsre a Windows (s a .NET) lehetv teszi msodlagos szlak (n. worker thread) hozzadst a f szlhoz. Az egyes szlak (a processekhez hasonlan) nllan mkdnek a folyamaton bell, s versenyeznek az erforrsok hasznlatrt (concurrent access). J plda lehet a szlkezels bemutatsra egy szvegszerkeszt hasznlata: amg kinyomtatunk egy dokumentumot (egy mellkszllal), az alkalmazs f szla tovbbra is figyeli a felhasznltl rkez utastsokat. A tbbszl programozs legnagyobb kihvsa a szlak s feladataik megszervezse, az erforrsok elosztsa. Fontos megrtennk, hogy valjban a tbbszlsg a szmtgp ltal nyjtott illzi, hiszen a processzor egyszerre csak egy feladatot tud vgrehajtani (br ma mr szinte csak tbbmagos rendszerek lteznek, de gondoljunk bele, hogy amikor hozz sem nylunk a szmtgphez is tven-szz szl fut), gy el kell osztania az egyes feladatok kzt a processzoridt (ezt a szlak prioritsa alapjn teszi) ez az n. idosztsos (time slicing) rendszer. Amikor egy szlnak kiosztott id lejr, akkor a futsi adatait eltrolja az n. Thread Local Storageben (ebbl minden szlnak van egy), s tadja a helyet egy msik szlnak, amely ha szksges betlti a sajt adatait a TLS-bl, s elvgzi a feladatt. A .NET szmos osztlyt s metdust bocst rendelkezsnkre, amelyekkel az egyes processeket felgyelhetjk, ezek a System.Diagnostics nvtrben vannak. rjunk egy programot, amely kirja az sszes fut folyamatot s azonostjukat:
using System; using System.Diagnostics; // erre a nvtrre szksg van class Program { static void Main(string[] args) { Process[] processList = Process.GetProcesses("."); foreach (Process process in processList) { Console.WriteLine("PID: {0}, Process - nv: {1}", process.Id, process.ProcessName); } Console.ReadKey(); } }

Amennyiben tudjuk a process azonostjt, akkor hasznlhatjuk a Process.GetProcessById(azonost) metdust is. A kvetkez programunk az sszes fut process minden szlt s azoknak adatait fogja kilistzni:

196

using System; using System.Diagnostics; // erre a nvtrre szksg van class Program { static void Main(string[] args) { Process[] processList = Process.GetProcesses("."); foreach (Process process in processList) { Console.WriteLine("A folyamat ({0}) szlai", process.ProcessName); ProcessThreadCollection ptc = process.Threads; foreach (ProcessThread thread in ptc) { Console.WriteLine("Id: {0}, llapot: {1}", thread.Id, thread.ThreadState); } } Console.ReadKey(); } }

Lehetsges, hogy a program futsakor kivtelt kapunk, hiszen a szlak listjba olyan szl is bekerlhet, amely a kirskor mr befejezte futst (ez szinte mindig az Idle process esetben fordul el, a meggondols hzi feladat, akrcsak a program kivtelbiztoss ttele). A fenti osztlyok segtsgvel remekl bele lehet ltni a rendszer lelkbe, az MSDNen megtalljuk a fenti osztlyok tovbbi metdusait, tulajdonsgait, amelyek az utazshoz szksgesek. A kvetkez programunk a process-ek irnytst szemllteti. Indtsuk el az Internet Explorert, vrjunk t msodpercet, s lltsuk le:
using System; using System.Diagnostics; using System.Threading; // erre a nvtrre szksg van class Program { static void Main(string[] args) { Process explorer = Process.Start("iexplore.exe"); Thread.Sleep(5000); explorer.Kill(); Console.ReadKey(); } }

Egyttal felhasznltuk az els igazi szlkezelshez tartoz metdusunkat is, a Thread osztly statikus Sleep metdust (a Thread osztly a System.Threading nvtrben tallhat).

197

APPLICATION DOMAIN -EK


Egy .NET program nem direkt mdon processknt fut, hanem be van gyazva egy n. application domain-be a processen bell (egy process tbb AD-t is tartalmazhat egymstl teljesen elszeparlva). Ezzel a megoldssal egyrszt elsegtik a platformfggetlensget, hiszen gy csak az Application Domaint kell portolni egy msik platformra, a benne fut folyamatoknak nem kell ismernik az opercis rendszert, msrszt biztostja a programok stabilitst, ugyanis ha egy alkalmazs sszeomlik egy AD-ben, attl a tbbi mg tkletesen mkdik majd. Amikor elindtunk egy .NET programot, elsknt az alaprtelmezett AD (default application domain) jn ltre, ezutn ha szksges a CLR tovbbi AD-ket hoz ltre. A kvetkez program kirja az aktulis AD nevt:
using System; class Program { static void Main(string[] args) { AppDomain currAD = AppDomain.CurrentDomain; Console.WriteLine(currAD.FriendlyName); Console.ReadKey(); } }

Az alkalmazs neve fog megjelenni, hiszen az alaprtelmezett AD, s egyelre nincs is tbb. Hozzunk ltre egy msodik AppDomain objektumot:
using System; class Program { static void Main(string[] args) { AppDomain secondAD = AppDomain.CreateDomain("second"); Console.WriteLine(secondAD.FriendlyName); AppDomain.Unload(secondAD); // megszntetjk Console.ReadKey(); } }

SZLAK
Elrkeztnk a fejezet eredeti trgyhoz, mr eleget tudunk ahhoz, hogy megrtsk a tbbszl alkalmazsok elvt. Els programunkban lekrjk az adott programrsz szlnak az azonostjt:
using System; using System.Threading; class Program { static void Main(string[] args) { Console.WriteLine("Szl-Id: {0}", Thread.CurrentThread.ManagedThreadId);

198

Console.ReadKey(); } }

A program utastsainak vgrehajtsa szerint megklnbztetnk szinkron s aszinkron mkdst. A fenti program szinkron mdon mkdik, az utastsait egyms utn hatja vgre, ha esetleg egy hosszas algoritmusba tkzik, akkor csak akkor lp a kvetkez utastsra, ha azt befejezte, Az aszinkron vgrehajts ennek pp az ellentte, az egyes feladatokat el tudjuk kldeni egy msik szlba, a f szl pedig fut tovbb, amg a mellkszl(ak) vissza nem trnek.

ASZINKRON DELEGATE-EK
A kvetkezkben delegateek segtsgvel fogunk aszinkron programot rni. Minden egyes delegate rendelkezik azzal a kpessggel, hogy aszinkron mdon hvjuk meg, ezt a BeginInvoke s EndInvoke metdusokkal fogjuk megtenni. Vegyk a kvetkez delegateet:
delegate int MyDelegate(int x);

Ez valjban gy nz ki:
public sealed class MyDelegate : System.MulticastDelegate { //...metdusok... public IAsyncResult BeginInvoke(int x, AsyncCallback cb, object state); public int EndInvoke(IAsyncResult result); }

Egyelre ne foglalkozzunk az ismeretlen dolgokkal, nzzk meg azt, amit ismernk! A BeginInvoke-val fogjuk meghvni a delegate-et, ennek els paramtere megegyezik a delegate paramtervel (vagy paramtereivel). Az EndInvoke fogja majd az eredmnyt szolgltatni, ennek visszatrsi rtke megegyezik a delegateval. Az IAsyncResult objektum, amit a BeginInvoke visszatrt, segt elrni az eredmnyt, s az EndInvoke is ezt kapja majd paramterl. A BeginInvoke msik kt paramtervel most nem foglalkozunk, ksztsnk egy egyszer metdust a delegatehez, s hvjuk meg aszinkron:
using System; using System.Threading; class Program { public delegate int MyDelegate(int x); static int Square(int x) { Console.WriteLine("Szl-ID: {0}", Thread.CurrentThread.ManagedThreadId); return (x * x); } static void Main(string[] args) { MyDelegate d = Square; Console.WriteLine("Szl-ID: {0}",

199

Thread.CurrentThread.ManagedThreadId); IAsyncResult iar = d.BeginInvoke(12, null, null); Console.WriteLine("BlaBla..."); int result = d.EndInvoke(iar); Console.WriteLine(result); Console.ReadKey(); } }

A kimenet a kvetkez lesz: Szl-ID: 1 BlaBla... Szl-ID: 3 144 Lthat, hogy egy j szl jtt ltre. Amit fontos megrtennk, hogy a BeginInvoke azonnal megkezdi a feladata vgrehajtst, de az eredmnyhez csak az EndInvoke hvsakor jutunk hozz, teht kls szemllknt gy ltjuk, hogy csak akkor fut le a metdus. A httrben fut szl zenete is ltszlag csak az eredmny kirtkelsnl jelenik meg, az igazsg azonban az, hogy a Main zenete elbb rt a processzorhoz, ezt hamarosan ltni fogjuk. Tbbszl program rsnl ssze kell tudnunk hangolni a szlak munkavgzst, pl. ha az egyik szlban kiszmolt eredmnyre van szksge egy msik, ksbb indult szlnak. Ezt szinkronizlsnak nevezzk. Szinkronizljuk az eredeti programunkat, vagyis vrjuk meg, amg a delegate befejezi a futst (termszetesen a szinkronizls ennl jval bonyolultabb, errl a kvetkez fejezetekben olvashatunk):
using System; using System.Threading; class Program { public delegate int MyDelegate(int x); static int Square(int x) { Console.WriteLine("Szl-ID: {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(100); return (x * x); } static void Main(string[] args) { MyDelegate d = Square; Console.WriteLine("Szl-ID: {0}", Thread.CurrentThread.ManagedThreadId); IAsyncResult iar = d.BeginInvoke(12, null, null); while (!iar.IsCompleted) { Console.WriteLine("BlaBla..."); }

200

int result = d.EndInvoke(iar); Console.WriteLine(result); Console.ReadKey(); } }

Ezt a feladatot az IAsyncResult interfsz IsCompleted tulajdonsgval oldottuk meg. A kimenet: Szl-ID: 1 BlaBla... BlaBla... BlaBla... Szl-ID: 3 BlaBla... BlaBla... BlaBla... BlaBla... 144 Itt mr tisztn ltszik, hogy az aszinkron metdus futsa azonnal elkezddtt, igaz, a Main itt is megelzte. A Square metdusban azrt hasznltuk a Sleep metdust, hogy lssunk is valamit, ellenkez esetben tl gyorsan lefut ez az egyszer program. Ersebb szmtgpeken nem rt mdostani az alvs idejt akr 1000 msre is. Valljuk be, elg macers mindig meghvogatni az EndInvokeot, gy felmerlhet a krds, hogy nem lehetne-e valahogy automatizlni az egszet. Nos, pp ezt a gondot oldja meg a BeginInvoke harmadik AsyncCallback tpus paramtere. Ez egy delegate, amely egy olyan metdusra mutathat, amelynek visszatrsi rtke void, valamint egy darab IAsyncResult tpus paramterrel rendelkezik. Ez a metdus azonnal le fog futni, ha a mellkszl elvgezte a feladatt:
using System; using System.Threading; using System.Runtime.Remoting.Messaging; // erre a nvtrre szksg van class Program { public delegate int MyDelegate(int x); static int Square(int x) { Console.WriteLine("Szl-ID: {0}", Thread.CurrentThread.ManagedThreadId); return (x * x); } static void AsyncMethodComplete(IAsyncResult iar) { Console.WriteLine("Aszinkron szl ksz..."); AsyncResult result = (AsyncResult)iar; MyDelegate d = (MyDelegate)result.AsyncDelegate; Console.WriteLine("Eredmny: {0}", d.EndInvoke(iar)); }

201

static void Main(string[] args) { MyDelegate d = Square; Console.WriteLine("Szl-ID {0}", Thread.CurrentThread.ManagedThreadId); IAsyncResult iar = d.BeginInvoke(12, new AsyncCallback(AsyncMethodComplete), null); Console.WriteLine("BlaBla..."); Console.ReadKey(); } }

Ha Console.ReadKey nlkl futtatnnk a programot, akkor azt ltnnk, hogy nem rja ki az eredmnyt. Ez azrt van, mert a BlaBla utn a program futsa megll, mivel elrte a Main vgt, s nincs tbb utasts, valamint ez gyorsabban trtnik, minthogy az aszinkron metdus ksz lenne. pp ezrt rdemes egy ReadKey vagy egy Sleep metdust hasznlni a program vgn. A kimenet a kvetkez lesz: Szl-ID 1 BlaBla... Szl-ID: 3 Aszinkron szl ksz... Eredmny: 144 Egyetlen dolog van htra, mgpedig a BeginInvoke utols paramternek megismerse. Ez egy object tpus vltoz, azaz brmilyen objektumot tadhatunk. Ezt a paramtert hasznljuk, ha valamilyen plusz informcit akarunk tovbbtani. A BeginInvoke most gy nz ki:
IAsyncResult iar = d.BeginInvoke(12, new AsyncCallback(AsyncMethodComplete), "zenet a jvbl :)");

Az zenetet az IAsyncResult AsyncState tulajdonsgval krdezhetjk le:


static void AsyncMethodComplete(IAsyncResult iar) { Console.WriteLine("Aszinkron szl ksz..."); AsyncResult result = (AsyncResult)iar; MyDelegate d = (MyDelegate)result.AsyncDelegate; Console.WriteLine("zenet: {0}", iar.AsyncState); Console.WriteLine("Eredmny: {0}", d.EndInvoke(iar)); }

SZLAK LTREHOZSA
Ahhoz, hogy msodlagos szlakat hozzunk ltre, nem felttlenl kell delegateeket hasznlnunk, mi magunk is elkszthetjk ket. Vegyk a kvetkez programot:
using System; using System.Threading; class Test

202

{ public void ThreadInfo() { Console.WriteLine("Szl-nv: {0}", Thread.CurrentThread.Name); } } class Program { static void Main(string[] args) { Thread current = Thread.CurrentThread; current.Name = "Current-Thread"; Test t = new Test(); t.ThreadInfo(); Console.ReadKey(); } }

Elsknt lekrtk s elneveztk az elsdleges szlat, hogy ksbb azonostani tudjuk, mivel alaprtelmezetten nincs neve. A kvetkez programban a Test objektum metdust egy httrben fut szlbl fogjuk meghvni:
using System; using System.Threading; class Test { public void ThreadInfo() { Console.WriteLine("Szl-nv: {0}", Thread.CurrentThread.Name); } } class Program { static void Main(string[] args) { Test t = new Test(); Thread backgroundThread = new Thread( new ThreadStart(t.ThreadInfo)); backgroundThread.Name = "Background-Thread"; backgroundThread.Start(); } }

A Thread konstruktorban szerepl ThreadStart delegate-nek kell megadnunk azt a metdust, amelyet a msodlagos szl majd futtat. A programot rdemes Console.ReadKey nlkl futtatni, mivel azon fennakadna a futs. gy is csak egy villanst lehet ltni belle, parancssorbl indtva ltvnyosabb lehet. Ez eddig szp s j, de mi van akkor, ha a meghvott metdusnak paramterei is vannak? Ilyenkor a ThreadStart parametrizlt vltozatt hasznlhatjuk, ami igen eredeti mdon a ParameterizedThreadStart nvre hallgat. A ThreadStarthoz hasonlan ez is egy delegate, szintn void visszatrsi tpusa lesz, a paramtere pedig object tpus lehet:

203

using System; using System.Threading; class Test { public void ThreadInfo(object parameter) { Console.WriteLine("Szl-nv: {0}", Thread.CurrentThread.Name); if (parameter is string) { Console.WriteLine("Paramter: {0}", parameter); } } } class Program { static void Main(string[] args) { Test t = new Test(); Thread backgroundThread = new Thread(new ParameterizedThreadStart(t.ThreadInfo)); backgroundThread.Name = "Background-Thread"; backgroundThread.Start("Hello"); Console.ReadKey(); } }

Nylvn a metdusban nem rt ellenrizni a paramter tpust, mieltt brmit csinlunk vele.

FOREGROUND S BACKGROUND SZLAK


A .NET kt klnbz szltpust klnbztet meg: amelyek eltrben s amelyek a httrben futnak. A kett kzti klnbsg a kvetkez: a CLR addig nem lltja le az alkalmazst, amg egy eltrbeli szl is dolgozik, ugyanez a httrbeli szlakra nem vonatkozik (az aszinkron delegate esetben is ezrt kellett a program vgre a lassts). Logikus (lenne) a felttelezs, hogy az elsdleges s msodlagos szlak fogalma megegyezik jelen fejezetnk trgyaival. Az igazsg viszont az, hogy ez az llts nem llja meg a helyt, ugyanis alaprtelmezs szerint minden szl (a ltrehozs mdjtl s idejtl fggetlenl) eltrben fut. Termszetesen van arra is lehetsg, hogy a httrbe kldjk ket:
using System; using System.Threading; class Test { public void ThreadInfo() { Thread.Sleep(5000); Console.WriteLine("Szl-nv: {0}", Thread.CurrentThread.Name); } } class Program { static void Main(string[] args)

204

{ Test t = new Test(); Thread backgroundThread = new Thread( new ThreadStart(t.ThreadInfo)); backgroundThread.IsBackground = true; backgroundThread.Name = "Background-Thread"; backgroundThread.Start(); } }

Ez a program semmit nem fog kirni, s pont ezt is vrtuk tle, mivel belltottuk az IsBackground tulajdonsgot, ezrt az ltalunk ksztett szl valdi httrben fut szl lett, vagyis a fszlnak nem kell megvrnia, hogy vgezzen a dolgval.

SZINKRONIZCI
A szlak szinkronizcijnak egy primitvebb formjt mr lttuk a delegateek esetben, most egy kicsit komolyabban kzeltnk a tmhoz. Ngyflekppen szinkronizlhatjuk a szlainkat, ezek kzl az els a blokkols. Ennek mr ismerjk egy mdjt, ez a Thread.Sleep metdus:
using System; using System.Threading; class Program { static void Main(string[] args) { Console.WriteLine("Start..."); Thread.Sleep(2000); Console.WriteLine("Stop..."); Console.ReadKey(); } }

Amikor egy szlat leblokkolunk, az azonnal elereszti a processzort, s addig inaktv marad, amg a blokkols felttelnek a krnyezet eleget nem tesz, vagy a folyamat valamilyen mdon megszakad. A Join metdus addig vrakoztatja a hv szlat, amg az a szl, amin meghvtk, nem vgezte el a feladatt:
using System; using System.Threading; class Program { static void Main(string[] args) { Thread t = new Thread(delegate() { Thread.Sleep(2000); }); t.Start(); t.Join(); Console.WriteLine("Vge"); Console.ReadKey(); } }

205

A Join-nak megadhatunk egy timeout paramtert (ezredmsodpercben), amely id lejrta utn ha a szl nem vgzett feladatval hamis rtkkel tr vissza. A kvetkez plda (ezttal lambda kifejezssel) ezt mutatja meg:
using System; using System.Threading; class Program { static void Main(string[] args) { Thread t = new Thread(() => Thread.Sleep(2000)); t.Start(); if (t.Join(1000) == false) { Console.WriteLine("Az id lejrt..."); t.Abort(); // megszaktjuk a szl futst } Console.ReadKey(); } }

A kvetkez szinkronizcis mdszer a lezrs (locking). Ez azt jelenti, hogy erforrsokhoz, illetve a program bizonyos rszeihez egyszerre csak egy szlnak engednk hozzfrst. Ha egy szl hozz akar frni az adott dologhoz, amelyet egy msik szl mr hasznl, akkor automatikusan blokkoldik, s vrlistra kerl, ahonnan rkezsi sorrendben lehet hozzjutni az erforrshoz (ha az elz szl mr vgzett). Nzzk a kvetkez pldt:
using System; using System.Threading; class Test { static int x = 10; static int y = 20; static public void Divide() { if (Test.x != 0) { Thread.Sleep(2); Console.WriteLine(Test.y / Test.x); Test.x = 0; } } } class Program { static void Main(string[] args) { Thread t1 = new Thread(new ThreadStart(Test.Divide)); Thread t2 = new Thread(new ThreadStart(Test.Divide)); t1.Start(); t2.Start(); } }

206

Tegyk fel, hogy megrkezik egy szl, eljut odig, hogy kirja az eredmnyt, s pp ekkor rkezik egy msik szl is! Megvizsglja a felttelt, rendben tallja s tovbblp. Ebben a pillanatban azonban az elsknt rkezett szl lenullzza a vltozt, s amikor a msodik szl osztani akar, akkor kap egy szp kis kivtelt. A Divide metdus felttelben nem vletlenl van ott a Sleep, ezzel tesznk rla, hogy tnyleg legyen kivtel. Mivel ez egy egyszer program, muszj lelasstani egy kicsit az els szlat (rdemes tbbszr lefuttatni, nem biztos, hogy azonnal kivtelt kapunk). A mveletet lezrhatjuk a kvetkez mdon:
static object locker = new object(); static public void Divide() { lock (locker) { if (Test.x != 0) { Thread.Sleep(2); Console.WriteLine(Test.y / Test.x); Test.x = 0; } } }

A lock kijell egy blokkot, amelyhez egyszerre csak egy szl fr hozz. Ahhoz azonban, hogy ezt megtehessk, ki kell jellnnk egy n. tokent, amelyet lezrhat. A tokennek minden esetben referenciatpusnak kell lennie. A lock-hoz hasonlan mkdik a Mutex is, a legnagyobb klnbsg az a kett kzt, hogy utbbi processz szinten zrol, azaz a szmtgpen fut sszes folyamat ell elzrja a hasznlat lehetsgt. Az erforrs/metdus/stb. hasznlata eltt meg kell hvnunk a WaitOne metdust, a hasznlat utn pedig el kell engednnk az erforrst a ReleaseMutex metdussal (ha ezt nem tesszk meg a kdbl, akkor az alkalmazs futsnak vgn a CLR automatikusan megteszi helyettnk). A kvetkez pldban ltrehozunk tbb szlat, s versenyeztetjk ket egy metdus hasznlatrt. Elsknt ksztsk el az osztlyt, amely trolja az erforrst s a Mutex objektumot:
class Test { private Mutex mutex = new Mutex(); public void ResourceMetod() { mutex.WaitOne(); Console.WriteLine("{0} hasznlja az erforrst...", Thread.CurrentThread.Name); Thread.Sleep(1000); mutex.ReleaseMutex(); Console.WriteLine("{0} elengedi az erforrst...", Thread.CurrentThread.Name); } }

Most pedig jjjn a fprogram:


class Program { static Test t = new Test();

207

static public void ResourceUserMethod() { for (int i = 0; i < 10; ++i) { t.ResourceMetod(); } } static void Main(string[] args) { List<Thread> threadList = new List<Thread>(); for (int i = 0; i < 10; ++i) { threadList.Add( new Thread(new ThreadStart(Program.ResourceUserMethod)) { Name = "Thread" + i.ToString() }); } threadList.ForEach((thread) => thread.Start()); } }

A Semaphore hasonlt a lockra s a Mutexre, azzal a klnbsggel, hogy megadhatunk egy szmot, amely meghatrozza, hogy egy erforrshoz maximum hny szl frhet hozz egy idben. A kvetkez program az elz tirata, egy idben maximum hrom szl frhet hozz a metdushoz:
class Test { private Semaphore semaphore = new Semaphore(3, 3); public void ResourceMetod() { semaphore.WaitOne(); Console.WriteLine("{0} hasznlja az erforrst...", Thread.CurrentThread.Name); Thread.Sleep(1000); semaphore.Release(); Console.WriteLine("{0} elengedi az erforrst...", Thread.CurrentThread.Name); } }

A fprogram pedig ugyanaz lesz.

THREADPOOL
Kpzeljk el a kvetkez szitucit: egy kliens-szerver alkalmazst ksztnk, a szerver a fszlban figyeli a bejv kapcsolatokat, s ha kliens rkezik, akkor kszt neki egy szlat, majd a httrben kiszolglja. Tegyk mg hozz azt is, hogy a kliensek viszonylag rvid ideig tartjk a kapcsolatot a szerverrel, viszont sokan vannak!

208

Ha gy ksztjk el a programot, hogy a bejv kapcsolatoknak mindig j szlat ksztnk, akkor nagyon gyorsan teljestmnyproblmkba fogunk tkzni: Egy objektum ltrehozsa kltsges. Ha mr nem hasznljuk, akkor a memriban marad, amg el nem takartja a GC. Mivel sok bejv kapcsolatunk van, ezrt hamarabb lesz tele a memria szemttel, vagyis gyakrabban fut majd a GC.

A problma gykere az egyes pont, vagyis az, hogy minden egyes kapcsolatnak j szlat ksztnk, majd eldobjuk azt. Sokkal hatkonyabbak lehetnnk, ha megprblnnk valahogy jrahasznostani a szlakat. Ezt a mdszert thread-pooling-nak nevezzk, s szerencsnk van, mivel a .NET beptett megoldssal rendelkezik (ugyanakkor, ha igazn hatkonyak akarunk lenni, rhatunk sajt, az adott kvetelmnyeknek legjobban megfelel ThreadPool osztlyt is). Egy ksbbi fejezetben elksztnk majd egy, a fenti felvzolt helyzethez hasonl programot, most csak egy egyszer pldn keresztl fogjuk megvizsglni ezt a technikt. Nzzk meg a kvetkez programot:
using System; using System.Threading; class Program { static public void Do(object inObj) { Console.WriteLine("A kvetkez adatot hasznljuk: {0}", (int)inObj); Thread.Sleep(500); } static void Main(string[] args) { ThreadPool.SetMaxThreads(5, 0); for (int i = 0; i < 20; ++i) { ThreadPool.QueueUserWorkItem( new WaitCallback(Program.Do), i); } Console.ReadKey(); } }

Ami elssorban feltnhet, az az, hogy a ThreadPool egy statikus osztly,vagyis nem tudjuk pldnyostani, ehelyett a metdusait hasznlhatjuk. A SetMaxThread metdus a maximlisan memriban tartott szlak szmt lltja be, az els paramter a rendes, a msodik az aszinkron szlak szmt jelzi (utbbira most nincs szksg, ezrt kapott nulla rtket). A QueueUserWorkItem metdus lesz a ThreadPool lelke, itt indtjuk tjra az egyes szlakat. Ha egy feladat bekerl a listra, de nincs az adott pillanatban szabad szl, akkor addig vr, amg nem kap egyet. A metdus els paramtere egy delegate, amely olyan metdusra mutathat, amelynek visszatrsi rtke nincs (void), s egyetlen object tpus paramterrel rendelkezik. Ezt a paramtert adjuk meg a msodik paramterben. Fontos tudni, hogy a ThreadPool osztly csakis background szlakat indt, vagyis a program nem fog vrni, amg minden szl vgez, hanem kilp. Ennek megakadlyozsra tettnk a program vgre egy Console.ReadKey parancsot, gy ltni is fogjuk, hogy mi trtnik ppen (erre pl. a fent emltett kliens-szerver pldban nincs szksg, mivel a szerver a fszlban vgtelen ciklusban vrja a bejv kapcsolatokat).

209

PRHUZAMOS PROGRAMOZS TASK PARALLEL LIBRARY


Ma mr a legtbb szmtgp tbb processzormaggal rendelkezik, amelyek fizikailag is lehetv teszik tbb szl prhuzamos futst. Ahhoz, hogy az ebben rejl lehetsgeket kihasznljuk, a mltban alacsony szinten, kzvetlenl a szlakkal kellett dolgoznunk. A .NET Framework 4 (s a C# 4.0) olyan j osztlyokat s keretrendszert ad a keznkbe, amelyekkel ezeket a feladatokat leegyszersthetjk.

TBBSZLSG VS. PRHUZAMOSSG


Amikor tbbszl programokat ksztnk, alapveten nem trdnk a hardver lehetsgeivel, ltrehozunk szlakat, amelyek versengnek a processzoridrt. Ilyenkor rtelemszeren nem fogunk klnsebb teljestmnynvekedst kapni, hiszen minden mvelet ugyanannyi ideig tart, nem tudjuk ket kzvetlenl sztosztani az esetleges tbb processzor kztt. Ezt a mdszert pl. olyankor hasznljuk, amikor nem akarjuk, hogy a httrben fut szlak megzavarjk a fszl kezelhetsgt (pl. ha egy bngszben tbb oldalt is megnyitunk, nem akarjuk megvrni, amg mindegyik letltdik), vagy egyszerre tbb krst kell kezelnnk (pl. egy kliens-szerver kapcsolat). A prhuzamos programozs ugyanezt nyjtja, de kpes a processzorok szmnak fggvnyben sztosztani a munkt a CPUk kztt, ezzel pedig teljestmnynvekedst r el. Ennek a mdszernek a nagy htrnya, hogy olyan algoritmust kell talljunk, amely minden helyzetben a lehet leghatkonyabban tudja elosztani a munkt, ankl, hogy brmely processzor resjratban llna.

TELJESTMNY
Nagyon knnyen azt gondolhatjuk, hogy a processzorok szmnak nvelsvel egyenes arnyban n a teljestmny, magyarul kt processzor ktszer gyorsabb, mint egy. Ez az llts nem teljesen igaz (ezt ksbb a sajt szemnkkel is ltni fogjuk), ezt pedig Gene Amdahl bizonytotta (Amdahls Law), miszerint: Egy prhuzamos program maximum olyan gyors lehet, mint a leghosszabb szekvencilis (tovbb mr nem prhuzamosthat) rszegysge. Vegynk pldul egy programot, amely 10 rn keresztl fut nem prhuzamosan. Ennek a programnak 9 rnyi rsze prhuzamosthat, mg a maradk egy ra nem. Ha ezt a 9 rt prhuzamostjuk, akkor a ttel alapjn a programnak gy is minimum egy rs futsideje lesz. Amdahl a kvetkez kpletet tallta ki: 1 +

Ahol P a program azon rsze, amely prhuzamosthat, (1 P) az, amelyik nem, N pedig a processzorok szma. Nzzk meg, hogy mekkora a maximum teljestmny, amit a fenti esetbl ki tudunk prselni! P ekkor 0,9 lesz (9 ra = 90% = 0,9), s a kplet (kt processzort hasznlva): 1 1 0,9 + 0,9/2 Knnyen kiszmolhat, hogy az eredmny 1/0,55 (1,81) lesz, vagyis 81% -os teljestmnynvekedst rhetnk el kt processzor bevezetsvel. Vegyk szre, hogy a processzorok szmnak nvelsvel P/N a nullhoz tart, vagyis kimondhatjuk, hogy minden prhuzamosthat feladat maximum 1/(1 P) nagysgrend teljestmnynvekedst eredmnyezhet (felttelezve, hogy mindig annyi processzor ll rendelkezsnkre, hogy P/N a lehet legkzelebb legyen nullhoz: ez nem felttlenl jelent nagyon sokat, a plda esetben 5 processzor mr 550%-os nvekedst jelent, innen pedig egyre lassabban n az eredmny, mivel ekkor P/N rtke mr 0,18, hat processzornl 0,15 s gy tovbb). Teht a fenti konkrt esetben a maximlis teljestmny

210

a hagyomnyos futsid tzszerese lehet (1 / (1 0,9), vagyis pontosan az az egy ra, amelyet a nem prhuzamosthat programrsz hasznl fel!

PRHUZAMOS CIKLUSOK
A TPL tartalmazza a for s foreach ciklusok prhuzamostott vltozatt. Nzznk meg egyszer pldt, hasonltsuk ssze a sima for s a prhuzamos for teljestmnyt! Ksztsnk egy tmbt, tltsk fel szmokkal, s rjuk ki a tartalmt! Nem valami okos plda, de arra elg, hogy lssuk a prhuzamos vltozatok szintaxist.
using using using using System; System.Diagnostics; System.Threading.Tasks; System.Linq;

namespace TestApp { class Program { static void Main(string[] args) { int[] array = Enumerable.Range(0, 10000).ToArray<int>(); Stopwatch sw = new Stopwatch(); long result1 = 0; long result2 = 0; sw.Start(); Parallel.For( 0, //honnan array.Length, //meddig (i) => Console.WriteLine(array[i])); //ciklustrzs sw.Stop(); result1 = sw.ElapsedMilliseconds; sw.Reset(); sw.Start(); for (int i = 0; i < array.Length; ++i) { Console.WriteLine(array[i]); } sw.Stop(); result2 = sw.ElapsedMilliseconds; Console.WriteLine("Az els ciklus ltal felhasznlt id: {0} ms", result1); Console.WriteLine("A msodik ciklus ltal felhasznlt id: {0} ms", result2); Console.ReadKey(); } } }

211

A Parallel.For ebben a legegyszerbb vltozatban hrom paramtert vr, akrcsak a nem prhuzamos trsa: a ciklusvltoz kiindulsi rtkt, a maximlis rtket (amely mr nem szmt bele az itercikba), illetve a ciklustrzset. A ciklusvltoz int vagy long tpus lehet, s minden esetben eggyel lp elre. Az Enumerable.Range a paramtereiknt megadott intervallumban generl szmokat, ezen meghva a ToArray fggvnyt visszakapjuk ennek a sorozatnak a tmbalakjt. A Stopwatch osztlyt hasznljuk idmrsre? a Start s Stop metdusval elindtjuk, illetve meglltjuk az rt, a Reset pedig alaphelyzetbe rakja. Ha megnzzk a program futsa utn az eredmnyeket, akkor azt ltjuk, hogy a ktfle ciklus nagyjbl ugyanannyi id alatt rta ki a 10000 szmot. Azt is vegyk szre, hogy mg a rendes vltozat sorrendben veszi el a szmokat a tmbbl, addig a prhuzamos meglehets sszevisszasgban! Ennek oka, hogy utbbi esetben a prhuzamos vgrehajts csoportokba rendezi az elemeket (particionlja) s az egyes csoportokat kldi feldolgozsra. Ezek aztn nem sorrendben rkeznek a processzorhoz, gy a kimenet nem rendezett. Nzznk egy sszetettebb pldt: adjuk ssze az els egymilli szmot! A forrskd a kvetkez lesz:
using using using using using using System; System.Diagnostics; System.Threading.Tasks; System.Linq; System.Collections.Concurrent; System.Threading;

namespace TestApp { class Program { static void Main(string[] args) { int[] array = Enumerable.Range(1, 1000000).ToArray<int>(); Stopwatch sw = new Stopwatch(); long sum = 0; sw.Start(); for (int i = 0; i < array.Length; ++i) { sum += array[i]; } sw.Stop(); Console.WriteLine("Az eredmny: {0}", sum); Console.WriteLine("A ciklus ltal felhasznlt id: {0} ms", sw.ElapsedMilliseconds); sum = 0; sw.Reset(); sw.Start(); Parallel.For(0, array.Length, (i) => sum += array[i]); sw.Stop(); Console.WriteLine("Az eredmny: {0}", sum); Console.WriteLine("A ciklus ltal felhasznlt id: {0} ms", sw.ElapsedMilliseconds); Console.ReadKey();

212

} } }

Ha elindtjuk a programot, kt nagyon rdekes dolgot tapasztalunk: a prhuzamos vltozat jelentsen lassabb (akr ngy-tszr is), illetve rossz eredmnyt is ad vissza. Elssorban kezeljk az utbbi problmt: a Parallel.For ha mst nem adtunk meg, automatikusan int tpus ciklusvltozt hasznl, amelyben a vrt eredmny nem fr el. rjuk t long tpusra:
Parallel.For(0, array.Length, (long i) => sum += array[i]);

Ezutn is fennll azonban, hogy sok esetben rossz az eredmny (nem rt nhnyszor futtatni). Mirt van ez gy? A TPL ugyan egyszersti a dolgunkat, de nem lehet sz nlkl hasznlni. Ebben az esetben az alapproblma, hogy egy cikluson kvli vltozt hasznlunk, radsul olyan mvelettel, amely nem szlbiztos. A += opertor itt sum = sum + array[i] alakban rtkeldik ki. Kpzeljk el, hogy megrkezik az egyik szl, elkezd szmolni, ebben a pillanatban pedig egy jabb szl bukkan fel! Az els mr kisajttotta magnak a sum vltozt, a msodik nem jut hozz, az adott mvelet egyszeren kimarad. gy mr nem csoda, hogy a vgeredmny nem megfelel. rjuk t ismt a prhuzamos ciklust:
Parallel.For(0, array.Length, () => 0, (long i, ParallelLoopState loop, long total) => { total += array[i]; return total; }, (x) => Interlocked.Add(ref sum, x));

A paramterek sorban: a ciklusvltoz kiindulsi rtke, a maximlis rtk, utna a programrsz, amellyel az egyes szlaknak adhatunk informcit. Az utols kt paramter a lnyeg: az els maga is kap hrom paramtert, ezek a ciklusvltoz, egy objektum, amelynek segtsgvel id eltt lellthatjuk a ciklus futst, illetve egy vltoz, amelyet tovbbadhatunk a tovbbi iterciknak. A ciklustrzs ennek fnyben egyszer: a helyi vltoznkat hasznljuk az sszeadshoz. Vgl a For utols paramtere jn, amely a szlbiztos mveleteket segti: az Interlocked osztllyal elemi mveleteket vgezhetnk (ebben az esetben sszeadst). Ez a fggvny nem fut le minden egyes iterciban, helyette az egyes szlak mkdse vgn hvja meg csak a rendszer, ezltal jelents mennyisg teljestmnyt nyerhetnk. Ha jra futtatjuk a programunk, most mr minden esetben helyes vgeredmnyt kapunk, viszont a sebessg nem vltozott. Ezzel el is rkeztnk a fejezet lnyeghez: a prhuzamos vgrehajts meglehetsen nagy odafigyelst s specilis krlmnyeket ignyel. Amennyiben a ciklustrzs rvid (rtsd: kevs vagy olcs mvelet) magnak a prhuzamossgnak a bezemelse megli a teljestmnyt, gyakorlatilag tbb erforrs szksges hozz mint a mveletek vgrehajtshoz. Ugyangy a lehetsges mveletek is behatroltak, ahogy lttuk, nem biztos, hogy j tlet kls vltozkat, objektumokat hasznlni (viszont tmbk esetn ha az egyes elemekkel dolgozunk amg nem mdostunk tbbszr egy elemet, addig szlbiztos a program). Nem rt azt is tudni, hogy rdemes olyan mveletek et hasznlni, ahol az egyes rszeredmnyek nem fggenek egymstl (persze vannak mdszerek a kevsb egyrtelm esetek kezelsre is). Nem kell azonban elkeseredni, a TPL ettl fggetlenl is rendkvl hasznos. Nzznk meg egy olyan pldt, ahol tnyleges elnyhz jutunk hasznlatval! A kvetkez programunkban mtrixokat szorzunk ssze, ez egy kellen komplex mveletsorozatot ignyel. Lssuk, hogy teljest a prhuzamos ciklus. Ksztsnk elszr egy fggvnyt, amellyel tetszleges mret mtrixot generlhatunk:

213

static public double[,] GenerateMatrix(int size) { double[,] matrix = new double[size, size]; Random r = new Random(); for (int i = 0; i < size; ++i) { for (int j = 0; j < size; ++j) { matrix[i, j] = r.Next(1000, 100000); } } return matrix; }

Most jjjn a szorzst vgz programrsz a tesztcsomaggal:


static void Main(string[] args) { int size = 1000; double[,] a = GenerateMatrix(size); double[,] b = GenerateMatrix(size); double[,] c = new double[size, size]; Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < size; ++i) { for (int j = 0; j < size; ++j) { double v = 0; for (int k = 0; k < size; ++k) { v += a[i, k] * b[k, j]; } c[i, j] = v; } } sw.Stop(); Console.WriteLine("A ciklus ltal felhasznlt id: {0} ms", sw.ElapsedMilliseconds); c = new double[size, size]; sw.Reset(); sw.Start(); Parallel.For(0, size, (i) => { for (int j = 0; j < size; ++j) { double v = 0; for (int k = 0; k < size; ++k) {

214

v += a[i, k] * b[k, j]; } c[i, j] = v; } });

sw.Stop(); Console.WriteLine("A ciklus ltal felhasznlt id: {0} ms", sw.ElapsedMilliseconds); Console.ReadKey(); }

A 100x100-as mtrixok sszeszorzsra a tesztgpen (AMD 255 X2, kt mag) a hagyomnyos ciklusnak 14 msodpercre volt szksge, a prhuzamos 10 alatt vgzett. 1000x1000-es mtrixokkal hasonl az elny: 162 msodperc 108 ellen. Lthat, hogy egy elavult processzor felett is kzel 50% plusz teljestmnyt lehet kifacsarni a rendszerbl. A Parallel.For-nl jval rugalmasabb a Paralell.ForEach. Amg elbbinl csak egy szmsorozaton iterltunk t s kls forrsbl szedtk el az adatokat, megkzdve a szlbiztossg rmvel, addig utbbi kzvetlenl egy gyjtemny elemein megy vgig. rjunk egy programot, amellyel egy knyvtr fjljait tudjuk feldolgozni:
using System; using System.Threading.Tasks; using System.IO; namespace TestApp { class Program { static public void Main(string[] args) { DirectoryInfo di = new DirectoryInfo("D:\\"); Parallel.ForEach(di.GetFiles(), (file) => { Console.WriteLine(file.FullName); }); Console.ReadKey(); } } }

A For-hoz hasonl a szintaxis, de ebben az esetben a ciklustrzs szmra az aktulis elemet adjuk t.

PARALLEL.INVOKE
Ciklusokkal egy adott feladatot hajthatunk vgre tbbszr. Ha viszont tbb klnbz akr egymstl fggetlen akcit szeretnnk elvgezni, akkor ms eszkzt kell vlasztanunk. A Parallel.Invoke pp ennek a felttelnek tesz eleget: paramtereknt tetszleges szm tevkenysget adhatunk meg, amelyeket aztn prhuzamosan hajt vgre:

215

using System; using System.Threading.Tasks; using System.Net; namespace TestApp { class Program { static public void Main(string[] args) { Parallel.Invoke( () => Console.WriteLine("Task 1"), () => Console.WriteLine("Task 2"), () => Console.WriteLine("Task 3") ); Console.ReadKey(); } } }

Az Invoke Action delegate-ek paramtertmbjt vrja paramterknt, ezeknek nem lehet bemen paramtere. A fenti program egyszeren kirja a hrom mondatot, csinljunk valami bonyolultabbat. Pldul egy hosszabb szvegbl keressk meg a leghosszabb szt, s hogy sszesen hny sz szerepel az rsban!
using using using using System; System.Threading.Tasks; System.Net; System.Linq;

namespace TestApp { class Program { static public void Main(string[] args) { WebClient wc = new WebClient(); string[] s = wc.DownloadString( "http://www.gutenberg.org/cache/epub/103/pg103.txt") .Split( new char[] { ' ', '\u000A', ',', '.', ';', ':', '-', '_', '/' }, StringSplitOptions.RemoveEmptyEntries ); Parallel.Invoke( () => Console.WriteLine( s.OrderByDescending((item) => item.Length).First()), () => Console.WriteLine(s.Length) ); Console.ReadKey(); } } }

Ha futtatjuk a programot, akkor szrevehetjk, hogy nem biztos, hogy az ltalunk megadott sorrendben futnak le az egyes mveletek. Illetve vegyk szre azt is, hogy ugyanazt az erforrst hasznlja a kt Action objektum, de mivel nem vgeznek rajta mdostst, ezrt a mveletek szlbiztosak.

216

TASK
A TPL egyik alapkve a Task osztly. Ne keverjk ssze azonban t a szlakkal! Egy Task objektum egy komplett prhuzamos feladatot reprezentl, mg egy szl csak a munkafolyamat rsze. Ugyanakkor ennek az osztlynak a kezelse elgg hasonlt a szlakhoz. Nzznk is egy egyszer pldt:
using System; using System.Threading.Tasks; namespace TestApp { class Program { static public void Main(string[] args) { Task task1 = Task.Factory.StartNew( () => Console.WriteLine("n egy Task vagyok!") ); Task task2 = Task.Factory.StartNew( () => Console.WriteLine("n is egy Task vagyok!") ); task2.Wait(); Console.ReadKey(); } } }

A Task.Factory.StartNew egy j Task objektumot ad vissza, amelyet egyttal el is indt. A Wait metdus megvrja, amg a hozz tartoz objektum befejezi a futst. Ha nem hvtuk volna meg, a program indtsakor semmit nem ltnnk, hiszen a Console.ReadKey blokkolja a fszlat. Egy billentylets utn megjelenne a kt mondat, de ugye csak egy villansnyi idre. Ugyanez a helyzet, ha kzzel gyrtunk egy Task objektumot:
using System; using System.Threading.Tasks; namespace TestApp { class Program { static public void Main(string[] args) { Task task = new Task( () => Console.WriteLine("n egy Task vagyok!") ); task.Start(); task.Wait(); Console.ReadKey(); } } }

Egy Task objektum eredmnyt is adhat vissza a kvetkezkppen:

217

using System; using System.Threading.Tasks; namespace TestApp { class Program { static public void Main(string[] args) { Task<int> task = new Task<int>( () => { return 10; } ); task.Start(); Console.WriteLine(task.Result); // 10 Console.ReadKey(); } } }

A Task paramterestett vltozatn megjelenik a Result tulajdonsg. Ilyenkor a paramter tpusa lesz a visszatrsi rtk tpusa. Hasonlkppen jrunk el a Task.Factory.StartNew esetben is. Egy Taskhoz rendelhetnk paramtert is:
using System; using System.Threading.Tasks; namespace TestApp { class Program { static public void Main(string[] args) { string[] array = new string[] { "n egy Task vagyok!", "n is egy Task vagyok!", "n nem vagyok egy Task!" }; foreach (string item in array) { Task task = new Task( (obj) => Console.WriteLine(obj), item); task.Start(); } Console.ReadKey(); } } }

A paramtert a Taskon bell object tpusnak ltjuk, ha szksges, el kell vgezni a megfelel konverzit.

218

ASYNC/AWAIT
Amikor egy aszinkron programrszt hvunk meg, meglehetsen hossz forrskdot kell rnunk. El kell indtani a folyamatot, majd ha vgzett, egy msik fggvnynek t kell adnunk az eredmnyt, ahonnan ki tudja, hov utazik majd tovbb. A C# 5.0 s a .NET 4.5 bevezeti az async/await kulcsszavakat, amelyek segtsgvel igencsak leegyszersdik ez a folyamat. Az albbi forrskdok lefordtshoz Visual Studio 2011-re van szksg! Nzzk meg, hogy mkdik ez az j konstrukci:
using System; using System.Threading; using System.Threading.Tasks; namespace AsyncDemo { class Program { static async void DoOperations() { var result = await Task<string>.Factory.StartNew( () => ReallyLongOperation() ); Console.WriteLine("Az eredmny: {0}", result); } static string ReallyLongOperation() { Thread.Sleep(2000); return "Siker"; } static void Main(string[] args) { Console.WriteLine("A mvelet eltt..."); DoOperations(); Console.WriteLine("A mvelet utn..."); Console.ReadKey(); } } }

Ha futtatjuk a programot, akkor ltni fogjuk, hogy az eltt s utn zenetek jelennek meg elszr, s csak utnuk az eredmny, ami arra utal, hogy a ReallyLongOperation fggvny aszinkron mdon futott le. Hogyan rtk ezt el? A metdus szignatrja el helyezett async sz azt jelzi a fordtnak, hogy a blokkjban valahol hasznlni szeretnnk az await utastst. Amikor ahhoz a bizonyos helyhez r a program futsa, az trtnik, hogy abban a pillanatban a metdus futsa megszakad, s httrbe vonul, amg az adott mvelet vget nem r. Azutn felbred s befejezi a feladatt. Az async kulcsszt csakis olyan metdusok/fggvnyek esetben hasznlhatjuk, ahol a visszatrsi rtk tpusa void, Task vagy Task<T> (nhny kivtellel, pldul a Main-en nem lehet). Az async meglte nem ktelez minket az await hasznlatra, ez fordtva viszont nem igaz. Az await utastst hasznlhatjuk minden olyan fggvnyen, amelynek visszatrsi tpusa Task vagy Task<T>, illetve ha megvalstja a fggvnyt tartalmaz osztly a GetAwaiter metdust. A .NET 4.5 beptve tartalmaz olyan mveleteket, amelyek hasznlhatak ezzel az utastssal, pldul a klnbz Stream osztlyok tbb metdusa is ilyen. Jellemzen Async uttagot kapnak, illetve az IntelliSense a fggvny/metdus paramterlistja mellett megjelenti az awaitable szt is.

219

REFLECTION
A reflection fogalmt olyan programozsi technikra alkalmazzuk, ahol a program (futs kzben) kpes megvltoztatni sajt struktrjt s viselkedst. Az erre a paradigmra pl programozst reflektv programozsnak nevezzk. Ebben a fejezetben csak egy rvid pldt tallhat a kedves olvas, a Reflection tmakre risi, s a mindennapi programozsi feladatok vgzse kzben viszonylag ritkn szorulunk r (ugyanakkor bizonyos esetekben nagy hatkonysggal jrhat a hasznlata). Vegyk a kvetkez pldt:
using System; class Test { } class Program { static public void Main(string[] args) { Test t = new Test(); Type type = t.GetType(); Console.WriteLine(type); // Test Console.ReadKey(); } }

Futsidben lekrtk az objektum tpust (a GetType metdust minden osztly rkli az objecttl), persze a reflektv programozs ennl tbbrl szl, lpjnk elre egyet: mi lenne, ha az objektumot gy ksztennk el, hogy egyltaln nem rjuk le a new opertort:
using System; using System.Reflection; // erre a nvtrre szksg van class Test { } class Program { static public void Main(string[] args) { Type type = Assembly.GetCallingAssembly().GetType("Test"); var t = Activator.CreateInstance(type); Console.WriteLine(t.GetType()); // Test Console.ReadKey(); } }

Megjegyzend, hogy fordtsi idben semmilyen ellenrzs sem trtnik a pldnyostand osztlyra nzve, gy ha elgpeltnk valamit, az bizony kivtelt fog dobni futskor (System.ArgumentNullException).

220

LLOMNYKEZELS
Ebben a fejezetben megtanulunk fjlokat rni/olvasni s megtanuljuk a knyvtrstruktra llapotnak lekrdezst illetve mdostst is.

OLVASS/RS FJLBL/FJLBA
A .NET szmos osztlyt biztost szmunkra, amelyekkel fjlokat tudunk kezelni. Ebben a fejezetben a leggyakrabban hasznltakkal ismerkednk meg. Kezdjk egy fjl megnyitsval s tartalmnak a kpernyre rsval. Legyen pl. a szveges fjl tartalma a kvetkez: alma krte di cskny pnz knyv A fjlnak a futtathat programunk mellett kell lennie, vagy teljes elrsi utat kell megadnunk. Visual Studio-ban pldul hozzadhatunk egy Text File-t a projekthez (jobb klikk, majd Add New Item), ezutn a fjlon jobb gombbal kattintva Properties, s a Copy To Output Directory sorban vlasszuk a Copy Always lehetsget! A program pedig:
using System; using System.IO; class Program { static public void Main(string[] args) { FileStream fs = new FileStream("test.txt", FileMode.Open); StreamReader sr = new StreamReader(fs); string s = sr.ReadLine(); while (s != null) { Console.WriteLine(s); s = sr.ReadLine(); } sr.Close(); fs.Close(); Console.ReadKey(); } }

Az IO osztlyok a System.IO nvtrben vannak. A C# n. streameket, adatfolyamokat hasznl az IO mveletek vgzshez. Az els sorban megnyitottunk egy ilyen folyamot, s azt is megmondtuk, hogy mit akarunk tenni vele. A FileStream konstruktornak els paramtere a fjl neve (ha nem tall ilyen nev fjlt, akkor kivtelt dob a program). Amennyiben nem adunk meg teljes elrsi utat, akkor automatikusan a sajt knyvtrban fog keresni a program. Ha kls knyvtrbl szeretnnk megnyitni az llomnyt, akkor azt a kvetkezkppen tehetjk meg:

221

FileStream fs = new FileStream("C:\\Dir1\\Dir2\\test.txt", FileMode.Open);

Azrt hasznlunk dupla backslasht (\), mert ez egy n. escape karakter, nmagban nem lehetne hasznlni (persze ez nem azt jelenti, hogy minden ilyen karaktert kettzve kellene rni, minden specilis karakter eltt a backslasht kell hasznlnunk). Egy msik lehetsg, hogy az at jelet (@) hasznljuk az elrsi t eltt, ekkor nincs szksg dupla karakterekre, mivel mindent normlis karakterknt fog rtelmezni:
FileStream fs = new FileStream(@"C:\Dir1\Dir2\test.txt", FileMode.Open);

A FileMode enumnak a kvetkez rtkei lehetnek: Create CreateNew Open OpenOrCreate Append Truncate Ltrehoz egy j fjlt, ha mr ltezik, a tartalmt kitrli. Ugyanaz, mint az elz, de ha mr ltezik a fjl, akkor kivtelt dob. Megnyit egy fjl, ha nem ltezik, kivtelt dob. Ugyanaz, mint az elz, de ha nem ltezik, akkor ltrehozza a fjlt. Megnyit egy fjlt, s automatikusan a vgre pozicionl. Ha nem ltezik, ltrehozza. Megnyit egy ltez fjlt, s trli a tartalmt. Ebben a mdban a fjl tartalmt nem lehet olvasni (egybknt kivtelt dob).

A FileStream konstruktornak tovbbi kt paramtere is lehet, amelyek rdekesek szmunkra (tulajdonkppen 15 klnbz konstruktora van), mindkett felsorolt tpus. Az els a FileAccess, amellyel belltjuk, hogy pontosan mit akarunk csinlni az llomnnyal: Read Write ReadWrite A fenti pldt gy is rhattuk volna:
FileStream fs = new FileStream("test.txt", FileMode.Open, FileAccess.Read);

Olvassra nyitja meg. rsra nyitja meg. Olvassra s rsra nyitja meg

Vgl a FileSharerel azt lltjuk be, ahogy ms folyamatok frnek hozz a fjlhoz: None Read Write ReadWrite Delete Inheritable Ms folyamat nem frhet hozz a fjlhoz, amg azt be nem zrjuk. Ms folyamat olvashatja a fjlt. Ms folyamat rhatja a fjlt. Ms folyamat rhatja s olvashatja is a fjlt. Ms folyamat trlhet a fjlbl (de nem magt a fjlt). A gyermek processzek is hozzfrhetnek a fjlhoz.

Ha a fenti programot futtatjuk, akkor elfordulhat, hogy az kezetes karakterek helyett krdjel jelenik meg. Ez azrt van, mert az ppen aktulis karaktertbla nem tartalmazza ezeket a karaktereket, ez tipikusan nem magyar nyelv opercis rendszer esetn fordul el. A megolds, hogy kzzel lltjuk be a megfelel tblt, ezt a StreamReader konstruktorban tehetjk meg (a tbla pedig iso-8859-2 nven szerepel):
StreamReader rs = new StreamReader(fs, Encoding.GetEncoding("iso-8859-2"), false);

Ehhez szksg lesz mg a System.Text nvtrre is.

222

Most rjunk is a fjlba. Erre a feladatra a StreamReader helyett a StreamWriter osztlyt fogjuk hasznlni:
using System; using System.IO; using System.Text; class Program { static public void Main(string[] args) { FileStream fs = new FileStream("Test.txt", FileMode.Open, FileAccess.Write, FileShare.None); StreamWriter sw = new StreamWriter(fs); Random r = new Random(); for (int i = 0; i < 10; ++i) { sw.Write(r.Next()); sw.Write(Environment.NewLine); } sw.Close(); fs.Close(); Console.ReadKey(); } }

Az Environment.NewLine egy jsor karaktert (Carriage Return s Line Feed) ad vissza. Binris fjlok kezelshez a BinaryReader/BinaryWriter osztlyokat hasznlhatjuk:
using System; using System.IO; using System.Text; class Program { static public void Main(string[] args) { BinaryWriter bw = new BinaryWriter(File.Create("file.bin")); for (int i = 0; i < 100; ++i) { bw.Write(i); } bw.Close(); BinaryReader br = new BinaryReader(File.Open("file.bin", FileMode.Open)); while (br.PeekChar() != -1) { Console.WriteLine(br.ReadInt32()); } br.Close(); Console.ReadKey(); } }

223

Ksztettnk egy binris fjlt, s belertuk a szmokat egytl szzig. Ezutn megnyitottuk, s elkezdtk kiolvasni a tartalmt. A PeekChar metdus a soron kvetkez karaktert (byteot) adja vissza, illetve -1et, ha elrtk a file vgt. A folyambeli aktulis pozcit nem vltoztatja meg. A cikluson bell van a trkks rsz. A ReadTpusnv metdus a megfelel tpus adatot adja vissza, de vigyzni kell vele, mert ha nem megfelel mret a beolvasand adat, akkor hibzni fog. A fenti pldban, ha a ReadString metdust hasznltuk volna, akkor kivtel (EndOfStreamException) keletkezik, mivel a kett nem ugyanakkora mennyisg adatot olvas be. Az egsz szmokat kezel metdus nylvn mkdni fog, hiszen tudjuk, hogy szmokat rtunk ki. Eddig kzzel zrtuk le a stream-eket, de ez nem olyan biztonsgos, mivel gyakran elfelejtkezik rla az ember. Hasznlhatjuk ehelyett a using blokkokat, amelyek ezt automatikusan megteszik. Fent pldul rhatnnk ezt is:
using (BinaryWriter bw = new BinaryWriter(File.Create("file.bin"))) { for (int i = 0; i < 100; ++i) { bw.Write(i); } }

KNYVTRSTRUKTRA KEZELSE
A fjlok mellett a .NET a knyvtrstruktra kezelst is tmogatja. A System.IO nvtr ebbl a szempontbl kt rszre oszlik: informcis s opercis eszkzkre. Elbbiek (ahogyan a nevk is sugallja) informcit szolgltatnak, mg az utbbiak (tbbsgkben statikus metdusok) bizonyos mveleteket (j knyvtr ltrehozsa, trlse stb.) vgeznek a fjlrendszeren. Els pldnkban rjuk ki mondjuk a C meghajt gykernek knyvtrait:
using System; using System.IO; class Program { static public void Main(string[] args) { foreach (string s in Directory.GetDirectories("C:\\")) { Console.WriteLine(s); } Console.ReadKey(); } }

Termszetesen nemcsak a knyvtrakra, de a fjlokra is kvncsiak lehetnk. A programunk mdostott vltozata nmi plusz informcival egytt ezeket is kirja neknk:
using System; using System.IO; class Program { static public void PrintFileSystemInfo(FileSystemInfo fsi) { if ((fsi.Attributes & FileAttributes.Directory) != 0) { DirectoryInfo di = fsi as DirectoryInfo;

224

Console.WriteLine("Knyvtr: {0}, Kszlt: {1}", di.FullName, di.CreationTime); } else { FileInfo fi = fsi as FileInfo; Console.WriteLine("File: {0}, kszlt: {1}", fi.FullName, fi.CreationTime); } } static public void Main(string[] args) { foreach (string s in Directory.GetDirectories("C:\\")) { PrintFileSystemInfo(new DirectoryInfo(s)); } foreach (string s in Directory.GetFiles("C:\\")) { PrintFileSystemInfo(new FileInfo(s)); } Console.ReadKey(); } }

Elszr a mappkon, majd a fjlokon megynk vgig. Ugyanazzal a metdussal rjuk ki az informcikat kihasznlva azt, hogy a DirectoryInfo s a FileInfo is egy kzs stl, a FileSystemInfo-bl szrmazik (mindkett konstruktora a vizsglt alany elrsi tjt vrja paramterknt), gy a metdusban csak meg kell vizsglni, hogy ppen melyikkel van dolgunk, s tkonvertlni a megfelel tpusra. A vizsglatnl egy bitenknti s mveletet hajtottunk vgre, hogy ez mirt s hogyan mkdik, annak meggondolsa az olvas feladata. Eddig csak informcikat krtnk le, most megtanuljuk mdostani is a knyvtrstruktrt. Arra figyeljnk, hogy olyan knyvtrat vagy meghajtt hasznljunk a program tesztelsre, amelybe joga van rni a programunknak, ellenkez esetben kivtelt kaphatunk!
using System; using System.IO; class Program { static public void Main(string[] args) { string dirPath = "C:\\test"; string filePath = dirPath + "\\file.txt"; // ha nem ltezik a knyvtr if (Directory.Exists(dirPath) == false) { // akkor elksztjk Directory.CreateDirectory(dirPath); } FileInfo fi = new FileInfo(filePath); // ha nem ltezik a file if (fi.Exists == false) { // akkor elksztjk s runk bele StreamWriter sw = fi.CreateText(); sw.WriteLine("Dio");

225

sw.WriteLine("Alma"); sw.Close(); } } }

A FileInfo CreateText metdusa egy StreamWriter objektumot ad vissza, amellyel rhatunk egy szveges fjlba.

IN-MEMORY STREAMEK
A .NET a MemoryStream osztlyt biztostja szmunkra, amellyel memriabeli adatfolyamokat rhatunk/olvashatunk. Mire is jk ezek a folyamok? Gyakran van szksgnk arra, hogy sszegyjtsnk nagy mennyisg adatot, amelyeket majd a folyamat vgn ki akarunk rni a merevlemezre. Egy tmb vagy egy lista nem nyjtja a megfelel szolgltatsokat, elbbi rugalmatlan, utbbi memriaignyes, ezrt a legegyszerbb, ha kzvetlenl a memriban troljuk el az adatokat. A MemoryStream osztly jelentsen megknnyti a dolgunkat, mivel lehetsget ad kzvetlenl fjlba rni a tartalmt. A kvetkez program erre mutat pldt:
using System; using System.IO; class Program { static public void Main(string[] args) { MemoryStream mstream = new MemoryStream(); StreamWriter sw = new StreamWriter(mstream); for (int i = 0; i < 1000; ++i) { sw.WriteLine(i); } sw.Flush(); FileStream fs = File.Create("D:\\inmem.txt"); mstream.WriteTo(fs); sw.Close(); fs.Close(); mstream.Close(); } }

A MemoryStream-re rlltottunk egy StreamWriter objektumot. Miutn minden adatot a memriba rtunk, a Flush metdussal (amely egyttal kirti a StreamWriter-t is) ltrehoztunk egy fjlt s a MemoryStream WriteTo metdusval kirtuk bel az adatokat.

XML
Az XML ltalnos cl lernyelv, amelynek elsdleges clja strukturlt szveg s informci megosztsa az interneten keresztl. Lssunk egy pldt:
<?xml version="1.0" encoding="utf-8" ?> <list> <item>1</item>

226

<item>2</item> <item>3</item> </list>

Az els sor megmondja, hogy melyik verzit s milyen kdolssal akarjuk hasznlni, ezutn kvetkeznek az adatok. Minden XML dokumentum egyetlen gykrelemmel rendelkezik (ez a fenti pldban a <list>), amelynek minden ms elem a gyermekeleme (egyttal minden nem-gykrelem egy msik elem gyermeke kell legyen, ez nem felttlenl jelenti a gykrelemet). Minden elemnek rendelkeznie kell nyit (<list>) s zr (</list>) tagekkel. res elemeknl ezeket egyszerre is deklarlhatjuk (<res />). Az egyes elemek trolhatnak attribtumokat a kvetkez formban:
<item value="10" /> <item value="10"></item>

Itt mindkt forma leglis. A .NET Framework ersen pt az XML-re, mind offline (konfigurcis fjlok, inmemory adatszerkezetek), mind online (SOAP alap informcicsere stb.) tren. A szmunkra szksges osztlyok a System.Xml nvtrben vannak, a kt legalapvetbb ilyen osztly az XmlReader s az XmlWriter. Ezeknek az absztrakt osztlyoknak a segtsgvel hajthatak vgre a szoksos irs/olvass mveletek. Elszr nzzk meg, hogyan tudunk beolvasni egy XML fjlt. A megnyitshoz az XmlReader egy statikus metdust, a Create-et fogjuk hasznlni, ez egy streamtl kezdve egy szimpla fjlnvig mindent elfogad:
XmlReader reader = XmlReader.Create("test.xml");

Fggetlenl attl, hogy az XmlReader egy absztrakt osztly, tudjuk pldnyostani, mgpedig azrt, mert ilyenkor valjban egy XmlTextReaderImpl tpus objektum jn ltre, amely az XmlReader egy bels, internal elrs leszrmazottja (teht kzvetlenl nem tudnnk pldnyostani). Miutn teht megnyitottuk a fjlt, vgig tudunk iterlni a tartalmn:
while (reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: Console.WriteLine("<{0}>", reader.Name); break; case XmlNodeType.EndElement: Console.WriteLine("</{0}>", reader.Name); break; case XmlNodeType.Text: Console.WriteLine(reader.Value); break; default: break; }; }

Az XmlReader NodeType tulajdonsga egy XmlNodeType felsorols egy tagjt adja vissza, a pldban a legegyszerbb tpusokat vizsgltuk, s ezek fggvnyben rtuk ki a fjl tartalmt. A kvetkez pldban hasznlni fogjuk az XmlWriter osztly egy leszrmazottjt, az XmlTextWritert, ugyanis a fjlt kdbl fogjuk ltrehozni:
XmlTextWriter writer = new XmlTextWriter("newxml.xml", Encoding.UTF8); writer.Formatting = Formatting.Indented;

Az Encoding felsorols hasznlathoz szksges a System.Text. A Formatting tulajdonsg az Xml fjloktl megszokott hierarchikus szerkezetet fogja megteremteni (ennek az rtkt is bellthatjuk).

227

writer.WriteStartDocument(); writer.WriteComment(DateTime.Now.ToString());

Elkezdtk az adatok feltltst, s beszrtunk egy kommentet is. A file tartalma most a kvetkez:
<?xml version="1.0" encoding="utf-8"?> <!--2012.07.24. 7:05:24-->

A fjl ebben a pldban szemlyek adatait fogja tartalmazni:


writer.WriteStartElement("PersonsList"); writer.WriteStartElement("Person"); writer.WriteElementString("Name", "Reiter Istvan"); writer.WriteElementString("Age", "26"); writer.WriteEndElement();

A StartElement egy j tagcsoportot kezd a paramtereknt megadott nvvel, a WriteElementString pedig feltlti azt rtkekkel, kulcs-rtk prokkal. A fjl:
<?xml version="1.0" encoding="utf-8"?> <!--2012.07.24. 7:07:04--> <PersonsList> <Person> <Name>Reiter Istvan</Name> <Age>22</Age> </Person> </PersonsList>

Az egyes tagekhez attribtumokat is rendelhetnk:


writer.WriteStartElement("PersonsList"); writer.WriteAttributeString("Note", "List of persons");

Els paramter az attribtum neve, utna pedig a hozz rendelt rtk jn. Ekkor a fjl gy alakul:
<?xml version="1.0" encoding="utf-8"?> <!--2012.07.24. 7:08:03--> <PersonsList Note="List of persons"> <Person> <Name>Reiter Istvan</Name> <Age>22</Age> </Person> </PersonsList>

Az XmlReader rendelkezik nhny MoveTo eltaggal rendelkez metdussal, ezekkel a fjl egy olyan pontjra tudunk naviglni, amely megfelel egy felttelnek. Nhny plda:
bool l = reader.MoveToAttribute("Note");

Ez a metdus a paramtereknt megadott attribtumra lltja a reader objektumot, s igaz rtkkel tr vissza, ha ltezik az attribtum. Ellenkez esetben a pozci nem vltozik, s a visszatrsi rtk hamis lesz.
reader.MoveToContent();

Ez a metdus a legkzelebbi olyan csompontra ugrik, amely tartalmaz adatot is, ekkor az XmlReader (vagy leszrmazottainak) Value tulajdonsga ezt az rtket fogja tartalmazni.

228

A MoveToElement metdus visszalp arra a csompontra, amely azt az attribtumot tartalmazza, amelyen a reader ll (rtelemszeren kvetkezik, hogy az elz kett metdust gyakran hasznljuk egytt):
while (reader.Read()) { if (reader.HasAttributes) { for (int i = 0; i < reader.AttributeCount; ++i) { reader.MoveToAttribute(i); Console.WriteLine("{0} {1}", reader.Name, reader.Value); } reader.MoveToElement(); } }

A HasAttribute metdussal megtudakoljuk, hogy vane a csomponton attribtum, utna pedig az AttributeCount tulajdonsg segtsgvel (amely azoknak a szmt adja vissza) vgigiterlunk rajtuk. Miutn vgeztnk, visszatrnk a kiindulsi pontra (hogy a Read metdus tudja vgezni a dolgt). Emltst rdemelnek mg a MoveToFirstAttribute s a MovetoNextAttribute metdusok, amelyek nevkhz mltan az els illetve a kvetkez attribtumra pozicionlnak. Mdostsuk egy kicsit az elz pldt:
for (int i = 0; i < reader.AttributeCount; ++i) { //reader.MoveToAttribute(i); reader.MoveToNextAttribute(); Console.WriteLine("{0} {1}", reader.Name, reader.Value); }

Vgl, de nem utolssorban a Skip metdus maradt, amely tugorja egy csompont gyermekeit, s a kvetkez azonos szinten lv csompontra ugrik.

XML DOM
Eddig az XML llomnyokat hagyomnyos fjlknt kezeltk, ennl azonban van egy nmileg knyelmesebb lehetsgnk is. Az XML DOM (Document Object Model) a fjlokat hierarchikus felptsk szerint kezeli. Az osztly amelyen keresztl elrjk a DOM-ot az XmlDocument lesz. Egy msik fontos osztly, amelybl egybknt maga az XmlDocument is szrmazik, az XmlNode. Egy XML fjl egyes csompontjait egy-egy XmlNode objektum fogja jelkpezni. Ahogy azt mr megszokhattuk, az XmlDocument szmos forrsbl tpllkozhat, pl. stream vagy egyszer fjlnv. A forrs betltst az XmlDocument Load illetve LoadXml metdusaival vgezzk:
XmlDocument xdoc = new XmlDocument(); xdoc.Load("test.xml");

Egy XmlDocument bejrhat foreach ciklussal a ChildNodes tulajdonsgon keresztl, amely egy XmlNodeList objektumot ad vissza. Persze nem biztos, hogy lteznek gyermekei, ezt a HasChildNodes tulajdonsggal tudjuk ellenrizni. A kvetkez forrskdban egy XmlDocument els szinten lv gyermekeit jrjuk be:
using System; using System.Xml; class Program { static public void Main(string[] args)

229

{ XmlDocument xdoc = new XmlDocument(); xdoc.Load("test.xml"); foreach (XmlNode node in xdoc.ChildNodes) { Console.WriteLine(node.Name); } Console.ReadKey(); } }

Az XmlDocument a ChildNodes tulajdonsgt az XmlNode osztlytl rkli (ugyangy a HasChildNodest is, amely igaz rtket tartalmaz, ha lteznek gyermek-elemek), gy az egyes XmlNodeokat bejrva a teljes ft megkaphatjuk. Most nzzk meg, hogyan tudunk manipullni egy dokumentumot! Elszr hozzunk ltre egy j fjlt:
using System; using System.Xml; class Program { static public void Main(string[] args) { XmlDocument xdoc = new XmlDocument(); XmlElement element = xdoc.CreateElement("Test"); XmlText text = xdoc.CreateTextNode("Hello XML DOM!"); XmlNode node = xdoc.AppendChild(element); node.AppendChild(text); xdoc.Save("domtest.xml"); Console.ReadKey(); } }

Az XmlText illetve XmlElement osztlyok is az XmlNode leszrmazottai. A Save metdussal el tudjuk menteni a dokumentumot, akr fjlnevet, akr egy streamet megadva. A fenti kd eredmnye a kvetkez lesz:
<Test>Hello XML DOM!</Test>

A CloneNode metdussal mr ltez cscsokat klnozhatunk:


XmlNode source = xdoc.CreateNode(XmlNodeType.Element, "test", "test"); XmlNode destination = source.CloneNode(false);

A metdus egyetlen paramtert vr, amely jelzi, hogy a cscs gyermekeit is tmsoljuke. Kt msik metdusrl is megemlkeznk, els a RemoveChild, amely egy ltez cscsot tvolt el a cscsok listjbl, a msik pedig a ReplaceChild, amely fellr egy cscsot. Az XmlDocument esemnyeket is szolgltat, amelyek segtsgvel teljes kr felgyeletet nyerhetnk. Kezeljk le pldul azt az esemnyt, amikor egy j cscsot adunk hozz:
XmlDocument xdoc = new XmlDocument(); XmlNodeChangedEventHandler handler = null; handler = (sender, e) =>

230

{ Console.WriteLine(e.Node.Value); }; xdoc.NodeInserting += handler;

XML SZERIALIZCI
Mi is az a szrializls? A legegyszerbben egy pldn keresztl lehet megrteni a fogalmat. Kpzeljk el, hogy rtunk egy jtkot, s a jtkosok pontszmt szeretnnk eltrolni! Az elrt pontszm mell jn a jtkos neve s a teljests ideje is. Ez elsre semmi klns, hiszen simn kirhatjuk az adatokat egy fjlba, majd visszaolvashatjuk onnan. Egy apr gond mgis van, mgpedig az, hogy ez elg bonyolult feladat: figyelni kell a beolvasott adatok tpusra, formtumra stb. Nem lenne egyszerbb, ha a kirt adatokat meg tudnnk feleltetni egy osztlynak? Ezt megtehetjk, ha ksztnk egy megfelel osztlyt, amelyet szrializlva kirhatunk XML formtumba, s onnan vissza is olvashatjuk (ezt deszrializlsnak hvjk). Termszetesen a szrializls jelentsge ennl sokkal nagyobb, s nemcsak XML segtsgvel tehetjk meg. Tulajdonkppen a szrializls annyit tesz, hogy a memriabeli objektumainkat egy olyan szekvencilis formtumba (pl. binris vagy most XML) konvertljuk, amelybl vissza tudjuk alaktani az adatainkat. Mr emltettk, hogy tbb lehetsgnk is van a szerializcira, ezek kzl az XML a legltalnosabb, hiszen ezt ms krnyezetben is felhasznlhatjuk. Kezdjk egy egyszer pldval (hamarosan megvalstjuk a pontszmos pldt is), szrializljunk egy hagyomnyos beptett objektumot:
FileStream fstream = new FileStream("serxml.xml", FileMode.Create); XmlSerializer ser = new XmlSerializer(typeof(DateTime)); ser.Serialize(fstream, DateTime.Now);

Ezutn ltrejn egy XML file, benne a szrializlt objektummal:


<?xml version="1.0"?> <dateTime>2008-10-23T16:19:44.53125+02:00</dateTime>

Az XmlSerializer a System.Xml.Serialization nvtrben tallhat. A deszrializls hasonlkppen mkdik:


FileStream fstream = new FileStream("serxml.xml", FileMode.Open); XmlSerializer ser = new XmlSerializer(typeof(DateTime)); DateTime deser = (DateTime)ser.Deserialize(fstream);

Most mr eleget tudunk ahhoz, hogy ksztsnk egy szrializlhat osztlyt. Egy nagyon fontos dolgot kell megjegyeznnk: az osztlynak csakis a publikus tagjai szrializlhatak, a private vagy protected elrsek automatikusan kimaradnak (emellett az osztlynak magnak is publikus elrsnek kell lennie). Ezeken kvl mg szksg lesz egy alaprtelmezett konstruktorra is (a deszrializlshoz, hiszen ott mg nem tudjuk, hogy milyen objektumrl van sz).
public class ScoreObject { public ScoreObject() { } private string playername; [XmlElement("PlayerName")] public string PlayerName

231

{ get { return playername; } set { playername = value; } } private int score; [XmlElement("Score")] public int Score { get { return score; } set { score = value; } } private DateTime date; [XmlElement("Date")] public DateTime Date { get { return date; } set { date = value; } } }

Az egyes tulajdonsgoknl belltottuk, hogy az miknt jelenjen meg a fjlban (sima element helyett lehet pl. attribtum is). Enlkl is lefordulna s mkdne, de gy jobban kezelhet. Rendben, most szrializljuk:
ScoreObject so = new ScoreObject(); so.PlayerName = "Player1"; so.Score = 1000; so.Date = DateTime.Now; FileStream fstream = new FileStream("scores.xml", FileMode.Create); XmlSerializer ser = new XmlSerializer(typeof(ScoreObject)); ser.Serialize(fstream, so);

s az eredmny:
<?xml version="1.0"?> <ScoreObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <PlayerName>Player1</PlayerName> <Score>1000</Score> <Date>2012-07-24T10:31:43.8922442+02:00</Date> </ScoreObject>

232

KONFIGURCIS FJL HASZNLATA


Eddig, amikor egy programot rtunk, minden apr vltoztatsnl jra le kellett fordtani, mg akkor is, ha maga a program nem, csak a hasznlt adatok vltoztak. Sokkal knyelmesebb lenne a fejleszts s a ksz program hasznlata is, ha a kls adatokat egy kln fjlban trolnnk. Termszetesen ezt megtehetjk gy is, hogy egy sima szveges fjlt ksztnk, s azt olvassuk/rjuk, de a .NET ezt is megoldja helyettnk a konfigurcis fjlok bevezetsvel. Ezek tulajdonkppen XML dokumentumok, amelyek a kvetkezkppen plnek fel:
<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <add key="1" value="alma" /> <add key="2" value="korte" /> </appSettings> </configuration>

Az els sor egy szabvnyos XML file fejlce, evvel nem kell foglalkoznunk. Sokkal rdekesebb viszont az appSettings szekci, ahov mr be is raktunk nhny adatot. Az appSettings az ltalnos adatok trolsra szolgl, vannak specilis szekcik (pl. adatbzisok elrshez), s mi magunk is kszthetnk ilyet (errl hamarosan). Visual Studio-ban knnyen hozzadhatunk egy konfigurcis fjlt a projektnkhz: jobb gombbal kattintsunk a projekten, majd az Add menbl vlasszuk a New Item elemet, a listban pedig keressk meg az Application Configuration File sort! Az OK gombra kattintva ltrejn egy App.config nev fjl a projekten bell. Az res <configuration> szekciba msoljuk bele a fenti sorokat! Mg valamit meg kell tennnk: jobb gombbal kattintsunk a projekten bell a References mappn, majd vlasszuk az Add Reference elemet, majd a .NET fln a listbl keressk ki a System.Configuration assemblyt, s az Add gombbal adjuk hozz a projekthez! Most mr rhatunk egy programot, amely felhasznlja ezt a fjlt:
using System; using System.Configuration; class Program { static public void Main(string[] args) { string s = ConfigurationManager.AppSettings["1"]; Console.WriteLine(s); // alma Console.ReadKey(); } }

Lthat, hogy az AppSettings rendelkezik indexelvel, amely visszaadja a megfelel rtket a megadott kulcshoz. Ugyanezt elrhetjk a ConfigurationSettings.AppSettings-szel is, de az mr elavult erre a fordt is figyelmeztet, ha hasznlni akarjuk , csak a korbbi verzik kompatibilitsa miatt van mg benne a frameworkben. Az AppSettings indexelje minden esetben stringet ad vissza, teht ha ms tpussal akarunk dolgozni, akkor konvertlni kell. Vezessnk be a konfig-fjlba egy j kulcs-rtk prost:
<?xml version="1.0"?> <configuration>

233

<appSettings> <add key="1" value="alma"/> <add key="2" value="korte"/> <add key="3" value="10"/> </appSettings> </configuration>

s hasznljuk is fel a forrsban:


using System; using System.Configuration; class Program { static public void Main(string[] args) { int x = int.Parse(ConfigurationManager.AppSettings["3"]); Console.WriteLine(x); // 10 Console.ReadKey(); } }

KONFIGURCI-SZEKCI KSZTSE
Egyszerbb feladatokhoz megteszi a fenti mdszer is, de hosszabb programok esetben nem knyelmes mindig figyelni a konverzikra. pp ezrt kszthetnk olyan osztlyt is, amely egyrszt tpusbiztos, msrszt a konfigurcis fjlban is megjelenik. A kvetkez pldban elksztnk egy programot, amely konfigurcis fjlbl kiolvas egy fjlnevet s a kdtblt, s ezek alapjn kirja az llomny tartalmt. Els lpsknt ksztsnk egy j osztlyt (Add/Class) AppDataSection.cs nven, amely a kvetkezt tartlamazza:
using System; using System.Configuration; public class AppDataSection : ConfigurationSection { private static AppDataSection settings = ConfigurationManager.GetSection("AppDataSettings") as AppDataSection; public static AppDataSection Settings { get { return AppDataSection.settings; } } [ConfigurationProperty("fileName", IsRequired = true)] public string FileName { get { return this["fileName"] as string; } set { this["fileName"] = value; } }

234

[ConfigurationProperty("encodeType", IsRequired = true)] public string EncodeType { get { return this["encodeType"] as string; } } }

Az osztlyt a ConfigurationSection osztlybl szrmaztattuk, ennek az indexeljt hasznljuk. Itt arra kell figyelni, hogy az AppSettingsszel ellenttben object tpussal tr vissza, vagyis muszj konverzit vgrehajtani. A Settings property segtsgvel az osztlyon keresztl egyttal hozzfrnk az osztly egy pldnyhoz is, gy soha nem kell pldnyostanunk azt. A tulajdonsgok attribtumval belltottuk a konfig-fjlban szerepl nevet, illetve, hogy muszj megadnunk ezeket az rtkeket (a DefaultValue segtsgvel kezdrtket is megadhatunk). Most jn a konfigurcis llomny, itt regisztrlnunk kell az j szekcit:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="AppDataSettings" type="AppDataSection, main" /> </configSections> <AppDataSettings fileName="file.txt" encodeType="iso-8859-2" /> </configuration>

A type tulajdonsgnl meg kell adnunk a tpus teljes elrsi tjt nvtrrel egytt (ha van), illetve azt az assemblyt, amelyben a tpus szerepel ez most a projekt neve lesz. Most jjjn az osztly, amely hasznlja az j szekcit! Ksztsnk egy DataHandlerClass.cs nev fjlt is:
using using using using System; System.IO; System.Text; System.Configuration;

public class DataHandlerClass { public void PrintData() { Encoding enc = Encoding.GetEncoding( AppDataSection.Settings.EncodeType); string filename = AppDataSection.Settings.FileName; using (FileStream fs = new FileStream(filename, FileMode.Open)) { using (StreamReader sr = new StreamReader(fs, enc, false)) { string s = sr.ReadLine(); while (s != null) { Console.WriteLine(s); s = sr.ReadLine(); } }

235

} } }

Most mr knnyen hasznlhatjuk az osztlyt:


using System; using System.Configuration; class Program { static public void Main(string[] args) { DataHandlerClass handler = new DataHandlerClass(); handler.PrintData(); Console.ReadKey(); } }

236

HLZATI PROGRAMOZS
A .NET meglehetsen szles eszkztrral rendelkezik hlzati kommunikci kialaktshoz. A lista a legegyszerbb hobbiszint programokban hasznlhat szerver-kliens osztlyoktl egszen a legalacsonyabb szint osztlyokig terjed. Ebben a fejezetben ebbl a listbl szemezgetnk. A fejezethez tartoz forrskdok megtallhatak a jegyzethez tartoz Sources\Network knyvtrban.

SOCKET
A socketek szmtgpes hlzatok (pl. az Internet) kztti kommunikcis vgpontok. Minden socket rendelkezik kt adattal, amelyek ltal egyrtelmen azonosthatak s elrhetek: ezek az IP cm s a port szm. Mi az az IP cm? Az Internet az n. Internet Protocol (IP) szabvnya szerint mkdik. Eszerint a hlzaton lv minden szmtgp egyedi azonostval IP cmmel rendelkezik (ugyanakkor egy szmtgp tbb cmmel is rendelkezhet, ha tbb hlzati hardvert hasznl). Egy IP cm egy 32 bites egsz szm, amelyet 8 bites rszekre osztunk (pl.: 123.255.0.45), ezltal az egyes szekcik legmagasabb rtke 255 lesz (ez a jellemz az IP negyedik genercijra vonatkozik, az j hatos generci mr 128 bites cmeket hasznl, igaz ez egyelre kevsb elterjedt (a .NET tmogatja az IPv4 s IPv6 verzikat is)). Az IP cmet tekinthetjk az orszg/vros/utca/hzszm ngyesnek, mg a portszmmal egy szobaszmra hivatkozunk. A portszm egy 16 bites eljel nlkli szm 1 s 65535 kztt. A portszmokat ki lehet sajttani, vagyis ezltal biztostjk a nagyobb szoftvergyrtk, hogy ne legyen semmilyen tkzs a termk hasznlatakor. A portszmok hivatalos regisztrcijt az Internet Assigned Numbers Authority (IANA) vgzi. Az 1 s 1023 kztti portokat n. well-knowed portknt ismerjk, ezeken olyan szles krben elterjedt szolgltatsok futnak, amelyek a legtbb rendszeren megtalhatak (opercis rendszertl fggetlenl). Pl. a bngszk a HTTP protokollt a 80as porton rik el, a 23 a Telnet, mg a 25 az SMTP szerver portszma (ezektl el lehet s biztonsgi okokbl a nagyobb cgek el is szoktak trni, de a legtbb szmtgpen ezekkel az rtkekkel tallkozunk). Az 1024 s 49151 kztti portokat regisztrlt portoknak nevezik, ezeken mr olyan szolgltatsokat is felfedezhetnk amelyek opercis rendszerhez (is) ktttek pl. az 1433 a Microsoft SQL Server portja, ami rtelemszeren Windows rendszer alatt fut. Ugyanitt megtallunk szoftverhez kttt portot is, pl. a World of Warcraft a 3724 portot hasznlja. Ez az a tartomny amit az IANA kezel. Az e felettieket dinamikus vagy privt portoknak nevezik, ezt a tartomnyt nem lehet lefoglalni, programfejleszts alatt clszer ezeket hasznlni. Mieltt nekillunk a Socket osztly megismersnek, jtsszunk egy kicsit az IP cmekkel! Azt tudjuk mr, hogy a hlzaton minden szmtgp sajt cmmel rendelkezik, de szmokat viszonylag nehz megjegyezni, ezrt feltalltk a domain-nv vagy tartomnynv intzmnyt, amely rl egy adott IP cmre, vagyis ahelyett, hogy 65.55.21.250 rhatjuk azt, hogy www.microsoft.com. A kvetkez programunkban lekrdezzk egy domain-nv IP cmt s fordtva. Ehhez a System.Net nvtr osztlyai lesznek segtsgnkre:
using System; using System.Net; class Program { static public void Main(string[] args) { IPHostEntry host1 = Dns.GetHostEntry("www.microsoft.com"); foreach (IPAddress ip in host1.AddressList) { Console.WriteLine(ip.ToString()); } IPHostEntry host2 = Dns.GetHostEntry("91.120.22.150"); Console.WriteLine(host2.HostName);

237

Console.ReadKey(); } }

Most mr ideje nagyobb kihvs utn nzni, elksztjk az els szervernket. A legegyszerbb mdon fogjuk csinlni a beptett TcpListener osztllyal, amely a TCP/IP protokollt hasznlja (errl hamarosan rszletesebben). Nzzk meg a forrst:
using System; using System.Net; using System.Net.Sockets; class Program { static public void Main(string[] args) { IPAddress ip = IPAddress.Parse("127.0.0.1"); int port = 5000; IPEndPoint endPoint = new IPEndPoint(ip, port); TcpListener server = new TcpListener(endPoint); server.Start(); Console.WriteLine("A szerver elindult!"); Console.ReadKey(); } }

A 127.0.0.1 egy specilis cm, ez az n. localhost, vagyis a helyi cm, amely jelen esetben a sajt szmtgpnk (ehhez Internet kapcsolat sem szksges, mindig rendelkezsre ll). Ez az ip cm lesz az, ahol a szerver bejv kapcsolatokra fog vrni (rtelemszeren ehhez az IP cmhez csak a sajt szmtgpnkrl tudunk kapcsoldni, ha egy tvoli gpet is szeretnnk bevonni, akkor szksg lesz a valdi IP re, ezt pl. a parancssorba bert ipconfig paranccsal tudhatjuk meg). A szervert a Stop metdussal llthatjuk le.
using System; using System.Net.Sockets; using System.Net; namespace Server { class Program { static void Main(string[] args) { TcpListener server = null; try { IPAddress ipAddr = IPAddress.Parse("127.0.0.1"); int portNum = 5000; IPEndPoint endPoint = new IPEndPoint(ipAddr, portNum); server = new TcpListener(endPoint); server.Start(); Console.WriteLine("A szerver elindult!");

238

Console.ReadKey(); // vrunk bejv kapcsolatra } catch (Exception e) { Console.WriteLine(e.Message); } finally { server.Stop(); Console.WriteLine("A szerver lellt!"); } } } }

A TcpListener konstruktorban meg kell adnunk egy vgpontot a szervernek, ahol figyelheti a bejv kapcsolatokat, ehhez szksgnk lesz egy IP cmre s egy portszmra. Elbbit az IPAddress.Parse statikus metdussal egy karaktersorozatbl nyertk ki. A Console.ReadKey azrt kell, mert klnben ugrannk is tovbb a finally gra, ahol lezrjuk a szervert. A kvetkez lpsben bejv kapcsolatot fogadunk, ehhez viszont szksgnk lesz egy kliensre is, t a TcpClient osztllyal ksztjk el, amely hasonlan mkdik, mint a prja, szintn egy cm-port kettsre lesz szksgnk (de most nem kell vgpontot definilnunk):
using System; using System.Net; using System.Net.Sockets; class Program { static public void Main(string[] args) { TcpClient client = null; try { client = new TcpClient("127.0.0.1", 5000); Console.WriteLine(client.Connected); // true } catch (Exception e) { Console.WriteLine(e.Message); } finally { client.Close(); } Console.ReadKey(); } }

A TcpClient-nek vagy a konstruktorban rgtn megadjuk az elrni kvnt szerver nevt s portjt (s ekkor azonnal csatlakozik is), vagy meghvjuk az alaprtelmezett konstruktort, s a Connect metdussal elhalasztjuk a kapcsoldst egy ksbbi idpontra (termszetesen ekkor neki is meg kell adni a szerver adatait). Ahhoz, hogy a szervert s klienst is egyszerre elindthassuk anlkl, hogy kt Visual Studio-t nyitnnk, elszr ksztsnk egy kln projektet a kliens vagy szerver szmra! Ezutn kattintsunk jobb gombbal a Solution-n

239

(nem a projekten), s vlasszuk a Set StartUp Projects menpontot! Jelljk be a Multiple StartUp Projects kapcsolt s a kvnt projektek Action oszlopban lltsuk a megadott rtket Start-ra! Figyeljnk arra, hogy a szervert tartalmaz projekt legyen ell! Most mr elindthatjuk a programjainkat, s a kliens ablakban a True feliratot fogjuk ltni, hiszen a Connected tulajdonsg azt mondja meg, hogy van-e aktv kapcsolat vagy sem. Ahhoz, hogy a szerver kezelni is tudja a kliensnket (eddig csak kapcsolatot ltestettnk), meg kell mondanunk neki, hogy vrjon, amg bejv kapcsolat rkezik. Ezt a TcpListener osztly AcceptTcpClient metdusval tehetjk meg:
TcpClient client = server.AcceptTcpClient();

Persze szerveroldalon egy TcpClient objektumra lesz szksgnk, ezt adja vissza a metdus. A programunk jl mkdik, de nem csinl tl sok mindent. A kvetkez lpsben adatokat kldnk a szerverrl a kliensnek. Nzzk az j szervert:
using using using using System; System.Text; System.Net.Sockets; System.Net;

namespace Server { class Program { static void Main(string[] args) { TcpListener server = null; NetworkStream stream = null; try { IPAddress ipAddr = IPAddress.Parse("127.0.0.1"); int portNum = 5000; IPEndPoint endPoint = new IPEndPoint(ipAddr, portNum); server = new TcpListener(endPoint); server.Start(); Console.WriteLine("A szerver elindult!"); TcpClient client = server.AcceptTcpClient(); byte[] data = Encoding.ASCII.GetBytes("Hello kliens..."); stream = client.GetStream(); stream.Write(data, 0, data.Length); } catch (Exception e) { Console.WriteLine(e.Message); } finally { stream.Close(); server.Stop(); } } } }

240

Az elkldeni kvnt zenetet bytetmb formjban kell tovbbtanunk, mivel a TCP/IP byet-ok sorozatt tovbbtja, ezrt szmra emszthet formba kell hoznunk az zenetet. A fenti pldban a legegyszerbb, ASCII kdolst vlasztottuk, de mst is hasznlhatunk; a lnyeg, hogy tudjuk byte-onknt kldeni (rtelemszeren mind a kliens, mind a szerver ugyanazt a kdolst kell hasznlja). A NetworkStream ugyanolyan adatfolyam, amit a fjlkezelssel foglalkoz fejezetben megismertnk, csak ppen az adatok most a hlzaton keresztl folynak t. A Write metdus hrom paramtere kzl az els a kldeni kvnt adat (byte-tmb formban), utna a kezdpozici (ebben azesetben az elejtl kezdve runk, vagyis nulla), majd a hossz kvetkezik. A szerver az zenet elkldse utn lell, a program pedig befejezi futst. Fontos, hogy az erforrsokat megfelel sorrendben zrjuk le, elszr a streamet s csak utna kvetkezik maga szerver. Lssuk a kliens oldalt:
using using using using System; System.Net; System.Net.Sockets; System.Text;

class Program { static public void Main(string[] args) { TcpClient client = null; NetworkStream stream = null; try { client = new TcpClient("127.0.0.1", 5000); byte[] data = new byte[128]; stream = client.GetStream(); int length = stream.Read(data, 0, data.Length); Console.WriteLine("A szerver zenete: {0}", Encoding.ASCII.GetString(data)); } catch (Exception e) { Console.WriteLine(e.Message); } finally { stream.Close(); client.Close(); } Console.ReadKey(); } }

Az alapelv ugyanaz, egyedli jdonsg a byte-tmb inicializlsa. Most nem tudjuk pontosan, hogy milyen hossz adatot kapunk a szervertl, ezrt tippelnk (igazbl tudjuk, de ez most nem szmt). Ha nincs elg hely akkor nem kapunk kivtelt, de a bejv adatbl csak annyit ltunk viszont, amennyi a tmbben elfr. A NetworkStream Read metdusa hasonlan mkdik a Write-hoz, viszont visszaadja a bejv zenet hosszt (byte-ban). Mr ismerjk az alapokat, eljtt az ideje, hogy egy szinttel lejjebb lpjnk a socketek vilgba.

241

using using using using

System; System.Text; System.Net.Sockets; System.Net;

namespace Server { class Program { static void Main(string[] args) { Socket server = null; Socket client = null; try { server = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint endPoint = new IPEndPoint( IPAddress.Parse("127.0.0.1"), 5000); server.Bind(endPoint); server.Listen(2); client = server.Accept(); byte[] data = new byte[256]; int length = client.Receive(data); Console.WriteLine("A kliens zenete: {0}", Encoding.ASCII.GetString(data, 0, length)); data = new byte[256]; data = Encoding.ASCII.GetBytes("Hello kliens!"); client.Send(data, data.Length, SocketFlags.None); } catch (Exception e) { Console.WriteLine(e.Message); } finally { client.Close(); server.Close(); } Console.ReadKey(); } } }

A Socket osztly konstruktorban megadtuk a cmzsi mdot (InterNetwork, ez az IPv4), a socket tpust (Stream, ez egy oda-vissza kommunikcit biztost kapcsolat lesz) s a hasznlt protokollt, ami jelen esetben TCP. A hrom paramter nem fggetlen egymstl, pl. Stream tpus socketet csak TCP portokoll felett hasznlhatunk.

242

Ezutn ksztettnk egy IPEndPoint objektumot, ahogyan azt az egyszerbb vltozatban is tettk. Ezt a vgpontot a Bind metdussal ktttk a sockethez, majd a Listen metdussal megmondtuk, hogy figyelje a bejv kapcsolatokat. Ez utbbi paramtere azt jelzi, hogy maximum hny bejv kapcsolat vrakozhat. Innentl kezdve nagyon ismers a forrskd, lnyegben ugyanazt tesszk, mint eddig, csak pp ms a metdus neve. A kliens osztly sem okozhat nagy meglepetst:
using using using using System; System.Net; System.Net.Sockets; System.Text;

class Program { static public void Main(string[] args) { Socket client = null; try { client = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint endPoint = new IPEndPoint( IPAddress.Parse("127.0.0.1"), 5000); ; client.Connect(endPoint); byte[] data = new byte[256]; data = Encoding.ASCII.GetBytes("Hello szerver!"); client.Send(data, data.Length, SocketFlags.None); data = new byte[256]; int length = client.Receive(data); Console.WriteLine("A szerver zenete: {0}", Encoding.ASCII.GetString(data, 0, length)); } catch (Exception e) { Console.WriteLine(e.Message); } finally { client.Close(); } Console.ReadKey(); } }

A klnbsget a Connect metdus jelenti, mivel most kapcsoldni akarunk, nem hallgatzni.

243

BLOKK ELKERLSE
Az eddigi programjaink mind megegyeztek abban, hogy bizonyos mveletek blokkoltk a fszlat, s gy vrakozni knyszerltnk. Ilyen mvelet volt pl. az Accept/AcceptTcpClient, de a Read/Receive is. A blokk elkerlsre n. elre-ellenrzst (prechecking) fogunk alkalmazni, vagyis megvizsgljuk, hogy adott idpillanatban van-e bejv adat, vagy rrnk ksbb jraellenrizni, addig pedig csinlhatunk mst. A TcpListerner/TcpClient osztlyok a rjuk csatlakoztatott NetworkStream objektum DataAvailable tulajdonsgn keresztl tudjk vizsglni, hogy jn-e adat vagy sem. A kvetkez pldban a kliens rendszeres idkznknt ellenrzi, hogy rkezett-e vlasz a szervertl, s ha az eredmny negatv, akkor foglalkozhat a dolgval:
bool l = false; while (!l) { if (stream.DataAvailable) { data = new byte[256]; int length = stream.Read(data, 0, data.Length); Console.WriteLine("A szerver zenete: {0}", Encoding.ASCII.GetString(data, 0, length)); l = true; } else { Console.WriteLine("A szerver mg nem kldtt vlaszt!"); System.Threading.Thread.Sleep(200); } }

A ciklus egszen addig fut, amg nincs bejv kapcsolat, ezt az l vltozval jelezzk. Ugyanezt a hatst socketek esetben a Socket osztly Available tulajdonsgval rhetjk el, amely jelzi, hogy van-e mg vrakoz adat a csatornn (egszen pontosan azoknak a byte-oknak a szmt adja vissza, amelyeket mg nem olvastunk be):
while (true) { if (client.Available > 0) { int length = client.Receive(data); Console.WriteLine("A szerver zenete: {0}", Encoding.ASCII.GetString(data, 0, length)); break; } else { Console.WriteLine("Vrunk a szerver vlaszra..."); System.Threading.Thread.Sleep(200); } }

Itt egy msik mdszert, egy vgtelen ciklust vlasztottunk a ciklus kezelsre, amikor rkezik bejv adat, a break utastssal kitrnk belle. A vrakozs idszakban pedig 200 ezredmsodpercre meglltjuk a szlat. Most nzzk meg, hogy mi a helyzet szerveroldalon! Itt a tipikus problma az, hogy az AcceptTcpClient/Accept teljesen blokkol, amg bejv kapcsolatra vrakozunk. Erre is van persze megolds, a TcpListener esetben ezt a Pending , mg a Socket osztlynl a Poll metdus jelenti.

244

Nzzk elsknt a TcpListener -t:


while (true) { if (server.Pending()) { client = server.AcceptTcpClient(); Console.WriteLine("Kliens kapcsoldott..."); stream = client.GetStream(); byte[] data = new byte[256]; int length = stream.Read(data, 0, data.Length); Console.WriteLine("A kliens zenete: {0}", Encoding.ASCII.GetString(data, 0, length)); data = Encoding.ASCII.GetBytes("Hello kliens!"); stream.Write(data, 0, data.Length); } else { Console.WriteLine("Most valami mst csinlunk"); System.Threading.Thread.Sleep(500); } }

A Pending azt az informcit osztja meg velnk, hogy vrakozik-e bejv kapcsolat. Tulajdonkppen ez a metdus a kvetkez forrsban szerepl (a Socket osztlyhoz tartoz) Poll metdust hasznlja:
while (true) { if (server.Poll(0, SelectMode.SelectRead)) { client = server.Accept(); /* itt pedig kommuniklunk a klienssel */ } else { Console.WriteLine("A szerver bejv kapcsolatra vr!"); System.Threading.Thread.Sleep(500); } }

A Poll metdus els paramtere egy egsz szm, amely mikromsodpercben (nem milli-, itt valban a msodperc milliomod rszrl van sz, vagyis ha egy msodpercig akarunk vrni, akkor 1000000ot kell megadnunk) adja meg azt az idt, amg vrunk bejv kapcsolatra/adatra. Amennyiben az els paramter negatv szm, akkor addig vrunk, amg nincs kapcsolat (vagyis blokkoljuk a programot), ha pedig nullt adunk meg, akkor hasznlhatjuk prechecking-re is a metdust. A msodik paramterrel azt mondjuk meg, hogy mire vrunk. A SelectMode felsorolt tpus hrom taggal rendelkezik: SelectRead: a metdus igaz rtkkel tr vissza, ha meghvtuk a Listen metdust, s vrakozik bejv kapcsolat, vagy van bejv adat, illetve ha a kapcsolat megsznt; minden ms esetben a visszatrsi rtk false lesz. SelectWrite: igaz rtket kapunk vissza, ha a Connect metdus hvsa sikeres volt, azaz csatlakoztunk a tvoli llomshoz, illetve ha lehetsges adat kldse. SelectError: true rtket ad vissza, ha a Connect metdus hvsa sikertelen volt.

245

Egy msik lehetsg a blokk feloldsra, ha a Socket objektum Blocking tulajdonsgt false rtkre lltjuk. Ekkor a Socket osztly Receive s Send metdusainak megadhatunk egy SocketError tpus (out) paramtert, amely WouldBlock rtkkel tr vissza, ha a metdushvs blokkot okozna (vagyis gy jra prblhatjuk kldeni/fogadni ksbb az adatokat). Azt azonban nem rt tudni, hogy ez s a fenti Poll metdust hasznl megoldsok nem hatkonyak, mivel folyamatos metdushvsokat kell vgrehajtanunk (vagy a httrben a rendszernek). A kvetkez fejezetben tbb kliens egyidej kezelsvel sokkal hatkonyabb mdszer(eke)t fogunk megvizsglni.

TBB KLIENS KEZELSE


Az eddigi programjainkban csak egy klienst kezeltnk, ami nagyon knyelmes volt, mivel egy sor dolgot figyelmen kvl hagyhattunk: A kliensekre mutat referencia trolsa. A szerver akkor is tudjon kapcsolatot fogadni, amg a bejelentkezett kliensekkel foglalkozunk. Minden kliens zavartalanul (lehetleg blokk nlkl) tudjon kommuniklni a szerverrel s viszont.

A kvetkezkben hromflekppen fogjuk krljrni a problmt. Select Az els versenyznk a Socket osztly Select metdusa lesz, amelynek segtsgvel meghatrozhatjuk egy vagy tbb Socket pldny llapott. Lnyegben arrl van sz, hogy egy listbl kivlaszthatjuk azokat az elemeket, amelyek megfelelnek bizonyos kvetelmnyeknek (rhatak, olvashatak). A Select (statikus) metdus szignatrja a kvetkezkppen nz ki:
static public void Select( IList checkRead, IList checkWrite, IList checkError, int microSeconds )

Az els hrom paramter olyan IList interfszt implementl gyjtemnyek (lnyegben az sszes beptett gyjtemny ilyen, belertve a sima tmbket is), amelyek Socket pldnyokat tartalmaznak. Az els paramternek megadott listt olvashatsgra, a msodikat rhatsgra, mg a harmadikat hibkra ellenrzi a Select, majd a felttelnek megfelel listaelemeket megtartja a listban, a tbbit eltvoltja (vagyis ha szksgnk van a tbbi elemre is, akkor clszer egy msolatot hasznlni az eredeti lista helyett). Az utols paramterrel azt adjuk meg, hogy mennyi ideig vrjunk a kliensek vlaszra (mikroszekundum). Ksztsnk egy Selectet hasznl kliens-szerver alkalmazst! A kliens oldalon lnyegben semmit nem vltoztatunk azt leszmtva, hogy folyamatosan zenetet kldnk a szervernek (most csak egyirny lesz a kommunikci):
Random r = new Random(); while (true) { if (r.Next(1000) % 2 == 0) { byte[] data = new byte[256]; data = Encoding.ASCII.GetBytes("Hello szerver!"); client.Send(data, data.Length, SocketFlags.None); } System.Threading.Thread.Sleep(500); }

246

Most nzzk a szervert:


int i = 0; while (i < MAXCLIENT) { Socket client = server.Accept(); clientList.Add(client); ++i; } while (true) { ArrayList copyList = new ArrayList(clientList); Socket.Select(copyList, null, null, 1000); foreach (Socket client in clientList) { Program.CommunicateWithClient(client); } }

A MAXCLIENT vltoz egy egyszer egsz szm, meghatrozzuk vele, hogy maximum hny klienst fogunk kezelni. Miutn megfelel szm kapcsolatot hoztunk ltre, elkezdnk beszlgetni a kliensekkel. A ciklus minden itercijban meghvja a Select metdust, vagyis kivlasztjuk, hogy melyik kliensnek van mondandja. A CommunicateWithClient statikus metdus a mr ismert mdon olvassa a kliens zenett:
static public void CommunicateWithClient(Socket client) { byte[] data = new byte[256]; int length = client.Receive(data); Console.WriteLine("A kliens zenete: {0}", Encoding.ASCII.GetString(data, 0, length)); }

Aszinkron socketek Kliensek szinkron kezelsre is felhasznlhatjuk a Socket osztlyt, lnyegben az aszinkron delegate-ekre pl ez a megolds. Az aszinkron hvhat metdusok Begin s End eltagot kapnak, pl. kapcsolat elfogadsra most a BeginAccept metdust fogjuk hasznlni. A kliens ezttal is vltozatlan, nzzk a szerver oldalt:
while (true) { done.Reset(); Console.WriteLine("A szerver kapcsolatra vr..."); server.BeginAccept(Program.AcceptCallback, server); done.WaitOne(); }

A done vltoz a ManualResetEvent osztly egy pldnya, segtsgvel szablyozni tudjuk a szlakat. A Reset metdus alaphelyzetbe lltja az objektumot, mg a WaitOne meglltja (blokkolja) az aktulis szlat, amg egy jelzst (a Set metdussal) nem kap. A BeginAccept aszinkron metdus els paramtere az a metdus lesz, amely a kapcsolat fogadst vgzi, msodik paramternek pedig tadjuk a szervert reprezentl Socket objektumot. Teht: meghvjuk a BeginAccept-et, ezutn pedig vrunk, hogy az AcceptCallback metdus visszajelezzen a fszlnak, hogy a kliens csatlakozott, s folytathatja a figyelst. Nzzk az AcceptCallback-ot:

247

static public void AcceptCallback(IAsyncResult ar) { Socket client = ((Socket)ar.AsyncState).EndAccept(ar); done.Set(); Console.WriteLine("Kliens kapcsoldott..."); StringState state = new StringState(); state.Client = client; client.BeginReceive(state.Buffer, 0, State.BufferSize, SocketFlags.None, Program.ReadCallback, state); }

A kliens fogadsa utn meghvtuk a ManualResetEvent Set metdust, ezzel jeleztk, hogy ksz vagyunk, vrhatjuk a kvetkez klienst (lthat, hogy csak addig kell blokkolnunk, amg hozz nem jutunk a klienshez, az adatcsere nyugodtan elfut a httrben). A StringState osztlyt knyelmi okokbl mi magunk ksztettk el, ezt fogjuk tadni a BeginReceive metdusnak:
class State { public const int BufferSize = 256; public State() { Buffer = new byte[BufferSize]; } public Socket Client { get; set; } public byte[] Buffer { get; set; } } class StringState : State { public StringState() : base() { Data = new StringBuilder(); } public StringBuilder Data { get; set; } }

Vgl nzzk a BeginReceive callback metdust:


static public void ReadCallback(IAsyncResult ar) { StringState state = (StringState)ar.AsyncState; int length = state.Client.EndReceive(ar); state.Data.Append(Encoding.ASCII.GetString(state.Buffer, 0, length)); Console.WriteLine("A kliens zenete: {0}", state.Data); state.Client.Close(); }

A fordtott irny adatcsere ugyangy zajlik, a megfelel metdusok Begin/End eltag vltozataival.

248

Szlakkal megvalstott szerver Ez a mdszer nagyon hasonl az aszinkron vltozathoz, azzal a klnbsggel, hogy most manulisan hozzuk ltre a szlakat, illetve nemcsak a beptett aszinkron metdusokra tmaszkodhatunk, hanem tetszs szerinti mveletet hajthatunk vgre a httrben. A kvetkez programunk egy szmkitalls jtkot fog megvalstani gy, hogy a szerver gondol egy szmra s a kliensek ezt megprbljk kitallni. Minden kliens tszr tallgathat, ha nem sikerl kitallnia, akkor elveszti a jtkot. Mindent tipp utn a szerver visszakld egy szmot a kliensnek: -1-et, ha a gondolt szm nagyobb, 1et, ha a szm kisebb, 0-t, ha eltallta a szmot s 2-t, ha valaki mr kitallta a szmot. Ha egy kliens kitallta a szmot, akkor a szerver megvrja, mg minden kliens kilp, s j szmot sorsol. Ksztettnk egy osztlyt, amely megknnyti a dolgunkat. A Listen metdussal indtjuk el a szervert:
public void Listen() { if (EndPoint == null) { throw new Exception("IPEndPoint missing"); } server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); server.Bind(EndPoint); server.Listen(5); ThreadPool.SetMaxThreads(MaxClient, 0); NewTurnEvent += NewTurnHandler; Console.WriteLine("A szerver elindult, a szm: {0}", NUMBER); Socket client = null; while (true) { client = server.Accept(); Console.WriteLine("Kliens bejelentkezett, a kvetkez IP cmrl: {0}", ((IPEndPoint)client.RemoteEndPoint).Address.ToString()); ThreadPool.QueueUserWorkItem(ClientHandler, client); } }

A kliensek kezelshez a ThreadPool osztlyt fogjuk hasznlni, amelynek segtsgvel a kliens objektumokat tadjuk a ClientHandler metdusnak:
private void ClientHandler(object socket) { Socket client = null; string name = String.Empty; int result = 0; try { client = socket as Socket; if (client == null) { throw new ArgumentException(); }

249

++ClientNumber; byte[] data = new byte[7]; int length = client.Receive(data); name = Encoding.ASCII.GetString(data, 0, length); Console.WriteLine("j jtkos: {0}", name); int i = 0; bool win = while (i < { data = length

false; 5 && win == false) new byte[128]; = client.Receive(data);

GuessNumber(name, int.Parse(Encoding.ASCII.GetString(data, 0, length)), out result); data = Encoding.ASCII.GetBytes(result.ToString()); client.Send(data, data.Length, SocketFlags.None); if (result == 0) { win = true; } ++i; } } catch (Exception e) { Console.WriteLine(e.Message); } finally { client.Close(); if (--ClientNumber == 0 && result == 0) { NewTurnEvent(this, null); } } }

Minden kliens rendelkezik nvvel is, amely 7 karakter hossz (7 byte) lehet, elsknt ezt olvassuk be. A GuessNumber metdusnak adjuk t a tippnket s a result vltozt out paramterknt. Vgl a finally blokkban ellenrizzk, hogy van-e bejelentkezett kliens, ha pedig nincs, akkor j szmot sorsolunk. Nzzk a GuessNumber metdust:
private void GuessNumber(string name, int number, out int result) { lock (locker) { if (NUMBER != -1) { Console.WriteLine("{0} szerint a szm: {1}", name, number); if (NUMBER == number) { Console.WriteLine("{0} kitallta a szmot!", name); result = 0; NUMBER = -1; }

250

else if (NUMBER < number) { result = 1; } else { result = -1; } } else result = 2; } Thread.Sleep(300); }

A metdus trzst le kell zrnunk, hogy egyszerre csak egy szl (egy kliens) tudjon hozzfrni. Ha valamelyik kliens kitallta a szmot, akkor annak -1et adunk vissza, gy gyorsan ellenrizhetjk, hogy a felttel melyik gba kell bemennnk. Kliens oldalon sokkal egyszerbb dolgunk van, ezt a forrskdot itt nem rszletezzk, de megtallhat a jegyzethez tartoz forrsok kztt.

TCP S UDP
Ez a fejezet elsdlegesen a TCP protokollt hasznlta, de emltst kell tennnk az Internet msik alapprotokolljrl, az UDP-rl is. A TCP megbzhat, de egyttal kicsit lassabb mdszer. A csomagokat sorszmmal ltja el, ez alapjn pedig a fogad fl nyugtt kld, hogy az adott csomag rendben megrkezett. Amennyiben adott idn bell a nyugta nem rkezik meg, akkor a csomagot jra elkldi. Ezenkvl ellenrzi, hogy a csomagok srlsmentesek legyenek, illetve kiszri a dupln elkldtt (redundns) adatokat is. Az UDP pp az ellenkezje, nem biztostja, hogy minden csomag megrkezik, cserben gyors lesz. Jellemzen olyan helyeken hasznljk, ahol a gyorsasg szmt, s nem nagy problma, ha egy-kt csomag elveszik, pl. vals idej mdia lejtszsnl illetve jtkoknl. A .NET a TCPhez hasonlan biztostja szmunkra az UdpListener/Client osztlyokat, ezek kezelse gyakorlatilag megegyezik TCP t hasznl prjaikkal, ezrt itt most nem rszletezzk. Hagyomnyos Socketek esetn is elrhet ez a protokoll, ekkor a Socket osztly konstruktora gy fog kinzni:
Socket server = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

Ezutn ugyangy hasznlhatjuk ezt az objektumot, mint a TCP-t alkalmaz trst.

251

LINQ TO OBJECTS
A C# 3.0 bevezette a LINQ t (Language Integrated Query), amely lehetv teszi, hogy knnyen, uniformizlt ton kezeljk a klnbz adatforrsokat, vagyis pontosan ugyangy fogunk kezelni egy adatbzist, mint egy memriban lv gyjtemnyt. Mirt j ez neknk? Napjainkban rengeteg adatforrs ll rendelkezsnkre, ezek kezelshez pedig j eszkzk hasznlatt illetve j nyelveket kell megtanulnunk (SQL a relcis adatbzisokhoz, XQuery az XML-hez stb.).A LINQ lehetv teszi, hogy egy plusz rteg (a LINQ fellet) bevezetsvel mindezeket thidaljuk, mghozz teljes mrtkben fggetlenl az adatforrstl. Egy msik elnye pedig, hogy a LINQ lekrdezsek ersen tpusosak, vagyis a legtbb hibt mg fordtsi idben el tudjuk kapni s kijavtani. A LINQ csald jelenleg hrom fcsapst jellt ki, ezek a kvetkezek: LINQ To XML: XML dokumentumok lekrdezst s szerkesztst teszi lehetv. LINQ To SQL (vagy DLINQ) s LINQ To Entities (Entity Framework): relcis adatbzisokon (elssorban MS SQL-Server) vgezhetnk mveleteket velk. A kett kzl a LINQ To Entites a fnk, a LINQ To SQL inkbb csak technolgiai demnak kszlt, a Microsoft nem fejleszti tovbb (de tovbbra is elrhet marad, mivel kisebb projectekre illetve hobbifejlesztsekhez kivl). Az Entity Framework hasznlathoz a Visual Studio 2008 els szervizcsomagjra van szksg. LINQ To Objects: ennek a fejezetnek a trgya, memriban lv gyjtemnyek, listk, tmbk feldolgozst teszi lehetv (lnyegben minden olyan osztllyal mkdik, amely megvalstja az IEnumerable<T> interfszt).

A fentieken kvl szmos third-party/hobbi project ltezik, mivel a LINQ framework viszonylag knnyen kiegszthet tetszs szerinti adatforrs hasznlathoz. A fejezethez tartoz forrskdok megtallhatak a Sources\LINQ knyvtrban.

NYELVI ESZKZK
A C# 3.0ban megjelent nhny jdonsg rszben a LINQ miatt kerlt a nyelvbe, jelenltk jelentsen megknnyti a dolgunkat. Ezek a kvetkezk: Kiterjesztett metdusok (extension method): velk mr korbban megismerkedtnk, a LINQ To Objects teljes funkcionalitsa ezekre pl, lnyegben az sszes mvelet az IEnumerable<T>/IEnumerable interfszeket egszti ki. Objektum s gyjtemny inicializlk: vagyis a lehetsg, hogy az objektum deklarlsval egy idben bellthassuk a tulajdonsgaikat, illetve gyjtemnyek esetben az elemeket:
using System; using System.Collections.Generic; namespace Server { class Program { static void Main(string[] args) { /*Objektum inicializl*/ MyObject mo = new MyObject() { Property1 = "value1", Property2 = "value2", }; /*Gyjtemny inicializl*/

252

List<string> list = new List<string>() { "alma", "krte", "di", "kakukktojs" }; } } }

Lambda kifejezsek: a lekrdezsek tbbsgnl nagyon knyelmes lesz a lambdk hasznlata, ugyanakkor lehetsg van a hagyomnyos nvtelen metdusok felhasznlsra is. A var: a lekrdezsek egy rsznl rendkvl nehz elre megadni az eredmnylista tpust, ezrt ilyenkor a var-t fogjuk hasznlni, bzzuk magunkat a fordtra. Nvtelen tpusok: sok esetben nincs szksgnk egy objektum minden adatra, ilyenkor feleslegesen foglaln egy lekrdezs eredmnye a memrit. A nvtelen tpusok bevezetse lehetv teszi, hogy helyben deklarljunk egy nvtelen osztlyt:
using System; namespace Server { class Program { static void Main(string[] args) { var type1 = new { Value1 = "alma", Value2 = "krte" }; Console.WriteLine(type1.Value1); //alma Console.ReadKey(); } } }

KIVLASZTS
A legegyszerbb dolog, amit egy listval tehetnk, hogy egy vagy tbb elemt valamilyen kritrium alapjn kivlasztjuk. A kvetkez forrskdban pp ezt fogjuk tenni, egy egsz szmokat tartalmaz List<T> adatszerkezetbl az sszes szmot lekrdezzk. Termszetesen ennek gy sok rtelme nincsen, de szrsrl majd csak a kvetkez fejezetben fogunk tanulni.
using System; using System.Collections.Generic; using System.Linq; namespace Server { class Program { static void Main(string[] args) { List<int> list = new List<int>() { 10, 2, 4, 55, 22, 75, 30, 11, 12, 89 }; var result = from number in list select number;

253

foreach (var item in result) { Console.WriteLine("{0}", item); } Console.ReadKey(); } } }

Elssorban vegyk szre az j nvteret, a System.Linq-t. R lesz szksgnk mostantl az sszes lekrdezst tartalmaz programban, teht ne felejtsk le! Nem mintha lenne vlasztsunk, hiszen ebben az esetben le sem fordulna a program. Most pedig jjjn a lnyeg, nzzk a kvetkez sort:
var result = from number in list select number;

Egy LINQ lekrdezs a legegyszerbb formjban a kvetkez sablonnal rhat le: eredmny = from azonost in kifejezs select kifejezs A lekrdezs els fejben meghatrozzuk az adatforrst: from azonost in kifejezs Egszen pontosan a kifejezs jelli a forrst, mg az azonost a forrs egyes tagjait adja meg a kivlaszts minden itercijban, lnyegben pontosan ugyangy mkdik, mint a foreach ciklus: foreach(var azonost in kifejezs) Vagyis a lekrdezs alatt a forrs minden egyes elemvel tehetnk, amit jlesik. Ezt a valamit a lekrdezs msodik fele tartalmazza: select kifejezs A fenti pldban egyszeren vissza akarjuk kapni a szmokat eredeti formjukban, de akr ezt is rhattuk volna:
var result = from number in list select (number + 10);

Azaz minden szmhoz hozzadunk tzet. Az SQL t ismerk szmra furcsa lehet, hogy elszr az adatforrst hatrozzuk meg, de ennek megvan az oka, mgpedig az, hogy ilyen mdon a fejleszteszkz (a Visual Studio) tmogatst illetve tpusellenrzst adhat a select utni kifejezshez (illetve a lekrdezs tbbi rszhez). A fenti kdban SQL-szer lekrdezst ksztettnk (n. Query Expression Format), de mshogyan is megfogalmazhattuk volna a mondanivalnkat. Emlkezznk: minden LINQ To Objects mvelet egyben kiterjesztett metdus, vagyis ezt is rhattuk volna:
var result = list.Select(number => number);

Pontosan ugyanazt rtk el, s a fordts utn pontosan ugyanazt a kifejezst is fogja hasznlni a program, mindssze a szintaxis ms (ez pedig az Extension Method Format; a fordt is ezt a formtumot tudja rtelmezni, teht a Query Syntax is erre alakul t). A Select az adatforrs elemeinek tpust hasznl Func<T, V> generikus delegateet kap paramterl, jelen esetben ezt egy lambda kifejezssel helyettestettk, de rhattuk volna a kvetkezket is:

254

var result1 = list.Select( delegate(int number) { return number; }); Func<int, int> selector = (x) => x; var result2 = list.Select(selector);

A kt szintaxist keverhetjk is (Query Dot Format), de ez az olvashatsg rovsra mehet, ezrt nem ajnlott (leszmtva olyan eseteket, amikor egy mvelet csak az egyik formval hasznlhat). Ha csak lehet, ragaszkodjunk csak az egyikhez! A kvetkez pldban a Distinct metdust hasznljuk, amely a lekrdezs eredmnybl eltvoltja a dupliklt elemeket. t csakis Extension Method formban hvhatjuk meg:
using System; using System.Collections.Generic; using System.Linq; namespace Server { class Program { static void Main(string[] args) { List<int> list = new List<int>() { 1, 1, 3, 5, 6, 6, 10, 11, 1 }; var result = (from number in list select number).Distinct(); foreach (var item in result) { Console.WriteLine("{0}", item); } Console.ReadKey(); } } }

A jegyzet ezutn mindkt vltozatot bemutatja a forrskdokban. Projekci Vegyk a kvetkez egyszer osztly-hierarchit:
class Address { public string Country { get; set; } public int PostalCode { get; set; } public int State { get; set; } public string City { get; set; } public string Street { get; set; } } class Customer

255

{ public public public public public public } int ID { get; set; } string FirstName { get; set; } string LastName { get; set; } string Email { get; set; } string PhoneNumber { get; set; } Address Address { get; set; }

Minden vsrlhoz tartozik egy Address objektum, amely a vev cmt trolja. Tegyk fel, hogy egy olyan lekrdezst akarok rni, amely visszaadja az sszes vev nevt, e-mail cmt s telefonszmt. Ez nem bonyolult dolog, a kd a kvetkez lesz:
var result = from customer in custList select customer; foreach(var customer in result) { Console.WriteLine("Nv: {0}, Email: {1}, Telefon: {2}", customer.FirstName + + customer.LastName, customer.Email, Customer.PhoneNumber); }

A problma a fenti kddal, hogy a szksges adatokon kvl megkaptuk az egsz objektumot, belertve a cm pldnyt is, amelyre pedig semmi szksgnk nincsen. A megolds, hogy az eredeti eredmnyhalmazt leszktjk gy, hogy a lekrdezsben ksztnk egy nvtelen osztlyt, amely csak a krt adatokat tartalmazza. Ezt projekcinak nevezzk. Az j kd:
var result = from customer in custList select new { Name = customer.FirstName + Customer.LastName, Email = customer.Email, Phone = customer.PhoneNumber };

Termszetesen nem csak nvtelen osztlyokkal dolgozhatunk ilyen esetekben, hanem kszthetnk specializlt direkt erre a clra szolgl osztlyokat is. A lekrdezs eredmnynek tpust eddig nem jelltk, helyette a var kulcsszt hasznltuk, mivel ez rvidebb. Minden olyan lekrdezs, amelytl egynl tbb eredmnyt vrunk (teht nem azok az opertorok, amelyek pontosan egy elemmel trnek vissza errl ksbb rszletesebben), IEnumerable<T> tpus eredmnyt (vagy annak leszrmazott, specializlt vltozatt) ad vissza. Let A let segtsgvel a lekrdezs hatkrn belli vltozkat hozhatunk ltre, amelyek segtsgvel elkerlhetjk egy kifejezs ismtelt felhasznlst. Nzznk egy pldt:
string[] poem = new string[] { "Ej mi a k! tykany, kend", "A szobban lakik itt bent?", "Lm, csak j az isten, jt d,", "Hogy flvitte a kend dolgt!" }; var result = from line in poem let words = line.Split(' ') from word in words

256

select word;

A let segtsgvel minden sorbl egy jabb string-tmbt ksztettnk, amelyeken egy bels lekrdezst futtattunk le (teht az eredmnylistban az eredeti stringek szavai lesznek).

SZRS
Nyilvn nincs szksgnk mindig az sszes elemre, ezrt kpesnek kell lennnk szrni az eredmnylistt. A legalapvetbb ilyen mvelet a where, amelyet a kvetkez sablonnal rhatunk le: from azonost in kifejezs where kifejezs select azonost Nzznk egy egyszer pldt:
using System; using System.Collections.Generic; using System.Linq; namespace Server { class Program { static void Main(string[] args) { List<int> list = new List<int>() { 12, 4, 56, 72, 34, 0, 89, 22 }; var result1 = from number in list where number > 30 select number; var result2 = list.Where(number => number > 30); var result3 = (from number in list select number) .Where(number => number > 30); foreach (var item in result1) { Console.WriteLine(item); } Console.ReadKey(); } } }

A forrsban mindhrom lekrdezs szintaxist lthatjuk, mind pontosan ugyanazt fogja visszaadni, s teljesen szablyosak. A where egy paramterrel rendelkez, bool visszatrsi rtk metdust (anonim metdust, lambda kifejezst stb.) vr paramtereknt:
Func<int, bool> predicate = (x) => x > 30; var result1 = from number in list where predicate(number)

257

select number; var result2 = list.Where(predicate);

A where feltteleinek megfelel elemek nem a lekrdezs hvsakor kerlnek az eredmnylistba, hanem akkor, amikor tnylegesen felhasznljuk ket, ezt elhalasztott vgrehajtsnak (deferred execution) nevezzk (ez all kivtelt jelent, ha a lekrdezs eredmnyn azonnal meghvjuk pl. a ToList metdust, ekkor az elemek szrse azonnal megtrtnik). A kvetkez pldban teszteljk a fenti lltst:
using System; using System.Collections.Generic; using System.Linq; namespace Server { class Program { static void Main(string[] args) { List<int> list = new List<int>() { 12, 4, 56, 72, 34, 0, 89, 22 }; Func<int, bool> predicate = (x) => { Console.WriteLine("Szrs..."); return x > 30; }; Console.WriteLine("Lekrdezs eltt..."); var result = from number in list where predicate(number) select number; Console.WriteLine("Lekrdezs utn..."); foreach (var item in result) { Console.WriteLine("{0}", item); } Console.ReadKey(); } } }

A kimenet pedig ez lesz:

Lekrdezs eltt... Lekrdezs utn... Szrs... Szrs... Szrs... 56 Szrs...

258

72 Szrs... 34 Szrs... Szrs... 89 Szrs... Jl lthat, hogy a foreach ciklus vltotta ki a szr elindulst. A where kt alakban ltezik, az elst mr lttuk, most nzzk meg a msodikat is:
var result = list.Where((number, index) => index % 2 == 0);

Ez a vltozat kt paramtert kezel, ahol index az elem indext jelli, termszetesen nulltl szmozva. A fenti lekrdezs a pros index elemeket vlasztja ki. Ne hasznljuk a kvetkez formt:
var result = list.Select(x => x).Where(x => x > 10);

Ltszlag szablyos a lekrdezs, azonban a fordt a Select s Where szmra is elkszt egy fggvnyt, holott a munkt egyedl a Where is elvgezn. gy viszont egy plusz felesleges fggvnyhvs az eredmny.

RENDEZS
A lekrdezsek eredmnyt knnyen rendezhetjk az orderby utastssal, a lekrdezs sablonja ebben az esetben gy alakul: from azonost in kifejezs where kifejezs orderby tulajdonsg ascending/descending select kifejezs Az els pldban egyszeren rendezzk egy szmokbl ll lista elemeit:
var result1 = list.OrderBy(x => x); // nvekv sorrend var result2 = from number in list orderby number ascending select number; // szintn nvekv var result3 = from number in list orderby number descending select number; // cskken sorrend

A rendezs a gyorsrendezs algoritmust hasznlja. Az elemeket tbb szinten is rendezhetjk, a kvetkez pldban neveket fogunk sorrendbe rakni, mgpedig gy, hogy az azonos kezdbetvel rendelkezket tovbb rendezzk a nv msodik karaktere alapjn:

using System; using System.Collections.Generic; using System.Linq; namespace Server { class Program {

259

static void Main(string[] args) { List<string> names = new List<string>() { "Szandra", "Istvn", "Ivn", "Joln", "Jen", "Bla", "Balzs", "Viktria", "Vazul", "Thtm", "Tams" }; var result1 = names.OrderBy(name => name[0]) .ThenBy(name => name[1]); var result2 = from name in names orderby name[0], name[1] select name; foreach (var item in result2) { Console.WriteLine(item); } Console.ReadKey(); } } }

Az Extension Method formban a ThenBy volt segtsgnkre, mg a Query szintaxissal csak annyi a dolgunk, hogy az alapszably mg rjuk a tovbbi kritriumokat. Egy lekrdezs pontosan egy orderby/OrderByt s brmennyi ThenByt tartalmazhat. Az OrderBy metdus egy msik vltozata kt paramtert fogad, az els a rendezs alapszablya, mg msodik paramterknt megadhatunk egy tetszleges IComparer<T> interfszt megvalst osztlyt.

CSOPORTOSTS
Lehetsgnk van egy lekrdezs eredmnyt csoportokba rendezni a group by/GroupBy metdus segtsgvel. A sablon ebben az esetben gy alakul: from azonost in kifejezs where kifejezs orderby tulajdonsg ascending/descending group kifejezs by kifejezs into azonost select kifejezs Hasznljuk fel az elz fejezetben elksztett program neveit, s rendezzk ket csoportokba a nv els betje szerint:
using System; using System.Collections.Generic; using System.Linq; namespace Server { class Program { static void Main(string[] args) { List<string> names = new List<string>() { "Szandra", "Istvn", "Ivn", "Joln", "Jen", "Bla", "Balzs", "Viktria", "Vazul", "Thtm", "Tams" };

260

var result1 = names.OrderBy(name => name[0]) .GroupBy(name => name[0]); var result2 = from name in names orderby name[0] group name by name[0] into namegroup select namegroup; foreach (var group in result1) { Console.WriteLine(group.Key); foreach (var name in group) { Console.WriteLine("-- {0}", name); } } Console.ReadKey(); } } }

A kimenet a kvetkez lesz: B -- Bla -- Balzs I -- Istvn -- Ivn J -- Joln -- Jen S -- Szandra T -- Thtm -- Tams V -- Viktria -- Vazul A csoportostshoz meg kell adnunk egy kulcsot, ez lesz az OrderBy paramtere. Az eredmny tpusa a kvetkez lesz: IEnumerable<IGrouping<TKey, TElement>> Az IGrouping interfsz tulajdonkppen maga is egy IEnumerable<T> leszrmazott kiegsztve a rendezshez hasznlt kulccsal, vagyis lnyegben egy lista a listban tpus adatszerkezetrl van sz.

Null rtkek kezelse

261

Elfordul, hogy olyan listn akarunk lekrdezst vgrehajtani, amelynek bizonyos indexein null rtk van. A Select kpes kezelni ezeket az eseteket, egyszeren null rtket tesz az eredmnylistba, de amikor rendeznk, akkor szksgnk van az objektum adataira, s ha null rtket akarunk vizsglni, akkor gyorsan kivtelt kaphatunk. Hasznljuk fel az elz programok listjt, egsztsk ki nhny null rtkkel, s rjuk t a lekrdezst, hogy kezelje ket:
var result1 = names.GroupBy(name => { return name == null ? '0' : name[0]; }); var result2 = from name in names group name by name == null ? '0' : name[0] into namegroup select namegroup; foreach (var group in result2) { Console.WriteLine(group.Key); foreach (var name in group) { Console.WriteLine("-- {0}", name == null ? "null" : name); } }

Tbbfle megolds is ltezik erre a problmra. Hasznlhattuk volna a ?? opertort is, vagy akr kszthetnk egy metdust, amely a megfelel alaprtelmezett rtket adja vissza, a lnyeg, hogy amikor a listban elfordulhatnak null rtkek, akkor azt figyelembe kell venni a lekrdezs megrsakor. Ez akkor is szmt, ha egybknt a lekrdezs nem vgez mveleteket az elemekkel, hanem csak kivlasztst vgznk, hiszen az eredmnylistban attl mg ott lesznek a null elemek, amelyek ksbb fejfjst okozhatnak. sszetett kulcsok Kulcs meghatrozsnl lehetsgnk van egynl tbb rtket kulcsknt megadni, ekkor nvtelen osztlyknt kell azt definilni. Hasznljuk fel a korbban megismert Customer illetve Address osztlyokat, ezeket, illetve a hozzjuk tartoz listkat a jegyzet mell csatolt forrskdok kzl a Data.cs file-ban tallja az olvas.
class Address { public string Country { get; set; } public int PostalCode { get; set; } public int State { get; set; } public string City { get; set; } public string Street { get; set; } } class Customer { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; } public Address Address { get; set; } }

A lekrdezst pedig a kvetkezkppen rjuk meg:

262

using System; using System.Collections.Generic; using System.Linq; namespace Server { class Program { static void Main(string[] args) { var result = from customer in DataClass.GetCustomerList() group customer by new { customer.Address.Country, customer.Address.State } into customergroup select customergroup; foreach (var group in result) { Console.WriteLine("{0}, {1}", group.Key.Country, group.Key.State); foreach (var customer in group) { Console.WriteLine("-- {0}", customer.FirstName + " " + customer.LastName); } } Console.ReadKey(); } } }

LISTK SSZEKAPCSOLSA
A relcis adatbzisok egyik alapkve, hogy egy lekrdezsben tbb tblt sszekapcsolhatunk (join) egy lekrdezssel, pl. egy webruhzban a vev-ru-rendels adatokat. Memriban lv gyjtemnyek esetben ez viszonylag ritkn szksges, de a LINQ To Objects tmogatja ezt is. A join-mveletek azon az egyszer felttelen alapulnak, hogy az sszekapcsoland objektumlistk rendelkeznek kzs adattagokkal (relcis adatbzisoknl ezt elsdleges kulcs (primary key) idegen kulcs (foreign key) kapcsolatknt kezeljk). Nzznk egy pldt:
class Customer { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; } public Address Address { get; set; } } class Order { public int CustomerID { get; set; }

263

public public public public }

int ProductID { get; set; } DateTime OrderDate { get; set; } DateTime? DeliverDate { get; set; } string Note { get; set; }

class Product { public int ID { get; set; } public string ProductName { get; set; } public int Quantity { get; set; } }

Ez a fent emltett webruhz egy egyszer megvalstsa. Mind a Customer, mind a Product osztly rendelkezik egy ID nev tulajdonsggal, amely segtsgvel egyrtelmen meg tudjuk klnbztetni ket, ez lesz az elsdleges kulcs (tegyk fel, hogy egy listban egy pldnybl s gy kulcsbl csak egy lehet). Az Order osztly mindkt pldnyra tartalmaz referencit (hiszen minden rendels egy vev-termk prost ignyel), ezek lesznek az idegen kulcsok. rjunk egy lekrdezst, amely visszaadja minden vsrl rendelst! Elsknt rjuk fel a join-nal felszerelt lekrdezsek sablonjt: from azonost in kifejezs where kifejezs join azonost in kifejezs on kifejezs equals kifejezs into azonost orderby tulajdonsg ascending/descending group kifejezs by kifejezs into azonost select kifejezs Most pedig jjjn a lekrdezs (az adatokat most is a Data.cs trolja):
var result = from order in DataClass.GetOrderList() join customer in DataClass.GetCustomerList() on order.CustomerID equals customer.ID select new { Name = customer.FirstName + " " + customer.LastName, Products = DataClass.GetProductList() .Where(order.ProductID == product.ID) }; foreach (var orders in result) { Console.WriteLine(orders.Name); foreach (var product in orders.Products) { Console.WriteLine("-- {0}", product.ProductName); } }

A join tipikusan az a lekrdezs tpus, ahol az SQL-szer szintaxis olvashatbb, ezrt itt csak ezt rtuk meg. A lekrdezs nagyon egyszer: elsknt csatoltuk az elsdleges kulcsokat tartalmaz listt az idegen kulccsal rendelkez listhoz, majd megmondtuk, hogy milyen felttelek szerint prostsa az elemeket (itt is hasznlhatunk sszetett kulcsokat ugyangy nvtelen osztly ksztsvel). Az eredmnylistt persze ki kell egsztennk a vsrolt termkek listjval, ezrt egy bels lekrdezst is rtunk. A kimenet a kvetkez lesz: Istvan Reiter -- Eloszt Istvan Reiter -- Paprzsebkend Jzsef Fekete -- Elektromos vzforral

264

OUTER JOIN
Az elz fejezetben azokat az elemeket vlasztottuk ki, amelyek kapcsoldtak egymshoz, de gyakran van szksgnk azokra is, amelyek ppen hogy nem kerlnnek bele az eredmnylistba, pl. azokat a vsrlkat keressk, akik eddig mg nem rendeltek semmit. Ez a feladat a join egy specilis outer joinnak nevezett vltozata. A LINQ To Objects br kzvetlenl nem tmogatja ezt (pl. az SQL tartalmaz OUTER JOIN utastst), de knnyen szimullhatjuk a DefaultIfEmpty metdus hasznlatval, amely egyszeren egy null elemet helyez el az eredmnylistban az sszes olyan elem helyn, amely nem szerepelne a hagyomnyos join ltal kapott lekrdezsben. A kvetkez pldban a lekrdezs visszaadja a vsrlk rendelsnek a sorszmt (most nincs szksgnk a Products listra), vagy ha mg nem rendelt, akkor megjelentnk egy nincs rendels feliratot.:
var result = from customer in DataClass.GetCustomerList() join order in DataClass.GetOrderList() on customer.ID equals order.CustomerID into tmpresult from o in tmpresult.DefaultIfEmpty() select new { Name = customer.FirstName + " " + customer.LastName, Product = o == null ? "nincs rendels" : o.ProductID.ToString() }; foreach (var order in result) { Console.WriteLine("{0}: {1}", order.Name, order.Product); }

KONVERZIS OPERTOROK
A LINQ To Objects lehetsget ad listk konverzijra a kvetkez opertorok segtsgvel: OfType s Cast: k a sima IEnumerable interfszrl konvertlnak generikus megfelelikre, elsdlegesen a .NET 1.0val val kompatibilits miatt lteznek, hiszen a rgi ArrayList osztly nem valstja meg a generikus IEnumerablet, ezrt kasztolni kell, ha LTO t akarunk hasznlni. A kett kzti klnbsget a hibakezels jelenti: az OfType egyszeren figyelmen kvl hagyja a konverzis hibkat, s a nem megfelel elemeket kihagyja az eredmnylistbl, mg a Cast kivtelt (System.InvalidCastException) dob:
using using using using System; System.Collections.Generic; System.Linq; System.Collections;

namespace Server { class Program { static void Main(string[] args) { ArrayList list = new ArrayList(); list.Add("alma"); list.Add("di"); list.Add(12345);

265

var result1 = from item in list.Cast<string>() select item; var result2 = from item in list.OfType<string>() select item; foreach (var item in result1) { Console.WriteLine(item); // kivtel } Console.ReadKey(); } } }

A program ugyan kirja a lista els kt elemt, de a harmadiknl mr kivtelt dob, teht a konverzi elemenknt trtnik, mgpedig akkor, amikor az eredmnylistt tnylegesen felhasznljuk, nem pedig a lekrdezsnl. ToArray, ToList, ToLookup, ToDictionary: ezek a metdusok ahogyan a nevkbl is ltszik az IEnumerable<T> eredmnylistt tmbb vagy generikus gyjtemnny alaktjk. A kvetkez pldban a ToList metdus lthat:
using using using using System; System.Collections.Generic; System.Linq; System.Collections;

namespace Server { class Program { static void Main(string[] args) { List<int> list = new List<int>() { 10, 32, 45, 2, 55, 32, 21 }; var result = (from number in list where number > 20 select number).ToList<int>(); result.ForEach((number) => Console.WriteLine(number)); Console.ReadKey(); } } }

Amikor ezeket az opertorokat hasznljuk, akkor a Where vgrehajtsa nem toldik el, hanem azonnal kiszri a megfelel elemeket, vagyis itt rdemes figyelni a teljestmnyre. A ToArray metdus rtelemszeren tmbb konvertlja a bemen adatokat. A ToDictionary s ToLookup metdusok hasonl feladatot ltnak el abban az rtelemben, hogy mindkett kulcs-rtk prokkal operl adatszerkezetet hoz ltre. A klnbsg a dupliklt kulcsok kezelsben van: mg a Dictionary<T, V> szerkezet ezeket nem engedlyezi (st kivtelt dob), addig a ToLookup ILookup<T, V> szerkezetet ad vissza, amelyben az azonos kulccsal rendelkez adatok listt alkotnak a listn bell, ezrt t kivlan alkalmazhatjuk a join mveletek sorn. A kvetkez pldban ezt a metdust hasznljuk, hogy a vsrlkat megye szerint rendezze:

266

var result = (from customer in DataClass.GetCustomerList() select customer) .ToLookup((customer) => customer.Address.State + " megye"); foreach (var item in result) { Console.WriteLine(item.Key); foreach (var customer in item) { Console.WriteLine("-- {0} {1}", customer.FirstName, customer.LastName); } }

A ToLookUp paramtereknt a lista kulcsrtkt vrja. AsEnumerable: Ez az opertor a megfelel IEnumerable<T> tpusra konvertlja vissza a megadott IEnumerable<T> interfszt megvalst adatszerkezetet.

ELEMENT OPERTOROK
A kvetkez opertorok megegyeznek abban, hogy egyetlen elemmel (vagy egy elre meghatrozott alaprtkkel) trnek vissza az eredmnylistbl. First/Last s FirstOrDefault/LastOrDefault: ezeknek a metdusoknak kt vltozata van: a paramter nkli az els/utols elemmel tr vissza a listbl, mg a msik egy Func<T, bool> tpus metdust kap paramternek, s e szerint a szr szerint vlasztja ki az els elemet. Amennyiben nincs a felttelnek megfelel elem a listban (vagy res listrl beszlnk), akkor az els kt opertor kivtelt dob, mg a msik kett az elemek tpusnak megfelel alaprtelmezett null rtkkel tr vissza (pl. int tpus elemek listjnl ez nulla, mg stringek esetn null lesz):
using System; using System.Collections.Generic; using System.Linq; namespace Server { class Program { static void Main(string[] args) { List<int> list = new List<int>() { 10, 3, 56, 67, 4, 6, 78, 44 }; var result1 = list.First(); // 10 var result2 = list.Last(); // 44 var result3 = list.First((item) => item > 10); // 56 try { var result4 = list.Last((item) => item < 3); // kivtel } catch (Exception e) { Console.WriteLine(e.Message);

267

} var result5 = list.FirstOrDefault((item) => item < 3); // 0 Console.WriteLine("{0}, {1}, {2}, {3}", result1, result2, result3, result5); Console.ReadKey(); } } }

Single/SingleOrDefault: ugyanaz, mint a First/FirstOrDefault pros, de mindkett kivtelt dob, ha a felttelnek tbb elem is megfelel. ElementAt/ElementAtOrDefault: visszaadja a paramterknt tadott indexen tallhat elemet. Az els kivtelt dob, ha az index nem megfelel, a msik pedig a megfelel alaprtelmezett rtket adja vissza. DefaultIfEmpty: a megfelel alaprtelmezett rtkkel tr vissza, ha egy lista nem tartalmaz elemeket, t hasznltuk korbban a join mveleteknl.

HALMAZ OPERTOROK
Nzzk a halmaz opertorokat, amelyek kt lista kztti halmazmveleteket tesznek lehetv. Concat s Union: mindkett kpes sszefzni kt lista elemeit, de az utbbi egy elemet csak egyszer tesz t az j listba:
List<int> list1 = new List<int>() { 10, 3, 56, 67, 4, 6, 78, 44 }; List<int> list2 = new List<int>() { 10, 5, 67, 89, 3, 22, 99 }; var result1 = list1.Concat(list2); /* 10, 3, 56, 67, 4, 6, 78, 44, 10, 5, 67, 89, 3, 22, 99 */ var result2 = list1.Union(list2); /* 10, 3, 56, 67, 4, 6, 78, 44, 5, 89, 22, 99 */

Intersect s Except: az els azokat az elemeket adja vissza, amelyek mindkt listban szerepelnek, mg a msodik azokat, amelyek csak az elsben:
List<int> list1 = new List<int>() { 10, 3, 56, 67, 4, 6, 78, 44 }; List<int> list2 = new List<int>() {

268

10, 5, 67, 89, 3, 22, 99 }; var result1 = list1.Intersect(list2); /* 10, 3, 67 */ var result2 = list1.Except(list2); /* 56, 4, 6, 78, 44 */

AGGREGT OPERTOROK
Ezek az opertorok vgigrnak egy listt, elvgeznek egy mveletet minden elemen, s vgeredmnyknt egyetlen rtket adnak vissza (pl. elemek sszege vagy tlagszmts). Count s LongCount: visszaadjk az elemek szmt egy listban. Alaprtelmezs szerint az sszes elemet szmoljk, de megadhatunk felttelt is. A kt opertor kztti klnbsg az eredmny nagysgban van, a Count 32 bites egsz szmmal (int), mg a LongCount 64 bites egsz szmmal (int64) tr vissza:
List<int> list = new List<int>() { 10, 3, 56, 67, 4, 6, 78, 44 }; var result1 = list.Count(); // 8 var result2 = list.Count((item) => item > 10); // 4

Min s Max: a lista legkisebb illetve legnagyobb elemt adjk vissza. Mindkt opertornak megadhatunk egy szelektor kifejezst, amelyet az sszes elemre alkalmaznak, s e szerint vlasztjk ki a megfelel elemet:
List<int> list = new List<int>() { 10, 3, 56, 67, 4, 6, 78, 44 }; var result1 = list.Max(); // 78 var result2 = list.Max((item) => item % 3); // 2

Termszetesen a msodik eredmny maximum kett lehet, hiszen hrommal val oszts utn ez lehet a legnagyobb maradk. Mindkt metdus az IComparable<T> interfszt hasznlja, gy minden ezt megvalst tpuson hasznlhatak. Average s Sum: a lista elemeinek tlagt illetve sszegt adjk vissza. k is rendelkeznek szelektor kifejezst hasznl vltozattal:

List<int> list = new List<int>() { 10, 3, 56, 67, 4, 6, 78, 44 }; var result1 = list.Sum(); // 268 var result2 = list.Average(); // 33.5 var result3 = list.Sum((item) => item * 2); // 536

269

Aggregate: ez az opertor lehetv teszi tetszleges mvelet elvgzst s a rszeredmnyek felhalmozst. Az Aggregate hrom formban ltezik, tetszs szerint megadhatunk neki egy kezdrtket, illetve egy szelektor kifejezst is:
List<int> list = new List<int>() { 1, 2, 3, 4 }; var sum = list.Aggregate((result, item) => result + item); var max = list.Aggregate(-1, (result, item) => item > result ? item : result); var percent = list.Aggregate(0.0, (result, item) => result + item, result => result / 100);

Az els esetben a Sum opertort szimulltuk, ezt nem kell magyarzni. A msodik vltozatban maximumkeresst vgeztnk, itt megadtunk egy kezdrtket, amelynl biztosan van nagyobb elem a listban. Vgl a harmadik mveletnl kiszmoltuk a szmok sszegnek egy szzalkt (itt figyelni kell arra, hogy double tpusknt lssa a fordt a kezdrtket, hiszen tizedes trtet akarunk visszakapni). A vgeredmnyt trol vltoz brmilyen tpus lehet, mg tmb is.

PLINQ PRHUZAMOS VGREHAJTS


Egy korbbi fejezetben mr megismerkedtnk a prhuzamos programozst tmogat Task Parallel Library-val. Azonban a Microsoft nemcsak a hagyomnyos programok eltt nyitotta meg a prhuzamossg kapujt, hanem a LINQ lekrdezseinket is felgyorsthatjuk ezen a mdon. PLINQ a gyakorlatban Hasonltsuk ssze a hagyomnyos s prhuzamos LINQ lekrdezsek teljestmnyt! Elssorban szksgnk lesz valamilyen megfelelen nagy adatforrsra, amely jelen esetben egy szveges fjl lesz. A knyvhz mellkelt forrskdok kztt megtalljuk a DataGen.cs nevt, amely az els paramterknt megadott fjlba a msodik paramterknt megadott mennyisg vezetknv-keresztnv-kor-foglalkozs-megye adatokat r. A kvetkez pldban tzmilli szemllyel fogunk dolgozni, teht gy futtassuk a programot: DataGen.exe E:\Data.txt 10000000 Az elrsi t persze tetszleges. Most ksztsk el a lekrdezst! Felhasznljuk, hogy a .NET 4.0ban a File.ReadLines metdus IEnumerable<string>-gel tr vissza, vagyis kzvetlenl hajthatunk rajta vgre lekrdezst:
var lines = File.ReadLines(@"E:\Data.txt"); // System.IO kell var result = from line in lines let data = line.Split(new char[] { ' ' }) let name = data[1] let age = int.Parse(data[2]) let job = data[3] where name == "Istvn" && (age > 24 && age < 55) && job == "brtnr" select line;

270

Keressk az sszes 24 s 55 v kztti Istvn nev brtnrt. Elsknt sztvgunk minden egyes sort, majd a megfelel indexekrl (az adatok sorrendjrt ltogassuk meg a DataGen.cst) sszeszedjk a szksges adatokat. Az eredmnyt rassuk ki egy egyszer foreach ciklussal:
foreach (var line in result) { Console.WriteLine(line); }

A fenti lekrdezs egyelre nem prhuzamostott, nzzk meg, hogy mi trtnik:

Ez a kp a processzorhasznlatot mutatja. Kt dolog vilgosan ltszik: 1. a kt processzormag nem ugyanazt a teljestmnyt adja le, 2. egyik sem teljest maximumon. Most rjuk t a lekrdezst prhuzamosra! Ezt rendkvl egyszeren tehetjk meg, mindssze az AsParallel metdust kell meghvnunk az adatforrson:
var result = from line in lines.AsParallel() let data = line.Split(new char[] { ' ' }) let name = data[1] let age = int.Parse(data[2]) let job = data[3] where name == "Istvn" && (age > 24 && age < 55) && job == "brtnr" select line;

Lssuk az eredmnyt:

A kp nmagrt beszl, de a teljestmnyklnbsg is a tesztgpen tlagosan 25% volt a prhuzamos lekrdezs elnye a hagyomnyoshoz kpest (ez termszetesen mindenkinl ms s ms lehet). Az AsParallel kiterjesztett metdus egy ParalellQuery objektumot ad vissza, amely megvalstja az IEnumerable interfszt. A ktoperandus LINQ opertorokat (pl. join, Concat) csakis gy hasznlhatjuk prhuzamosan, ha mindkt adatforrst AsParallel-lel jelljk, ellenkez esetben nem fordul le:
var result = list1.AsParallel().Concat(list2.AsParallel());

Br gy tnhet, hogy nagyon egyszeren felgyorsthatjuk a lekrdezseinket, a valsg ennl kegyetlenebb. Ahhoz, hogy megrtsk, hogy mirt csak bizonyos esetekben kell prhuzamostanunk, ismerni kell a prhuzamos lekrdezsek munkamdszert:

271

1.

2.

3. 4.

Analzis: a keretrendszer megvizsglja, hogy egyltaln megri-e prhuzamosan vgrehajtani a lekrdezst. Minden olyan lekrdezs, amely nem tartalmaz legalbb viszonylag sszetett szrst vagy egyb drga mveletet szinte biztos, hogy szekvencilisan fog vgrehajtdni (s egyttal a lekrdezs futsidejhez hozzaddik a vizsglat ideje is). Termszetesen az ellenrzst vgrehajt algoritmus sem tvedhetetlen, ezrt lehetsgnk van manulisan kiknyszerteni a prhuzamossgot a WithExecutionMode metdussal. Ha az tlet a prhuzamossgra nzve kedvez, akkor a kvetkez lpsben a feldolgozand adatot a rendszer a rendelkezsre ll processzorok szma alapjn elosztja. Ez meglehetsen bonyolult tma, az adatforrstl fggen tbb stratgia is ltezik, ezt itt nem rszletezzk. Vgrehajts. A rszeredmnyek sszeillesztse. Erre a rszre is lehet rhatsunk, miszerint egyszerre szeretnnk a vgeredmnyt megkapni, vagy megfelelnek a rszeredmnyek is. Erre a clra a WithMergeOptions metdust kell meghvnunk a lekrdezsen. A paramtereknt hasznlatos ParallelMergeOptions felsorols hrom taggal rendelkezik: a NotBuffered hatsra azonnal visszakapunk minden egyes elemet a lekrdezsbl, az AutoBuffered periodikusan ad vissza tbb elemet, mg a FullyBuffered csak akkor kldi vissza az eredmnyt, ha a lekrdezs teljesen ksz van.

Lthatjuk, hogy komoly szmtsi kapacitst ignyel a rendszertl egy prhuzamos lekrdezs vgrehajtsa, pp ezrt a nem megfelel lekrdezsek parallel mdon val indtsa nem fogja az elvrt teljestmnyt hozni, tulajdonkppen mg teljestmnyromls is lehet az eredmnye. Tipikusan olyan helyzetekben akarunk ilyen lekrdezseket hasznlni, ahol nagy mennyisg elemen sok vagy drga mveletet vgznk el, mivel itt nyerhetnk a legtbbet. Rendezs Nzzk a kvetkez kdot:
List<int> list = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var result1 = from x in list select x; var result2 = from x in list.AsParallel() select x;

Vajon mit kapunk, ha kiratjuk mindkt eredmnyt? A vlasz meglep lehet: result1: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 result2: 0, 6, 3, 4, 8, 2, 5, 1, 9, 7 Hogyan kaphattunk egy rendezett listbl rendezetlen eredmnyt? A vlasz nagyon egyszer, hiszen tudjuk, hogy a PLINQ rszegysgekre bontja az adatforrst, vagyis nem fogjuk sorban visszakapni az eredmnyeket (a fent mutatott eredmny nem lesz mindenkinl ugyanaz, minden futsnl ms sorrendet kaphatunk vissza). Amennyiben szmt az eredmny rendezettsge, akkor vagy hasznlnunk kell az orderbyt, vagy az AsParallel mellett meg kell hvnunk az AsOrdered kiterjesztett metdust:
var result2 = from x in list.AsParallel().AsOrdered() select x; var result3 = from x in list.AsParallel() orderby x select x;

A kt lekrdezs hasonlnak tnik, de nagy klnbsg van kzttk. Ha lemrjk a vgrehajtsi idt, akkor ltni fogjuk, hogy az els valamivel gyorsabban vgzett, mint a msodik, ennek pedig az az oka, hogy amg az orderby mindig vgrehajtja a rendezst, addig az AsOrdered egyszeren megrzi az eredeti sorrendet, s aszerint osztja szt az adatokat (nagy adatmennyisggel a kett kztti sebessgklnbsg is jelentsen megn, rdemes nhny ezer elemre is tesztelni).

272

Gyakran elfordul, hogy az eredeti llapot fenntartsa nem elnys szmunkra, pl. kivlasztunk nhny elemet egy listbl, s ezek alapjn akarunk egy join mveletet vgrehajtani. Ekkor nem clszer fenntartani a sorrendet, hasznljuk az AsUnordered metdust, amely minden rvnyes rendezst rvnytelent! AsSequential Az AsSequential metdus pp ellenkezje az AsParallelnek, vagyis szablyozhatjuk, hogy egy lekrdezs mely rsze legyen szekvencilis s melyik prhuzamos. A kvetkez pldban megnzzk, hogy ez mirt j neknk. A PLINQ egyelre mg nem teljesen kiforrott, annak a szablyai, hogy mely opertorok lesznek mindig szekvencilisan kezelve a jvben valsznleg vltozni fognak, ezrt a pldaprogramot illik fenntartssal kezelni:
var result = (from x in list.AsParallel() select x).Take(10);

Kivlasztjuk az eredmnylistbl az els tz elemet. A problma az, hogy a Take opertor szekvencilisan fut majd le fggetlenl attl, hogy mennyire bonyolult a bels lekrdezs, pp ezrt nem kapunk semmifle teljestmnynvekedst. rjuk t egy kicsit:
var result2 = (from x in list.AsParallel() select x) .AsSequential().Take(10);

Most pontosan azt kapjuk majd, amire vrunk: a bels prhuzamos mveletsor vgeztvel visszavltottunk szekvencilis mdba, vagyis a Take nem fogja vissza a sebessget.

273

GRAFIKUS FELLET ALKALMAZSOK WINDOWS FORMS


Az eddigi fejezetekben parancssoros alkalmazsokat ksztettnk, hogy a nyelv elemeinek megrtsre koncentrlhassunk. Azonban a modern alkalmazsok nagy rsze rendelkezik grafikus fellettel, amellyel kezelsk knyelmesebb vlik az tlagfelhasznlk szmra is. Szmos lehetsgnk van ilyen programok ksztsre, ebben a knyvben a Windows Forms knyvtrral ismerkednk meg, ami meglehetsen egyszeren elsajtthat! Ez a rendszer a .NET-tel szlltott els grafikus interfsz, tulajdonkppen a .Net eltti COM osztlyokat burkolja be, hogy azok menedzselt krnyezetben legyenek hasznlhatak.

HELLO WINDOWS FORMS


Ksztsnk egy j projektet, Windows Forms Application sablonnal:

A projekt elkszlte utn az n. Design nzetbe kerlnk, ahol az alkalmazsunk lthat, egyelre mg resen. Ugyanakkor mris elindthatjuk, br sok haszna mg nincsen. rdemes ilyen projekt tpusnl a kpen lthat mdon megnyitni a Properties ablakot, hiszen az egyes vezrlk tulajdonsgait itt knnyen bellthatjuk.

274

Mieltt csinlnnk valami hasznosat, rdemes megnzni, hogyan pl fel egy ilyen projekt. Kattintsunk jobb gombbal a Design nzeten, s vlasszuk a View Code menpontot! Ekkor megjelenik az n. code-behind fjl tartalma:
using using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms;

namespace WFDemo { public partial class Form1 : Form { public Form1() { InitializeComponent(); } } }

Kt dolgot kell szrevennnk: az egyik, hogy ez egy parcilis osztly, a msik pedig az, hogy a konstruktorban meghvtunk egy InitializeComponent nev metdust, aminek a defincijt viszont nem ltjuk. Ezt a metdust a fordt automatikusan generlja, s az ablakon szerepl vezrlk s az alkalmazs alapbelltsrt felels. Ha a Solution Explorerben lenyitjuk a Form1.cs fjlt, lthatunk egy Form1.Designer.cs nev llomnyt is, amely nem ms, mint a fent lthat osztlyunk msik fele. Ebbe a fjlba semmikppen se rjunk, minden egyes fordtskor jragenerldik, vagyis az ltalunk rt kd elveszne. Erre egybknt szksg sincsen, hiszen az ltalunk mdosthat felbl ugyangy elrnk mindent, mint innen. Amennyiben a konstruktorban szeretnnk a program valamely tulajdonsgn lltani azt mindenkppen az InitializeComponent hvs utn tegyk meg, ellenkez esetben Nullreference kivtelt kapunk, hiszen a vezrlk csak a futsa utn lesznek pldnyostva. Nemcsak ennyibl ll azonban a projektnk: ha emlksznk, minden .NET program tartalmaz egy Main fggvnyt is. Ezt a Program.cs fjlban leljk meg:

275

using using using using

System; System.Collections.Generic; System.Linq; System.Windows.Forms;

namespace WFDemo { static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } } }

Most mr elkszthetjk az els igazi Windows Forms programunkat. Trjnk vissza a Design nzetbe, s hozzuk el a ToolBox-ot, ez alaprtelmezs szerint bal oldalt kell legyen, ha mgsem, akkor a Ctrl+W billentykombincival csalogathatjuk el. Az Auto Hide kikapcsolsval (kis rajzszg az ablak jobb fls sarkban) rgzthetjk is. Ebben az ablakban megtallhatjuk az sszes vezrlt, amelyet felhasznlhatunk, egyelre elg lesz a Common Controls csoportban lv gyjtemny is:

276

Hzzunk egy Button, vagyis gomb vezrlt az alkalmazsunkra:

A gombra kattintva a Properties ablakban a Text mezben lltsuk be a feliratt:

Helyezznk el az ablakon mg egy TextBox vezrlt is, tetszleges mdon! Most pedig programozzunk egy kicsit! Azt szeretnnk elrni, hogy a gomb megnyomsakor valamilyen szveg jelenjen meg a szvegmezben. Valjban minden tudssal rendelkeznk, amely ehhez a feladathoz szksges, legalbbis ha mr elolvastuk az Esemnyekrl szl fejezetet. Ugyanis minden vezrlhz tartoznak esemnyek, amelyekre feliratkozva tetszleges mveletet hajthatunk vgre. Ha dupln kattintunk a gombon, a code-behind fjlba kerlnk, s megjelenik ott egy esemnykezel metdus is button1_Click nven (minden vezrl automatikusan nevet kap a vezrl tpusa+sorszm formban, ezt mdosthatjuk a Properties ablak Name mezjben). A duplakattints az adott vezrl alaprtelmezett esemnyhez kszt esemnykezelt, ez egy gomb esetn rtelemszeren a kattints lesz. Ms esemnyekhez a Properties ablakban rendelhetnk esemnykezelket, a kis villm gombra kattintva. Mr csak annyi a dolgunk, hogy runk valamit a TextBox-ba, ezt a Text tulajdonsgn keresztl tudjuk megtenni:
private void button1_Click(object sender, EventArgs e) { textBox1.Text = "Hello"; }

277

VEZRLK
Vezrlk alatt azokat az elemeket rtjk, amelyekbl egy grafikus fellet felpl. A legtbb esetben valamilyen akcit hajthatunk vgre rajtuk, pldul megnyomunk egy gombot, vagy runk egy szvegmezbe. Ebben a fejezetben megismerkednk a leggyakrabban hasznlt elemekkel. Form Minden Windows Forms alkalmazs alapja. Mivel a ContainerClass osztlybl szrmazik, kpes magban ms vezrlket trolni. A Form alaprtelmezett esemnye a Load, amely azeltt fut le, hogy a vezrl megjelenne. Egy Formhoz ktflekppen adhatunk vezrlt, igaz a kt mdszer tulajdonkppen egy s ugyanaz. A legegyszerbb, hogy a tervez nzetben egyszeren rhzzuk a ToolBox-bl. A msik eset, amikor futsidben forrskdbl szeretnnk ezt megtenni. Ez utbbihoz tudni kell, hogy minden ContainerClass-bl szrmaz osztly rendelkezik egy Controls nev tulajdonsggal, amely valjban az objektum vezrlinek listja. Egy elemet a kvetkezkppen adhatunk egy Form-hoz, pozci s mret megadsval:
Button button = new Button() { Width = 100, Height = 30, Location = new Point(30, 40), Text = "Click" }; this.Controls.Add(button);

A this itt a fablakra, vagyis az aktulis Form objektumra mutat. A Controls.Add egy Control objektumot vr, amely osztlybl minden vezrl szrmazik. Button A gomb vezrlt mr ismerjk, elsdlegesen arra hasznljuk, hogy elindtsunk vele egy folyamatot, pldul adatokat mentsnk el. Alaprtelmezett esemnye a kattints. Label A felhasznl ltal nem mdosthat szveges tartalmat jelenthetnk meg vele. rtkt a TextBox-hoz hasonlan a Text tulajdonsgn keresztl adhatjuk meg.

A Label specilis vltozata a LinkLabel, amellyel hivatkozst adhatunk meg. Alaprtelmezett esemnye a LinkClicked, ahol tirnythatjuk a felhasznlt a kivlasztott helyre.

278

TextBox Szintn szveges vezrl, de kpes fogadni a felhasznl billentyletseit. MultiLine tulajdonsgt true rtkre lltva tbb sort is megjelent. A PasswordChar tulajdonsgnak rtkl adott karaktert jelenti meg minden ms karakter helyett, gy jelszmezknt funkcionl.

Alaprtelmezett esemnye a TextChanged, amely a vezrl tartalmnak megvltozsakor fut le. ListBox Ez olyan vezrl, amelyben jellemzen listkat trolunk. Egy ListBox-ot feltlthetnk az Items tulajdonsgn keresztl (akr a Properties ablakban), vagy pedig n. adatktssel (data binding). Ehhez a DataSource tulajdonsgot hasznljuk fel. Generljuk le a Form Load esemnyt, majd rjuk a kvetkezt:
private void Form1_Load(object sender, EventArgs e) { string[] items = new string[] { "Istvn", "Szandra", "Bla", "Balzs" }; listBox1.DataSource = items; }

Amikor a DataSource tulajdonsgot hasznljuk, a ListBox az elemek ToString metdust hvja meg, hogy elkrje a megjelentend informcit. Amennyiben a felhasznlt osztlyunk nem valstja ezt meg (override hasznlatval), akkor az elemek tpust adja vissza.

A ListBox alaprtelmezett esemnye a SelectedIndexChanged, amely akkor fut le, ha megvltozott a vezrlben a kijellt elem. Az aktulis kivlasztott elemet a SelectedItem segtsgvel rjk el, ez object tpussal tr vissza, teht konvertlni kell, ha tovbbi terveink vannak vele. A kivlasztott elem indext (nulltl kezdve) a SelectedIndex tulajdonsg trolja. Az Items tulajdonsga hagyomnyos listaknt viselkedik, hasznlhatjuk rajta pldul a foreach ciklust is. A SelectionMode tulajdonsgt megvltoztatva tbb elemet is kijellhetnk egyszerre. Ez egy enum tpus, rtkei lehetnek None (nem lehet kivlasztani egy elemet sem), MultiSimple (a Ctrl billenty nyomva

279

tartsval lehet tbb elemet kivlasztani) illetve MultiExtended (az egr nyomva tartsa mellett lehet kijellni az elemeket). CheckBox Ez a vezrl a vlaszts lehetsgt knlja. Tulajdonkppen egy jellngyzetrl van sz, amely segtsgvel egy vagy tbb opci kzl vlaszthatunk. Alaprtelmezett esemnye a CheckedChanged, amelyet a bejells/visszavons vlt ki. A Checked tulajdonsgn keresztl krdezhetjk le, hogy be van-e jellve vagy sem.

Helyezznk el a programunkban mg egy Label-t is, amelynek feliratt annak fggvnyben vltoztassuk, hogy a CheckBox be van-e jellve vagy sem (a Label Text tulajdonsgt lltsuk res stringre a program futsnak elejn)! Ezutn kezeljk a CheckedChanged esemnyt:
private void checkBox1_CheckedChanged(object sender, EventArgs e) { if (checkBox1.Checked) label1.Text = "A CheckBox be van jellve!"; else label1.Text = "A CheckBox nincs bejellve!"; }

RadioButton Hasonl funkcionalitst knl, mint a CheckBox, de egy adott troln bell (pl. Form) tbb ilyen vezrl egy csoportot alkot, s egy idben csak egy darab lehet bejellve. Alaprtelmezett esemnye a CheckedChanged.

Egy Formon bell elfordulhatnak nll RadioButton csoportok, amelyeket valahogy el kell klnteni egymstl. Erre a clra Panel vagy GroupBox vezrlket hasznlhatunk, elbbi grgethet, de nem rendelhetnk hozz feliratot, utbbi fix s lehet fejlcet megadni hozz. Ezeket a vezrlket a ToolBox Containers fln talljuk meg. Az albbi kpen GroupBox s Panel vezrlkben helyeztnk el kt-kt RadioButton-t. Lthat, hogy elbbi eset ltvnyosabb, hiszen a GroupBox kerettel is rendelkezik, mg a Panel gyakorlatilag lthatatlan.

280

Ha szeretnnk lekrdezni a RadioButton-t trol elemet (ez lehet GroupBox, Form, brmely vezrl, amely kpes trolni ms vezrlket), azt a Parent tulajdonsgon tehetjk meg. Egy csoporton bell rdemes ugyanazt az esemnykezelt rendelni az sszes RadioButton-hoz, ezt gy tehetjk meg, hogy kijelljk egyesvel a vezrlket (Ctrl nyomva tartva s bal egrgomb), majd a Properties ablakban a villm gombbal elhozzuk a vlaszthat esemnyek listjt, s a kvnt elemen dupln kattintunk:

Ezutn a sender paramteren elvgezve a megfelel tpuskonverzit visszakapjuk azt a vezrlt, amely az esemnyrl rtestt kldtt:
private void radioButton1_CheckedChanged(object sender, EventArgs e) { RadioButton radioButton = (RadioButton)sender; }

ComboBox Ez a vezrl a TextBox s a ListBox egyfajta kombincijnak tekinthet, a bert szveg alapjn kivlaszthatunk egy elemet egy elre megadott listbl. Az adatok feltltse ugyangy zajlik, mint a ListBox esetn (Properties ablak vagy DataSource tulajdonsg).
private void Form1_Load(object sender, EventArgs e) { string[] items = new string[] { "Szandra", "Istvn", "Bla", "Balzs" };

281

comboBox1.DataSource = items; }

Alaprtelmezett esemnye a SelectedIndexChanged, amely az aktulis elem vltozst jelli. A kivlasztott elemet illetve indext a ListBox-szal megegyez mdon krdezhetjk le. NumericUpDown Ezzel a vezrlvel egy adott szmtartomnybl vlaszthatunk ki egy rtket. A Minimum s Maximum tulajdonsgaival a legkisebb illetve legnagyobb lehetsges szmot, mg az Increment propertyvel a lptket llthatjuk be.

Alaprtelmezett esemnye a ValueChanged, amelyet a mutatott rtk vltozsa vlt ki. ProgressBar A ProgressBar egy folyamat jellsre szolgl. A NumericUpDown-hoz hasonlan a Maximum, Minimum s Step tulajdonsgokkal llthatjuk rtkeit. Helyezznk el az alkalmazsunkban egyet, lltsuk a Step rtket 1re, s tegynk mell egy gombot is! A gomb esemnykezeljbe rjuk a kvetkezt:
private void button1_Click(object sender, EventArgs e) { for (int i = 0; i < 100; ++i) { progressBar1.PerformStep(); System.Threading.Thread.Sleep(100); } }

A PerformStep metdus a Step rtk szerint lpteti a vezrlt.

Minden lpsben pihentetjk kicsit a fszlat, hogy lelasstsuk a folyamatot.

282

PictureBox Ezt a vezrlt kpek megjelentsre hasznljuk. Ktflekppen adhatjuk meg a kvnt elemet: a terveznzetben a vezrln megjelen kis nyilat kinyitva a Choose Image menpontban az Import gombokkal vlaszthatunk begyazott illetve kls erforrsbl. Van egy msik lehetsgnk is: forrskdbl egy Image objektumot rendelhetnk az Image tulajdonsghoz.
private void Form1_Load(object sender, EventArgs e) { Image img = new Bitmap("Desert.jpg"); pictureBox1.Image = img; }

Alaprtelmezett esemnye a kattints. RichTextBox A TextBox-hoz hasonlan szveges bevitelre alkalmas, m annl lnyegesen rugalmasabb s tbb lehetsget is nyjt. Szveget rendelhetnk hozz a Text tulajdonsgn keresztl, txt vagy rtf fjlokbl. Ez utbbi esetben a klnfle formzsokat (betszn, bettpus stb.) is tmogatja. Alaprtelmezett esemnye a TexChanged. Az AppendText metdussal a mr ltez tartalomhoz tudunk hozzfzni karaktereket. Fjlokat a LoadFile metdussal tlthetnk be. Ennek prja a SaveFile, amellyel tbb formtumba is menthetnk:
// szveges llomny richTextBox1.SaveFile("test.txt", RichTextBoxStreamType.PlainText); //rtf ffl richTextBox1.SaveFile("test.rtf", RichTextBoxStreamType.RichText);

A vezrl lehetsget ad a szveg formzshoz is, ehhez a Font tulajdonsgt llthatjuk, ekkor az sszes ltez szveg formzsa megvltozik. Ha csak a kijellt szveget szeretnnk lltani, akkor a SelectionFont tulajdonsgon keresztl tehetjk ezt meg.
Font font = new Font("Arial", 12, FontStyle.Bold); richTextBox1.SelectionFont = font;

Ugyangy dolgozhatunk a tbbi Selection eltag tulajdonsggal.

DateTimePicke Ezzel a vezrlvel lehetsgnk van egy adott dtum kivlasztsra.

283

Tulajdonkppen kt rszbl ll, egy TextBox s egy gomb, amellyel megjelenthetjk a naptrat, illetve maga a naptr vezrl. Alaprtelmezett esemnye a ValueChanged:
private void dateTimePicker1_ValueChanged(object sender, EventArgs e) { MessageBox.Show(dateTimePicker1.Value.ToString()); }

A Value tulajdonsga DateTime tpussal tr vissza. MenuStrip Mensort kszthetnk vele, a Toolbox-ban a Menus & ToolBars szekciban talljuk meg. A tervez nzetben azonnal fel is tlthetjk elemekkel, illetve a helyi ment kinyitva bellthatjuk a megjelenst, illetve hogy hol helyezkedjen el. Az egyes menpontokhoz (amelyek ToolStripMenuItem tpusak) esemnykezelt rendelhetnk (az alaprtelmezett esemnyk a kattints).
private void subMenu1ToolStripMenuItem_Click(object sender, EventArgs e) { ToolStripMenuItem menu = (ToolStripMenuItem) sender; MessageBox.Show(menu.Text); // kirjuk a mne feliratt }

Prbeszdablakok Hasznlhatunk elre megrt dialgusablakokat, mint pldul a fjl megnyitsa/elmentse. A lehetsges ablakokat megtalljuk a ToolBox-ban is a Dialogs szekciban, s rhzhatjuk ket az alkalmazsunkra, ekkor azok osztlytagknt elrhetv vlnak. Azonban clszerbb ket helyben pldnyostani, csakis akkor, ha

284

szksg van rjuk. Ezt a kvetkezkppen tehetjk meg (feltesszk, hogy egy gomb megnyomsakor megnylik az adott ablak):
private void button1_Click(object sender, EventArgs e) { using (OpenFileDialog dialog = new OpenFileDialog()) { if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { MessageBox.Show(dialog.FileName); } } }

Az OpenFileDialog lehetsget ad kivlasztani egy fjlt. A ShowDialog hvsakor megjelenik a fjlvlaszt ablak. Minden mvelet (OK gomb, ablak bezrsa stb.) egy DialogResult felsorols-tpus eredmnyt ad vissza, ebben az esetben csak arra vagyunk kvncsiak, hogy a felhasznl megnyit egy fjlt. A FileName illetve FileNames (ha tbb fjlt nyitunk meg) a fjlok nevt adja vissza teljes elrsi ttal.

285

GYAKORL FELADATOK V.
SZMOLGP
Ksztsnk szmolgp alkalmazst, amely Windows Forms alatt fut! A programnak kt szmon kell elvgeznie az alapmveleteket. Megolds Ksztsnk egy j projektet, s vltsunk tervez nzetbe! Hzzunk 15 gombot s egy TextBox-ot az ablakra, 10et a szmoknak, ngyet az alapmveletek s egyet az egyenlsgjel rszre, a szvegmezben pedig a kpletet s az eredmnyt jelentjk majd meg. Valahogy gy kell most kinznie a programunknak:

Jelljk ki az sszes szmot tartalmaz gombot, kivve az egyenlsgjelet tartalmazt, s rendeljk hozzjuk ugyanazt az esemnykezelt (Click):
private void button13_Click(object sender, EventArgs e) { textBox1.Text += ((Button)sender).Text; }

Nem csinlunk itt mst mint, hogy hozzfzzk a szvegdobozhoz a soron kvetkez karaktert. Egy hibalehetsg, hogy bizony tbb opertor karaktert is berhatunk gy, ezt az olvasnak kell megoldania, egyszer felttellel. Az eredmnyt egy kln esemnykezelben szmoljuk ki, amit az egyenlsg gombhoz rendelnk:
private { int int int void button15_Click(object sender, EventArgs e) opIdx = textBox1.Text.IndexOfAny(new char[] { '+', '-', '/', '*' }); x = int.Parse(textBox1.Text.Substring(0, opIdx)); y = int.Parse(textBox1.Text.Substring(opIdx + 1));

int result = 0; switch(textBox1.Text[opIdx]) { case '+': result = x + y; break; case '-': result = x - y; break; case '/':

286

result = x / y; break; case '*': result = x * y; break; } textBox1.Text += "=" + result; }

Az els sorban meghatrozzuk az opertor indext a stringben, hogy sztvlaszthassuk a szmokat, amit a msodik s harmadik sorban tesznk meg a Substring fggvnnyel. Figyeljnk az indexekre, a msodik szm az opertor indexe utn kvetkezik! Vgl egy switch szerkezettel kiszmoljuk az eredmnyt.

SZVEGSZERKESZT
Ksztsnk egyszer szvegszerkeszt programot, amellyel dokumentumokat, illetve legyen lehetsg alapvet formzsokra is! Megolds Helyezznk el a tervez nzetben egy MenuStrip illetve egy RichTextBox vezrlt, hogy az albbi kphez hasonl eredmnyt kapjunk: betlthetnk, illetve elmenthetnk

Ksztsk el az egyes menpontokhoz az esemnykezeljket (Click)! A Megnyits menpont:

287

private void openToolStripMenuItem_Click(object sender, EventArgs e) { using (OpenFileDialog dialog = new OpenFileDialog()) { dialog.Filter = "Szveges Fjlok| *.txt|RTF Fjlok|*.rtf|All Files|*.*"; if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { richTextBox1.LoadFile(dialog.FileName); } } }

A Filter tulajdonsg segtsgvel megadhatjuk, milyen fjltpusok jelenjenek meg a dialgusablakban. Jjjn a Ments:
private void saveToolStripMenuItem_Click(object sender, EventArgs e) { using (SaveFileDialog dialog = new SaveFileDialog()) { if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { richTextBox1.SaveFile(dialog.FileName); } } }

Vgl a kt formz menpont:


private void italicToolStripMenuItem_Click(object sender, EventArgs e) { FontStyle style = FontStyle.Italic; richTextBox1.SelectionFont = new Font(richTextBox1.Font, style); } private void boldToolStripMenuItem_Click(object sender, EventArgs e) { FontStyle style = FontStyle.Bold; richTextBox1.SelectionFont = new Font(richTextBox1.Font, style); }

Az alkalmazs tetszlegesen kiegszthet a fentiek mintjra. Javasolt fejlesztsek: bettpus megadsa, betmret lltsa, nyomtats stb.).

288

You might also like