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

A jegyzet ingyenesen letölthető a devPortal-ról: http://devportal.hu/content/CSharpjegyzet.aspx

-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 14

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

az általunk készített kód minden esetben a prefix operátort jelenti.Value < rhs. 22. Ugye ez értéktípusok esetében tökéletesen működik. MyInt rhs) { return lhs. míg a tágabbról szűkebbre konvertálást explicite (ezt jelölnünk kell) végzi. >=): static public bool operator<(MyInt lhs. ehelyett x++ esetében pontosan ugyanaz történik. >) és (<=. 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). de referenciatípusoknál az első lépésben nem az értéket hanem a referenciát tároljuk. hogy mégis megoldjuk a fenti problémát: az operátornak egy teljesen új objektumot kell visszaadnia.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). meghívja az értéket növelő kódrészletet. vagyis a késöbbi változás itt is érvényben lesz.Parse(rhs)). ezért 11–et kellene tartalmaznia. mint ha ++xet írtunk volna.152 - . Ezekkel hamarosan többet is foglalkozunk. . ekkor erre már nem mutat korábbi referencia. és visszadja az első lépésben félrerakott értéket. } static public explicit operator MyInt(string rhs) { return new MyInt(int.Nézzük az utolsó sort! Mivel y–nak a postfixes formában adtunk értéket. és bizony erre is létezik operátor. hogy kifejezetten postfixes operátort nem tudunk definiálni. Ugyanakkor létezik módszer arra. Viszont meghívhatjuk az általunk definiált operátorokat postfixes alakban. } 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. vagyis biztonságosan használható (viszont az új művelet (az objektum létrehozása) miatt a teljesítményre negatív hatással lehet (ez persze néha elfogadható)). ekkor az adott objektumot eltárolja a rendszer. ha a saját típusunk ilyesmire is képes legyen. 22. 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.Value.Value.Value > rhs. Természetesen szeretnénk. vagyis (<. A probléma. } static public bool operator>(MyInt lhs.

} Fontos. de bizonyos helyzetekben jól jön.5 Kompatibilitás más nyelvekkel Mivel nem minden nyelv teszi lehetővé az operátorok kiterjesztését ezért a CLS javasolja.Value). . hogy a konverziós operátorok mindig statikusak. //explicit konverzió Console. hogy készítsük el a hagyományos változatot is: static public MyInt Add(MyInt lhs.WriteLine("x == {0}. y. // implicit konverzió MyInt y = (MyInt)"20".} Ezeket most így használhatjuk: static public void Main() { MyInt x = 10. } static public MyInt operator+(MyInt lhs. } Ez természetesen nem kötelező. 22. x. MyInt rhs) { return new MyInt(lhs + rhs).153 - .Value).Value + rhs. MyInt rhs) { return new MyInt(lhs.Value. y == {1}".

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

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

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

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

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

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

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 delegatekhez egynél több metódust is hozzáadhatunk a += és + operátorokkal, valamint elvehetjük őket a -= és – operátorokkal. A delegate hívásakor az összes a listáján lévő metódust meghívja a megadott paraméterre.
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 -

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

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

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

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

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

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

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

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

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

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

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

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

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 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- 217 -

32

Állománykezelés

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

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

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

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

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

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

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

Truncate

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

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

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

Delete Inheritable

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

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

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

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

- 220 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

hogy mi jelenjen meg indításkor.1 Az első lépések A VS 2008 telepítéséhez legalább Windows XP SP2 szükséges. 36. ehhez válasszuk ki a Tools menü Options pontját. 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. Ekkor megjelenik a következő ablak: . ez a mi esetünkben a C# lesz. másrészt nagy különbség nincs közöttük – aki az egyiket tudja használni annak a másikkal sem lehet problémája.36 Visual Studio A . A VS első indításakor megkérdezi. 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. Érdemes telepíteni a Visual Studio 2008 szervízcsomagját is.275 - .NET Framework programozásához az első számú fejlesztőeszköz a Microsoft Visual Studio termékcsaládja.. de ez egyrészt még nem annyira elterjedt.. hogy melyik nyelvhez tartozó beállításokkal működjön. A „nagy” fizetős Professional/Ultimate/stb. Beállíthatjuk. Ebben a fejezetben a Visual Studio 2008 verzióval fogunk megismerkedni (a jegyzet írásának pillanatában már létezik a 2010 változat is.

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

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

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

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

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

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

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

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

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

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

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

Sign up to vote on this title
UsefulNot useful