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

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

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

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

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

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

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

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

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

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

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

amelyeket a nyelv nem tart fenn speciális használatra. 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. de különleges jelentéssel bírnak.2 Megjegyzések A forráskódba megjegyzéseket tehetünk.WriteLine("Hello C#").ReadKey().16 - . Amennyiben lehetséges. metódusok. Megjegyzéseket a következőképpen hagyhatunk: using System. egy metódus leírása) magunknak vagy a többi fejlesztőnek. Ezzel egyrészt üzeneteket hagyhatunk (pl. // Ez egy egysoros komment Console. ami szintén az első célt szolgálja . class HelloWorld { static public void Main() { Console. a megfelelő fejezet bővebb információt ad majd ezekről az esetekről. másrészt a kommentek segítségével dokumentációt tudunk generálni. csak éppen élvezhetőbb formában. kerüljük a használatukat “hagyományos” változók. 3.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ó.1. /* Ez egy többsoros komment */ } } .

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

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

String System.C# típus . Alakítsuk át a “Hello C#” programot úgy.Int32 System..Byte System.Double System.. 16 bites egész szám (0.0 már lehetővé teszi. amikor értéket rendeltünk a változóhoz . Ezt az akciót a var kulcsszóval kivitelezhetjük.. 64 bites egész szám Előjel nélküli.ReadKey(). hogy a kiírandó szöveget egy változóba tesszük: using System. hogy a “rendes” vagy a . értéke igaz(1 vagy true) vagy hamis(0 vagy false) Előjeles. 32 bites egész szám (0.SByte System.Uint64 System. Ez természetesen nem jelenti azt. 8 bites egész szám (128.Decimal System. mint egy típustalan környezetet! Abban a pillanatban.WriteLine(message). Előjel nélküli..32767 Előjel nélküli. 16 bites egész szám (32768.Uint16 System.NET néven hivatkozunk egy típusra. 2147483647). benne a kiírandó szöveg string message="Hello C#".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.127) Előjeles.Int64 System. Console. class HelloWorld { static public void Main() { //string típusú változó. Általában olyankor tesszük ezt.65535) Előjeles. amikor hosszú típusnévről van szó.. 64 bites egész szám Unicode karakterek szekvenciája Minden más típus őse A forráskódban teljesen mindegy.19 - .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. hogy úgy használhatjuk a nyelvet.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.Char System. 32 bites egész szám (– 2147483647. } } A C# 3. Console..Uint32 System.Boolean System.Single 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.Int16 System.255) Egy Unicode karakter Logikai típus. vagy nehéz meghatározni a típust.

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

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

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

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

value-type-pointert ad vissza. boxObject).24 - . // kidobozolva vagyis a bedobozolt Az object típusú változón explicit típuskonverziót hajtottunk végre (erről hamarosan). A következő forráskód azt mutatja. amelynek semmi köze az eredetihez: using System. object z = x. hogy a bedobozolás után teljesen új objektum keletkezik. amely egy ún. Console. hogy miként tudunk “kézzel” dobozolni: int x = 10.WriteLine("X értéke: {0}". hogy most minden fordítva történik. Ezt a címet azonban nem használhatjuk közvetlenül a verembe másoláshoz. 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. 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. bár nagyon hasonlónak látszanak. mint a bedobozolásnál. Itt ugyanaz történik. Fontos még megjegyezni. 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. object obj = x. Az unboxing (vagy kidobozolás) a boxing ellentéte. // bedobozolva int y = (int)obj.nevezzük és nem tévesztendő össze a polimorfizmussal (hamarosan). A kidobozolás szintén érdekes folyamat: logikusan gondolkodva azt hinnénk. értéktípusunkból kinyerjük az eredeti értékét: int x = 0.WriteLine(x). A kettős másolás pazarlásnak tűnhet. Console. class Program { static public void Main() { int x = 10. z = (int)z + 10. // bedobozolva Console. így visszakaptuk az eredeti értéket. vagyis a vermen elkészítünk egy új értéktípust és átmásoljuk az értékeket. de ez egyrészt megkerülhetetlen szabály másrészt a JIT ezt is képes úgy optimalizálni. hogy ne legyen nagy teljesítményveszteség. } } . amely a halomra másolt és bedobozolt értéktípusra mutat.WriteLine(z). object boxObject = x.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

