Előszó
Általában ez az a rész, ahol a szerző bemutatkozik, kifejti a motivációit illetve köszönetet mond a környezete segítségéért. Nem fogok nagy meglepetést okozni, ez most is így lesz. Az elmúlt két évben, mióta a jegyzet létezik rengeteg levelet kaptam, különféle témában. Egy közös pont viszont mindegyikben volt: a levélírók egy idősebb emberre számítottak, számos esetben tanárnak gondoltak. Ez alapvetően nem zavar, sőt jól esik, hiszen ez is bizonyítja, hogy sikerült egy „érett”, mindenki számára emészthető könyvet készítenem. Most viszont - abból az alkalomból, hogy a jegyzet életében ekkora esemény történt, úgy érzem ideje „hivatalosan” bemutatkoznom: Reiter István vagyok, 24 éves programozó. Bő tíz éve foglalkozom informatikával, az utóbbi hatot pedig már a „sötét” oldalon töltöttem. Elsődlegesen (vastag)kliens oldalra specializálódtam ez ebben a pillanatban a WPF/Silverlight kettőst jelenti - bár előbbi közelebb áll a szívemhez. Jelenleg - munka mellett - az ELTE Programtervező Informatikus szakán folytatok tanulmányokat. 2008-ban elindítottam szakmai blogomat a „régi” msPortal-on - ez ma a devPortal akadémiai szekciójaként szolgál - és ekkor született meg bennem egy kisebb dokumentáció terve, amely összefoglalná, hogy mit kell a C# nyelvről tudni. Elkezdtem írni, de az anyag egyre csak nőtt, terebélyesedett és végül megszületett a jegyzet első százegynéhány oldalas változata. A pozitív fogadtatás miatt folytattam az írást és néhány hónap után az eredeti, viszonylag összeszedett jegyzetből egy óriási, kaotikus, éppenhogy használható „massza” keletkezett. Ez volt az a pont, ahol lélekben feladtam az egészet, nem volt kedvem, motivációm rendberakni. Eltelt több mint fél év, megérkezett 2010 és elhatároztam, hogy - Újévi fogadalom gyanánt - feltámasztom a „szörnyeteget”. Az eredeti jegyzet túl sokat akart, ezért úgy döntöttem, hogy kiemelem az alapokat - ez gyakorlatilag a legelső változat - és azt bővítem ki. Ez olyannyira jól sikerült, hogy közel háromszoros terjedelmet sikerült elérnem a kiinduláshoz képest. Már csak egy dologgal tartozom, köszönetet kell mondjak a következőknek: Mindenkinek aki az elmúlt két évben tanácsokkal, kiegészítésekkel, javításokkal látott el. Mindenkinek aki elolvasta vagy el fogja olvasni ezt a könyvet, remélem tetszeni fog. A devPortal közösségének A Microsoft Magyarországnak

-2-

Tartalomjegyzék
1 Bevezető ............................................................................................................................. 9 1.1 A jegyzet jelölései........................................................................................................ 9 1.2 2 Jogi feltételek ............................................................................................................... 9 Microsoft .NET Framework ............................................................................................. 10 2.1 A .NET platform ........................................................................................................ 10 2.1.1 2.1.2 2.1.3 2.2 2.3 MSIL/CIL ........................................................................................................... 10 Fordítás és futtatás .............................................................................................. 11 BCL .................................................................................................................... 11

A C# programozási nyelv .......................................................................................... 11 Alternatív megoldások ............................................................................................... 12 SSCLI ................................................................................................................. 12 Mono................................................................................................................... 12 DotGNU ............................................................................................................. 13

2.3.1 2.3.2 2.3.3 3

“Hello C#!”....................................................................................................................... 14 3.1 A C# szintaktikája...................................................................................................... 15 3.1.1 3.1.2 3.2 Kulcsszavak ........................................................................................................ 15 Megjegyzések ..................................................................................................... 16

Névterek..................................................................................................................... 17

4

Változók ........................................................................................................................... 18 4.1 Deklaráció és definíció .............................................................................................. 18 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 4.10 Típusok ...................................................................................................................... 18 Lokális és globális változók....................................................................................... 20 Referencia- és értéktípusok........................................................................................ 20 Referenciák ................................................................................................................ 22 Boxing és unboxing ................................................................................................... 23 Konstansok ................................................................................................................ 25 A felsorolt típus ......................................................................................................... 25 Null típusok ............................................................................................................... 27 A dinamikus típus................................................................................................... 28

5

Operátorok ........................................................................................................................ 30 5.1 Operátor precedencia ................................................................................................. 30 5.2 5.3 5.4 5.5 5.6 5.7 Értékadó operátor....................................................................................................... 31 Matematikai operátorok ............................................................................................. 32 Relációs operátorok ................................................................................................... 32 Logikai és feltételes operátorok ................................................................................. 33 Bit operátorok ............................................................................................................ 36 Rövid forma ............................................................................................................... 39 -3-

5.8 6

Egyéb operátorok ....................................................................................................... 40

Vezérlési szerkezetek ....................................................................................................... 42 6.1 Szekvencia ................................................................................................................. 42 6.2 6.3 Elágazás ..................................................................................................................... 42 Ciklus ......................................................................................................................... 45 Yield ................................................................................................................... 50 Párhuzamos ciklusok .......................................................................................... 50

6.3.1 6.3.2 7

Gyakorló feladatok ........................................................................................................... 52 7.1 Szorzótábla ................................................................................................................ 52 7.2 7.3 7.4 Számológép................................................................................................................ 55 Kő – Papír – Olló ....................................................................................................... 57 Számkitaláló játék...................................................................................................... 59

8

Típuskonverziók ............................................................................................................... 64 8.1 Ellenőrzött konverziók............................................................................................... 64 8.2 8.3 Is és as ........................................................................................................................ 65 Karakterkonverziók ................................................................................................... 66

9 10

Tömbök............................................................................................................................. 67 9.1 Többdimenziós tömbök ............................................................................................. 68 Stringek............................................................................................................................. 71 10.1 Metódusok .............................................................................................................. 72 10.2 10.3 StringBuilder .......................................................................................................... 73 Reguláris kifejezések.............................................................................................. 74

11

Gyakorló feladatok II. ...................................................................................................... 77 11.1 Minimum- és maximumkeresés ............................................................................. 77 11.2 11.3 11.4 Szigetek .................................................................................................................. 77 Átlaghőmérséklet ................................................................................................... 79 Buborékrendezés .................................................................................................... 79

12

Objektum-orientált programozás - elmélet....................................................................... 81 12.1 UML ....................................................................................................................... 81 12.2 12.3 12.4 12.5 12.6 Osztály.................................................................................................................... 81 Adattag és metódus ................................................................................................ 82 Láthatóság .............................................................................................................. 82 Egységbezárás ........................................................................................................ 83 Öröklődés ............................................................................................................... 83

13

Osztályok .......................................................................................................................... 85 13.1 Konstruktorok......................................................................................................... 86 13.2 13.3 13.4 Adattagok ............................................................................................................... 89 Láthatósági módosítók ........................................................................................... 90 Parciális osztályok .................................................................................................. 90 -4-

13.5 13.6 13.7 13.7.1 14

Beágyazott osztályok.............................................................................................. 92 Objektum inicializálók ........................................................................................... 93 Destruktorok ........................................................................................................... 93 IDisposable ....................................................................................................... 100

Metódusok ...................................................................................................................... 102 14.1 Paraméterek .......................................................................................................... 104 14.1.1 14.1.2 14.2 14.3 Alapértelmezett paraméterek ............................................................................ 109 Nevesített paraméterek ..................................................................................... 110 Visszatérési érték.................................................................................................. 110 Kiterjesztett metódusok ........................................................................................ 111

15 16 17

Tulajdonságok ................................................................................................................ 113 Indexelők ........................................................................................................................ 115 Statikus tagok ................................................................................................................. 117 17.1 Statikus adattag .................................................................................................... 117 17.2 17.3 17.4 17.5 Statikus konstruktor.............................................................................................. 118 Statikus metódus .................................................................................................. 120 Statikus tulajdonság.............................................................................................. 120 Statikus osztály..................................................................................................... 120

18

Struktúrák ....................................................................................................................... 122 18.1 Konstruktor........................................................................................................... 122 18.2 18.3 18.4 18.5 Destruktor ............................................................................................................. 123 Adattagok ............................................................................................................. 124 Hozzárendelés ...................................................................................................... 124 Öröklődés ............................................................................................................. 126

19

Gyakorló feladatok III. ................................................................................................... 127 19.1 Faktoriális és hatvány ........................................................................................... 127 19.2 19.3 19.4 Gyorsrendezés ...................................................................................................... 128 Láncolt lista .......................................................................................................... 130 Bináris keresőfa .................................................................................................... 131

20

Öröklődés ....................................................................................................................... 136 20.1 Virtuális metódusok ............................................................................................. 138 20.2 20.3 20.4 Polimorfizmus ...................................................................................................... 140 Lezárt osztályok és metódusok ............................................................................ 141 Absztrakt osztályok .............................................................................................. 141

21

Interfészek ...................................................................................................................... 144 21.1 Explicit interfészimplementáció........................................................................... 146 21.2 Virtuális tagok ...................................................................................................... 147 Operátor kiterjesztés ....................................................................................................... 149 22.1 Egyenlőség operátorok ......................................................................................... 150 -5-

22

.................................................................................................................................... 161 Mátrix típus ............1 Generikus metódusok ........................................ 173 26................................ 169 26 Generikusok............................................................... 157 Finally blokk ............................................................................................... 194 29..................................... 159 24......................................................................................................................................... U> .. 182 ReadOnlyCollection<T> ......... 156 Kivételek továbbadása...............................1 Paraméter és visszatérési érték .......................4 26. 162 25 Delegate–ek ............ 191 Unsafe kód ...................................... 181 Dictionary<T..........1 Fix objektumok ............4 Kifejezésfák........................... 174 Generikus megszorítások ........................................................................................................... 188 Lambda kifejezések változóinak hatóköre .............................. 156 23.................8 27 Generikus osztályok ............................... 164 25.............3 23........................................................................................... 158 24 Gyakorló feladatok IV.............................................. Események...................2 27................................................3 26........................ 178 Generikus gyűjtemények ............................................................................................... 159 24..........................3 IComparable és IComparer ...........................................6................................................1 Kivétel hierarchia ................................................................................................................................................ U> és SortedDictionary<T................................. 176 Öröklődés ..5 26.............................3 27................................................ 152 Kompatibilitás más nyelvekkel ......................... 182 LinkedList<T>.............................................................4 Kivétel készítése.............................6..................................................................... 189 28 29 Attribútumok ....................... 153 Kivételkezelés.... 196 -6- ....................................................................................................................4 26...........................7 26................................................................................................................................................................................................ 173 26.......................................... U> ..... ..........................................6 26................ 178 List<T> ....................6............................................................... 178 Statikus tagok .................................................................................................................................................................... 183 Generikus interfészek...................................... 152 Konverziós operátorok .......... 151 Relációs operátorok ............. 186 27.........1 IEnumerator és IEnumerable......... delegate –ek és események.................. 186 27.....3 22.................... 167 25... 184 Lambda kifejezések ...............................................................2 24.............................2 26....2 26.........................................................................................................................................................................................................5 26.......................6....................................................2 Névtelen metódusok ................................ 183 Kovariancia és kontravariancia .................................22........................................ 154 23...............................................................2 22.........................1 26........................... 179 SortedList<T.......................................................3 26........5 23 A ++/-................................................ 168 25............4 22.................................................................................................................................6...............operátorok .......................2 23................................................................ 188 Névtelen metódusok kiváltása lambda kifejezésekkel ..............................................1 Generikus kifejezések .................

................ 250 35........ 258 Null értékek kezelése......................................................... 243 Aszinkron socketek ...................................5 35.............3............... 201 Aszinkron delegate-ek ..................................................................4 35...4 Blokk elkerülése ..............................................................6 30..............................................6 Kiválasztás ............................................................................................................................................ 202 Párhuzamos delegate hívás ........ 251 Projekció ............................................1 Socket .....2 30 Natív DLL kezelés ...........5...2 30.. 216 Állománykezelés ........................................................................ 232 Hálózati programozás ......................................... 229 33 34 Konfigurációs file használata ............................................... 260 Összetett kulcsok .......................3 35.........................2 35.....................1 Konfiguráció-szekció készítése ...................................................................1 34....6 Könyvtárstruktúra kezelése ...............................................................................4 32......... 255 Rendezés.....................1 35........................................................2 32........................................................................................................................................ 208 Szinkronizáció .............................5 30..................................................... 235 34................................................................. 231 33............................................................. 218 32.............5 32................3....................................... 207 Foreground és background szálak ........................................................................................1 Application Domain -ek ........... 245 Szálakkal megvalósított szerver .................................... 235 34............................................................................................... 206 Szálak létrehozása ......................................... 254 Let ...3 32................................................3................................ 223 XML .................2 34..............................................................................................................................................................................................................3 34.....................1 35................................3 34........ 224 XML DOM............................................................................................................................................................................................................ 241 Több kliens kezelése ...................5..1 30...................................................3.. 218 32..........................................................................................................................................2.......................................................................................................................................................................... 199 30.2 35.............. 250 35....... 221 In–memory streamek ............ 260 Listák összekapcsolása ...................................... 249 35 LINQ To Objects .................................... 255 Szűrés ..........................................................4 30............................................................. 213 31 32 Reflection ............................................................................................. 257 Csoportosítás ........ 197 Többszálú alkalmazások................................29.....1 Nyelvi eszközök ............................................................................. 262 -7- .....................................2................................................................................. 243 Select ....................................... 246 TCP és UDP .........................................2 34.................................................................................. 227 XML szerializáció .......................................................................................................................................................................7 Szálak ...................................................................... 201 30............................................................................................. 209 ThreadPool .....................................................................................2 35..........................1 Olvasás/írás fileból/fileba.........................................................................3 30......

.............3 36............... 269 Többszálúság vs............. 275 36... 273 AsSequential ........ 275 36.....................12............................. 264 „Element” operátorok........................................... 268 PLINQ – Párhuzamos végrehajtás ......12 Outer join... 266 Halmaz operátorok ....................................................................7 35........ 278 Debug .............9 35.......................... 283 -8- .....................................8 35.......................... 274 35.................................................................................................... 267 Aggregát operátorok........................................................................... 282 37 Osztálykönyvtár.................................4 35...................................................12..........................12.............................................................................................3 35................................................................ 269 PLINQ a gyakorlatban ............................................................1 Az első lépések .................................................................. Párhuzamosság .................................10 35...................................................35...........................................11 35................................................2 35.................4 Felület .......................................12............................................................................................................5 36 Visual Studio .................................................................... 269 Teljesítmény ..................................................... 263 Konverziós operátorok ................................................................... 270 Rendezés ...... 280 Debug és Release .....................................................................1 35....................................................................12............................................2 36.....................................................

skydrive. A jegyzet a C# 2. 1. ha valamit nem ért. ezért ne essen kétségbe a kedves olvasó.0.com e-mail címre. bekeretezve Parancssor: fekete alapon. viszont alapvető informatikai ismeretek (pl.live. számrendszerek) jól jönnek.5 Magyarország liszensze alá tartozik.com/browse. hogy megismertesse az olvasóval ezt a nagyszerű technológiát. ha rátalált a válaszra. 3. javaslatot és hibajavítást szívesen várok a reiteristvan@gmail.NET Framework és egyik fő nyelve a C#.2 Jogi feltételek A jegyzet teljes tartalma a Creative Commons Nevezd meg!-Ne add el! 2. mindennemű értékesítési kísérlet tiltott és a szerző beleegyezése nélkül történik! -9- . Ez a jegyzet abból a célból született.0 és 4. bekeretezve Megjegyzés: fehér alapon. Szabadon módosítható és terjeszthető a forrás feltüntetésével.1 Bevezető Napjainkban egyre nagyobb teret nyer a .0 verziójával foglalkozik. Néhány fejezet feltételez olyan tudást. A jegyzet megértéséhez nem szükséges programozni tudni. keret nélkül 1. A jegyzet ingyenes.1 A jegyzet jelölései Forráskód: szürke alapon.aspx/Nyilv%C3%A1nos/Jegyzet Bármilyen kérést. az utóbbi kettő által bevezetett új eszközöket az adott rész külön jelöli. A jegyzethez tartozó forráskódok letölthetőek a következő webhelyről: http://cid-283edaac5ecc7e07. amely alapját egy későbbi rész képezi. egyszerűen olvasson tovább és térjen vissza a kérdéses anyaghoz.

amely maga is több részre oszlik:  A CTS (Common Type System) az adatok kezelését. natív kódra fordulnak le.2 Microsoft . így számtalan fejlesztő döntött úgy. csak Microsoft köntösben. nem beszélve a számtalan hobbifejlesztésről. a memóriafoglalásért vagy a kivételek kezeléséért. a memóriában való megjelenést. hanem eg y környezet. 50 nyelvnek létezik hivatalosan . írja le. Az addigi programnyelvek/platformok különböző okokból nem tudták felvenni a Java –val a versenyt.  A VES (Virtual Execution System) a futási környezetet specifikálja. Gyakorlatilag bármelyik programozási nyelvnek lehet . a C++ – megírt programok ún.NET (akárcsak a Java) más úton jár. stb. Ez a nyelv a . Részben a piac visszaszerzésének érdekében a Microsoft a kilencvenes évek végén elindította a Next Generation Windows Services fedőnevű projektet.NET Framework A kilencvenes évek közepén a Sun MicroSystems kiadta a Java platform első nyilvános változatát. a Hewlett Packard. a fordító először egy köztes nyelvre (Intermediate Language) fordítja le a forráskódot. 2. az Intel és mások közreműködésével megfogalmazott CLI (Common Language Infrastructure) egy implementációja. A .NET megfelelője. A valóságban nincs . mellette pedig ott a keretrendszer.NET platform Maga a . amely felelős pl. minden Windows rendszer részét képezi és számos . nevezik CLR nek (Common Language Runtime) is. az egymással való interakciót. felügyelt (vagy managed) kódot használ. 2.10 - . Ez abból a szintén téves elképzelésből alakult ki.NET.NET implementációja. hogy a kényelmesebb és sokoldalúbb Java –t választja.NET virtuális gép.  A CLS (Common Language Specification) a CLI kompatibilis nyelvekkel kapcsolatos elvárásokat tartalmazza. amely a kissé elavult és nehézkesen programozható COM platformot hívatott leváltani (ettől függetlenül a COM ma is létező viszonylag népszerű eszköz – ez főleg a hatalmas szoftverbázisnak köszönhető.NET világában az .NET ugyanaz. A CLI egy szabályrendszer.NET könyvtár is épít rá).1 A . ame lyből aztán megszületett a .NET platform a Microsoft. A . mint a Java. hogy a . Jelenleg kb. helyette ún.1 MSIL/CIL A “hagyományos” programnyelveken – mint pl.NET nem egy programozási nyelv. vagyis a processzor számára – kis túlzással – azonnal értelmezhetőek.1. vagyis a program teljes mértékben natív módon. Általános tévhit. közvetlenül a processzoron fut. hogy a VES/CLR –t virtuális gépként azonosítják.

3 BCL A . ami az alapvető feladatok (file olvasás/ írás. osztályok szerkezete.2 Fordítás és futtatás A natív programok ún. stb…) elvégzéséhez szükséges eszközöket tartalmazza.).NET Framework –nek szóló utasításokat tartalmaz.exe (futtatható állomány) vagy .1. stb…) ezekre épül. hogy nincs köztük különbség. típus biztos.NET Framework telepítésével a számítógépre kerül – többek között – a BCL (Base Class Library). akkor egy ún.NET fő programozási nyelve. Igaz. illetve a szabványosítás után a CIL (MICROSOFT/CommonIL) – különbség csak az elnevezésben van. adatbázis kezelés.dll (osztálykönyvtár) kiterjesztéssel. általános felhasználású nyelv. 2. Amikor futtatjuk ezeket az állományokat . lefordítja őket gépi kódra. Metadata) amelyek a futtató környezetnek szolgálnak információval (pl. Bizonyos területeken viszont egyik vagy másik megközelítés jelentős eltérést eredményezhet. Másfelől a felügyelt környezet a hatékonyabb memóriakezelés miatt jobban teljesít olyan helyzetekben ahol nagy mennyiségű adatot mozgatunk a memórián belül (pl. amelyek elkerülése a felügyelt környezetben kiegyenlíti az esélyeket. Jogos a kérdés. de napjainkban a legnagyobb hatékonyságot a Microsoft implementációja biztosítja. adatszerkezetek. gépi kódra fordulnak le. A C# tisztán objektumorientált. . 1999 – ben Anders Hejlsberg vezetésével kezdték meg a fejlesztését.NET.2 A C# programozási nyelv A C# (ejtsd: szí-sárp) a Visual Basic mellett a . amit a processzor már képes kezelni. hogy a két módszer közül melyik a jobb? Ha nagy általánosságban beszélünk.MSIL. Egy Assembly egy vagy több fileból is állhat. metódusai. WCF. stb. 2. JIT (Just–In–Time) fordító veszi kezelésbe. Jó példa a számítógépes grafika ahol a natív nyelvek vannak előnyben pont azért.11 - . először az ún. Az összes többi könyvtár (ADO. 2. számos rendező algoritmus ilyen). akkor a válasz az. illetve megvalósított típusok adatait (ez az ún. Amikor “először” fordítjuk le a programunkat. Ez tartalmazza a felhasznált. Ez a kód a feltelepített .NET forráskódokból egy CIL nyelvű futtatható állomány keletkezik. A nyelv elméletileg platform független (létezik Linux és Mac fordító is).1. hogy a natív nyelvek hardver-közelibbek és emiatt gyorsabbak tudnak lenni. tipikusan . Assembly (vagy szerelvény) keletkezik. A tervezésénél a lehető legnagyobb produktivitás elérését tartották szem előtt. viszont ez több hibalehetőséggel is jár. míg a . mert az ilyen számításigényes feladathoz minden csepp erőforrást ki kell préselni a hardverből.

aspx?FamilyId=8C09FD61-3F26-4555-AE173121B4F51D4D&displaylang=en 2. Az SSCLI projekt jelen pillanatban leállni látszik. bár eddig még nem sikerült teljes mértékben reprodukálni az eredetit.com/downloads/details. UNIX.2.microsoft. néhol jelentős eltérések vannak a valósághoz képest. Linux. keresztplatformos változata a . jelen pillanatban valamivel a . A Ximian (amelyet Icaza és Nat Friedman alapított) felkarolta az ötletet és 2001 júliusában hivatalosan is elkezdődött a Mono fejlesztése. Napjainkban a Mono mutatja a legígéretesebb fejlődést. hogy a Microsoft időközben számos . A Mono elérhető Windows.2 Mono A Mono projekt szülőatyja Miguel de Icaza. a specifikációban nem szereplő változtatást végzett a keretrendszeren. ezen ismeretek birtokában pedig több független csapat vagy cég is létrehozta a saját CLI implementációját.0 verzió már Novell termékként készült el egy évvel később. amely – bár nem elsősorban a . Ez a rendszer nem szolgáltatja az eredeti keretrendszer teljes funkcionalitását. Ettől függetlenül a forráskód és a hozzá tartozó dokumentációk rendelkezésre állnak. Ezen céljukat nehezíti. 2003 –ban a Novell felvásárolta a Ximian –t. Az SSCLI Windows. Az SSCLI –t kimondottan tanulási célra készítette a Microsoft. Ugyanakkor a szabványosítás után a CLI specifikáció nyilvános és bárki számára elérhető lett.0 mögött jár. mint a Microsoft .NET 2. Ehelyett ajánlott a C# nyelv fejlesztői által készített C# referencia.3. egyedül a piaci értékesítést tiltja meg.NET Framework jelen pillanatban csak és kizárólag Microsoft Windows operációs rendszerek alatt elérhető.1 SSCLI Az SSCLI (Shared Source Common Language Infrastructure) vagy korábbi nevén Rotor a Microsoft által fejlesztett nyílt forrású. BSD.3.NET Frameworknek (tehát nem az eredeti lebutított változata). Mac OSX és Solaris rendszereken is. 2000 –ben kezdte meg a fejlesztést és egy évvel később mutatta be ez első kezdetleges C# fordítót.3 Alternatív megoldások A Microsoft .NET –hez készült – értékes információkat tartalmaz.12 - .NET . az 1. ezért a liszensze engedélyez mindenfajta módosítást. letölthetőek a következő webhelyről: http://www. FreeBSD és Mac OSX rendszereken fut. 2. A “hivatalosnak” tekinthető ECMA szab vány nem feltétlenül tekinthető tökéletes útmutatónak a keretrendszer megértéséhez.

A DotGNU saját CLI megvalósításának a Portable .NET nevet adta. A DotGNU hivatalos oldala: http://www.jövőbeli “ellenfele”. Ez a projekt – szemben a Mono –val – nem a Microsoft BCL –lel való kompatibilitást helyezi előtérbe.3. illetve keresztplatformos társa. hanem az eredeti szabvány pontos és tökéletes implementációjának a létrehozását.org/software/dotgnu/ .13 - .com/Main_Page 2.3 DotGNU A DotGNU a GNU projekt része. A Mono emblémája egy majmot ábrázol.mono-project. a szó ugyanis spanyolul majmot jelent. A jegyzet írásának idején a projekt leállni látszik.gnu. amelynek célja egy ingyenes és nyílt alternatívát nyújtani a Microsoft implementáció helyett. A Mono hivatalos oldala: http://www.

ReadKey(). hogy a C# fordító melyik verziójára van szükségünk).5.3 “Hello C#!” A híres “Hello World!” program elsőként Dennis Ritchie és Brian Kerningham “A C programozási nyelv” című könyvében jelent meg és azóta szinte hagyomány. A rendszerváltozók listájából keressük ki a Path –t és kattintsunk a Szerkesztés gombra. azt indítsuk újra és írjuk be. Azt kell látnunk. ezért nevezzük is át. Ezután létrehozunk egy osztályt – mivel a C# teljesen objektumorientált –. tegyünk pár lépést a parancssorból való fordítás elősegítésére. Nyissuk meg vagy a v2. Nyomjuk meg az OK gombot és kész is vagyunk. Természetesen a szöveges file kiterjesztése . C: meghajtó.0 üzenete. hogy mit is tettünk: az első sor megmondja a fordítónak. A “HelloWorld” osztályon belül definiálunk egy Main nevű statikus függvényt. majd menjünk vissza a Path –hoz.txt.) Most már fordíthatunk a csc filenév.) és illesszük be az elérési utat. Ha van megnyitva konzol vagy PowerShell. hogy így le tudjunk fordítani egy forrásfilet. ezért ennek megfelelően módosítsuk a forráskódot: using System. hanem a C# nyelvet üdvözöljük. A változó értékének sorában navigáljunk el a végére.5 … (Az évszám és verzió változhat. Windows mappa. a v3. ami a programunk . ezért utasítást csak osztályon belül adhatunk meg. ez itt most a C# 3.0…. hogy használja a System névteret. azon belül Microsoft.WriteLine("Hello C#!"). Most nyissuk meg a Sajátgépet.14 - . Utóbbi lelőhelye: Vezérlőpult/Rendszer -> Speciális fül/Környezeti változók. class HelloWorld { static public void Main() { Console. Mi itt most nem a világot.cs paranccsal. Másoljuk ki a címsorból ezt a szép hosszú elérést.. hogy csc. kezdetű mappát (attól függően. } } Mielőtt lefordítjuk. Ahhoz. vagy meg kell adnunk a fordítóprogram teljes elérési útját (ez a mi esetünkben elég hosszú) vagy a fordítóprogram könyvtárát fel kell venni a PATH környezeti változóba. írjunk egy pontosvesszőt (. mivel a C# forráskódot tartalmazó fileok kiterjesztése: .hogy: Microsoft ® Visual C# 2008 Compiler Version 3.cs Nézzük. Console.. hogy egy programozási nyelv bevezetőjeké nt ezt a programot mutatják be.NET/Framework. stb.

mert az egyes fordítóprogramok csak ezekkel a szabályokkal létrehozott kódot tudják értelmezni. Minden egyes C# program a Main függvénnyel kezdődik. 3.) áll  A kis. amik ismeretlenek lehetnek.writeline –t írtunk volna.és nagybetűk különböző jelentőséggel bírnak. .1 A C# szintaktikája Amikor egy programozási nyelv szintaktikájáról beszélünk. Az “int” egy beépített típus a neve is. de a jegyzet későbbi fejezeteiben mindenre fény derül majd. blokkokkal jelöljük ki.  A program egységeit (osztályok. metódusok. akkor azokra a szabályokra gondolunk.//hiba A legtöbb fejlesztőeszköz beszínezi a kulcsszavakat (is). Ez azért fontos. Ebben a bekezdésben szerepel néhány (sok) kifejezés. Előbbi kiírja a képernyőre a paraméterét.WriteLine helyett console. Ha a fenti kódban Console.1. ellenkező esetben a fordító hibát jelez. amelyek speciális jelentőséggel bírnak a fordító számára.) ún. aminek az “int” nevet akarjuk adni. A C# úgynevezett C-stílusú szintaxissal rendelkezik (azaz a C programozási nyelv szintaxisát veszi alapul). kapcsos zárójelek ({ és }) segítségével. azaz kulcsszó. int int. azaz a “program” és “Program” azonosítók különböznek. Ezeket az azonosítókat a saját meghatározott jelentésükön kívül nem lehet másra használni. ezt mindenképpen létre kell hoznunk. ezért könnyű elkerülni a fenti hibát. akkor a program nem fordulna le. amelyek megszabják a forráskód felépítését. 3.15 - . utóbbi vár egy billentyű leütésére. stb . Vegyünk például egy változót. Végül meghívjuk a Console osztályban lévő WriteLine és ReadKey függvényeket. tehát nem fog lefordulni a program. ez három fontos szabályt von maga után:  Az egyes utasítások végén pontosvessző (.belépési pontja lesz.1 Kulcsszavak Szinte minden programnyelv definiál kulcsszavakat.

metódusok. ami szintén az első célt szolgálja .ReadKey(). osztályok létrehozásánál: add ascending by descending equals from get global group in into join let on orderby partial Remove var Select where Set yield Value Néhányuk a környezettől függően más-más jelentéssel is bírhat. egy metódus leírása) magunknak vagy a többi fejlesztőnek. class HelloWorld { static public void Main() { Console.1. // Ez egy egysoros komment Console. Ezzel egyrészt üzeneteket hagyhatunk (pl. /* Ez egy többsoros komment */ } } . amelyeket a nyelv nem tart fenn speciális használatra. de különleges jelentéssel bírnak. Amennyiben lehetséges. kerüljük a használatukat “hagyományos” változók. Megjegyzéseket a következőképpen hagyhatunk: using System.16 - .2 Megjegyzések A forráskódba megjegyzéseket tehetünk. másrészt a kommentek segítségével dokumentációt tudunk generálni.WriteLine("Hello C#").A C# 77 kulcsszót 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 kívül létezik még 23 azonosító. csak éppen élvezhetőbb formában. a megfelelő fejezet bővebb információt ad majd ezekről az esetekről. 3.

Utóbbiakat nem lehet egymásba ágyazni: /* /* */ */ Ez a “kód” nem fordul le. ha valamilyen kifejező nevű névtérben vannak (pl.Valami A jegyzet első felében főleg a System névteret fogjuk használni. Ilyen nagyságrenddel elkerülhetetlen. amelyben a logikailag összefüggő osztályok.System. hogy a nevek ne ismétlődjenek. Névteret magunk is definiálhatunk. másrészt a fordító sem tudná. A kommenteket a fordító nem veszi figyelembe. tulajdonképpen a fordítóprogram első lépése. mikor mire gondolunk. vagy az azonosító elé írt teljes eléréssel hivatkozhatunk: using MyNameSpace.2 Névterek A . Ennek a problémának a kiküszöbölésére hozták létre a névterek fogalmát.Az egysoros komment a saját sora legvégéig tart. míg a többsoros a “/*” és “*/” párokon belül érvényes. Nyilván könnyebb megtalálni az adatbázis-kezeléshez szükséges osztályokat. stb. hogy a forráskódból eltávolít minden megjegyzést. Ekkor egyrészt nehéz eligazodni közöttük. a namespace kulcsszóval: namespace MyNameSpace { } Ezután a névtérre vagy a program elején a using kulcsszóval.17 - .Data). metódusok. .NET Framework osztálykönyvtárai szerény becslés szerint is legalább tízezer nevet. Egy névtér tulajdonképpen egy virtuális doboz. azonosítót tartalmaznak. vannak. //vagy MyNameSpace. 3.

). Értelemszerűen a deklaráció és a definíció egyszerre is megtörténhet.1 Deklaráció és definíció Egy változó (illetve lényegében minden objektum) életciklusában megkülönböztetünk deklarációt és definíciót. Egy változót a következő módon hozhatunk létre C# nyelve n: Típus változónév.18 - . // delaráció és definíció 4. A típus határozza meg. stb.2 Típusok A C# erősen (statikusan) típusos nyelv. mellettük ott a . hogy a program pontosan csak olyan műveletet hajthat végre amire valóban képes. A változók a memória egy (vagy több) cellájára hivatkozó leírók. a méretük és egy rövid leírás: . A deklaráció nak tartalmaznia kell a típust és azonosítót.4 Változók Amikor programot írunk. // deklaráció x = 10. Konvenció szerint a változónevek kisbetűvel kezdődnek. akkor szükség lehet tárolókra. ami azt jelenti. hogy egy változó milyen értékeket tartalmazhat. A változónév első karaktere csak betű vagy alulvonás jel (_) lehet. Amennyiben a változónév több szóból áll.NET megfelelőjük. ahová az adatainkat ideiglenesen eltároljuk. pirosAlma. // definíció int y = 11. hogy minden egyes változó típusának ismertnek kell lennie fordítási időben. Ezeket a tárolókat változóknak nevezzük. akkor célszerű azokat a szóhatárnál nagybetűvel “elválasztani” (pl. illetve mekkora helyet foglal a memóriában. int x. A következő táblázat a C# beépített típusait tartalmazza. a definícióban pedig megadjuk az objektum értékét. vanSapkaRajta. a többi karakter szám is. Lehetőleg kerüljük az ékezetes karakterek használatát. ezzel biztosítva azt. 4.

Előjel nélküli.WriteLine(message). Console. 8 bites egész szám (128.C# típus .Byte System. mint egy típustalan környezetet! Abban a pillanatban. amikor értéket rendeltünk a változóhoz .Int32 System.String System.Uint64 System.127) Előjeles.Uint32 System. 32 bites egész szám (– 2147483647.ReadKey().Double System.32767 Előjel nélküli.4294967295) Egyszeres pontosságú lebegőpontos szám Kétszeres pontosságú lebegőpontos szám Fix pontosságú 28+1 jegyű szám Előjeles.NET típus Méret (byte) Leírás byte char bool sbyte short ushort int uint float double decimal long ulong string object System.Int16 System. hogy úgy használhatjuk a nyelvet.255) Egy Unicode karakter Logikai típus.Object 1 1 1 1 2 2 4 4 4 8 8 8 8 N/A N/A Előjel nélküli 8 bites egész szám (0. Ez természetesen nem jelenti azt.. 2147483647).NET néven hivatkozunk egy típusra. class HelloWorld { static public void Main() { //string típusú változó. } } A C# 3.. 16 bites egész szám (32768.Int64 System. hogy egy metódus hatókörében deklarált változó típusának meghatározását a fordítóra bízzuk.SByte System.. 64 bites egész szám Unicode karakterek szekvenciája Minden más típus őse A forráskódban teljesen mindegy. vagy nehéz meghatározni a típust. Console.Decimal System. értéke igaz(1 vagy true) vagy hamis(0 vagy false) Előjeles.Uint16 System. amikor hosszú típusnévről van szó.Boolean System.0 már lehetővé teszi. hogy a “rendes” vagy a ... 16 bites egész szám (0. Ezt az akciót a var kulcsszóval kivitelezhetjük. benne a kiírandó szöveg string message="Hello C#". Alakítsuk át a “Hello C#” programot úgy..Char System. Általában olyankor tesszük ezt. 32 bites egész szám (0. 64 bites egész szám Előjel nélküli.Single System.19 - . hogy a kiírandó szöveget egy változóba tesszük: using System.65535) Előjeles.

amelynek semmiféle köze sincs a System. Az ilyen változók típusa nem változtatható meg.és értéktípusok A .3 Lokális és globális változók Egy blokkon belül deklarált változó lokális lesz a blokkjára nézve. // int típusú változó "string".Object nevű típusból származik. // int típusú változó z = 10. az úgy fog viselkedni. mert ezek összetett adatszerkezetek és így hatékony a kezelésük. az egyik a verem (stack). .4 Referencia. majd a végeredményt visszateszi a stack-be: int x=10. hogy a változó hatóköre a blokk jára terjed ki). Minden művelet a stack-et használja. Áthidalhatjuk a helyzetet statikus változók használatával.Object-hez). A C# nem rendelkezik a más nyelvekből ismerős globális változóval. amit a CLR tetszés szerint használhat. hogy hol deklaráltuk őket. A heap nem adatszerkezet. A stack egy ún. erről később szó lesz. A kettő közötti különbség leginkább a memóriában való elhelyezkedésben jelenik meg. ha egy másik függvényből vagy osztályból próbálnánk meg elérni. 4. pl. //fordítási hiba 4. x + y A verem: |11| |10| -- összeadás művelet--|21| A referencia-típusok minden esetben a halomban jönnek létre.NET minden típus direkt vagy indirekt módon a System. Az értéktípusok vagy a stack-ben vagy a heap-ben vannak attól függően. ha össze akarunk adni két számot akkor a CLR lerakja mindkettőt a stack-be és meghívja a megfelelő utasítást. amelyek a program bármely részéből elérhetőek. vagyis a program többi részéből nem látható (úgy is mondhatjuk. akkor a program nem fordulna le. kivenni pedig csak a mindenkori legfelső elemet tudjuk. A CLR két helyre tud adatokat pakolni. összeadja őket. de a megfelelő típuskonverziók végrehajthatóak. Globális változónak azokat az objektumokat nevezzük. és ezen belül szétoszlik érték. int y=11.20 - . mivel deklarációt csak osztályon belül végezhetünk. A fenti példában a message egy lokális változó. // fordítási hiba w. mint az ekvivalens típus.(ráadásul ezt azonnal meg is kell tennünk!). a másik a halom (heap). vagyis a legutoljára berakott elem lesz a tetején.és referencia-típusokra (egyetlen kivétel a pointer típus. Ezután kiveszi a verem legfelső két elemét. LIFO (last-in-first-out) adattár. hanem a program által lefoglalt nyers memória. int var z = var x = 10.

és mi referencia-típus: értéktípus lesz az összes olyan objektum.21 - . x pedig még mindig adattag. class MyClass { private int x = 10. stb.) A felsorolt típus (enum) Logikai típus (bool) Karakter típus (char) Struktúrák (struct) Referencia-típusok lesznek a következők:     Osztályok (class) Interfész típusok (interface) Delegate típusok (delegate) Stringek . hogy a verembe fog kerülni. } } Most egy kicsit bonyolultabb a helyzet. a referenciatípuson belül adattagként deklarált értéktípusok pedig a heap-ben foglalnak helyet. byte. double. } } Ebben a “programban” x–et lokálisan deklaráltuk egy metóduson belül. } Most x egy referencia-típuson (esetünkben egy osztályon) belüli adattag. amelyeket a következő típusokkal deklarálunk:      Az összes beépített numerikus típus (int. így a veremben fog tárolódni. public void MyMethod() { int y = 10.Metóduson belül. class MyClass { private int x = 10. Nézzünk néhány példát! using System. lokálisan deklaráltuk. de egy metódusban. lokálisan deklarált értéktípusok a stack-be kerülnek. Az y nevű változót egy referencia-típuson belül. class Program { static public void Main() { int x = 10. ezért a halomban foglal majd helyet. hogy mi lesz érték. ezért marad a halomban. Végül nézzük meg. ezért biztosak lehetünk benne.

hogy a p nevű referencia ugyanarra a memóriaterületre hivatkozik majd. mint az s. Minden olyan típus. ahol az objektum ténylegesen helyet foglal. MyClassp = s.x = 10. ezért fizikailag lehetetlen lenne az értékeikkel dolgozni. Mivel ők összetett típusok .Object–ből vagy bármely class kulcsszóval bevezetett szerkezetből. Console. p. ha p módosul.WriteLine(s. class MyClass { public int x. viszont amikor a második változónak értékül adjuk az elsőt. mint az előző forráskódnál. akkor az történik. hogy a két változó értéke egyenlő lesz. vagy egy teljesen más területre? Amikor egy értéktípusra hivatkozunk. Az első sorban létrehoztuk az x nevű változót. de nem ugyanazon a memóriaterületen helyezkednek el. a másodikban pedig egy új változónak adtuk értékül x–et. ahogyan a forráskódban hivatkozunk rájuk.22 - .5 Referenciák Az érték. vagyis tulajdonképpen s-nek egy álneve (alias) lesz. teljesen önálló változó.x). A kérdés az.x = 14. vagyis a kérdésünkre a válasz az. tehát y máshova mutat. ezért a fenti program kimenete 14 lesz. } } Vajon mit fog kiírni a program? Kezdjük az elejéről! Hasonló a felállás. Vegyük a következő kódot: int x = 10. ezért egy referencia-típusként létrehozott változó tulajdonképpen a memóriának arra a szeletére mutat. 4. . akkor s is így tesz. akkor ténylegesen az értékét használjuk fel.illetve referencia-típusok közötti különbség egy másik aspektusa az. s. hogy y hova mutat a memóriában: oda ahol x van. int y = x. A helyzet más lesz referencia-típusok esetében. } class Program { static public void Main() { MyClasss = new MyClass(). Nézzük meg ezt közelebbről: using System. Értelemszerűen. amely közvetlen módon származik a System.

metódustábla. A probléma az.) több helyet foglalnának. hogy a ToString–et a System. leánykori nevén System. Az ilyen kapcsolatot implicit konverzábilis kapcsolatnak . azt nagyon egyszerű kitalálni: az értéktípusok egyszerű típusok amelyek kis mennyiségű adatot tartalmaznak. ilyen módon a referencia-típusok mind rendelkeznek vele. illetve az összes szükséges információt ahhoz. ami a következőképpen működik: a rendszer előkészít a halmon egy – az értéktípus valódi típusának megfelelő – keretet (dobozt) amely tartalmazza az eredeti változó adatait. hogy referencia-típusként tudjon működni – lényegében az is. hogy a referencia-típusokhoz “adott” extrák (sync blokk. ami egy igen speciális helyzetet jelent. hogy minden típus közvetlenül vagy indirekt módon a System. hogy meghívja rajtuk a ToString metódust. Még nagyobb baj..23 - . Hogy miért van ez így. hogy érték. mint a tényleges adat).Object más szóval egy referencia-típus. A baj az. de ez nem feltétlenül van így. mint referenciatípusok esetében. nem tartozik hozzájuk semmiféle “túlsúly” (elméletileg akár az is előfordulhatna ilyenkor. vagyis tudnunk kell úgy kezelni őket. Első ránézésre azt gondolná az ember. A megoldást a boxing művelet jelenti.és referencia-típusok között nincs szoros reláció. ezenkívül ezeket a típusokat különösen gyakran fogjuk használni. ami a GetType metódussal történik – amelyet szintén a System. Nézzünk egy példát: az eddig használt Console. néhány esetben pedig nagyjából ugyanazt a teljesítményt érjük el. ezért elengedhetetlen. A valóságban a fordító képes úgy optimalizálni a végeredményt.6 Boxing és unboxing Boxing–nak (bedobozolás) azt a folyamatot nevezzük. hogy nagyon kevés hátrányunk származzon ebből a műveletből. hogy a paraméter típusa object.. hogy úgy viselkedjen. hogy a ToString hívásához a sima object–ként meghatározott változóknak elő kell keríteniük a tárolt objektum valódi típusát. ha egy int típusú változót (egy értéktípust) akarunk így kiírni? A WriteLine metódus minden típust úgy ír ki.Object deklarál – ami nem is lenne önmagában probléma. mint egy referencia-típus. Az értéktípusok alapvetően nem származnak az Object–ből. mint egy referenciatípust és itt jön képbe a boxing művelet. Az értéktípusok esetében az utóbbi teljesül. amely visszaadja az adott típus string-alakját. de az értéktípusok nem tárolnak magukról típusinformációt épp a kis méret miatt. mivel így hatékony a kezelésük.4.Object deklarálja. hogy az értéktípusoknak a fentiektől függetlenül illeszkedniük kell a típusrendszerbe. Korábban azt mondtuk. de az értéktípusok már nem. Mi történik vajon. amely megengedi egy értéktípusnak.Object –ből származik. hogy az eddigi WriteLine hívásoknál a “konverzió” kérés nélkül – azaz implicit módon – működött annak ellenére. hogy a lehető leggyorsabban kezelhessük őket. Vegyük észre. hogy a dobozolás rendkívül drága mulatság. stb.WriteLine metódus deklarációja így néz ki: public static void WriteLine( Object value ) Látható.

boxObject). amely egy ún. class Program { static public void Main() { int x = 10. object obj = x.WriteLine("X értéke: {0}".WriteLine(x). hogy ne legyen nagy teljesítményveszteség. z = (int)z + 10. amely a halomra másolt és bedobozolt értéktípusra mutat. mint a bedobozolásnál. Itt ugyanaz történik. Fontos még megjegyezni.WriteLine(z). amelynek semmi köze az eredetihez: using System. A következő forráskód azt mutatja. mintha rögtön az x változót adnánk át a metódusnak csak éppen egy lépéssel hamarabb elkészítettük x referencia-típus klónját.24 - . bár nagyon hasonlónak látszanak. ehelyett az adatok egy ideiglenes vermen létrehozott objektumba másolódnak majd onnan egy újabb másolás művelettel a számára kijelölt helyre vándorolnak. hogy miként tudunk “kézzel” dobozolni: int x = 10. } } . Ezt a címet azonban nem használhatjuk közvetlenül a verembe másoláshoz. // bedobozolva int y = (int)obj. // kidobozolva vagyis a bedobozolt Az object típusú változón explicit típuskonverziót hajtottunk végre (erről hamarosan). de ez egyrészt megkerülhetetlen szabály másrészt a JIT ezt is képes úgy optimalizálni. vagyis a vermen elkészítünk egy új értéktípust és átmásoljuk az értékeket. így visszakaptuk az eredeti értéket.nevezzük és nem tévesztendő össze a polimorfizmussal (hamarosan). A kettős másolás pazarlásnak tűnhet. object z = x. értéktípusunkból kinyerjük az eredeti értékét: int x = 0. Az unboxing (vagy kidobozolás) a boxing ellentéte. hogy most minden fordítva történik. // bedobozolva Console. object boxObject = x. Ezt majdnem teljesen igaz egyetlen apró kivétellel: amikor vissza akarjuk kapni a bedobozolt értéktípusunkat a z unbox IL utasítást hívjuk meg. Console. hogy a bedobozolás után teljesen új objektum keletkezik. Console. value-type-pointert ad vissza. A kidobozolás szintén érdekes folyamat: logikusan gondolkodva azt hinnénk.

mégpedig a deklarációnál. Vegyük észre azt is. 4.A kimenet 10 illetve 20 lesz.ReadLine metódus egy sort olvas be a standard bemenetről (ez alapértelmezés szerint a konzol lesz. és egész számot ad vissza. } } A Console.7 Konstansok A const típusmódosító kulcsszó segítségével egy objektumot konstanssá. // Hiba A konstans változóknak adott értéket/kifejezést fordítási időben ki kell tudnia értékelni a fordítónak. A metódus egy string típusú értékkel tér vissza. amelyet termináló karakterrel (Carriage Return. class Program { static public void Main() { Console. // Ez jó x = 11. az Enter-rel zárunk. pl. Wolf }.25 - . Erre fogjuk használni az int. Tiger. ami paraméterként egy stringet vár.WriteLine("Adjon meg egy számot: "). 4.8 A felsorolt típus A felsorolt típus olyan adatszerkezet. const int x. A következő forráskód éppen ezért nem is fog lefordulni: using System. A konstansoknak egyetlen egyszer adhatunk (és ekkor kötelező is adnunk) értéket. ellenkező esetben a program kivételt dob. Ezután így használhatjuk: . de az értékadáshoz nem (először kidobozoltuk. Felsorolt típust az enum kulcsszó segítségével deklarálunk: enum Animal { Cat. hogy z –n konverziót kellett végrehajtanunk az összeadáshoz. Dog.Parse(Console. összeadtuk a két számot. de megváltoztatható).). Line Feed. amely meghatározott értékek névvel ellátott halmazát képviseli. megváltoztathatatlanná tehetünk. // Hiba const int x = 10. A paraméterként megadott karaktersor nem tartalmazhat numerikus karakteren kívül mást. majd az eredményt visszadobozoltuk). const int x = int. stb.ReadLine()).Parse metódust. Bármely későbbi próbálkozás fordítási hibát okoz. amelyből ki kell nyernünk a felhasználó által megadott számot.

Ezen a módon az enum objektumokon explicit konverziót hajthatunk végre a megfelelő numerikus értékre: enum Animal { Cat.Animal b = Animal. ..26 - . int x = (int)a. Tiger. } Enum típust csakis metóduson kívül (osztályon belül. ezen változtathatunk: enum Animal : byte { Cat.WriteLine("b egy tigris. // x == 0 a = Animal. class Program { static public void Main() { enum Animal { Cat = 1. Wolf } } } Ez a kód hibás! Nézzük a javított változatot: using System. Dog. if(b == Animal. hogy a tagok értékének az adott típus értékhatárai között kell maradniuk. Dog. akkor alapértelmezés szerint a számozás nullától kezdődik és deklaráció szerinti sorrendben (értsd: balról jobbra) eggyel növekszik. Tiger. Dog = 3. vagy “önálló” típusként) deklarálhatunk. // x == 3 A tagok értékei alapértelmezetten int típusúak.Tiger) // Ha b egy tigris { Console. A felsorolás minden tagjának megfeleltethetünk egy egész (numerikus) értéket.Wolf. Wolf }. vagyis a példában egy tag értéke nem lehet több mint 255.Tiger. Wolf } static public void Main() { } } Most már jó lesz (és akkor is lefordulna. x = (int)a. Dog = 3. ha a Program osztályon kívül deklarálnánk).Cat. Ha mást nem adunk meg. ellenkező esetben a program nem fordul le: using System. Tiger. Wolf } Animal a = Animal."). Tiger.. class Program { enum Animal { Cat = 1. Természetesen ez együtt jár azzal.

} } 4. ezért lehetséges null értéket megadni nekik.) Azok az “nevek” amelyekhez nem rendeltünk értéket implicit módon. az őket megelőző név értékétől számítva kapják meg azt. } } Az Enum. Dog = 3. byte. illetve mi magunk is megjelölhetjük őket “beállítatlannak”: class RefType{ } RefType rt = null.Tiger. true. Animal a1.. class Program { enum Animal { Cat = 1. Enum. out a2). Console. stb.Ilyen módon csakis a beépített egész numerikus típusokat használhatjuk (pl.TryParse(s2.9 Null típusok A referencia-típusok az inicializálás előtt automatikusan nullértéket vesznek fel. long. uint. out a1).TryParse(s1. true.TryParse metódussal string értékekből “gyárthatunk” enum értékeket: using System. Enum. Tiger. // ez le sem fordul Azt már tudjuk. Így a lenti példában Tiger értéke négy lesz: using System.WriteLine((int)a). hogy a referencia-típusokra referenciákkal. Dog = 3. Wolf } static public void Main() { Animal a = Animal. növekvő sorrendben. Ugyanez az értéktípusoknál már nem működik: int x = null. Tiger.27 - . Az . Wolf } static public void Main() { string s1 = "1". azaz a nekik megfelelő memóriacímmel mutatunk. string s2 = "Dog". a2.. class Program { enum Animal { Cat = 1.

hogy egy értéktípus még nem inicializált. Console. Ahhoz. class Program { static public void Main() { dynamic x = 10. ezért ők nem vehetnek fel null értéket. amit a “rendes” típus után írt kérdőjellel jelzünk: int? i = null. // x most 10 x = "szalámi". Ezenkívül az összes ilyen „objektum” futásidőben megváltoztathatja a típusát is: using System.WriteLine(x).értéktípusok pedig az általuk tárolt adatot reprezentálják. másrészt ez futási idő alatt nem változhatott meg. // ez már működik Egy nullable típusra való konverzió implicit módon (külön kérés nélkül) megy végbe. int? x = y. vagyis egyrészt a típust fordításkor meg kellett tudnia határozni a fordítónak. a nullable típust kell használnunk. Mit is jelent ez a gyakorlatban? Lényegében azt. // x most szalámi } } Vegyük a következő osztályt: . amely használatával dinamikusan típusossá tehetünk objektumokat. még olyan dolgokat is.0–ig bezárólag minden változó és objektum statikusan típusos volt.WriteLine(x). hogy meg tudjuk állapítani. egy speciális típust.10A dinamikus típus Ennek a fejezetnek a teljes megértéséhez szükség van az osztályok. A C# 4. Console. // implicit konverzió y = (int)x. A C# 3. // explicit konverzió 4. amelyek futásidejű hibát okozhatnának. hogy minden dynamic–cal jelölt objektum bármit megtehet fordítási időben. míg az ellenkező irányban explicit konverzióra lesz szükségünk (vagyis ezt tudatnunk kell a fordítóval): int y = 10. illetve metódusok fogalmára. ezeket egy későbbi fejezetben találja meg az olvasó.0 bevezeti a dynamic kulcsszót.28 - .

Kivéve. A dynamic kulcsszó mögött egy komoly platform.29 - . t. akkor meg kell adnunk számára egy string típusú paramétert is. Python vagy Ruby. a Dynamic Language Runtime (DLR) áll (természetesen a dynamic mellett jó néhány osztály is helyett kapott a csomagban). ellenkező esetben a program nem fordul le. akár használtuk a deklarációnál a dynamic–ot. akár nem. viszont futni nem fog. azaz gyengén típusos nyelvekkel tud együttműködni. PHP. A DLR olyan „típustalan”. A paramétereket minden esetben kötelező megadnunk. mint a Lua. de komoly teljesítményproblémákat is okozhat. . Bár a fenti tesztek „szórakoztatóak”. A dynamic „hagyományos” objektumokon való használata lényegében nemcsak átláthatatlanná teszi a kódot. valójában nem túl hasznosak. JavaScript. A konstruktorok nem tartoznak az „átverhető” metódusok közé. ha a dynamic–ot használjuk: static public void Main() { dynamic t = new Test(). ezért mindenképpen kerüljük el az ilyen szituációkat! A dinamikus típusok igazi haszna a más programnyelvekkel – különösen a script alapú nyelvekkel – való együttműködésben rejlik. // ez lefordul } A fenti forráskód minden további nélkül lefordul.Method().class Test { public void Method(string s) { } } Ha a fenti metódust meg akarjuk hívni.

egy speciális részterület kapcsolódik hozzájuk. utolsón pedig az értékadó operátor. A fenti kifejezés tehát így nézne ki: . Ezek az utasítások kifejezésekből állnak. hogy bizonyos operátorok önmagukban nem hordoznak jelentést. a kifejezések pedig operátorokból és operandusokból. Ha bizonytalanok vagyunk a végrehajtás sorrendjében. utasításokat adunk a számítógépnek. A következő néhány fejezetben átveszünk néhány operátort. az operátorok végrehajtásának sorrendjében a szorzás és az osztás előnyt élvez (természetesen érvényesülnek a matematikai szabályok) . a zárójeles kifejezések. a „+‟ jel pedig az összeadás művelet operátora. a fordítónak muszáj valamilyen sorrendet (precedenciát) felállítani közöttük.(unáris) és háromoperandusú (ternáris) operátorokkal is rendelkezik.1 Operátor precedencia Amikor több operátor is szerepel egy kifejezésben. akkor mindig használjunk zárójeleket. de nem az összeset. egy . sorrendtől függően az eredmény lehet 51 vagy 60. Ugyanígy a második pontban i és * (vagyis x + y) az operandusok. hiszen az eredmény ettől is függhet. Például: 10 * 5 + 1 Ennél a kifejezésnél. Egy operátornak nem csak két operandusa lehet. az indexelő operátor most kimarad. az értékadás művelet („=‟) pedig az operátor. ez a végleges programra nézve semmilyen hatással nincs (és a forráskód olvashatóságát is javítja). elsőként a tömböknél találkozhat majd vele a kedves olvasó). ezért ezeket az operátorokat majd a megfelelő helyen ismerjük meg (pl. illetve ezek kombinációjából jönnek létre: i = x + y. Két kifejezés is van az utasításban: 1 lépés: x + y –> ezt az értéket jelöljük * -al 2 lépés: i = * –> i –nek értékül adjuk a * -ot Az első esetben x és y operandusok. A jó megoldás az előbbi. A legelső sorrendi helyen szerepelnek pl. Ebben az utasításban i–nek értékül adjuk x és y összegét. Ennek oka az. 5.5 Operátorok Amikor programozunk. A C# nyelv egy.30 - .

Ettől függetlenül a legtöbb esetben ajánlott akkor értéket adni egy változónak. 7. 9. Bit-eltoló (>> és <<) operátorok 6. 14. metódushívás. Pozitív és negatív operátorok (x = -5). . hogy ugyanazt generálja le a fenti két kódrészletből). illetve a “rövid formában” használt operátorok (pl: x +=y) 5. a fordító lesz annyira okos. sizeof. postfix inkrementáló és dekrementáló operátorok. illetve ha létezik megfelelő konverzió (a típuskonverziókkal egy későbbi fejezet foglalkozik). de csakis abban az esetben. Szorzás. ezt el lehet halasztani: int x. amit elvégezhetünk az az. Természetesen nem kötelező a deklarációnál megadni a definíciót (amikor meghatározzuk. typeof. 12. checked és unchecked 2. maradékos és maradék nélküli osztás 4. as.és bináris tagadás. ha a két változó azo nos típusú. explicit típuskonverzió 3. 13. hogy egy változónak értéket adunk. prefix inkrementáló és dekrementáló operátorok. Létrehoztunk egy int típusú változót. nagyobb (vagy egyenlő). A C# nyelvben ezt az egyenlőségjel segítségével tehetjük meg: int x = 10. adattag hozzáférés (pont („. 10. logika.‟) operátor). hogy a változó milyen értéket kapjon). Kisebb (vagy egyenlő). is Egyenlő és nem egyenlő operátorok Logikai ÉS Logikai XOR Logikai VAGY Feltételes ÉS Feltételes VAGY Feltételes operátor ( ? : ) Értékadó operátor.31 - . Egy változónak nem csak konstans értéket. majd kezdőértékének 10–et adtunk. Zárójel. a new operátor. 11. x = 10. Összeadás. amikor deklaráljuk (persze ez inkább csak esztétikai kérdés. elneveztük x –nek.2 Értékadó operátor Az egyik legáltalánosabb művelet. 8.(10 * 5) + 1 A C# nyelv precedencia szerint 14 kategóriába sorolja az operátorokat (a kisebb sorszámút fogja a fordító hamarabb kiértékelni): 1. kivonás 5. de egy másik változót is értékül adhatunk.

mert egy elütés is nehezen kideríthető . Console.WriteLine(z). public class Operators { static public void Main() { int x = 10.WriteLine(x } } > y). a másodikban az egyenlőséget vizsgáljuk a kettős egyenlőségjellel. // Az osztás maradékát írja ki: 1 Console. A numerikus típusokon értelmezve van egy rendezés reláció: using System. // y értéke most 10 5. // 3 z = x % y.3 Console. Console. // 7 z = x * y. // x kisebb-egyenlő mint y: true Az első sor egyértelmű. //Vár egy billentyű leütésére } } 5. // x nem egyenlő y –al: true <= y). int y = 23. Relációs operátort használó műveletek eredménye vagy igaz ( true) vagy hamis (false) lesz.WriteLine(z).//30 z = x / y.WriteLine(x Console. // false != y).WriteLine(z). //Szorzás: z = 10 * 3 Console.WriteLine(z). int y = x. int z = x + y. // Maradék nélküli osztás: z = 10 / 3.WriteLine(z). // Kiírja az eredményt: 13 z = x .y.3 Matematikai operátorok A következő példában a matematikai operátorok használatát vizsgáljuk meg: using System.4 Relációs operátorok A relációs operátorok segítségével egy adott értékkészlet elemei közötti viszonyt tudjuk lekérdezni. // Kivonás: z = 10 . // Kiírja az eredményt: false == y).ReadKey(). Ilyen esetekben figyelni kell. int y = 3.WriteLine(x Console.32 - . public class RelOp { static public void Main() { int x = 10. // Összeadás: z = 10 + 3 Console. // Maradékos osztás: z = 10 % 3 Console.int x = 10.WriteLine(x Console.

mint y x egyenlő y-nal x nem egyenlő y-nal 5. if(l == true && k == false) { Console.5 Logikai és feltételes operátorok Akárcsak a C++. public class RelOp { static public void Main() { bool l = true. mint y x kisebb vagy egyenlő. akkor végrehajt egy utasítást (vagy utasításokat). csak kiírja.33 - . Az esetek többségében ugyanis így is le fog fordulni a program. mint y x kisebb. a C# sem rendelkezik „igazi” logikai típussal. helyette 1 és 0 jelzi az igaz és hamis értékeket: using System. A program nem sok mindent tesz. } } } Először felvettünk két logikai (bool) változót. amikor egyenlőség helyett az értékadó operátort használjuk. hogy „Igaz”. Ezután egy elágazás következik.hibát okoz. A relációs operátorok összefoglalása: x>y x >= y x<y x <= y x == y x != y x nagyobb. hogy akár az előző fejezetben megismert relációs operátorokból felépített kifejezések. bool k = false. A fenti példában az „ÉS” (&&) operátort használtuk.WriteLine("Igaz"). vagy matematikai formulák is lehetnek operandusok. erről bővebben egy későbbi fejezetben lehet olvasni. ha mindkét operandusa „igaz” vagy nullánál nagyobb értéket képvisel. Ebből következik az is. Nézzük az „ÉS” igazságtáblázatát: A B Eredmény hamis hamis hamis hamis igaz hamis igaz hamis hamis igaz igaz igaz . működni viszont valószínűleg rosszul fog. a lényege az. ez két operandust vár és akkor ad vissza „igaz” értéket. hogy ha a feltétel igaz. az elsőnek „igaz” a másodiknak „hamis” értéket adtunk. mint y x nagyobb vagy egyenlő.

ha az operandusai közül valamelyik „igaz” vagy nagyobb. Tudni kell azt is. } } } . A „VAGY” igazságtáblázata: A B Eredmény hamis hamis hamis hamis igaz igaz igaz hamis igaz igaz igaz igaz Az eredmény kiértékelése az ún. a különbség a feltételben van.A fenti forráskód jó gyakorlás az operátor-precedenciához. if(!(x == 11)) // x nem 11. ezért false. hogy a kiértékelés mindig balról jobbra halad. bool k = false. mint az előző. public class RelOp { static public void Main() { int x = 10. azaz a program csak addig vizsgálja a feltételt. A harmadik a „tagadás” (!): using System. mint nulla. ezért a feltétel is biztosan teljesül. Ez a program is ugyanazt csinálja. A második operátor a „VAGY”: using System. public class RelOp { static public void Main() { bool l = true. if(l == true || k == true) { Console. a fenti példában k soha nem fog kiértékelődni. de ezt tagadjuk: true { Console.WriteLine("Igaz"). amíg muszáj. mert l van az első helyen. } } } A „vagy” (||) operátor akkor térít vissza „igaz” értéket. „lusta kiértékelés” (vagy „rövidzár”) módszerével történik. ezért pl. hogy k biztosan nem „igaz” (hiszen éppen előtte kapott „hamis” értéket). Látható.34 - . és mivel ő „igaz” értéket képvisel. az elágazás feltételében először az egyenlőséget fogjuk vizsgálni (a hetes számú kategória) és csak utána a feltételes ÉS –t (tizenegyes kategória).WriteLine("X nem egyenlő 11 -gyel!").

A különbség annyi. akkor az egyenlőség jel utáni érték lesz a teljes kifejezésünk értéke. Ha igaz. } A logikai operátorok családjához tartozik (ha nem is szorosan) a feltételes operátor. Ez az egyetlen háromoperandusú operátor és a következőképpen működik: feltétel ? igaz-ág : hamis-ág. áttekinthetőbb lesz tőle.35 - . sok gépelést megspórolhatunk vele és a kódunk is kompaktabb. nem élnek a lusta kiértékeléssel.Ennek az operátornak egy operandusa van és akkor ad vissza igaz értéket. ha az operandusban megfogalmazott feltétel hamis. } A logikai „ÉS”: if(l == true & k == true) { Console. } } Az operátor úgy működik. majd megnézi. Console. akkor pedig a kettőspont utáni. public class RelOp { static public void Main() { int x = 10. A logikai „VAGY” művelet: if(l == true | k == true) { Console.WriteLine((x == y) ? "Egyenlő" : "Nem egyenlő"). hogy a logikai operátorok az eredménytől függetlenül kiértékelik a teljes kifejezést. hogy a kérdőjel előtti kifejezést kiértékeli. int y = 10.WriteLine("Igaz").WriteLine("Igaz"). ha pedig hamis. közülük pedig az „ÉS” és a „VAGY” operátoroknak létezik „csonkolt” logikai párja is. vagy – ha numerikus kifejezésről beszélünk . hogy a kifejezés igaz vagy hamis. A „tagadás” (negáció) igazságtáblája: A Eredmény hamis igaz igaz hamis Ez a három operátor ún.egyenlő nullával. . feltételes operátor. Egyszerű if-else (erről később) ágakat lehet ezzel a módszerrel kiváltani. using System.

36 - . kiszámolásához.5.6 Bit operátorok Az előző fejezetben említett logikai operátorok bitenkénti műveletek elvégzésére is alkalmasak. azaz ha mindkét bit 1 állásban van. ha van egy byte típusú változónk (ami egy byte. azaz 8 bit hosszúságú). így például.WriteLine a művelet eredményét tízes számrendszerben írja majd ki. mint az „ÉS”. public class Program { static public void Main() { Console. egyébként pedig 0: 01101101 00010001 AND 00000001 Elég egyértelmű. akkor az adott helyen az eredményben is az lesz. A műveletek: Bitenkénti „ÉS”: veszi a két operandus bináris alakját és a megfelelő bitpárokon elvégzi az „ÉS” műveletet. de a végeredményben egy bit értéke akkor lesz 1.WriteLine(10 & 2). //1010 & 0010 = 0010 = 2 } } hogy az „ÉS” igazságtáblát használtuk az eredmény A programot futtatva látni fogjuk. A számítógép az adatokat kettes számrendszerbeli alakban tárolja. A bitenkénti „VAGY” hasonlóan működik. akkor az a következőképpen jelenik meg a memóriában: 2  00000010 A bit operátorok ezzel a formával dolgoznak. ha a két operandus adott bitje közül valamelyik legalább az: 01101101 00010001 OR 01111101 . hogy a Console. aminek a „2” értéket adjuk. Példa: using System. Az eddig megismert kettő mellé még jön négy másik operátor is.

és felül pótoljuk a hiányt. hogy így biztosítja a .WriteLine(x << 1). előjeles szám (int) lesz. public class Program { static public void Main() { int x = 143. 10001111 LEFT SHIFT 100011110 Példa: using System. hogy a művelet végeredménye minden esetben 32 bites. Az operátor jele: <<. Console. Ennek nagyon egyszerű oka az. Példa: using System. public class Program { static public void Main() { byte x = 143.Példa a „VAGY”-ra: using System. public class Program { static public void Main() { Console. figyelnünk kell arra.NET. majd a jobb oldalon keletkező „üres” bitet nullára állítjuk.WriteLine(x >> 1). Az operátor: >>. Biteltolás jobbra: most az alsó bitet toljuk el.WriteLine(10 | 2). hiszen akkor 9 bitre lenne szükségünk). // 10001111 (=143) >> 1 = 01000111 = 71 } } . Console.37 - . hogy az eredmény elférjen (a fenti példában használt 143 pl. azaz egy byte típusban már nem férne el az eltolás után. pontosan 8 biten felírható szám. // 1010 | 0010 = 1010 = 10 } } Biteltolás balra: a kettes számrendszerbeli alak „felső” bitjét eltoljuk. // 10001111 (=143) << 1 = 100011110 = 286 } } Amikor biteltolást végzünk.

WriteLine(Convert. mint a hagyományos szorzást (tulajdonképpen a szorzás a processzor egyik leglassabb művelete). // 61 Console. public class Program { static public void Main() { byte x = 10.WriteLine(Convert. 16)). Értelemszerűen a balra tolás ezért mindig növelni. amely erősen épít kettő vel vagy hatványaival való szorzásra/osztásra. // 97 } } Ez a metódus a konvertálás után csak a „hasznos” részt fogja visszaadni. Ennél is tovább mehetünk.változó csak 4 biten megjelenni (hiszen a 10 egy pontosan 4 biten felírható szám).WriteLine(Convert. mive l ezeket a processzor sokkal gyorsabban végzi el.egyébként 32 bites . Console.ToString(z. akkor ajánlott bitműveleteket használni.ToString(z. Vegyük észre. Ez az ‟a‟ karakter esetében 97 (tízes számrendszer). // 1010 char z = 'a'. // 1010 int y = 10. amit konvertálunk.) lesz. addig a jobbra tolás elvesz belőle. hiszen a felső nyolc bit itt nullákból áll. A char típus numerikus értékre konvertálásakor az Unicode táblában elfoglalt helyét adja vissza. 2)). hogy amíg a balra való eltolás ténylegesen – fizikailag – hozzátett az eredeti számunkhoz.ToString(x. y pedig a cél-számrendszer: using System.ToString(x. . 2)). Ez utóbbinál is csak a hasznos részt kapjuk vissza. 1100001 (kettes szr. y) metódussal. Console.WriteLine(Convert.38 - .ToString(y.ToString(z.) vagy 0061 (tizenhatos szr. hiszen a „felülre” érkező nulla bitek nem hasznosulnak az eredmény szempontjából. felfedezve a biteltolások valódi hasznát: egy n bittel balra tolás megfelel az alapszám 2 az n–edik hatványával való szorzásnak: 143 << 1 = 143 * (2^1) = 286 143 << 2 = 143 * (2^2) = 572 Ugyanígy a jobbra tolás ugyanazzal a hatvánnyal oszt (nullára kerekítéssel): 143 >> 1 = 143 / (2^1) =71 143 >> 2 = 143 / (2^2) = 35 Amikor olyan programot készítünk. a jobbra tolás pedig mindig csökkenteni fogja az eredményt.WriteLine(Convert. // 1100001 Console. ezért fog az int típusú . 10)). ahol x az az objektum. 2)). Console.A legtöbb beépített típust könnyen konvertálhatjuk át különböző számrendszerekre a Convert.

elsőként létrehoz egy átmeneti változót. A postfixes forma egy kicsit bonyolultabb. Az igazsághoz azért az is hozzátartozik. amikor lényegesen megkönnyíti az életünket a használa ta. majd megnöveli eggyel az operandust. Mi történik valójában? Elsőként értelmezni kell a jobb oldalt. azonban az eggyel való növelésre /csökkentésre van önálló operátorunk: ++x és --x x++ és x-- Ebből az operátorból rögtön két verziót is kapunk. Rövidebb. Az összes aritmetikai operátornak létezik rövid formája. szebb és hatékonyabb. hogy növeljük vagy csökkentjük az opera ndust. hogy a fordítóprogram elvileg felismeri a fent felvázolt szituációt és a rövid formával egyenértékű IL –t készít belőle (más kérdés. amiben eltárolja az operandusa értékét. hozzá kell adni tízet és eltárolni a veremben.7 Rövid forma Vegyük a következő példát: x = x + 10. A prefixes alak pontosan azt teszi. Ez az operátor használható az összes beépített numerikus típuson.elől) és postfixes (++/-. Attól függően.39 - . A probléma ugyanaz. de vannak helyzetek. amit elvárunk tőle. . valamint a char illetve enum típusokon is. inkrementáló illetve dekrementáló operátorról beszélünk. Ez elsőre talán nem tűnik hasznosnak. prefixes ( ++/-. ezúttal a bal oldalon. Szerencsére van megoldás. hogy a forráskód így viszont szebb és olvashatóbb).hátul) formát. Szemmel láthatóan ugyanaz a baj. de a megoldás más a következő esetben: x = x + 1. azaz megnöveli(vagy értelemszerűen csökkenti) az operandusát eggyel. rövid forma.5. Az x nevű változót megnöveltük tízzel. mégpedig az ún. Csakhogy van egy kis baj: ez a megoldás nem túl hatékony. azaz ki kell értékelni x–et. végül visszaadja az átmeneti változót. Ezután ismét kiértékeljük x–et. A fenti sorból ez lesz: x + = 10.

byte típusra alkalmaztuk).Object–hez tartozó metódus. mivel az operátor int típussal tér vissza (akkor is.WriteLine(y). classProgram { staticpublicvoidMain() { int x = 143. byte y = -x. public class Program { static public void Main() { byte x = 10. Console. int y = 10 + (-x). ha pl. } } } A változón meghívott GetType metódus a változó típusát adja vissza (ez egy System.GetType()) { Console.8 Egyéb operátorok Unáris (‟+‟ és ‟-’): az adott szám pozitív illetve negatív értékét jelezzük vele: using System. . if(typeof(int) == x.40 - . Ezeket az operátorokat csakis előjeles típusokon használhatjuk. így a használatához dobozolni kell az objektumot). A következő program le sem fordul: using System. } } Ez a program nullát fog kiírni (természetesen érvényesülnek a matematikai szabályok).WriteLine("x típusa int").5. } } A typeof az operandusa típusát adja vissza: usingSystem. public class Program { static public void Main() { int x = 10.

A programot az unsafe kapcsolóval kell lefordítanunk: csc /unsafe main.41 - .A sizeof operátor a „paramétereként” megadott értéktípus méretét adja vissza byte – ban. Ez az operátor kizárólag unsafe módban használható és csakis értéktípusokon (illetve pointer típusokon): using System.cs . public classProgram { static public void Main() { unsafe { Console.WriteLine(sizeof(int)). hiszen az int típus 32 bites. } } } Ez a program négyet fog kiírni. azaz 4 byte méretű típus.

WriteLine("x értéke nem 10").42 - . Ilyenkor használjuk az else ágat: using System. és attól függően. if(x == 10) // Ha x egyenlő 10 -zel { Console. 6. if(x == 10) // Ha x egyenlő 10 -zel { Console.WriteLine("x értéke 10"). 6. } } } . amikor x értéke nem tíz.2 Elágazás Gyakran előfordul.WriteLine("x értéke 10"). Ilyen esetekben elágazást használunk: using System. hogy azt a helyzetet is kezelni tudjuk . Ez tulajdonképpen egymás után megszabott sorrendben végrehajtott utasításokból áll. hogy igaz vagy hamis. hogy meg kell vizsgálnunk egy állítást. public class Program { static public void Main() { int x = 10.1 Szekvencia A legegyszerűbb vezérlési szerkezet a szekvencia.6 Vezérlési szerkezetek a program utasításainak sorrendiségét szabályozó Vezérlési szerkezetnek konstrukciókat nevezzük. a programnak más-más utasítást kell végrehajtania. } else // Ha pedig nem { Console. public class Program { static public void Main() { int x = 11. } } } Természetes az igény arra is.

mint az előző. .WriteLine("x értéke 10"). } elseif(x == 12) // Vagy 12 -vel { Console. hogy a feltételtől függően mindkét állítás lehet igaz). A fenti helyzetben írhattuk volna ezt is: using System. } } } A program az első olyan ágat fogja végrehajtani. } if(x != 10)//Ha pedig x nem 10 { Console. hiszen két különböző szerkezetről beszélünk (ez egyúttal azzal is jár. ha a hozzá kapcsolódó feltétel(ek) nem igaz(ak).WriteLine("x értéke 12"). de van egy nagy különbség a kettő között: mindkét feltételt ki kell értékelnie a programnak.WriteLine("x értéke 10").WriteLine("x értéke nem 10 vagy 12"). Arra is van lehetőségünk. if(x == 10) // Ha x == 10 -zel { Console. Önmagában else ág nem állhat (nem is lenne sok értelme). } else // De ha egyik sem { Console. amelynek a feltétele teljesül (vagy ha egyik feltétel sem bizonyult igaznak. } } } Ez a program pontosan ugyanazt csinálja. public class Program { static public void Main() { int x = 13. akkor az else ágat – ha adtunk meg ilyet). public class Program { static public void Main() { int x = 11.Az else szerkezet akkor lép életbe.WriteLine("x értéke nem 10"). hogy több feltételt is megvizsgáljunk.43 - . ekkor else-if –et használunk: using System. if(x == 10) // Ha x egyenlő 10 -zel { Console.

break. amely nagyon sokféle értéket vehet fel. } } } A switch szerkezeten belül megadhatjuk azokat az állapotokat. ez lényegében az else ág testvére lesz. Ezt akkor használjuk. break. } } } Újdonságként megjelenik a default állapot.WriteLine("x értéke 11"). Ilyen esetekben azonban van egy egyszerűbb és elegánsabb megoldás. default: Console. switch(x) { case 10: Console. public class Program { enum Animal { TIGER. Egy elágazáson belül is írhatunk elágazást. break. static public void Main() { Animal animal = Animal. switch(animal) { case Animal. Az utolsó példában olyan változót vizsgáltunk. break. bármennyi else-if és pontosan egy else ág lehet. akkor kerül ide a vezérlés.DOG. WOLF. Nyílván ilyenkor nem tudunk minden egyes állapothoz feltételt írni (pontosabban tudunk. hogy mi történjen ezután.WriteLine("x értéke 10"). mégpedig a switch-case szerkezet. amelyekre reagálni szeretnénk. Az egyes ágak a kijelölt feladatuk végrehajtása után a break utasítással kilépnek a szerkezetből: using System. DOG}. CAT.WriteLine("Tigris"). public class Program { static public void Main() { int x = 11. hogy a switch egy ága mindenképpen lefusson).WriteLine("Nem ismerem ezt az állatot!"). csak az nem lesz szép). ha a s witch nem tartalmazza a vizsgált változó állapotát (vagyis a default biztosítja.TIGER: Console. case 11: Console.Egy elágazásban pontosan egy darab if. ha egy változó több lehetséges állapotát akarjuk vizsgálni: using System.44 - . Az egyes esetek utasításai után meg kell adnunk. .

WOLF. Ez alól a szabály alól egyetlen kivétel. Az első az ún.TIGER: goto default. break. CAT. switch(animal) { case Animal. Nézzük a következő programot: .DOG: goto default.DOG. DOG }.TIGER: case Animal. DOG}. public class Program { enum Animal { TIGER. switch(animal) { case Animal. CAT. public class Program { enum Animal { TIGER. ekkor átugrunk a megadott ágra: using System. static public void Main() { Animal animal = Animal.DOG: default: Console.WriteLine("Ez egy állat!").A C++ nyelvtől eltérően a C# nem engedélyezi. break. számlálós ciklus (nevezzük for-ciklusnak). A C# négyféle ciklust biztosít számunkra. ha az adott ág nem tartalmaz semmilyen utasítást: using System. hogy break utasítás hiányában egyik állapotból átcsússzunk egy másikba.3 Ciklus Amikor egy adott utasítássorozatot egymás után többször kell végrehajtanunk. } } } A break utasításon kívül használhatjuk a goto –t is. } } } 6.WriteLine("Ez egy állat!"). WOLF. static public void Main() { Animal animal = Animal.45 - .DOG. default: Console. akkor ciklust használunk. case Animal.

++i) { Console. hogy milyen típust használunk a számoláshoz és azt. először inkább nézzük meg azt. public class Program { static public void Main() { for(int i = 0.using System. amelyre a ciklusfeltétel épül. de ha i ennél nagyobb. hogy a ciklusváltozó milyen értéket vehet fel. Ciklusváltozót. } } } Vajon mit ír ki a program? Mielőtt ezt megmondanám. ez minden ciklus része lesz. hogy honnan kezdjük a számolást. akkor a ciklust be kell fejezni. hogy mit csinál: a for utáni zárójelben találjuk az ún. // itt a hiba } } Következzen a „Meddig?”! Most azt kell megválaszolnunk.i < 10. A számlálós ciklus feltétele első ránézésre eléggé összetett. ami kielégíti a ciklusfeltételt. de ez ne tévesszen meg minket. Épp ezért a következő forráskód nem fordulna le: using System.i < 10. A ciklusváltozó neve konvenció szerint i lesz az angol iterate – ismétel szóból. public class Program { static public void Main() { for(int i = 0. vagyis kilenc még jó. de ez nem fedi a valóságot . és azt adjuk meg benne. j. sorrendet követünk. A ciklusfeltételen belül deklarált ciklusváltozó lokális lesz a ciklust tartalmazó blokkra nézve. Mindössze három kérdésre kell választ adnunk : Honnan? Meddig? És Hogyan? Menjünk sorjában: a honnanra adott válaszban megmondjuk azt.++i) { Console. Most azt adtuk meg. ciklusfeltételt.46 - .. valójában nem az. } int i = 10.. hogy a ciklusváltozó a lokális lesz a ciklus blokkjára nézve. Tulajdonképpen ebben a lépésben adjuk meg az ún. hogy hányszor fusson le a ciklus. Mivel a ciklusfeltétel után blokkot nyitunk. .WriteLine(i).WriteLine(i). Több ciklusváltozó használatakor általában i. A fenti példában egy int típusú ciklusváltozót hoztunk létre a ciklusfeltételen belül és nulla kezdőértéket adtunk neki. k . azt hinné az ember. hogy i-nek kisebbnek kell lennie tíznél.

Végtelen ciklusnak nevezzük azt a ciklust.Természetesen bonyolultabb kifejezést is megadhatunk: using System. public class Program { static public void Main() { for(int i = 1.47 - .++i) { Console.WriteLine(i). Most már meg tudjuk válaszolni.WriteLine(i). A for ciklusból tetszés szerint elhagyhatjuk a ciklusfej bármely részét – akár az egészet is. hiszen mire a négyhez ér . mivel néha erre is szükségünk lesz. amely soha nem ér véget.i += 2) { Console. de a ciklusfeltétel érdekesebb. . } } } Persze ennek a programnak különösebb értelme nincs. Értelemszerűen csak háromig fogja kiírni a számokat. } } } Ebben a kódban kettesével növeljük a ciklusváltozót. vagyis azt. Addig megy a ciklus.i < 10 && i != 4. a ciklusfeltétel már nem lesz igaz. hogy milyen módon változtatjuk a ciklusváltozó értékét. de szándékosan is. de itt is megadhatunk összetett kifejezést: using System. A leggyakoribb módszer a példában is látható inkrementáló (dekrementáló) operátor használata. Utoljára a „Hogyan?” kérdésre adjuk meg a választ.i < 10. ekkor végtelen ciklust készíthetünk. hogy az első programunk mit csinál: nullától kilencig kiírja a számokat. Ilyen ciklus születhet programozási hibából. public class Program { static public void Main() { for(int i = 0. amíg i kisebb tíznél és nem egyenlő néggyel. vagyis a páros számokat íratjuk ki a képernyőre.

WriteLine("i értéke: {0}". ha parancssorból futtattuk. i).using System. nem nehéz kitalálni. A „program” futását a Ctrl+C billentyűkombinációval állíthatjuk le. public class Program { static public void Main() { int i = 0. hogy azért kapta ezt a nevet. Működését tekintve az elől-tesztelős ciklus hasonlít a számlálósra (mindkettő először a ciklusfeltételt ellenőrzi). } } } Ez a forráskód lefordul. hogy a ciklus-törzs egyszer sem fut le: using System. ++i. így legalább egyszer biztosan lefut: . őt hátul-tesztelős ciklusnak hívják (legyen dowhile).) { Console. ezért előfordulhat az is. de az előbbi sokkal rugalmasabb. hogy „gyanús” kódot észlelt.. hogy a további paraméterek közül melyiket helyettesítse be a helyére (nullától számozva). viszont itt jól láthatóan elkülönülnek a ciklusfeltételért felelős utasítások (kezdőérték.WriteLine("Végtelen ciklus"). hogy a ciklusmag végrehajtása előtt ellenőrzi a ciklusfeltételt. amely ún. ciklusfeltétel. A változó értékének kiíratásánál a Console. de a fordítótól figyelmeztetést kapunk (warning). amely onnan kapta a nevét. public class Program { static public void Main() { for(. A harmadik versenyző következik. // ciklusváltozó növelése } } } A program ugyanazt csinálja mint az előző. mert a ciklusmag végrehajtása után ellenőrzi a ciklusfeltételt. // ciklusváltozó deklaráció while(i < 10) // ciklusfeltétel: fuss amíg i kisebb.WriteLine egy másik verzióját használtuk. mivel több lehetőségünk van a ciklusfeltétel megválasztására. mint 10 { Console. növel/csökkent). Az első paraméterben a kapcsos zárójelek között megadhatjuk. Második kliensünk az elől-tesztelős ciklus (mostantól hívjuk while-ciklusnak).48 - . formátum-sztringet kap paraméterül.

megvalósítja az IEnumerable és IEnumerator interfészeket. A példában használt ch változó nem ciklusváltozó. A foreach pontos működésével az interfészekről szóló fejezet foglalkozik majd. ami megvalósítja az IEnumerable és IEnumerator interfészeket (interfészekről egy későbbi fejezet fog beszámolni. } } } A ciklusfejben felveszünk egy char típusú változót (egy string karakterekből áll). hanem ún. Ezzel a ciklussal végigiterálhatunk egy tömbön vagy gyűjteményen. de ha nem. ahol többek között megvalósítunk egy osztályt. utána az in kulcsszó következik. . ++i. amivel kijelöljük. amelyen a foreach képes végigiterálni (azaz megvalósítjuk az IEnumerable és IEnumerator interfészeket).Write(ch). public class Program { static public void Main() { int i = 0. } } Végül – de nem utolsósorban – a foreach (neki nincs külön neve) ciklus következik. akkor azokat fogja használni. az mindenképpen megmarad).WriteLine("i értéke: {0}". mint egy számlálós ciklus esetében (leszámítva az iterációs változót. A példánk most nem a már megszokott „számoljunk el kilencig” lesz.49 - . foreach(char ch in str) { Console. }while(i < 10). public class Program { static public void Main() { string str = "abcdefghijklmnopqrstuvwxyz". akkor hasonló lesz a végeredmény. iterációs változó. illetve minden olyan objektumon. A foreach ciklus kétféle módban képes működni: ha a lista. i). amin alkalmazzuk. Éppen ezért egy foreach ciklus nem módosíthatja egy gyűjtemény elemeit (le sem fordulna ebben az esetben a program). amely felveszi az iterált gyűjtemény aktuális elemének értékét. helyette végigmegyünk egy stringen: using System.using System. hogy min megyünk át. ott lesz szó erről a kettőről is). do { Console.

ezekről egy későbbi fejezet szól. amely a .Write(i).0 verziójában kapott helyet. hanem onnan folytatja ahol legutóbb abbahagytuk. erről egy későbbi fejezetben). } } } A yield működési elve a következő: a legelső metódushívásnál a ciklus megtesz egy lépést.6. amely megvalósítja az IEnumerable interfészt és ezáltal használható legyen pl. hogy egy ciklusból olyan osztályt generáljon a fordító. a foreach ciklussal: using System.++i) { yield return i. A TPL számunkra érdekes része a párhuzamos ciklusok megjelenése. } } static public void Main() { foreach(int i in EnumerableMethod(10)) { Console.Collections.0 a for és a foreach ciklusok párhuzamosítását támogatja a következő módon: .0 –hoz készült fordító szükséges.50 - .2 Párhuzamos ciklusok Ennek a fejezetnek a megértéséhez szükség van a generikus listák és a lambda kifejezések ismeretére. using System.1 Yield A yield kifejezés lehetővé teszi. A NET 4.3. azaz a következő hívásnál nem újraindul a ciklus. ezért ehhez a fejezethez a C# 4. 6. A több processzormaggal rendelkező számítógépek teljesítményének kihasználása céljából a Microsoft elkészítette a Task Parallel Library –t (illetve a PLINQ –t.i < max.3. public class Program { static public IEnumerable EnumerableMethod(int max) { for(int i = 0.NET 4. ezután „kilépünk” a metódusból – de annak állapotát megőrizzük.

míg a harmadik helyen a ciklusmagot jelentő Action<int> generikus delegate áll.microsoft.WriteLine().Write("{0}.Collections. 56.Threading. } } A For első paramétere a ciklusváltozó kezdőértéke. Parallel. // ez kell class Program { static public void Main() { List<int> list = new List<int>() { 1. Mindkét ciklus számos változattal rendelkezik.Tasks.ForEach(list. (item) => Console.Write("{0}. 78. amely egyetlen bemenő paramétere a ciklusváltozó aktuális értéke. ". ". 3. Parallel. list. list[index]).using System.com/en-us/library/system.parallel_members.Generic.51 - . míg a második a ciklusmag. item)). Console. 2. }). második a maximumérték. using System.tasks.threading. ezek megtalálhatóak a következő MSDN oldalon: http://msdn.Count.For(0.aspx . A ForEach két paramétere közül az első az adatforrás. 67 }. (index) => { Console. 4. using System.

exe 12 Vagyis a 12 –es szorzótáblát szeretnénk látni. hogy a Main metódus kapott egy paramétert. mégpedig egy string típusú elemekből álló tömböt (tömbökről a következő fejezetek adnak több tájékoztatást.52 - .cs Ebben az esetben a csc a fordítóprogram neve.cs) Elsőként készítsük el a program vázát: using System.Parse metódust használjuk majd. amely számmá konvertálja a paramétereként kapott szöveget (persze csak akkor. De mi is az a parancssori paraméter? Egy nagyon egyszerű példát nézzünk meg. Erre a feladatra az int. most ez nem annyira lesz fontos). akkor az args tömb egyetlen elemet tartalmaz a csc-re nézve. hogy hogyan fog ez kinézni a mi programunkban (feltesszük.1 Szorzótábla Készítsünk szorzótáblát! A program vagy a parancssori paraméterként kapott számot használja. hogy szám típussá alakítsuk a paramétert. Megoldás (7/Mtable. Ehhez még szükségünk van arra is. míg a forráskódot tartalmazó file neve pedig a paraméter. hogy írja ki a paraméterként megadott szám kétszeresét. parancssori paramétereink. egyébként kivételt dob). azt. hiszen azt stringként kapjuk meg.7 Gyakorló feladatok 7. Ebben a tömbben lesznek az ún. Lássuk. ha ez lehetséges. class Program { static public void Main(string[] args) { } } Vegyük észre. amikor lefordítunk egy C# forráskódot: csc main. Ha ezt vesszük alapul.exe lesz a neve): mul. akkor generáljon egy véletlen számot. mégpedig a „main. hogy mul. A következő lépésben fejlesszük tovább a programot.cs” –t. . vagy ha ilyet nem adtunk meg.

ezért az első parancssori paraméter – a megadott szám – a nulladik helyen lesz.exe 10 Erre az eredmény 20 lesz.WriteLine("Adjon meg egy paramétert!").Parse(args[0]). ha nem adunk meg paramétert. } } Mivel a tömböket mindig nullától kezdve indexeljük . A programot most így tudjuk futtatni: mul. és ha ez az érték nulla. } else { int number = int. ha az else ág használata helyett az if ágba teszünk egy return utasítást. } } } Egy kicsit szebb lesz a forráskód.A forráskód most így alakul: using System.WriteLine(number * 2). class Program { static public void Main(string[] args) { if(args. Egyetlen probléma van. class Program { static public void Main(string[] args) { int number = int.Length == 0) { Console. hogy meg kell adnia egy számot is! Ezt úgy fogjuk megoldani.Parse(args[0]). akkor kiírjuk az utasítást: using System. Console. amely visszaadja a vezérlést annak a „rendszernek” amely az őt tartalmazó metódust hívta (ez a metódus jelen esetben a Main.WriteLine(number * 2).53 - . azaz a program befejezi a futását): . a program „összeomlik”. Most módosítsuk úgy. Console. hogy figyelmeztesse a felhasználót. őt pedig mi – vagyis inkább az operációs rendszer – hívta. hogy lekérdezzük a paramétereket tartalmazó tömb hosszát.

Length { Random r number = } else { number = } } } == 0) = newRandom(). int. class Program { static public void Main(string[] args) { if(args.Length == 0) { Console. Console. Már nincs más dolgunk. Véletlenszámot a Next metódussal generáltunk.Next(10. a szorzótáblát: . inkább generálunk egy véletlen számot.54 - . a fenti formájában 0 és 100 között generál egy számot. de használhatjuk így is: number = r. } } A következő lépésben ahelyett.WriteLine(number * 2). r. class Program { static public void Main(string[] args) { int number. ha nincs paraméter.WriteLine("Adj meg egy paramétert!"). if(args.Next(100). 100). return. } int number = int.using System. A forráskód most ilyen lesz: using System.Parse(args[0]). Ehhez szükségünk lesz egy Random típusú objektumra. hogy kilépünk.Parse(args[0]). mint megírni a feladat lényegét. Ekkor 10 és 100 közötti lesz a szám.WriteLine(number * 2). Console.

hogy pontosan három paramétert kaptunk–e! . Megoldás (7/Calculator. number. hanem írjuk ki rögtön az eredményt): main.exe 12 23 + (az eredmény pedig 35 lesz).55 - .cs) Most is két részből áll a programunk. Ezúttal is a szükséges változók deklarációjával kezdjük a programírást.2 Számológép Készítsünk egy egyszerű számológépet! A program indításakor kérjen be két számot és egy műveleti jelet.Parse(args[0]). vizsgáljuk meg. Ezután bekérjük a felhasználótól a szükséges adatokat. i * number).Next(100). } for(int i = 1. } else { number = int. először hozzá kell jutnunk a számokhoz és az operátorhoz. } } } 7. i. if(args.using System. három darab kell. number = r. Ezután bővítsük ki a programot. Ez utóbbi esetben végezzünk egy kis hibaellenőrzést. vagy pedig felhasználjuk a paramétereket.i <= 10.WriteLine("{0} x {1} = {2}". majd írja ki az eredményt. hogy a két számot illetve a műveleti jelet parancssori paraméterként is megadhassuk (ekkor nincs külön műveletválasztó menü. majd elvégezzük a megfelelő műveletet és kiírjuk az eredményt. két numerikus (legyen most int) és egy karaktertípus. class Program { static public void Main(string[] args) { int number.Length == 0) { Randomr = new Random().++i) { Console.

} else { x = int.ToChar(args[2]). *. /): ").Parse(Console. char op. } else { if(args. } } } } Az operátor „megszerzéséhez” egyrészt a Console. op = Convert.WriteLine("Túl sok/kevés paraméter!").WriteLine("Az első szám: ").56 - . y = int.ReadLine()).Read metódust használtuk (mivel csak egyetlen karakterre van szükség).Length == 0) { Console. ehhez a Convert. Most már nagyon egyszerű dolgunk van. Ezt nyílván a beolvasott operátor alapján fogjuk megtenni.Parse(Console. y. másrészt ennek a metódusnak a visszatérési értékét – amely egy egész szám – át kellett konvertálnunk karaktertípussá.ToChar metódus nyújtott segítséget. ha a s witch szerkezetet használjuk: .WriteLine("A második szám: ").ToChar(Console. mindössze ki kell számolnunk az eredményt. Console. op = Convert. class Program { static public void Main(string[] args) { int x.Parse(args[0]).A forráskód eddig így néz ki: using System. if(args.Read()).Parse(args[1]).WriteLine("A művelet(+. Console.Length != 3) { Console.ReadLine()). return. -. y = int. ebben a helyzetben pedig a legkézenfekvőbb. x = int.

hogy megtörténik a változó-definíció. Ugyan az értéktípusok bizonyos helyzetekben automatikusan nulla értéket kapnak. hogy mit választottak a „versenyzők”). és két byte típust (ezekben pedig az eredményeket tartjuk számon. } = x + y.cs) A programunk lényegében három részből fog állni: elsőként megkérdezzük a felhasználótól. = x / y. ezzel fogjuk jelezni a programnak. Ez azért van így. 7. Megoldás (7/SPS. ellenkező esetben ugyanis nem fordul le a program (uninitialized variable kezdetű hibaüzenetet kapunk). Ezért minden olyan esetben. ha bevezetünk a switch szerkezetben egy default címkét.57 - . case '*': result break. A játék folyamatos legyen. Első dolgunk legyen. Console. mert feltesszük. akkor hibaüzenetet fogunk kapni.WriteLine("A művelet eredménye: {0}". A fenti esetben megoldást jelenthet az is. majd sorsolunk a „gépnek” is valamit.valamilyen formában – az értékadás. amelyre a játék végetér). hogy senki nem fog több százszor játszani egymás után). Tartsuk nyílván a játék állását és minden forduló után írjuk ki az aktuális eredményt. case '-': result break. hogy akarunk –e még játszani. hogy mit választott. de ez nem minden esetben igaz és lokális változók esetén épp ez a helyzet. hogy a result változó kapjon kezdőértéket. . végül pedig kiértékeljük az eredményt. switch(op) { case '+': result break. mert a változó-deklaráció és az utolsó sorban le vő kiíratás között nem biztos. amikor egy lokális változó deklarációja és definíciója között használni akarjuk a változót. = x * y.y. Amire figyelni kell az az. amíg a felhasználó kilép (nehezítésképpen lehet egy karakter. hogy deklarálunk öt darab változót. vagyis addig tart. = x . Szükségünk lesz még egy logikai típusra is. result). egy Random objektumot.3 Kő – Papír – Olló Készítsünk kő-papír-olló játékot! Ebben az esetben is használjuk a véletlenszámgenerátort. case '/': result break. amivel minden esetben megtörténik . itt elég lesz a byte is. két stringet (ezekben tároljuk.int result=0.

ReadKey(true).Next(0. Most kérjük be az adatokat: Console. } }while(l).58 - . bool l = true. string playerChoice = "".ReadKey metódussal egyetlen karaktert olvasunk be a standard bemenetről. hogy akarunk –e még játszani): using System. case 'o': playerChoice = "olló". A ReadKey most kapott egy paramétert is. } switch(r. amellyel azt jeleztük. } } A Console. break.WriteLine("Akarsz még játszani? i/n"). de ezt az adatot nem használhatjuk fel azonnal.KeyChar) { case 'k': playerChoice = "kő". class Program { static public void Main() { Random r = new Random(). break.3)) { case 0: compChoice = "kő". case 2: compChoice = "olló". mivel nem char típust hanem ConsoleKeyInfo objektumot ad vissza. break.ReadKey(true).KeyChar == 'n'){ l = false. int compScore = 0. } . case 'p': playerChoice = "papír". if(Console. break. switch(Console. hogy az adott karakter ne jelenjen meg a konzolon. string compChoice = "".Készítsük el a program vázát a változó-deklarációkkal. case 1: compChoice = "papír". Ez utóbbi KeyChar tulajdonságával kapjuk vissza a beírt karaktert. do { Console. break.WriteLine("Mit választasz? (k/p/o)"). int playerScore = 0. break. illetve a főciklussal (a ciklustörzsben kérdezzünk rá.

WriteLine("Döntetlen! Az állás:\nSzámítógép: {0}\nJátékos:{1}". Már csak az eredmény kiértékelése van hátra: if((playerChoice == "kő" && compChoice == "papír") || (playerChoice == "papír" && compChoice == "olló") || (playerChoice == "olló" && compChoice == "kő")) { Console. ++playerScore). A kitalált szám legyen 1 és 100 között. A felhasználótól a játék végén kérdezzük meg. Legyen az alapötlet az.cs) Ennek a feladatnak a legnagyobb kihívása. Ha a gépen van a sor. ehhez egy kis segítség: jelöljük a három lehetőséget (k/p/o) a következőképpen: 100. Nyilvánvaló.WriteLine("Nyertél! Az állás:\nSzámítógép: {0}\nJátékos:{1}". compScore. hogy a tippelt szám nagyobb. Nézzük meg így a program vázát: . vagy kisebb-e. mint a kitalált szám. KO. Megoldás (7/Number. playerScore). ha a kitalált szám nagyobb. hogy a felhasználó próbálja kitalálni a program által kisorsolt számot. Az egyes lehetőségeket a k/p/o billentyűkre állítottuk. hogy akar–e ismét játszani. compScore. 7. amelyeket pl.először 50–et tippel. minden tipp után írjuk ki.59 - . vagyis ugyanazt kapjuk vissza. amely átlátható és könnyen módosítható. hogy egy bitenkénti VAGY művelettel ellenőrizni tudjuk a döntetlen eredményt mivel ekkor 100 | 100 = 100. hogy elágazásokat használunk. ++compScore.Ez a kódrészlet természetesen a ciklustörzsbe n a kérdés elé kerül.4 Számkitaláló játék Készítsünk számkitaláló játékot. vagy fordítva. } Érdekesebb megoldás bitműveletekkel elkészíteni a kiértékelést. Ha az eredmény nem döntetlen.WriteLine("Veszítettél! Az állás:\nSzámítógép: {0}\nJátékos:{1}". és így tovább). A gépi játékos úgy találja ki a számot. 101 és 011. amely lehetőséget ad kiválasztani. egy s witch szerkezettel dolgozhatunk fel. akkor használjunk véletlenszám-generátort a szám létrehozására. playerScore). akkor a VAGY háromféle eredményt adhat vissza (KP. hogy mindig felezi az intervallumot (pl. OP): 110. hogy olyan programszerkezetet rakj unk össze. 010. } elseif(playerChoice == compChoice) { Console. akkor 75 jön. Öt próbálkozása lehet a játékosnak. 001. } else { Console.

elfedi előlünk). case '2': goto COMPUTER. de ahhoz elég. ezért most szabad „rosszalkodnunk”.KeyChar) { case '1': goto PLAYER. Console.static public void Main() { // Itt kiválasztjuk. Ez a módszer a magas szintű nyelvek esetén nem igazán ajánlott (főleg.WriteLine("\nAkarsz még játszani? i/n").WriteLine("2 .WriteLine("Válassz játékmódot!").60 - . Console.Te gondolsz egy számra"). A procedurális és alacsonyszintű nyelvek az ilyen feladatokat „ugró” utasításokkal oldják meg. } PLAYER: goto END. de jelenleg nem is használjuk ki teljesen a nyelv adta lehetőségeket.ReadKey(true). hogy valami mást próbáljunk ki. emiatt pedig kevésbé lesz olvasható a forráskód. hogy ki választ számot if(/* A játékos választ */) { // A számítógép megpróbálja kitalálni a számot } else// A számítógép választ { // A játékos megpróbálja kitalálni a számot } // Megkérdezzük a játékost. Ez persze nem olyan nagy baj. hogy akar-e még játszani } Nem néz ki rosszul. } } } . END: Console. hogy a két feltétel blokkja nagyon el fog „hízni”.ReadKey(true). Írjuk át a fenti vázat egy kicsit: using System. switch(Console. class Program { static public void Main() { START: Console. mert megvannak az eszközök az ugrálás kikerülésére).WriteLine("1 .KeyChar) { case 'i': goto START. COMPUTER: goto END. csak ezt mi nem látjuk mivel az if/s witch/stb. case 'n': break.A számítógép gondol egy számra"). switch(Console. vagyis a forráskódban elhelyezett címkék között ugrálnak (tulajdonképpen a magas szintű nyelveknél is ez történik. de a probléma az.

WriteLine("A szám ennél nagyobb!"). rögtön a START címke előtt. switch(Console. Nyílván a default címkét kell használni és ott egy ugró utasítással a vezérlést a megfelelő helyre tenni: END: Console. Ami biztosan mindkét esetben kell az a véletlenszámgenerátor.WriteLine("Nyertél!"). . hogy a címkékkel kellemesen olvashatóvá és érthetővé vált a kód (persze ennél nagyobb terjedelmű forrásnál már problémásabb lehet). } else { Console.". Ezt könnyen kijavíthatjuk . lehet vele kísérletezni. amikor a játékos próbálja kitalálni a számot. mindössze egy ciklusra lesz szükségünk: COMPUTER: int number = r. Egy apró hibája van. a szám {0} volt.61 - . Ezt a kettőt deklaráljuk egyelőre. amíg el nem készítjük a lényeget. valamint két int típusú változó az egyikben a játékos és a számítógép tippjeit tároljuk . neki adjunk azonnal nulla értéket.ReadLine()).WriteLine("A szám ennél kisebb!"). Jól látható. Szükségünk lesz természetesen változókra is. } Most következik a program lényege: a játék elkészítése.Közben ki is egészítettük a kódot egy kicsit.Next(100). goto END. case 'n': break. mivel ez az egyszerűbb. default: goto END. while(i < 5) { Console. if(x < number) { Console.WriteLine("\nVesztettél. akkor a program végetér.Parse(Console. hogy ha i vagy n helyett más billentyűt nyomunk le. Elsőként azt a szituációt implementáljuk. ha egy kicsit gondolkodunk. } Console. hogy úgy vegyük fel őket. goto END. amely megkérdezi a játékost. } elseif(x > number) { Console. nem lesz nehéz dolgunk. hogy mindkét programrész használhassa őket.ReadKey(true). Már elkészítettük a programrészt. x = int. Készítsük is el a programot. } ++i. i = 0. number).WriteLine("\nAkarsz még játszani? i/n"). a másik pedig a ciklusváltozó lesz. de érdemes átgondolni. mégpedig az.WriteLine("\nA tipped: "). hogy szeretne-e még játszani.KeyChar) { case 'i': goto START.

az utolsó körben pedig tippelünk. x -= (max . int min = 0. Most már könnyen fel tudjuk írni. hogy ez nem elég. amire természetesen azt a választ kapjuk.WriteLine("A számítógép nyert!").x). Nézzük a kész kódot: PLAYER: Console. } break. Ismét felezünk.100)"). Console.WriteLine("Gondolj egy számra! (1 . hogy mindig felezzük az intervallumot. A legáltalánosabb módszer. case 'n': if(i == 3){ x = r. } ++i. ismét felezünk. Már csak 50 lehetséges szám maradt.WriteLine("A számítógép szerint a szám {0}".Next(x + 1. hogy a szám egy és száz között van. magunknak is tisztában kell lennünk azzal.WriteLine("A számítógép nem tudta kitalálni a számot. Az első tippünk 50 lesz.Next(min. így az utolsó tippre már elég szűk lesz az a számhalmaz. hogy a szám ennél nagyobb. x += (max .ReadKey(true). vagyis tizenkettőt adunk hozzá a hetvenöthöz és így ki is találtuk a gondolt számot. léphetünk a következő állomásra. int max = 100. . } break. amiből választhatunk (persze így egy kicsit szerencsejáték is lesz). } Console. ami viszont kicsit nehezebb lesz. while(i < 5) { Console.min) / 2. Nézzünk meg egy példát: a gondolt szám legyen a 87 és tudjuk. Ahhoz. case 'e': Console. méghozzá maradék nélkül. Ismét azt kapjuk vissza.62 - . "). x).ReadLine().max). a következő tippünk így a 75 lesz.min) / 2. } else { max = x. } else { min = x.KeyChar) { case 'k': if(i == 3){ x = r. goto END. switch(Console.WriteLine("Szerinted? (k/n/e)"). hogy mit kell tennie a számítógépnek: az első négy kísérletnél felezzük az intervallumot. hogy hogyan lehet megnyerni ezt a játékot. goto END.Ennek megértése nem okozhat gondot. hogy megfelelő stratégiát készítsünk a számítógép számára. x = 50. Console.

Egy példán keresztül nézzük meg. illetve felső határát. hogy a szám ennél biztosan nagyobb. Ez az elágazás fogja visszaadni az utolsó tippet a véletlenszám-generátorral a megfelelően leszűkített intervallumban. Az x változóban tároljuk az aktuális tippet. mi erre azt mondjuk. vagyis x–hez hozzá kell adni a felső és alsó határok különbségének a felét: (100 – 50) / 2 = 25 (ellenkező esetben pedig nyílván le kellene vonni ugyanezt). hogy hogyan működik a kódunk. A felső határ nyílván nem változik. hogy a szám ennél nagyobb. ezért a s witch ‟n‟ ága fog beindulni. már csak az új x–et kell kiszámolni. A számítógép első tippje 50. Legyen a gondolt szám ismét 87. .63 - . mivel tudjuk. Az intervallum alsó határa ezután x (vagyis 50) lesz. Amiről még nem beszéltünk az az a feltétel. amelyben x egyenlőségét vizsgáljuk hárommal (vagyis azt akarjuk tudni. neki meg is adtuk a kezdőértéket.A min illetve max változókkal tartjuk számon az intervallum alsó. hogy eljött –e az utolsó tipp ideje).

ezért explicit konverziót hajtottunk végre. persze az int kapacitása ennél nagyobb. amely kivételt dob (erről hamarosan). és ha mégis akkor adatvesztés is felléphet. byte y = (byte)x. de most csak a hasznos részre van szükség. hogy az egyes típusok másként jelennek meg a memóriában. implicit konverzió Ebben az esetben a long és int mindketten egész numerikus típusok. Egy implicit konverzió minden esetben sikeres és nem jár adatvesztéssel. Vegyük a következő példát: int x = 300.8 Típuskonverziók Azt már tudjuk. ellenőrzött konverziót fogunk használni. szinte minden esetben a szűkebb típusról a tágabbra: int x = 10. ami pont 44. y == ?? A byte szűkebb típus mint az int (8 illetve 32 bitesek). azonban gyakran kerülünk olyan helyzetbe. Vajon mennyi most az y változó értéke? A válasz elsőre meglepő lehet: 44. ezért a 300–nak csak az első 8 bitjét (00101100) adhatjuk át y–nak. Egy explicit konverzió nem feltétlenül fog működni. byte y = (byte)x. } . ezért a konverzió gond nélkül végbe megy. mint egy másiknak. Ennek ellenőrzésére ún. Ha lehetséges a konverzió. // explicit konverzió. akkor végbemegy. 8. long y = x. A byte viszont (ahogy a nevében is benne van) egy nyolcbites értéket tárolhat (vagyis a maximum értéke 255 lehet). A magyarázat: a 300 egy kilenc biten felírható szám (100101100). egyébként a fordító figyelmeztetni fog.1 Ellenőrzött konverziók A programfejlesztés alatt hasznos lehet tudnunk. Ilyen helyzetekben típuskonverziót (vagy castolást) kell elvégeznünk. ha a forrás nem fér el a célváltozóban: checked { int x = 300. ezt a változó előtti zárójelbe írt típussal jelöltük. Implicit konverzió általában „hasonló” típusokon működik. Kétféleképpen konvertálhatunk: implicit és explicit módon. a fordító kérés nélkül elvégzi helyettünk. és mivel a long tágabb (az int 32 bites míg a long 64 bites egész szám).64 - . hogy egy adott típusnak úgy kellene viselkednie. hog y minden konverzió gond nélkül lezajlott-e vagy sem. // y == 10. Az előbbi esetben nem kell semmit tennünk.

OverflowException) fog dobni (ha elindítjuk a lefordított programot hibaüzenet fogad majd). Előfordul.2 Is és as Az is operátort futásidejű típus-lekérdezésre használjuk: using System. Ennek az operátornak a leggyakoribb megvalósítás lekérdezése (erről később).Ez a kifejezés kivételt (System. de figyelmeztetést kapunk. értéktípusra nem (ekkor le sem fordul a program).WriteLine("x típusa int"). hogy csak egy-egy konverziót szeretnénk vizsgálni. Az ellenőrzés kikapcsolását is megtehetjük az unchecked használatával: int x = 300. statikus és tagváltozókat vizsgálhatjuk. Figyeljünk arra. if(x is int) // ha x egy int { Console. public class Program { static public void Main() { int x = 10. amihez nincs szükség egy egész blokkra: int x = 300. Az ajánlás szerint ellenőrzött konverziókat csak a fejlesztés ideje alatt (tesztelésre) használjunk. byte y = checked((byte)x). byte y = unchecked((byte)x). mivel némi teljesítményvesztéssel jár. } } } Ez a program lefordul. 8. .65 - . mivel a fordító felismeri. hogy ilyen esetekben csak a blokkon belül deklarált. Ezzel az operátorral csakis referencia-típusra konvertálhatunk. hogy a feltétel mindig igaz lesz. felhasználási területe az interfész- Párja az as az ellenőrzés mellett egy explicit típuskonverziót is végrehajt.

ekkor a karakter unicode értékét kapjuk vissza: using System. 99. string aa = a as string.3 Karakterkonverziók A char típust implicit módon tudjuk numerikus típusra konvertálni. tehát a konverzió a tízes számrendszerbeli értéket adja vissza. ami a 97 decimális számnak felel meg. A kis ‟a‟ betű hexadecimális unicode száma 0061h.WriteLine((int)ch). 8. // 123 string bb = b as string. Console. // NULL } } Amennyiben ez a konverzió nem hajtható végre . object c = 10.. public class Program { static public void Main() { object a = "123".ch <= 'z'.WriteLine(bb == null ? "NULL" : bb). a célváltozóhoz null érték rendelődik (ezért is van a referencia-típusokhoz korlátozva ez az operátor).. 100.WriteLine(aa == null ? "NULL" : aa). class Program { static public void Main() { for(char ch = 'a'. object b = "Hello". 98. } } } Erre a kimeneten a következő számok jelennek meg: 97.WriteLine(cc == null ? "NULL" : cc). // Hello string cc = c as string. . . Console.++ch) { Console.Nézzünk egy példát: using System.66 - . Console.

Látható. mivel ekkor a tömbelemek null -ra inicializálódnak. hogy több azonos típusú objektumot tároljunk el. Random r = new Random(). A számozás mindig nullától kezdődik. A tömb deklarációja után az egyes indexeken lévő elemek automatikusan a megfelelő nullértékre inicializálódnak (ebben az esetben 10 darab nullát fog tartalmazni a tömbünk). A C# mindig folytonos memóriablokkokban helyezi el egy tömb elemeit. az array[i ]a tömb i–edik elemét jelenti. } } } A példában a ciklusfeltétel megadásakor a tömb Length nevű tulajdonságát használtuk. for(int i = 0. Tömböt a következőképpen deklarálhatunk: int[] array = new int[10]. class Program { static public void Main() { int[] array = new int[10]. Az egyes elemekre az indexelő operátorral (szögletes zárójelek: [ ]) és az elem indexével (sorszámával) hivatkozunk. még leírni is egy örökkévalóság lenne). hiszen rendelkezésünkre áll a tömb adatszerkezet.9 Tömbök Gyakran van szükségünk arra. A következő példában feltöltünk egy tömböt véletlen számokkal és kiíratjuk a tartalmát: using System. de ezt nem is kell megtennünk. míg referencia-típusoknál ugyanez NullReferenceException típusú kivételt fog generálni. } foreach(int item in array) { Console. az indexelő-operátor használata is.Length. A tömb meghatározott számú. Az indexeléssel vigyázni kell.++i) { array[i] = r. A tömbök referencia-típusok. mivel értéktípusok esetében szimpla nullát kapnánk vissza az általunk nem beállított indexre hivatkozva (vagyis ez egy teljesen szabályos művelet). Ez nagy különbség. ilyenkor kényelmetlen lenne mindegyiknek külön változót foglalnunk (képzeljünk el 30 darab int típusú változót. Minden elemre egyértelműen mutat egy index (egész szám). azonos típusú elemek halmaza.67 - . így a legutolsó elem indexe: az elemek száma mínusz egy.Next(). viszont . amely visszaadja a tömb hosszát.i < array.WriteLine(item). ugyanis a fordító nem ellenőrzi fordítási időben az indexek helyességét. Ez a szabály referencia-típusoknál kissé máshogy működik. Ez a tömb tíz darab int típusú elem tárolására alkalmas.

Array osztályból származik. egydimenziós tömböt (vektort) használtuk. 0] (ne feledjük. 3]. 23. nem változtatható. Vegyük például a matematikából már ismert mátrixot: 12. 2 A = [ 13. 'd'. 'c' }.helytelen indexelés esetén futás időben IndexOutOfRangeException kivételt fog dobni a program. ahány dimenziós a tömb. 67. Az elemek számát a deklarációval azonnal meghatározzuk. mint amit fönt leírtunk. 55. az egyes elemekre két indexxel hivatkozunk. // tömb rendezése 9. Ekkor az elemek számát a fordító fogja meghatározni. 52 ] 45. Egy tömböt akár a deklaráció pillanatában is feltölthetünk a nekünk megfelelő értékekkel: char[] charArray = new char[]{ 'b'. hanem annyival. Így a 45 indexe: [2.] matrix = new int[3. rendezhetünk egy tömböt a Sort metódussal): chararray. .Sort(). még mindig nullától indexelünk). Multidimenziós tömböt a következő módon hozunk létre C# nyelven: int[. első helyen a sor áll és utána az oszlop. ezen a későbbiekben nem lehet változtatni. Itt is összeköthetjük az elemek megadását a deklarációval. olyan mint a fent látható. 2}. 55. Lehetőségünk van azonban többdimenziós tömbök létrehozására is. 1} }. {13. bár egy kicsit trükkösebb a dolog: int[.68 - . Ez a mátrix pontosan olyan. Dinamikusan bővíthető adatszerkezetekről a Gyűjtemények című fejezet szól. Az elemszám most is meghatározott. 67. ezért néhány hasznos művelet azonnal rendelkezésünkre áll (pl. ekkor nem egy indexszel hivatkozunk egy elemre. 52}. {45.] { {12. Minden tömb a System. 23. Ez itt egy 3x3–as mátrix.] matrix = new int[.1 Többdimenziós tömbök Eddig az ún. 'a'. 1 Ez egy kétdimenziós tömbnek (mátrix a neve – ki hinné?) felel meg.

] matrix = new int[3. Random r = new Random(). } } } } Most nem írjuk ki a számokat. A többdimenziós tömbök egy variánsa az ún. Erre a megoldást az ún. tehát a példában az első esetben a sor.Next(). hiszen egy ciklus biztosan nem lesz elég ehhez. 3]. j] = r. egyenetlen (jagged) tömb. a második az oszlopot adja meg. pontosabban az adott sorban elfoglalt indexét.GetLength(0). a belső pedig a sorok elemein: using System.i < matrix. azaz szépen végig kell valahogyan mennünk minden soron egyesével.GetLength(1). ha egyszerre csak egy dologgal foglalkozunk. ez nem okozhat gondot.Nyílván nem akarjuk mindig kézzel feltölteni a tömböket. ez konstans marad viszont a belső tömbök hossza tetszés szerint megadható: int[][] jarray = new int[3][]. Ekkor legalább egy dimenzió hosszát meg kell adnunk. azonban a sorok hosszát (az egyes sorok nyílván önálló vektorok) ráérünk később megadni. class Program { static public void Main() { int[. a másodikban az oszlop hosszát adjuk meg a ciklusfeltételben.69 - . vagyis gondolkodnunk kell: az index első tagja a sort. .++i) { // oszlopok for(int j = 0. A tömbök GetLength metódusa a paraméterként megadott dimenzió hosszát adja vissza (nullától számozva).++j) { matrix[i. viszont ezúttal nem olyan egyszerű a dolgunk. Készítettünk egy három sorral rendelkező tömböt. egymásba ágyazott ciklusok jelentik: a külső ciklus a sorokon megy át.j < matrix. és nem kell ugyanolyan hosszúnak lenniük. // sorok for(int i = 0. Ez alapján pedig jó ötletnek tűnik.

j < jarray[i].Length. 4. 5)]. } } } } Véletlenszám-generátorral adjuk meg a belső tömbök hosszát. class Program { static public void Main() { int[][] jarray = new int[3][]. de ott nem lenne értelme külön elérhetővé tenni az egyes sorokat). 2. for(int i = 0. A belső ciklusban jól látható. hogy a tömb elemei valóban tömbök. newint[]{ 1 } }.++i) { jarray[i] = new int[r.i < 3. for(int j = 0. newint[]{ 1. 3. hiszen használtuk a Length tulajdonságot (persze a hagyományos többdimenziós tömbök esetében is ez a helyzet.Next(1.70 - . Random r = new Random(). 2. 3 }.++j) { jarray[i][j] = i + j. Az inicializálás a következőképpen alakul ebben az esetben: int[][] jarray = new int[][] { newint[]{ 1. 5 }. persze értelmes kereteken belül. .using System.

Ez a viselkedés főleg akkor okozhat (teljesítmény)problémát. hanem egy teljesen új objektum keletkezik a memóriában (vagyis a string ún. // e } } Ekkor a visszaadott objektum típusa char lesz. Console. Egy string egyes betűire az indexelő operátorral hivatkozhatunk (vagyis minden stringet kezelhetünk tömbként is): using System. Nagyon fontos tudni.WriteLine(ch). hogy mikor egy létező string objektumnak új értéket adunk. A foreach ciklussal indexelő operátor nélkül is végigiterálhatunk a karaktersorozaton: foreach(char ch in s) { Console.WriteLine(s). de „nyers” szövegen is alkalmazhatjuk: Console.WriteLine(s[0]). ha sokszor van szükségünk erre a műveletre. using System. } Az indexelő operátort nem csak változókon. } } A látszat ellenére a string referencia-típus. A szintén beépített string típus ilyen karakterekből áll (tehát az egyes betűket char–ként kezelhetjük). .71 - . // y Ilyenkor egy „névtelen” változót készít a fordító és azt használja.10 Stringek A C# beépített karaktertípusa (char) egy unicode karaktert képes tárolni két byteon.WriteLine("ezegystring"[4]). immutable – megváltoztathatatlan típus). class Program { static public void Main() { string s = "ezegystring". akkor nem az eredeti példány módosul. viszont nem kötelező használnunk a new operátort. Console. class Program { static public void Main() { string s = "ezegystring".

} elseif(x < 0) { Console.WriteLine(s.LastIndexOf('n')). 'z'. class Program { static public void Main() { string s = "verylonglongstring". hogy a metódusoknak számos változata lehet.WriteLine("Az 'a' a kisebb").WriteLine(s.WriteLine(s. Most megvizsgálunk néhányat.Compare metódus nullát ad vissza. 'o' }.WriteLine("A két string egyenlő").WriteLine(s. class Program { static public void Main() { string a = "egyik". } else { Console. Keresés: using System.WriteLine("A 'b' a kisebb").IndexOfAny(chs)).Contains("long")). // true } } . b). itt most a leggyakrabban használtakat nézzük meg: Összehasonlítás: using System. char[] chs = new char[]{ 'y'.LastIndexOfAny(chs)). // 3 Console. } } } A String. int x = String.NET számos hasznos metódust biztosít a stringek hatékony kezeléséhez.Compare(a. string b = "másik".72 - . if(x == 0) { Console. // 16 Console.IndexOf('r')). de tudni kell. ha a két string egyenlő. és nullánál kisebbet/nagyobbat.WriteLine(s. // 9 Console.1 M etódusok A .10. Console. ha lexikografikusan – lényegében ábécésorrend szerint – kisebb/nagyobb). ha nem (pontosabban. // 2 Console.

ez pedig nem feltétlenül „olcsó” művelet. // mallstrin Console. akkor automatikusan egy új példány jön létre a memóriában. hogy amikor módosítunk egy stringet. // smallstring } } A Replace metódus az első paraméterének megfelelő karaktereket lecseréli a második paraméterre. Az Insert/Remove metódusok hozzáadnak. hogy ezek a metódusok soha nem az eredeti stringen végzik a módosításokat.WriteLine(s. Fontos megjegyezni. akkor a visszaadott érték -1 lesz. ha a paramétereként megadott karakter(sorozat) benne van a stringben. és azt adják vissza. illetve elvesznek a stringből (a megadott indexeken). és ha ez sem elég. "one")). Console. ekkor a csak a kezdőindexet adjuk meg és a végéig megy). // sma Console.2 StringBuilder Azt már tudjuk.Insert(0. A Trim string elején és végén lévő karaktereket vágja le. hanem egy új példányt hoznak létre. a Substring pedig kivág egy karaktersorozatot. Ha nincs találat. Ha sokszor (legalább 10+) van szükségünk erre.WriteLine(s.WriteLine(s. akkor használjuk inkább a StringBuilder típust. akkor allokál egy megfelelő méretű területet és átmásolja magát oda.ToLower()).Replace('s'. 2)). 'g' }. Végül a ToLower és ToUpper metódusok kisilletve nagybetűssé alakítják az eredeti stringet. A két metódus Any–re végződő változata egy karaktertömböt fogad paramétereként és az abban található összes karaktert próbálja megtalálni. 10.WriteLine(s. // allstring Console.ToUpper()).Remove(0. 3)). A StringBuilder a System. ez automatikusan lefoglal egy nagyobb darab memóriát . char[] chs = new char[]{ 's'. A Contains igaz értékkel tér vissza. 'l')).WriteLine(s. // SMALLSTRING Console.Az IndexOf és LastIndexOf metódusok egy string vagy karakter első illetve utolsó előfordulási indexét (stringek esetén a kezdőindexet) adják vissza.Text névtérben található.73 - . paraméterei a kezdő és végindexek (van egyparaméteres változata is. // lmallltring Console.Substring(0.WriteLine(s.WriteLine(s. Módosítás: using System.Trim(chs)). // onesmallstring Console. . class Program { static public void Main() { string s = "smallstring".

IsMatch(s3) ? "természetes szám" : "nem természetes szám")). using System. s3. Nézzük is egy példát: készítsünk olyan reguláris kifejezést. (pattern. Console. ekkor tizenhat karakternek foglal helyet). Console. string s3 = "2". hogy egy karaktersorozat megfelele egy adott mintának. Az Append metódussal tudunk karaktereket (vagy egész stringeket) hozzáfűzni. } Console.WriteLine("{0} : {1}".3 Reguláris kifejezések Reguláris kifejezések segítségével vizsgálhatjuk.WriteLine("{0} : {1}". // ez kell class Program { static public void Main() { Regex pattern = new Regex("^[1-9][0-9]*").Append(ch).RegularExpressions. class Program { static public void Main(string[] args) { StringBuilder sb = new StringBuilder(50).IsMatch(s1) ? "természetes szám" : "nem természetes szám")).WriteLine(sb). amely természetes számokra illik! A példában a nullát nem soroljuk a természetes számok közé.WriteLine("{0} : {1}". Console. A forráskód: using System.++ch) { sb. } } A StringBuilder fenti konstruktora (van több is) helyet foglal ötven karakter számára (létezik alapértelmezett konstruktora is. (pattern. using System.ch <= 'z'.74 - . string s1 = "012345". vagyis a minta a következő lesz: az első számjegy egy és kilenc közötti lesz.IsMatch(s2) ? "természetes szám" : "nem természetes szám")). for(char ch = 'a'.Text. 10. s2. string s2 = "112345". } } . (pattern.Text.Példa a StringBuilder használatára: using System. ezután pedig bármennyi nulla és kilenc közötti számjegy jöhet. s1.

string s3 = "+56 30 667-876-987-456".IsMatch(s3)).75 - . [0-9]*: a csillag(*) nulla vagy több előfordulást jelent. // true Console.A reguláris kifejezést egy Regex példánynak adjuk át. az előhívószám két vagy három számjegyből áll. hogy a mintaillesztést a string elejétől kell kezdeni. hogy hogyan épül fel: ^: ezzel a karakterrel (Alt Gr + 3) megmondjuk. Egy példa: +36 30 123-456-789 Lássuk a forráskódot: string s = @"^(\+)[1-9][0-9]{1. A programunk a következő kimenetet produkálja: 012345 : nem természetes szám 112345 : természetes szám 2 : természetes szám A következő példánk egy kicsit bonyolultabb lesz a reguláris kifejezés pedig már-már ijesztő: képzeljük el. Minden telefonszám három darab három számjegyből álló blokkból áll ezeket kötőjel választja el.IsMatch(s2)). Végül következik a telefonszám. a következő módon: minden szám az ország előhívójával kezdődik amelyet egy ‟+‟ jel előz meg.2}\s(\d{3}(-)){2}\d{3}$".WriteLine(pattern.WriteLine(pattern. [1-9]: a string elején egy darab numerikus karakter áll – kivéve a nullát. amelyet a körzetszám követ. mivel speciális karaktereket is tartalmaz (erre a fordító is figyelmeztet). Regex pattern = new Regex(s).IsMatch(s1)). lássuk.2}\s: ez a minta lesz az első két helyen és ő hívatott az előhívót illetve a körzetszámot ellenőrizni: a már ismert ^-vel az illesztést a . A kifejezésünk két részből áll: ^(\+)[1-9][0-9]{1. hogy a világon uniformizálják a telefonszámokat.2}\s[1-9][0-9]{1. vagyis az első számjegy után nulla vagy több nulla és kilenc közötti szám állhat. // false A reguláris kifejezés elé kötelezően oda kell tennünk a @ jelet. ami szintén két vagy három számjegy az első helyen itt sem állhat nulla. string s2 = "+3630 661-567-233". Ezután egy szóköz jön. Console. az első szám nem lehet nulla. ami a körzetszámot szóközzel elválasztva követi. // false Console.WriteLine(pattern. string s1 = "+36 30 661-345-612".

ahol megmondtuk. A . A \d előtt szereplő {2} az egész zárójelben lévő kifejezésre vonatkozik. További információt találhatunk az MSDN megfelelő oldalán: http://msdn. mivel ennek a jelnek önálló jelentése is van reguláris kifejezésben. amelyet a \d–vel (d mint digit) jelölünk. az ezt követő (\+) pedig megmondja. hogy a minta végén három számjegy áll.90).karaktersor elejétől kezdjük. rengeteg lehetőségünk van. hogy azt jelenti: az előtte lévő kifejezésnek pontosan háromszor kell szerepelnie. hogy az előtte lévő meghatározás ([0 -9]) legalább egy legfeljebb két alkalommal szerepel egymás után. Végül a \s egy szóközt jelöl. ez jelen esetben azt jelenti. hiszen benne van az amit kerestünk). hogy három számjegy után egy kötőjel következik. a fenti két példa csak ízelítő volt. hogy az első helyen egy ‟+‟ jel van (ez persze nem lesz ott a körzetszám előtt). (\d{3}(-)){2}\d{3}$: ez a kifejezés a kötőjellel elválasztott számblokkokat jelöli. A zárójelek közé „nyers” karaktereket (is) írhatunk.microsoft. hogy a karaktersorozat végéig kell illesztenie. A végén lévő $ jel jelzi. Ezután ismerős következik: az [19][0-9] –et már nem okozhat gondot megérteni az utána következő {1.2} –t már inkább. vagyis az egészet ellenőrizze (máskülönben a 345-345-345-456 sorozat érvényes lenne. Haladjunk továbbra is a végéről a {3}–ról már tudjuk.aspx . Ezzel azt közöltük. A ‟+‟ elé ‟\‟ –t kell tennünk.com/en-us/library/az24scfc(v=VS.76 - .NET reguláris kifejezései meglepően sokrétűek.

Nézzük is meg a forráskódot (csak a lényeg szerepel itt.1 M inimum.és maximumkeresés Keressük ki egy tömb legnagyobb. hogy épp hol vagyunk. ez az algoritmus nem túl gyors nagy elemszám esetén. amelyet vagy a billentyűzetről vagy – ha van – a parancssori paraméterből kap meg.++i) { if(array[i] < min) { min = array[i].11 Gyakorló feladatok II. Megoldás (11/MinMax.cs) A mininum/maximum-keresés a legalapvetőbb algoritmusok egyike. ezeket úgy kell megválasztani.i < 30. Értelemszerűen. minIdx = 0. max = -1. A feladatok: . mint a tömb elemei (feltesszük. akkor nullát. a tömb feltöltése nem okozhat gondot): int int int int min = 1000. és minden elemet összehasonlítunk az aktuális legkisebbel/legnagyobbal. A programunk ezt az adatot dolgozza föl. illetve legkisebb elemét és ezek indexeit (ha több ilyen elem van. mivel minden elemet kötelezően meg kell vizsgálnunk. maxIdx = 0. } if(array[i] > max) { max = array[i]. 11. hogy nullánál kisebb és ezernél nagyobb szám nincs a tömbben). minIdx = i. Ha sziget (szárazföld) fölött. for(int i = 0. hogy ezek biztosan kisebbek illetve nagyobbak legyenek. ha tenger fölött.2 Szigetek Egy szigetcsoport fölött elrepülve bizonyos időközönként megnéztük. 11. viszont nagyon jól működik párhuzamos környezetben – a tömb egyes részeit külön feldolgozóegység kapja meg. végigmegyünk a tömb elemein. Az alapelv rendkívül egyszerű.77 - . } } A min és max változóknak kezdőértéket is adtunk. maxIdx = i. akkor leírtunk egy egyest. akkor elég az első előfordulás).

} } A kódban két érdekes dolog is van. ahol a belső ciklus abbahagyta a vizsgálatot. Adjuk meg a leghosszabb egybefüggő szárazföld hosszát. } i = j.  Adjuk meg. . int j = i. string típusú változóban tároltuk el. } } else { ++i. A két feladatot egyszerre fogjuk megoldani. hogy a feltételek kiértékelése balról jobbra halad. amely attól az indextől megy egészen addig. Ha a string adott indexén egyest találunk. Ekkor egyrészt megnöveljük eggyel a szigetek számát tároló változót. amíg nullához nem ér. akkor elindítunk egy másik ciklust. hogy hány szigetet találtunk. while(j < data. Megoldás (11/Islands. A másik érdekesség a két ciklusváltozó. ellenkező esetben ugyanis kivételt kapnánk (hiszen ha a string vége után vagyunk ott már nincs semmi). hogy még a szigetet mérjük és azt is. while(i < data.Length) { if(data[i] == '1') { ++islandCount. mivel a programunk a következő elven alapul: a data stringen fogunk keresztülmenni egy ciklus segítségével.78 - . A szigeteken végzett méréseket a data nevű. Nézzük is meg a forráskódot: int islandCount = 0. másrészt tudni fogjuk. ++tmp. Amikor befejezzük a belső ciklust .Length && data[j] == '1') { ++j. int maxIslandLength = 0. int tmp = 0. akkor a külső ciklusváltozót új pozícióba kell helyeznünk. if(tmp > maxIslandLength){ maxIslandLength = tmp. hogy nem-e értünk a string végére. méghozzá oda. az a feltételek sorrendje: mivel tudjuk. hogy milyen hosszú volt a sziget és összehasonlíthatjuk az eddigi eredményekkel. a beolvasásuk nem okozhat mostanra gondot.cs) A megoldásban csak az adatok feldolgozását nézzük meg. hogy helyes indexet használunk -e. ezért először azt kell vizsgálnunk. int i = 0. az első a belső ciklus feltétele: ellenőrizzük. Ami fontos.

Ez a tömb végéről fog visszafelé menni és cserélgeti az elemeket.Length .és maximum-kiválasztásról van szó.3 Átlaghőmérséklet Az év minden napján megmértük az átlaghőmérsékletet. a tömb utolsó néhány eleme: .cs) Ehhez a feladathoz nem tartozik írásos megoldás.--j) { if(array[j . } } } Kezdjük a belső ciklussal.1] > array[j]) { int tmp = array[j]. 11. hogy minden hónap harminc napos.1]. amelynek alapelve. hogy a kisebb elemek – buborék módjára – felszivárognak.4 Buborékrendezés Valósítsuk meg egy tömbön a buborékrendezést. array[j] = array[j .11.  Keressük meg az év legmelegebb és leghidegebb napját!  Adjuk meg az év legmelegebb és leghidegebb hónapját!  Volt –e egymást követő öt nap (egy hónapon belül). Az első így néz ki: for(int i = 0.1] = tmp.++i) { for(int j = array.cs) A buborékos rendezés egy alapvető rendezési algoritmus. hogy a legkisebbet vigye tovább magával.79 - . amikor mínusz fokot mértünk? Megoldás (11/Temperature.1. tulajdonképpen egy minimum. az eredményeket pedig véletlenszám-generátorral (ésszerű kereteken belül) sorsoljuk ki). az eredményeket pedig egy mátrixban tároltuk (az egyszerűség kedvéért tegyük fel.i < array. mi most két változatát is megvizsgáljuk. míg a nagyobb elemek lesüllyednek. array[j . csak éppen kétdimenziós tömbben (viszont a jegyzethez csatolva van egy lehetséges megoldás). Megoldás (11/BubbleSort. Ennek az algoritmusnak többféle implementációja is létezik.Length . Legyen pl.j > i.1.

Length. ami most megint az eddigi legkisebb elemre az 5 –re fog mutatni és cserélhetjük tovább.1. } array[j + 1] = y. ezért megcseréljük a kettőt: 10 5 34 Ezután csökkentjük a ciklusváltozót. int j = i . Nézzünk meg egy másik megoldást is: for(int i = 1. nagy ordó) jelölést használjuk egy algoritmus megbecsülésére (illetve használják a matematika más területein is). } Itt lényegében ugyanarról van szó. amíg a legkisebb elem elfoglalja a tömb első indexét. Természetesen.i < array. Nem árt tudni. ami a 34 (array[j-1]). csak most elölről vizsgáljuk az elemeket. hogy a buborékos rendezés csak kis elemszám esetében hatékony. ha kisebb elemet találunk. akkor ezután őt fogjuk tovább vinni. Itt jön képbe a külső ciklus. ami azt biztosítja. --j. hogy a rendezett elemeket már ne vizsgáljuk. futásidejének . hiszen a belső ciklus minden futásakor a tömb elejére tesszük az aktuális legkisebb elemet. while(j > -1 && y < array[j]) { array[j + 1] = array[j]. egészen addig.10 34 5 Fogjuk az 5 –öt (array[j]) és összehasonlítjuk az előtte le vő elemmel. Az O() (ún. nagyjából O(n^2) nagyságrendű. Mivel nagyobb nála.++i) { int y = array[i].80 - .

1 UM L Az OOP tervezés elősegítésére hozták létre az UML –t (Unified Modelling Language). Ebben a fejezetben az OOP elméleti oldalával foglalkozunk. csóválja a farkát. ezt pédányosításnak nevezzük. Az osztály és példány közötti különbségre jó példa a recept (osztály) és a sütemény (példány). életkor. játszik. súly. 12.81 - . polimorfizmus).2 Osztály Az OOP világában egy osztály olyan adatok és műveletek összessége. így egyszerűsítve a munkát. hogy egy minden fejlesztő által ismert közös jelrendszert valósítson meg. ezért ezek csak később lesznek tárgyalva. stb. Tervezői (Ole-Johan Dahl és Kristen Nygaard) hajók viselkedését szimulálták és ekkor jött az ötlet. 12. a cél a paradigma megértése. . amelyek gyakorlati példákon keresztül érthetőbben megfogalmazhatók. hanem az egyes adatokat (adatszerkezeteket) és a közöttük levő kapcsolatokat (hierarchiát). amellyel leírhatjuk egy modell (vagy entitás) tulajdonságait és működését. Ahogy aztán a számítógépek széles körben elterjedtek.elmélet A korai programozási nyelvek nem az adatokra. hogy a procedurális módszerrel kényelmesen és hatékonyan kezelni lehessen őket. a célja.) és van meghatározott viselkedése (pl. Az első objektum-orientált programozási nyelv a Simula 67 volt. Legyen például a modellünk a kutya állatfaj. az adatok pedig túl komp lexekké váltak ahhoz. Egy kutyának vannak tulajdonságai (pl. mert akkoriban még főleg matematikai számításokat végeztek a számítógépekkel. akkor az adott osztályból (osztályokból) létre kell hoznunk egy (vagy több) példányt. hanem a műveletekre helyezték a hangsúlyt. A következőkben az UML eszközeit fogjuk felhasználni az adatok közötti relációk grafikus ábrázolásához.pl. Az OOP már nem a műveleteket helyezi a középpontba.12 Objektum-orientált programozás .) Az UML a következőképpen jelöl egy osztályt: Kutya Amikor programot írunk. megváltoztak az igények. stb. hogy a különböző hajótípusok adatait egy egységként kezeljék. Ez egy általános tervezőeszköz. gyakorlati példákkal a következő részekben találkozhatunk (szintén a következő részekben található meg néhány elméleti fogalom is.

de nem módosíthatják (a származtatás és öröklődés hamarosan jön) (UML jelölés: -).12. A kutyás példában az eszik() művelet magába foglalja a rágást. a kutya eszik (ez egy művelet). A műveletek összességét felületnek is hívjuk. ekkor megváltozik a „jóllakottság” tulajdonsága/állapota). de a leszármazott osztályok módosíthatják is (UML jelölés: #). Módosítsuk a diagramunkat: Kutya jollak : int eszik() : void Az adattagokat név: típus alakban ábrázoljuk. Az OOP egyik alapelve. illetve biztosítani kell a szükséges műveleteket a tulajdonságok megváltoztatásához (pl. de erről nem fontos tudniuk. a metódusokat pedig név(paraméterlista): visszatérési_érték formában. hogy a felhasználó csak annyi adatot kapjon meg.3 Adattag és metódus Egy objektumnak az életciklusa során megváltozhat az állapota. de idegenekkel nem akarom megosztani).82 - . tulajdonságai. Ezt az állapotot valahogy el kell tudnunk tárolni. A háromféle láthatóság:  Public: mindenki láthatja (UML jelölés: +). 12. .  Protected: ugyanaz. csak az evés ténye számít. Az ős-OOP szabályai háromféle láthatóságot fogalmaznak meg (ez nyelvtől függően bővülhet). A tulajdonságokat tároló változókat adattagoknak (vagy mezőnek). ha mindenki hozzájuk fér (az elfogadható. a műveleteket metódusoknak nevezzük. Ezekkel a fogalmakkal egy későbbi fejezet foglalkozik majd. illetve a leszármazott osztályok is láthatják. mint a private. nyelést. amennyi feltétlenül szükséges.  Private: csakis az osztályon belül elérhető. a C# láthatóságairól a következő részekben lesz szó.4 Láthatóság Az egyes tulajdonságokat és metódusokat nem feltétlenül kell közszemlére bocsátani. ha a közvetlen család hozzáfér a számlámhoz. emésztést is. Ugyanígy egy tulajdonság (adattag) esetében sem jó.

Ezen belül megkülönböztethetünk „Emlős” –t vagy „Hüllő” –t. Az „Emlős” osztály egy leszármazottja lehet a „Kutya” és így tovább. hogy a felület ne változzon (pl. Ez egy eléggé tág fogalom. Így amikor azt mondjuk. Bevett szokás ezeket elkülöníteni egy önálló forrásfileba.6 Öröklődés Az öröklődés vagy származtatás az új osztályok létrehozásának egy módja. Egy (vagy több) már létező osztályból hozunk létre egy újat úgy. 12. a C) az adatokat és a rajtuk végezhető műveleteket a program külön részeiként kezelik. A specializálás során az osztályok között ún. de végeredményben egy „Állat”.83 - . ez az ún. . hogy egy adott osztály belső szerkezetét gond nélkül megváltoztathatjuk. illetve hozzáférnek a struktúrákhoz és nem megfelelően használhatják fel azokat. attól függetlenül. egységbezárás (encapsulation vagy information hiding). mindössze arra kell figyelni.A Kutya osztály most így néz ki: Kutya -jollak : int +eszik() : void 12. Az öröklődést specializálásnak is nevezik. A kettő között nincs összerendelés. hogy a „Kutya” egy specializáltabb forma. A legegyszerűbben egy példán keresztül érthető meg. egy autót biztosan tudunk kormányozni. de ez még mindig nem elég biztonságos. mondjuk a „Gerinces Állatok”–ra. Ennek a gondolatmenetnek a gyakorlati felhasználás során lesz jelentősége. hogy az minden szülőjének tulajdonságát örökli vagy átfogalmazza azokat. „az-egy” (is-a) reláció áll fenn. Ennek egyik nagy előnye. nem OO programnyelvek (pl. amelynek megvan a saját karakterisztikája. hogy a „Kutya” az egy „Állat” akkor arra gondolunk. Az OO paradigma egységbe zárja az adatokat és a hozzájuk tartozó felületet. Legyen egy „Állat” osztályunk. ezért más programozók gond nélkül átírhatják egyiket vagy másikat. hogy az egy személyautó.5 Egységbezárás A „hagyományos”. traktor vagy Forma-1–es gép). ezért szűkíthetjük a kört.

csak a felületet szeretnénk átörökíteni. csakis a műveleteket deklarálja. az nem lényeges. Az interfészek az osztálytól függetlenek. de az evés az állatokhoz köthető valami. ún. amelynek nincsenek adattagjai. amit az utódok megvalósítanak .84 - . Ilyenkor nem egy „igazi” osztályról. .egy krokodil nyílván máshogy eszik mint egy hangya. Az osztályok között fennálló kapcsolatok összességét hierarchiának nevezzük. a kettő között ugyanis lényegi különbség van. A C# rendelkezik önálló interfészekkel. de ez nem minden programnyelvre igaz. UML-ül az öröklődést „üres” nyíllal jelöljük. absztrakt osztályokat használnak. hogy nem fontos számunkra a belső szerkezet. Előfordul. A C# nyelvi szinten támogat absztrakt osztályokat is. ezért ők egy hasonló szerkezetet. csakis felületet biztosítanak (például az IEnumerable és IEnumerator a foreach–nek (is) biztosítanak felületet. amelyben mondjuk megadunk egy absztrakt „evés” metódust. Ezekben előfordulnak adattagok is. hogy az osztályunkat fel tudja használni a programunk egy másik része (ilyen például a már említett foreach ciklus). ezért közös). hanem egy interfészről – felületről – beszélünk. hogy milyen osztályról van szó). de leginkább a felület definiálására koncentrálnak. amely a specializált osztály felől mutat az általánosabbra.A diagram a fenti példát ábrázolja. Az absztrakt osztályok viszont egy őshöz kötik az utódokat (erre az esetre példa az „Állat” osztály.

hogy a főprogram és a saját osztályunk elkülönül. hogy honnan tudja a fordító.13 Osztályok Osztályt a class kulcsszó segítségével deklarálhatunk: using System. kerüljük el. hogy melyik a „fő” osztály? A helyzet az.cs Futtatáskor a „Program1” szöveget fogja kiírni a program. class Dog { } class Program { static public void Main(string[] args) { } } Látható. } } class Program2 { static public void Main() { Console.WriteLine("Program1"). hogy a Main egy speciális metódus.WriteLine("Program2"). mint a program belépési pontját. Ettől függetlenül ezt a megoldást. Felmerülhet a kérdés. class Program1 { static public void Main() { Console. ekkor a fordítóprogramnak meg kell adni (a /main kapcsoló segítségével). Nézzünk egy példát: using System. Konvenció szerint az osztálynév mindig nagybetűvel kezdődik. ezt a fordító automatikusan felismeri és megjelöli. Igazából lehetséges több Main nevű metódust is létrehozni. ha csak lehet. hogy melyik az igazi belépési pont. } } Ezt a forráskódot így fordíthajuk le: csc /main:Program1 main. .85 - .

A C++ nyelvet ismerők figyeljenek arra. A new hívásakor lefut a konstruktor. hogy az osztály-deklaráció végén nincs pontosvessző. akkor ez a lehetőség megszűnik). mivel a Base osztály csakis paraméteres konstruktorral rendelkezik. 13. akkor valamely másik konstruktorát explicit módon hívni kell a leszármazott osztály konstruktorából a base metódussal. hogy: csc -help A fenti példában a lehető legegyszerűbb osztályt hoztuk létre. megfelelő nagyságú hely foglalódik a memóriában. akkor ez a System. hogy „beállítsa” az osztály értékeit. Az osztályunkból a new operátor segítségével tudunk készíteni egy példányt. amelynek nincs konstruktora (amennyiben bármilyen konstruktort létrehoztunk. Bár a fenti osztályunkban nem definiáltunk semmi ilyesmit ettől függetlenül rendelkezik alapértelmezett (azaz paraméter nélküli) konstruktorral. Dog d = new Dog(). ha az ősosztály nem tartalmaz alapértelmezett konstruktort (mert van neki paraméteres). Ha nem származtattunk direkt módon (mint a fenti programban). Ez igaz minden olyan osztályra. class Base { public Base(string s){ } } class Derived : Base { } Ez a forráskód(részlet) nem fordul le.Object konstruktora lesz (tulajdonképpen ez előbb vagy utóbb mindenképpen meghívódik. melynek feladata. .A fordító további kapcsolóiról tudhatunk meg többet. ezután pedig megtörténik az adattagok inicializálása is.86 - .1 Konstruktorok Minden esetben amikor példányosítunk egy speciális metódus a konstruktor fut le. minden más esetben fordítási hiba az eredmény. Az alapértelmezett konstruktor legelőször meghívja a saját ősosztálya alapértelmezett konstruktorát. hiszen az ősosztály konstruktora is meghívja a saját ősét és így tovább). ha parancssorba beírjuk. Abban az esetben.

87 - . private int _age. mivel itt csak alapértelmezett konstruktort kapunk automatikusan. amely minden esetben az ősosztály valamely konstruktorát hívja. A C++ nyelvet ismerők vigyázzanak. Jelen pillanatban az osztályunkat semmire nem tudjuk használni. referencia. A base nem összekeverendő a Base nevű osztállyal. értékadó operátort illetve másoló konstruktort nem.és nullabletípusok ->null). Az alapértelmezett konstruktor valamilyen formában minden esetben lefut._age = age. public Dog(string name. 2). bool ->false. class Dog { private string _name.class Base { public Base(string s){ } } class Derived : Base { public Derived() : base("abc"){ } } Így már viszont működni fog. akkor is._name = name. } } class Program { static public void Main() { Dog d = new Dog("Rex". Az adattagok – ha vannak – automatikusan a nekik megfelelő nullértékre inicializálódnak(pl: int-> 0. Ugyanakkor minden osztály a System. ezért készítsünk hozzá néhány adattagot és egy konstruktort: using System. } } . ha az osztályban deklaráltunk paraméterest. hiszen továbbra is ez felel a memóriafoglalásért.Object –ből származik (még akkor is ha erre nem utal semmi). egyébként nem fordul le a program. int age) { this. this. ez egy önálló metódus. Egy osztály példányosításához legalább egy public elérhetőségű konstruktorra van szükség. ezért néhány metódust (például a típus lekérdezéséhez) a konstruktorhoz hasonlóan azonnal használhatunk.

A fordítóprogram automatikusan „odaképzeli” magának a fordítás során. elméleti rész). int age) { this. Ez paramétereként egy saját magával megegyező típusú objektumot kap és annak értékeivel inicializálja magát. Nem csak a konstruktorban adhatunk értéket a mezőknek. ahol az osztálypéldányra van szükség). inicializálókat is: class Dog { private string _name = "Rex". this. így most egyszerűbben oldjuk meg: .A konstruktor neve meg kell egyezzen az osztály nevével és semmilyen visszatérési értéke nem lehet. azaz most csakis az osztályon belül használhatjuk és módosíthatjuk őket. A példában a konstruktor törzsében értéket adtunk a mezőknek a this hivatkozással. akkor a paraméter típusához leginkább illeszkedő fut le. } } Az inicializálás mindig a konstruktor előtt fut le. például a konstruktorban. public Dog(string name. A konstruktorok egy speciális változata az ún. hanem használhatunk ún. private int _age = 5. amelyen meghívták (a this kifejezés így minden olyan helyen használható. A mi konstruktorunk két paramétert vár. Az inicializálás sorrendje megegyezik a deklarálás sorrendjével (felülről lefelé halad). Egy osztálynak paraméterlistától függően bármennyi konstruktora lehet és egy konstruktorból hívhatunk egy másikat a this–szel: class Test { public Test() : this(10){ } public Test(int x){ } } Ha több konstruktor is van. hogy az utóbbi felülbírálhatja. ami viszont publikus. Az adattagok private elérésűek (ld. akkor a példányosítás során minden esetben felülírnánk az alapértelmezettnek megadott kort. Másoló konstruktort általában az értékadó operátorral szoktak implementálni. így mindig tudja mivel dolgozik. másoló.88 - . Ha a Dog osztálynak ezt a módosított változatát használtuk volna fentebb. Nem kötelező használni._name = name. hogy ha sok adattag/metódus van. a nevet és a kort (metódusokkal és paramétereikkel a követke ző rész foglalkozik bővebben).vagy copy-konstruktor. de az operátor-kiterjesztés egy másik fejezet témája. ez egyben azt is jelenti. illetve ha a paraméterek neve megegyezik az adattagokéval. ugyanakkor hasznos lehet._age = age. amely mindig arra a példányra mutat.

amelyeket egy osztályon (vagy struktúrán) belül deklaráltunk.89 - . Az adattagokon használhatjuk a const típusmódosítót is. Az eddigi példáinkban is használtunk már adattagokat. mivel a C# a privát elérhetőséget csak osztályon kívül érvényesíti._age) { } } A program első ránézésre furcsa lehet. Most már használhatjuk is az új konstruktort: Dog d = new Dog("Rex". 2)._name. int age) { this. A mezőkön alkalmazható a readonly módosító is. otherDog. Az adattagok az osztálypéldányhoz tartoznak (azaz minden egyes példány különálló adattagokkal rendelkezik) vele születnek és „halnak” is meg. hasonlóan az előző fejezetben említett inicializáláshoz. ugyanolyan típusú objektumok látják egymást. Doge = new Dog(d). ez két dologban különbözik a konstansoktól: az értékadás elhalasztható a konstruktorig és az értékül adott kifejezés eredményének nem szükséges ismertnek lennie fordítási időben. private int _age = 5.2 Adattagok Az adattagok vagy mezők olyan változók._name = name.class Dog { private string _name = "Rex". public Dog(string name. Ezek a mezők pontosan ugyanúgy viselkednek mint a hagyományos konstansok. . felesleges minden alkalommal k ülön példányt készíteni belőle). 13. mivel a fordító egyébként is úgy fogja kezelni (ha egy adat minden objektumban változatlan. mivel privát elérhetőségű tagokat használunk._age = age. this. Egy konstans mezőt nem lehet statikusnak (statikus tagokról hamarosan) jelölni. } public Dog(Dog otherDog) : this(otherDog. ilyenek voltak a Dog osztályon belüli _name és _age változók. ekkor a deklarációnál értéket kell adnunk a mezőnek. de ezt minden gond nélkül megtehetjük. vagyis minden konstans adattagból globálisan – minden példányra vonatkozóan egy darab van.

 internal: csakis a tartalmazó (és a barát) assembly(ke)n belül látható.  private: csakis a tartalmazó osztályon belül látható.13. ha egy osztály-deklarációban használjuk a partial kulcsszót (ezt minden darabnál meg kell tennünk). illetve az ősosztály deklaráció a teljes osztályra (értsd: minden töredékre) érvényes lesz (ebből következik. Egy parciális osztály definíciója több részből (tipikusan több forrásfile-ból) állhat. p. hogy egy olyan metódust hívtunk.90 - .Do(). Egy parciális osztály minden töredékének ugyanazzal a láthatósági módosítóval kell rendelkeznie. a leszármazottak sem láthatják. valamint az egyik résznél alkalmazott egyéb módosítók (pl. töredék) osztályokat (partial class).4 Parciális osztályok C# nyelvben létrehozhatunk ún. . Ezek közül leggyakrabban az első hármat fogjuk használni.3 Láthatósági módosítók A C# ötféle módosítót ismer:  public: az osztályon/struktúrán kívül és belül teljes mértékben hozzáférhető. partial class PClass { } class Program { static public void Main() { PClass p = new PClass(). osztályok/struktúrák esetében az alapértelmezés. 13. hogy ne adjunk meg egymásnak ellentmondó módosítókat (pl.  protected: csakis a tartalmazó osztályon és leszármazottain belül látható. parciális (darab.  protected internal: a protected és internal keveréke. egy osztály nem kaphat abstract és sealed módosítókat egyidőben). hogy ezeket nem kötelező feltüntetni minden darabnál).cs using System. amelynek hiányzik a deklarációja. Nézzünk egy példát: // main. Ugyanakkor ennél az utolsó feltételnél figyelni kell arra. abstract). } } Látható.

ekkor a metódus deklarációja és definíciója szétoszlik: partial class PClass { partial void Do(). } } A két filet így tudjuk fordítani: csc main.0 már engedélyezi parciális metódusok használatát is. amikor az osztály egy részét a fordító generálja (pl. } } Parciális metódusnak nem lehet elérhetőségi módosítója (épp ezért minden esetben private elérésű lesz) valamint void-dal kell visszatérnie. ott a beágyazott osztályok töredékei szétoszolhatnak a tartalmazó osztály darabjai között).cs using System. a grafikus felületű alkalmazásoknál a kezdeti beállításokat az InitializeComponent metódus végzi. A partial kulcsszót ilyenkor is ki kell tenni minden előfordulásnál.WriteLine("Hello!"). partial class PClass { public void Do() { Console. Csakis parciális osztály tartalmazhat parciális metódust. Ennek a megoldásnak az a nagy előnye. } partial class PClass { partial void Do() { Console.cs A . Bármelyik osztály (tehát a nem-parciális is) tartalmazhat beágyazott parciális osztályt.NET a parciális osztályokat főként olyan esetekben használja.WriteLine("Hello!"). ekkor értelemszerűen a töredékek a tartalmazó osztályon belül kell legyenek (ugyanez nem vonatkozik a parciális osztályon belül lévő parciális osztályokra.Készítsünk egy másik forrásfilet is: // partial. .91 - .cs partial. Egy parciális osztály darabjainak ugyanabban az assemblyben kell lenniük. ezt teljes egészében a fordító készíti el). hogy könnyen ki tudjuk egészíteni ezeket a generált osztályokat. A C# 3.

} class Inner { Outer parent.Inner x = new Outer.Inner(). de ha mégis publikus elérésűnek deklaráljuk. } } } . private Inner child. de csakis akkor. } public void Do() { child. } public void Do() { Console.92 - . akkor a „külső” osztályon keresztül érhetjük el. public Outer() { child = new Inner(this). // példányosítás Egy beágyazott osztály hozzáfér az őt tartalmazó osztálypéldány minden tagjához (beleértve a private elérésű tagokat és más beágyazo tt osztályokat is).Do(). public Inner(Outer o) { parent = o.5 Beágyazott osztályok Egy osztály tartalmazhat metódusokat. ha a beágyazott osztály tárol egy. A beágyazott osztályok alapértelmezés szerint privát elérésűek. class Outer { class Inner { // a beágyazott osztály nem látható } } class Outer { public class Inner { // így már látszik } } Outer.value).13.WriteLine(parent. a külső osztályra hivatkozó referenciát: class Outer { private int value = 11. Egy ilyen osztályt általában elrejtünk. adattagokat és más osztályokat is. Ezeket a „belső” osztályokat beágyazott (nested) osztálynak nevezzük.

Természetesen. public string Name { get{ return _name. ugyanakkor kézzel is meghívható. // az objektumra már nem mutat semmi.NET ún. az értéktípusokat nem a GC kezeli. akkor is használhatjuk ezt a beállítási módot. de ez nem ajánlott. 13. amelynek lényege.6 Objektum inicializálók A C# 3. felszabadítható Objektumok alatt ebben a fejezetben csak és kizárólag referencia-típusokat értünk. } set{ _name = value. ha létezik paraméteres konstruktor. A következő példában foglalunk némi memóriát. azaz előre nem tudjuk megmondani.13.} } } class Program { static public void Main() { Person p = new Person() { Name = "István" }. amelyek az osztály által használt erőforrások felszabadításáért felelősek.WriteLine(p. Console. A . hogy mikor fut le. hogy mi történik felszabadítás előtt és után: .Name). vagy egy tulajdonságra hivatkozunk (ez utóbbit használtuk).7 Destruktorok A destruktorok a konstruktorokhoz hasonló speciális metódusok. // mc egy MyClass objektumra mutat mc = null. majd megvizsgáljuk. automatikus szemétgyűjtő (garbage collector) rendszert használ. hogy a hivatkozás nélküli objektumokat (nincs rájuk mutató érvényes referencia) a keretrendszer automatikusan felszabadítja.0 objektumok példányosításának egy érdekesebb formáját is tartalmazza: using System.93 - . } } Ilyen esetekben vagy egy nyilvános tagra. A szemétgyűjtő működése nem determinisztikus. MyClass mc = new MyClass(). class Person { public Person(){ } private string _name.

for(int i = 0. GC. generációs hipotézis) (gondoljunk csak a lokális változókra. hogy a legfrissebben létrehozott objektumok lesznek leghamarabb felszabadíthatóak (ez az ún. . } Console.++i) { int[] x = new int[1000].WriteLine("Foglalt memória: {0}".using System. és ha talál hivatkozás nélküli objektumot azt törli (pontosabban az elfoglalt memóriaterületet szabadnak jelöli).Collect(). } } A GC osztály GetTotalMemory metódusa a program által lefoglalt byte -ok számát adja vissza.WriteLine("Foglalt memória: {0}". // meghívjuk a szemétgyűjtőt Console. amelyeket viszonylag sokat használunk). GC. hogy meg szeretnénk –e hívni a szemétgyűjtőt. hogy a ciklusban létrehozott tömbök minden ciklus végén eltüntethetőek. a GC először ezt a generációt vizsgálja meg. ha a program megáll (illetve elfogyhat a memória is. Az egyes generációk összefüggő memóriaterületen vannak. GC. a maradékot pedig átrakja az első generációba. paraméterként megadhatjuk.GetTotalMemory(false)). ekkor OutOfMemoryException kivétel keletkezik). Ezután sorban átvizsgálja a többi generációt (még kettő van) és elvégzi a megfelelő módosításokat.GetTotalMemory(false)). Ez alapján a következő történik: minden friss objektum a nulladik – legfiatalabb – generációba kerül. Értelemszerűen a második generációs objektumok akkor törlődnek.i < 10. A fenti program kimenete valami ilyesmi kell legyen: Foglalt memória: 21060 Foglalt memória: 70212 Foglalt memória: 27144 Vegyük észre. class Program { static public void Main() { Console.94 - .GetTotalMemory(false)). Amikor eljön a szemétgyűjtés ideje. GC. amely abból a feltevésből indul ki. De hogyan is működik a szemétgyűjtő? A . mivel nincs többé rájuk hivatkozó referencia (hiszen a lokális objektumok hatóköre ott véget ér).WriteLine("Foglalt memória: {0}". generációs garbage collector -t használ.NET ún. így kevesebbet kell dolgoznia a gyűjtőnek.

az az ún. application root objektumok. A GC mindig a root objektumokat vizsgálja meg először. hogy melyik objektumok feleslegesek. hogy lassú. amelyek akkor és csakis akkor kerülnek hatókörön kívülre (és takaríthatóak el a GC által). tehát nem feltétlenül fog igazán nagy gondot jelenteni az . vizsgáljuk meg. hogy a takarítás valóban hatékony legyen. Ahhoz. amelyekről feltételezhetjük. Ezek olyan objektumok. vagy azzal párhuzamosan dolgozni. Mindkét esetben rosszul járunk. vagy kevesebb erőforráshoz jut (ugyanakkor azt is számításba kell venni.Az is érdekes kérdés. ha nincs rájuk hivatkozó érvényes referencia. hogy a GC átvizsgálja a generációkat. hogy elérhetőek (ilyen objektumok lesznek pl. bármikor törölhe tő az általuk mutatott objektum ha nincs elég memória – akkor is ha létezik rá mutató érvényes – gyenge – hivatkozás. a gyenge.NET nyelvi szinten támogatja a gyenge referenciákat: int[][] array = new int[10][].Target != null) { int[][]array = (int[][])wr. és rajtuk keresztül építi fel a memóriatérképet.++i) { array[i] = new int[1000]. hiszen vagy „lefagy” a program egy időre. if(wr. } WeakReference wr = new WeakReference(array). amelyet a new operátorral hozunk létre.95 - . ezek „normális” objektumok. az összes lokális és globális változó). azaz bármikor eltakaríthatóak.i < 10. Most már tisztában vagyunk az alapokkal. A . Egy WeakReference objektumból „visszanyerhető” az eredeti erős referencia a Target tulajdonság segítségével. Azt mondtuk. } A másik fogalom. viszont ne felejtsük el ellenőrizni ennek nullértékét.  A gyenge referenciák ennek épp ellenkezőjét nyújtják.illetve erős referenciák ( weak. hogy a GC a lehető legjobb időben – értsd: akkor amikor a program a legkevesebb erőforrást használja – fog beindulni. hogy mi történik valójában. Ehhez be kell vezetnünk két fogalmat. fel kell függeszteni a program futását. mert lehet. a tömb objektumokra már csak gyenge referencia mutat. erős referenciával hivatkozunk. mégpedig az.Target. for(int i = 0. amelyet ismernünk kell.és strong-reference) intézményét:  Minden olyan objektumra. array = null. hogy honnan tudja a GC. hogy már átment rajta a GC (és konvertálnunk is kell. array = null. ennek azonban van egy kis hátránya. Ebben a példában miután az eredeti tömbhivatkozást null–ra állítottuk. mivel object típussal tér vissza): WeakReference wr = new WeakReference(array).

NET alkalmazáshoz automatikusan létrejön egy adatszerkezet (képzeljük el tömbként). a natív kód hátrányba is kerül(het). Ezután nyugodtan készíthetünk objektumokat. Mit is értünk ez alatt? Hatékonyság szempontjából az a legjobb. hiszen a teljes felhasznált memóriának csak kis részét kell megvizsgálnia.NET programot. bevezették a részleges takarítás fogalmát. Épp ezért. hogy rendbe rakja a memóriát. a GC automatikusan új szegmenseket fog igényelni.alkalmazás szempontjából). Tehát eljön a GC ideje.96 - . A managelt kód éppen a fenti tények miatt tudja felvenni a versenyt a natív programokkal. Amikor elindítunk egy . . vagyis feltehetjük. a lokális változók szinte soha nem fognak átkerülni még az egyes generációba sem. de minden objektum életében eljön a pillanat. mert ha elfogy a hely. Természetesen ezt nem tudhatjuk biztosan. Nyílván többmagos processzorral szerelt PC –knél jobb a helyzet. hogy pl. A megoldást az objektumok rögzítése (ún. nevezetesen a GC–nek. hogy ilyen módon nem használhatunk unmanaged kódot. mégpedig kétszegmensnyit: egyet a hagyományos objektumoknak (GC Heap) és egyet a nagyméretű (100+ kilobyte) objektumoknak (LOH – Large Object Heap) (ez utóbbit csakis teljes takarításnál vizsgálja a GC). mivel ez teljes mértékben megakadályozza a pointerműveleteket. valójában az ő dolga az objektumok teljes életciklusának a kezelése és a memória megfelelő szervezése is. ahelyett. ahol sokszor foglalunk és szabadítunk fel memóriát. Épp ezért a GC minden (fő)gyűjtőciklus (tehát teljes takarítás) alkalmával átmozgatja az objektumokat. card table) és megvizsgál minden olyan objektumot. Ez drámaian megnöveli a GC hatékonyságát. Összességében azt mondhatjuk. Sőt. Ilyenkor rá hárul az a rendkívül fontos feladat is. hogy a lehető legoptimálisabban érjük el őket. hogy van rájuk hivatkozó referencia (gondoljunk arra. majd fogja a fenti adatszerkezetet (ún. Azt gondolná az ember.NET ún. amely a következő feltevésre épül: az egyes illetve kettes generációkban lévő objektumok nagy valószínűséggel nem módosultak. átválogatja a nulladik generációt. amikor visszaadja a lelkét a teremtőjének. vagyis az egyes és kettes generáció tagjai tipikusan hosszú életű objektumok lesznek). hogy natív és managelt program között nincs nagy különbég sebesség tekintetében. erről egy későbbi fejezet számol be. hogy ennyi az egész. ha az osztálypéldányok egymáshoz közel – lehetőleg egymás mellett – vannak a memóriában. pinning) jelenti. hogy minden alkalommal teljes vizsgálatot végezne a GC. olyan alkalmazások esetében. A GC a nevével ellentétben nem csak ennyit tesz. akkor a GC elsőként szabad memóriát kér az operációs rendszertől (a . ezért minden . minden szegmens 16 MB méretű). de attól még fennáll a hatékonyság problémája. amely olyan memóriaterületen fekszik amelyet a card table módosítottnak jelölt. Ennek a megoldásnak egy hátulütője. szegmensekre osztja a memóriát. amelynek egyes indexei a memória egy bizonyos nagyságú területének állapotát mutatják (nem az objektumokét!).

szükségünk lesz egy konfigurációs filera (erről egy későbbi fejezetben). Ez a mód sokkal kisebb G0 kerettel rendelkezik. amely a következőket tartalmazza ( a példában kikapcsoljuk a párhuzamos futást): <conf iguration> <runtime> <gcConcurrent enabled="false"/> </runtime> </configuration> Vagy: <conf iguration> <runtime> <gcServer enabled="true"/> </runtime> </configuration> Ezzel pedig a szervermódot állítottuk be. amely felfüggeszti a program futását a tisztítás idejére. viszont ő teljes mértékben „leállítja” az alkalmazást (ún. hogy felhasználói felület reszponzív maradjon. A GC megsemmisítés előtt az objektumokon meghívja a hozzájuk tartozó destruktort. hogy felszabadítsa az osztály . akkor a program többi szálát csak nagyon rövid ideig függeszti fel és a takarítással egyidejűleg a program továbbra is helyet foglalhat a memóriában. másnéven Finalizer-t. Ha meg akarjuk változtatni egy program GC módját. ez pedig a destruktor. hogy hogyan használhatjuk a GC –t a gyakorlatban: A konstruktor(ok) mellett egy másik speciális metódus is jár minden referenciatípushoz. Ennek a metódusnak a feladata.97 - .Ezt a módszert olyan alkalmazásoknál használjuk. ha szükség van tisztításra. szól a GC–nek. tehát azt szabja meg. a GC beindul). Ez a limitálás a nulladik generációra vonatkozik. Ez az alapértelmezett mód. Hasonlóan működik a második is. Ha egy alkalmazás kifut a memóriából. hogy mennyi memóriát használhat fel egyszerre a G0 (amennyiben ezt az értéket elérjük.A GC háromféle módban tud működni:  A GC párhuzamosan fut az alkalmazással  A GC felfüggesztheti az alkalmazást  Szerver mód Nézzük az elsőt: az objektumok allokálását egy különálló szál végzi. Stop-The-World módszer) a tisztítás idejére. amikor fontos. Szerver módban minden egyes processzor külön heap–pel és GC–vel rendelkezik. Most pedig megnézzük. kivéve ha az átlépte a maximálisan kiszabott keretet.

vagyis ez egy felesleges metódushívás lenne. } finally { base. hogy lezárja a hálózati kapcsolatokat. először a „Konstruktor” szót fogjuk látni. A fenti kód valójában a következő formában létezik: class DestructableClass { public DestructableClass() { Console. } protected override void Finalize() { try { Console. } } class Program { static public void Main() { DestructableClass dc = newDestructableClass(). } } } .WriteLine("Destruktor"). Ha lefordítjuk ezt a kódot és elindítjuk a programot. majd egy gomb lenyomása után megjeleni a párja is (ha látni is akarjuk. ha üres..WriteLine("Destruktor").WriteLine("Konstruktor"). nem árt parancssorból futtatni). neve megegyezik az osztályéval. ez csakis a GC előjoga). és mindenképpen meghívja mindegyiket – akkor is. Soha ne készítsünk üres destruktort.98 - . bezárjon minden egyes megnyitott file-t. mivel a GC minden destruktorról bejegyzést készít.Finalize().által használt erőforrásokat (pl. } } A destruktor neve tilde jellel (~) kezdődik. class DestructableClass { public DestructableClass() { Console.ReadKey(). és nem lehet semmilyen módosítója vagy paramétere (értelemszerűen egy destruktor mindig privát elérhetőségű lesz.). stb. } ~DestructableClass() { Console.WriteLine("Konstruktor"). Vegyük a következő kódot: using System. Console. vagyis közvetlenül soha nem hívhatjuk.

mivel a Finalize metódust nem definiálhatjuk felül.99 - . ezek a következőek:     Egy osztálynak csak egy destruktora lehet A destruktor nem örökölhető A destruktort nem lehet direkt hívni.Object destruktora lesz) és így tovább. amikor valamilyen unmanaged erőforrást (memória. struktúrának nem Legtöbbször felesleges destruktort készíteni. } } class Program { static public void Main() { Derived d = new Derived(). } } A destruktorokra vonatkozik néhány szabály. } } class Derived : Base { ~Derived() { Console. file.Object –től.) használunk.. ez csak néhány speciális esetben szükséges.. stb. Először felszabadítja az osztály erőforrásait (a destruktorban általunk megszabott módon). nem fordul le.WriteLine("Derived"). . pl. azután meghívja az ősosztály Finalize metódusát (ez legalább a System.Ez a forráskód csak példa. class Base { ~Base() { Console. amíg a lánc végére nem ér: using System. a hívás mindig automatikusan történik Destruktora csakis osztálynak lehet. erre való a destruktor. A Finalize-t minden referencia-típus örökli a System.WriteLine("Base").

} private void Dispose(bool disposing) { if(!disposed) { if(disposing) { // managed erõforrások felszabadítása } // unmanaged erõforrások felszabadítása disposed = true. ha valóban definiáltunk destruktort. hogy jelezzük a GC –nek. } } Az interfész által deklarált Dispose metódusban meg kell hívnunk a GC. hogy ez az osztály már felszabadította az erőforrásait. Dispose tervezési mintát alkalmazzuk. GC.1 IDisposable Az IDisposable interfész segítségével egy osztály által használt erőforrások felszabadítása kézzel – előre meghatározott időpontban – is megtörténhet. egyébként felesleges. class DisposableClass : IDisposable { public void Dispose() { // Takarítunk GC. using System. tehát nem kell a GC –re várni.SuppressFinalize metódust.7. és nem kell destruktort hívnia. public void Dispose() { Dispose(true).SuppressFinalize(this). } } ~DisposableClass() { Dispose(false). } } class Program { static public void Main() { DisposableClass dc = new DisposableClass(). amely megvalósításához nyílván az IDisposable interfész lesz segítségünkre.100 - .13. Destruktorok használata helyett általában az ún. A fenti kódban egy kicsit csaltunk: a SuppressFinalize csak akkor kell.SuppressFinalize(this). class DisposableClass : IDisposable { private bool disposed = false. } } .

} } } A legtöbb I/O művelettel (file. class DisposableClass : IDisposable { public void Dispose() { Console. vagyis csak az unmanaged erőforrásokkal kell törődnünk. GC. mind a natív erőforrások felszabadíthatóak. hogy a blokk hatókörén kívülre érve a Dispose metódus automatikusan meghívódik: using System. Ha a második metódus paramétere true értékű a híváskor. amit az interfésztől kaptunk.WriteLine("Takarítunk. hogy összehangoljuk a GC munkáját a kézi erőforrás-felszabadítással. } } class Program { static public void Main() { using(DisposableClass dc = new DisposableClass()) { Console.."). hogy kézzel hívtuk a Dispose metódust.és hálózatkezelés) kapcsolatos osztály megvalósítja az IDisposable –t. Ha a paraméter értéke false.Ebben a forráskódban két Dispose metódust készítettünk.. akkor tudjuk. using blokk-ban. ami azt jelenti. akkor pedig a hívás a destruktorból származik. az első paraméter nélküli az... . Az IDisposable interfészt megvalósító osztályok használhatóak ún.SuppressFinalize(this).101 - .WriteLine("Using blokk."). ezért ezeket ajánlott mindig using blokkban használni. míg a másik arra szolgál. vagyis mind a menedzselt.

name = name.. int age. Ebben a fejezetben az osztály (instance) metódusokról lesz szó. végül a sort a paraméterlista zárja. public Dog(string name. minden operátor valójában metódus formájában létezik.. this. hogy nem várunk ilyesmit. class Dog { string name. jelen esetben a void–dal jeleztük. d. hanem tenne is valamit. hogyan épül fel egy metódus: elsőként megadjuk a láthatóságot és itt is érvényes a szabály.Sleep(). ha a korábban elkészített kutya osztályunk nem csak lógna a semmiben. } public void Eat() { Console.. } } Nézzük meg. } public void Sleep() { Console.WriteLine("A kutya alszik.WriteLine("A kutya eszik. Egy metódussal megváltoztathatjuk egy objektum állapotát. ez konvenció szerint nagybetűvel kezdődik. Előbbi az ún. Következik a metódus neve.102 - . Készítsünk néhány metódust a legfontosabb műveletekhez: az evéshez és az alváshoz: using System. utóbbi pedig a statikus metódus. osztály metódus.age = age. vagy információt kaphatunk annak adatairól."). d. Ezután a visszatérési érték típusa áll. . } } class Program { static public void Main() { Dog d = new Dog("Rex". Bizonyára szeretnénk. ha látszólag nem így történik. amely vagy egy objektumhoz.. int age) { this. hogy ennek hiányában az alapértelmezett privát elérés lesz érvényben. pl."). 2). Optimális esetben egy adattaghoz csakis metódusokon keresztül férhetünk hozzá (ez akkor is igaz. vagy egy osztályhoz köthető.Eat(). ezt majd látni fogjuk).14 Metódusok Az objektum-orientált programozásban egy metódus olyan programrész.

de van egy nagy különbség. csak egy doboz. Az első osztályban metódust definiáltunk. public NewString1(string s) { this. } public void PrintUpper() { Console.ToUpper()). Mi a különbség? Azt mondtuk. NewString2 ns2 = new NewString2(). egyedül is életképes szerkezet van.PrintUpper().aString.PrintUpper("baba"). erről hamarosan). a C vagy Pascal nyelv) a metódusokhoz hasonló.Egy metódust az objektum neve után írt pont operátorral hívhatunk meg (ugyanez érvényes a publikus adattagokra. konstruktorral. annak életciklusában játszanak szerepet. mert egyébként nem fordulna le a program (ebben az esetben egy statikus „metódust” kellett volna készítenünk. ezek a függvény (function) és az eljárás (procedure). mindössze azért kell az osztály-definíció. ns1. A második osztály nem igazi osztály. hogy utóbbinak van visszatérési értéke).ToUpper()). } } class Program { static public void Main() { NewString1 ns1 = new NewString1("baba"). } } Pontosan ugyanaz történik mindkét esetben. amelyben egy teljesen önálló. de filozófiájában más eszközöket használ. Van állapota. .WriteLine(s. Az első osztály „valódi” osztály: adattaggal. ns2. Nézzünk egy példát: using System.aString = s. hogy a metódusok egy osztályhoz köthetőek. A „hagyományos” procedurális programozás (pl. class NewString1 { private string aString. stb. } } class NewString2 { public void PrintUpper(string s) { Console.103 - . végezhetünk rajta műveleteket. a másodikban eljárást (eljárás és függvény között a lényegi különbség. tulajdonságokra.WriteLine(this. stb. is).

Az értéktípusok alapértelmezetten érték szerint adódnak át. Erre a magyarázat nagyon egyszerű: már említettük. az nem eredményez fordítási hibát. de a változás csakis a metóduson belül lesz észlelhető. Utóbbi esetben van azonban egy kivétel. hogy míg a referencia-típus értékeit megváltoztathatjuk (és ez az eredeti objektumra is hat) addig magát a referenciát már nem (tehát nem készíthetünk új példányt. using System. Ha ezt mégis megtesszük.WriteLine("A paraméter: {0}". A paraméterek számát és típusait a metódus deklarációjában. amelynek értékei megegyeznek az eredetiével. míg a referenciatípusoknál a cím szerinti átadás az előre meghatározott viselkedés. Egy osztály bármennyi azonos nevű metódust tartalmazhat. tehát az eredeti objektummal dolgozunk. vagyis paramétereket. param). class Test { public void Method(string param) { Console. amelyre az átadott referencia mutat).és referencia-típusok különbözően viselkednek az átadás szempontjából. } } A C# nyelvben paraméterek átadhatunk érték és cím szerint is.104 - . ameddig a paraméterlistájuk különbözik. Az érték. A paraméterek a metóduson belül lokális változókként viselkednek. Egy metódusnak gyakorlatilag bármennyi paramétere lehet. Előbbi esetben egy teljesen új példány jön létre az adott osztályból. A másik esetben egy az objektumra mutató referencia adódik át. és a paraméter nevével hivatkozunk rájuk. mégpedig az. t. hogy egy metódusparaméter lokális változóként viselkedik. } } class Program { static public void Main() { Test t = new Test(). vesszővel elválasztva adjuk meg.Method("Paraméter"). szignatúrának vagy prototípusnak nevezzük. A metódus nevét és paraméterlistáját aláírásnak.14. .1 Paraméterek Az objektummal való kommunikáció érdekében képesnek kell lennünk kívűlről megadni adatokat. vagyis ebben az esetben egyszerűen egy lokális referenciával dolgoznánk.

TestMethod(t).x = 11. // 10 } } Ha mégis módosítani akarjuk egy referencia-típus referenciáját. } } class Program { static public void Main() { Test t = new Test(). Ha ezt nem tettük meg. t. Console. kivételt képez. de a metódus hívásakor kivételt fogunk kapni (NullReferenceException). // 10. hogy „valódi” referenciaként akarjuk átadni. akkor külön jeleznünk kell azt.A referencia szerinti átadást a forráskódban is jelölni kell.x). class Test { public int x = 10. A ref értéktípusok esetében már sokkal hasznosabb. attól a program még lefordul.using System. Az első esetben az átadott objektumnak inicializáltnak kell lennie (tehát mindenképpen mutatnia kell valahová. használnunk kellett a new operátort). nézzük a következő forráskódot: .105 - .WriteLine(t. Referencia-típust gyakorlatilag soha nem kell ilyen módon átadnunk (persze nincs megtiltva.x). Console. public void TestMethod(Test t) { t = new Test(). Kétféleképpen adhatunk át paramétert referencia szerint. ha ezt valamilyen . t. mind a metódus prototípusánál.WriteLine(t.NET–en kívüli eszköz megköveteli (a lényeg. de gondos tervezéssel elkerülhető). hogy már allokált objektumra mutató referenciát optimális esetben nem állítunk máshová). mind a hívás helyén a ref módosítóval.

y). y = tmp. y = tmp. int y = 20. vagyis a metódus belsejében teljesen új változókkal dolgozunk. Test t = new Test(). y = {1}". mert int típusok (mivel értéktípusról van szó) érték szerint adódnak át. ref y). x = y. x.Swap(x.WriteLine("x = {0}. y = {1}". class Test { public void Swap(ref int x. } } A Swap eljárással megpróbáljuk felcserélni x és y értékeit. y). int y) { int tmp = x.Swap(ref x. Írjuk át egy kicsit a forrást: using System. class Test { public void Swap(int x.WriteLine("x = {0}.106 - . x. t. } } class Program { static public void Main() { int x = 10. } } Most már az történik. Azért csak próbáljuk. int y = 20. amit szeretnénk: x és y értéke megcserélődött. . t.using System. Console. ref int y) { int tmp = x. } } class Program { static public void Main() { int x = 10. x = y. y). Console. Test t = new Test().

hogy x és y ne legyen egyenlő) Bár ez az eljárás hatékonyabbnak tűnik. ez az érték a helyén van) Végül a harmadik sor: 11110 01010 XOR --------10100 (kész vagyunk) Hogy ez a módszer miért működik. Most lássuk. ahol nincs elég memória/regiszter manőverezni – tipikusan mikrokontrollerek esetébe n. A XOR – Swap olyan limitált helyzetekben hasznos. egy kis segítség azért jár: felhasználjuk a XOR következő tulajdonságait: Kommutatív: A XOR B = B XOR A Asszociatív: (A XOR B) XOR C = A XOR (B XOR C) Létezik neutrális elem (jelöljük NE –vel): A XOR NE = A Minden elem saját maga inverze: A XOR A = 0 (ez az állítás az oka annak. A cím szerinti átadás másik formájában nem inicializált paramétert is átadhatunk. x ^= y. hogy mi történik! Az első sor: 01010 10100 XOR --------11110 (ez lesz most x) A második sor: 10100 11110 XOR -------01010 (ez most y. y ^= x. } } A két számot írjuk fel kettes számrendszerben: x (= 10) = 01010 és y (= 20) =10100. mint az átmeneti változót használó társa.107 - .Egy érdekesebb módszer két szám megcserélésére: használjuk a kizáró vagy operátort. ami akkor ad vissza igaz értéket. de ekkor feltétel. Nézzük először a kódot: public void Swap(ref int x. ref int y) { if(x != y) { x ^= y. hogy ellenőriznünk kell. azt mindenki gondolja át maga. hogy a metóduson belül állítsuk be (átadhatunk így már inicializált . ha a két operandusa közül pontosan az egyik igaz. igazából ez egy hagyományos PC –n még lassúbb is lehet.

t. t. hogy hány paramétere van egy metódusnak. A használata megegyezik a ref–fel.108 - . A használandó kulcsszó az out (Nomen est omen – A név kötelez): using System. de ekkor is feltétel. using System.WriteLine(t.paramétert is. 1. i. "körte".PrintElements(). class Test { public void PrintElements(params object[] list) { foreach(var item in list) { Console. class Init { public void TestInit(out Test t) { t = new Test() { s = "Hello!"}. // ez is működik } } . azaz a szignatúrában és a hívásnál is jelezni kell a szándékunkat. "dió"). Előfordul viszont. } } } class Program { static public void Main() { Test t = new Test().TestInit(out t). paraméter-tömböket kell használnunk. 4. ekkor ún. illetve egy paraméter-listában csak egyszer használható ez a szerkezet. hogy új objektumot készítsünk). Init i = new Init().s). } } class Test { public string s = null. Console. } class Program { static public void Main() { Test t = null. // Hello! } } A fenti programokban pontosan tudtuk.WriteLine(item). Ha ezt tess zük. akkor az adott metódus paraméter-listájában a paraméter-tömbnek kell az utolsó helyen állnia. hogy ezt nem tudjuk egyértelműen megmondani.PrintElements("alma".

ezután a metódus belsejében pontosan úgy viselkedik. } public string FirstName {get. ha valaki csak az első konstruktort használja. } } A „job” paraméterhez most alapértelmezett értéket rendeltünk. string lastName. ha rendelkezésünkre állnak az alapértelmezett paraméterek? Írjuk át a forráskódot: class Person { public Person(string firstName. } public string LastName {get. } public string Person(string firstName. Az osztályt most így tudjuk használni: . } public string Job { get. string job) : this(firstName. Nyílván ezt sem nagy gond megoldani. private set. private set. Paraméter-tömbként átadhatunk megfelelő típusú tömböket is. string job = "N/A") { FirstName = firstName.109 - . LastName = lastName.1.A paraméter-tömböt a params kulcsszóval vezetjük be. Nézzük a következő példát: class Person { public Person(string firstName. private set. private set. } public string Job {get. } public string FirstName { get. } public string LastName { get. ezért két konstruktort kellett készítenünk. 14. Job = job. LastName = lastName. viszont az gondot okozhat. lastName) { Job = job. de miért fáradnánk. hogy egy paramétereknek alapértelmezett értékeket adjunk. majd megpróbál hozzáférni a munka tulajdonsághoz. Ez alapvetően nem nagy probléma. private set. string lastName. ezáltal nem kell kötelezően megadnunk minden paramétert a metódus hívásakor. hogy minden adattag megfelelően inicializált. private set. string lastName) { FirstName = firstName.0 bevezeti az alapértelmezett paramétereket. mint egy normális tömb.1 Alapértelmezett paraméterek A C# 4. amelyek lehetővé teszik. így biztosak lehetünk benne. } } Mivel nem tudunk biztosan minden emberhez munkahelyet rendelni.

hogy pontosan melyik paraméterre gondolunk. Amennyiben ezt megtettük."Reiter"). Mivel tudatjuk a fordítóval.Personp1=newPerson("István". A metódus-deklarációnál meg kell adnunk a visszatérési érték típusát is. Ezenkívül szeretnénk olyan függvényeket is készíteni. lastName:"Reiter"). hogy melyik paraméternek adunk értéket. class Test { public int Add(int x.2 Nevesített paraméterek A C# 4.0 az alapértelmezett paraméterek mellett be vezeti a nevesített paraméter (named parameter) fogalmát. a metódusnak mindenképpen tartalmaznia kell egy return utasítást a megfelelő típusú elemmel (ez .Add(10. az Int. amely összead két számot. Personp2=newPerson("István". } } A végeredményt a return utasítással adhatjuk vissza. Nézzük az előző fejezet Person osztályának konstruktorát: Person p = new Person(firstName:"István". amelyek nem kapcsolódnak közvetlenül egy osztályhoz."Reiter". int y) { return x + y. amely segítségével explicit megadhatjuk."börtönõr"). az eredményt pedig visszatérési értékként kapjuk meg: using System.2 Visszatérési érték Az objektumainkon nem csak műveleteket végzünk.1. } } class Program { static public void Main() { Test t = new Test(). 14.Parse függvény ilyen).110 - . int result = t. firstName:"István"). 14. de hasznosak lehetnek (pl. 11). Készítsünk egy egyszerű függvényt. ezért nem kell betartanunk az eredeti metódus-deklarációban előírt sorrendet: Person p = new Person(lastName:"Reiter". de szeretnénk lekérdezni az állapotukat is és felhasználni ezeket az értékeket.

hogy azt közvetlenül módosítanánk. Egészítsük ki a string típust egy metódussal. vagy a kettő között léteznie kell implicit típuskonverziónak: public int Add(int x.). anélkül. ciklusfeltétel. vagy származtatnánk belőle. } 14.0 lehetőséget ad arra. int y) { return (long)(x + y). ha egy külső program/scipt hívta meg a programunkat és kíváncsiak vagyunk. A visszatérített érték típusának vagy egyeznie kell a visszatérési érték típusával.3 Kiterjesztett metódusok A C# 3. ami kiírja a képernyőre az adott karaktersorozatot: .// ez le sem fordul } Visszatérési értékkel rendelkező metódust használhatunk minden olyan helyen. int y) { if(x != 0 && y != 0) { return x + y. ahol a program valamilyen típust vár (értékadás. int y) { return (byte)(x + y). Ezt a gyakorlatban akkor tudjuk használni.és nullable típusok esetében) és ennek az utasításnak mindenképpen le kell futnia: public int Add(int x. hogy legyen (erről a következő fejezetben). hog y a program futása sikeres volt–e (1) vagy sem (0). // ez működik. mivel nem lesz minden körülmények között visszatérési érték. } } Ez a metódus nem fordul le. bár nincs sok értelme } public int Add(int x. hogy egy már létező típushoz új metódusokat adjunk.111 - . metódus paraméterei. hogy sikeres volt–e a futása. stb. A Main esetében a visszatérési érték azt jelöli. static public int Main() { return 0. Egy kiterjesztett metódus (extension method) minden esetben egy statikus osztály statikus metódusa kell. logikai kifejezések.lehet null is referencia. Valójában a Main metódus két formában létezik: visszatérési értékkel és a nélkül.

akkor a hagyományos. } } class Program { static public void Main() { string s = "ezegystring".Print(s). Ha két kiterjesztett metódus ugyanazzal a szignatúrával rendelkezik. statikus úton kell hívnunk őket. A fenti példában látható.WriteLine(s).using System. s.112 - .Print(). akkor a speciálisabb (szűkebb típusú) paraméterű metódus fog meghívódni. Kiterjesztett metódust nem definiálhatunk beágyazott osztályban. amely meghatározza a kiterjesztett osztály típusát. // így is használhatjuk } } A this módosító után a paraméter típusa következik. static public class StringHelper { static public void Print(this string s) { Console. StringHelper. . hogy „rendes” statikus metódusként is használható egy extension method. Ha nem így teszünk.

} } } class Program { static public void Main() { Person p = new Person("István").name = value. hogy a setter–ben egy ismeretlen.113 - . }//ez viszont jó } . azt az értéket tartalmazza amelyet hozzárendeltünk a setter-hez: Person p = new Person("István"). } } Láthatjuk. de a getternek minden esetben publikusnak kell lennie: public string Name { private get { return this.name._name. getter és setter blokkal. public string Name { get { return this. de valójában ezek speciális metódusok. hogy nincs paraméterlista. Ez egy speciális elem. Vegyük észre. mint a hagyományos változók.WriteLine(p. // value == "Béla" A getter és setter elérhetőségének nem muszáj megegyeznie. } set { this. class Person { public Person(string name) { this.name = name. value nevű változót használtunk. A tulajdonságok kívülről nézve pontosan ugyanolyanok. hogy megsértenénk az egységbezárás elvét. p.name = value. előbbi a property mögött lévő mező értékét adja vissza. Console.name = value. } // ez nem működik set { this. Minden tulajdonság rendelkezhet ún. } } public string Name { get { return this.Name). hogy egy property deklaráció hasonlóan épül fel mint a metódusoké. } string name. utóbbi pedig értéket ad neki: using System.name.15 Tulajdonságok A tulajdonságokat (property) a mezők közvetlen módosítására használjuk. anélkül.Name = "Béla". azzal a kivétellel. } private set { this.

} } Egyik esetben sem vagyunk rákényszerítve. Van azonban egy probléma. a fordító mindkettőt legenerálja nekünk: public string Name { get. ekkor csak írható/olvasható tulajdonságokról beszélünk: public string Name { get { return this. } } . } } A C# 3. } A fordító automatikusan létrehoz egy private elérésű. " + this. méghozzá az.0 rendelkezik egy nagyon érdekes újítással._name. a konstruktorban.114 - .Arra is van lehetőség. sem a teljes tulajdonságot. } public string Name { get.Name = name. az ún._name. automatikus tulajdonságokkal. set. Ilyenkor a setter-en keresztül kell értéket adnunk. string típusú „name” nevű adattagot és elkészíti hozzá a getter-t/setter-t is. tetszés szerint végezhetünk műveleteket is rajtuk: public string Name { get { return "Mr. vagyis közvetlenül nem hivatkozhatunk rá pl. hogy csak az egyiket használjuk. Nem kell létrehoznunk sem az adattagot. set. hogy azonnal visszaadjuk/beolvassuk az adattag értékét. class Person { public Person(string name) { this. hogy a fordítás pillanatában ez a változó még nem létezik.

using System. for(int i = 0.WriteLine(n[i]). azzal a különbséggel.Add("István"). nameList. Egy indexelőt így implementálhatunk: using System. hanem egy indexxel férünk hozzá az adott információhoz. amikor az osztály/struktúra tartalmaz egy tömböt vagy valamilyen gyűjteményt (vagy olyan objektumot.115 - . nameList. class Names { private ArrayList nameList. nameList. amely maga is megvalósít egy indexelőt). } } } .Add("Judit").++i) { Console.i < n.Count. public Names() { nameList = new ArrayList(). nameList.Count. Általában olyan esetekben használják. } } public string this[int idx] { get { if(idx >= 0 && idx <n ameList.Collections. } public int Count { get { return nameList.Add("Eszter").Add("Béla").16 Indexelők Az indexelők hasonlóak a tulajdonságokhoz.Count) { return nameList[idx].ToString(). hogy nem névvel. } return null. } } } class Program { static public void Main() { Names n = new Names().

amin az indexelőt definiáltuk.Ez gyakorlatilag egy „névtelen tulajdonság”. a this mutat az aktuális objektumra. amelyek különböző típusú indexxel vagy visszatérési értékkel rendelkezhetnek.116 - . Több indexet is megadhatunk. .

AnimalCounter.AnimalCounter). } } class Program { static public void Main() { Animal a = new Animal(). amelyekből osztályszinten összesen egy darab létezik. ugyanis nem definiálhatunk globális (mindenki számára egyformán elérhető) tagokat. ha meg szeretnénk számolni. A statikus tagok – ha az osztálynak nincs statikus konstruktora – rögtön a program elején inicializálódnak.117 - .. Gyakran van azonban szükségünk objektumtól független mezőkre/metódusokra. de ez nem ajánlott. Statikus tagokat akkor is használhatunk. Ezt (is) váltják ki a statikus adattagok és metódusok. mivel rontja az olvashatóságot). statikus tagok. } } A példában a statikus adattag értékét minden alkalommal megnöveljük eggyel. Az olyan osztályok statikus tagjai. amelyek rendelkeznek . pl.WriteLine(Animal.17 Statikus tagok A hagyományos adattagok és metódusok objektumszinten léteznek. hogy hány objektumot hoztunk létre. azaz minden objektum minden adattagjából saját példánnyal rendelkezik. A statikus tagokhoz az osztály nevén (és nem egy példányán) keresztül férünk hozzá (a statikus tag „gazdaosztályából” az osztály neve nélkül is hivatkozhatunk rájuk. public Animal() { ++Animal. 17. Console. Erre a célra szolgálnak az ún. class Animal { static public int AnimalCounter = 0. amikor meghívjuk a konstruktort és csökkentük. } ~Animal() { --Animal. vagyis az aktív példányok számát tartjuk számon vele.1 Statikus adattag Statikus tagot a static kulcsszó segítségével hozhatunk létre: using System.AnimalCounter. A statikus tagok jelentősége a C# nyelv tisztán objektum orientált mivoltában rejlik. amikor az objektum elpusztul. ha az osztályból nem készült példány.

Var = 10 Statikus konstruktor Konstruktor . Konvenció szerint minden statikus tag (adattagok is) neve nagybetűvel kezdődik. A statikus és láthatósági módosító megadásának sorrendje mindegy.").WriteLine("Konstruktor"). hogy egy példány keletkezik az adott osztályból vagy hozzáfértek valamely tagjához. 17.118 - .. } static Test() { Console. a következő kimenetet kapjuk: Start. return 10. } } class Program { static public void Main() { Console. Test t = new Test(). using System.. static public int Init() { Console. } } Ha elindítjuk a programot.WriteLine("Start. illetve nincsenek paraméterei sem. A statikus konstruktor közvetlenül azelőtt fut le.2 Statikus konstruktor A statikus konstruktor a statikus tagok beállításáért felel. } public Test() { Console.statikus konstruktorral. Nem férhet hozzá példánytagokhoz sem. class Test { static public int Var = Test.Init().WriteLine("Statikus konstruktor").. az inicializálást elhalasztják addig a pontig. A statikus konstruktornak nem lehet láthatóságot adni.. amikor először használjuk az adott osztály egy példányát.WriteLine("Var = 10").

Mindkétszer tízmillió alkalommal kértük el a statikus tag értékét. mégis óriási teljesítménykülönbség van köztük: class Program { static public void Main() { Stopwatch sw = Stopwatch. lássuk az eredményt: Eltelt idő: 12ms Eltelt idő: 88ms A különbség elképesztően nagy.ElapsedMilliseconds).A statikus konstruktoroknak van azonban egy hatalmas hátulütőjük. amelyet a következő példában láthatunk: static class A1 { static public int x = 10. sw. hogy meghívódott-e már a statikus konstruktor.++i) { int x = A1.i < 10000000. } static class A2 { static public int x.x.StartNew(). } } A két osztály látszólag ugyanazt teszi. static A2() { x = 10.WriteLine("Eltelt idõ: {0}ms". ez pedig a fenti teljesítményveszteséget eredményezi.Diagnostics névtérben van és – ahogyan látszik is – időt tudunk mérni vele. amikor statikus taghoz próbálunk hozzáférni. ellenőrzi. for(int i = 0. az ok pedig a következő: ha definiáltunk statikus konstruktort akkor a rendszer minden egyes alkalommal. for(int i = 0.StartNew(). } Console. } } A Stopwatch osztály a System.i < 10000000.x.ElapsedMilliseconds).119 - .WriteLine("Eltelt idõ: {0}ms". sw. } Console. sw = Stopwatch.++i) { int x = A2. .

mindössze a static kulcsszóra van szükségünk. Statikus metódust általában akkor használunk.120 - .3 Statikus metódus Statikus metódust a hagyományos metódusokhoz hasonlóan készítünk. A statikus konstruktortól eltérően rá nem vonatkozik.14.14. static class MathHelper { static public double PI { get { return 3. nem lehet példány-konstruktora (de statikus igen) és mindig lezárt (ld. A fordító minden esetben ellenőrzi ezeknek a feltételeknek a teljesülését. ha egy példány referenciáját adjuk át neki). ha csak és kizárólag statikus tagjai vannak.4 Statikus tulajdonság A statikus tulajdonságok a C# egy viszonylag ritkán használt lehetősége. 17.5 Statikus osztály Egy osztályt statikusnak jelölhetünk. legalábbis direkt módon nem (az minden további nélkül működik. ha nem egy példány állapotának a megváltoztatása a cél. Statikus metódusok nem férnek hozzá az osztály „normális” tagjaihoz. Példa: using System. hanem egy osztályhoz kapcsolódó művelet elvégzése. Öröklődés). A „leghíresebb” statikus metódus a Main. Általában osztályokhoz kapcsolódó konstans értékek lekérdezésére használjuk (lényegében a statikus metódusok egy olvashatóbb verziója). hogy nem lehetnek paraméterei. Egy statikus osztályból nem hozható létre példány. Ilyen metódus például az Int32 osztályhoz tartozó Parse statikus metódus is.17. Példa: class Math { static public double PI { get { return 3. Ilyen metódus volt az előző példában az Init metódus is. } } static public double Cos(double x) . } } } 17.

Cos(x).{ return Math.Cos(1)). } } .WriteLine(MathHelper.121 - . } } class Program { static public void Main() { Console.

Console. hogy referenciatípusként viselkedjenek (lásd: Boxing). amely elvégzi a tagok nullára inicializálását (lényegében nullákkal tölti fel az adott memóriaterületet). // x == 0 } } Nem kötelező használni a new operátort. Épp ezért struktúrát általában akkor használunk. ha egyszerű adatokkal kell dolgoznunk.x). ellenkező esetben a program nem fordul le: using System.1 Konstruktor Minden struktúra alapértelmezetten rendelkezik egy konstruktor-szerűséggel (vigyázat. hanem értéktípusok. akkor a struktúra tagjainak használata előtt definiálni kell az értéküket. Ez a lehetőség mindig él. de nincs szükségünk egy osztály mi nden szolgáltatására. Ez egy speciális típus.WriteLine(t. using System.122 - . de ha így teszünk. // nem jó.ValueType osztályból származik. } class Program { static public void Main() { Test t.WriteLine(t. } class Program { static public void Main() { Test t = new Test(). viszont azoktól eltérően nem referencia-. struct Test { public int x.18 Struktúrák A struktúrák – szerkezetüket tekintve – hasonlóak az osztályokhoz. amely lehetőséget biztosít értéktípusok számára. míg az osztályok „csak” referenciákat tárolnak. nem rejthető el. Minden struktúra indirekt módon a System.x). 18. x inicializálatlan } } . Console. struct Test { public int x. A struktúrák közvetlenül tartalmazzák a saját értékeiket. nem igazi konstruktor).

123 - . int y) { _y = y. Viszont ha ezt megtettük. Egy struktúra mezőit nem inicializálhatjuk: structTest { int _x = 10. hogy megértsük.2 Destruktor Struktúrák nem rendelkezhetnek destruktorral. ha a struktúra példány egy referenciatípusban foglal helyet. Ahhoz. int _y. szükségünk van a következőre: egy struktúrában lévő referenciatípusnak csak a referenciáját tároljuk. _x = x. Test t2 = new Test(). de ekkor minden mező gondoskodnunk kell. . akkor előbb vagy utóbb kikerül onnan. és mivel így a benne lévő referenciatípusra már nem mutat referencia (legalábbis a struktúrából nem) ezért eltakarítható. Ugyanez a történet akkor is. hogy miért nincs destruktor.// ez sem jó. praméter nélküli alapértelmezettet nem. x nem kap értéket } } értékadásáról Struktúrának csakis paraméteres konstruktort definiálhatunk. // ez nem jó int _y. public Test(int x. Ha a veremben van a struktúra.Készíthetünk saját konstruktort. int y) { _y = y. attól az alapértelmezett konstruktor még használható marad: using System. struct Test { int _x. //ez is működik } } 18. } } class Program { static public void Main() { Test t1 = new Test(10. Egy struktúra két helyen lehet a memóriában: a stack-ben és a heap-ben (ha egy referencia-típus tagja). 11). public Test(int x.

t1.x = {0}. } Mindhárom struktúra hibás.x.x). } class Program { static public void Main() { Test t1 = new Test(). Ugyanígy.WriteLine("t1. struct Test { public int x. } struct Test1 { Test2 t.4 Hozzárendelés Amikor egy struktúra példánynak egy másik példányt adunk értékül akkor egy teljesen új objektum keletkezik: using System. Test t2 = t1. valamint mivel a struktúrák nem vehetnek fel null értéket a fenti szerkezetek mind végtelen hurkot (és végtelen memóriafoglalást) okoznának (Test1 struktúra amiben Test2 amiben Test1 és így tovább) (lásd: tranzitív lezárt). Egy struktúra minden adattagja – amennyiben a konstruktorban nem adunk meg mást – automatikusan a megfelelő nulla értékre inicializálódik. t2. egy struktúra nem tartalmazhat olyan típ usú tagot. Console. } } . amely típus hivatkozik az eredeti struktúrára: struct Test { Test t. Struktúra nem tartalmazhat saját magával megegyező típusú adattagot.x = 10. t2.124 - . 18.18.3 Adattagok A struktúrák az adattagokat közvetlenül tárolják (míg osztályok esetében mindig referenciákat tartunk számon). Az ok nagyon egyszerű: mivel a struktúrák direkt módon – nem referenciákon keresztül – tárolják az adattagjaikat. t2.x = {1}". } struct Test2 { Test1 t.

public int Y { get { return_y. Egészítsük ki a kódot: . } } Teljesen szabályos forrás. } set { _x = value. Most nézzünk meg egy nagyon gyakori hibát.B = new Point(20. Adott a következő programkód: using System. } } } class Program { static public void Main() { Line l = new Line(). l. vagyis jogosnak tűnik.x értéke nem változott. l. hogy a Line struktúrán keresztül módosítani tudjuk a koordinátákat. } set { _y = value. Látható. 10). le is fordul. } } int _y. _y = y. } set { a = value. public Point B { get { return b. public int X { get { return_x. } } struct Line { Point a. public Point A { get { return a. hogy t1. tehát nem referenciát adtunk át t2–nek. } set { b = value.A = new Point(10. azt fogjuk látni. struct Point { int _x. } } Point b. 20).125 - . amibe belefuthatunk.Ha lefordítjuk ezt a kódot. } } public Point(int x. int y) { _x = x. hogy a Point struktúra publikus tulajdonságokkal bír.

10).A = new Point(10. amelynek a tagjait módosítani viszont nincs értelme. Ez utóbbi esetben a metódushívások nem járnak bedobozolással: using System.B = new Point(20. l. Az l.x = 10. l.ToString().ValueType) metódusait definiálhatja át. public override string ToString() { return "X == " + x.126 - . mivel nem változónak akarunk értéket adni. metódusai nem lehetnek virtuálisak illetve csak a System. Mi lehet a hiba oka? A probléma ott van.A valójában a getter-t hívja meg. l. mivel a . l. hogy rosszul értelmeztük ezt a kifejezést.NET beépített Point típusa szintén struktúra.ToString()). class Test { public int x. 10).WriteLine(t. 20). 10). 18. Ilyen módon egy struktúra nem lehet absztrakt.A = new Point(10.static public void Main() { Line l = new Line(). l. 20). tagjainak elérhetősége nem lehet protected/protected internal. t. } Ez a hiba viszonylag gyakran fordul elő grafikus felületű alkalmazások készítése közben. minden struktúra automatikusan sealed módosítót kap. ami az eredeti struktúra egy másolatával tér vissza. } Ez a forráskód nem fog lefordulni.B = new Point(20. l.5 Öröklődés Struktúrák számára az öröklődés tiltott. Ilyen esetekben mindig új struktúrát kell készítenünk: static public void Main() { Line l = new Line(). Console.A = new Point(5. } } .X = 5.A. } } class Program { static public void Main() { Test t = new Test().

Vegyük pl.WriteLine(result).WriteLine(result). Rengeteg olyan probléma van. ekkor befejezzük az „ördögi” kört és visszaadjuk az eredményt.cs) Mi is az a rekurzív függvény? Egy függvény.1). 19. amely önmagát hívja. mint meghatározott számú szorzást végzünk.127 - . Hasonlóképpen készíthetjük el a faktoriálist számoló programot is: using System. egészen addig.1). méghozzá ugyanazzal a számmal. Console. 10). lényegében azonos feladatot végrehajtó részre lehet osztani. int y) { if(y == 0){ return 1.cs és 19/RecPow. class Program { static public double Pow(double x. hogy x–et (az alapot) érintetlenül hagyjuk. } else return x * Fact(x . class Program { static public int Fact(int x) { if(x == 0){ return 1. Nézzük meg a hatványozás rekurzív megfelelőjét: using System.19 Gyakorló feladatok III. míg a kitevőt (y) a függvény minden hívásakor eggyel csökkentjük. } } . } static public void Main() { double result = Pow(2. amelyeket több. } else return x * Pow(x.1 Faktoriális és hatvány Készítsünk rekurzív faktoriális és hatványt számító függvényeket! Megoldás (19/RecFact. // 1024 } } Látható. } static public void Main() { int result = Fact(10). Írhatunk persze egy egyszerű ciklust is. de ez egy kicsit atombombával egérre típusú megoldás lenne.0. a hatványozást: semmi mást nem teszünk. amíg értéke nulla nem lesz. y . Console.

} } Készítettünk egy osztályt.Length . amely reprezentálja a rendezendő tömböt.cs) A gyorsrendezés a leggyorsabb rendező algoritmus. class Array { private int[] array. amely elé a nála nagyobb. } private void QuickSort(int left. } } public int Length { get { return array... A metódus implementációja a következőképpen néz ki: . array. } public int this[int idx] { get { return array[idx]. public Array(int length) { array = new int[length]. hogy hogyan is néz ez ki a gyakorlatban! Nagy elemszámnál ugyan jól teljesít ez az algoritmus.2 Gyorsrendezés Valósítsuk meg a gyorsrendezést! Megoldás (19/QuickSort.Length. nagy elemszám esetén O(n*logn) nagyságrendű átlaggal. A Sort metódussal fogjuk meghívni a tényleges rendező metódust amely két paramétert kap. majd az így kapott két csoportra ismét meghívja a gyorsrendezést (tehát egy rekurzív algoritmusról beszélünk).19. pl.128 - . hogy a rendezendő elemek közül kiválaszt egy ún. } } public void Sort() { QuickSort(0. A legtöbb programozási nyelv „beépített” rendezései általában a gyorsrendezés egy variációját használják. Éppen ezért amikor a rendezésre átadott tömbrészlet kellően kicsi akkor egy általánosabb rendezést. Lássuk.1). Az algoritmus lényege. mögé pedig a nála kisebb elemeket teszi. a rendezésre kiválasztott tömbrészlet alsó és felső indexét. pivot elemet. } set { array[idx] = value. int right) { // rendezés. buborékrendezést érdemes használni. de kevés elem esetén fordul a kocka.

pivot = left. ugyanúgy ahogyan a második ciklusba sem. és nullától indexelünk).private void QuickSort(int left. } if(left != right) { array[right] = array[left]. 6. hogy right értéke 0 lesz. 4.1). Nézzük meg az algoritmus működését egy példán keresztül! A rendezendő számsorozat legyen: 3. int rhold = right. A következő . } } array[left] = pivot. ++left. right). ezeket az értékeket eltároljuk. } while(array[left] <= pivot && left < right) { ++left. int lhold = left. int right) { int pivot = array[left]. } } A tömb mindkét oldaláról behatároljuk a kisebb/nagyobb elemeket majd továbbhívjuk a rendezést. de a metódus végén szükség van az eredetiekre. if(left < pivot) { QuickSort(left. right = rhold. Az első ciklus – mivel nem fog a left indexen lévő számnál (3) kissebbet találni – úgy végződik. Az elágazásba – mivel right és left egyenlő – nem megyünk bele. } if(left != right) { array[left] = array[right]. mivel a hármasnál kisebb elem nincs a nála nagyobbak pedig utána helyezkednek el. mivel az értékük módosulni fog. --right. pivot . 11 Left és right 0 és 5 (ugye hat elem van a tömbben. 9. while(left < right) { while(array[right] >= pivot && left < right) { --right. left = lhold. } if(right > pivot) { QuickSort(pivot + 1.129 - . 8.

hogy miként hívjuk meg újra a metódust. Az első ciklus egyszer fog lefutni hiszen a tömb negyedik indexén ülő nyolcas szám már kissebb mint a pivot elem. míg left és right értéke 1 és 5. } public NodePrevious { get. amíg eléri a right által mutatott nyolcast. ha a megelőző elemre is. Left előrelép eggyel. A láncolt lista olyan adatszerkezet. hogy right értéke ismét 5) és meghívjuk a QuickSort metódust 1 illetve 5 paraméterekkel. egészen addig fogjuk növelni left értékét. hogy a tömb második indexére (4) mutasson. hiszen ott a ciklus feltétel második fele sérül.vagy gyökérelemen keresztűl érjük el. akkor egyszeresen-.elágazás szintén kimarad és nekiállhatunk kiszámolni. Ha az elemek csak a következő tagra mutatnak. Ezután a második elágazást fogjuk használni (ne feledjük.Value = value. akkor kétszeresen láncolt listáról beszélünk: vagy Elsőként valósítsuk meg az elemeket jelképező osztályt: class Node { public Node(int value) { this. a pivot változó nulla értéket kap. Left kissebb most. A tömb változatlan marad.cs) Amikor tömbökkel dolgozunk akkor a tömb elemeit indexekkel érjük el. mint a pivot és right pedig nagyobb nála így mindkét elágazás feltétele teljesül. vagyis most mindkét oldalra hívjuk a metódust. set.3 Láncolt lista Valósítsuk meg a láncolt lista adatszerkezetet! Megoldás (19/LinkedList. amivel helyre is áll a rend. A második ciklus is lefut. right és left pedig visszakapják az eredeti értéküket. Az ez utáni események átgondolása pedig az olvasó feladata. } } . } public Node Next{ get.130 - . amelynek elemei a soron következő elemre hivatkozó referenciát tartalmaznak. vagyis az első elemet (3) – mivel ő már a helyén van – átugorjuk. Éppen ezért a második elágazást ki hagyjuk és tovább lépünk. set. a pivot változó értéke most kilenc lesz. Most left és right értéke egyenlő: 4. set. Left nem egyenlő right –tal ezért a következő elágazásba is bemegyünk és a left indexre helyezzük a right indexen lévő nyolcast (a pivot elem pedig pont az itt „már nem” lévő kilencest tárolja). Következik a második forduló. A left által mutatott indexre behelyezzük a pivotban lévő kilencest. 19. } public int Value{ get. A láncolt listát az első fej. Pivot értéke ezután négy lesz és a két másik változó is visszakapja az értékét.

se rákövetkező elem). amelynek elemei nulla vagy több gyermekelemmel és maximum egy szülőelemmel rendelkeznek: . } } public NodeRoot { get. amelyik számunkra érdekes.Next.Most pedig jöjjön a láncolt lista osztály: class LinkedList { public LinkedList(){ } public LinkedList(int[] values) { foreach(int value in values) { this. Ahhoz.131 - .cs) A fa típus olyan adatszerkezet.Next != null) { current = current. Elsőként megvizsgáljuk. ekkor meg kell keresnünk a legutolsó elemet és utána fűzni az újat (megvalósíthattuk volna úgy is a listát. hogy létezik-e gyökérelem. hogy megkeressük az utolsó elemet szükségünk lesz egy átmeneti referenciára. while(current.Add(value). Más a helyzet. 19. } } public void Add(int value) { if(Root == null) { Root = new Node(value). amely mindig az aktuális elemet mutatja majd.Previous = current. ekkor beállítjuk a Next és Previous értékeket is. current. } } Az Add metódus az. hogy tárolunk egy referenciát az utolsó elemre. private set. ez lényegesen gyorsabb lenne – de kevésbé érdekes). ha nem akkor létrehozzuk és nincs is más dolgunk (ugye ilyenkor még nincs se előző. } current. ameddig az aktuális elem rákövetkezője null értékre nem mutat. ha van már néhány elem a listában.4 Bináris keresőfa Készítsünk bináris keresőfát! Megoldás (19/BinaryTree.Next. } else { Node current = Root.Next = new Node(value). A ciklust addig kell futtatni.

amely minden elemének pontosan egy szülő és maximum kettő gyermek eleme lehet. itt is szükségünk van egy gyökérelemre. . ezáltal egyértelműen meghatározható. } } Az osztály váza hasonlít a láncolt listáéhoz. } public int Value { get. set. A fa típus egy speciális esete a bináris fa. A bináris fa speciális esete pedig a bináris keresőfa. vagyis ugyanaz az elem kétszer nem szerepelhet a fában. A bináris keresőfa minden elemének egyedi kulcssal kell rendelkeznie. set. hogy egy elem benne van-e a fában vagy nincs (értelemszerűen a részfák is keresőfák. } public TreeNode Right { get. hogy egy szülő elem bal oldali részfájában a szülőelemnél kisebb jobboldali részfájában pedig a szülőelemné l nagyobb elemek vannak.Insert(value). } public TreeNode Left { get.A B C A képen az A elem gyermekei B illetve C akiknek természetesen A lesz a közös szülőelemük. } public TreeNode Parent { get. amelynek jellemzője. most is készítsük el a fa csúcsait jelképező osztályt: class TreeNode { public TreeNode(int value) { this. set.132 - . } } Most pedig készítsük el a fa osztályt! class BinaryTree { public BinaryTree() { } public BinaryTree(int[] values) { foreach(int value in values) { this. } } public void Insert(int value) { } public TreeNode Root { get. private set.Value = value. Ahogyan az előző feladatban. vag yis rájuk is ugyanez vonatkozik). A keresés művelet O(logn) nagyságrendű. ez tulajdonképpen a legelső beszúrt csúcs lesz. set.

Írjuk meg a hiányzó Insert metódust: public void Insert(int value) { if(Root == null) { Root = new TreeNode(value).Right == null) { current. 12 Ekkor a bináris keresőfa így fog kinézni: .Value > value) { if(current.Left = new TreeNode(value). while(current != null) { if(current.Right.Right = new TreeNode(value).Left == null) { current.Parent = current. egyébként megkeressük az új elem helyét oly módon. Tegyük fel. Amennyiben az adott érték már szerepel a fában egyszerűen elhagyjuk a ciklust. 3. 4. } else { TreeNode current = Root.Parent = current. hogy az elemek a következő sorrendben érkeznek: 10. akkor készítünk egy új TreeNode objektumot. } else { current = current. current. } else { current = current.Value < value) { if(current. 6. 9. current.Left. return.Left. return.Right. 6. } } else return. } } } Ha a gyökérelem null értéken áll. hogy minden csúcsnál a megfelelő irányba „fordulunk”. } } else if(current.133 - . 1.

Visszalépünk a szülőelemre és kiírjuk őt (4) majd lépünk jobbra. 9. Egy rekurzív algoritmust fogunk használni. Az algoritmust a gyökérelemre (10) fogjuk meghívni. 3. hiszen megfelelő algoritmusra lesz szükségünk ahhoz. ezért kiírjuk. Mivel neki nincsen bal oldali részfája.10 1 12 4 3 6 9 A következő feladatunk. ezért a következő szám. Inorder módon a bal oldali részfa. majd jön a jobb fában a kilences amit megint csak kiírunk hiszen nem rendelkezik gyermekelemmel. Háromféle stratégiát ismerünk: preorder.134 - . A preorder elsőként a csúcsot majd a bal és jobb oldali részfát veszi kezelésbe. Nézzük meg. Ezen a ponton végeztünk a gyökérelem bal oldali fájával ezért őt jelenítjük meg. 4. 6. ezért kiírjuk az egyes számot és lépünk a jobb oldali részfára (4). a jobb oldali részfa végül pedig a csúcs. hogy kiírjuk a fa elemeit a konzolra. a csúcs és a jobb oldali részfa lesz a sorrend végül pedig a postorder bejárás sorrendje a bal oldali részfa. Persze ez nem is olyan egyszerű. A hatos csúcsnak sincs bal oldali fája. 12 A forráskódban ez az algoritmus meglehetősen egyszerűen jelenik meg. amely elsőként a bal oldali részfa csúcsát (1) fogja meglátogatni. A végső sorrend tehát: 1. amely stratégiától függően az egyes csúcsok részfáit majd magát a csúcsot látogatja meg. Neki már van bal oldali ága ezért őt vizsgáljuk a továbbiakban. 10. amit kiírhatunk a három. inorder és postorder. Itt nincs gyermekelem. következzen az inorder bejárás: . ezután pedig már csak egyetlen elem marad. hogy a fa elemeit bejárhassuk. hogyan is működik mindez a gyakorlatban! A fent felépített fán fogunk inorder módon végigmenni.

action). action). _inOrder(root.135 - . } Az Action<T> osztályról a Lambda kifejezések c.Right. Action<int> action) { if(root == null) { return.public void InOrder(Action<int> action) { _inOrder(Root. } _inOrder(root. action).Value). } private void _inOrder(TreeNode root. fejezetben olvashat többet az olvasó. action(root. .Left.

hogy paraméteres konstruktort készítettünk az ősosztálynak.Object –et). } } Vegyük észre.Hamm").136 - . Az első változatot (az alapértelmezett konstruktorral) így használhattuk: static public void Main() { Dog d = new Dog(). Az egyszerűség kedvéért hagyjuk ki az Állat és Kutya közti speciálisabb osztályokat: class Animal { } class Dog : Animal { } class Crocodile : Animal { } Az ősosztályt az osztálydeklaráció után írt kettőspont mögé kell tenni. } public void Eat() { Console. } . } public string Name { get. A C# csakis egyszeres öröklődést engedélyez (vagyis minden osztály egyetlen ősosztályból származhat.WriteLine("Hamm . ugyanakkor megengedi több interfész impementálását (interfészekről hamarosan). vagyis át kell gondolnunk a példányosítást. leszámítva a System. szintén itt lesznek majd az osztály által megvalósított interfészek is. set. Crocodile c = new Crocodile().20 Öröklődés Öröklődéssel egy már létező típust terjeszthetünk ki vagy bővíthetjük tetszőleges szolgáltatással. Készítsük el az elméleti rész példáját (Állat-Kutya-Krokodil) C# nyelven.Name = name. Bővítsük hát ki: class Animal { public Animal(string name) { this. A Kutya és Krokodil osztályok egyaránt megvalósítják az ősosztály (egyelőre szegényes) funkcionalitását.

Crocodile c = new Crocodile("Aladár"). Console.Eat(). set. } public string Name { get.137 - .Hamm"). ezért a leszármazott osztályokban explicit módon hívni kell a megfelelő konstruktort: class Animal { public Animal(string name) { this. d.Name).Name = name. hogy az ősosztálynak már nincs ilyenje.WriteLine("{0} és {1}". hogy ki tudjuk javítani a hibát tudnunk kell. mivel nem fordul le a program.Name. } public void Eat() { Console.WriteLine("Hamm .Eat(). vagyis – ha nem adunk meg mást – az alapértelmezett konstruktort. d. hogy a leszármazott osztályok először mindig a közvetlen ősosztály konstruktorát hívják meg. A probléma az. } . Crocodile c = new Crocodile("Aladár"). Ahhoz. } } class Dog : Animal { public Dog(string name) : base(name) { } } class Crocodile : Animal { public Crocodile(string name) : base(name) { } } Ezután így példányosítunk: static public void Main() { Dog d = new Dog("Bundás"). c.Ha ezt az új Animal osztállyal próbálnánk meg akkor meglepetés fog érni. } Ugyanígy használhatjuk az ősosztály metódusát is: static public void Main() { Dog d = new Dog("Bundás"). c.

c. metódus-táblával.WriteLine("Kro .1 Virtuális metódusok Az ősosztályban deklarált virtuális (vagy polimorfikus) metódusok viselkedését a leszármazottak átdefiniálhatják.WriteLine("Hamm . hogy egy ősosztálybeli metódust kell meghívnia? A referenciatípusok speciális módon jelennek meg a memóriában. Persze ezt is meg kell határozni valahogy.Eat(). Crocodile c = new Crocodile().Hamm .Eat(). ez nagy vonalakban úgy történik. ami mutatja. rendelkeznek többek közt egy ún. ezért az eggyel feljebbi őst kell megnéznünk.Hamm .WriteLine("Vau . } } .138 - . 20.Eat().Honnan tudja vajon a fordító. hogy a fordító a fordítás pillanatában megkapja a metódus nevét és elindul „visszafelé” az osztályhierarchia mentén. } } class Program { static public void Main() { Animal a = new Animal(). hogy az egyes metódushívásoknál melyik metódust kell meghívni. } } class Crocodile : Animal { public override void Eat() { Console. Ez egészen a lehető „legújabb” metódusdefinícióig megy. class Animal { public virtual void Eat() { Console. a. } } class Dog : Animal { public override void Eat() { Console. Dog d = new Dog().Hamm").Hamm"). A fenti példában a hívó osztály nem rendelkezik Eat nevű metódussal és nem is definiálja át annak a viselkedését (erről hamarosan).Vau . és amikor megtalálja a megfelelő implementációt. d. bejegyzi azt a metódustáblába.Kro . Virtuális metódust a szignatúra elé ír t virtual kulcsszó segítségével deklarálhatunk: using System.Hamm").

hogy szándékosan hoztunk létre az ősosztályéval azonos szignatúrájú metódust és a leszármazott osztályon ezt kívánjuk használni mostantól.KRO . így az ő leszármazottai is átdefiniálhatják a működését: class Crocodile : Animal { public override void Eat() { Console.Kro . vagyis a new virtual kulcsszavakkal ellátott metódus lesz az új metódussorozat gyökere. hogy szándékosan takarjuk el az eredeti implementációt. Ezt a jelenséget árnyékolásnak (shadow) nevezik.WriteLine("Kro .A leszármazott osztályokban az override kulcsszóval mondjuk meg a fordítónak. amelyet az utódai már kedvükre használhatnak.WriteLine("Vau . És valóban. Természetesen mi azt szeretnénk. hogy eltakarjuk az öröklött metódust. Viszont készíthetünk belőle virtuális metódust. Ezt a ne w kulcsszóval tehetjük meg: class Animal { public virtual void Eat() { Console. ha meghívnánk. Azaz. Ekkor a program ugyan lefordul.HAMM").HAMM .Hamm"). de a fordító figyelmezet minket. hogy a fordítás hiba nélkül menne végbe.Hamm .WriteLine("Hamm . Egy override–dal jelölt metódus automatikusan virtuális is lesz. } } Ezután a Dog utódjai már nem látják az eredeti Eat metódust. } } class BigEvilCrocodile : Crocodile { public override void Eat() { Console.Hamm . hogy már létezik). így tájékoztatnunk kell a fordítót. hogy nem ismerjük az ősosztály felületét és a hagyományos módon deklaráljuk az Eat metódust (ugye nem tudjuk.139 - . akkor az új metódus futna le.Hamm"). amikor a fordító felépíti a metódustáblát. a new módosítóval ellátott metódus új sort kezd. } } Az utódosztály metódusának szignatúrája és láthatósága meg kell egyezzen azzal amit át akarunk definiálni.Hamm").WriteLine("KRO . } } class Dog : Animal { public new void Eat() { Console. Tegyük fel. .Vau .

hogy ez visszafelé nem működik.WriteLine("Vau . Abban az esetben ugyanis. animalArray[1]. Ez az ún. hogy készítettünk egy Animal típusú elemekből álló tömböt és. hogy minden olyan helyen. hogy az elemein meghívtuk az Eat metódust. Szerencsére a C# nyelvben ezt megoldották. azaz az adott objektum elveszítené a speciálisabb osztályra jellemző karakterisztikáját. } Amit a fordító lát az az. hogy az ős és leszármazottak közt az-egy (is-a) reláció áll fent. animalArray[1] = new Dog(). Így a fordító el tudja dönteni a futásidejű típust. hogyan épül fel a metódustábla.Vau . Például gond nélkül írhatom a következőt: Animal d = new Dog("Bundás"). animalArray[0]. Ez a gyakorlatban azt jelenti. 20.Hamm . ráadásul van leszármazottbeli implementációja is. A kimenet így már nem lehet kétséges.Hamm"). késői kötés (late binding). amely átdefinálja az eredeti viselkedést. de ezt nem kell külön jelölni). a fordító hibát jelezne. A new operátor meghívása után d úgy fog viselkedni. } } Nem jelölhetünk virtuálisnak statikus. mivel ott egy pointeren keresztül megtehető a „lebutítás”. ha a fordító engedné a visszafelé konverziót az ún. egy állatkertben állatok vannak. Arra azonban figyeljünk. és most már azt is tudjuk. Már beszéltünk arról. Mi történik vajon a következő esetben: static public void Main() { Animal[] animalArray = new Animal[2]. absztrakt és override–dal jelölt tagokat (az utolsó kettő egyébként virtuális is lesz.2 Polimorfizmus Korábban már beszéltünk arról.Eat(). és ez által ütemezi a metódushívásokat. adattagjait.140 - . animalArray[0] = new Animal(). a fordító megkeresi a legkorábbi implementációt. mint a Dog osztály egy példánya (elvégre az is lesz).Eat(). ahol egy őstípust használunk. de az állatok helyére (nyílván) behelyettesíthetek egy speciálisabb fajt). hogy az első ilyen . ott használhatunk leszármazottat is (pl. Csakhogy az Eat egy virtuális metódus. így nem kell aggódnunk miatta.class Dog : Animal { public new virtual void Eat() { Console. és ezt explicit jelöltük is az override kulcsszóval. leszeletelődés (slicing) effektus lépne fel. használhatja annak metódusait. A C++ nyelvben sokszor jelent gondot ez a probléma.

azaz a keresés legkésőbb az első virtuális változatnál megáll.4 Absztrakt osztályok Egy absztrakt osztályt nem lehet példányosítani.implementáció egy virtuális metódus lesz. } } sealed class Dobermann : Dog { public override void Eat() // ez sem jó { } } 20. azaz megtilthatjuk.141 - .Hamm"). hogy közös felületet biztosítsunk a leszármazottainak: using System. } } .Vau . 20. ekkor a leszármazottak már nem definiálhatják át a működését: class Dog : Animal { public sealed override void Eat() { Console. } class Dog : Animal { public override void Eat() { Console.Hamm .Hamm"). abstract class Animal { abstract public void Eat().Hamm . A létrehozásának célja az. hogy új osztályt származtassunk belőle: sealed class Dobermann : Dog { } class MyDobermann : Dobermann // ez nem jó { } Egy metódust is deklarálhatunk lezártként.WriteLine("Vau .Vau .WriteLine("Vau .3 Lezárt osztályok és metódusok Egy osztályt lezárhatunk.

Hamm"). A fordító feladata az.Hamm . } } Vajon. hogy betartassa a rá vonatkozó szabályokat.Vau . } abstract public void Eat(). Egy absztrakt osztály csak a fordítás közben absztrakt.WriteLine("Vau . a lefordított kódban teljesen normális osztályként szerepel. mint a hagyományos nem-virtuális társaik. Ezek a szabályok a következők: absztrakt osztályt nem lehet példányosítani absztrakt metódusnak nem lehet definíciója a leszármazottaknak definiálnia kell az öröklött absztrakt metódusokat. ugyanakkor a metódus (látszólag) nem virtuális és nincs definíciója.Name = name. mégpedig azért. } class Dog : Animal { public Dog(string name) : base(name) { } public override void Eat() { Console.Eat(). set. Amennyiben egy osztálynak van legalább egy absztrakt metódusa az osztályt is absztraktként kell jelölni. //ez nem fordul le Dog d = new Dog(). Annak ellenére. ezek pont úgy viselkednek. } } Látható. Az öröklött absztrakt metódusokat az override kulcsszó segítségével tudjuk definiálni (hiszen virtuálisak.142 - . . animalArray[1] = new Crocodile("Aladár"). hogy beállíthassuk vele az adattagokat: abstract class Animal { public Animal(string name) { this. hogy mind az osztály.class Program { static public void Main() { // Animal a = new Animal(). mind a metódus absztraktként lett deklarálva. } public string Name { get. virtuális metódusokkal. még ha nem is látszik). hogyan működik a következő példában a polimorfizmus elve? : Animal[] animalArray = new Animal[2]. d. Absztrakt osztály tartalmazhat nem absztrakt metódusokat is. hogy egy absztrakt osztályt nem példányosíthatunk még lehet konstruktora. animalArray[0] = new Dog("Bundás").

Természetesen a következő esetben nem fordulna le: animalArray[0] = new Animal("Animal"). hogy mi van a new operátor jobb oldalán. hiszen ténylegesen egyszer sem példányosítottuk az absztrakt ősosztályt. az alaposztály nem érdekli.Ennek a kódnak hiba nélkül kell fordulnia. .143 - . A fordító csak azt fogja megvizsgálni.

mindannyiuk elérhetősége publikus. A tagoknak nincs külön megadott láthatóságuk. Ezen felül interfészt használhatunk struktúrák esetében is. hogy a metódusokhoz nem tartozik definíció. } . d. A következő példában átírjuk az Animal ősosztályt interfészre: using System. Egy másik előnye az interfészek használatának.WriteLine("Vau . A nagy különbség a kettő közt az. illetve jelölhetjük internal–ként. Magának az interfésznek az elérhetősége alapesetben publikus.Vau . tulajdonságok. amit meg kell valósítani. addig bármennyi interfészt megvalósíthat. ha a szóban forgó osztály egy absztrakt osztály. mindössze előír egy mintát. csak deklaráció. hogy meghatározzák egy osztály viselkedését. indexelők és események (erről hamarosan). egyetlen kivétellel. felületét. } } class Program { static public void Main() { Dog d = new Dog(). ekkor az interfész metódusait abszraktként jelölve elhalaszthatjuk a metódusdefiníciót az absztrakt osztály leszármazottainak implementálásáig: interface IAnimal { void Eat(). } } Az interfész nevét konvenció szerint nagy I betűvel kezdjük. Látható.Hamm"). interface IAnimal { void Eat(). egy interfész nem köthető közvetlenül egy osztályhoz.Eat(). Egy interfészt implementáló osztálynak meg kell valósítania az interfész metódusait. másféle láthatóságot nem adhatunk meg (illetve osztályon belül deklarált (beágyazott) interfész elérhetősége lehet privát). hogy míg előbbi eleve meghatároz egy osztályhierarchiát.21 Interfészek Az interfészek hasonlóak az absztrakt osztályokhoz. } abstract class AbstractAnimal : IAnimal { public abstract void Eat(). hogy míg egy osztálynak csak egy őse lehet.144 - . Egy interfész a következőket tartalmazhatja: metódusok. } class Dog : IAnimal { public void Eat() { Console. abban az értelemben.Hamm . A megvalósító osztály dolga lesz majd implementálni a tagjait.

d. } .Eat(). } class Dog : IDog { public void Eat() { Console.Fontos. } } class Program { static public void Main() { Dog d = new Dog(). id.WriteLine("Vau .145 - .Vau . d. utána jönnek az interfészek: class Base { } interface IFace { } class Derived : IFace. interface IAnimal { void Eat().Hamm").WriteLine("Vau . akkor a felsorolásnál az ősosztály nevét kell előrevenni.Vau(). ia = d.Eat(). = d. Egy adott interfészt megvalósító objektumot implicit módon átkonvertálhatjuk az interfész „típusára”: static public { Dog d = IAnimal IDog id void Main() new Dog().Vau"). } public void Vau() { Console. Base { } // ez nem fordul le Egy interfészt származtathatunk más interfészekből: using System.Hamm . } interface IDog : IAnimal { void Vau(). hogy amennyiben egy osztályból is származtatunk.Vau(). } } Ekkor természetesen az összes interfészt meg kell valósítanunk. ia.

Az is és as operátorokkal pedig azt is megtudhatjuk.Method(). hogy egy adott osztály megvalósít–e egy interfészt: static public void Main() { Dog d = new Dog(). } class Test : IOne. } } Ez a forráskód lefordul. melyik interfészhez tartozik. Írjuk át a fenti kódot: .. ITwo { public void Method() { Console.1 Explicit interfészimplementáció Ha több interfészt implementálunk.146 - .. } } 21. hogy két metódust kellene implementálnunk. } interface ITwo { void Method(). Nyílván nem ez az elvárt viselkedés. if(ia != null) { Console. Ennek kiküszöbölésére explicit módon megadhatjuk a megvalósítani kívánt funkciót: using System. IAnimal ia = d as IAnimal. és a metódust is meg tudjuk hívni. interface IOne { void Method(). a probléma ott van. } if(d is IDog) { Console. hogy melyik metódus/tulajdonság/etc.WriteLine("Az objektum megvalósítja az IDog -ot"). } } class Program { static public void Main() { Test t = new Test().WriteLine("Az objektum megvalósítja az IAnimal -t"). ezért ilyen esetekben explicit módon meg kell mondanunk. t.WriteLine("Method!"). az névütközéshez is vezethet. de csak egy van – viszont a program működik.

Wuff . ilyenkor az interfész láthatósága érvényes ezekre a tagokra.Vau . it.147 - .WriteLine("IOne Method!").Method() { Console.Vau . } public virtual void Vau() { Console.Hamm"). de az utódnál is jelöljük a megvalósítást: .WriteLine("Vau .Vau").WriteLine("Vau . } } Vegyük észre.Method().Method().Vau"). } void ITwo. // ez is mûködik } 21. // ez mûködik ITwo it = t.WriteLine("ITwo Method!").class Test : IOne.Hamm . méghozzá az. ((IOne)t).Method() { Console. hogy hogyan fogjuk meghívni a metódusokat? Most fogjuk kihasználni. Ezután az osztály leszármazottjai tetszés szerint módosíthatják a definíciót. a már ismert override kulcsszóval: class Dog : IDog.WriteLine("Wuff . de a megvalósításnál jelölhetjük őket virtuálisnak. IAnimal { public void Eat() { Console. } } class WuffDog : Dog { public override void Vau() { Console. hogy egy osztály konvertálható a megvalósított interfészek típusára: static public void Main() { Test t = new Test(). Újabb problémánk van. } } Egy leszármazott újraimplementálhatja az adott interfészt. hogy nem használtunk láthatósági módosítót. amennyiben nemcsak az ősnél. ITwo { void IOne.2 Virtuális tagok Egy interfész tagjai alapértelmezés szerint lezártak.

IAnimal { public new void Eat() { Console.WriteLine("Wuff .Wuff .Hamm .148 - .class WuffDog : Dog. } public override void Vau() { Console.Hamm"). } } Ez esetben használnunk kell a new kulcsszót annak jelölésére. hogy eltakarjuk az ős megvalósítását.WriteLine("Wuff . .Vau .Vau").Wuff .

vagyis egy adott operátort tetszés sze rinti funkcióval ruházhatunk fel az osztályunkra vonatkoztatva. visszatérési értékük pedig az eredmény. 20). ha az összeadás. 20). Vegyük pl. azt a példát. static public void Main() { Matrix m1 = new Matrix(20. Tehát most már nyugodtan írhatom a következőt: . nem pedig metódushívásokkal. } public int Value { get. } } A ‟+‟ operátort működését fogalmaztuk át. string.Add(m2). stb…). kivonás. ugyanis engedi az operátorok kiterjesztését (operator overloading). } A kiterjeszthető operátorok listája: +(unáris) -% >> >= -(unáris) + & == <= ! | != ~ * ^ > ++ / << < A C# nyelvben az operátorok valójában statikus metódusok. Jó lenne. hogy az általunk készített típusok hasonló funkcionalitással rendelkezzenek. paramétereik az operandusok. //de ez még jobb lenne m1 += m2. mint egy egész szám esetében. Egy egyszerű példa: class MyInt { public MyInt(int value) { this. Matrix m2 = new Matrix(20. mint a beépített típusok (int. MyInt rhs) { return new MyInt(lhs. szorzás. //ez is jó m1.149 - .22 Operátor kiterjesztés Nyílván szeretnénk.Value + rhs. private set. utalva a jobb és baloldali operandusra. műveleteket úgy tudnánk végrehajtani. amikor egy mátrix típust valósítunk meg. stb. A paraméterek (operandusok) nevei konvenció szerint lhs (left-hand-side) és rhs (right-hand-side).Value = value.Value). Szerencsére a C# ezt is lehetővé teszi számunkra. } static public MyInt operator+(MyInt lhs.

operator+(x.1 Egyenlőség operátorok A . ezért tagadjuk az eredményt.WriteLine(result.Value + rhs. elsőként megvizsgáljuk. de mi a nem-egyenlőségre vagyunk kíváncsiak.static public void Main() { MyInt x = new MyInt(10). MyInt rhs) { return !(lhs == rhs). hogy a két elem egyenlő–e. private set. ő egyetlen object típusú paramétert vár. ami pontosan ezt a választ adja meg). erről később még lesz szó. hogy azt használja és átalakítja a műveletet: MyInt result = MyInt. A fenti esetben ezt a metódust is illik megvalósítani. } public int Value { get. y). ami a CLS kompatibilitást hívatott megőrizni. } static public bool operator!=(MyInt lhs. hogy valóban a saját objektumunkkal van –e dolgunk: . MyInt rhs) { return new MyInt(lhs.Value). Ezekhez az operátorokhoz tartozik az object típustól örökölt virtuális Equals metódus is. Az Equals azonban egy kicsit különbözik. hogy ha túlterheljük az egyenlőség operátort ( ==) akkor definiálnunk kell a nem-egyenlő (!=) operátort is: class MyInt { public MyInt(int value) { this. Ezek egyike az. Console.150 - . } static public bool operator==(MyInt lhs. MyInt result = x + y. MyInt rhs) { return lhs.NET megfogalmaz néhány szabályt az operátor-kiterjesztéssel kapcsolatban. } static public MyInt operator+(MyInt lhs.Value.Value == rhs. } } A nem-egyenlő operátor esetében a saját egyenlőség operátort használtuk fel (a megvalósítás elve nem feltétlenül világos. } Mivel definiáltunk az osztályunkon egy saját operátort így a fordító tudni fogja. 22. ezért meg kell majd győződnünk arról. MyInt y = new MyInt(20).Value = value.Value).

++x. } } // 10 // 11 // 12 // 12 (!) .WriteLine(x.2 A ++/-. } 22.operátorok Ez a két operátor elég nagy fejfájást tud okozni.Value).Value). Console. } return this == (MyInt)rhs. így az osztály használható lesz gyűjteményekkel és a HashTable típussal is. stb…): public override int GetHashCode() { return this.public override bool Equals(object rhs) { if(!(rhs is MyInt)) { return false. A legegyszerűbb implementáció visszaad egy számot az adattag(ok)ból számolva (pl. } static public MyInt operator++(MyInt rhs) { ++rhs. } public int Value { get. Ha az Equals –t megvalósítottuk.: hatványozás. Console.Value << 2. amin meghívtuk a metódust. akkor ezt kell tennünk a szintén az object –től öröklött GetHashCode metódussal is. biteltolás. class MyInt { public MyInt(int value) { this. nézzük meg a következő kódot: using System.WriteLine(x. Console.WriteLine(x.Value).Value = value. return rhs.Value. Console. } } class Program { static public void Main() { MyInt x = new MyInt(10). } Mivel ez egy példány tag ezért a this–t használjuk az objektum jelölésére. MyInt y = x++. private set.Value).151 - .WriteLine(y.

hogy postfixes operátort egyszerűen nem készíthetünk (ezt egyébként maga a Microsoft sem ajánlja) (illetve készíthetünk. >=): static public bool operator<(MyInt lhs. vagyis (<. MyInt rhs) { return lhs. // implicit konverzió MyInt y = (MyInt)"20".Nézzük az utolsó sort! Mivel y–nak a postfixes formában adtunk értéket. //explicit konverzió Console. y. ezért 11–et kellene tartalmaznia. mint ha ++xet írtunk volna. 22.3 Relációs operátorok Hasonlóan a logikai operátorokhoz a relációs operátorokat is csak párban lehet elkészíteni.Parse(rhs)). de nem fog úgy működni. } Ebben az esetben az IComparable és IComparable<T> interfészek megvalósítása is szükséges lehet a különböző gyűjteményekkel való együttműködés érdekében. 22. >) és (<=.Value < rhs.Value. A probléma gyökere.Value. Ezeknél az operátoroknál az implicit illetve explicit kulcsszavakkal fogjuk jelölni a konverzió típusát: static public implicit operator MyInt(int rhs) { return new MyInt(rhs). } .Value. és bizony erre is létezik operátor. y == {1}".Value > rhs.4 Konverziós operátorok A C# a szűkebbről tágabbra konverziókat implicit módon (azaz különösebb jelölés nélkül). } static public explicit operator MyInt(string rhs) { return new MyInt(int.152 - . } static public bool operator>(MyInt lhs. MyInt rhs) { return lhs. ha a saját típusunk ilyesmire is képes legyen.WriteLine("x == {0}. ahogyan szeretnénk). míg a tágabbról szűkebbre ko nvertálást explicite (ezt jelölnünk kell) végzi. ehelyett x++ esetében pontosa n ugyanaz történik. Ezekkel hamarosan többet is foglalkozunk.Value). } Ezeket most így használhatjuk: static public void Main() { MyInt x = 10. x. Természetesen szeretnénk.

153 - .Value). MyInt rhs) { return new MyInt(lhs. hogy készítsük el a hagyományos változatot is: static public MyInt Add(MyInt lhs. } Ez természetesen nem kötelező. MyInt rhs) { return new MyInt(lhs + rhs). 22. hogy a konverziós operátorok mindig statikusak.Value + rhs.Fontos.5 Kompatibilitás más nyelvekkel Mivel nem minden nyelv teszi lehetővé az operátorok kiterjesztését ezért a CLS javasolja. } static public MyInt operator+(MyInt lhs. de bizonyos helyzetekben jól jön. .

} } } A try blokk jelöli ki a lehetséges hibaforrást. Természetesen mi azt szeretnénk. hogy valahogy kijavíthassuk ezt a hibát. mégpedig egy System. a catch pedig elkapja a megfelelő kivételt (arra figyeljünk.Exception osztály. Ezután a program leáll. hogy ezek is blokkok.23 Kivételkezelés Nyílván vannak olyan esetek.Message). Látható. Ehhez a művelethez három dologra van szükségünk: kijelölni azt a programrészt. amikor túlindexeltünk: using System.IndexOutOfRangeException e) { Console. amikor az alkalmazásunk. Amikor az alkalmazásunk „rossz” állapotba kerül. ami dobhat kivételt. ezért el fogjuk kapni a kivételt. azaz a blokkon belül deklarált változók a blokkon kívül nem láthatóak). akkor írhattuk volna ezt is: . kivételt fog dobni. Minden kivétel őse a System. } catch(System. bár gond nélkül lefordul. akkor egy ún. } } Itt az utolsó érvényes index az 1 lenne. try { array[2] = 10. Az ilyen „abnormális” működés kezelésére találták ki a kivételkezelést. array[2] = 10. class Program { static public void Main() { int[] array = new int[2].WriteLine(e. elkapni azt és végül kezeljük a hibát: using System. hogy a kivétel egy objektum formájában létezik. így kivételt kapunk. A fenti programra a következő lesz a kimenet: A hiba: Index was outside the bounds of the array. ahogy elképzeltük. ilyennel már találkoztunk a tömböknél.154 - . így ha nem speciálisan egy kivételt akarunk elkapni. mégsem úgy fog működni.IndexOutOfRangeException–t. class Program { static public void Main() { int[] array = new int[2].

} Ekkor minden kivételt el fog kapni a catch blokk. méghozzá egy System.155 - . Értelemszerűen ekkor a legújabb kivételt kaphatjuk el és az InnerException –ön keresztűl követhetjük vissza az eredeti kivételig. A System. ha több kivétel is történik.Exception("Kivétel. amikor a kivétel megszületik a rendszer azonnal elkezdi keresni a legközelebbi megfelelő catch blokkot.TypeInitializationException–nel. } catch(Exception e) { Console. majd ezután következik a catch blokk.Message).Message). hogyan működnek a ki vételek.Exception e) { Console. Abban a pillanatban. A másik lehetőség. . Kivételt a throw utasítással dobhatunk: using System.WriteLine(e.WriteLine(e.Exception tulajdonságai közül kettőt kell megemlítenünk: Message: ez egy olvashatóbb formája a kivétel okának. Kivétel két módon keletkezhet: vagy a throw utasítással szándékosan mi magunk idézzük elő. Ha mégis talált használható catch blokkot. akkor kap értéket. Ha több egymásba ágyazott kivételről van szó. InnerException: ez alapértelmezetten null értékkel rendelkezik. akkor a kivétel helyéről a vezérlés a talált catch –re kerül. akkor a megelőző catch blokkhoz tartozó finally blokk fut le. majd ha ott nem volt sikeres. Hurrá!"). az ekkor szintén kivétellel jár. A keresés közben két dolog is történhet: ha egy statikus tag vagy konstruktor inicializálása történik. class Program { static public void Main() { try { throw new System. } } } A C++-tól eltérően itt példányosítanunk kell a kivételt. } catch(System.try { array[2] = 10. elsőként abban a metódusban amelyben a kivétel keletkezett. hogy nem talál megfelelő catch blokkot. Nézzük meg. amely kivételobjektum InnerException tulajdonságába kerül az eredeti kivétel. akkor abban amely ezt a metódust hívta (és így tovább amíg nem talál olyat amely kezelné). ekkor a program futása – hibaüzenet társaságában – leáll. vagy az alkalmazás hibás működése miatt.

hiszen a többinek esélye sem lenne.Exception) { Console. array[3] = 10.1 Kivétel hierarchia Amikor kivétel dobódik. amelyik az ős kivételt kapja el. ha a z az utolsó helyen áll. } Ilyenkor viszont nem használhatjuk a kivételobjektumot. 23. akkor a vezérlést az első alkalmas catch blokk veszi át.IndexOutOfRangeException) { Console. a System.Exception osztályból származik. } catch { Console.A catch–nek nem kötelező megadni a kivétel típusát. persze ekkor nem használhatjuk a kivételobjektumot.WriteLine("Exception"). } } } Látható. } catch(System. } catch(System. using System. hogy a catch–nek elég csak a kivétel típusát megadni.156 - . class Program { static public void Main() { try { int[] array = new int[2]. akkor az összes lehetséges kivételt el fogjuk kapni vele.2 Kivétel készítése Mi magunk is készíthetünk kivételt. Hurrá!").Exception(). Mivel az összes kivétel a System.WriteLine("Kivétel. így ha ezt adjuk meg a catch–nél. ekkor minden kivételt elkap: try { throw new System. akkor a program csak akkor fog lefordulni. de ha van olyan.WriteLine("OutOfRange"). 23. Egyszerre több catch is állhat egymás után.Exception–ból származtatva: .

Ez hasznos olyan esetekben.157 - . Hurrá!"). } catch(MyException e) { Console. Exception inner) : base(message. //továbbadjuk throw(new System.ArgumentNullException("Tovább". amikor feljegyzést akarunk készíteni illetve. //vagy egy újat dobunk } Természetesen a fenti példában csak az egyik throw szerepelhetne „legálisan”. } } } 23.using System.ArgumentException e) { throw. } catch(System. inner) { } } class Program { static public void Main() { try { throw new MyException("Kivétel.Exception { public MyException() { } public MyException(string message) : base(message) { } public MyException(string message.ArgumentNullException()). e)). ebben a formában nem fog lefordulni. class MyException : System. Ilyenkor beállíthatjuk az Exception InnerException tulajdonságát is: try { throw new Exception().ArgumentException e) { throw(new System. } .3 Kivételek továbbadása Egy kivételt az elkapása után ismét eldobhatunk. ha egy specifikusabb kivételkezelőnek akarjuk átadni a kivételt: try { } catch(System.Message).WriteLine(e.

x). hogy nem szabadulnak fel időben az erőforrások (megnyitott file. try { Console. } Console. Lényegében a using-blokkal használt erőforrások fordítás után a megfelelő try-catch-finally blokkokká alakulnak. hogy a kivétel keletkezése után az éppen végrehajtott programrész futása megszakad. } finally { Console. Megoldást a finally–blokk használata jelent. az új kivétel már tartalmazni fogja a régit is.Itt az Exception osztály harmadik konstruktorát használtuk.WriteLine("x értéke a kivétel elõtt: {0}". hálózati kapcsolat. x = 11. 23. } } „Valódi” erőforrások kezelésekor kényelmesebb a using–blokk használata.158 - . Hurrá!"). ha a try blokk tartalmaz return kifejezést): using System.4 Finally blokk A kivételkezelés egy problémája. hogy történt–e kivétel mindig lefut (kivéve. .WriteLine("x értéke a kivétel után {0}". amely függetlenül attól. így előfordulhat. throw new Exception(). amely hibát okozhat. mivel az automatikusan lezárja azokat. illetve objektumok olyan formában maradnak meg a memóriában. class Program { static public void Main() { int x = 10. } catch(Exception) { Console. x).WriteLine("Kivétel.WriteLine("Finally blokk"). stb).

azaz -1 –re.1 IEnumerator és IEnumerable Készítsünk osztályt. ugyanis a ciklus meghívja a metódusát.159 - . Ezért kell megvalósítani egyúttal az IEnumerator interfészt is.Name = name. } } . hogy csak olyan osztályokon képes végigiterálni. 24. Használjuk az Animal osztályunk egy kissé módosított változatát: public class Animal { public Animal(string name) { this. ellenkező esetben (vagyis ha a lista végére ért) false értékkel tér vissza. } } A MoveNext a következő elemre mozgatja a mutatót. de erről később). amelyek megvalósítják az IEnumerator és IEnumerable interfészeket. és annak vissza kell adnia az osztályt IEnumerator–ként (ld. void Reset(). private set. } public string Name { get. ami így néz ki: public interface IEnumerator { bool MoveNext(). Ennek object típussal kell visszatérnie. hiszen minden típusra működnie kell (létezik generikus változata is. object Current { get. ha tudja. interfészeket. A Reset alapértelmezésre állítja a mutatót. Végül a Current (read-only) tulajdonság az aktuális pozicióban lévő elemet adja vissza.Collections névtérben található. implicit konverzió). és már tudjuk.24 Gyakorló feladatok IV. Mindkettő a System. Elsőként nézzük az IEnumerable interfészt: public interface IEnumerable { IEnumerator GetEnumerator(). amely megvalósítja Megoldás Korábban már találkoztunk a foreach ciklussal. } az IEnumerator és IEnumerable Ez a foreach–nek fogja szolgáltatni a megfelelő felületet.

Most készítsünk egy osztályt, amelyen megvalósítjuk a két interfészt, és ami tartalmaz egy Animal objektumokból álló listát:
public class AnimalContainer : IEnumerable, IEnumerator { private ArrayList container = new ArrayList(); private int currPosition = -1; public AnimalContainer() { container.Add(new Animal("Rex")); container.Add(new Animal("Bundás")); container.Add(new Animal("Parizer")); } }

Ez persze még nem az egész osztály, felvettünk egy ArrayListet, amiben eltároljuk az objektumokat, illetve deklaráltunk egy egész számot, ami az aktuális pozíciót tárolja el és kezdőértékéül -1–et adtunk (ld. Reset). Készítsük el az IEnumerator által igényelt metódusokat:
public bool MoveNext() { return (++currPosition < container.Count); } public object Current { get { return container[currPosition]; } } public void Reset() { currPosition = -1; }

Végül az IEnumerable interfészt valósítjuk meg:
public IEnumerator GetEnumerator() { return (IEnumerator)this; }

Ezután használhatjuk is az osztályt:
static public void Main() { AnimalContainer ac = new AnimalContainer(); foreach(Animal animal in ac) { Console.WriteLine(animal.Name); } }

Amennyiben a foreach-en kívül akarjuk használni az osztályt pl. ha készítettünk indexelőt is, akkor gondoskodnunk kell a megfelelő konverzióról is (a foreach kivételt képez, mivel ő ezt megteszi helyettünk). - 160 -

24.2 IComparable és IComparer Valósítsuk meg az IComparable illetve IComparer interfészt! Megoldás A második gyakorlati példánkban az IComparable interfészt fogjuk megvalósítani, amelyre gyakran van szükségünk. Ez az interfész általában olyan adatszerkezeteknél követelmény, amelyek az elemeiken megvalósítanak valamilyen rendezést. A generikus List típusmak is van rendező metódusa (Sort), amely ezzel a metódussal dolgozik. Az IComparable egyetlen metódussal a CompareTo–val rendelkezik, amely egy object típust kap paraméteréül:
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 osztályban a beépített típusok CompareTo metódusát használtuk, hiszen ők mind megvalósítják ezt az interfészt. Ez a metódus -1–et ad vissza, ha a hívó fél kisebb, 0–át, ha egyenlő és 1–et ha nagyobb. A használata:
static public void Main() { 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);

- 161 -

} Console.WriteLine(); }

A List<T> típusról bővebben a Generikusok című fejezetben olvashatunk. Hasonló feladatot lát el, de jóval rugalmasabb az IComparer interfész. Az IComparer osztályok nem részei az „eredeti” osztályoknak, így olyan osztályok esetén is használhatunk ilyet, amelyek implementációjához nem férünk hozzá. Például a List<T> rendezésénél megadhatunk egy összehasonlító osztályt, amely megvalósítja az IComparer interfészt, így tetszőleges rendezést valósíthatunk meg anélkül, hogy ki kellene egészíteni a List<T> -t. Most is csak egy metódust kell elkészítenünk, ez a Compare, amely két object típust vár paramétereként:
class ComparableClassComparer : IComparer { 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õ paraméter..."); } }

A metódus elkészítésénél az egyszerűség miatt feltételeztük, hogy az összehasonlított osztály megvalósítja az IComparable interfészt (a beépített típusok ilyenek), természetesen mi magunk is megírhatjuk az eredményt előállító programrészt. Ezután a következőképpen rendezhetjük a listát:
list.Sort(new ComparableClassComparer());

Az IComparer előnye, hogy nem kötődik szorosan az osztályhoz (akár a nélkül is megírhatjuk, hogy ismernénk a belső szerkezetét), így többféle megvalósítás is lehetséges.

24.3 M átrix típus
Készítsük el a mátrix típust és valósítsuk meg rajta az összeadás műveletet! Az egyszerűség kedvéért tegyük fel, hogy a mátrix csak egész számokat tárol. Megoldás

- 162 -

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); 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; } }

Mátrixokat úgy adunk össze, hogy az azonos indexeken lévő értékeket összeadjuk: 123 145 1+1 2+4 3+5 456 + 532 = (stb…) 789 211 Az összeadás műveletet csakis azonos nagyságú dimenziók mellet lehet elvégezni (3x3–as mátrixhoz nem lehet hozzáadni egy 4x4 –eset). Ezt ellenőriztük is az operátor megvalósításánál. Most csak az összeadás műveletet valósítottuk meg, a többi a kedves olvasóra vár. Plusz feladatként indexellenőrzést is lehet végezni az indexelőnél.

- 163 -

25

Delegate–ek

A delegate–ek olyan típusok, amelyek egy vagy több metódusra hivatkoznak. Minden delegate különálló objektum, amely egy listát tárol a meghívandó metódusokról (értelemszerűen ez egyúttal erős referencia is lesz a metódust szolgáltató osztályra). Nemcsak példány-, hanem statikus metódusokra is mutathat. Egy delegate deklarációjánál megadjuk, hogy milyen szignatúrával rendelkező metódusok megfelelőek:
delegate int TestDelegate(int x);

Ez a delegate olyan metódusra mutathat, amelynek visszatérési értéke int típusú és egyetlen int típusú paramétere van:
public int Pow(int x) { return (x * x); }

A használata:
TestDelegate dlgt = Pow; int result = dlgt(10);

A delegate hívásakor az összes a listáján lévő metódust neghívja. A delegate–ekhez a += illetve + operátorokkal hozzáadni a -= és – operátorokkal elvenni tudunk metódusokat:
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); } }

- 164 -

i < array. } array. hogy nem kell előre megadott metódusokat használnunk. } set { array[idx] = value.i < array. for(int i = 0. } static public void Main() { Array array = new Array(10).i < array. } } public void Transform(Transformer t) { for(int i = 0. } public int Length { get.Length.WriteLine(array[i]).: class Program { static public void TransformerMethod(ref int item) { item *= item.TransformerMethod). private int[] array.++i) { Console. } } } Két delegate szerkezetileg nem egyenlő. még akkor sem ha megegyezik: . array = new int[Length].165 - a szignatúrájuk . ehelyett dinamikusan adhatjuk meg az elvégzendő műveletet: class Array { public delegate void Transformer(ref int item).++i) { t(ref array[i]).++i) { array[i] = i. amely elvégzi a változtatásokat a tömbön.Length.A delegate–ek legnagyobb haszna. public Array(int length) { Length = length.Length. Pl. for(int i = 0. } } } A Transform metódus egy delegate–et kap paraméteréül.Transform(Program. set. } public int this[int idx] { get { return array[idx].

public delegate void Dlgt2().CMethod. static public void Method() { } static public void Main() { Dlgt1 d1 = Program.WriteLine(t1 == t2).Method1. ha mindkettő értéke null. // False . Test1 t2 = null. // False t1 -= Program. class Program { public delegate void Dlgt1(). static public void Method1() { } static public void Method2() { } static public void Main() { Test1 t1 = null.using System. // ez hiba } } Ugyanakkor ugyanazon delegate „típus” példányai között használhatjuk az == és != operátorokat. Console.Method1. Két delegate egyenlő. Console.166 - . = new C1(). Console. Dlgt2 d2 = d1.Method2. c2.CMethod. // True t1 += Program.WriteLine(t1 == t2). t2 = Program.Method. illetve ha a híváslistájukon ugyanazon objektumok ugyanazon metódusai szerepelnek (vagy ugyanazok a statikus metódusok): using System.WriteLine(t1 == t2). C1 C1 t1 t2 } } c1 c2 += += = new C1(). // True t1 = Program. class C1 { public void CMethod() { } } class Program { public delegate void Test1().Method2. Console. c1.WriteLine(t1 == t2).

class Animal { } class Dog : Animal { } class Cat : Animal { } class Program { public delegate Animal GetAnimal(). Dog d = (Dog)ga(). } } .GetType()).167 - . c.25. amelyek az eredeti paraméternél általánosabbak: using System. class Animal { } class Dog : Animal { } class Cat : Animal { } class Program { public delegate void DogDelegate(Dog d). } static public void Main() { GetAnimal ga = AnimalMethod. d. } } Ez az ún. Console. static public void AnimalMethod(Animal a) { } static public void Main() { DogDelegate d = AnimalMethod. azaz az átadott metódus visszatérési értéke lehet specifikusabb az eredetinél: using System. static public Animal AnimalMethod() { return new Animal(). ga = CatMethod.1 Paraméter és visszatérési érték Egy delegate–nek átadott metódus paraméterei lehetnek olyan típusok. a.GetType(). } static public Cat CatMethod() { return new Cat(). {1}. Ennek a fordítottja igaz a visszatérési értékre. Animal a = ga(). ga = DogMethod.GetType().WriteLine("{0}. Cat c = (Cat)ga(). {2}". kontravariáns (contravariant) viselkedés. } static public Dog DogMethod() { return new Dog().

using System. }. t(). } } Ilyenkor figyelni kell arra..: goto.). Console.Ezt pedig kovariáns (covariant) viselkedésnek nevezzük. .WriteLine(x). } } Egy névtelen metódus eléri az őt tároló blokk lokális változóit és módosíthatja is őket: using System. lehetőségünk van helyben kifejteni egyet. vagyis a változó akkor válik eltakaríthatóvá. class Program { public delegate void Test(). break. 25. Névtelen metódus nem használhat semmilyen ugró utasítást (pl.2 Névtelen metódusok Egy delegate–nek nem kötelező létező metódust megadnunk. Természetesen ez a névtelen metódus a program többi részéből közvetlenül nem hívható. }. t(10). static public void Main() { Test t = delegate(int x) { Console. csak a delegate–en keresztül. stb..168 - .WriteLine(x). ha a delegate maga is érvényét veszti. static public void Main() { int x = 10. Test t = delegate() { x = 11. hogy a külső változóra a delegate is erős referenciával mutat. class Program { public delegate void Test(int x).

Eddig ez nagyon úgy hangzik. Nézzünk egy egyszerű példát: class Test { public delegate void EventHandlerDelegate(string message). Ekkor meghívjuk az OnStatusChanged metódust. } } private void OnStatusChange() { if(TestStatusChange != null) { TestStatusChange("Az osztály állapota megváltozott!"). ún. mindössze három dologban különböznek: Esemény lehet része interfésznek míg delegate nem.OnStatusChange(). public event EventHandlerDelegate TestStatusChange. public int Data { get { return data. amely két paraméterrel (erről hamarosan) rendelkezik és void visszatérési típussal bír. mivel rendelkezésünkre áll a beépített általános EventHandler delegate. az eseménynek megfelelő szignatúrájú metódus sal. } set { data = value.169 - .utóbbi esetben a hívás kivételt váltana ki. Egy esemény rendelkezik add és remove „metódusokkal” felülbírálhatóak. hogy a saját állapota megváltozásakor értesítsen más osztályokat. amely elsőként megvizsgálja. eseménykezelővel. this. Ezt az osztály így használhatjuk: . Események Egy osztály eseményeket (event) használhat. amikor a data mező értéke megváltozik. és valóban egy esemény tulajdonképpen egy speciális delegate. private int data = 10. Egy eseményt csakis az az osztály „hívhat” meg amely deklarálta. Ehhez a „megfigyelő” osztályoknak fel kell iratkozni a „megfigyelt” osztály eseményére azáltal. hogy az eseményre feliratkoztak–e vagy sem . amelyek Egy esemény deklarációjában meg kell adnunk azt a delegate–et amely az eseményhez szükséges szignatúrát definiálja.25. Az esemény megtörténtekor ezek a metódusok fognak lefutni. hogy az előbbiek rendelkeznek egy. Az esemény akkor fog beindulni. } } } Nem feltétlenül kell delegate–et deklarálnunk. mintha a delegate–ekről beszéltünk volna.

} } Ezután már csak módosítani kell a delegate–et és az esemény kiváltását: public delegate void EventHandlerDelegate(object sender. TestEventArgs e).Message = message. } } Az eseményekhez rendelt eseménykezelőknek konvenció szerint (ettől eltérhetünk.170 - .Handler. Elsőként készítünk egy EventArgs osztályból származó új osztályt.class Program { static public void Handler(string message) { Console. amely képes tárolni az eseményhez kapcsolódó üzenetet (az EventArgs alapértelmezetten nem redelekezik ilyesmivel csak egy alaposztály a specializált eseményekhez): class TestEventArgs : EventArgs { public TestEventArgs(string message) : base() { this.Data = 11. set. mivel ugyanazt az eseményt használhatjuk különböző osztályokkal).WriteLine(message). t. az első az az objektum. Még módosítsuk az eseménykezelőt is: . a második pedig az eseményhez kapcsolódó információk. Módosítsuk ennek megfelelően a fenti programot. A második paraméter ekkor olyan típus lehet. } } A sender paraméternek a this–szel adjuk meg az értékét. t. } public string Message { get.TestStatusChanged += Program. de a Framework eseményei mind ilyenek) két paramétere van. } static public void Main() { Test t = new Test(). ezután explicit konverzióval visszakaphatjuk belőle a küldő példányt (az első paraméter szintén konvenció szerint minden esetben object típusú lesz. new TestEventArgs("Az osztály állapota megváltozott")). private void OnStatusChange() { if(TestStatusChanged != null) { TestStatusChanged(this. amely az EventArgs osztályból származik. amely kiváltotta az eseményt.

this. } public string Message { get. A probléma.Name)). Készítsünk egy EventArgs osztályt.ServerChange += client. hiszen gondoskodni kell arról. client. ServerEventArgs e).ServerChange -= client. } } Most pedig a szervert készítjük el: class Server { public delegate void ServerEvent(object sender.ServerMessageHandler. set.Format("Felhasználó <{0}> csatlakozott!". public Server() { } public void Connect(Client client) { this. hogy minden ilyen eseményről küldjünk értesítést az összes csatlakozott kliensnek. illetve a lista mérete is gondot jelenthet. A feladat. hogy ez azért nem olyan egyszerű.Format("Felhasználó <{0}> kilépett!". } public void Disconnect(Client client) { OnServerChange(string. csak szimuláljuk).Message = message. } } } . Készítsünk egy egyszerű kliens-szerver alkalmazást (persze nem a hálózaton.WriteLine(e. client.171 - . Normális esetben a szerver osztálynak tárolnia kellene a kliensek hivatkozásait pl. Események alkalmazásával viszont nagyon egyszerű lesz a dolgunk. egy tömbben.Message).ServerMessageHandler. public event ServerEvent ServerChange. } protected void OnServerChange(string message) { if(ServerChange != null) { ServerChange(this. OnServerChange(string. TestEventArgs e) { Console. } A következő példában az események valódi hasznát fogjuk látni. new ServerEventArgs(message)). hogy a kilépett klienseket töröljük a listából.Name)). amely segítségünkre lesz az eseménykezelők értesítésében: class ServerEventArgs : EventArgs { public ServerEventArgs(string message) : base() { this. A kliensek csatlakozhatnak a szerverhez (ezután pedig kiléphetnek).static public void Handler(object sender.

server. } public string Name { get.Connect(c1).Disconnect(c1).172 - . Client c1 = new Client("Józsi").Connect(c3).Name. Nézzük meg a kliens osztályt: class Client { public Client(string name) { Name = name. this. set.Connect(c2). ServerEventArgs e) { Console.Látható. server. } } Végül a Main: static public void Main() { Server server = new Server().WriteLine("{0} üzenetet kapott: {1}". } public void ServerMessageHandler(object sender. Client c3 = new Client("Tomi"). server.Message). hogy a kliensek kezelése nagyon egyszerű. } . mindössze egy műveletet kell elvégeznünk az eseményekre való felira tkozásért/leiratkozásért. server. e. Client c2 = new Client("Béla").

cserében sokkal biztonságosabb a használatuk. string s2 = "dió". ezt generikus paraméternek hívják. Program.WriteLine("x == {0} és y == {1}". ref s2). } A T fogja jelképezni az aktuális típust (lehet más nevet is adni neki. hogy minél többször felhasználhassuk. hogy melyik típust használjuk (ezt megadhatjuk mi magunk is explicite): static public void Main() { int x = 10. x = y. Ezután a metódust a hagyományos úton használhatjuk. eredetileg a Template szóból jött). Console. Ennek megvalósítására két eszköz áll rendelkezésünkre. x = y. ref int y) { int tmp = x.Swap<int>(ref x.Swap<string>(ref s1. az egyik az öröklődés. y = tmp. ha írunk egy generikus metódust: static public void Swap<T>(ref T x. ref T y) { T tmp = x.WriteLine("s1 == {0} és s2 == {1}".173 - .1 Generikus metódusok Vegyük a következő metódust: static public void Swap(ref int x. vagyis. } A C# generikusai hasonlítanak a C++ sablonjaira. s2). Két fontos különbség van a kettő . 26. akkor bizony sokat kell gépelnünk. y = tmp. a fordító felismeri. a másik pedig jelen fejezet tárgya a generikusok. y). Generikus paramétere csakis osztálynak vagy metódusnak lehet és ebből többet is használhatnak. de annál kevésbé hatékonyabbak. ref y). hogy egy adott kódrészletet elég általánosra írjunk meg ahhoz. Kivéve. hogy ez a metódus más típusokkal is működjön. } Ha szeretnénk.26 Generikusok Az objektum-orientált programozás egyik alapköve a kód-újrafelhasználás. int y = 20. Program. string s1 = "alma". s1. x. Console.

>= 0) { return t[pointer]. A másik eltérés az elsőből következik. 26. } } . Így a legkézenfekvőbb megoldás. int pointer. A következő példa nem fog lefordulni: static public T Sum<T>(T x.2 Generikus osztályok Képzeljük el.. mivel a C++ fordításkor ki tudja szűrni azokat az eseteket. } pointer = 0.174 - . Azt is képzeljük el.. throw(new InvalidOperationException("Üres. hogy sikeres lesz–e a végrehajtás. } public object Pop() { if(pointer-. hogy azt a feladatot kaptunk. amelyek hibásak.. addig a C# ezt a műveletet futási időben végzi el. hogy létező típust adtunk–e meg. ezért a fenti kód az összeadás miatt (nem feltétlenül valósítja meg minden típus) „rossz”. } t[pointer++] = item. hogy készítsünk egy verem típust. ha az elemeket egy object típusú tömbben tároljuk: class Stack { object[] t..közt: míg a C++ fordítási időben készíti el a specializált metódusokat/osztályokat. size = capacity. a fordító csakis olyan műveletek elvégzését fogja engedélyezni. összeadunk két típust a sablonmetódusban.. T y) { return x + y. hogy még nem hallottunk generikusokról. amelyeken nincs értelmezve összeadás. így nem tudhatja a fordító. readonly int size. pointer = 0. pl. } Fordításkor csak azt tudja ellenőrizni. A C# ezzel szemben kénytelen az ilyen problémákat megelőzni. public Stack(int capacity) { t = new object[capacity]. amely bármely típusra alkalmazható. } public void Push(object item) { if(pointer >= size) { throw(new StackOverflowException("Tele van. amelyek mindenképpen működni fognak.")).")).

A hatékonyság az érték/referenciatípusok miatt csökken jelentősen (ld. int pointer. Ezeket a problémákat könnyen kiküszöbölhetjük.++i) { s. } public object Pop() { if(pointer-. se nem kényelmes. } public void Push(T item) { if(pointer >= size) { throw(new StackOverflowException("Tele van.. épp milyen típussal dolgozunk.>= 0) { return t[pointer]. } for(int i = 0.. public Stack(int capacity) { t = new T[capacity]. ha generikus osztályt készítünk: class Stack<T> { T[] t. a kényelem pedig amiatt.Pop()). } pointer = 0.i < 10.")).Ezt most a következőképpen használhatjuk: static public void Main() { Stack s = new Stack(10). pointer = 0. for(int i = 0. nehogy olyan kasztolással éljünk ami kivételt dob..175 - . boxing/unboxing). de se nem hatékony. size = capacity.++i) { Console. } } Ezután akármelyik típuson könnyen használhatjuk: . throw(new InvalidOperationException("Üres. hogy mindig figyelni kell. } t[pointer++] = item.")).i < 10.WriteLine((int)s. } } Működni működik..Push(i). readonly int size.

static public void Main() { Stack<int> s = new Stack<int>(10). Ezeket a where kulcsszóval vezetjük be: where where where where where where T T T T T T : : : : : : alaposztály interfész osztály struktúra new() U Az utolsó két sor magyarázatra szorul.i < 10. } for(int i = 0.Pop()).++i) { Console. értéktípusra van szükség. minden más esetben fordítási hibát kapunk.3 Generikus megszorítások Alapértelmezetten egy generikus paraméter bármely típust jelképezhet. Az U pedig ebben az esetben egy másik generikus paramétert jelöl. for(int i = 0.i < 10. vagyis T olyan típusnak felel meg amely vagy U – ból származik. . A deklarációnál azonban kiköthetünk megszorításokat a paraméterre. A new() megszorítás olyan osztályra utal.WriteLine(s. Nézzünk néhány példát: class Test<T> where T : class { } Ezt az osztályt csak referenciatípusú generikus paraméterrel példányosíthatjuk. class Test<T> where T : struct { } Ez pedig épp az ellenkezője.++i) { s. amely rendelkezik alapértelmezett konstruktorral.Push(i). } } 26. vagy egyenlő vele.176 - .

class Test<T> where T : IEnumerable { }

Most csakis IEnumerable interfészt megvalósító típussal példányosíthatjuk az osztályt.
class Base { } class Derived : Base { } class Test<T> where T : Base { }

Ez már érdekesebb. Ez a megszorítás a generikus paraméter ősosztályára vonatkozik, vagyis példányosíthatunk a Base és a Derived típussal is.
class DefConst { public DefConst() { } } class Test<T> where T : new() { }

Itt olyan típusra van szükségünk, amely rendelkezik alapértelmezett konstruktorral, a DefConst osztály is ilyen.
class Base { } class Derived : Base { } class Test<T, U> where T : U { }

Most T típusának olyannak kell lennie, amely implicit módon konvertálható U típusára, vagyis T vagy megegyezik U–val, vagy belőle származik:
static public void Main() { Test<Derived, Base> t1 = new Test<Derived, Base>(); // ez jó Test<Base, Derived> t2 = new Test<Base, Derived>(); // ez nem jó }

Értelemszerűen írhattunk volna <Base, Base>-t, vagy <Derived, Derived>-et is.

- 177 -

26.4 Öröklődés
Generikus osztályból származtathatunk is, ekkor vagy az ősosztály egy specializált változatát vesszük alapul, vagy a nyers generikus osztályt:
class Base<T> { } class Derived<T> : Base<T> { } //vagy class IntDerived : Base<int> { }

26.5 Statikus tagok
Generikus típusok esetében minden típushoz külön statikus tag tartozik:
using System; class Test<T> { static public int Value; } class Program { static public void Main() { Test<int>.Value = 10; Test<string>.Value = 20; Console.WriteLine(Test<int>.Value); // 10 Console.WriteLine(Test<string>.Value); // 20 } }

26.6 Generikus gyűjtemények
A C# 2.0 bevezetett néhány hasznos generikus adatszerkezetet, többek közt listát és vermet. A következőkben megvizsgálunk közülük néhányat. Ezek a szerkezetek a System.Collections.Generic névtérben találhatóak.

- 178 -

26.6.1 List<T>
A List<T> az ArrayList generikus, erősen típusos megfelelője. A legtöbb esetben a List<T> hatékonyabb lesz az ArrayList –nél, emellett pedig típusbiztos is. Amennyiben értéktípussal használjuk a List<T>-t az alapértelmezetten nem igényel bedobozolást, de a List<T> rendelkezik néhány olyan művelettel, amely viszont igen, ezek főleg a kereséssel kapcsolatosak. Azért, hogy az ebből következő teljesítményromlást elkerüljük, a használt értéktípusnak meg kell valósítania az IComparable és az IEquatable interfészeket (a legtöbb beépített egyszerű (érték)típus ezt meg is teszi) (ezeket az interfészeket az összes többi gyűjtemény is igényli).
using System; using System.Collections.Generic; class Program { static public void Main() { List<int> list = new List<int>(); for(int i = 0;i < 10;++i) { list.Add(i); } foreach(int item in list) { Console.WriteLine(item); } } }

Az Add metódus a lista végéhez adja hozzá a paraméterként megadott elemet, hasonlóan az ArrayList–hez. Használhatjuk rajta az indexelő operátort is. A lista elemeit könnyen rendezhetjük a Sort metódussal (ez a metódus igényli, hogy a lista típusa megvalósítsa az IComparable interfészt):
using System; using System.Collections.Generic; class Program { static public void Main() { List<int> list = new List<int>(); Random r = new Random(); for(int i = 0;i < 10;++i) { list.Add(r.Next(1000)); } list.Sort(); } }

- 179 -

Kereshetünk is az elemek között a BinarySearch metódussal, amely a keresett objektum indexét adja vissza:
using System; using System.Collections.Generic; class Program { static public void Main() { List<string> list = new List<string>() { "alma", "dió", "körte", "barack" }; Console.WriteLine(list[list.BinarySearch("körte")]); } }

Megkereshetjük az összes olyan elemet is, amely eleget tesz egy feltételnek a Find és FindAll metódusokkal. Előbbi az első, utóbbi az összes megfelelő példányt adja vissza egy List<T> szerkezetben:
using System; using System.Collections.Generic; class Program { static public void Main() { 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 páros szám a listában: {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 páros elem a listában:"); evenList.ForEach(delegate(int item) { Console.WriteLine(item); }); } }

A feltételek megadásánál és a páros számok listájának kiíratásánál névtelen metódusokat használtunk.

- 180 -

Újdonságot jelent a listán metódusként hívott foreach ciklus. Ezt a C# 3.0 vezette be és az összes generikus adatszerkezet rendelkezik vele, lényegében teljesen ugyanúgy működik mint egy „igazi” foreach. paramétereként egy „void Method(T item) szignatúrájú metódust (vagy névtelen metódust) vár, ahol T a lista elemeinek típusa.

26.6.2 SortedList<T, U> és SortedDictionary<T, U>
A SortedList<T, U> kulcs – érték párokat tárol el és a kulcs alapján rendezi is őket:
using System; using System.Collections.Generic; class Program { static public void Main() { SortedList<string, int> list = new SortedList<string, int>(); list.Add("egy", 1); list.Add("kettõ", 2); list.Add("három", 3); } }

A lista elemei tulajdonképpen nem a megadott értékek, hanem a kulcs – érték párokat reprezentáló KeyValuePair<T, U> objektumok. A lista elemeinek eléréséhez is használhatjuk ezeket:
foreach(KeyValuePair<string, int> item in list) { Console.WriteLine("Kulcs == {0}, Érték == {1}", item.Key, item.Value); }

A lista kulcsai csakis olyan típusok lehetnek, amelyek megvalósítják az IComparable interfészt, hiszen ez alapján történik a rendezés. Ha ez nem igaz, akkor mi magunk is definiálhatunk ilyet, részletekért ld. az Interfészek fejezetet. A listában minden kulcsnak egyedinek kell lennie (ellenkező esetben kivételt kapunk), illetve kulcs helyén nem állhat null érték (ugyanez viszont nem igaz az értékekre). A SortedDictionary<T, U> használata gyakorlatilag megegyezik a SortedList<T, U>val, a különbség a teljesítményben és a belső szerkezetben van. A SD új (rendezetlen) elemek beszúrását gyorsabban végzi, mint a SL (O(Log n) és O(n)). Előre rendezett elemek beszúrásánál pont fordított a helyzet. Az elemek közti keresés mindkét szerkezetben O(Log n). Ezen kívül a SL kevesebb memóriát használ fel.

- 181 -

using System. list. item. U> rendezetlen párja a Dictionary<T.Add("kettõ". list.AddFirst("narancs"). 3). 26. using System.Collections.AddLast("dió"). A lista First tulajdonsága az első. LinkedListNode<string> current = list. class Program { static public void Main() { Dictionary<string.182 - . class Program { static public void Main() { LinkedList<string> list = new LinkedList<string>().Collections. item. foreach(KeyValuePair<string. list. list. Érték == {1}". U> A SortedDictionary<T. Elemeket az AddFirst (első helyre szúr be) és AddLast (utolsó helyre tesz) metódusokkal tudunk beilleszteni. } } } Teljesítmény szempontjából a Dictionary<T.Value).3 Dictionary<T. akkor használjuk ezt.Generic. U> mindig jobb eredményt fog elérni (egy elem keresése kulcs alapján O(1)).4 LinkedList<T> A LinkedList<T> egy kétirányú láncolt lista.AddLast("körte"). 2). int>(). int> item in list) { Console.First. Egy elem beillesztése illetve eltávolítása O(1) nagyságrendű művelet. egy-egy LinkedListNode<T> példány. 1). ezért ha nem fontos szempont a rendezettség. list. Last tulajdonsága pedig az utolsó tagra mutat. int> list = new Dictionary<string.Generic. A lista minden tagja különálló objektum . using System.Add("egy". U>: using System. list.Key.6. while(current != null) .WriteLine("Kulcs == {0}.26.6.AddLast("alma"). list.Add("három". A LLN<T> Next és Previous tulajdonságai a megelőző illetve a következő elemre mutatnak.

ReadOnlyCollection<string> roc = new ReadOnlyCollection<string>(list).183 - . using System. mint a hagyományos esetben. IEnumerator<T> { } Ekkor a megvalósítás teljesen ugyanúgy működik. foreach(string item in roc) { Console.WriteLine(current.Value). A listához nem adhatunk új elemet sem (ezt nem is támogatja).Next. // ez is kell class Program { static public void Main() { List<string> list = new List<string>() { "alma". using System. "körte". a kimeneten a „narancs” elemet látjuk majd első helyen.WriteLine(item).5 ReadOnlyCollection<T> Ahogy a nevéből látszik ez az adatszerkezet az elemeit csak olvasásra adja oda.7 Generikus interfészek. Például az IEnumerable és IEnumerator is ilyen: class MyClass<T> : IEnumerable<T>. using System. } } } Ebben a példában bejárunk egy láncolt listát. csak épp használnunk kell a generikus paraméter(eke)t.6.Generic. 26.ObjectModel. "dió" }. .Collections. mivel őt az AddFirst metódussal helyeztük be. current = current. } } } 26.{ Console. delegate –ek és események A legtöbb hagyományos interfésznek létezik generikus változata is.Collections. csakis a konstruktorban tölthetjük fel.

set. mivel a .A generikus adatszerkezetek (többek között) a generikus ICollection. Lássuk. amely szintén a Person osztályból származik. vagyis nincs kompatibilitás az általánosabb típusról a szűkebbre sem. a következő kódot: List<Student> studentList = new List<Student>(). de ezzel az egész generikus adatszerkezet értelmét vesztené). 26. még akkor sem. hogy a generikus paraméterek nem kovariánsak (covariance). hogy lehetséges lenne egy elem olyan tulajdonságát módosítani amellyel nem rendelkezik. nem kontravariánsak (contravariance). így ezeket megvalósítva akár mi magunk is létrehozhatunk ilyet. pontosabban a második nem fordul le. akkor lehetséges lenne Student és Teacher objektumokat is egy listába tenni ez pedig azzal a problémával jár. ha azok kompatibilisak lennének. Miért van ez így? Képzeljük el azt a helyzetet. legalábbis elvileg. ahol egy Person objektum használható ott egy Student objektum is megfelel. Azt mondjuk. ez pedig nyílván hibát okoz (persze típusellenőrzéssel ez is áthidalható.0 bevezeti a kovariáns és kontravariáns típusparamétereket.NET 4. } } . List<Person> personList = studentList. A dolog fordítottja is igaz. amikor a fenti osztályokat kiegészítjük még egy Teacher osztállyal. Ez az ő esetükben egyáltalán nem jár semmilyen „extra” kötelezettséggel. A következő példában egy generikus delegate segítségével nézzük meg az új lehetőségeket (új listaszerkezetet írni bonyolultabb lenne) (a példa megértéséhez szükség van a lambda kifejezések ismeretére). A fenti két sor.184 - . úgy oldva meg a fent vázolt problémát. Ha a generikus paraméterek kovariánsan viselkednének.NET nem tekinti egyenlőnek a generikus paramétereket. IList és IDictionary interfészeken alapulnak. Az interfészekhez hasonlóan a delegate–ek és események is lehetnek generikusak.8 Kovariancia és kontravariancia Nézzük a következő „osztályhierarchiát”: class Person { } class Student : Person { } A polimorfizmus elve miatt minden olyan helyen. hogy a kérdéses típusok csak olvashatóak illetve csak írhatóak lesznek. A . Először egészítsük ki a Person osztály egy Name tulajdonsággal: class Student : Person { public string Name { get.

Most lássuk a kontravarianciát: delegate void Method<in T>(T t). } } A fenti kód nem fordul le.NET 4.: IEnumerable.) képes a kovariáns illetve kontravariáns viselkedésre.Most lássuk a forrást: delegate void Method<T>().WriteLine(person. } } A .Name). hogy minden típust megfelelően kezeljük. Method<Student> m2 = m1. IComparable.. módosítsuk a delegate deklarációját: delegate void Method<out T>().0–tól kezdve az összes fontosabb interfész (pl. class Program { static public void Main() { Method<Person> m1 = (person) => Console. . etc.185 - . class Program { static public void Main() { Method<Student> m1 = () => new Student(). Most viszont minden működik. hiszen biztosítottuk. Method<Person> m2 = m1..

WriteLine(func(10)). azt a fordító magától „kitalálja” (a legtöbb esetre ez igaz. Egy lambda kifejezés gyakorlatilag megfelel egy névtelen metódus „civilizáltabb”. y) => (x * y).0 bevezeti a lambda kifejezéseket. int y). de néha szükség lesz rá. } } 27. 2)). A bemenő paraméternél nem kell (de lehet) explicit módon jeleznünk a típust. de ha megszokta az ember sokkal olvashatóbb kódot eredményez). Minden lambda kifejezés tartalmazza az ún. hogy „legyen”. elsőként ezt nézzük meg: using System.186 - . Természetesen nem csak egy bemenő paramétert használhatunk. ennek jelentése nagyjából annyi. elegánsabb változatának (ugyanakkor első ránézésre talán ijesztőbb. } } Egy olyan metódusra van tehát szükség amely egy int típusú bemenő paramétert vár és ugyanilyen típust ad vissza. a következő példában összeszorozzuk a lambda kifejezés két paraméterét: using System. A lambda kifejezés bal oldalán a bemenő paraméter (x) jobb oldalán pedig a visszadatott értékről gondoskodó kifejezés (x * x) áll. class Program { public delegate int IntFunc2(int x. lambda operátort ( =>).WriteLine(func(10. Az operátor bal oldalán a bemenő változók. class Program { public delegate int IntFunc(int x). static public void Main() { IntFunc func = (x) => (x * x). static public void Main() { IntFunc2 func = (x.1 Generikus kifejezések Generikus kifejezéseknek (tulajdonképpen ezek generikus delegate–ek) is megadhatunk lambda kifejezéseket. ezzel önálló lambda kifejezéseket hozva létre (ugya nakkor a . jobb oldalán pedig a bemenetre alkalmazott kifejezés áll. Console. Console. Mivel névtelen metódus ezért egy lambda kifejezés állhat egy delegate értékadásában is.27 Lambda kifejezések A C# 3. hogy jelöljük a típust). amelyek nem igénylik egy előzőleg definiált delegate jelenlétét.

hogy az első paraméter nagyobb–e a másodiknál. using System. ezt a fordító ellenőrzi. class Program { static public void Main() { Func<int. Kétféle generikus kifejezés létezik a Func. int.WriteLine(func(10)). Console. y) => (x > y). Console. A Func minden esetben rendelkezik legalább egy paraméterrel. de nem lehet visszatérési értéke: . bool> func = (x. amely szintén maximum négy bemenő paramétert kaphat. int> func = (x) => (x * x). mégpedig a visszatérési érték típusával. Elsőként a Func–ot vizsgáljuk meg: using System. using System.WriteLine(func(10. amely nem (void) (lásd: függvény és eljárás). // True } } Most megnéztük. Értelemszerűen a lambda operátor jobb oldalán lévő kifejezésnek megfelelő típust kell eredményeznie. class Program { static public void Main() { Func<int. 5)). class Program { static public void Main() { Func<bool> func = () => true. // True } } A Func párja az Action.187 - . ez biztosítja. amely adhat visszatérési értéket és az Action. Console.WriteLine(func()).generikus kifejezések kaphatnak névtelen metódusokat is). } } A generikus paraméterek között utolsó helyen mindig a visszatérési érték áll. előtte pedig a bemenő paraméterek (maximum négy) kapnak helyet. hogy mindig legyen visszatérési érték.

27. Egy kifejezésfa egy generikus kifejezést kap generikus paraméterként: using System.3 Lambda kifejezések változóinak hatóköre Egy lambda kifejezésben hivatkozhatunk annak a metódusnak a paramétereire és lokális változóira.2 Kifejezésfák Generikus kifejezések segítségével felépíthetünk kifejezésfákat.using System. act = (x) => Console. A lambda kifejezés fenntart magának egy másolatot a lokális változóból/paraméterből.188 - .Linq. amelyben definiáltuk. A felhasznált változókat inicializálni kell. mielőtt használnánk egy lambda kifejezésben. csak azután hívhatjuk meg. A külső változók akkor értékelődnek ki.Invoke(10.WriteLine(x).Expressions. hogy futási időben a CLR ki tudja azt értékelni.WriteLine(expression. // True } } A programban először IL kódra kell fordítani (Compile). 2)).Compile(). bool>> expression = (x. y) => (x > y). act(10). . vagyis az adott változó legutolsó értékadása számít majd. public void Method() { int local = 11. } } 27. class Program { static public void Main() { Action<int> act = (x) => Console. nem pedig a deklaráláskor. Console. class Test { public Action<int> act. using System. amikor a delegate ténylegesen meghívódik. ha az időközben kifut a saját hatóköréből: using System.WriteLine(x * local). // ez kell class Program { static public void Main() { Expression<Func<int. int. még akkor is. amelyek olyan formában tárolják a kifejezésben szereplő adatokat és műveleteket.

} int result = list.FindAll((item) => (item % 2 != 0)).Generic.Find((item) => (item % 2 == 0)).Method().4 Névtelen metódusok kiváltása lambda kifejezésekkel Lambda kifejezést használhatunk minden olyan helyen. hogy hogyan használhatjuk így a List<T> típust: using System. A lambda kifejezésben létrehozott változók ugyanúgy viselkednek.WriteLine(item)). Console. List<int> oddList = list. Nézzük meg pl. using System. t.WriteLine("Az elsõ páros szám: {0}". t. ahol névtelen metódus állhat. class Program { static public void Main() { List<int> list = new List<int>().. 27. result).ForEach((item) => Console. mint a hagyományos lokális változók.act(100). Console. vagyis valóban a legutolsó értékét használta a lambda a lokális változónak. } } Eseménykezelőt is írhatunk így: .i < 10. oddList.WriteLine("Az összes páratlan szám:").++i) { list.Add(i). } } Ez a program 10000–et fog kiírni.189 - .local = 100. a delegate minden hívásakor új példány jön létre belőlük. A lokális változók és paraméterek módosíthatóak egy lambda kifejezésben.Collections. for(int i = 1. } } class Program { static public void Main() { Test t = new Test().

} } Lambda kifejezés helyett ún.WriteLine("Eseménykezelõ!"). }. null). t. e) => { Console.OnTestEvent(). t. így akár több soros utasításokat is adhatunk. Most nézzük a programot: class Program { static public void Main() { Test t = new Test().190 - . . lambda állítást írtunk. ezért itt nyugodtan használhatunk null értéket.class Test { public event EventHandler TestEvent.TestEvent += (sender. Az esemény elindításánál nincs szükségünk most EventArgs objektumra. public void OnTestEvent() { if(TestEvent != null) { TestEvent(this. } } } Az EventHandler általános delegate–et használtuk az esemény deklarációjánál.

Szimbólumok definícióját mindig a forrásfile elején kell megtennünk.Attribute absztrakt osztályból származik felhasználható attribútumként. Egy tesztelés során általánosan használt attribútum a Conditional. ellenkező esetben a program nem fordul le. using System. Ahogy az már elhangzott. } } class Program { static public void Main() { DebugClass. Ezek általában be vannak betonozva az adott nyelvbe. amely egy előfordító által definiált szimbólumhoz köti programrészek végrehajtását.28 Attribútumok Már találkoztunk nyelvbe épített módosítókkal.Attribute { } [Test] class C { } . így a Conditional eredeti neve is ConditionalAttribute. Minden olyan osztály. message).191 - . mivel a fordító így is képes értelmezni a kódot. Ha a programban nem lenne definiálva az adott szimbólum a metódus nem futna le. Egy attribútum a fordítás alatt beépül a Metadata információkba. A Conditional a System. de az utótagot tetszés szerint elhagyhatjuk.NET Framework–kel dolgozunk.WriteLine("Debugger üzenet: {0}". } } Egy attribútumot mindig szögletes zárójelek közt adunk meg. amely bármilyen módon a System. ha a . amelyeket a futtató környezet (a CLR) felhasznál majd az objektumok kreálása során. mi magunk nem készíthetünk újakat – kivéve.DebugMessage("Main metódus"). mi magunk is készíthetünk attribútumokat: class TestAttribute : System. // ez is kell class DebugClass { [Conditional("DEBUG")] // ha a DEBUG létezik static public void DebugMessage(string message) { Console. Konvenció szerint minden attribútum osztály neve a névből és az utána írt „Attribute” szóból áll. mint amilyen a static vagy a virtual.Diagnostics névtérben rejtőzik: #define DEBUG // definiáljuk a DEBUG szimbólumot using System.Diagnostics.

hogy az attribútummal ellátott osztály leszármazottai is öröklik-e az attribútumot: [AttributeUsage(AttributeTargets. megadhatjuk. csak referenciatípuson vagy csak metódusoknál (ezeket akár kombinálhatjuk is a bitenkénti vagy operátorral(|)).Ez nem volt túl nehéz.Class. Az AttributeTargets. Lépjünk az AllowMultiple tulajdonságra! Ő azt fogja jelezni. Kezdjük az elsővel: a ValidOn azt határozza meg.és referenciatípusokkal is. Inherited = true)] class TestAttribute : System. Ezeket a szabályokat az AttributeUsage osztállyal deklarálhatjuk.Attribute { } [Test] class C { } [Test] // ez nem lesz jó struct S { } Így nem használhatjuk ezt az attribútumot.Class | AttributeTargets. [AttributeUsage(AttributeTargets. AllowMultiple = false)] class TestAttribute : System.Attribute { } Most már működni fog érték.Attribute { } [Test] [Test] // ez már sok class C { } Végül az Inherited tulajdonság azt jelöli. hogy hol használhatjuk az adott attribútumot. pl. hogy egy szerkezet használhat-e többet is egy adott attribútumból: [AttributeUsage(AttributeTargets. hogy bárhol használhatjuk az attribútumot.Struct)] class TestAttribute : System. hogy milyen típusokon használhatjuk. persze az attribútum sem nem túl hasznos.All pedig azt mondja meg.Attribute { } [Test] class C { } class CD : C { } A CD osztály a C osztály leszármazottja és mivel az attribútum Inherited tulajdonsága true értéket kapott ezért ő is örökli az ősosztálya attribútumát.Class. amelynek mindig kell értéket .192 - . Előbbinek mindig kötelező értéket adnunk. Módosítsuk egy kicsit! Egy attribútum-osztályhoz több szabályt is köthetünk a használatára vonatkozóan.Class)] class TestAttribute : System. Attribútumok kétféle paraméterrel rendelkezhetnek: positional és named. Módosítsunk rajta: [AttributeUsage(AttributeTargets. Ennek az osztálynak egy kötelezően megadandó és két opcionális paramétere van: ValidOn illetve AllowMultiple és Inherited. tulajdonképpen ez a konstruktor paramétere lesz (ilyen az AttributeUsage osztály ValidOn tulajdonsága. pl.

Attribute { public DescriptionAttribute(string description) { this. } } } } . méghozzá egy olyat. set.Description = description.Class | AttributeTargets.Struct.Description). Inherited = false)] class DescriptionAttribute : System. } public string Description { get. amelyeknek van alapértelmezett értéke. Utóbbiak az opcionális paraméterek. Inherited = false)] class DescriptionAttribute : System.adnunk).193 - .Description = description.Struct.WriteLine(((DescriptionAttribute)attribute).Class | AttributeTargets. } public string Description { get. hogy egy használható attribútumot készítsünk.Attribute { public DescriptionAttribute(string description) { this. AllowMultiple = false. Eljött az ideje. } } Az attribútumok értékeihez pedig így férünk hozzá: using System. amellyel megjegyzéseket fűzhetünk egy osztályhoz vagy struktúrához. } } [Description("Ez egy osztály")] class C { } class Program { static public void Main() { Attribute[] a = Attribute. így nem kell kötelezően megadnunk őket.GetCustomAttributes(typeof(C)). AllowMultiple = false. Az attribútumosztály nagyon egyszerű lesz: [AttributeUsage(AttributeTargets. foreach(Attribute attribute in a) { if(attribute is DescriptionAttribute) { Console. [AttributeUsage(AttributeTargets. set.

osztályt. A pointer típus az érték.WriteLine(*t. mint a natív. hogy a memóriában hol van és nem is tudjuk áthelyezni. Egy pointer mindig egy memóriacímet hordoz.x = &y. méghozzá egy int típusra mutató pointert.: Windows API hívások). Console. de ez nem jelenti azt.29 Unsafe kód A . amelyen elérjük.194 - . Vannak azonban helyzetek.Object–ből és konverziós kapcsolat sincs közöttük (bár az egyszerű numerikus típusokról létezik explicit konverzió). unsafe context). A menedzselt kód nem enged közvetlen hozzáférést a memóriához.x). class Test { public unsafe int* x. hogy közvetlenül elérjük a memóriát: A lehető legjobb teljesítményt szeretnénk elérni egy rendkívül számításigényes feladathoz (pl. int y = 10. a rendszer elkészíti nekünk és kapunk hozzá egy referenciát. } } } Először deklaráltunk egy unsafe adattagot.NET–en kívüli osztálykönyvtárakat akarunk használni (pl. amikor igenis fontos. Értelemszerűen boxing/unboxing sem alkalmazható rajtuk. t. amely memóriaterületen egy teljesen normális objektum van. de ezt behozzuk a memória gyorsabb elérésével/kezelésével ezért a két módszer között lényegében nincs teljesítménybeli különbség. . } class Program { static public void Main() { unsafe { Test t = new Test(). hogy szeretnénk egy ilyen és ilyen típusú objektumot. . A C# a memória direkt elérését mutatókon (pointer) keresztül teszi lehetővé. Épp ezért a menedzselt kód biztonságosabb. Ahhoz.: számítógépes grafika). mivel a fentiek miatt egy egész sor hibalehetőség egész egyszerűen eltűnik.NET platform legnagyobb eltérése a natív nyelvektől a memória kezelésében rejlik. méghozzá a sebesség. Nem fogjuk tudni. adattagot vagy blokkot az unsafe kulcsszóval kell jelölnünk (ez az ún. Nézzük a következő példát: using System. hogy megmondjuk. A pointerek nem származnak a System. hogy maga az osztály is unsafe lenne. Nyílván ennek ára van.és referenciatípusok mellett a harmadik típuskategória. Egy osztályon belül egy adattagot vagy metódust jelölhetünk unsafe módosítóval. vagyis annyi a dolgunk. hogy mutatókat használhassunk az adott metódust.

csc /unsafe main. ezt a dereference operátorral (*) tehetjük meg. amely nem tartalmaz az eddig felsoroltakon kívül mást. } } } Pointer csakis a beépített numerikus típusokra (beleértve a char is). Ez visszaadja a mutatott értéket. más objektum memóriacímét viszont nem tudom. mivel numerikus értékadás esetén nem fordul le a program (hiszen nem egy int objektumról van szó). Explicit konverzió létezik bármely két pointer típus között. A programot parancssorból a /unsafe kapcsolóval fordíthatjuk le. Sebaj. A programban ki akarjuk írni a memóriaterületen lévő objektum értékét. hogy jó Implicit konverzió van viszont bármely pointer típusról a void* univerzális pointer típusra. amely az adott objektum kezdőcíme. logikai típusokra.195 - . byte y = 20. erre való az ún. // ez nem biztos. A pointer úgy tudja visszaadni az értékét. // ez jó p1 = (int*)&y.cs A megfelelő explicit konverzióval a memóriacímet is lekérhetjük: using System. hogy ha A és B pointer nem ugyanakkora méretű területre mutat akkor az A–ról B–re való konverzió nem definiált működést okoz: int x = 10. Ezeket a típusokat összefoglaló néven unmanaged típusoknak nevezzük. míg ha magát a változót használjuk az „csak” a memóriacímet. int* y = &x. Visual Studio esetén jobb klikk a projecten.WriteLine((int)y). hogy tudja mekkora méretű az objektum (pl. A void*-on nem használható a dereference operátor: . ezért fennállhat a veszélye. Properties és ott állítsuk be. int* p1 = &x. A memóriacím a memória egy adott byte–jára mutat (vagyis a pointer növelése/csökkentése egy byte–al rakja odébb a mutatót). Console. felsorolt típusokra. class Program { static public void Main() { unsafe { int x = 10. más pointerekre illetve minden olyan általunk készített struktúrára hivatkozhat. egy int pointer egy 32 bites – 4 byte méretű – területet vesz majd elő). „címe–operátor” (&) amellyel átadhatom egy hagyományos objektum címét.Ebből következően a fenti deklarációban nem adhatok azonnal értéket az unsafe pointernek.

Mielőtt jobban megnéznénk a fixed–et vegyük szemügyre a következő forráskódot: . valamilyen erőforrás). Console. Console. } class Program { static public void Main() { unsafe { Test t = new Test().WriteLine((*p). struct Test { public int x. t. Test* p = &t.1 Fix objektumok Normális esetben a szemétgyűjtő a memória töredezettség mentesítése érdekében mozgatja az objektumokat a memóriában.x). class Program { static public void Main() { unsafe { int x = 10. hogy az objektumok a helyükön maradjanak. ami pedig nem fog frissülni. // 10 } } } 29.WriteLine( *((int*)p1) ). akkor a fixed kulcsszót kell használnunk. Egy pointer azonban mindig egy fix helyre mutat.using System. pl.WriteLine(p->x). ekkor a tagjait kétféleképpen érhetjük el: vagy a mutatón keresztül. } } } Egy struktúrára is hivatkozhatunk pointerrel.x = 10. // 10 Console. amikor hosszabb ideig van a memóriában a mutatott adat. Ez néhány esetben gondot okozhat (pl. vagy a nyíl (->) operátorral amely tulajdonképpen az előbbi rövidítése: using System.196 - . void* p1 = &x. Ha szeretnénk. hiszen a mutatott objektumnak a címét kértük le.

és a következőt fogjuk látni: . } } } } 29. fixed(int* p = array) for(int i = 0. class Program { static public void Main() { unsafe { int[] array = new int[] { 1. A következő kód már működni fog: using System. 7 }. C++ nyelven írt) DLL tartalmaz.net/win32hlp. hogy a GC ne mozdíthassa el. 3.WriteLine(*(p + i)). mivel használhatunk rajtuk pointert. 6. 7 }.using System.197 - .++i) { Console. int* p = &array. 4.2 Natív DLL kezelés Előfordu. hogy olyan metódust akarunk hívni. hogy ezt meg tudjuk tenni elsősorban ismernünk kell az eredeti metódus szignatúráját. class Program { static public void Main() { unsafe { int[] array = new int[] { 1. 6. Bár a tömbök referenciatípusok mégis kivételt képeznek. Ahhoz.winasm. ehhez szükségünk lesz pl. } } } Ez a program nem fordul le. 3. Referenciatípuson belül deklarált értéktípusok esetén (ilyenek a tömbelemek is) fixálni kell az objektum helyzetét. A következő példában egy Windows API metódust hívunk meg. a Win32 Programmers Reference című dokume ntációra amelyet letölthetünk: http://www. 4.i < 5. amelyet egy natív (pl. igaz nem az eddig látott módon (valamint a tömb elemeinek unmanaged típusnak kell lenniük). amely megjelenít egy MessageBox–ot.html Keressük ki a MessageBox metódust.

// address of title of message box UINT uType // style of message box ).dll lesz. "Hello!". using System.InteropServices. akkor a következőt kell látnunk: . "Nincs". hogy a függvényt egy küldő forrásból kell előkerítenie. és ha elindítjuk. // handle of owner window LPCTSTR lpText. Mindent tudunk.dll")] public static extern int MessageBox(int hWnd.198 - . hogy melyik DLL tárolja az adott függvényt.Runtime. // address of text in message box LPCTSTR lpCaption. string text. készítsük el a programot! A függvény importálásához a DllImport attribútumot fogjuk használni: using System. ez jelen esetben a user32.int MessageBox( HWND hWnd. } } public class API { [DllImport("user32. } Az attribútumnak meg kell adnunk a dll nevét. string caption. Ezen kívül még tudnunk kell azt is.MessageBox(0. 0). uint type). A forráskódot a szokásos módon fordítjuk. public class Program { static public void Main() { API. hogy tudassuk a fordítóval. valamint a metódus-deklarációt az extern kulcsszóval kell jelölnünk.

Process .GetProcesses(". process. időosztásos (time slicing) rendszer. ha egy komplexebb feladatot hajtanak végre. } } } .Diagnostics. Fontos megértenünk. Minden egyes process rendelkezik egy ún. Egy process–t az azonosítója (PID – Process ID) alapján különböztetünk meg a többitől.199 - .NET) lehetővé teszi másodlagos szálak (ún. amely a belépési pontja a programnak (ld.ProcessName). Thread Local Storage–ben (ebből minden szálnak van egy) és átadja a helyet egy másik szálnak. ezek a System.Diagnostics névtérben vannak. de gondoljunk bele. így el kell osztania az egyes feladatok közt a processzoridőt (ezt a szálak prioritása alapján teszi) ez az ún.WriteLine("PID: {0}. amelyekkel az egyes process–eket felügyelhetjük.név: {1}". Az ilyen helyzetek elkerülésére a Windows (és a . worker thread) hozzáadását a fő szálhoz. Írjunk egy programot.Id. amely – ha szükséges – betölti a saját adatait a TLS-ből és elvégzi a feladatát."). Main). amely kiírja az összes futó folyamatot és azonosítójukat: using System. mivel csak egy szál fér hozzá az összes erőforráshoz. class Program { static public void Main() { Process[] processList = Process. amely teljes mértékben elkülönül az összes többitől.30 Többszálú alkalmazások Egy Windows alapú operációs rendszerben minden futtatható állomány indításakor egy ún. hiszen a processzor egyszerre csak egy feladatot tud végrehajtani (bár terjednek a többmagos rendszerek. process. Azokat az alkalmazásokat. process jön létre. using System. Ugyanakkor ezek az alkalmazások hajlamosak „elaludni”. Jó példa lehet a szálkezelés bemutatására egy szövegszerkesztő használata: amíg kinyomtatunk egy dokumentumot (egy mellékszállal) az alkalmazás fő szála továbbra is figyeli a felhasználótól érkező utasításokat. hogy valójában a többszálúság a számítógép által nyújtott illúzió. Amikor egy szálnak kiosztott idő lejár. A többszálú programozás legnagyobb kihívása a szálak és feladataik megszervezése. hiszen a fő szál ekkor nem tud figyelni a felhasználó interakciójára. foreach(Process process in processList) { Console. amelyek csak fő szállal rendelkeznek thread-safe alkalmazásoknak nevezzük. hogy amikor hozzá sem nyúlunk a számítógéphez is ötven – száz szál fut). Az egyes szálak (a process– ekhez hasonlóan) önállóan működnek a folyamaton belül és „versenye znek” az erőforrások használatáért (concurrent access).NET számos osztályt és metódust bocsát rendelkezésünkre. fő szállal (primary– vagy main thread). A . akkor a futási adatait eltárolja az ún. az erőforrások elosztása.

Amennyiben tudjuk a process azonosítóját, Process.GetProcessById(azonosító) metódust is.

akkor

használhatjuk

a

A következő programunk az összes futó process minden szálát és azoknak adatait fogja kilistázni:
using System; using System.Diagnostics; class Program { static public void Main() { Process[] processList = Process.GetProcesses("."); foreach(Process process in processList) { Console.WriteLine("A folyamat ({0}) szálai", process.ProcessName); ProcessThreadCollection ptc = process.Threads; foreach(ProcessThread thread in ptc) { Console.WriteLine("Id: {0}, Állapot: {1}", thread.Id, thread.ThreadState); } } } }

Elég valószínű, hogy a program futásakor kivételt kapunk, hiszen a szálak listájába olyan szál is bekerülhet, amely a kiíráskor már befejezte futását (ez szinte mindig az Idle process esetében fordul elő, a meggondolás házi feladat, akárcsak a program kivétel-biztossá tétele). A fenti osztályok segítségével remekül bele lehet látni a rendszer „lelkébe”, az MSDN–en megtaláljuk a fenti osztályok további metódusait, tulajdonságait amelyek az „utazáshoz” szükségesek. A következő programunk a process–ek irányítását szemlélteti, indítsuk el az Internet Explorert, várjunk öt másodpercet és állítsuk le:
using System; using System.Diagnostics; using System.Threading; class Program { static public void Main() { Process explorer = Process.Start("iexplore.exe"); Thread.Sleep(5000); explorer.Kill(); } }

- 200 -

Egyúttal felhasználtuk az első igazi szálkezeléshez tartozó metódusunkat is, a Thread osztály statikus Sleep metódusát (a Thread osztály a System.Threading névtérben található).

30.1 Application Domain -ek
Egy .NET program nem direkt módon processként fut, hanem be van ágyazva egy ún. application domain–be a processen belül (egy process több AD–t is tartalmazhat egymástól teljesen elszeparálva). Ezzel a megoldással egyrészt elősegítik a platformfüggetlenséget, hiszen így csak az Application Domaint kell portolni egy másik platformra, a benne futó folyamatoknak nem kell ismerniük az operációs rendszert, másrészt biztosítja a programok stabilitását ugyanis ha egy alkalmazás összeomlik egy AD–ben, attól a többi még tökéletesen működik majd. Amikor elindítunk egy .NET programot elsőként az alapértelmezett AD (default application domain) jön létre, ezután – ha szükséges – a CLR további AD–ket hoz létre. A következő program kiírja az aktuális AD nevét:
using System; class Program { static public void Main() { AppDomain currAD = AppDomain.CurrentDomain; Console.WriteLine(currAD.FriendlyName); } }

Az alkalmazás neve fog megjelenni, hiszen ő az alapértelmezett AD és egyelőre nincs is több. Hozzunk létre egy második AppDomaint:
using System; class Program { static public void Main() { AppDomain secondAD = AppDomain.CreateDomain("second"); Console.WriteLine(secondAD.FriendlyName); AppDomain.Unload(secondAD); // megszüntetjük } }

30.2 Szálak
Elérkeztünk a fejezet eredeti tárgyához, már eleget tudunk ahhoz, hogy megértsük a többszálú alkalmazások elvét. Első programunkban lekérjük az adott programrész szálának az azonosítóját: - 201 -

using System; using System.Threading; class Program { static public void Main() { Console.WriteLine("Szál-Id: {0}", Thread.CurrentThread.ManagedThreadId); } }

A kimenetre minden esetben az egyes számot kapjuk, jelezvén, hogy az első, a „fő” szálban vagyunk. A program utasításainak végrehajtása szerint megkülönböztetünk szinkron- és aszinkron működést. A fenti program szinkron módon működik, az utasításait egymás után hatja végre, ha esetleg egy hosszas algoritmusba ütközik, akkor csak akkor lép a következő utasításra, ha azt befejezte, Az aszinkron végrehajtás ennek épp az ellentéte az egyes feladatokat el tudjuk küldeni egy másik szálba, a fő szál pedig fut tovább, amíg a mellékszál(ak) vissza nem térnek.

30.3 Aszinkron delegate-ek
A következőkben delegate–ek segítségével fogunk aszinkron programot írni. Minden egyes delegate rendelkezik azzal a képességgel, hogy aszinkron módon hívjuk meg, ezt a BeginInvoke és EndInvoke metódusokkal fogjuk megtenni. Vegyük a következő delegate–et:
delegate int MyDelegate(int x);

Ez valójában így néz ki:
public sealed class MyDelegate : System.MulticastDelegate { //...metódusok... public IAsyncResult BeginInvoke(int x, AsyncCallback cb, object state); public int EndInvoke(IAsyncResult result); }

Egyelőre ne foglalkozzunk az ismeretlen dolgokkal, nézzük meg azt amit ismerünk. A BeginInvoke–val fogjuk meghívni a delegate-et, ennek első paramétere megegyezik a delegate paraméterével (vagy paramétereivel). Az EndInvoke fogja majd az eredményt szolgáltatni, ennek visszatérési értéke megegyezik a delegate–éval. Az IAsyncResult objektum, amit a BeginInvoke visszatérít segít elérni az eredményt és az EndInvoke is ezt kapja majd paraméteréül. A BeginInvoke másik két paraméterével most nem foglalkozunk, készítsünk egy egyszerű metódust a delegate-hez és hívjuk meg aszinkron:

- 202 -

using System; using System.Threading; class Program { public delegate int MyDelegate(int x); static int Square(int x) { Console.WriteLine("Szál-ID: {0}", Thread.CurrentThread.ManagedThreadId); return (x * x); } static public void Main() { MyDelegate d = Square; Console.WriteLine("Szál-ID: {0}", Thread.CurrentThread.ManagedThreadId); IAsyncResult iar = d.BeginInvoke(12, null, null); Console.WriteLine("BlaBla..."); int result = d.EndInvoke(iar); Console.WriteLine(result); } }

A kimenet a következő lesz: Szál-ID: 1 BlaBla... Szál-ID: 3 144 Látható, hogy egy új szál jött létre. Amit fontos megértenünk, hogy a BeginInvoke azonnal megkezdi a feladata végrehajtását, de az eredményhez csak az EndInvoke hívásakor jutunk hozzá, tehát külső szemlélőként úgy látjuk, hogy csak akkor fut le a metódus. A háttérben futó szál üzenete is látszólag csak az eredmény kiértékelésénél jelenik meg, az igazság azonban az, hogy a Main üzenete előbb ért a processzorhoz, ezt hamarosan látni fogjuk. Többszálú program írásánál össze kell tudnunk hangolni a szálak munkavégzését, pl. ha az egyik szálban kiszámolt eredményre van szüksége egy másik, később indult szálnak. Ezt szinkronizálásnak nevezzük. Szinkronizáljuk az eredeti programunkat, vagyis várjuk meg amíg a delegate befejezi a futását (természetesen a szinkronizálás ennél jóval bonyolultabb, erről a következő fejezetekben olvashatunk):

- 203 -

using System; using System.Threading; class Program { public delegate int MyDelegate(int x); static int Square(int x) { Console.WriteLine("Szál-ID: {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(100); return (x * x); } static public void Main() { MyDelegate d = Square; Console.WriteLine("Szál-ID: {0}", Thread.CurrentThread.ManagedThreadId); IAsyncResult iar = d.BeginInvoke(12, null, null); while(!iar.IsCompleted) { Console.WriteLine("BlaBla..."); } int result = d.EndInvoke(iar); Console.WriteLine(result); } }

Ezt a feladatot az IAsyncResult interfész IsCompleted tulajdonságával oldottuk meg. A kimenet: Szál-ID: 1 BlaBla... BlaBla... BlaBla... Szál-ID: 3 BlaBla... BlaBla... BlaBla... BlaBla... 144 Itt már tisztán látszik, hogy az aszinkron metódus futása azonnal elkezdődött, igaz a Main itt is megelőzte. A Square metódusban azért használtuk a Sleep metódust, hogy lássunk is valamit, ellenkező esetben túl gyorsan lefut ez az egyszerű program. Erősebb számítógépeken nem árt módosítani az alvás idejét akár 1000 ms–re is. Valljuk be, elég macerás mindig meghívogatni az EndInvoke–ot, felmerülhet a kérdés, hogy nem lehetne valahogy automatizálni az egészet. Nos, épp ezt a gondot - 204 -

new AsyncCallback(AsyncMethodComplete).Remoting. Console.WriteLine("BlaBla."). Eredmény: 144 .WriteLine("Szál-ID: {0}". ha a mellékszál elvégezte a feladatát: using System. akkor azt fogjuk látni. valamint egy darab IAsyncResult típusú paraméterrel rendelkezik.").Threading. Ez a metódus azonnal le fog futni.. Szál-ID: 3 Aszinkron szál kész. IAsyncResult iar = d. Console.AsyncDelegate. Console. amely egy olyan metódusra mutathat. d.CurrentThread. return (x * x).Messaging. static int Square(int x) { Console.. minthogy az aszinkron metódus kész lenne.. using System.. Ez egy delegate.oldja meg a BeginInvoke harmadik AsyncCallback típusú paramétere. } static public void Main() { MyDelegate d = Square.ManagedThreadId). valamint ez gyorsabban történik. Thread.. Épp ezért érdemes egy ReadKey vagy egy Sleep metódust használni a program végén. class Program { public delegate int MyDelegate(int x).. } static void AsyncMethodComplete(IAsyncResult iar) { Console. mert a „BlaBla” után a program futása megáll. using System. MyDelegate d = (MyDelegate)result.WriteLine("Szál-ID {0}".BeginInvoke(12. amelynek visszatérési értéke void.ManagedThreadId). mivel elérte a Main végét és nincs több utasítás. A kimenet a következő lesz: Szál-ID 1 BlaBla.. null).Runtime. Ez azért van.CurrentThread.205 - .. } } Ha futtatjuk ezt a programot.WriteLine("Eredmény: {0}". hogy nem írja ki az eredményt. AsyncResult result = (AsyncResult)iar.EndInvoke(iar)). Thread.WriteLine("Aszinkron szál kész.

AsyncState). Console.NET 4. "Üzenet a jövõbõl :)"). mégpedig a BeginInvoke utolsó paraméterének megismerése.206 - .Egyetlen dolog van hátra. ha valamilyen plusz információt akarunk tovább ítani. m += () => Console.WriteLine("4")). } 30.0–tól használt Action megfelelő: using System. hogy delegate-eket illetve önálló metódusokat is egyszerre. AsyncResult result = (AsyncResult)iar. class Program { static public void Method() { Console. azaz bármilyen objektumot átadhatunk. Console.BeginInvoke(12. d.3.EndInvoke(iar)). Ehhez a feladathoz a korábban már megismert Task Parallel Library lesz a segítségünkre a Parallel. } static public void Main() { Action m = () => Console. A BeginInvoke most így néz ki: IAsyncResult iar = d. Parallel.WriteLine("3").WriteLine("2").Tasks.Invoke metódussal. Ez egy object típusú változó. Az üzenetet az IAsyncResult AsyncState tulajdonságával kérdezhetjük le: static void AsyncMethodComplete(IAsyncResult iar) { Console. MyDelegate d = (MyDelegate)result. using System.WriteLine("Eredmény: {0}".Invoke Action típusú elemeket tartalmazó tömböt vár paraméterként. () => Console. new AsyncCallback(AsyncMethodComplete).Method.Invoke(m. Természetesen ez a metódus leginkább több processzormaggal ellátott számítógépen lesz igazán hatékony. „hagyományos” delegate–et így nem hívhatunk.. párhuzamosan hívjunk meg.1 Párhuzamos delegate hívás A .Threading. Egyetlen szabály azért van.WriteLine("Aszinkron szál kész. } } A Parallel.WriteLine("1"). .WriteLine("Üzenet: {0}".AsyncDelegate.. csakis a C# 3.0 lehetővé teszi. Ezt a paramétert használjuk. iar."). m += Program.

ThreadInfo)).ThreadInfo().Name = "Current-Thread". mi magunk is elkészíthetjük őket.30.CurrentThread.CurrentThread. class Test { public void ThreadInfo() { Console.207 - . backgroundThread.WriteLine("Szál-név: {0}". Vegyük a következő programot: using System.Start(). class Test { public void ThreadInfo() { Console. } } class Program { static public void Main() { Thread current = Thread. } } class Program { static public void Main() { Test t = new Test().Threading. Thread backgroundThread = new Thread( new ThreadStart(t.Name = "Background-Thread".Name). } } . t. Thread.WriteLine("Szál-név: {0}". } } Elsőként lekértük és elneveztük az elsődleges szálat.Threading. current.CurrentThread. Thread. Test t = new Test(). using System. using System. mivel alapértelmezetten nincs neve. A következő programban a Test objektum metódusát egy háttérben futó szálból fogjuk meghívni: using System. hogy később azonosítani tudjuk.Name). backgroundThread. hogy másodlagos szálakat hozzunk létre nem feltétlenül kell delegate–eket használnunk.4 Szálak létrehozása Ahhoz.

a paramétere pedig object típusú lehet: using System.5 Foreground és background szálak A . hogy ez az állítás nem állja meg a helyét. } } Nyílván a metódusban nem árt ellenőrizni a paraméter típusát mielőtt bármit csinálunk vele. class Test { public void ThreadInfo(object parameter) { Console. A ThreadStart–hoz hasonlóan ez is egy delegate.208 - . hogy az elsődleges és másodlagos szálak fogalma megegyezik jelen fejezetünk tárgyaival.ThreadInfo)). de mi van akkor. Thread. A kettő közti különbség a következő: a CLR addig nem állítja le az alkalmazást. ugyanez a háttérbeli szálakra nem vonatkozik (az aszinkron delegate esetében is ezért kellett a program végére a „lassítás”). Ez eddig szép és jó.WriteLine("Paraméter: {0}".Start("Hello"). 30. hogy a háttérbe küldjük őket: . using System. Az igazság viszont az. parameter).Name). Thread backgroundThread = new Thread( new ParameterizedThreadStart(t. Logikus (lenne) a feltételezés. backgroundThread.A Thread konsturktorában szereplő ThreadStart delegate-nek kell megadnunk azt a metódust amelyet a másodlagos szál majd futtat.Name = "Background-Thread".WriteLine("Szál-név: {0}".CurrentThread. és amelyek a háttérben futnak. ha a meghívott metódusnak paraméterei is vannak? Ilyenkor a ThreadStart parametrizált változatát használhatjuk. if(parameter is string) { Console. ami igen eredeti módon a ParameterizedThreadStart névre hallgat. } } } class Program { static public void Main() { Test t = new Test(). szintén void visszatérési típusa lesz.Threading. amíg egy előtérbeli szál is dolgozik. ugyanis alapértelmezés szerint minden szál (a létrehozás módjától és idejétől függetlenül) előtérben fut. backgroundThread. Természetesen van arra is lehetőség.NET két különböző száltípust különböztet meg: amelyek előtérben.

Thread backgroundThread = new Thread( new ThreadStart(t. backgroundThread.6 Szinkronizáció A szálak szinkronizációjának egy primitívebb formáját már láttuk a delegate–ek esetében. amíg a blokkolás feltételének a környezet eleget nem tesz. backgroundThread.ThreadInfo)). . 30.Threading.WriteLine("Stop.using System. class Program { static public void Main() { Console. using System.Start().").Sleep(5000).209 - .Sleep(2000). ez a Thread. } } class Program { static public void Main() { Test t = new Test().. Ennek már ismerjük egy módját.IsBackground = true.. ezért az általunk készített szál „valódi” háttérben futó szál lett. using System. vagy a folyamat valamilyen módon megszakad.Name). most egy kicsit komolyabban közelítünk a témához. } } Amikor egy szálat leblokkolunk az azonnal „elereszti” a processzort és addig inaktív marad. Console.. backgroundThread.. vagyis a főszálnak nem kell megvárnia.WriteLine("Szál-név: {0}". ezek közül az első a blokkolás.Threading. class Test { public void ThreadInfo() { Thread. Thread. Console. } } Ez a program semmit nem fog kiírni és pont ezt is vártuk tőle. mivel beállítottuk az IsBackground tulajdonságot.").CurrentThread.Name = "Background-Thread". Négyféleképpen szinkronizálhatjuk a szálainkat.WriteLine("Start. Thread.Sleep metódus: using System.

t. class Program { static public void Main() { Thread t = new Thread(delegate() { Thread.Abort(). static int y = 20.Join(). t. A következő példa (ezúttal lambda kifejezéssel) ezt mutatja meg: using System. Nézzük a következő példát: using System. amin meghívták nem végezte el a feladatát: using System.Start(). amelyet egy másik szál már használ.210 - . amely idő lejárta után – ha a szál nem végzett feladatával – hamis értékkel tér vissza. class Test { static int x = 10.Join(1000) == false) { Console."). class Program { static public void Main() { Thread t = new Thread(() => Thread. t. amíg az a szál. Ez azt jelenti. } } A Join–nak megadhatunk egy „timeout” paramétert (ezredmásodpercben).. using System. ahonnan érkezési sorrendben lehet hozzájutni az erőforráshoz (ha az előző szál már végzett). akkor automatikusan blokkolódik.Threading.Sleep(2000)). if(t..WriteLine("Az idő lejárt.Start(). // megszakítjuk a szál futását } } } A következő szinkronizációs módszer a lezárás (locking). és várólistára kerül. static public void Divide() { if(Test. t. hogy erőforrásokhoz.Sleep(2000).x != 0) { . illetve a program bizonyos részeihez egyszerre csak egy szálnak engedünk hozzáférést. using System. Ha egy szál hozzá akar férni az adott dologhoz.A Join metódus addig várakoztatja a hívó szálat. using System.Threading. } ).Threading.

y / Test. hogy tényleg legyen kivétel. amelyhez egyszerre csak egy szál fér hozzá. hogy ne változzon meg az állapota egy másik szál keze nyomán: .x != 0) { Thread. eljut odáig. hogy megérkezik egy szál. hogy azonnal kivételt kapunk). nem biztos. Test. Test. rendben találja és továbblép.WriteLine(Test.Start().Thread.x = 0.x). A jegyzet ezt a témát nem tárgyalja. static public void Divide() { lock(locker) { if(Test. A műveletet lezárhatjuk a következő módon: static object locker = new object(). } } } class Program { static public void Main() { Thread t1 = new Thread(new ThreadStart(Test. } } } A lock kijelöl egy blokkot. akkor kap egy szép kis kivételt. t1. tokent. a következő – angol nyelvű – weboldalon bővebb információt talál a kedves olvasó: http://igoro. Ahhoz azonban. ezzel teszünk róla.x).com/archive/volatile-keyword-in-c-memory-model-explained/ Természetesen nem csak statikus metódusokból áll a világ.Sleep(2). és amikor a második szál osztani akar. hogy kiírja az eredményt. de ilyen esetekben az egész objektumra kell vonatkoztatni a zárolást. t2. amelyet lezárhat. Console.y / Test. A Divide metódus feltételében nem véletlenül van ott a Sleep.Start().Divide)). és épp ekkor érkezik egy másik szál is. Thread t2 = new Thread(new ThreadStart(Test. mivel ez egy egyszerű program muszáj lelassítani egy kicsit az első szálat (érdemes többször lefuttatni. Megvizsgálja a feltételt. A lock helyett bizonyos esetekben egy lehetséges megoldás volatile kulcsszóval jelölt mezők használata. Ebben a pillanatban azonban az elsőként érkezett szál lenullázza a változót.Divide)).x = 0. } } Tegyük fel.Sleep(2). A tokennek minden esetben referenciatípusnak kell lennie.WriteLine(Test. Console.211 - . egy „normális” metódust is lezárhatunk. hogy ezt megtehessük ki jelölnünk egy ún. mivel a megértéséhez tisztában kell lennünk a fordító sajátosságaival.

. mutex. A következő példában létrehozunk több szálat és versenyeztetjük őket egy metódus használatáért.ReleaseMutex().i < 10. x = 0. használata előtt meg kell hívnunk a WaitOne metódust. } } Most pedig jöjjön a főprogram: class Program { static Test t = new Test(). } } .ResourceMetod().++i) { t. public void ResourceMetod() { mutex. Elsőként készítsük el az osztályt. public void Divide() { lock(this) { if(x != 0) { Thread.CurrentThread. azaz a számítógépen futó összes folyamat elöl elzárja a használat lehetőségét..212 - .".WaitOne(). Thread.WriteLine("{0} használja az erõforrást.class Test { int x = 10.CurrentThread. Console. int y = 20. Console. Az erőforrás/metódus/stb.Sleep(1000). Console.WriteLine(y / x). hogy utóbbi processz szinten zárol. akkor az alkalmazás futásának végén a CLR automatikusan megteszi helyettünk). } } } } A lock–hoz hasonlóan működik a Mutex is. a legnagyobb különbség az a kettő közt..".Name). static public void ResourceUserMethod() { for(int i = 0. Thread..Name).WriteLine("{0} elengedi az erõforrást. a használat után pedig el kell engednünk az erőforrást a ReleaseMutex metódussal (ha ezt nem tesszük meg a kódból. amely tárolja az „erőforrást” és a Mutex objektumot: class Test { private Mutex mutex = new Mutex(). Thread.Sleep(2).

viszont sokan vannak. hogy a bejövő kapcsolatoknak mindig új szálat készítünk. akkor nagyon gyorsan teljesítményproblémákba fogunk ütközni: .WaitOne(). Tegyük még hozzá azt is. A következő program az előző átirata. hogy megadhatunk egy számot. Console.7 ThreadPool Képzeljük el a következő szituációt: egy kliens-szerver alkalmazást készítünk.ToString() }).". 30. for(int i = 0. public void ResourceMetod() { semaphore. és ha kliens érkezik. hogy a kliensek viszonylag rövid ideig tartják a kapcsolatot a szerverrel. egy időben maximum három szál férhet hozzá a metódushoz: class Test { private Semaphore semaphore = new Semaphore(3. } threadList.ForEach((thread) => thread.Add( new Thread(new ThreadStart(Program. Thread.. Ha úgy készítjük el a programot..Name).CurrentThread.++i) { threadList.Start()).ResourceUserMethod)) { Name = "Thread" + i.. a szerver a főszálban figyeli a bejövő kapcsolatokat.WriteLine("{0} használja az erõforrást. hogy egy erőforráshoz maximum hány szál férhet hozzá egy időben. } } A főprogram pedig ugyanaz lesz.Release().".WriteLine("{0} elengedi az erõforrást. Thread.. Console. azzal a különbséggel.i < 10.213 - . amely meghatározza. 3).static public void Main() { List<Thread> threadList = new List<Thread>(). akkor készít neki egy szálat majd a háttérben kiszolgálja. semaphore.CurrentThread. Thread.Name).Sleep(1000). } } A Semaphore hasonlít a lock–ra és a Mutex–re.

. Egy későbbi fejezetben elkészítünk majd egy. A QueueUserWorkItem metódus lesz a ThreadPool lelke.i < 20. Sokkal hatékonyabbak lehetnénk. Nézzük meg a következő programot: using System. i). az első paraméter a „rendes” a második az aszinkron szálak számát jelzi (utóbbira most nincs szükség ezért kapott nulla értéket). az az.Do).++i) { ThreadPool. Egy objektum létrehozása költséges. hogy minden egyes kapcsolatnak új szálat készítünk. vagyis az. } } Ami elsősorban feltűnhet.  Mivel sok bejövő kapcsolatunk van. 0). Ezt a módszert threadpooling–nak nevezzük és szerencsénk van. vagyis gyakrabban fut majd a GC. class Program { static public void Do(object inObj) { Console. itt indítjuk útjára az egyes szálakat. Ezt a paramétert adjuk meg a második paraméterben. amely olyan metódusra mutathat. akkor addig vár. } static public void Main() { ThreadPool. majd eldobjuk azt. hogy a ThreadPool egy statikus osztály.  Ha már nem használjuk. Thread.SetMaxThreads(5. most „csak” egy egyszerű példán keresztül fogjuk megvizsgálni ezt a technikát.QueueUserWorkItem( new WaitCallback(Program. } Console.NET beépített megoldással rendelkezik (ugyanakkor ha igazán hatékonyak akarunk lenni. akkor a memóriában marad. a fenti felvázolt helyzethez hasonló programot.Sleep(500). using System.vagyis nem tudjuk példányosítani ehelyett a metódusait használhatjuk. amíg el nem takarítja a GC. A probléma gyökere az egyes pont. Ha egy „feladat” bekerül a listára. (int)inObj). amelynek visszatérési értéke nincs (void) és egyetlen object típusú paraméterrel rendelkezik.ReadKey(). írhatunk saját. for(int i = 0. ha megpróbálnánk valahogy újrahasznosítani a szálakat. mivel a . az adott követelményeknek legjobban megfelelő ThreadPool osztályt is). ezért hamarabb lesz tele a memória szeméttel.Threading. de nincs az adott pillanatban szabad szál.WriteLine("A következõ adatot használjuk: {0}". amíg nem kap egyet. A metódus első paramétere egy delegate.214 - . A SetMaxThread metódus a maximálisan memóriában tartott szálak számát állítja be.

215 - . hogy a ThreadPool osztály csakis background szálakat indít. így látni is fogjuk. hogy mi történik éppen (erre pl.ReadKey parancsot. vagyis a program nem fog várni amíg minden szál végez hanem kilép.Fontos tudni. Ennek megakadályozására tettünk a program végére egy Console. . a fent említett kliens -szerver példában nincs szükség. mivel a szerver a főszálban végtelen ciklusban várja a bejövő kapcsolatokat).

Az erre a paradigmára épülő programozást reflektív programozásnak nevezzük. Console. ahol a program (futás közben) képes megváltoztatni saját struktúráját és viselkedését. hogy egyáltalán nem írjuk le a new operátort: using System.CreateInstance(type).31 Reflection A „reflection” fogalmát olyan programozási technikára alkalmazzuk.WriteLine(t. Console. lépjünk előre egyet: mi lenne. Vegyük a következő példát: using System. // Test } } Megjegyzendő. class Test { } class Program { static public void Main() { Test t = new Test().Reflection. using System. Ebben a fejezetben csak egy rövid példát találhat a kedves olvasó.GetType("Test"). így ha elgépeltünk valamit az bizony kivételt fog dobni futáskor (System. // ez kell class Test { } class Program { static public void Main() { Type type = Assembly. a Reflection témaköre óriási és a mindennapi programozási feladatok végzése közben viszonylag ritkán szorulunk a használatára (ugyanakkor bizonyos esetekben nagy hatékonysággal járhat a használata).GetType()). Type type = t.216 - .WriteLine(type). persze a reflektív programozás ennél többről szól.GetCallingAssembly(). .GetType().ArgumentNullException). hogy fordítási időben semmilyen ellenőrzés sem történik a példányosítandó osztályra nézve. ha az objektumot úgy készítenénk el. var t = Activator. // Test } } Futásidőben lekértük az objektum típusát ( a GetType metódust minden osztály örökli az object–től).

A következő példában úgy fogunk meghívni egy példánymetódust, hogy nem példányosítottunk hozzá tartozó osztályt:
using System; using System.Reflection; // ez kell class Test { public void Method() { Console.WriteLine("Hello!"); } } class Program { static public void Main() { Type type = Assembly.GetCallingAssembly().GetType("Test"); type.InvokeMember("Method", BindingFlags.InvokeMethod, null, Activator.CreateInstance(type), null); } }

A felhasznált InvokeMember metódus első paramétere annak a konstruktornak/metódusnak/tulajdonságnak/… a neve amelyet meghívunk. A második paraméterrel jelezzük, hogy mit és hogyan fogunk hívni (a fenti példában egy metódust). Következő a sorban a binder paraméter, ezzel beállíthatjuk, hogy az öröklött/túlterhelt tagokat hogyan hívjuk meg. Ezután maga a típus, amin a hívást elvégezzük, végül a hívás paraméterei (ha vannak)).

- 217 -

32

Állománykezelés

Ebben a fejezetben megtanulunk fileokat írni/olvasni és a könyvtárstruktúra állapotának lekérdezését illetve módosítását.

32.1 Olvasás/írás fileból/fileba
A .NET számos osztályt biztosít számunkra, amelyekkel fileokat tudunk kezelni, ebben a fejezetben a leggyakrabban használtakkal ismerkedünk meg. Kezdjük egy file megnyitásával és tartalmának a képernyőre írásával. Legyen pl. a szöveges file tartalma a következő:
alma körte dió csákány pénz könyv

A program pedig:
using System; using System.IO; class Program { static public void Main() { 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(); } }

Az IO osztályok a System.IO névtérben vannak. A C# ún. stream–eket, adatfolyamokat használ az IO műveletek végzéséhez. Az első sorban megnyitottunk egy ilyen folyamot és azt is megmondtuk, hogy mit akarunk tenni vele. A FileStream konstruktorának első paramétere a file neve (ha nem talál ilyen nevű filet, akkor kivételt dob a program). Amennyiben nem adunk meg teljes elérési utat, akkor automatikusan a saját könyvtárában fog keresni a program. Ha külső könyvtárból szeretnénk megnyitni az állományt, akkor azt a következőképpen tehetjük meg: - 218 -

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

Azért használunk dupla backslash–t (\), mert ez egy ún. escape karakter, önmagában nem lehetne használni (persze ez nem azt jelenti, hogy minden ilyen karaktert kettőzve kellene írni, minden speciális karakter előtt a backslash–t kell használnunk). Egy másik lehetőség, hogy az „at” jelet (@) használjuk az elérési út előtt, ekkor nincs szükség dupla karakterekre, mivel mindent normális karakterként fog értelmezni:
FileStream fs = new FileStream(@'C:\Dir1\Dir2\test.txt', FileMode.Open);

A FileMode enum–nak a következő értékei lehetnek: Create CreateNew Open OpenOrCreate Append Létrehoz egy új filet, ha már létezik a tartalmát kitörli Ugyanaz mint az előző, de ha már létezik a file akkor kivételt dob. Megnyit egy filet, ha nem létezik kivételt dob. Ugyanaz mint az előző, de ha nem létezik akkor létrehozza a filet. Megnyit egy filet, és automatikusan a végére pozícionál. Ha nem létezik, létrehozza. Megnyit egy létező filet és törli a tartalmát. Ebben a módban a file tartalmát nem lehet olvasni (egyébként kivételt dob).

Truncate

A FileStream konstruktorának további két paramétere is lehet, amelyek érdekesek számunkra (tulajdonképpen 15 különböző konstruktora van), mindkettő felsorolt típus. Az első a FileAccess, amellyel beállítjuk, hogy pontosan mit akarunk csinálni az állománnyal: Read Write ReadWrite A fenti példát így is írhattuk volna:
FileStream fs = new FileStream("test.txt", FileMode.Open, FileAccess.Read);

Olvasásra nyitja meg. Írásra nyitja meg. Olvasásra és írásra nyitja meg

Végül a FileShare–rel azt állítjuk be, ahogy más folyamatok férnek hozzá a filehoz: None Read Write ReadWrite Más folyamat nem férhet hozzá a filehoz, amíg azt be nem zárjuk. Más folyamat olvashatja a filet. Más folyamat írhatja a filet. Más folyamat írhatja és olvashatja is a - 219 -

Delete Inheritable

filet. Más folyamat törölhet a fileból (de nem magát a filet). A gyermek processzek is hozzáférhetnek a filehoz.

Ha a fenti programot futtatjuk, akkor előfordulhat, hogy az ékezetes karakterek helyett kérdőjel jelenik meg. Ez azért van, mert az éppen aktuális karaktertábla nem tartalmazza ezeket a karaktereket, ez tipikusan nem magyar nyelvű operációs rendszer esetén fordul elő. A megoldás, hogy kézzel állítjuk be a megfelelő táblát, ezt a StreamReader konstruktorában tehetjük meg (a tábla pedig iso-8859-2 néven szerepel):
StreamReader rs = new StreamReader(fs, Encoding.GetEncoding("iso-8859-2"), false);

Ehhez szükség lesz még a System.Text névtérre is. Most írjunk is a fileba. Erre a feladatra a StreamReader helyett a StreamWriter osztályt fogjuk használni:
using System; using System.IO; class Program { static public void Main() { 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(); } }

Házi feladat következik: módosítsuk a programot, hogy ne dobja el az eredeti tartalmát a filenak, és az írás után azonnal írjuk ki a képernyőre az új tartalmát. Az Environment.NewLine egy újsor karaktert ad vissza. Bináris fileok kezeléséhez a BinaryReader/BinaryWriter osztályokat használhatjuk:

- 220 -

using System; using System.IO; class Program { static public void Main() { 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(); } }

Készítettünk egy bináris filet, és beleírtuk a számokat egytől százig. Ezután megnyitottuk és elkezdtük kiolvasni a tartalmát. A PeekChar metódus a soron következő karaktert (byte–ot) adja vissza, illetve -1–et, ha elértük a file végét. A folyambeli aktuális pozíciót nem változtatja meg. A cikluson belül van a trükkös rész. A ReadTípusnév metódus a megfelelő típusú adatot adja vissza, de vigyázni kell vele, mert ha nem megfelelő méretű a beolvasandó adat, akkor hibázni fog. A fenti példában, ha a ReadString metódust használtuk volna, akkor kivétel (EndOfStreamException) keletkezik, mivel a kettő nem ugyanakkora mennyiségű adatot olvas be. Az egész számokat kezelő metódus nyílván működni fog, hiszen tudjuk, hogy számokat írtunk ki. Eddig kézzel zártuk le a stream-eket, de ez nem olyan biztonságos, mivel gyakran elfelejtkezik róla az ember. Használhatjuk ehelyett a using blokkokat, amelyek ezt automatikusan megteszik. Fent például írhatnánk ezt is:
using(BinaryWriter bw = new BinaryWriter(File.Create("file.bin"))) { for(int i = 0;i < 100;++i) { bw.Write(i); } }

32.2 Könyvtárstruktúra kezelése
A fileok mellett a .NET a könyvtárstruktúra kezelését is széleskörűen támogatja. A System.IO névtér ebből a szempontból két részre oszlik: információs- és operációs - 221 -

készült: {1}". } foreach(string s in Directory. Ugyanazzal a metódussal írjuk ki az információkat kihasználva azt.FullName.GetDirectories("C:\\")) { Console.) végeznek a filerendszeren. de a fileokra is kíváncsiak lehetünk. Előbbiek (ahogyan a nevük is sugallja) információt szolgáltatnak. di.IO. using System. fi. törlése. fi.FullName.GetFiles("C:\\")) { PrintFileSystemInfo(new FileInfo(s)). } else { FileInfo fi = fsi as FileInfo. Console. } } } Először a mappákon.eszközökre.WriteLine("Könyvtár: {0}. class Program { static public void Main() { foreach(string s in Directory. hogy a DirectoryInfo és a FileInfo is egy közös őstől a FileSystemInfo–ból származik (mindkettő konstruktora a vizsgált alany elérési . using System.CreationTime). class Program { static public void PrintFileSystemInfo(FileSystemInfo fsi) { if((fsi.WriteLine(s). } } } Természetesen nem csak a könyvtárakra. Első példánkban írjuk ki mondjuk a C meghajtó gyökerének könyvtárait: using System.WriteLine("File: {0}.CreationTime).GetDirectories("C:\\")) { PrintFileSystemInfo(new DirectoryInfo(s)). A programunk módosított változata némi plusz információval együtt ezeket is kiírja nekünk: using System.Attributes & FileAttributes. stb.222 - . di. míg az utóbbiak (többségükben statikus metódusok) bizonyos műveleteket (új könyvtár létrehozása. majd a fileokon megyünk végig. Készült: {1}".Directory) != 0) { DirectoryInfo di = fsi as DirectoryInfo. Console.IO. } } static public void Main() { foreach(string s in Directory.

Mire is jók ezek a folyamok? Gyakran van szükségünk arra. mivel lehetőséget ad közvetlenül fileba írni a tartalmát. // ha nem létezik a file if(fi. A MemoryStream osztály jelentősen megkönnyíti a dolgunkat. sw.txt". utóbbi memóriaigényes.Close(). string filePath = dirPath + "\\file.WriteLine("Dio"). amellyel memóriabeli adatfolyamokat írhatunk/olvashatunk. A következő program erre mutat példát: . // ha nem létezik a könyvtár if(Directory. előbbi rugalmatlan. hogy ez miért és hogyan működik. ezért a legegyszerűbb.CreateDirectory(dirPath).IO. class Program { static public void Main() { string dirPath = "C:\\test".NET a MemoryStream osztályt biztosítja számunkra. most megtanuljuk módosítani is a A FileInfo CreateText metódusa egy StreamWriter objektumot ad vissza amellyel írhatunk egy szöveges fileba.223 - . sw. így a metódusban csak meg kell vizsgálni.WriteLine("Alma"). hogy összegyűjtsünk nagy mennyiségű adatot. hogy éppen melyikkel van dolgunk és átkonvertálni a megfelelő típusra. annak meggondolása az olvasó feladata. } } } le.útját várja paraméterként). 32. ha közvetlenül a memóriában tároljuk el az adatokat. A vizsgálatnál egy „bitenkénti és” műveletet hajtottunk végre. Eddig csak információkat kértünk könyvtárstruktúrát: using System.Exists == false) { // akkor elkészítjük és írunk bele StreamWriter sw = fi.CreateText(). Nyílván egy tömb vagy egy lista nem nyújtja a megfelelő szolgáltatásokat. using System.3 In–memory streamek A . } FileInfo fi = new FileInfo(filePath).Exists(dirPath) == false) { // akkor elkészítjük Directory. amelyeket majd a folyamat végén ki akarunk írni a merevlemezre. sw.

sw.Close().txt").using System.Close(). .WriteLine(i). Minden XML dokumentum egyetlen gyökérelemmel rendelkezik (ez a fenti példában a <list>).Create("inmem. } sw. for(int i = 0. Minden elemnek rendelkeznie kell nyitó (<list>) és záró (</list>) tagekkel. Üres elemeknél ezeket eg yszerre is deklarálhatjuk (<üres />).++i) { sw. amelynek elsődleges célja strukturált szöveg és információ megosztása az interneten keresztül. amelynek minden más elem a gyermekeleme (egyúttal minden nem-gyökérelem egy másik elem gyermeke kell legyen. } } A MemoryStream–re „ráállítottunk” egy StreamWriter objektumot. Lássunk egy példát: <?xml version="1. Miután minden adatot a memóriába írtunk a Flush metódussal (amely egyúttal kiüríti a StreamWriter–t is) létrehoztunk egy filet és a MemoryStream WriteTo metódusával kiírtuk belé az adatokat. mstream. ez nem feltétlenül jelenti a gyökérelemet). 32. FileStream fs = File. mstream. Az egyes elemek tárolhatnak attribútumokat a következő formában: <item value="10" /> <item value="10"></item> Itt mindkét forma legális.i < 1000.0" encoding="UTF-8" ?> <list> <item>1</item> <item>2</item> <item>3</item> </list> Az első sor megmondja.4 XM L Az XML általános célú leírónyelv.IO.224 - .Flush(). hogy melyik verziót és milyen kódolással akarjuk használni.WriteTo(fs). fs. StreamWriter sw = new StreamWriter(mstream). class Program { static public void Main() { MemoryStream mstream = new MemoryStream(). using System. ezután következnek az adatok.Close().

NodeType) { case XmlNodeType. ez egy streamtől kezdve egy szimpla filenévig mindent elfogad: XmlReader reader = XmlReader. reader.Name).WriteLine("<{0}>".Value). reader. hogy az XmlReader egy absztrakt osztály tudjuk példányosítani. inmemory adatszerkezetek) mind online (SOAP alapú információcsere.xml". amely az XmlReader egy belső. break.Name).Formatting = Formatting. case XmlNodeType. }.) téren. etc. a példában a legalapvetőbb típusokat vizsgáltuk és ezek függvényében írtuk ki a file tartalmát. writer.Read()) { switch (reader.. mind offline (konfigurációs fileok.Create("test.UTF8). Függetlenül attól. A számunkra szükséges osztályok a System. mert ilyenkor valójában egy XmlTextReaderImpl típusú objektum jön létre. Miután tehát megnyitottuk a filet.xml"). ..WriteLine("</{0}>".Xml névtérben vannak.EndElement: Console. Encoding.Now. A megnyitáshoz az XmlReader egy statikus metódusát a Create–et fogjuk használni. default: break. writer. Ezeknek az absztrakt osztályoknak a segítségével hajthatóak végre a szokásos – irás/olvasás – műveletek. a két legalapvetőbb ilyen osztály az XmlReader és az XmlWriter.WriteStartDocument().A .Text: Console. az XmlTextWriter–t. internal elérésű leszármazottja (tehát közvetlenül nem tudnánk példányosítani).Indented.ToString()). A következő példában használni fogjuk az XmlWriter osztály egy leszármazottját.NET Framework erősen épít az XML–re. case XmlNodeType. hogyan tudunk beolvasni egy XML filet. végig tudunk iterálni rajta: while (reader. break.WriteLine(reader.Element: Console. mégpedig azért. az Xml fileoktól megszokott hierarchikus szerkezetet fogja megteremteni (ennek az értékét is beállíthatjuk). writer. break. } Az XmlReader NodeType tulajdonsága egy XmlNodeType felsorolás egy tagját adja vissza. A Formatting tulajdonság. Először nézzük meg.WriteComment(DateTime.225 - . ugyanis a filet kódból fogjuk létrehozni: XmlTextWriter writer = new XmlTextWriter("newxml.

writer. A file tartalma most a következő: <?xml version="1.226 - .MoveToAttribute("Note"). Első paraméter az attribútum neve. "List of persons"). writer.MoveToContent(). 20:05:24--> <PersonsList Note="List of persons"> <Person> <Name>Reiter Istvan</Name> <Age>22</Age> </Person> </PersonsList> Az XmlReader rendelkezik néhány MoveTo előtaggal rendelkező metódussal.10. kulcs-érték párokkal.WriteStartElement("PersonsList").WriteElementString("Name". a WriteElementString pedig feltölti azt értékekkel. amely megfelel egy feltételnek. és a visszatérési érték hamis lesz.0" encoding="utf-8"?> <!--2008.10.0" encoding="utf-8"?> <!--2010. 20:02:05--> <PersonsList> <Person> <Name>Reiter Istvan</Name> <Age>24</Age> </Person> </PersonsList> Az egyes tagekhez attribútumokat is rendelhetünk: writer.WriteAttributeString("Note". és igaz értékkel tér vissza. utána pedig a hozzá rendelt érték jön. ezekkel a file egy olyan pontjára tudunk navigálni.08.17. writer. . reader. Ekkor a file így alakul: <?xml version="1. A file: <?xml version="1.20. és beszúrtunk egy kommentet is. ha létezik az attribútum.Elkezdtük az adatok feltöltését. "Reiter Istvan").WriteEndElement(). Ez a metódus a paramétereként megadott attribútumra állítja a reader–t. Ellenkező esetben a pozíció nem változik. A StartElement egy új „tagcsoportot” kezd a paramétereként megadott névvel. 12:04:21--> A file személyek adatait fogja tartalmazni: writer. "22").17. Néhány példa: bool l = reader.0" encoding="utf-8"?> <!--2008.WriteStartElement("Person"). writer.WriteElementString("Age".

hogy van–e a csomóponton attribútum.Name. Egy XML file egyes csomópontjait egy-egy XmlNode objektum fogja jelképezni. amelyen a reader áll (értelemszerűen következik. Miután végeztünk. amely átugorja egy csomópont gyermekeit és a következő azonos szinte n lévő csomópontra ugrik. stream vagy egyszerű filenév. } Végül. Console. } } A HasAttribute metódussal megtudakoljuk. Említést érdemelnek még a MoveToFirstAttribute és a MovetoNextAttribute metódusok. Ahogy azt már megszokhattuk az XmlDocument számos forrásból táplálkozhat. } reader. A MoveToElement metódus visszalép arra a csomópontra. amely tartalmaz adatot is.MoveToAttribute(i).Ez a metódus a legközelebbi olyan csomópontra ugrik.Read()) { if (reader. 32.Value).5 XM L DOM Eddig az XML állományokat hagyományos fileként kezeltük. A forrás betöltését az XmlDocument Load illetve LoadXml metódusaival végezzük: . reader. ++i) { reader. Az XML DOM (Document Object Model) a fileokat a hierarchikus felépítésük szerint kezeli. ekkor az XmlReader (vagy leszármazottainak) Value tulajdonsága ezt az értéket fogja tartalmazni. Az osztály amelyen keresztül elérjük a DOM –ot az az XmlDocument lesz. de nem utolsósorban a Skip metódus maradt.WriteLine("{0} {1}". Módosítsuk egy kicsit az előző példát: for (int i = 0. reader.MoveToElement().227 - . hogy az előző kettő metódust gyakran használjuk együtt): while (reader.MoveToAttribute(i).WriteLine("{0} {1}". reader. amely azt az attribútumot tartalmazza. reader. pl. ++i) { //reader. visszatérünk a kiindulási pontra (hogy a Read metódus tudja végezni a dolgát). reader. amelyből egyébként maga az XmlDocument is származik.AttributeCount.HasAttributes) { for (int i = 0.Value).AttributeCount. ennél azonban van egy némileg kényelmesebb lehetőségünk is.Name. Egy másik fontos osztály. az az XmlNode. amelyek nevükhöz méltóan az első illetve a „következő” attribútumra pozícionálnak.MoveToNextAttribute(). utána pedig az AttributeCount tulajdonság segítségével (amely azoknak a számát adja vissza) végigiterálunk rajtuk. Console. i < reader. i < reader.

Hozzunk létre kódból egy teljesen új dokumentumot: XmlDocument xdoc = new XmlDocument(). első a RemoveChild.Save("domtest.xml").CreateElement("Test"). amely egy XmlNodeList objektumot ad vissza. XmlText text = xdoc.xml").WriteLine(node. Két másik metódusról is megemlékezünk. amely egy létező csúcsot távolít el a csúcsok listájából. "test"). hogy léteznek gyermekei.228 - . ezt a HasChildNodes tulajdonsággal tudjuk ellenőrizni. xdoc. A fenti kód eredménye a következő lesz: <Test>Hello XML DOM!</Test> A CloneNode metódussal már létező csúcsokat „klónozhatunk”: XmlNode source = xdoc. amely felülír egy csúcsot.Name).CloneNode(false). amikor egy új csúcsot adunk hozzá: .CreateNode(XmlNodeType. amelyek segítségével teljes körű felügyeletet nyerhetünk. node. A Save metódussal el tudjuk menteni a dokumentumot. akár filenevet. XmlElement element = xdoc. akár egy streamet megadva. amely igaz értéket tartalmaz. XmlNode node = xdoc. } Az XmlDocument a ChildNodes tulajdonságát az XmlNode osztálytól örökli (ugyanígy a HasChildNodes–t is.ChildNodes) { Console. xdoc. a másik pedig a ReplaceChild. így az egyes XmlNode–okat bejárva a teljes „fát‟ megkaphatjuk. amely jelzi. A következő forráskódban egy XmlDocument első szinten lévő gyermekeit járjuk be: foreach (XmlNode node in xdoc. "test".Load("test. Az XmlDocument eseményeket is szolgáltat. A metódus egyetlen paramétert vár.Element.AppendChild(text). Az XmlText illetve XmlElement osztályok is az XmlNode leszármazottai. Most nézzük meg. Persze nem biztos.CreateTextNode("Hello XML DOM!").AppendChild(element). hogy a csúcs gyermekeit is átmásoljuk–e. hogyan tudjuk manipulálni a file t. XmlNode destination = source. ha léteznek gyermek-elemek). Kezeljük le például azt az eseményt.XmlDocument xdoc = new XmlDocument(). Egy XmlDocument bejárható foreach ciklussal a ChildNodes tulajdonságon keresztül.

Most már eleget tudunk ahhoz.53125+02:00</dateTime> Az XmlSerializer a System.xml". mégpedig az.Value). hogy több lehetőségünk is van a szerializációra. XmlSerializer ser = new XmlSerializer(typeof(DateTime)). Ezután létrejön egy XML file. A deszérializálás hasonlóképpen működik: FileStream fstream = new FileStream("serxml. Egy nagyon fontos dolgot kell megjegyeznünk. hogy milyen objektumról van szó). . amelyből vissza tudjuk alakíta ni az adatainkat. XmlSerializer ser = new XmlSerializer(typeof(DateTime)). szérializáljunk egy hagyományos beépített objektumot: FileStream fstream = new FileStream("serxml.6 XM L szerializáció Mi is az a szérializálás? A legegyszerűbben egy példán keresztül lehet megérteni a fogalmat.Now). bináris. hogy a memóriabeli objektumainkat egy olyan szekvenciális formátumba (pl. amelyet szérializálva kiírhatunk XML formátumba és onnan vissza is olvashatjuk (ezt deszérializálásnak hívják). Egy hátulütő mégis van. hiszen ezt más környezetben is felhasználhatjuk.XmlNodeChangedEventHandler handler = null. 32. Az elért pontszám mellé jön a játékos neve és a teljesítés ideje is. hogy ez egy elég bonyolult feladat: figyelni kell a beolvasott adatok típusára.Serialization névtérben található.Create). Tulajdonképpen a szérializálás annyit tesz. }.229 - . ha készítünk egy megfelelő osztályt. ezek közül az XML a legáltalánosabb.Open). xdoc. Kezdjük egy egyszerű példával (hamarosan megvalósítjuk a pontszámos példát is). benne a szérializált objektummal: <?xml version="1. Ez elsőre semmi különös. vagy most XML) konvertáljuk. Természetesen a szérializálás jelentősége ennél sokkal nagyobb és nemcsak XML segítségével tehetjük meg. ha a kiírt adatokat meg tudnánk feleltetni egy osztálynak? Ez megtehetjük.Xml. hogy készítsünk egy szérializálható osztályt. ser. az osztálynak csakis a publikus tagjai szérializálhatóak a private vagy protected elérésűek automatikusan kimaradnak (emellett az osztálynak magának is publikus elérésűnek kell lennie).Node. handler = (sender. Már említettük. formátumára.WriteLine(e.Serialize(fstream. hiszen simán kiírhatjuk az adatokat egy fileba. FileMode.0"?> <dateTime>2008-10-23T16:19:44. DateTime. és a játékosok pontszámát szeretnénk eltárolni. képzeljük el.NodeInserting += handler. hiszen ott még nem tudja. DateTime deser = (DateTime)ser. majd visszaolvashatjuk onnan. FileMode. stb… Nem lenne egyszerűbb.Deserialize(fstream). hogy írtunk egy játékot. e) => { Console. Ezeken kívül még szükség lesz egy alapértelmezett konstruktorra is (a deszérializáláshoz.xml".

attribútum is).Score = 1000. [XmlElement("PlayerName")] public string PlayerName { get { return playername.PlayerName = "Player1". } } } Az egyes tulajdonságoknál beállítottuk. } } private DateTime date.Serialize(fstream. ser.Create).734375+02:00</Date> </ScoreObject> . És az eredmény: <?xml version="1.org/2001/XMLSchema"> <PlayerName>Player1</PlayerName> <Score>1000</Score> <Date>2008-10-23T16:34:32.Date = DateTime.Now. } set { score = value. [XmlElement("Date")] public DateTime Date { get { return date.org/2001/XMLSchema-instance" xmlns:xsd="http://www. XmlSerializer ser = new XmlSerializer(typeof(ScoreObject)).xml".230 - .public class ScoreObject { public ScoreObject() { } private string playername. hogy az miként jelenjen meg a fileban (sima element helyett lehet pl. so. FileMode. FileStream fstream = new FileStream("scores. de így jobban kezelhető. } } private int score. } set { playername = value. Rendben. [XmlElement("Score")] public int Score { get { return score. } set { date = value.w3. most szérializáljuk: ScoreObject so = new ScoreObject(). so. so). so.w3.0"?> <ScoreObject xmlns:xsi="http://www. Enélkül is lefordulna és működne.

de a . amikor egy programot írtunk minden apró változtatásnál újra le kellett fordítani. még akkor is.config formában használandó (vagyis a main. Az AppSettings indexelője minden esetben stringet ad vissza. Természetesen ezt megtehetjük úgy is. hogy az AppSettings rendelkezik indexelővel. using System.AppSettings["1"]. Console. ha maga a program nem.exe. hogy egy sima szöveges filet készítünk. és azt olvassuk/írjuk.33 Konfigurációs file használata Eddig. Sokkal kényelmesebb lenne a fejlesztés és a kész program használata is. csak a használt adatok változtak.WriteLine(s). automatikusan felismeri a fordítóprogram.cs forráshoz – ha nem adunk meg mást – a main. A file ugyan megvan. akkor konvertálni kell. Ezek tulajdonképpen XML dokumentumok amelyek a következőképpen épülnek fel: <?xml version="1. tehát ha más típussal akarunk dolgozni. adatbázisok eléréséhez) és mi magunk is készíthetünk ilyet (erről hamarosan). vannak speciális szekciók (pl. amely visszaadja a megfelelő értéket a megadott kulcshoz. Írjunk is egy egyszerű programot: using System. A konfig file neve mindig alkalmazásneve.NET ezt is megoldja helyettünk a konfigurációs fileok bevezetésével.AppSettings –szel is.Configuration.231 - .0" encoding="utf-8"?> <configuration> <appSettings> <add key="1" value="alma" /> <add key="2" value="korte" /> </appSettings> </configuration> Az első sor egy szabványos XML file fejléce. Vezessünk be a konfig-fileba egy új kulcsérték párost: . de még nem tudjuk. Ugyanezt elérhetjük a ConfigurationSettings. ha a „külső” adatokat egy külön fileban tárolnánk. ahová már be is raktunk néhány adatot.config elnevezést kell használnuk). Az appSettings az általános adatok tárolására szolgál. hogy hogyan használjuk fel azt a programunkban. vele nem kell foglalkoznunk. de az már elavult – erre a fordító is figyelmeztet – csak a korábbi verziók kompatibilitása miatt van még benne a frameworkben.exe. // ez kell class Program { static public void Main() { string s = ConfigurationManager. hogy van ilyenünk is. ekkor a fordításnál ezt nem kell külön jelölni. Sokkal érdekesebb viszont az appSettings szekció. // alma } } Látható.

using System. } set { this["fileName"] = value. public class AppDataSection : ConfigurationSection { private static AppDataSection settings = ConfigurationManager.settings. Console. using System.<add key="3" value="42" /> És használjuk is fel a forrásban: using System. amely egyrészt típusbiztos másrészt a konfigurációs fileban is megjelenik. de hosszabb programok esetében nem kényelmes mindig figyelni a konverziókra. } } 33. class Program { static public void Main() { int x = int. IsRequired=true)] public string FileName { get { return this["fileName"] as string.WriteLine(x). amely a következőt tartlamazza: using System. IsRequired=true)] public string EncodeType .AppSettings["3"]). public static AppDataSection Settings { get { return AppDataSection.GetSection("AppDataSettings") as AppDataSection. Épp ezért készíthetünk olyan osztályt is. } } [ConfigurationProperty("encodeType".cs néven.1 Konfiguráció-szekció készítése Egyszerűbb feladatokhoz megteszi a fenti módszer is. } } [ConfigurationProperty("fileName".Parse(ConfigurationManager.232 - . amely konfigurációs fileból kiolvas egy filenevet és a kódtáblát és ezek alapján kiírja a file tartalmát. A következő példában elkészítünk egy programot.Configuration.Configuration. Első lépésként készítsünk egy új forrásfilet AppDataSection.

Settings. false)) { string s = sr. hogy muszáj megadnunk ezeket az értékeket (a DefaultValue segítségével kezdőértéket is megadhatunk).ReadLine(). System. A tulajdonságok attribútumával beállítottuk az konfig-fileban szereplő nevet illetve.GetEncoding( AppDataSection. using(FileStream fs = new FileStream(filename. Most jöjjön az osztály. System.Configuration. Itt arra kell figyelni.IO.Settings. while(s != null) { Console. ez a mi esetünkben a main lesz. System. } } .ReadLine().{ get { return this["encodeType"] as string. public class DataHandlerClass { public void PrintData() { Encoding enc = Encoding.EncodeType).cs nevű filet is: using using using using System.Text.233 - . készítsünk egy DataHandlerClass. } } } Az osztályt a ConfigurationSection osztályból származtattuk. hogy az AppSettings–szel ellentétben ő object típussal tér vissza. enc.0" encoding="utf-8"?> <configuration> <configSections> <section name="AppDataSettings" type="AppDataSection. A Settings property segítségével az osztályon keresztül egyúttal hozzáférünk az osztály egy példányához is.WriteLine(s). s = sr. illetve azt az assemblyt amelyben a típus szerepel.FileName. amely használja az új szekciót. string filename = AppDataSection. vagyis muszáj konverziót végrehajtani.txt" encodeType="iso-8859-2" /> </configuration> A type tulajdonságnál meg kell adnunk a típus teljes elérési útját névtérrel együtt (ha van). main" /> </configSections> <AppDataSettings fileName="file.Open)) { using(StreamReader sr = new StreamReader(fs. FileMode. így soha nem kell példányosítanunk azt. ennek az indexelőjét használjuk. Most jön a konfig-file itt regisztrálnunk kell az új szekciót: <?xml version="1.

using System.} } } Most már könnyen használhatjuk az osztályt: using System.cs .cs AppDataSection.PrintData(). handler.234 - . class Program { static public void Main() { DataHandlerClass handler = new DataHandlerClass(). } } A fileokat a fordítóprogram után egymás után írva fordíthatjuk le: csc main.Configuration.cs DataHandlerClass.

Ebben a fejezetben ebből a listából szemezgetünk. Az 1024 és 49151 közötti portokat regisztrált portoknak nevezik. ha több hálózati hardvert használ).34 Hálózati programozás A . ezeken olyan széleskörben elterjedt szolgáltatások futnak amelyek a legtöbb rendszeren megtaláhatóak (operációs rendszertől függetlenül). programfejlesztés alatt célszerű ezeket használni. a böngészők a HTTP protokollt a 80–as porton érik el. Pl. amely „ráül” egy adott IP címre. ezt a tartományt nem lehet lefoglalni. ami értelemszerűen Windows rendszer alatt fut.: 123. Mielőtt nekiállunk a Socket osztály megismerésének. Ez az a tartomány amit az IANA kezel. A lista a legegyszerűbb hobbiszintű programokban használható szerver-kliens osztályoktól egészen a legalacsonyabb szintű osztályokig terjed. hogy ne legyen semmilyen ütközés a termék használatakor. játsszunk egy kicsit az IP címekkel! Azt tudjuk már. Mi az az IP cím? Az Internet az ún. A portszám egy 16 bites előjel nélküli szám 1 és 65535 között. . míg a portszámmal egy szobaszámra hivatkozunk. 34. ezért feltalálták a domainnév vagy tartománynév intézményét.255. amelyek által egyértelműen azonosíthatóak és elérhetőek: ezek az IP cím és a port szám. ezeken már olyan szolgáltatásokat is felfedezhetünk amelyek operációs rendszerhez (is) kötöttek pl.NET támogatja az IPv4 és IPv6 verziókat is)). igaz ez egyelőre kevésbé elterjedt (a . Ugyanitt megtalálunk szoftverhez kötött portot is. az 1433 a Microsoft SQL Server portja. a 23 a Telnet.235 - . A fejezethez tartozó forráskódok megtalálhatóak a jegyzethez tartozó Sources\Network könyvtárban. de a legtöbb számítógépen ezekkel az értékekkel találkozunk). Egy IP cím egy 32 bites egész szám. de számokat viszonylag nehéz megjegye zni. hogy a hálózaton minden számítógép saját címmel rendelkezik. vagyis ahelyett. vagyis ezáltal biztosítják a nagyobb szoftvergyártók. Internet Protocol (IP) szabványa szerint működik. pl a World of Warcraft a 3724 portot használja. A portszámok hivatalos regisztrációját az Internet Assigned Numbers Authority (IANA) végzi. A portszámokat ki lehet „sajátítani”. míg a 25 az SMTP szerver portszáma (ezektől el lehet – és biztonsági okokból a nagyobb cégek el is szoktak – térni.1 Socket A socketek számítógépes hálózatok (pl. Eszerint a hálózaton lévő minden számítógép egyedi azonosítóval – IP címmel rendelkezik (ugyanakkor egy számítógép több címmel is rendelkezhet. Az IP címet tekinthetjük az ország/város/utca/házszám négyesnek.45).NET meglehetősen széles eszköztárral rendelkezik hálózati kommunikáció kialakításához. ezáltal az egyes szekciók legmagasabb értéke 255 lesz (ez a jellemző az IP negyedik generációjára vonatkozik. Az efelettieket dinamikus vagy privát portoknak nevezik. Az 1 és 1023 portokat ún. Minden socket rendelkezik két adattal. az Internet) közötti kommunikációs végpontok. amelyet 8 bites részekre osztunk (pl. az új hatos generáció már 128 bites címeket használ.0. well-knowed portként ismerjük.

} } Ezt a programot így futtathatjuk: . foreach(IPAddress ip in host1.microsoft.GetHostEntry("www. Ez az ip cím lesz az. A legegyszerűbb módon fogjuk csinálni a beépített TcpListener osztállyal.exe 127. a parancssorba beírt ipconfig paranccsal tudhatjuk meg).Net. TcpListener server = new TcpListener(endPoint). amely a TCP/IP protokollt használja (erről hamarosan részletesebben). ez az ún.microsoft. Nézzük meg a forrást: using System.Start(). } } Most már ideje mélyebb vizek felé venni az irányt.0. // ez kell class Program { static public void Main() { IPHostEntry host1 = Dns.ToString()).22.1 egy speciális cím.GetHostEntry("91. localhost vagyis a „helyi” cím.120.WriteLine(host2.WriteLine(ip. Console.150"). using System. hogy www. IPEndPoint endPoint = new IPEndPoint(ip.Parse(args[0]).\Server. public class Server { static public void Main(string[] args) { IPAddress ip = IPAddress.250 írhatjuk azt. .1 50000 A 127. using System. server.com").hogy 65.0. ahol a szerver bejövő kapcsolatokra fog várni (érelemszerűen ehhez az IP címhez csak a saját számítógépünkről tudunk kapcsolódni.55.236 - .0. Console.AddressList) { Console.com. } IPHostEntry host2 = Dns.WriteLine("A szerver elindult!"). using System. A következő programunkban lekérdezzük egy domain-név IP címét és fordítva. int port = int.HostName). port). Ehhez a System. amely jelen esetben a saját számítógépünk (ehhez Internet kapcsolat sem szükséges mindig rendelkezésre áll).Net.Net névtér osztályai lesznek segítségünkre: using System.21.0. ha egy távoli gépet is szeretnénk bevonni. elkészítjük az első szerverünket. akkor szükség lesz a „valódi” IP –re. ezt pl.Sockets.Parse(args[1]).Net.

Előbbit az IPAddress. szintén egy cím-port kettősre lesz szükségünk (de most nem kell végpontot definiálnunk): using System. } catch(Exception e) { Console. ehhez viszont szükségünk lesz egy kliensre is.Message).Sockets. ahol figyelheti a bejövő kapcsolatokat. server = new TcpListener(endPoint). portNum).Stop(). using System. Console. try { IPAddress ipAddr = IPAddress.using System.Net. IPEndPoint endPoint = new IPEndPoint(ipAddr.Parse statikus metódussal egy karaktersorozatból nyertük ki. Console. A következő lépésben bejövő kapcsolatot fogadunk.WriteLine(e.WriteLine(e. őt a TcpClient osztállyal készítjük el. } .Message).Parse(args[1])).Net. int portNum = int. public class Server { static public void Main(string[] args) { TcpListener server = null. ehhez szükségünk lesz egy IP címre és egy portszámra.Parse(args[0]).Parse(args[1]). amely hasonlóan működik.Net.Start(). using System.WriteLine("A szerver leállt!").237 - . class Program { static public void Main(string[] args) { TcpClient client = null.Net. } finally { server. int. } catch(Exception e) { Console. server. } } } A TcpListener konstruktorában meg kell adnunk egy végpontot a szervernek.WriteLine("A szerver elindult!"). using System.Sockets. using System. mint a párja. try { client = new TcpClient(args[0].

} } } . data. Nézzük a módosított klienst: using using using using System. de nem csinál túl sok mindent.Close().WriteLine(e.Length). try { client = new TcpClient(args[0]. NetworkStream stream = null.finally { client. Encoding. Ahhoz.Net.238 - . hogy a szerver fogadni tudja a kliensünket meg kell mondanunk neki. length)).Write(data. int length = stream.AcceptTcpClient(). int.ASCII.Parse(args[1])). System. client. Console. vagy meghívjuk az alapértelmezett konstruktort és a Connect metódussal elhalasztjuk a kapcsolódást egy későbbi időpontra (természetesen ekkor neki is meg kell adni a szerver adatait).Close(). A programunk jól működik. System.GetString(data. Persze szerveroldalon egy TcpClient objektumra lesz szükségünk.Read(data. byte[] data = Encoding.Message). stream = client. hogy várjon amíg bejövő kapcsolat érkezik. data = new byte[256].GetBytes("Hello szerver!"). A következő lépésben adatokat küldünk oda-vissza. ezt a TcpListener osztály AcceptTcpClient metódusával tehetjük meg: TcpClient client = server. 0.Close().ASCII. class Program { static public void Main(string[] args) { TcpClient client = null.GetStream(). stream. System.Length). 0. } finally { stream.Sockets.Text.WriteLine("A szerver üzenete: {0}".Net. } catch(Exception e) { Console. } } } A TcpClient –nek vagy a konstruktorban rögtön megadjuk az elérni kívánt szerver nevét és portját (és ekkor azonnal csatlakozik is). ezt adja vissza a metódus. data. 0.

Parse(args[0]).Receive(data).GetBytes("Hello kliens!").Stream. eljött az ideje. } } } . } catch(Exception e) { Console. data.Send(data. byte[] data = new byte[256].None). System. 0. mivel a TCP/IP byet-ok sorozatát továbbítja. using using using using using System.Message). length)). IPEndPoint endPoint = new IPEndPoint( IPAddress. client = server.WriteLine("A kliens üzenete: {0}".Text. class Program { static public void Main(string[] args) { Socket server = null. amit a filkezeléssel foglalkozó fejezetben megismertünk. try { server = new Socket( AddressFamily.Parse(args[1])). mind a szerver ugyanazt a kódolást kell használja). int. System. ezért számára emészthető formába kell hoznunk az üzenetet.Sockets. client. SocketType.Listen(2). csak éppen az adatok most a hálózaton keresztül „folynak” át.ASCII.WriteLine(e.Close(). } finally { client.IO. de mást is használhatunk a lényeg. data = new byte[256].Close(). System.Length. server.Tcp). Már ismerjük az alapokat. SocketFlags.239 - .Bind(endPoint). Socket client = null. A NetworkStream ugyanolyan adatfolyam. A fenti példában a legegyszerűbb. hogy egy szinttel lejjebb lépjünk a socketek világába. server. int length = client.ASCII.Accept().Net. hogy tudjuk byte-onként küldeni (értelemszerűen mind a kliens. Encoding.InterNetwork. server. ASCII kódolást választottuk. System.GetString(data. Console. ProtocolType. data = Encoding.Net.Az elküldeni kívánt üzenetet byte–tömb formájában kell továbbítanunk.

IPEndPoint endPoint = new IPEndPoint( IPAddress.Length.Receive(data).ASCII.Send(data. data = new byte[256]. } catch(Exception e) { Console. client. mint eddig. System.A Socket osztály konstruktorában megadtuk a címzési módot (InterNetwork.Connect(endPoint).Parse(args[1])). client.ASCII. Encoding.IO. Console. A három paraméter nem független egymástól. ami jelen esetben TCP.Sockets. } finally { client.240 - .Parse(args[0]). ez az IPv4).None). length)). System. csak épp más a metódus neve. byte[] data = new byte[256]. hogy maximum hány bejövő kapcsolat várakozhat. pl.WriteLine("A szerver üzenete: {0}".InterNetwork. Stream típusú socketet csak TCP portokoll felett használhatunk.Tcp). A kliens osztály sem okozhat nagy meglepetést: using using using using using System. } } } . Ez utóbbi paramétere azt jelzi. int. majd a Listen metódussal megmondtuk. ahogyan azt az egyszerűbb változatban is tettük. a socket típusát (Stream.GetBytes("Hello szerver!"). ez egy oda-vissza kommunikációt biztosító kapcsolat lesz) és a használt protokollt.Net. Ezután készítettünk egy IPEndPoint objektumot.Stream. System.Net.Text. 0. class Program { static public void Main(string[] args) { Socket client = null.Close().GetString(data. data. System.Message). Innentől kezdve nagyon ismerős a forráskód. Ezt a végpontot a Bind metódussal kötöttük a sockethez. int length = client.WriteLine(e. try { client = new Socket( AddressFamily. hogy figyelje a bejövő kapcsolatokat. data = Encoding. ProtocolType. SocketFlags. lényegében ugyanazt tesszük. SocketType.

0. hogy van-e még várakozó adat a csatornán (egészen pontosan azoknak a byte-oknak a számát adja vissza amelyeket még nem olvastunk be): while(true) { if(client. 0."). A TcpListerner/TcpClient osztályok a rájuk csatlakoztatott NetworkStream objektum DataAvailable tulajdonságán keresztül tudják vizsgálni. amely jelzi. data. vagyis megvizsgáljuk. mivel most kapcsolódni akarunk. System.Receive(data).ASCII. előre-ellenőrzést (prechecking) fogunk alkalmazni. nem hallgatózni.Read(data.Length).. Encoding. } else { Console. } else { Console.WriteLine("A szerver még nem küldött választ!"). A blokk elkerülésére ún.GetString(data. System. while(!l) { if(stream.A különbséget a Connect metódus jelenti. break. vagy ráérünk később újraellenőrizni.Threading. 0. de a Read/Receive is. hogy jön-e adat vagy sem.. } } Ugyanezt a hatást socketek esetében a Socket osztály Available tulajdonságával érhetjük el.Sleep(200). Encoding. A következő példában a kliens rendszeres időközönként ellenőrzi. Console. addig pedig csinálhatunk mást.Available > 0) { int length = client. 34.GetString(data. length)). } } .Thread.WriteLine("A szerver üzenete: {0}". az Accept/AcceptTcpClient. hogy bizonyos műveletek blokkolták a főszálat és így várakozni kényszerültünk.241 - .DataAvailable) { data = new byte[256]. Ilyen művelet volt pl.Sleep(200).Thread.2 Blokk elkerülése Az eddigi programjaink mind megegyeztek abban. Console.WriteLine("A szerver üzenete: {0}". hogy adott időpillanatban van-e bejövő adat. hogy érkezett-e válasz a szervertől és ha az eredmény negatív akkor foglalkozhat a dolgával: bool l = false. length)).ASCII.Threading. int length = stream.WriteLine("Várunk a szerver válaszára. l = true.

Thread. Itt a tipikus probléma az.GetString(data. data. hog y mire várunk. itt valóban a másodperc milliomod részéről van szó. stream = client. vagyis ha egy másodpercig akarunk várni akkor 1000000–ot kell megadnunk) adja meg azt az időt amíg várunk bejövő kapcsolatra/adatra.Sleep(500). A második paraméterrel azt mondjuk meg. míg a Socket osztálynál a Poll metódus jelenti.ASCII.Itt egy másik módszert választottunk a ciklus kezelésére.Length). } } A Poll metódus első paramétere egy egész szám. Console. length)).Write(data.Threading.Accept(). } else { Console.Threading. data.AcceptTcpClient(). Console. hogy az AcceptTcpClient/Accept teljesen blokkol. hogy várakozik-e bejövő kapcsolat. a TcpListener esetében ezt a Pending. 0. } } A Pending azt az információt osztja meg velünk. amíg bejövő kapcsolatra várakozunk. SelectMode.Sleep(500).WriteLine("A szerver bejövõ kapcsolatra vár!"). amíg nincs kapcsolat (vagyis blokkoljuk a programot). A SelectMode felsorolt típus három taggal rendelkezik: .Length).SelectRead)) { client = server. ha pedig nullát adunk meg akkor használhatjuk pre-checking–re is a metódust. Amennyiben az első paraméter nega tív szám.Pending()) { client = server.WriteLine("A kliens üzenete: {0}". Most nézzük meg.GetBytes("Hello kliens!").. amely mikromásodpercben (nem milli-.WriteLine("Most valami mást csinálunk"). data = Encoding. hogy mi a helyzet szerveroldalon. byte[] data = new byte[256]. stream.GetStream(). Erre is van persze megoldás.WriteLine("Kliens kapcsolódott. 0. 0. akkor addig várunk.ASCII. Encoding.. Nézzük elsőként a TcpListener –t: while(true) { if(server. /* itt pedig kommunikálunk a klienssel */ } else { Console..242 - ."). Tulajdonképpen ez a metódus a következő forrásban szereplő (a Socket osztályhoz tartozó) Poll metódust használja: while(true) { if(server.Read(data. int length = stream. System. System.Thread.Poll(0.

ami nagyon kényelmes volt. ha a Connect metódus híváa sikeres volt. olvashatóak). IList checkWrite. ha a Socket objektum Blocking tulajdonságát false értékre állítjuk. 3. ha a Connect metódus hívása sikertelen volt. és várakozik bejövő kapcsolat vagy van bejövő adat illetve ha a kapcsolat megszűnt. Ekkor a Socket osztály Receive és Send metódusainak megadhatunk egy SocketError típusú (out) paramétert. hogy ez és a fenti Poll metódust használó megoldások nem hatékonyak.3 Több kliens kezelése Az eddigi programjainkban csak egy klienst kezeltünk. A Select (statikus) metódus szignatúrája a következőképpen néz ki: public static void Select( IList checkRead. ha a metódushívás blokkot okozna (vagyis így újra próbálhatjuk küldeni/fogadni később az adatokat). mivel egy sor dolgot figyelmen kívül hagyhattunk: 1. 34. SelectWrite: igaz értéket kapunk vissza. amelynek segítségével meghatározhatjuk egy vagy több Socket példány állapotát. 2. illetve ha lehetséges adat küldése. amíg a bejelentkezett kliensekkel foglalkozunk. amely WouldBlock értékkel tér vissza. int microSeconds ) . A kliensekre mutató „referencia” tárolása 2. Egy másik lehetőség a blokk feloldására. Azt azonban nem árt tudni.243 - . Minden kliens zavartalanul (lehetőleg blokk nélkül) tudjon kommunikálni a szerverrel és viszont. amelyek megfelelnek bizonyos követelményeknek (írhatóak. hogy egy listából kiválaszthatjuk azokat az elemeket. SelectRead: a metódus igaz értékkel tér vissza. ha meghívtuk a Listen metódust. 34. Lényegében arról van szó.1 Select Az első versenyzőnk a Socket osztály Select metódusa lesz. A következőkben háromféleképpen fogjuk körüljárni a problémát. A következő fejezetben több kliens egyidejű kezelésével egy sokkal hatékonyabb módszer(eke)t fogunk megvizsgálni. 3. minden más esetben a visszatérési érték false lesz. mivel folyamatos metódushívásokat kell végrehajtanunk (vagy a há ttérben a rendszernek).3. A szerver akkor is tudjon kapcsolatot fogadni. IList checkError. azaz csatlakoztunk a távoli állomáshoz.1. SelectError: true értéket ad vissza.

null. while(true) { if(r. A ciklus minden iterációjában meghívja a Select metódust. Socket. Az első paraméternek megadott listát olvashatóságra. Az utolsó paraméterrel azt adjuk meg.Thread. meghatározzuk vele.Accept().Az első három paraméter olyan IList interfészt implementáló gyűjtemények (lényegében az összes beépített gyűjtemény ilyen beleértve a sima tömböket is) amelyek Socket példányokat tartalmaznak.Sleep(500). } } A MAXCLIENT változó egy egyszerű egész s zám.GetBytes("Hello szerver!"). while(i < MAXCLIENT) { Socket client = server.CommunicateWithClient(client). ++i. hogy folyamatosan üzenetet küldünk a szervernek (most csak egyirányú lesz a kommunikáció): Random r = new Random().ASCII.Length. clientList. hogy maximum hány klienst fogunk kezelni. majd a feltételnek megfelelő listaelemeket megtartja a listában a többit eltávolítja (vagyis ha szükségünk van a többi elemre is akkor célszerű egy másolatot használni az eredeti lista helyett). vagyis kiválasztjuk. Készítsünk egy Select–et használó kliens-szerver alkalmazást! A kliens oldalon lényegében semmit nem változtatunk azt leszámítva.Send(data. Miután megfelelő számú kapcsolatot hoztunk létre elkezdünk „beszélgetni” a kliensekkel.Select(copyList.Threading.Next(1000) % 2 == 0) { byte[] data = new byte[256]. } while(true) { ArrayList copyList = new ArrayList(clientList). null. foreach(Socket client in clientList) { Program.Add(client). hogy mennyi ideig várjunk a kliensek válaszára (mikroszekundum). míg a harmadikat hibákra ellenőrzi a Select.244 - .None). data = Encoding. } Most nézzük a szervert: int i = 0. SocketFlags. A CommunicateWithClient statikus metódus a már ismert módon olvassa a kliens üzenetét: . hogy melyik kliensnek van mondandója. client. } System. 1000). data. a másodikat írhatóságra.

SocketFlags.BufferSize.2 Aszinkron socketek Kliensek szinkron kezelésére is felhasználhatjuk a Socket osztályt. 0.ASCII.. server. state. A BeginAccept aszinkron metódus első paramétere az a metódus lesz.AsyncState).Receive(data).. amíg egy jelzést (A Set metódussal) nem kap.None. done.GetString(data.WriteLine("A kliens üzenete: {0}". Az aszinkron hívható metódusok Begin és End előtagot kaptak. hogy az AcceptCallback metódus visszajelezzen a főszálnak.ReadCallback.WriteLine("Kliens kapcsolódott. pl.Buffer.BeginAccept(Program. done. server).static public void CommunicateWithClient(Socket client) { byte[] data = new byte[256]. } A kliens fogadása után meghívtuk a ManualResetEvent Set metódusát. nézzük a szerver oldalt: while(true) { done. 0. második paraméternek pedig átadjuk a szervert reprezentáló Socket objektumot. Tehát: meghívjuk a BeginAccept–et. . az adatcsere nyugodtan elfut a háttérben).Set(). ezzel jeleztük. lényegében az aszinkron delegate -ekre épül ez a megoldás. Nézzük az AcceptCallback–ot: static public void AcceptCallback(IAsyncResult ar) { Socket client = ((Socket)ar. Encoding.3. A Reset metódus alaphelyzetbe állítja az objektumot.Client = client. ezután pedig várunk. Console. State.WaitOne(). client.EndAccept(ar).BeginReceive(state. várhatjuk a következő klienst (látható. Console.245 - . state). A kliens ezúttal is változatlan. int length = client. kapcsolat elfogadására most a BeginAccept metódust fogjuk használni. hogy a kliens csatlakozott és folytathatja a figyelést. Program. Console..WriteLine("A szerver kapcsolatra vár.AcceptCallback.Reset(). amely a kapcsolat fogadását végzi. míg a WaitOne megállítja (blokkolja) az aktuális szálat. amíg hozzá nem jutunk a klienshez.").. } A done változó a ManualResetEvent osztály egy példánya. length))."). } 34. hogy csak addig kell blokkolnunk. hogy kész vagyunk. StringState state = new StringState(). segítségével szabályozni tudjuk a szálakat.

azzal a különbséggel. 0.3. state. state. akkor elveszíti a játékot. Console. hanem tetszés szerinti műveletet hajthatunk végre a háttérben.3 Szálakkal megvalósított szerver Ez a módszer nagyon hasonló az aszinkron változathoz. A következő programunk egy számkitalálós játékot fog megvalósítani úgy. hogy a szerver gondol egy számra és a kliensek ezt megpróbálják kitalálni. Mindent tipp után a szerver visszaküld egy számot a kliensnek: -1–et.EndReceive(ar). set. 0–át ha eltalálta a számot és 2–t. 34. public State() { Buffer = new byte[BufferSize]. ha valaki már . hogy most manuálisan hozzuk létre a szálakat. 1–et ha a szám kissebb.WriteLine("A kliens üzenete: {0}". state. } A fordított irányú adatcsere ugyanígy zajlik. ezt fogjuk átadni a BeginReceive metódusnak: class State { public const int BufferSize = 256.A StringState osztályt kényelmi okokból mi magunk készítettük el.Append(Encoding. } } class StringState : State { public StringState() : base() { Data = new StringBuilder(). a megfelelő metódusok Begin/End előtagú változataival.Data.Client. set.ASCII.Client. length)). set.246 - .Data). int length = state. ha a gondolt szám nagyobb. } public StringBuilder Data { get. } public Socket Client { get. ha nem sikerül kitalálnia.GetString(state. illetve nem csak a beépített aszinkron metódusokra támaszkodhatunk. } } Végül nézzük a BeginReceive callback metódusát: static public void ReadCallback(IAsyncResult ar) { StringState state = (StringState)ar.Close(). } public byte[] Buffer { get. Minden kliens ötször találgathat.AsyncState.Buffer.

int result = 0. NUMBER). try { client = socket as Socket.WriteLine("A szerver elindult.Receive(data). .Stream.Accept().Empty. NewTurnEvent += NewTurnHandler. name). if(client == null) { throw new ArgumentException().Listen(5). Console. amely megkönnyíti a dolgunkat. 0).SetMaxThreads(MaxClient.GetString(data. és új számot sorsol.Tcp). Console.WriteLine("Új játékos: {0}". length).Bind(EndPoint). server. amelynek segítségével a kliens objektumokat átadjuk a ClientHandler metódusnak: private void ClientHandler(object socket) { Socket client = null. } server = new Socket(AddressFamily.ASCII. byte[] data = new byte[7]. server. while(true) { client = server. ProtocolType. ThreadPool.QueueUserWorkItem(ClientHandler. Készítettünk egy osztályt. client). int length = client.kitalálta a számot. a szám: {0}". ThreadPool. A Listen metódussal indítjuk el a szervert: public void Listen() { if(EndPoint == null) { throw new Exception("IPEndPoint missing"). } } A kliensek kezeléséhez a ThreadPool osztályt fogjuk használni. SocketType. akkor a szerver megvárja. Socket client = null. string name = String. Console.247 - . name = Encoding.WriteLine("Kliens bejelentkezett”). int i = 0. míg minden kliens kilép. } ++ClientNumber. 0.InterNetwork. Ha egy kliens kitalálta a számot.

Receive(data). null). } else { result = -1. hogy van-e bejelentkezett kliens.bool win = false.248 - . } else if(NUMBER < number) { result = 1. name). data. ha pedig nincs akkor új számot sorsolunk. A GuessNumber metódusnak adjuk át a tippünket és a result változót out paraméterként. Nézzük a GuessNumber metódust: private void GuessNumber(string name.GetBytes(result. } . } } catch(Exception e) { Console. } finally { client.WriteLine("{0} kitalálta a számot!".ASCII.Message). } } } Minden kliens rendelkezik névvel is. NUMBER = -1. SocketFlags. int. out int result) { lock(locker) { if(NUMBER != -1) { Console.ASCII. while(i < 5 && win == false) { data = new byte[128]. out result). int number. } ++i. number).Send(data.GetString( data.Parse(Encoding. length)). elsőként ezt olvassuk be. if(NUMBER == number) { Console. length = client.Length. GuessNumber(name.None). data = Encoding. Végül a finally blokkban ellenőrizzük. name. client. amely 7 karakter hosszú (7 byte) lehet.WriteLine("{0} szerint a szám: {1}". if(result == 0) { win = true.WriteLine(e. 0. if(--ClientNumber == 0 && result == 0) { NewTurnEvent(this. result = 0.ToString()).Close().

Dgram. cserében gyors lesz. mint a TCP–t használó társát.NET a TCP–hez hasonlóan biztosítja számunkra az UdpListener/Client osztályokat. Ezen kívül ellenőrzi. Amennyiben adott időn belül a nyugta nem érkezik meg. . Ezután ugyanúgy használhatjuk ezt az objektumot. Ha valamelyik kliens kitalálta számot.InterNetwork. ahol a gyorsaság számít és nem nagy probléma. ekkor a Socket osztály konstruktora így fog kinézni: Socket server = new Socket( AddressFamily. így gyorsan ellenőrizhetjük. hogy minden csomag megérkezik. pl. Jellemzően olyan helyeken használják. hogy az adott csomag rendben megérkezett. Hagyományos Socket–ek esetén is elérhető ez a protokoll. ha egy-két csomag elveszik.Sleep(300). ezt a forráskódot itt nem részletezzük.Udp). hogy a feltétel melyik ágába kell bemennünk. Az UDP épp az ellenkezője. de említést kell tennünk az Internet másik alapprotokolljáról az UDP–ről is. A . } A metódus törzsét le kell zárnunk. valós idejű média lejátszásnál illetve játékoknál. nem biztosítja. hogy a csomagok sérülésmentesek legyenek. illetve kiszűri a duplán elküldött (redundáns) adatokat is. A TCP egy megbízható. hogy egyszerre csak egy szál (egy kliens) tudjon hozzáférni. A csomagokat sorszámmal látja el. akkor a csomagot újra elküldi.249 - .} else result = 2. Kliens oldalon sokkal egyszerűbb dolgunk van. akkor annak -1–et adunk vissza. ProtocolType. ez alapján pedig a fogadó fél nyugtát küld. ezek kezelése gyakorlatilag megegyezik TCP –t használó párjaikkal. SocketType. } Thread. de megtalálható a jegyzethez tartozó források között. 34.4 TCP és UDP Ez a fejezet elsődlegesen a TCP protokollt használta. de egyúttal egy kicsit lassabb módszer. ezért itt most nem részletezzük.

. XQuery az XML–hez. Ezek a következők: Kiterjesztett metódusok (extension method): velük már korábban megismerkedtünk. a LINQ To Objects teljes funkcionalitása ezekre épül. A fejezethez tartozó forráskódok megtalálhatóak a Sources\LINQ könyvtárban. illetve gyűjtemények esetében az elemeket: .0–ban megjelent néhány újdonság részben a LINQ miatt került a nyelvbe.250 - .). amely lehetővé teszi.0 bevezette a LINQ –t (Language Integrated Query). hogy egy plusz réteg (a LINQ „felület”) bevezetésével mindezeket áthidaljuk méghozzá teljes mértékben függetlenül az adatforrástól. ezek kezeléséhez pedig új eszközök használatát illetve új nyelveket kell megtanulnunk (SQL a relációs adatbázisokhoz. LINQ To Objects: ennek a fejezetnek a tárgya. hogy az objektum deklarálásával egyidőben beállíthassuk a tulajdonságaikat. a Microsoft nem fejleszti tovább (de továbbra is elérhető marad. mivel a LINQ „framework” viszonylag könnyen kiegészíthető tetszés szerinti adatforrás használatához. jelenlétük jelentősen megkönnyíti a dolgunkat. A kettő közül a LINQ To Entites a „főnök”. A LINQ család jelenleg három főcsapást jelölt ki. tömbök feldolgozását teszi lehetővé (lényegében minden olyan osztállyal működik amely megvalósítja az IEnumerable<T> interfészt).A LINQ lehetővé teszi. Objektum és gyűjtemény inicializálók: vagyis a lehetőség. Egy másik előnye pedig.1 Nyelvi eszközök A C# 3. mint egy memóriában lévő gyűjteményt. stb. ezek a következőek: LINQ To XML: XML dokumentumok lekérdezését és szerkesztését teszi lehetővé.. Miért jó ez nekünk? Napjainkban rengeteg adatforrás áll rendelkezésünkre. LINQ To SQL (vagy DLINQ) és LINQ To Entities (Entity Framework): relációs adatbázisokon (elsősorban MS SQL-Server) végezhetünk műveleteket velük. - A fentieken kívül számos third-party/hobbi project létezik. 35. listák.35 LINQ To Objects A C# 3. hogy könnyen. hogy a LINQ lekérdezések erősen típusosak. vagyis a legtöbb hibát még fordítási időben el tudjuk kapni és kijavítani. vagyis pontosan ugyanúgy fogunk kezelni egy adatbázist. lényegében az összes művelet az IEnumerable<T>/IEnumerable interfészeket egészíti ki. mivel kisebb projectekre illetve hobbifejlesztésekhez kiváló). uniformizált úton kezeljük a különböző adatforrásokat. a LINQ To SQL inkább csak technológiai demónak készült. memóriában lévő gyűjtemények. Az Entity Framework használatához a Visual Studio 2008 első szervizcsomagjára van szükség.

. //alma } } 35. "kakukktojás" }.Value1).2 Kiválasztás A legegyszerűbb dolog. Console. } } Lambda kifejezések: a lekérdezések többségénél nagyon kényelmes lesz a lambdák használata. /*Gyűjtemény inicializáló*/ List<string> list = new List<string>() { "alma". Value2 = "körte" }.Collections. using System. hogy egy vagy több elemét valamilyen kritérium alapján kiválasztjuk. "körte". Természetesen ennek így sok értelme nincsen. Property2 = "value2". amit egy listával tehetünk. class Program { static public void Main() { var type1 = new { Value1 = "alma". ugyanakkor lehetőség van a „hagyományos” névtelen metódusok felhasználására is. ezért ilyenkor a var –t fogjuk használni. de szűrésről majd csak a következő fejezetben fogunk tanulni. A névtelen típusok beveztése lehetővé teszi. class Program { static public void Main() { /*Objektum inicializáló*/ MyObject mo = new MyObject() { Property1 = "value1".Generic. ilyenkor feleslegesen foglalná egy lekérdezés eredménye a memóriát. }.using System. "dió".251 - . Névtelen típusok: sok esetben nincs szükségünk egy objektum minden adatára. A „var”: a lekérdezések egy részénél egészen egyszerűen lehetetlen előre megadni az eredmény-lista típusát. hogy helyben deklaráljunk egy névtelen osztályt: using System. egy egész számokat tartalmazó List<T> adatszerkezetből az összes számot lekérdezzük. A következő forráskódban épp ezt fogjuk tenni.WriteLine(type1.

2.Collections. 11. míg az „azonosító” a forrás egyes tagjait jelöli a kiválasztás minden iterációjában. Ezt a valamit a lekérdezés második fele tartalmazza: select kifejezés A fenti példában egyszerűen vissza akarjuk kapni a számokat eredeti formájukban. 89 }. 75.Linq–t. nézzük a következő sort: var result = from number in list select number. 55. 22.Generic. Egy LINQ lekérdezés a legegyszerűbb formájában a következő sablonnal írható le: eredmény = from azonosító in kifejezés select kifejezés A lekérdezés első fejében meghatározzuk az adatforrást: from azonosító in kifejezés Egészen pontosan a „kifejezés” jelöli a forrást. tehát ne felejtsük le. Azaz minden számhoz hozzáadunk tízet. de akár ezt is írhattuk volna: var result = from number in list select (number + 10). Most pedig jöjjön a lényeg. using System. var result = from number in list select number. mint a foreach ciklus: foreach(var azonosító in kifejezés) Vagyis a lekérdezés alatt a forrás minden egyes elemével tehetünk. Rá lesz szükségünk mostantól az összes lekérdezést tartalmazó programban. 12. class Program { static public void Main() { List<int> list = new List<int>() { 10. using System. foreach(var item in result) { Console. amit jólesik. . 30.252 - . lényegében pontosan ugyanúgy működik.using System.Linq.WriteLine("{0}". item). 4. } } } Elsősorban vegyük észre az új névteret a System.

Linq.Select(selector).253 - . mindössze a szintaxis más (ez pedig az Extension Method Format) (a fordító is ezt a formátumot tudja értelmezni. és a fordítás után pontosan ugyanazt a kifejezést is fogja használni a program. Emlékezzünk. 5. 6. ezért nem ajánlott (leszámítva olyan eseteket. foreach(var item in result) { Console.Distinct(). A két szintaxist keverhetjük is (Query Dot Format). ragaszkodjunk csak az egyikhez. hogy ilyen módon a fejlesztőeszköz (a Visual Studio) támogatást illetve típusellenőrzést adhat a select utáni kifejezéshez (illetve a lekérdezés többi részéhez). de ennek megvan az oka. A fenti kódban SQL szerű lekérdezést készítettünk (ún. vagyis ezt is írhattuk volna: var result = list. 6. amely a lekérdezés eredményéből eltvolítja a duplikált elemeket. Pontosan ugyanazt értük el. A következő példában a Distinct metódust használjuk. amikor egy művelet csak az egyik formával használható). hogy először az adatforrást határozzuk meg. mégpedig az.Select(number => number).Select( delegate (int number) { return number. Func<int. 11. de ez az olvashatóság rovására mehet. var result = (from number in list select number). 3. 10. A Select az adatforrás elemeinek típusát használó Func<T. V> generikus delegate–et kap paraméteréül. item). class Program { static public void Main() { List<int> list = new List<int>() { 1. using System.WriteLine("{0}".Generic. jelen esetben ezt egy lambda kifejezéssel helyettesítettük. using System. Query Expression Format). Őt csakis Extension Method formában hívhatjuk meg: using System.Collections. ha csak lehet. 1 }. int> selector = (x) => x. minden LINQ To Objects művelet egyben kiterjesztett metódus.Az SQL –t ismerők számára furcsa lehet. } } } . 1. de máshogyan is megfogalmazhattuk volna a mondanivalónkat. de írhattuk volna a következőket is: var result1 = list. var result2 = list. }). tehát a Query Syntax is erre alakul át).

set. set.LastName. Email: {1}.LastName. hogy az eredeti eredményhalmazt leszűkítjük. Phone = customer. amely csak a kért adatokat tartalmazza. úgy. foreach(var customer in result) { Console. set.A jegyzet ezután mindkét változatot bemutatja a forráskódokban. } public int PostalCode { get. } public string PhoneNumber { get. . hogy a lekérdezésben készítünk egy névtelen osztályt.PhoneNumber }. set.1 Projekció Vegyük a következő egyszerű „osztály-hieraarchiát”: class Address { public string Country { get. } A probléma a fenti kóddal.254 - . } public string City { get. Customer. Telefon: {2}". set. } public int State { get. hanem készíthetünk specializált direkt erre a célra szolgáló osztályokat is. customer. } public string LastName { get. } } class Customer { public int ID { get. } public string FirstName { get. set. } } Minden vásárlóhoz tartozik egy Address objektum. email címét és telefonszámát.FirstName + ” ” + customer. hogy a szükséges adatokon kívül megkaptuk az egész objektumot beleértve a cím példányt is amelyre pedig semmi szükségünk nincsen. hogy egy olyan lekérdezést akarok írni.} public string Email { get. A megoldás. set.WriteLine("Név: {0}. Ez nem egy bonyolult dolog.Email.FirstName + Customer. 35. amely visszaadja az összes vevő nevét. set. E zt projekciónak nevezzük. amely a vevő címét tárolja. Az új kód: var result = from customer in custList select new { Name = customer. } public Address Address { get. Tegyük fel. customer. set.PhoneNumber). set.2. Természetesen nem csak névtelen osztályokkal dolgozhatunk ilyen esetekben.Email. Email = customer. a kód a következő lesz: var result = from customer in custList select customer. } public string Street { get. set.

255 - .Split(' ') from word in words select word. mivel ez rövidebb. 35. using System. amelyet a következő sablonnal írhatunk le: from azonosító in kifejezés where kifejezés select azonosító Nézzünk egy egyszerű példát: using System. kend". 56. 22 }. jót ád. 35.3 Szűrés Nyílván nincs szükségünk mindig az összes elemre. var result = from line in poem let words = line. csak jó az isten. 4. var result1 = from number in list where number > 30 . 34. 0.". class Program { static public void Main() { List<int> list = new List<int>() { 12. using System. specializált változatát) ad vissza. amelyek segítségével elkerülhetjük egy kifejezés ismételt felhasználását. A legalapvetőbb ilyen művelet a where.Generic.2. "A szobában lakik itt bent?". amelyek pontosan egy elemmel térnek vissza – erről később részletesebben) IEnumerable<T> típusú eredményt (vagy annak leszármazott.A lekérdezés eredményének típusát eddig nem jelöltük.Collections. ezért képesnek kell lennünk szűrni az eredménylistát.Linq. A let segítségével minden sorból egy újabb string-tömböt készítettünk. 72. "Hogy fölvitte a kend dolgát!" }. amelytől egynél több eredményt várunk (tehát nem azok az operátorok. Minden olyan lekérdezés. helyette a var kulcsszót használtuk. "Lám. Nézzünk egy példát: string[] poem = new string[] { "Ej mi a kõ! tyúkanyó.2 Let A let segítségével – a lekérdezés hatókörén belüli – változókat hozhatunk létre. 89. amelyeken egy belső lekérdezést futattunk le.

class Program { static public void Main() { List<int> list = new List<int>() { 12. bool visszatérési értékű metódust (anonim metódust. using System. var result2 = list.. Console. amikor ténylegesen felhasználjuk őket. hanem akkor. 0."). using System. 22 }.WriteLine("Lekérdezés után... Func<int. ha a lekérdezés eredményén azonnal meghívjuk pl.Where(number => number > 30).. bool> predicate = (x) => { Console.Linq. var result = from number in list where predicate(number) select number. A where feltételeinek megfelelő elemek nem a lekérdezés hívásakor kerülnek az eredménylistába. 34.WriteLine("Szűrés. bool> predicate = (x) => x > 30. foreach(var item in result) { ... }. A következő példában teszteljük a fenti állítást: using System. ezt elhalasztott végrehajtásnak (deferred execution) nevezzük (ez alól kivételt jelent. stb."). ekkor az elemek szűrése azonnal megtörténik). A where egy paraméterrel rendelkező.) vár paramétereként: Func<int.Where(number => number > 30). 89. mind pontosan ugyanazt fogja visszaadni és teljesen szabályosak. var result2 = list.256 - . a ToList metódust.select number. var result3 = (from number in list select number) .").Generic.. 4.Where(predicate). 56.WriteLine("Lekérdezés elõtt. return x > 30. } } A forrásban mindhárom lekérdezés szintaxist láthatjuk. Console..Collections. 72. lambda kifejezést. var result1 = from number in list where predicate(number) select number.

} } } A kimenet pedig ez lesz: Lekérdezés előtt. item).257 - .. Szűrés. // növekvő sorrend var result2 = from number in list orderby number ascending select number. // csökkenő sorrend .. Jól látható. index) => index % 2 == 0). // szintén növekvő var result3 = from number in list orderby number descending select number. 35.OrderBy(x => x). A where két alakban létezik....... Lekérdezés után. 89 Szűrés.. a lekérdezés sablonja ebben az esetben így alakul: from azonosító in kifejezés where ascending/descending select kifejezés kifejezés orderby tulajdonság Az első példában egyszerűen rendezzük egy számokból álló lista elemeit: var result1 = list. természetesen nullától számozva. Ez a változat két paramétert kezel. az elsőt már láttuk. Szűrés. 34 Szűrés.. Szűrés.. 56 Szűrés.4 Rendezés A lekérdezések eredményét könnyen rendezhetjük az orderby utasítással.. A fenti lekérdezés a páros indexű elemeket választja ki. most nézzük meg a másodikat is: var result = list.Console. Szűrés. ahol index az elem indexét jelöli.Where((number.WriteLine("{0}"... hogy a foreach ciklus váltotta ki a szűrő elindulását. 72 Szűrés.......

"Béla". using System. "Töhötöm".Collections.5 Csoportosítás Lehetőségünk van egy lekérdezés eredményét csoportokba rendezni a group by/GroupBy metódus segítségével. "Tamás" }. using System. Az OrderBy metódus egy másik változata két paramétert fogad. A sablon ebben az esetben így alakul: from azonosító in kifejezés where kifejezés orderby tulajdonság ascending/descending group kifejezés by kifejezés into azonosító select kifejezés Használjuk fel az előző fejezetben elkészített program neveit és rendezzük őket csoportokba a név első betűje szerint: . Az elemeket több szinten is rendezhetjük. míg a Query szintaxissal csak annyi a dolgunk. hogy az azonos kezdőbetűvel rendelkezőket tovább rendezzük a név második karaktere alapján: using System. var result2 = from name in names orderby name[0].OrderBy(name => name[0]) . 35.Generic. míg második paraméterként megadhatunk egy tetszőleges IComparer<T> interfészt megvalósító osztályt. az első a rendezés alapszabálya. "Viktória". "Iván".ThenBy(name => name[1]). Egy lekérdezés pontosan egy orderby/OrderBy–t és bármennyi ThenBy–t tartalmazhat. "Vazul".WriteLine(item). "Balázs". mégpedig úgy. } } } Az Extension Method formában a ThenBy volt segítségünkre. "Jolán". a következő példában neveket fogunk sorrendbe rakni. hogy az alapszabály mögé írjuk a további kritériumokat. class Program { static public void Main() { List<string> names = new List<string>() { "István". var result1 = names.Linq.258 - . name[1] select name. "Judit".A rendezés a gyorsrendezés algoritmusát használja. foreach(var item in result2) { Console. "Jenő".

Collections. "Balázs". using System.{0}". "Béla".WriteLine("-.OrderBy(name => name[0]) . ez lesz az OrderBy paramétere.using System. "Tamás" }.Linq. "Töhötöm".GroupBy(name => name[0]). Az eredmény típusa a következő lesz: . "Jenõ". "Vazul". "Jolán".Key). name).WriteLine(group.259 - .Generic. foreach(var group in result1) { Console. using System. "Viktória". var result2 = from name in names orderby name[0] group name by name[0] into namegroup select namegroup. foreach(var name in group) { Console. "Judit". } } } } A kimenet a következő lesz: B --I --J ---T --V --Béla Balázs István Iván Judit Jolán Jenő Töhötöm Tamás Viktória Vazul A csoportosításhoz meg kell adnunk egy kulcsot. "Iván". class Program { static public void Main() { List<string> names = new List<string>() { "István". var result1 = names.

TElement>> Az IGrouping interfész tulajdonképpen maga is egy IEnumerable<T> leszármazott kiegészítve a rendezéshez használt kulccsal. 35. ha egyébként a lekérdezés nem végez műveleteket az elemekkel. hogy olyan listán akarunk lekérdezést végrehajtani. egyszerűen null értéket tesz az eredménylistába. ezeket. illetve a hozzájuk tartozó listákat a jegyzet mellé csatolt forráskódok közül a Data.5. de amikor rendezünk. Ez akkor is számít. foreach(var name in group) { Console. hogy kezelje őket: var result1 = names. amely a megfelelő alapértelmezett értéket adja vissza.2 Összetett kulcsok Kulcs meghatározásánál lehetőségünk van egynél több értéket kulcsként megadni. Használjuk fel az előző programok listáját. 35. ekkor névtelen osztályként kell azt definiálni. foreach(var group in result2) { Console.260 - . Használjuk fel a korábban megismert Customer illetve Address osztályokat. A Select képes kezelni ezeket az eseteket. . var result2 = from name in names group name by name == null ? '0' : name[0] into namegroup select namegroup.GroupBy(name => { return name == null ? '0' : name[0].5. vagy akár készíthetünk egy metódust. hogy amikor a listában előfordulhatnak null értékek akkor azt figyelembe kell venni a lekérdezés megírásakor. akkor szükségünk van az objektum adataira és ha null értéket akarunk vizsgálni akkor gyorsan kivételt kaphatunk. hanem „csak” kiválasztást végzünk. hiszen az eredménylistában attól még ott lesznek a null elemek amelyek késöbb fejfájást okozhatnak. használhattuk volna a ?? operátort is.cs file-ban találja az olvasó.IEnumerable<IGrouping<TKey. egészítsük ki néhány null értékkel és írjuk át a lekérdezést.Key). name == null ? "null" : name). a lényeg. } } Többféle megoldás is létezik erre a problémára.{0}".1 Null értékek kezelése Előfordul.WriteLine(group. }). vagyis lényegében egy lista a listában típusú „adatszerkezetről” van szó. amelynek bizonyos indexein null érték van.WriteLine("-.

set.WriteLine("{0}.GetCustomerList() group customer by new { customer. {1}".class Address { public string Country { get. } public string LastName { get.Country. using System. set.cs .Key. set.FirstName + " " + customer. set.Linq.State } into customergroup select customergroup. } public int State { get. using System. customer. set. set.Country. set. } public Address Address { get. } public int PostalCode { get. customer.Generic. group. } } class Customer { public int ID { get.Key. } public string FirstName { get. } public string Street { get. class Program { static public void Main() { var result = from customer in DataClass. } } A lekérdezést pedig a következőképpen írjuk meg: using System.} public string Email { get. foreach(var group in result) { Console. } } } } Fordítani így tudunk: csc main. set.Address.Collections.{0}".LastName). set.261 - .WriteLine("-.cs Data.Address. set.State). set. group. } public string City { get. foreach(var customer in group) { Console. } public string PhoneNumber { get.

set. de a LINQ To Objects támogatja ezt is. set. hogy egy lekérdezésben több táblát összekapcsolhatunk (join) egy lekérdezéssel. hogy egy listában egy példányból – és így kulcsból – csak egy lehet). Írjunk egy lekérdezést. } } Ez a fent említett webáruház egy egyszerű megvalósítása. set. set. hogy az összekapcsolandó objektum-listák rendelkeznek közös adattagokkal (relációs adatbázisoknál ezt elsődleges kulcs (primary key) – idegen kulcs (foreign key) kapcsolatként kezeljük). set. } class Product { public int ID { get. } int ProductID { get. set. ez lesz az elsődleges kulcs (tegyük fel. } DateTime OrderDate { get. } public string LastName { get. } } class Order { public public public public public } int CustomerID { get.262 - . Elsőként írjuk fel a join–nal felszerelt lekérdezések sablonját: from azonosító in kifejezés where kifejezés join azonosító in kifejezés on kifejezés equals kifejezés into azonosító orderby tulajdonság ascending/descending group kifejezés by kifejezés into azonosító select kifejezés Most pedig jöjjön a lekérdezés (az adatokat most is a Data. } string Note { get.} public string Email { get. set. set. pl. A „join”–műveletek azon az egyszerű feltételen alapulnak. } DateTime? DeliverDate { get.35. egy webáruházban a vevő-árúrendelés adatokat. ezek lesznek az idegen kulcsok. } public int Quantity { get. Az Order osztály mindkét példányra tartalmaz referenciát (hiszen minden rendelés egy vevőtermék párost igényel). } public string FirstName { get. } public Address Address { get. Nézzünk egy példát: class Customer { public int ID { get. set. amely visszaadja minden vásárló rendelését. Memóriában lévő gyűjtemények esetében ez viszonlag ritkán szükséges. } public string ProductName { get. set.cs tárolja): . set.6 Listák összekapcsolása A relációs adatbázisok egyik alapköve. set. set. set. } public string PhoneNumber { get. amely segítségével egyértelműen meg tudjuk különböztetni őket. Mind a Customer mind a Product osztály rendelkezik egy ID nevű tulajdonsággal.

Elektromos vízforraló 35. product. A lekérdezés nagyon egyszerű. amelyek éppen hogy nem kerülnének bele az eredménylistába. A következő példában a lekérdezés visszaadja a vásárlók rendelésének a sorszámát (most nincs szükségünk a Products listára). } } A join tipikusan az a lekérdezés típus.Name). ezért egy belső lekérdezést is írtunk. Products = DataClass. Ez a feladat a join egy speciális outer join–nak nevezett változata. de könnyen szimulálhatjuk a DefaultIfEmpty metódus használatával. majd megmondtuk. Az eredménylistát persze ki kell egészítenünk a vásárolt termékek listájával. elsőként csatoltuk az elsődleges kulcsokat tartalmazó listát az idegen kulccsal rendelkező listához.LastName.Where(order.{0}".ProductName). de gyakran van szükségünk azokra is.GetOrderList() join customer in DataClass.GetProductList() . foreach(var product in orders. amelyek kapcsolódtak egymáshoz. azokat a „vásárlókat” keressük. az SQL tartalmaz OUTER JOIN „utasítást”).ID select new { Name = customer. amely nem szerepelne a „hagyományos” join által kapott lekérdezésben. ezért itt csak ezt írtuk meg.Elosztó Istvan Reiter -.Papír zsebkendő József Fekete -. pl.Products) { Console.7 Outer join Az előző fejezetben azokat az elemeket választottuk ki.WriteLine(orders.FirstName + " " + customer. A LINQ To Objects bár közvetlenül nem támogatja ezt (pl. akkor megjelenítünk egy „nincs rendelés” feliratot.GetCustomerList() on order. ahol az SQL-szerű szintaxis olvashatóbb.: .CustomerID equals customer.263 - . vagy ha még nem rendelt. foreach(var orders in result) { Console.ID) }. akik eddig még nem rendeltek semmit. hogy milyen feltételek szerint párosítsa az elemeket (itt is használhatunk összetett kulcsokat ugyanúgy névtelen osztály készítésével).ProductID == product.var result = from order in DataClass.WriteLine("-. A kimenet a következő lesz: Istvan Reiter -. amely egyszerűen egy null elemet helyez el az eredménylistában az összes olyan elem helyén.

order.Add(12345). ha LTO –t akarunk használni. hiszen a régi ArrayList osztály nem valósítja meg a generikus IEnumerable–t. System.Collections.Generic. A kettő közti különbséget a hibakezelés jelenti: az OfType egyszerűen figyelmen kívűl hagyja a konverziós hibákat és a nem megfelelő elemeket kihagyja az eredménylistából.ID equals order.0–val való kompatibilitás miatt léteznek.var result = from customer in DataClass. elsődlegesen a . // kivétel } } } .OfType<string>() select item.ToString() }.GetOrderList() on customer.Name. } 35.DefaultIfEmpty() select new { Name = customer.Cast<string>() select item. foreach(var order in result) { Console.Add("dió").Collections.CustomerID into tmpresult from o in tmpresult.ProductID.InvalidCastException) dob: using using using using System.NET 1. list.Linq. list. foreach(var item in result1) { Console. var result1 = from item in list.264 - . order.8 Konverziós operátorok A LINQ To Objects lehetőséget ad listák konverziójára a következő operátorok segítségével: OfType és Cast: ők a „sima” IEnumerable interfészről konvertálnak generikus megfelelőikre. ezért kasztolni kell.FirstName + " " + customer.GetCustomerList() join order in DataClass.WriteLine(item).LastName. class Program { static public void Main() { ArrayList list = new ArrayList().WriteLine("{0}: {1}".Add("alma"). var result2 = from item in list. System. Product = o == null ? "nincs rendelés" : o. list. míg a Cast kivételt (System.Product). System.

V> szerkezetet ad vissza. } } Amikor ezeket az operátorokat használjuk akkor a Where végrehajtása nem tolódik el. V> szerkezet ezeket nem engedélyezi (sőt kivételt dob). ToLookup.WriteLine(item. ahogyan a nevükből is látszik az IEnumerable<T> eredménylistát tömbbé vagy generikus gyűjteménnyé alakítják. customer. hogy mindkettő kulcs-érték párokkal operáló adatszerkezetet hoz létre.WriteLine(number)).WriteLine("-.{0} {1}". 32. hanem azonnal kiszűri a megfelelő elemeket. hogy a vásárlókat megye szerint rendezze: var result = (from customer in DataClass. 32. tehát a konverzió elemenként történik. System.Address.FirstName.265 - . amelyben az azonos kulccsal rendelkező adatok listát alkotnak a listán belül.Linq. foreach(var customer in item) { Console.A program ugyan kiírja a lista első két elemét. 55. ToList. míg a Dictionary<T. ToArray. class Program { static public void Main() { List<int> list = new List<int>() { 10. var result = (from number in list where number > 20 select number). A következő példában ezt a metódust használjuk. A különbség a duplikált kulcsok kezelésében van. } } .Key).ToList<int>().Collections. System. 21 }.Generic. customer. A ToDictionary és ToLookup metódusok hasonló feladatot látnak el abban az értelemben. foreach(var item in result) { Console.State + " megye"). 45. result.ToLookup((customer) => customer. mégpedig akkor. 2.LastName).ForEach((number) => Console. A ToArray metódus értelemszerűen tömbbé konvertálja a bemenő adatokat. addig a ToLookup ILookup<T. de a harmadiknál már kivételt dob. amikor az eredménylistát ténylegesen felhasználjuk nem pedig a lekérdezésnél. System. ezért őt kiválóan alkalmazhatjuk a join műveletek során. vagyis itt érdemes figyelni a teljesítményre. A következő példában a ToList metódus látható: using using using using System. ToDictionary: ezek a metódusok.GetCustomerList() select customer) .Collections.

44 }. ha a feltételnek több elem is megfelel. 6.WriteLine(e.Collections.First().FirstOrDefault((item) => item < 3). 78. de mindkettő kivételt dob.Message). var result1 = list. 67. result5).9 „Element” operátorok A következő operátorok megegyeznek abban. Amennyiben nincs a feltételnek megfelelő elem a listában (vagy üres listáról beszélünk) akkor az első két operátor kivételt dob.Generic. result3. míg a másik kettő az elemek típusának megfelelő alapértelmezett null értékkel tér vissza (pl. {3}".Linq. {1}. 3. . using System. } } Single/SingleOrDefault: ugyanaz. // kivétel } catch(Exception e) { Console. bool> típusú metódust kap paraméternek és e szerint a szűrő szerint választja ki az első elemet. result2.266 - . // 44 var result3 = list. } var result5 = list. míg a másik egy Func<T. 4.Last(). // 10 var result2 = list. mint a First/FirstOrDefault páros.WriteLine("{0}. result1. 35. hogy egyetlen elemmel (vagy egy előre meghatározott alapértékkel) térnek vissza az eredménylistából. // 0 Console.Last((item) => item < 3). using System. class Program { static public void Main() { List<int> list = new List<int>() { 10. {2}. int típusú elemek listájánál ez nulla míg stringek esetén null lesz): using System. // 56 try { var result4 = list. AsEnumerable: Ez az operátor a megfelelő IEnumerable<T> típusra konvertálja vissza a megadott IEnumerable<T> interfészt megvalósító adatszerkezetet. 56.First((item) => item > 10).A ToLookUp paramétereként a lista kulcsértékét várja. First/Last és FirstOrDefault/LastOrDefault: ezeknek a metódusoknak két változata van: a paraméter néküli az első/utolsó elemmel tér vissza a listából.

22. 67. 78. 22. 3. DefaultIfEmpty: a megfelelő alapértelmezett értékkel tér vissza. Az első kivételt dob. 3. 78. amelyek két lista közötti halmazműveleteket tesznek lehetővé. őt használtuk korábban a join műveleteknél. 89. 4. /* 10. 3. 3.Intersect(list2). /* 56. 67. List<int> list2 = new List<int>() { 10. 67. ha egy lista nem tartalmaz elemeket. 6. 67. 4. 4. ha az index nem megfelelő a másik pedig a megfelelő alapértelmezett értéket adja vissza. 6. 4. 4. 78. 6. 6. 89. 99 */ var result2 = list1. var result1 = list1. 44 }. 22. 6. 67. Concat és Union: mindkettő képes összefűzni két lista elemeit. 44. míg a második azokat amelyek csak az elsőben: List<int> list1 = new List<int>() { 10. 67 */ var result2 = list1.10 Halmaz operátorok Nézzük a halmaz operátorokat. 99 }. 89. 10. 67. 67. de az utóbbi egy elemet csak egyszer tesz át az új listába: List<int> list1 = new List<int>() { 10. 3. 3. amelyek mindkét listában szerepelnek. 35. 5. 78. 99 }. 3. 56.Concat(list2). 5. /* 10. 44. 99 */ Intersect és Except: az első azokat az elemeket adja vissza. 89. 56. 3.Except(list2). 78.267 - . List<int> list2 = new List<int>() { 10.ElementAt/ElementAtOrDefault: visszaadja a paraméterként átadott indexen található elemet. var result1 = list1. 5. 22. 56. /* 10. 44 */ .Union(list2). 44 }. 56. 5.

Sum(). var result1 = list. 3.Max((item) => item % 3). 56. 4. var result1 = list. Alapértelmezés szerint az összes elemet számolják. 56. // 4 Min és Max: a lista legkisebb illetve legnagyobb elemét adják vissza. // 78 var result2 = list.Count().Count((item) => item > 10). // 2 Természetesen a második eredmény maximum kettő lehet. Count és LongCount: visszaadják az elemek számát egy listában. 6. // 268 var result2 = list. 56. // 33. 44 }.Average(). illetve egy szelektor kifejezést is: . 3.Max(). Mindkét operátornak megadhatunk egy szelektor kifejezést. 78. 67. 44 }. és e szerint választják ki a megfelelő elemet: List<int> list = new List<int>() { 10. elemek összege vagy átlagszámítás).11 Aggregát operátorok Ezek az operátorok végigárnak egy listát. var result1 = list. Ők is rendelkeznek szelektor kifejezést használó változattal: List<int> list = new List<int>() { 10.5 var result3 = list. hiszen hárommal való osztás után ez lehet a legnagyobb maradék. Mindkét metódus az IComparable<T> interfészt használja. 67. a Count 32 bites egész számmal (int). // 536 Aggregate: ez az operátor lehetővé teszi tetszőleges művelet elvégzését és a részeredmények „felhalmozását”. 6. Az Aggregate három formában létezik. 4. Average és Sum: a lista elemeinek átlagát illetve összegét adják vissza. és végeredményként egyetlen értéket adnak vissza (pl. amelyet az összes elemre alkalmaznak. // 8 var result2 = list. míg a LongCount 64 bites egész számmal (int64) tér vissza: List<int> list = new List<int>() { 10. 67. így minden ezt megvalósító típuson használhatóak.268 - . 6.35. 78.Sum((item) => item * 2). de megadhatunk feltételt is. 4. tetszés szerint megadhatunk neki egy kezdőértéket. elvégeznek egy műveletet minden elemen. 3. 78. A két operátor közötti különbség az eredmény nagyságában van. 44 }.

Végül a harmadik műveletnél kiszámoltuk a számok összegének egy százalékát (itt figyelni kell arra. hogy olyan algoritmust kell találjunk. hiszen tizedes törtet akarunk visszakapni).Aggregate(-1. ezzel pedig teljesítménynövekedést ér el. item) => result + item).12.12 PLINQ – Párhuzamos végrehajtás Napjainkban már egyáltalán nem jelentenek újdonságot a több maggal rendelkező processzorok. Párhuzamosság Amikor többszálú programokat készítünk alapvetően nem törődünk a hardver lehetőségeivel. var sum = list.0 bevezeti nekünk a Parallel Task Library–t és a jelen fejezet tárgyát a Parallel–LINQ–t. miszerint: . vagy egyszerre több „kérést” kell kezelnünk (pl. var percent = list. vagyis a lekérdezések párhuzamosításának lehetőségét. A párhuzamos programozás ugyanezt képes nyújtani. olyankor használjuk. itt megadtunk egy kezdőértéket. item : Az első esetben a Sum operátort szimuláltuk. 35.List<int> list = new List<int>() { 1.1 Többszálúság vs. hiszen minden művelet ugyanannyi ideig tart. ezt pedig Gene Amdahl bizonyította (Amdahl‟s Law). magyarul két processzor kétszer gyorsabb. 3.Aggregate(0. ezt nem kell magyarázni. még tömb is. A végeredményt tároló „változó” bármilyen típus lehet. A .Aggregate((result. amikor nem akarjuk. amely minden helyzetben a lehető leghatékonyabban tudja elosztani a munkát. (result. így teljesen jogosan jelentkezett az igény. var max = list. de képes a processzorok számának függvényében szétosztani a munkát a CPU –k között. hogy double típusként lássa a fordító a kezdőértéket. 2. item) => result + item. létrehozunk szálakat amelyek versengnek a processzoridőért. Ennek a módszernek a nagy hátránya.0. mint egy. hogy a háttérben futó szálak megzavarják a „főszál” kezelhetőségét (pl. hogy bármely processzor üresjáratban álljon.269 - . amelynél biztosan van nagyobb elem a listában. item) => item > result ? result). hogy a processzorok számának növelésével egyenes arányban nő a teljesítmény. nem tudjuk őket közvetlenül szétosztani az – esetleges – több processzor között.2 Teljesítmény Nagyon könnyen azt gondolhatjuk.NET 4. 4 }. result => result / 100). Ez az állítás nem teljesen igaz (ezt később a saját szemünkkel is látni fogjuk). ha egy böngészőben több oldalt is megnyitunk. (result. 35. hogy minél jobban ki tudjuk ezt használni. A második változatban maximumkeresést végeztünk. 35.12. Ilyenkor értelemszerűen nem fogunk különösebb teljesítménynövekedést kapni. egy kliens-szerver kapcsolat). anékül. nem akarjuk megvárni amíg mindegyik letöltődik). Ezt a módszert pl.

270 - .9).81) lesz vagyis 81% -os teljesítménynövekedést érhetünk el két processzor bevezetésével. amely jelen esetben egy szöveges file lesz. megfelelően nagy adatforrásra. amely az első paraméterként megadott fileba a második paraméterként megadott mennyiségű vezetéknévkeresztnév-kor-foglalkozás-megye adatokat ír. és a képlet (két processzort használva): Könnyen kiszámolható.exe E:\Data. míg a maradék egy óra nem.Egy párhuzamos program maximum olyan gyors lehet. Elsősorban szükségünk lesz valamilyen. amelyet a nem párhuzamosítható programrész használ fel.55 (1.12. innen pedig egyre lassabban nő az eredmény. tehát így futassuk a programot: DataGen. N pedig a processzorok száma. hogy mindig annyi processzor áll rendelkezésünkre.cs nevűt. Ha ezt a 9 órát párhuzamosítjuk. vagyis pontosan az az egy óra. A jegyzethez mellékelt forráskódok között megtaláljuk a DataGen.txt 10000000 .18. mivel ekkor P/N értéke már 0. P ekkor 0. a példa esetében 5 processzor már 550%-os növekedést jelent. mint a leghosszabb szekvenciális (tovább már nem párhuzamosítható) részegysége. míg (1 – P) az amelyik nem. hogy mekkora a maximum teljesítmény.3 PLINQ a gyakorlatban Ebben a fejezetben összehasonlítjuk a hagyományos és párhuzamos LINQ lekérdezések teljesítményét. 35. Vegyük észre. hogy az eredmény 1/0. tehát a fenti konkrét esetben a maximális teljesítmény a hagyományos futásidő tízszerese lehet (1 / (1 – 0. Nézzük meg. hogy a processzorok számának növelésével P/N a nullához tart. vagyis kimondhatjuk. hogy P/N a lehető legközelebb legyen nullához: ez nem feltétlenül jelent nagyon sokat. A következő példában tízmillió személlyel fogunk dolgozni. Ennek a programnak 9 órányi „része” párhuzamosítható. Vegyünk például egy programot.9). akkor a tétel alapján a programnak így is minimum egy órás futásideje lesz. Amdahl a következő képletet találta ki: Ahol P a program azon része. hat processzornál 0.9 lesz (9 óra = 90% = 0.15 és így tovább). amit a fenti esetből ki tudunk préselni. amely párhuzamosítható. hogy minden párhuzamosítható feladat maximum 1/(1 – P) nagyságrendű teljesítménynövekedést eredményezhet (feltételezve. amely 10 órán keresztül fut nem párhuzamosan.

Split( new char[] { ' ' }) let name = data[1] let age = int. hogy a . hogy mi történik: Ez a kép a processzorhasználatot mutatja. két dolog világosan látszik: 1. Felhasználjuk.IO kell var result = from line in lines let data = line.ReadLines(@"E:\Data.0–ban a File. // System. Lássuk az eredményt: . vagyis közvetlenül hajthatunk rajta végre lekérdezést: var lines = File. majd a megfelelő indexekről (az adatok sorrendjéért látogassuk meg a DataGen.Split( new char[] { ' ' }) let name = data[1] let age = int.271 - .Az elérési út persze tetszőleges.txt"). Most írjuk át a lekérdezést párhuzamosra.WriteLine(line).NET 4. egyik sem teljesít maximumon.Parse(data[2]) let job = data[3] where name == "István" && (age > 24 && age < 55) && job == "börtönõr" select line.cs–t) összeszedjük a szükséges adatokat. Elsőként szétvágunk minden egyes sort. nézzük meg. Keressük az összes 24 és 55 év közötti István nevű börtönőrt. Az eredmény írassuk ki egy egyszerű foreach ciklussal: foreach(var line in result) { Console.ReadLines metódus IEnumerable<string>-gel tér vissza. } A fenti lekérdezés egyelőre nem párhuzamosított.Parse(data[2]) let job = data[3] where name == "István" && (age > 24 && age < 55) && job == "börtönõr" select line. Most készítsük el a lekérdezést. 2.AsParallel() let data = line. ezt rendkívül egyszerűen tehetjük meg. mindössze az AsParallel metódust kell meghívnunk az adatforráson: var result = from line in lines. a két processzormag nem ugyanazt a teljesítményt adja le.

Concat(list2. A ParallelMergeOptions három taggal rendelkezik: a NotBuffered hatására azonnal visszakapunk minden egyes elemet a lekérdezésből. ha mindkét adatforrást AsParallel –lel jelüljük. akkor a következő lépésben a feldolgozandó adatot a rendszer a rendelkezésre álló processzorok száma alapján elosztja. Ez egy meglehetősen bonyolult téma. Analízis: a keretrendszer megvizsgálja. hogy miért csak bizonyos esetekben kell párhuzamosítanunk. Bár úgy tűnhet. A részeredmények összeillesztése. Minden olyan lekérdezés. hogy egyáltalán megéri-e párhuzamosan végrehajtani a lekérdezést. vagy megfelelnek a részeredmények is: var result = from x in data. Az AsParallel kiterjesztett metódus egy ParalellQuery objektumot ad vissza. hogy megértsük. az AutoBuffered periódikusan . join. ezért lehetőségünk van manuálisan kikényszeríteni a párhuzamosságot: var result = select x from data. a tesztgépen átlagosan 25% volt a párhuzamos lekérdezés előnye a hagyományoshoz képest (ez természetesen mindenkinél más és más lehet). ezt itt nem részletezzük. Ahhoz. Erre a részre is lehet ráhatásunk. 2. 3.272 - .ForceParallelism).WithExecutionMode(ParallelExecutionMode.AsParallel() . A kétoperandusú LINQ operátorokat (pl. hogy nagyon egyszerűen felgyorsíthatjuk a lekérdezéseinket a valóság ennél kegyetlenebb.A kép önmagáért beszél. ellenkező esetben nem fordul le: var result = list1. ismerni kell a párhuzamos lekérdezések munkamódszerét: 1. amely megvalósítja az IEnumerable interfészt. de a teljesítménykülönbség is.AsParallel()). 4. az adatforrástól függően több stratégia is létezik.NotBuffered).AsParallel(). Természetesen az ellenőrzést végrehajtó algoritmus sem tévedhetetlen.AsParallel() . hogy szekvenciálisan fog végrehajtódni (és egyúttal a lekérdezés futásidejéhez hozzáadódik a vizsgálat ideje is). Concat) csakis úgy használhatjuk párhuzamosan. miszerint egyszerre szeretnénk a végeredményt megkapni.WithMergeOptions(ParallelMergeOptions. amely nem tartalmaz legalább viszonylag összetett szűrést vagy egyéb „drága” műveletet szinte biztos. Ha az ítélet a párhuzamosságra nézve kedvező. Végrehajtás.

amely minden érvényes rendezést érvénytelenít. míg a FullyBuffered csak akkor küldi vissza az eredményt. hogy az első valamivel gyorsabban végzett. akkor látni fogjuk. hogy amíg az orderby mindig végrehajtja a rendezést addig az AsOrdered egyszerűen megőrzi az eredeti sorrendet. 35. ha kiíratjuk mindkét eredményt? A válasz meglepő lehet: result1: 0. hogy a PLINQ részegységekre bontja az adatforrást. 8. 7. 2. de nagy különbség van közöttük. 6. 9 }. 6. hogy az eredeti állapot fenntartása nem előnyös számunkra. Vajon mit kapunk. var result3 = from x in list. vagyis nem fogjuk sorban visszakapni az eredményeket (a fent mutatott eredmény nem lesz mindenkinél ugyanaz minden futásnál más sorrendet kap hatunk vissza). 4.ad vissza több elemet. 8. 4. hiszen tudjuk. var result1 = from x in list select x. 5. 3. tulajdonképpen még teljesítményromlás is lehet az eredménye.273 - . var result2 = from x in list. 7 Hogyan kaphattunk egy rendezett listából rendezetlen eredményt? A válasz nagyon egyszerű. 7. 1. és aszerint osztja szét az adatokat (nagy adatmennyiséggel a kettő közötti sebességkülönbség is jelentősen megnő. 8. 9 result2: 0. A két lekérdezés hasonlónak tűnik. 3. használjuk az AsUnordered metódust. . kiválasztunk néhány elemet egy listából és ezek alapján akarunk egy join műveletet végrehajtani. Ha lemérjük a végrehajtási időt. 5. 2. 6.AsOrdered() select x.AsParallel(). ennek pedig az az oka.4 Rendezés Nézzük a következő kódot: List<int> list = new List<int>() { 0. Gyakran előfordul. Tipikusan olyan helyzetekben akarunk ilyen lekérdezéseket használni. 4. 1. épp ezért a nem megfelelő lekérdezések parallel módon való indítása nem fogja az elvárt teljesítményt hozni. ahol nagy mennyiségű elemen sok vagy drága műveletet végzünk el. mint a második. mivel itt nyerhetünk a legtöbbet. Ekkor nem célszerű fenntartani a sorrendet. 1. pl. érdemes néhányezer elemre is tesztelni). hogy komoly számítási kapacitást igényel a rendszertől egy párhuzamos lekérdezés végrehajtása. 2.AsParallel() select x. Amennyiben számít az eredmény rendezettsége.12. vagy az AsParallel mellett meg kell hívnunk az AsOrdered kiterjesztett metódust: var result2 = from x in list. Láthatjuk. 3. 5. akkor vagy használnunk kell az orderby–t. 9. ha a lekérdezés teljesen kész van.AsParallel() orderby x select x.

Most pontosan azt kapjuk majd. a belső párhuzamos műveletsor végeztével visszaváltottunk szekvenciális módba. vagyis szabályozhatjuk. annak a szabályai. A következő példában megnézzük.274 - .AsSequential().12. ezért a példaprogramot illik fenntartással kezelni: var result = (from x in list. Írjuk át egy kicsit: var result2 = (from x in list. .Take(10).5 AsSequential Az AsSequential metódus épp ellenkezője az AsParallel–nek. A PLINQ egyelőre még nem teljesen kiforrott. épp ezért nem kapunk semmiféle teljesítménynövekedést.AsParallel() select x). hogy a Take operátor szekvenciálisan fut majd le függetlenül attól. hogy ez miért jó nekünk. A probléma az. vagyis a Take nem fogja vissza a sebességet. hogy mennyire bonyolult a „belső” lekérdezés.AsParallel() select x) . Kiválasztjuk az eredménylistából az első tíz elemet.35. amire várunk.Take(10). hogy mely operátorok lesznek mindig szekvenciálisan kezelve a jövőben valószínűleg változni fognak. hogy egy lekérdezés mely része legyen szekvenciális és melyik párhuzamos.

A „nagy” fizetős Professional/Ultimate/stb. 36. változatok mellett az Express változatok ingyenes és teljes körű szolgáltatást nyújtanak számunkra. hogy mi jelenjen meg indításkor. Ezután – alapbeállítás szerint – a kezdőlap jelenik meg amely a korábban megnyitott projectek mellett élő internetes kapcsolat esetén a fejlesztői blogok illetve hírcsatornák legfrissebb bejegyzéseit is megjeleníti.NET Framework programozásához az első számú fejlesztőeszköz a Microsoft Visual Studio termékcsaládja. Ekkor megjelenik a következő ablak: . de ez egyrészt még nem annyira elterjedt. ez letölthető a Microsoft oldaláról.36 Visual Studio A . ehhez válasszuk ki a Tools menü Options pontját.. másrészt nagy különbség nincs közöttük – aki az egyiket tudja használni annak a másikkal sem lehet problémája. ez a mi esetünkben a C# lesz.275 - . Érdemes telepíteni a Visual Studio 2008 szervízcsomagját is.1 Az első lépések A VS 2008 telepítéséhez legalább Windows XP SP2 szükséges. Beállíthatjuk.. Ebben a fejezetben a Visual Studio 2008 verzióval fogunk megismerkedni (a jegyzet írásának pillanatában már létezik a 2010 változat is. A VS első indításakor megkérdezi. hogy melyik nye lvhez tartozó beállításokkal működjön.

276 - .Amennyiben a Show all settings jelölőnégyzet üres. Most készítsük el az első programunkat. ehhez a File menü New Project pontját válasszuk ki: . hogy mit szeretnénk látni indításkor. akkor jelöljük be. majd a megfelenő listából válasszuk ki a Startup–ot: Itt beállíthatjuk.

a jegyzetben eddig is ilyeneket készítettünk. A Ctrl+Shift+B kombinációval csak fordítunk. különben nem látunk majd semmit). hogy nincs bekapcsolva. Az OK gombra kattintva elkészíthetjük a projectet. Alul pedig a project nevét és könyvtárát adhatjuk meg.sln és egy . Amennyiben nem működik az (valószínűleg) azt jelenti..ReadKey utasítást tenni a végére. ekkor egy .277 - . hogy a Visual Studio kiegészíti a forráskódot ezzel rengeteg gépeléstől megkímélve minket. . ez most egy Console Application lesz. A Tools menüből válasszuk ki ismét az Options–t és azon belül pedig a Text Editor elemet. Újdonságot jelenthet. hogy milyen típusú projectet akarunk készíteni.” jelölőnégyzetet kell megjelölni. A most megjelenő forráskód valószínűleg ismerős lesz. míg az F5 egyszerre fordít és futtat. A nem mentett változásokat a VS mindkét esetben automatikusan menti. hogy a Framework melyik verziójával akarunk dolgozni.Itt a Visual C# fül alatt a Windows elem következik.. ezt IntelliSense–nek hívják. A jobb fölső sarokban beállíthatjuk. Írjunk egy egyszerű Hello World! programot. Nyissuk meg a könyvtárat ahová mentettük. bár néhány (eddig ismeretlen) névteret már előre beállított a VS. Nézzük meg. ahol kiválaszthatjuk az előre elkészített sablonok közül. Ekkor a listából kiválaszthatjuk a C# nyelvet és azon belül pedig az IntelliSense menüt: Ahogy a képen is látható a „Show completion list. ezt az F5 gomb segítségével fordíthatjuk és futtathatjuk (ne felejtsünk el egy Console. hogy hogyan néz ki egy Visual Studio project.suo filet illetve egy mappát kell látnunk.

"{4909A576-5BF0-48AA-AB70-4B96835C00FF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {4909A576-5BF0-48AA-AB70-4B96835C00FF}. de a Solution Explorerben (és így fordításkor sem) nem szerepelnek.Debug|Any CPU.ActiveCfg = Release|Any CPU {4909A576-5BF0-48AA-AB70-4B96835C00FF}.csproj".cs file amelyben pl.suo file pedig a Solution tényleges fordítási adatait kapcsolóit tartalmazza bináris formában (hexa editorral többé-kevésbé olvashatóvá válik). Kezdjük a Solution Explorer–rel. . ez lesz minden project gyökere (egy Solution tartalmazhat több projectet is). A bin mappában találjuk a lefordított végleges programunkat vagy a debug vagy a release mappában attól függően. a program verziószáma.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal A projectek mellett a fordításra vonatkozó információk is megtalálhatóak itt. segítik pl.Build.0 = Debug|Any CPU {4909A576-5BF0-48AA-AB70-4B96835C00FF}. A .2 Felület Térjünk most vissza a VS–hez és nézzük meg. megtalálhatjuk benne a projectet leíró .278 - . láthatjuk. "ConsoleApplication1\ConsoleApplication1. 36. hogy hogyan fordítottuk (erről később bővebben). Az obj mappa a fordítás során keletkezett „mellékterméket” tárolja. Ezt a problémát megelőzhetjük rendszeres biztonsági mentéssel (nagyobb programok esetén érdemes valamilyen verziókezelő eszközt használni). akkor csak egy „fülecske” jelenik meg az ablak szélén).Release|Any CPU. a debuggolást (erről is később). hogy bár egyetlen exe kiterjesztésű filera számítottunk találunk még három ismeretlen filet is.Debug|Any CPU. Ez a file egy egyszerű szöveges file.Release|Any CPU. Ilyenkor két megoldás van: vagy kézzel újra fel kell venni (jobb klikk a projecten. majd Add/Existing Item). Format Version 10. Most nyissuk meg a mappát is. Amennyiben nem látjuk ezt az ablakot (ha az Auto Hide be van kapcsolva.00 # Visual Studio 2008 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApplication1". akkor a View menüben keressük meg. Ezek a Visual Studio számára hordoznak információt. amely a project fordításának információit tárolja. Solution–t írja le. vagy a csproj filet kell szerkeszteni. ezek lényegében átmeneti fileok a végleges program működéséhez nem szükségesek. valami ilyesmit kell látnunk: Microsoft Visual Studio Solution File. Ha megnézzük a mappa tartalmát. a gyártója illetve a COM interoperabilitásra vonatkozó információk vannak.Build. vagyis bár fizikailag jelen vannak. tulajdonosa.ActiveCfg = Debug|Any CPU {4909A576-5BF0-48AA-AB70-4B96835C00FF}. hogy miként épül fel a kezelőfelület. amelyben az aktuális Solution elemeit kezelhetjük. A Visual Studio 2008 hajlamos „eldobni” fileokat a projectekből.csproj filet. A Properties mappában található AssemblyInfo.Az sln file a projectet tartalamzó ún.

A „rajzszögre” kattintva az ablakot rögzíthetjük is. projecteket. A Solution–ön vagy a projecten jobb egérgombbal kattintva új forrásfileokat. stb. többek között a grafikus felületű alkalmazások alkotóelemeinek beállításakor. Többféle területen is felhasználjuk majd. A Properties ablakban a kiválasztott elem tulajdonságait állíthatjuk. adhatunk a programunkhoz. .279 - .

azokat szerkeszthetjük. illetve a hibák okának felderítése sem okoz nagy nehézséget. másrészt beszennyezzük a forráskódot ami így nehezebben olvashatóvá válik. mint. Nyílván úgy tudjuk a legjobban felderíteni a programjaink hibáit. neki főként grafikus felületű alkalmazások fejlesztésekor vesszük hasznát. 36. hiszen egyrészt be kell gépelni a parancsokat. hogy kiírjuk a konzolra ezeket az értékeket. ekkor innen tudjuk kiválasztani a komponenseket/vezérlőket. ha működés közben láthatjuk az objektumok állapotát.cs file fordítási beállításait láthatjuk. A Server Explorer segítségével (távoli) adatbázisokhoz kapcsolódhatunk.3 Debug A Visual Studio segítségével a programok javítása. Ebben a részben elsajátítjuk a hibakeresés alapjait: elsőként ismerkedjünk meg a breakpoint (vagy töréspont) fogalmával. Erre olyan „fapados” módszereket is használhatunk. illetve lekérdezéseket hajthatunk végre. Ennek persze megvan a maga hátránya.A képen a Program. A breakpointok segítségével a .280 - . Végül a ToolBox ablak maradt.

így kényelmesen megvizsgálhatjuk az objektumokat. akkor ezen a ponton a futása megáll. Fontos. ha az egérmutatót közvetlenül a változó fölé visszük: . A Visual Studio–ban a forráskód ablak bal oldalán lévő üres sáv szolgál töréspontok elhelyezésére: Ezután.281 - . A képen látható a Locals ablak. hogy a törést tartalmazó sor már nem fog lefutni. ha futtatjuk a programot. amely a változók állapotát/értékét mutatja. Ugyanígy elérhetjük ezeket az értékeket. vagyis a fenti képen s értéke még „baba” marad.program egy adott pontján lehetőségünk van megállítani annak futását.

Mind itt.4 Debug és Release A Visual Studio kétféle módban – Debug és Release – tud fordítani. Alapértelmezés szerint a Visual Studio Toolbar –ján ki kell tudnunk választani. akár 100% -os sebességveszteséget is kaphatunk. Amikor a sebességet is tesztelni akarjuk. mivel a lefordított programot kiegészíti a szükséges információkkal. hogy mit szereténk: Ha mégsincs ott. mind a Locals ablakban módosíthatjuk az objektumokat. akkor a ToolBar –on jobb gombbal kattintva jelöljük be a Debug elemet: . mivel – ahogyan a nevében is benne van – a debuggolást segíti. ha így próbálunk számításigényes műveleteket végezni (épp ezért a Debug módot csakis a program általános funkcióinak fejlesztésekor használjuk).282 - . Az F11 gombbal utasításonként lépkedhetünk debug módban. akkor a Release módot kell bevetnünk. Ebből kifolyólag ez a mód a lassabb. Az előbbit tipikusan a fejlesztés során használjuk. 36.

tehát egy COM DLL nem használható közvetlenül egy . A „főprogramot” pedig így készítjük el: using System. namespace TestNamespace { public class TestClass { public string Text = "Hello DLL!". Sokkal egyszerűbb lenne a dolgunk.dll main. Fordítani a következőképpen tudjuk: csc /target:library TestLib. vagyis egyértelműen meg tudjuk mondani. ha az újrahasznosítandó forráskódot külön tudnánk választani a tényleges programtól. Console.Text).37 Osztálykönyvtár Eddig mindig egyetlen futtatható állományt készítettünk.dll nevű filet kapunk. amely a Windows alapú rendszereken a DLL -t (Dynamic Link Library) jelenti. A DLL könyvtárak felépítése a használt fejlesztői platformtól függ. ez célszerű. } } Ezúttal névteret is megadtunk. akkor publikusként kell deklarálnunk. hogy a nagyobb osztályokat minden egyes alkalommal. utána a Visual Studio segítségével.cs Ezután egy TestLib.cs a file neve) nagyon egyszerű lesz: using System. Az osztályunk (TestLib. Erre a célra találták ki a shared library (~megosztott könyvtár) fogalmát. his zen most két különböző assembly– ről van szó. class Program { static public void Main() { TestClass tc = new TestClass(). Amire még figyelni kell. using TestNamespace. amikor használni akarjuk újra be kell másolni a forráskódba.283 - . ennek viszont megvan az a hátránya. az az osztály elérhetősége.NET programban (de megoldható). hogy más osztállyal ütközzön. vagyis ha kívűlről is el akarjuk érni ezt az osztályt. hogy mire gondoltunk.cs . ezzel időt és helyet takarítva meg. } } És így fordítjuk: csc /reference:TestLib.WriteLine(tc. Készítsük el az első osztálykönyvtárunkat először parancssort használva. mivel nem akarjuk.

Csináljunk elsőként egy Console Application–t mint az előző fejezetben. majd kattintsunk jobb egérgombbal a Solution–ön (a Solution Explorerben) és az Add menüből válasszuk ki a New Projectet: Majd a Class Library sablont .Most készítsük el ugyanezt Visual Studio–val.284 - .

285 - . mivel egy „Project with an Output Type. Ez azt jelenti. Most térjünk vissza a főprojecthez és nyissuk le a References fület..Az OK gombra kattintva a Solution–höz hozzáadódik az osztálykönyvtár. E zt megváltoztathatjuk. hogy ezután nem tudjuk futtatni a programot. ha a valóban futtatni kívánt projecten jobb egérgombbal kattintva kiválasztjuk a „Set as StartUp Project” pontot: Készítsük el az osztálykönyvtárat és fordítsuk le (jobb egérgomb a projecten és Build).. ez a már hozzáadott osztálykönyvtárakat tartalmazza: . hogy a frissen hozzáadott osztálykönyvtár lett a Solution „főprojectje”. Előfordulhat.” kezdetű hibaüzenetet kapunk.

hogy a már beépített osztálykönyvtárakból (.286 - . Nekünk természetesen ez utóbbi kell: Az Ok gombra kattintva a References alatt is megjelenik az új könyvtár. Ezután a szerkesztőablakban is láthatjuk az osztálykönyvtárunk névterét (ha mégsem akkor fordítsuk le az egész Solution–t): .NET) akarunk válogatni vagy megkeressük a nekünk kellő filet (Browse).Kattintsunk jobb egérgombbal rajta és válasszuk ki az Add Reference pontot: A megjelenő ablakban kiválaszthatjuk. esetleg az egyik aktuális projectre van szükségünk (Projects).

Sign up to vote on this title
UsefulNot useful