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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.24 Gyakorló feladatok IV. és már tudjuk. hogy csak olyan osztályokon képes végigiterálni. } public string Name { get. void Reset(). } } . Ennek object típussal kell visszatérnie. ami így néz ki: public interface IEnumerator { bool MoveNext(). } 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.Collections névtérben található. ha tudja. Mindkettő a System. interfészeket.Name = name. Végül a Current (read-only) tulajdonság az aktuális pozicióban lévő elemet adja vissza. de erről később). implicit konverzió). Ezért kell megvalósítani egyúttal az IEnumerator interfészt is. azaz -1 –re. ugyanis a ciklus meghívja a metódusát. object Current { get. A Reset alapértelmezésre állítja a mutatót. Elsőként nézzük az IEnumerable interfészt: public interface IEnumerable { IEnumerator GetEnumerator(). private set. é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.1 IEnumerator és IEnumerable Készítsünk osztályt. hiszen minden típusra működnie kell (létezik generikus változata is. 24. } } A MoveNext a következő elemre mozgatja a mutatót.

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

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

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

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

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

24.2 IComparable és IComparer Valósítsuk meg az IComparable illetve IComparer interfészt! Megoldás A második gyakorlati példánkban az IComparable interfészt fogjuk megvalósítani, amelyre gyakran van szükségünk. Ez az interfész általában olyan adatszerkezeteknél követelmény, amelyek az elemeiken megvalósítanak valamilyen rendezést. A generikus List típusmak is van rendező metódusa (Sort), amely ezzel a metódussal dolgozik. Az IComparable egyetlen metódussal a CompareTo–val rendelkezik, amely egy object típust kap paraméteréül:
class ComparableClass : IComparable { public ComparableClass(int value) { this.Value = value; } public int Value { get; private set; } public int CompareTo(object o) { if(o is ComparableClass) { ComparableClass c = (ComparableClass)o; return Value.CompareTo(c.Value); } else throw(new Exception("Nem megfelelõ objektum...")); } }

Az osztályban a beépített típusok CompareTo metódusát használtuk, hiszen ők mind megvalósítják ezt az interfészt. Ez a metódus -1–et ad vissza, ha a hívó fél kisebb, 0–át, ha egyenlő és 1–et ha nagyobb. A használata:
static public void Main() { List<ComparableClass> list = new List<ComparableClass>(); Random r = new Random(); for(int i = 0;i < 10;++i) { list.Add(new ComparableClass(r.Next(1000))); } foreach(ComparableClass c in list) { Console.Write("{0} ", c.Value); } Console.WriteLine("\nA rendezett lista:"); list.Sort(); foreach(ComparableClass c in list) { Console.Write("{0} ", c.Value);

- 161 -

} Console.WriteLine(); }

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

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

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

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

- 162 -

class Matrix { int[,] matrix; public Matrix(int n, int m) { matrix = new int[n, m]; } public int N { get { return matrix.GetLength(0); } } public int M { get { return matrix.GetLength(1); } } public int this[int idxn, int idxm] { get { return matrix[idxn, idxm]; } set { matrix[idxn, idxm] = value; } } static public Matrix operator+(Matrix lhs, Matrix rhs) { if(lhs.N != rhs.N || lhs.M != rhs.M) return null; Matrix result = new Matrix(lhs.N, lhs.M); for(int i = 0;i < lhs.N;++i) { for(int j = 0;j < lhs.M;++j) { result[i, j] = lhs[i, j] + rhs[i, j]; } } return result; } }

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

- 163 -

25

Delegate–ek

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

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

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

A delegate hívásakor az összes a listáján lévő metódust neghívja. A delegate–ekhez a += illetve + operátorokkal hozzáadni a -= és – operátorokkal elvenni tudunk metódusokat:
class Test { public delegate void TestDelegate(string msg); private TestDelegate handler; public Test() { handler += Test.StaticMethod; handler += this.InstanceMethod; } static public void StaticMethod(string msg) { Console.WriteLine(msg); } public void InstanceMethod(string msg) { Console.WriteLine(msg); } public void CallDelegate(string msg) { handler(msg); } }

- 164 -

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

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

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

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

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

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

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

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

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

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

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

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

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 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- 217 -

32

Állománykezelés

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

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

A program pedig:
using System; using System.IO; class Program { static public void Main() { FileStream fs = new FileStream("Test.txt", FileMode.Open); StreamReader sr = new StreamReader(fs); string s = sr.ReadLine(); while(s != null) { Console.WriteLine(s); s = sr.ReadLine(); } sr.Close(); fs.Close(); } }

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

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

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

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

Truncate

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

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

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

Delete Inheritable

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

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

Ehhez szükség lesz még a System.Text névtérre is. Most írjunk is a fileba. Erre a feladatra a StreamReader helyett a StreamWriter osztályt fogjuk használni:
using System; using System.IO; class Program { static public void Main() { FileStream fs = new FileStream("Test.txt", FileMode.Open, FileAccess.Write, FileShare.None); StreamWriter sw = new StreamWriter(fs); Random r = new Random(); for(int i = 0;i < 10;++i) { sw.Write(r.Next()); sw.Write(Environment.NewLine); } sw.Close(); fs.Close(); } }

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

- 220 -

using System; using System.IO; class Program { static public void Main() { BinaryWriter bw = new BinaryWriter(File.Create("file.bin")); for(int i = 0;i < 100;++i) { bw.Write(i); } bw.Close(); BinaryReader br = new BinaryReader( File.Open("file.bin", FileMode.Open)); while(br.PeekChar() != -1) { Console.WriteLine(br.ReadInt32()); } br.Close(); } }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

mivel egy „Project with an Output Type.. E zt megváltoztathatjuk.Az OK gombra kattintva a Solution–höz hozzáadódik az osztálykönyvtár. Ez azt jelenti.285 - . hogy ezután nem tudjuk futtatni a programot.. ez a már hozzáadott osztálykönyvtárakat tartalmazza: .” 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). hogy a frissen hozzáadott osztálykönyvtár lett a Solution „főprojectje”. Előfordulhat. Most térjünk vissza a főprojecthez és nyissuk le a References fület.

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): .Kattintsunk jobb egérgombbal rajta és válasszuk ki az Add Reference pontot: A megjelenő ablakban kiválaszthatjuk. hogy a már beépített osztálykönyvtárakból (. Nekünk természetesen ez utóbbi kell: Az Ok gombra kattintva a References alatt is megjelenik az új könyvtár.NET) akarunk válogatni vagy megkeressük a nekünk kellő filet (Browse).286 - . esetleg az egyik aktuális projectre van szükségünk (Projects).

Sign up to vote on this title
UsefulNot useful