41 - . azaz 4 byte méretű típus.WriteLine(sizeof(int)). } } } Ez a program négyet fog kiírni.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. A programot az unsafe kapcsolóval kell lefordítanunk: csc /unsafe main.cs . hiszen az int típus 32 bites. public classProgram { static public void Main() { unsafe { Console.

42 - . amikor x értéke nem tíz. hogy meg kell vizsgálnunk egy állítást. 6.1 Szekvencia A legegyszerűbb vezérlési szerkezet a szekvencia. } } } . és attól függően. if(x == 10) // Ha x egyenlő 10 -zel { Console. Ez tulajdonképpen egymás után megszabott sorrendben végrehajtott utasításokból áll.WriteLine("x értéke 10"). 6. hogy azt a helyzetet is kezelni tudjuk . Ilyen esetekben elágazást használunk: using System. hogy igaz vagy hamis.WriteLine("x értéke nem 10"). if(x == 10) // Ha x egyenlő 10 -zel { Console.WriteLine("x értéke 10"). } else // Ha pedig nem { Console. public class Program { static public void Main() { int x = 11.2 Elágazás Gyakran előfordul. Ilyenkor használjuk az else ágat: using System. } } } Természetes az igény arra is.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. public class Program { static public void Main() { int x = 10.

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

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

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

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

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

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

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

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. A TPL számunkra érdekes része a párhuzamos ciklusok megjelenése. 6. using System.1 Yield A yield kifejezés lehetővé teszi.0 a for és a foreach ciklusok párhuzamosítását támogatja a következő módon: . ezekről egy későbbi fejezet szól. ezért ehhez a fejezethez a C# 4. amely a . erről egy későbbi fejezetben).0 –hoz készült fordító szükséges. a foreach ciklussal: using System. } } static public void Main() { foreach(int i in EnumerableMethod(10)) { Console.0 verziójában kapott helyet. } } } A yield működési elve a következő: a legelső metódushívásnál a ciklus megtesz egy lépést. hanem onnan folytatja ahol legutóbb abbahagytuk. public class Program { static public IEnumerable EnumerableMethod(int max) { for(int i = 0.++i) { yield return i.Collections.50 - . hogy egy ciklusból olyan osztályt generáljon a fordító.3.Write(i).3.6. ezután „kilépünk” a metódusból – de annak állapotát megőrizzük. A NET 4. 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. azaz a következő hívásnál nem újraindul a ciklus.NET 4.i < max. amely megvalósítja az IEnumerable interfészt és ezáltal használható legyen pl.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

.cs using System. } partial class PClass { partial void Do() { Console. 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. Egy parciális osztály darabjainak ugyanabban az assemblyben kell lenniük. Ennek a megoldásnak az a nagy előnye.cs partial.WriteLine("Hello!"). } } A két filet így tudjuk fordítani: csc main. } } 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. Csakis parciális osztály tartalmazhat parciális metódust. A C# 3. A partial kulcsszót ilyenkor is ki kell tenni minden előfordulásnál. a grafikus felületű alkalmazásoknál a kezdeti beállításokat az InitializeComponent metódus végzi. ott a beágyazott osztályok töredékei szétoszolhatnak a tartalmazó osztály darabjai között). ekkor a metódus deklarációja és definíciója szétoszlik: partial class PClass { partial void Do(). 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.cs A .Készítsünk egy másik forrásfilet is: // partial.91 - .0 már engedélyezi parciális metódusok használatát is. ezt teljes egészében a fordító készíti el). amikor az osztály egy részét a fordító generálja (pl. partial class PClass { public void Do() { Console. hogy könnyen ki tudjuk egészíteni ezeket a generált osztályokat.WriteLine("Hello!").

