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

-2-

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

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

2.3.1 2.3.2 2.3.3 3

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

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

4

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

5

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

5.8 6

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

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

6.3.1 6.3.2 7

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

8

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

9 10

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

11

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

12

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

13

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

13.5 13.6 13.7 13.7.1 14

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

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

15 16 17

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

18

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

19

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

20

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

21

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

22

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- 161 -

} Console.WriteLine(); }

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

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

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

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

- 162 -

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

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

- 163 -

25

Delegate–ek

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

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

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

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

- 164 -

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

WriteLine(t1 == t2). = new C1(). Console. // False . ha mindkettő értéke null. // False t1 -= Program. Console.WriteLine(t1 == t2). static public void Method() { } static public void Main() { Dlgt1 d1 = Program.WriteLine(t1 == t2). t2 = Program. 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ő.WriteLine(t1 == t2). static public void Method1() { } static public void Method2() { } static public void Main() { Test1 t1 = null. Dlgt2 d2 = d1.166 - . // True t1 += Program.Method2. c2. public delegate void Dlgt2().Method2. class Program { public delegate void Dlgt1(). Console.CMethod. // True t1 = Program.Method1. Test1 t2 = null.CMethod.Method.using System. // ez hiba } } Ugyanakkor ugyanazon delegate „típus” példányai között használhatjuk az == és != operátorokat. class C1 { public void CMethod() { } } class Program { public delegate void Test1(). C1 C1 t1 t2 } } c1 c2 += += = new C1(). Console. c1.Method1.

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

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

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

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

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

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

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

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

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

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

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 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

akkor

használhatjuk

a

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

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

- 200 -

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

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

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

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

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

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

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

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

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

- 202 -

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

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

- 203 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- 217 -

32

Állománykezelés

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

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

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

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

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

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

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

Truncate

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

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

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

Delete Inheritable

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

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

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

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

- 220 -

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

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

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

stb. Console.IO. készült: {1}". class Program { static public void PrintFileSystemInfo(FileSystemInfo fsi) { if((fsi.Attributes & FileAttributes. Készült: {1}". fi.Directory) != 0) { DirectoryInfo di = fsi as DirectoryInfo. } foreach(string s in Directory. Első példánkban írjuk ki mondjuk a C meghajtó gyökerének könyvtárait: using System. törlése. } } static public void Main() { foreach(string s in Directory. hogy a DirectoryInfo és a FileInfo is egy közös őstől a FileSystemInfo–ból származik (mindkettő konstruktora a vizsgált alany elérési . de a fileokra is kíváncsiak lehetünk.WriteLine("Könyvtár: {0}. Előbbiek (ahogyan a nevük is sugallja) információt szolgáltatnak.GetFiles("C:\\")) { PrintFileSystemInfo(new FileInfo(s)).CreationTime).GetDirectories("C:\\")) { Console. } } } Természetesen nem csak a könyvtárakra.WriteLine("File: {0}.CreationTime). using System.222 - . di.FullName. Ugyanazzal a metódussal írjuk ki az információkat kihasználva azt. Console. } } } Először a mappákon.WriteLine(s). using System.eszközökre. majd a fileokon megyünk végig. } else { FileInfo fi = fsi as FileInfo.FullName.GetDirectories("C:\\")) { PrintFileSystemInfo(new DirectoryInfo(s)). fi. class Program { static public void Main() { foreach(string s in Directory. míg az utóbbiak (többségükben statikus metódusok) bizonyos műveleteket (új könyvtár létrehozása.IO.) végeznek a filerendszeren. di. A programunk módosított változata némi plusz információval együtt ezeket is kiírja nekünk: 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. sw.txt".WriteLine("Dio"). A vizsgálatnál egy „bitenkénti és” műveletet hajtottunk végre. utóbbi memóriaigényes. A MemoryStream osztály jelentősen megkönnyíti a dolgunkat.WriteLine("Alma").Close().CreateText(). using System.útját várja paraméterként). } } } le. annak meggondolása az olvasó feladata. 32. // ha nem létezik a könyvtár if(Directory. Nyílván egy tömb vagy egy lista nem nyújtja a megfelelő szolgáltatásokat. hogy összegyűjtsünk nagy mennyiségű adatot. ezért a legegyszerűbb. string filePath = dirPath + "\\file. Eddig csak információkat kértünk könyvtárstruktúrát: using System. hogy ez miért és hogyan működik.3 In–memory streamek A .IO.CreateDirectory(dirPath).NET a MemoryStream osztályt biztosítja számunkra. sw.Exists == false) { // akkor elkészítjük és írunk bele StreamWriter sw = fi. így a metódusban csak meg kell vizsgálni. sw. amellyel memóriabeli adatfolyamokat írhatunk/olvashatunk. // ha nem létezik a file if(fi. } FileInfo fi = new FileInfo(filePath).223 - .Exists(dirPath) == false) { // akkor elkészítjük Directory. hogy éppen melyikkel van dolgunk és átkonvertálni a megfelelő típusra. amelyeket majd a folyamat végén ki akarunk írni a merevlemezre. előbbi rugalmatlan. class Program { static public void Main() { string dirPath = "C:\\test". mivel lehetőséget ad közvetlenül fileba írni a tartalmát. Mire is jók ezek a folyamok? Gyakran van szükségünk arra. A következő program erre mutat példát: . ha közvetlenül a memóriában tároljuk el az adatokat.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Sign up to vote on this title
UsefulNot useful