de csakis akkor. Ezeket a „belső” osztályokat beágyazott (nested) osztálynak nevezzük. } } } . akkor a „külső” osztályon keresztül érhetjük el. public Outer() { child = new Inner(this). adattagokat és más osztályokat is. Egy ilyen osztályt általában elrejtünk. public Inner(Outer o) { parent = o. } class Inner { Outer parent.Do(). a külső osztályra hivatkozó referenciát: class Outer { private int value = 11. // 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).WriteLine(parent.5 Beágyazott osztályok Egy osztály tartalmazhat metódusokat. private Inner child. ha a beágyazott osztály tárol egy.13.value). } public void Do() { child.Inner(). 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.92 - . } public void Do() { Console. de ha mégis publikus elérésűnek deklaráljuk.Inner x = new Outer.

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

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

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

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

97 - . viszont ő teljes mértékben „leállítja” az alkalmazást (ún. Most pedig megnézzük. hogy mennyi memóriát használhat fel egyszerre a G0 (amennyiben ezt az értéket elérjük. Ennek a metódusnak a feladata. Ha egy alkalmazás kifut a memóriából. ez pedig a destruktor. ha szükség van tisztításra. Hasonlóan működik a második is. Ez a mód sokkal kisebb G0 kerettel rendelkezik. Ez a limitálás a nulladik generációra vonatkozik. kivéve ha az átlépte a maximálisan kiszabott keretet. Stop-The-World módszer) a tisztítás idejére. Szerver módban minden egyes processzor külön heap–pel és GC–vel rendelkezik. A GC megsemmisítés előtt az objektumokon meghívja a hozzájuk tartozó destruktort. tehát azt szabja meg.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. másnéven Finalizer-t. 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. amikor fontos. 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. a GC beindul). amely felfüggeszti a program futását a tisztítás idejére. Ha meg akarjuk változtatni egy program GC módját. szól a GC–nek. 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. Ez az alapértelmezett mód. hogy felhasználói felület reszponzív maradjon.Ezt a módszert olyan alkalmazásoknál használjuk. hogy felszabadítsa az osztály . szükségünk lesz egy konfigurációs filera (erről egy későbbi fejezetben).

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

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

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

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

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

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

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

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

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

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. azt mindenki gondolja át maga. 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. hogy ellenőriznünk kell. de ekkor feltétel. ha a két operandusa közül pontosan az egyik igaz.Egy érdekesebb módszer két szám megcserélésére: használjuk a kizáró vagy operátort. 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 két számot írjuk fel kettes számrendszerben: x (= 10) = 01010 és y (= 20) =10100. Most lássuk. ahol nincs elég memória/regiszter manőverezni – tipikusan mikrokontrollerek esetébe n.107 - . Nézzük először a kódot: public void Swap(ref int x. mint az átmeneti változót használó társa. hogy x és y ne legyen egyenlő) Bár ez az eljárás hatékonyabbnak tűnik. x ^= y. hogy a metóduson belül állítsuk be (átadhatunk így már inicializált . ami akkor ad vissza igaz értéket. igazából ez egy hagyományos PC –n még lassúbb is lehet. A cím szerinti átadás másik formájában nem inicializált paramétert is átadhatunk. ref int y) { if(x != y) { x ^= y. y ^= x.

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

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

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

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

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

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

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

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

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

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

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

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

5 Statikus osztály Egy osztályt statikusnak jelölhetünk. Öröklődés). } } static public double Cos(double x) . A fordító minden esetben ellenőrzi ezeknek a feltételeknek a teljesülését. nem lehet példány-konstruktora (de statikus igen) és mindig lezárt (ld.3 Statikus metódus Statikus metódust a hagyományos metódusokhoz hasonlóan készítünk.17. A statikus konstruktortól eltérően rá nem vonatkozik.14. legalábbis direkt módon nem (az minden további nélkül működik. Egy statikus osztályból nem hozható létre példány. Példa: using System.14. hogy nem lehetnek paraméterei. 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. hanem egy osztályhoz kapcsolódó művelet elvégzése. mindössze a static kulcsszóra van szükségünk. } } } 17. Statikus metódust általában akkor használunk. ha egy példány referenciáját adjuk át neki). Ilyen metódus például az Int32 osztályhoz tartozó Parse statikus metódus is.120 - . Á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). A „leghíresebb” statikus metódus a Main. Példa: class Math { static public double PI { get { return 3. static class MathHelper { static public double PI { get { return 3. 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. 17. Ilyen metódus volt az előző példában az Init metódus is.

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

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

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

WriteLine("t1.x = {1}". t2. t1. Egy struktúra minden adattagja – amennyiben a konstruktorban nem adunk meg mást – automatikusan a megfelelő nulla értékre inicializálódik.18.124 - . struct Test { public int x. } Mindhárom struktúra hibás. amely típus hivatkozik az eredeti struktúrára: struct Test { Test t. egy struktúra nem tartalmazhat olyan típ usú tagot.x. } struct Test2 { Test1 t. t2.x = {0}. } class Program { static public void Main() { Test t1 = new Test(). } struct Test1 { Test2 t.x). Struktúra nem tartalmazhat saját magával megegyező típusú adattagot.x = 10. Az ok nagyon egyszerű: mivel a struktúrák direkt módon – nem referenciákon keresztül – tárolják az adattagjaikat. Console.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. Ugyanígy. 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). } } . 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). t2.

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

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

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

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

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

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

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

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

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

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

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

Object –et).Hamm"). Az első változatot (az alapértelmezett konstruktorral) így használhattuk: static public void Main() { Dog d = new Dog(). } public string Name { get. szintén itt lesznek majd az osztály által megvalósított interfészek is.136 - . Készítsük el az elméleti rész példáját (Állat-Kutya-Krokodil) C# nyelven. leszámítva a System. ugyanakkor megengedi több interfész impementálását (interfészekről hamarosan). Bővítsük hát ki: class Animal { public Animal(string name) { this. hogy paraméteres konstruktort készítettünk az ősosztálynak. A C# csakis egyszeres öröklődést engedélyez (vagyis minden osztály egyetlen ősosztályból származhat. } . } public void Eat() { Console.WriteLine("Hamm . set. Crocodile c = new Crocodile(). 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. A Kutya és Krokodil osztályok egyaránt megvalósítják az ősosztály (egyelőre szegényes) funkcionalitását. vagyis át kell gondolnunk a példányosítást.Name = name.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. } } Vegyük észre.

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

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

Ezt a jelenséget árnyékolásnak (shadow) nevezik. } } Ezután a Dog utódjai már nem látják az eredeti Eat metódust. Természetesen mi azt szeretnénk.WriteLine("Vau .HAMM . ha meghívnánk. hogy a fordítás hiba nélkül menne végbe. Tegyük fel. . Viszont készíthetünk belőle virtuális metódust.WriteLine("Hamm .A leszármazott osztályokban az override kulcsszóval mondjuk meg a fordítónak. } } class BigEvilCrocodile : Crocodile { public override void Eat() { Console.Kro .KRO . 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.Vau . } } class Dog : Animal { public new void Eat() { Console. hogy már létezik).HAMM"). Ezt a ne w kulcsszóval tehetjük meg: class Animal { public virtual void Eat() { Console. vagyis a new virtual kulcsszavakkal ellátott metódus lesz az új metódussorozat gyökere. És valóban. Ekkor a program ugyan lefordul.Hamm . akkor az új metódus futna le. de a fordító figyelmezet minket. 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.WriteLine("Kro . amelyet az utódai már kedvükre használhatnak. Azaz.Hamm"). így az ő leszármazottai is átdefiniálhatják a működését: class Crocodile : Animal { public override void Eat() { Console. Egy override–dal jelölt metódus automatikusan virtuális is lesz.Hamm"). 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.Hamm . hogy eltakarjuk az öröklött metódust. amikor a fordító felépíti a metódustáblát. hogy szándékosan takarjuk el az eredeti implementációt.139 - .Hamm").WriteLine("KRO . így tájékoztatnunk kell a fordítót.

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

WriteLine("Vau .Hamm . 20.Hamm . } } . hogy közös felületet biztosítsunk a leszármazottainak: using System. azaz a keresés legkésőbb az első virtuális változatnál megáll. 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.Hamm").Vau . azaz megtilthatjuk.Vau .141 - . } class Dog : Animal { public override void Eat() { Console. A létrehozásának célja az.WriteLine("Vau .3 Lezárt osztályok és metódusok Egy osztályt lezárhatunk. } } sealed class Dobermann : Dog { public override void Eat() // ez sem jó { } } 20.Hamm"). 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. abstract class Animal { abstract public void Eat().implementáció egy virtuális metódus lesz.4 Absztrakt osztályok Egy absztrakt osztályt nem lehet példányosítani.

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

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

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

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

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

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

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

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

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

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

míg a tágabbról szűkebbre ko nvertálást explicite (ezt jelölnünk kell) végzi. 22. ha a saját típusunk ilyesmire is képes legyen. >=): static public bool operator<(MyInt lhs. mint ha ++xet írtunk volna.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).Value < rhs. Ezekkel hamarosan többet is foglalkozunk. y == {1}".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.152 - . 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. ezért 11–et kellene tartalmaznia. ehelyett x++ esetében pontosa n ugyanaz történik. ahogyan szeretnénk).Parse(rhs)). // implicit konverzió MyInt y = (MyInt)"20". de nem fog úgy működni. MyInt rhs) { return lhs. és bizony erre is létezik operátor. >) és (<=. } static public explicit operator MyInt(string rhs) { return new MyInt(int.Value.Value). //explicit konverzió Console. A probléma gyökere.WriteLine("x == {0}.Value > rhs.Value. } static public bool operator>(MyInt lhs. y. } 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. } Ezeket most így használhatjuk: static public void Main() { MyInt x = 10. } . 22. 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.Nézzük az utolsó sort! Mivel y–nak a postfixes formában adtunk értéket. MyInt rhs) { return lhs. x. Természetesen szeretnénk. vagyis (<.

hogy készítsük el a hagyományos változatot is: static public MyInt Add(MyInt lhs. MyInt rhs) { return new MyInt(lhs. MyInt rhs) { return new MyInt(lhs + rhs). hogy a konverziós operátorok mindig statikusak.153 - . 22. de bizonyos helyzetekben jól jön.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. . } Ez természetesen nem kötelező.Value + rhs.Fontos.Value).

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

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

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

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

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

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

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 -

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

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

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

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

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

set. Még módosítsuk az eseménykezelőt is: . a második pedig az eseményhez kapcsolódó információk. Elsőként készítünk egy EventArgs osztályból származó új osztályt. 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. } public string Message { get.class Program { static public void Handler(string message) { Console. } } A sender paraméternek a this–szel adjuk meg az értékét. t. TestEventArgs e). mivel ugyanazt az eseményt használhatjuk különböző osztályokkal). Módosítsuk ennek megfelelően a fenti programot. de a Framework eseményei mind ilyenek) két paramétere van. az első az az objektum. amely az EventArgs osztályból származik. amely kiváltotta az eseményt. private void OnStatusChange() { if(TestStatusChanged != null) { TestStatusChanged(this.Message = message.170 - .WriteLine(message). t.Data = 11. new TestEventArgs("Az osztály állapota megváltozott")). } static public void Main() { Test t = new Test().Handler.TestStatusChanged += Program. } } 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. A második paraméter ekkor olyan típus lehet. } } Az eseményekhez rendelt eseménykezelőknek konvenció szerint (ettől eltérhetünk. 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.

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

Name. e.Connect(c1).Disconnect(c1).Connect(c3). 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. Client c3 = new Client("Tomi"). Nézzük meg a kliens osztályt: class Client { public Client(string name) { Name = name. ServerEventArgs e) { Console. } } Végül a Main: static public void Main() { Server server = new Server().Látható. server. server. } public void ServerMessageHandler(object sender.WriteLine("{0} üzenetet kapott: {1}". Client c2 = new Client("Béla"). server.Message).Connect(c2). Client c1 = new Client("Józsi"). } . server. this. set.172 - . } public string Name { get.

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

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

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

i < 10. A deklarációnál azonban kiköthetünk megszorításokat a paraméterre. vagy egyenlő vele. class Test<T> where T : struct { } Ez pedig épp az ellenkezője. for(int i = 0. A new() megszorítás olyan osztályra utal.Pop()). . } } 26. minden más esetben fordítási hibát kapunk.WriteLine(s. értéktípusra van szükség.++i) { s. vagyis T olyan típusnak felel meg amely vagy U – ból származik. 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.3 Generikus megszorítások Alapértelmezetten egy generikus paraméter bármely típust jelképezhet. amely rendelkezik alapértelmezett konstruktorral. } for(int i = 0.static public void Main() { Stack<int> s = new Stack<int>(10).Push(i).176 - .i < 10. Az U pedig ebben az esetben egy másik generikus paramétert jelöl.++i) { Console. 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.

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 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 -

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

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

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

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

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

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

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

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

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

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

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

persze a reflektív programozás ennél többről szól.31 Reflection A „reflection” fogalmát olyan programozási technikára alkalmazzuk. Console. using System. ha az objektumot úgy készítenénk el.GetType(). ahol a program (futás közben) képes megváltoztatni saját struktúráját és viselkedését.WriteLine(type).GetType()). // Test } } Megjegyzendő.Reflection. // ez kell class Test { } class Program { static public void Main() { Type type = Assembly. hogy fordítási időben semmilyen ellenőrzés sem történik a példányosítandó osztályra nézve. .WriteLine(t.216 - .CreateInstance(type). Type type = t. var t = Activator.GetCallingAssembly(). így ha elgépeltünk valamit az bizony kivételt fog dobni futáskor (System. lépjünk előre egyet: mi lenne. // 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). Console. class Test { } class Program { static public void Main() { Test t = new Test(). Vegyük a következő példát: using System. hogy egyáltalán nem írjuk le a new operátort: using System.GetType("Test"). 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).ArgumentNullException). Az erre a paradigmára épülő programozást reflektív programozásnak nevezzük. Ebben a fejezetben csak egy rövid példát találhat a kedves olvasó.

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 -

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

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

WriteLine(i). Üres elemeknél ezeket eg yszerre is deklarálhatjuk (<üres />). amelynek minden más elem a gyermekeleme (egyúttal minden nem-gyökérelem egy másik elem gyermeke kell legyen.WriteTo(fs). Minden elemnek rendelkeznie kell nyitó (<list>) és záró (</list>) tagekkel. using System. mstream.Close(). Minden XML dokumentum egyetlen gyökérelemmel rendelkezik (ez a fenti példában a <list>). sw.txt"). ez nem feltétlenül jelenti a gyökérelemet). 32. } } A MemoryStream–re „ráállítottunk” egy StreamWriter objektumot. class Program { static public void Main() { MemoryStream mstream = new MemoryStream(). StreamWriter sw = new StreamWriter(mstream).Create("inmem. 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. fs. hogy melyik verziót és milyen kódolással akarjuk használni. } sw.Flush(). mstream.Close().using System. FileStream fs = File.IO.224 - . amelynek elsődleges célja strukturált szöveg és információ megosztása az interneten keresztül.i < 1000. Lássunk egy példát: <?xml version="1.0" encoding="UTF-8" ?> <list> <item>1</item> <item>2</item> <item>3</item> </list> Az első sor megmondja. ezután következnek az adatok. . for(int i = 0.++i) { sw.Close().4 XM L Az XML általános célú leírónyelv. 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.

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

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

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

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

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

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

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

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

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

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

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

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

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

WriteLine("A szerver üzenete: {0}".238 - . A programunk jól működik. System. Nézzük a módosított klienst: using using using using System. Encoding. int.Write(data.Close(). 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().Close(). Ahhoz. A következő lépésben adatokat küldünk oda-vissza. hogy a szerver fogadni tudja a kliensünket meg kell mondanunk neki. 0. 0.Length).Net. } } } 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). System. Console.finally { client. hogy várjon amíg bejövő kapcsolat érkezik. } finally { stream.GetString(data.ASCII. length)).WriteLine(e. } } } . data. } catch(Exception e) { Console. NetworkStream stream = null. try { client = new TcpClient(args[0]. int length = stream.GetStream(). Persze szerveroldalon egy TcpClient objektumra lesz szükségünk.Sockets. stream = client. data. data = new byte[256].Length).AcceptTcpClient().Parse(args[1])).Message). byte[] data = Encoding. 0. de nem csinál túl sok mindent. stream. client. ezt adja vissza a metódus.Text. System.Net. ezt a TcpListener osztály AcceptTcpClient metódusával tehetjük meg: TcpClient client = server.Read(data.ASCII. class Program { static public void Main(string[] args) { TcpClient client = null.GetBytes("Hello szerver!").

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

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

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

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

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

} } A MAXCLIENT változó egy egyszerű egész s zám. foreach(Socket client in clientList) { Program. Az utolsó paraméterrel azt adjuk meg. null.Length. while(true) { if(r. Miután megfelelő számú kapcsolatot hoztunk létre elkezdünk „beszélgetni” a kliensekkel. míg a harmadikat hibákra ellenőrzi a Select. } while(true) { ArrayList copyList = new ArrayList(clientList). } System.Add(client). } Most nézzük a szervert: int i = 0.Send(data.CommunicateWithClient(client). meghatározzuk vele.244 - . Az első paraméternek megadott listát olvashatóságra. A ciklus minden iterációjában meghívja a Select metódust. while(i < MAXCLIENT) { Socket client = server.Accept().Next(1000) % 2 == 0) { byte[] data = new byte[256]. a másodikat írhatóságra. hogy maximum hány klienst fogunk kezelni. hogy folyamatosan üzenetet küldünk a szervernek (most csak egyirányú lesz a kommunikáció): Random r = new Random().ASCII. A CommunicateWithClient statikus metódus a már ismert módon olvassa a kliens üzenetét: .GetBytes("Hello szerver!"). data = Encoding. hogy mennyi ideig várjunk a kliensek válaszára (mikroszekundum). SocketFlags. ++i.Threading.Sleep(500). vagyis kiválasztjuk.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. null. 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). 1000). Socket. 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. clientList. client.Select(copyList. hogy melyik kliensnek van mondandója.None). data.Thread.

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

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

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

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

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

LINQ To Objects: ennek a fejezetnek a tárgya.A LINQ lehetővé teszi. Az Entity Framework használatához a Visual Studio 2008 első szervizcsomagjára van szükség. jelenlétük jelentősen megkönnyíti a dolgunkat. A fejezethez tartozó forráskódok megtalálhatóak a Sources\LINQ könyvtárban.35 LINQ To Objects A C# 3. listák. 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. Objektum és gyűjtemény inicializálók: vagyis a lehetőség. Miért jó ez nekünk? Napjainkban rengeteg adatforrás áll rendelkezésünkre.250 - . mivel a LINQ „framework” viszonylag könnyen kiegészíthető tetszés szerinti adatforrás használatához. Egy másik előnye pedig. vagyis a legtöbb hibát még fordítási időben el tudjuk kapni és kijavítani.0 bevezette a LINQ –t (Language Integrated Query). mint egy memóriában lévő gyűjteményt. 35. A LINQ család jelenleg három főcsapást jelölt ki. mivel kisebb projectekre illetve hobbifejlesztésekhez kiváló). 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 fentieken kívül számos third-party/hobbi project létezik. 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. A kettő közül a LINQ To Entites a „főnök”. XQuery az XML–hez. 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. memóriában lévő gyűjtemények.)..0–ban megjelent néhány újdonság részben a LINQ miatt került a nyelvbe. a LINQ To Objects teljes funkcionalitása ezekre épül. hogy könnyen. amely lehetővé teszi. a LINQ To SQL inkább csak technológiai demónak készült.1 Nyelvi eszközök A C# 3. Ezek a következők: Kiterjesztett metódusok (extension method): velük már korábban megismerkedtünk. lényegében az összes művelet az IEnumerable<T>/IEnumerable interfészeket egészíti ki. hogy a LINQ lekérdezések erősen típusosak. uniformizált úton kezeljük a különböző adatforrásokat.. ezek a következőek: LINQ To XML: XML dokumentumok lekérdezését és szerkesztését teszi lehetővé. illetve gyűjtemények esetében az elemeket: . vagyis pontosan ugyanúgy fogunk kezelni egy adatbázist. stb.

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

. amit jólesik. Azaz minden számhoz hozzáadunk tízet. lényegében pontosan ugyanúgy működik. using System. de akár ezt is írhattuk volna: var result = from number in list select (number + 10).Linq–t.WriteLine("{0}". 2.Collections. 22. 75.using System. foreach(var item in result) { Console. 89 }. } } } Elsősorban vegyük észre az új névteret a System.252 - .Generic. Most pedig jöjjön a lényeg. 55. using System. tehát ne felejtsük le. 12. 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. Rá lesz szükségünk mostantól az összes lekérdezést tartalmazó programban. míg az „azonosító” a forrás egyes tagjait jelöli a kiválasztás minden iterációjában. 11. 30.Linq. 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. class Program { static public void Main() { List<int> list = new List<int>() { 10. 4. var result = from number in list select number. nézzük a következő sort: var result = from number in list select number. item). 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.

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

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

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

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

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

"Töhötöm". Egy lekérdezés pontosan egy orderby/OrderBy–t és bármennyi ThenBy–t tartalmazhat. a következő példában neveket fogunk sorrendbe rakni. "Iván". name[1] select name. hogy az alapszabály mögé írjuk a további kritériumokat. míg második paraméterként megadhatunk egy tetszőleges IComparer<T> interfészt megvalósító osztályt. } } } Az Extension Method formában a ThenBy volt segítségünkre.OrderBy(name => name[0]) . Az OrderBy metódus egy másik változata két paramétert fogad. var result1 = names.258 - . "Jolán". az első a rendezés alapszabálya. "Vazul". "Tamás" }. foreach(var item in result2) { Console. class Program { static public void Main() { List<string> names = new List<string>() { "István".Generic. var result2 = from name in names orderby name[0]. using System.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. 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. "Jenő".WriteLine(item).A rendezés a gyorsrendezés algoritmusát használja. "Béla". hogy az azonos kezdőbetűvel rendelkezőket tovább rendezzük a név második karaktere alapján: using System. 35.ThenBy(name => name[1]). "Viktória".Collections. using System. "Judit". mégpedig úgy.Linq. "Balázs".

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

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

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

} public string LastName { get. de a LINQ To Objects támogatja ezt is. set.} public string Email { get. } } class Order { public public public public public } int CustomerID { get. } public string PhoneNumber { get. set. } DateTime OrderDate { get. set. set. set. amely segítségével egyértelműen meg tudjuk különböztetni őket. amely visszaadja minden vásárló rendelését. A „join”–műveletek azon az egyszerű feltételen alapulnak. } } Ez a fent említett webáruház egy egyszerű megvalósítása. ezek lesznek az idegen kulcsok. hogy egy listában egy példányból – és így kulcsból – csak egy lehet). pl.6 Listák összekapcsolása A relációs adatbázisok egyik alapköve. } public int Quantity { get. Nézzünk egy példát: class Customer { public int ID { get. ez lesz az elsődleges kulcs (tegyük fel.cs tárolja): . egy webáruházban a vevő-árúrendelés adatokat. Az Order osztály mindkét példányra tartalmaz referenciát (hiszen minden rendelés egy vevőtermék párost igényel). } int ProductID { get. Mind a Customer mind a Product osztály rendelkezik egy ID nevű tulajdonsággal. 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).35. } DateTime? DeliverDate { get. set. } public Address Address { get. set. hogy egy lekérdezésben több táblát összekapcsolhatunk (join) egy lekérdezéssel. set. } string Note { get. } public string FirstName { get. } public string ProductName { get. set. } class Product { public int ID { get. Memóriában lévő gyűjtemények esetében ez viszonlag ritkán szükséges. set. set. set. 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. set. set. Írjunk egy lekérdezést.262 - .

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

GetOrderList() on customer.WriteLine(item).ProductID. foreach(var item in result1) { Console.Linq.NET 1. class Program { static public void Main() { ArrayList list = new ArrayList(). // kivétel } } } .ToString() }.Name.Add("dió"). hiszen a régi ArrayList osztály nem valósítja meg a generikus IEnumerable–t.Add(12345). var result2 = from item in list.264 - . ha LTO –t akarunk használni. System.GetCustomerList() join order in DataClass.var result = from customer in DataClass. order.OfType<string>() select item.Collections. } 35. System. var result1 = from item in list. list.InvalidCastException) dob: using using using using System.DefaultIfEmpty() select new { Name = customer.LastName.Add("alma"). elsődlegesen a .Cast<string>() select item.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. list.CustomerID into tmpresult from o in tmpresult.Generic.Product). order.FirstName + " " + customer. list. foreach(var order in result) { Console.0–val való kompatibilitás miatt léteznek.ID equals order. 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. System. míg a Cast kivételt (System. ezért kasztolni kell. Product = o == null ? "nincs rendelés" : o.WriteLine("{0}: {1}".Collections.

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

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

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

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

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

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

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

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

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

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

ehhez válasszuk ki a Tools menü Options pontját. 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.275 - . Érdemes telepíteni a Visual Studio 2008 szervízcsomagját is. 36. Ekkor megjelenik a következő ablak: . 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.36 Visual Studio A . 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. Beállíthatjuk. ez letölthető a Microsoft oldaláról. A VS első indításakor megkérdezi.. de ez egyrészt még nem annyira elterjedt.1 Az első lépések A VS 2008 telepítéséhez legalább Windows XP SP2 szükséges. változatok mellett az Express változatok ingyenes és teljes körű szolgáltatást nyújtanak számunkra. hogy melyik nye lvhez tartozó beállításokkal működjön. ez a mi esetünkben a C# lesz. A „nagy” fizetős Professional/Ultimate/stb.. hogy mi jelenjen meg indításkor.

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

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

akkor csak egy „fülecske” jelenik meg az ablak szélén). 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).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). Solution–t írja le. megtalálhatjuk benne a projectet leíró . "ConsoleApplication1\ConsoleApplication1. segítik pl.Release|Any CPU. a debuggolást (erről is később). akkor a View menüben keressük meg.ActiveCfg = Release|Any CPU {4909A576-5BF0-48AA-AB70-4B96835C00FF}.ActiveCfg = Debug|Any CPU {4909A576-5BF0-48AA-AB70-4B96835C00FF}.Az sln file a projectet tartalamzó ún. a program verziószáma. Amennyiben nem látjuk ezt az ablakot (ha az Auto Hide be van kapcsolva. 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. Ha megnézzük a mappa tartalmát. tulajdonosa.Debug|Any CPU. A Visual Studio 2008 hajlamos „eldobni” fileokat a projectekből. Ezek a Visual Studio számára hordoznak információt. amelyben az aktuális Solution elemeit kezelhetjük.csproj filet. vagy a csproj filet kell szerkeszteni. hogy bár egyetlen exe kiterjesztésű filera számítottunk találunk még három ismeretlen filet is. majd Add/Existing Item). vagyis bár fizikailag jelen vannak. valami ilyesmit kell látnunk: Microsoft Visual Studio Solution File.csproj". ez lesz minden project gyökere (egy Solution tartalmazhat több projectet is). hogy hogyan fordítottuk (erről később bővebben). láthatjuk.2 Felület Térjünk most vissza a VS–hez és nézzük meg. a gyártója illetve a COM interoperabilitásra vonatkozó információk vannak.0 = Debug|Any CPU {4909A576-5BF0-48AA-AB70-4B96835C00FF}.Build. Format Version 10.Debug|Any CPU.278 - . hogy miként épül fel a kezelőfelület. Az obj mappa a fordítás során keletkezett „mellékterméket” tárolja.Release|Any CPU. amely a project fordításának információit tárolja.Build. A . Kezdjük a Solution Explorer–rel. Ilyenkor két megoldás van: vagy kézzel újra fel kell venni (jobb klikk a projecten. de a Solution Explorerben (és így fordításkor sem) nem szerepelnek. . "{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}.cs file amelyben pl. A Properties mappában található AssemblyInfo.00 # Visual Studio 2008 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApplication1". Ez a file egy egyszerű szöveges file. ezek lényegében átmeneti fileok a végleges program működéséhez nem szükségesek.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. 36. Most nyissuk meg a mappát is.

A „rajzszögre” kattintva az ablakot rögzíthetjük is. A Solution–ön vagy a projecten jobb egérgombbal kattintva új forrásfileokat. . 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.279 - . stb. A Properties ablakban a kiválasztott elem tulajdonságait állíthatjuk. projecteket. adhatunk a programunkhoz.

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

281 - . így kényelmesen megvizsgálhatjuk az objektumokat. ha az egérmutatót közvetlenül a változó fölé visszük: . amely a változók állapotát/értékét mutatja. Fontos. hogy a törést tartalmazó sor már nem fog lefutni.program egy adott pontján lehetőségünk van megállítani annak futását. vagyis a fenti képen s értéke még „baba” marad. 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. ha futtatjuk a programot. Ugyanígy elérhetjük ezeket az értékeket. A képen látható a Locals ablak. akkor ezen a ponton a futása megáll.

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

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

284 - . Csináljunk elsőként egy Console Application–t mint az előző fejezetben.Most készítsük el ugyanezt Visual Studio–val. 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 .

Előfordulhat.” kezdetű hibaüzenetet kapunk. 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)... Most térjünk vissza a főprojecthez és nyissuk le a References fület. Ez azt jelenti. mivel egy „Project with an Output Type. hogy ezután nem tudjuk futtatni a programot. ez a már hozzáadott osztálykönyvtárakat tartalmazza: . E zt megváltoztathatjuk. hogy a frissen hozzáadott osztálykönyvtár lett a Solution „főprojectje”.285 - .Az OK gombra kattintva a Solution–höz hozzáadódik az osztálykö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): . esetleg az egyik aktuális projectre van szükségünk (Projects). Nekünk természetesen ez utóbbi kell: Az Ok gombra kattintva a References alatt is megjelenik az új könyvtár.286 - . hogy a már beépített osztálykönyvtárakból (.Kattintsunk jobb egérgombbal rajta és válasszuk ki az Add Reference pontot: A megjelenő ablakban kiválaszthatjuk.NET) akarunk válogatni vagy megkeressük a nekünk kellő filet (Browse).

Sign up to vote on this title
UsefulNot useful