Poznáváme C# a Microsoft
Transkript
Poznáváme C# a Microsoft
Poznávame C# a Microsoft .NET Poznáváme C# a Microsoft .NET – 1.díl..............................................................................5 Důvody vzniku ..............................................................................................................................5 Princip běhového prostředí .............................................................................................................5 Klíčové vlastnosti ..........................................................................................................................6 Typy aplikací ................................................................................................................................6 Poznáváme C# a Microsoft .NET – 2. díl .............................................................................7 Vlastnosti jazyka ...........................................................................................................................7 Požadavky na běh .........................................................................................................................7 Vývojová prostředí ........................................................................................................................8 První program „Ahoj světe“ ............................................................................................................8 Základní datové typy .....................................................................................................................9 Rozdělení datových typů ................................................................................................................9 Příklad na konec............................................................................................................................9 Poznáváme C# a Microsoft .NET – 3. díl ........................................................................... 10 Proč objekty?.............................................................................................................................. 10 Objekty a třídy............................................................................................................................ 10 Zapouzdření objektů .................................................................................................................... 11 Implementace v C#..................................................................................................................... 12 Konstruktor ................................................................................................................................ 12 Atributy a vlastnosti .................................................................................................................... 12 Metody ...................................................................................................................................... 13 Specifikátory přístupu .................................................................................................................. 13 Ukázková třída žárovka................................................................................................................ 14 Poznáváme C# a Microsoft .NET – 4. díl ........................................................................... 16 Jednoduchá dědičnost v C# .......................................................................................................... 16 Využití dědičnosti ........................................................................................................................ 16 Implementace dědičnosti v C#...................................................................................................... 17 Specifikátor přístupu protected ..................................................................................................... 17 Virtuální metody a jejich překrytí pomocí new a override.................................................................. 17 Metody a konstruktory se vstupními parametry............................................................................... 18 Přetěžování metod a konstruktorů ................................................................................................. 18 Příklad se zaměstnanci v C# ......................................................................................................... 18 Poznáváme C# a Microsoft .NET – 5. díl ........................................................................... 21 Statické členy tříd ....................................................................................................................... 21 Statické atributy ......................................................................................................................... 21 Statické metody .......................................................................................................................... 22 Statické konstruktory .................................................................................................................. 23 Polymorfismus ............................................................................................................................ 23 Poznáváme C# a Microsoft.NET – 6. díl............................................................................ 25 Abstraktní třídy ........................................................................................................................... 25 Rozhraní .................................................................................................................................... 27 Rozhraní a dědičnost ................................................................................................................... 28 Vícenásobná implementace........................................................................................................... 29 Poznáváme C# a Microsoft .NET – 7. díl ........................................................................... 29 Uzavřené třídy ............................................................................................................................ 29 Privátní konstruktory ................................................................................................................... 30 Specifikátor přístupu internal ........................................................................................................ 30 Vnořené třídy.............................................................................................................................. 31 Konstanty .................................................................................................................................. 32 Proměnné pouze pro čtení ............................................................................................................ 32 Poznáváme C# a Microsoft.NET – 8. díl............................................................................ 33 Relační operátory ........................................................................................................................ 33 Logické operátory........................................................................................................................ 34 Podmínkové příkazy..................................................................................................................... 35 Příkaz if ..................................................................................................................................... 35 Ternární operátor ........................................................................................................................ 36 Příkaz switch .............................................................................................................................. 36 Přetěžování operátorů.................................................................................................................. 37 Poznáváme C# a Microsoft.NET – 9. díl............................................................................ 38 Prefixový versus postfixový zápis inkrementace/dekrementace ......................................................... 38 Příkazy pro vytváření cyklů........................................................................................................... 39 Příkazy break a continue .............................................................................................................. 39 Příkaz while ................................................................................................................................ 39 Příkaz do-while........................................................................................................................ 40 Příkaz for ................................................................................................................................... 40 Poznáváme C# a Microsoft.NET – 10. díl .......................................................................... 42 Struktury v C#............................................................................................................................ 42 Struktury a konstruktory .............................................................................................................. 43 Výčtové typy .............................................................................................................................. 43 Číselná reprezentace a bázové typu výčtů ...................................................................................... 44 1 Poznáváme C# a Microsoft.NET – 11. díl .......................................................................... 45 Co je pole? ................................................................................................................................. 45 Deklarace pole ............................................................................................................................ 45 Další možnosti inicializace ............................................................................................................ 46 Vlastnost Length ......................................................................................................................... 47 Vícerozměrná pole....................................................................................................................... 47 Pole polí ..................................................................................................................................... 48 Pole jako parametr metody........................................................................................................... 49 Poznáváme C# a Microsoft.NET – 12. díl .......................................................................... 50 System.Array.............................................................................................................................. 50 Zjištění počtu rozměrů pole .......................................................................................................... 50 Další možnost vytvoření pole ........................................................................................................ 50 Převrácení pořadí prvků pole ........................................................................................................ 50 Získáni indexů okrajů pole............................................................................................................ 51 Mělká kopie pole ......................................................................................................................... 51 Vyhledání prvku v jednorozměrném poli......................................................................................... 52 Cyklus foreach ............................................................................................................................ 52 Poznáváme C# a Microsoft.NET 13. díl – výjimky .............................................................. 53 Co jsou výjimky?......................................................................................................................... 53 Proč výjimky? ............................................................................................................................. 53 Vyhození výjimky ........................................................................................................................ 54 Chráněné bloky a handlery výjimek ............................................................................................... 54 Handlery pro více typů výjimek ..................................................................................................... 55 Závěrečné bloky.......................................................................................................................... 55 Poznáváme C# a Microsoft.NET 14. díl – výjimky po druhé ................................................. 56 Předávání výjimek ....................................................................................................................... 56 Vlastnost Message třídy System.Exception ..................................................................................... 57 Vlastnost StackTrace ................................................................................................................... 58 Vytváření vlastních výjimek .......................................................................................................... 58 Omezení při definici více handlerů ................................................................................................. 59 Opakované vyhození výjimky........................................................................................................ 59 Zabalení výjimky......................................................................................................................... 60 Poznáváme C# a Microsoft.NET 15. díl – delegáty ............................................................. 61 K čemu delegáty?........................................................................................................................ 62 Deklarace delegáta ...................................................................................................................... 62 Vytvoření instance delegáta.......................................................................................................... 62 Volání delegáta ........................................................................................................................... 63 Skládání delegátů........................................................................................................................ 64 Poznáváme C# a Microsoft.NET 16. díl – události .............................................................. 65 Co jsou události?......................................................................................................................... 65 Deklarace události ....................................................................................................................... 65 Vyvolání události ......................................................................................................................... 65 Ukázková třída vyvolávající událost ............................................................................................... 66 Obsluha událostí ......................................................................................................................... 67 Události a dědičnost .................................................................................................................... 68 Poznáváme C# a Microsoft.NET 17. díl – události podruhé .................................................. 69 Doporučený způsob implementace tříd ........................................................................................... 69 Delegát typu System.EventHandler ............................................................................................... 69 Přenášení dodatečných údajů o události ......................................................................................... 71 Poznáváme C# a Microsoft.NET 18. díl – indexery ............................................................. 72 Indexery .................................................................................................................................... 73 Použití cyklu foreach se třídou s indexery ....................................................................................... 75 Poznáváme C# a Microsoft.NET 19. díl – převody číselných typů.......................................... 77 Konverze v C# ............................................................................................................................ 77 Explicitní číselné konverze ............................................................................................................ 77 Klíčové slovo checked a kontrolované převody ................................................................................ 78 Konverze pomocí třídy System.Convert .......................................................................................... 79 Poznáváme C# a Microsoft.NET 20. díl – konverze referenčních typů.................................... 80 Explicitní konverze ...................................................................................................................... 81 Příklady s obrazci ........................................................................................................................ 81 Přetypování na proměnnou rozhraní .............................................................................................. 82 Referenční proměnná typu abstraktní třída ..................................................................................... 83 Operátor is ................................................................................................................................. 83 Operátor as ................................................................................................................................ 84 Poznáváme C# a Microsoft.NET 21. díl – komentování a dokumentace ................................. 84 Komentování kódu ...................................................................................................................... 84 Běžné komentáře v C# ................................................................................................................ 85 Dokumentace v XML .................................................................................................................... 85 Kontextová nápověda ve Visual C# .NET ........................................................................................ 88 Poznáváme C# a Microsoft.NET 22. díl – uživatelsky definované konverze ............................ 89 2 Uživatelské konverze ................................................................................................................... 89 Konverze mezi třídami a strukturami ............................................................................................. 91 Poznáváme C# a Microsoft.NET 23. díl – direktivy pre-procesoru ......................................... 94 Direktivy určené k podmíněnému vykonání určité sekce kódu ........................................................... 94 Direktiva #line ............................................................................................................................ 95 Diagnostické direktivy.................................................................................................................. 97 Direktivy pro určení specifických bloků kódu ................................................................................... 97 Poznáváme C# a Microsoft .NET 24. díl – speciální případy metod ....................................... 98 Metody s proměnným počtem parametrů ....................................................................................... 98 Modifikátor ref ............................................................................................................................ 99 Modifikátor out.......................................................................................................................... 100 Poznáváme C# a Microsoft.NET 25. díl - třídy kolekcí ....................................................... 101 Třídy kolekcí a jmenný prostor System.Collections ........................................................................ 101 Třída ArrayList .......................................................................................................................... 101 Poznáváme C# a Microsoft.NET 26. díl – třídy kolekcí II. .................................................. 103 Třída Stack ............................................................................................................................... 103 Třída Queue.............................................................................................................................. 104 Slovníky a rozhraní IDictionary ................................................................................................... 105 Třída HashTable ........................................................................................................................ 105 Poznáváme C# a Microsoft.NET 27. díl – třídy kolekcí III. ................................................. 106 Definice vlastního porovnání instance .......................................................................................... 106 Definice více možných způsobů porovnání .................................................................................... 108 Poznáváme C# a Microsoft.NET 28. díl – HashProvidery a Klonování .................................. 110 Komplexnější využití hešových tabulek a vlastní HashProvidery....................................................... 111 Kopírování instancí a Rozhraní ICloneable .................................................................................... 113 Poznáváme C# a Microsoft.NET 29. díl – řetězce ............................................................. 115 Bližší pohled na třídu System.String ............................................................................................ 115 Poznáváme C# a Microsoft.NET 30. díl – StringBuilder a Regulární výrazy........................... 118 Třída StringBuilder .................................................................................................................... 118 Úvod do regulárních výrazů v C# ................................................................................................ 119 Poznáváme C# a Microsoft. NET 31. díl – regulární výrazy ................................................ 120 Libovolné znaky v regulárních výrazech ....................................................................................... 121 Intervaly znaků......................................................................................................................... 121 Skupiny v regulárních výrazech................................................................................................... 122 Poznáváme C# a Microsoft.NET 32. díl – I/O a streamy .................................................... 123 I/O operace v rámci .NET ........................................................................................................... 123 Operace se soubory ................................................................................................................... 124 Třídy BinaryWriter a BinaryReader............................................................................................... 125 Poznáváme C# a Microsoft.NET 33. díl – I/O podruhé ...................................................... 126 Třídy TextReader a TextWriter .................................................................................................... 126 Práce s pamětí pomocí proudů .................................................................................................... 127 Třída File.................................................................................................................................. 128 Poznáváme C# a Microsoft.NET 34. díl – informace o adresářích a sledování souborového systému .................................................................................................................... 129 Třída Directory.......................................................................................................................... 129 Třídy FileInfo a DirectoryInfo ...................................................................................................... 129 Sledování změn souborového systému ......................................................................................... 130 Poznáváme C# a Microsoft.NET 35. díl – izolovaná úložiště ............................................... 131 Třídy StringReader a StringWriter................................................................................................ 132 Izolovaná úložiště ..................................................................................................................... 133 Poznáváme C# a Microsoft.NET 36. díl – úvod do reflexe.................................................. 135 Aplikace v .NET frameworku ....................................................................................................... 135 Reflexe v .NET .......................................................................................................................... 135 Poznáváme C# a Microsoft.NET 37. díl – použití reflexe.................................................... 138 Třída System.Type .................................................................................................................... 138 Dynamické vytvoření instance třídy ............................................................................................. 139 Reflexe datových členů .............................................................................................................. 140 Reflexe metod .......................................................................................................................... 141 Poznáváme C# a Microsoft.NET 38. díl – atributy a jejich reflexe ....................................... 143 Proč atributy? ........................................................................................................................... 143 Jak atributy použít? ................................................................................................................... 143 Poziční a pojmenované parametry atributů ................................................................................... 144 Cílené použití atributů ................................................................................................................ 144 Vytváření vlastních atributů ........................................................................................................ 145 Získání uživatelských atributů při použití reflexe ........................................................................... 146 Poznáváme C# a Microsoft.NET 39. díl – další použití reflexe ............................................ 147 Použití třídy ConstructorInfo ....................................................................................................... 147 Reflexe a polymorfizmus ............................................................................................................ 149 Dynamické vytváření nových elementů aplikace pomocí reflexe ...................................................... 150 3 Poznáváme C# a Microsoft.NET 40. díl – serializace ......................................................... 151 Co je to serializace?................................................................................................................... 151 Jak učinit typy serializovatelnými? ............................................................................................... 152 Jak instanci serializovat? ............................................................................................................ 153 Poznáváme C# a Microsoft.NET 41. díl - pokročilé využití serializace .................................. 154 Selektivní serializace členů ......................................................................................................... 154 Rozhraní IDeserializationCallback ................................................................................................ 156 Vlastní řízení serializace ............................................................................................................. 156 Poznáváme C# a Microsoft.NET 42. díl – úvod do použití XML ........................................... 159 XML? Co je to? .......................................................................................................................... 159 Jak na zpracování XML v .NET frameworku ................................................................................... 160 Jednosměrné čtení XML dokumentu ............................................................................................. 160 Použití třídy XMLTextReader ....................................................................................................... 160 Poznáváme C# a Microsoft.NET 43. díl – práce s XML....................................................... 161 Jak na atributy v XML ................................................................................................................ 161 Zápis do XML dokumentu ........................................................................................................... 163 Poznáváme C# a Microsoft.NET 44. díl – zpracování XML pomocí DOM ............................... 164 Document Object Model ............................................................................................................. 164 Použití DOM v prostředí .NET ...................................................................................................... 165 Modifikace XML pomocí DOM ...................................................................................................... 166 Poznáváme C# a Microsoft.NET 45. díl – validace XML dokumentů ..................................... 167 XML schémata .......................................................................................................................... 167 Validace podle XSD v .NET frameworku........................................................................................ 168 Poznáváme C# a Microsoft. NET 46. díl – použití XPath .................................................... 169 Jazyk XPath .............................................................................................................................. 169 Užití XPath v .NET ..................................................................................................................... 170 Rozhraní XPath a třída XPathNavigator......................................................................................... 171 Poznáváme C# a Microsoft.NET 47. díl – použití XSL transformací...................................... 172 XSL transformace? O co jde? ...................................................................................................... 172 Použití XSLT v .NET frameworku ................................................................................................. 174 Předávání parametrů transformačnímu jádru ................................................................................ 175 Poznáváme C# a Microsoft. NET 48. díl – úvod do použití vláken ....................................... 176 Vlákna? O co jde?...................................................................................................................... 177 Použití vláken v .NET ................................................................................................................. 177 Poznáváme C# a Microsoft .NET – 49. díl – použití vláken ................................................ 179 Zjištění informací o vláknu ......................................................................................................... 179 Uspání vlákna ........................................................................................................................... 180 Vlákna s parametry ................................................................................................................... 181 Poznáváme C# a Microsoft.NET – 50. díl – použití vláken II. ............................................. 182 Spojení vláken .......................................................................................................................... 182 Synchronizace vláken ................................................................................................................ 183 Klíčové slovo lock ...................................................................................................................... 187 Atomické operace...................................................................................................................... 187 Poznáváme C# a Microsoft.NET – 51.díl – použití vláken III. ............................................. 188 Test na získání zámku objektu .................................................................................................... 188 Notifikace vláken a čekání na zámek............................................................................................ 189 Poznáváme C# a Microsoft. NET 52. díl – ThreadPool ....................................................... 192 Vlákna typu démon ................................................................................................................... 192 Použití třídy ThreadPool ............................................................................................................. 193 Informace o thread poolu ........................................................................................................... 194 Poznáváme C# a Microsoft.NET – 53. díl – Timer a asynchronní delegáti............................. 195 Asynchronní delegáti ................................................................................................................. 195 Třída Timer............................................................................................................................... 196 4 Poznáváme C# a Microsoft .NET – 1.díl Nový seriál o jazyku C# a s ním spjaté platformy Microsoft .NET začneme úvodním seznámením s ní a jejími důležitými vlastnostmi. I když úvod do světa .NETu byl již několikrát v našich končinách napsán, nemohu si tuto důležitou část dovolit vynechat, protože se určitě mezi zájemci o vývoj pro tuto platformu najdou tací, kteří ještě neměli tu čest se s ní seznámit. Důvody vzniku Platforma .NET byla oficiálně představena firmou Microsoft v roce 2000 jako klíčový produkt, jehož rozvoj a propagace je součástí dlouhodobé strategie firmy. Microsoft .NET znamená novou generaci systému vývoje aplikací pro operační systémy Windows založeném na řízeném běhovém prostředí, obohaceném o neskromnou sadu základních tříd, nesoucím jméno .NET framework. Hlavními důvody vedoucí k více než čtyřletému vývoji, jehož výsledkem je .NET, byly: • • • • nekompatibilita jednotlivých programovacích jazyků a s tím související obtížná spolupráce mezi programy/knihovnami napsanými v odlišných jazycích (např. C++ a Visual Basic) vysoká chybovost aplikací (chyby v práci s pamětí, neplatné konverze datových typů) problémy s verzemi knihoven (obtížná práce s provozem více verzí knihoven) zastaralý a nepřehledný způsob vývoje dosavadních webových aplikací Všechny tyto problémy efektivně řeší platforma .NET – a to použitím již zmíněného řízeného běhového prostředí, systémem assemblies, což jsou základní stavební prvky aplikací, a novou technologií ASP .NET pro vývoj webových aplikací. Princip běhového prostředí V předchozím odstavci jsem zmínil, že pro Microsoft .NET jsou aplikace vyvíjeny pod řízeným běhovým prostředím .NET framework. Bylo by dobré objasnit co se skrývá pod pojmem řízené běhové prostředí. Většina dnešních aplikací, vytvořených například v jazyce C++, Visual Basicu nebo Delphi, jsou zkompilovány přímo pro danou platformu, nejčastěji je to pro platformu Win32 operačních systémů Windows, ale mohou to samozřejmě být i jiné. To znamená, že zdrojový kód je kompilací převeden do strojového kódu počítače. To ve výsledku přináší velmi dobrou rychlost běhu výsledné aplikace. Avšak na druhou stranu z toho plynou i některé nevýhody – nepřenositelnost mezi jednotlivými platformami, popřípadě verzemi operačních systémů a nezřídka jsou k vidění chyby v přístupech do operační paměti. Princip řízených běhových prostředí, použitý právě u platformy .NET, ale i u velmi známé platformy Java firmy Sun Microsystems, na to jde trochu jinak a přidává k převodu zdrojového kódu do kódu strojového ještě jednu vrstvu. Tuto vrstvu představuje mezikód, do kterého jsou zdrojové kódy zkompilovány, a tento mezikód je běhovým prostředím na cílové platformě (Windows, Linux) převeden do strojového kódu. Tento převod je na cílové platformě realizován vždy při spouštění konkrétní aplikace. Mínusem tohoto překladu je vyšší náročnost na výkon uživatelského počítače, a z tohoto důvodu se tento způsob nepoužívá pro vývoj výpočetně náročných aplikací (např. počítačové hry). S jeho častým použitím se naopak můžete setkat spíše u obchodních 5 aplikací, které přeci jen nejsou tak náročné na výpočetní výkon a rychlost běhu daných aplikací je naprosto vyhovující. Poznámka: Předchozí větu si prosím nevyložte tak, že aplikace pod těmito platformami jsou nepoužitelně pomalé. U spousty úloh (přístup k databázi, souborům atd.) uživatel snížení rychlosti aplikace ani nepocítí. Navíc je dobré dodat, že u těchto běhových prostředí při spuštění aplikace nedochází k překladu celé aplikace najednou, ale používá se JIT (Just-in-Time) kompilace. JIT kompilace znamená, že do strojového kódu je převedena pouze potřebná část mezikódu a při opětovném použití této (již přeložené) části se spouští její zkompilovaná forma, což se příznivě projeví na rychlosti, která si již nezadá s během neřízeného programu. Klíčové vlastnosti Tolik k jemnému osvětlení řízeného běhového prostředí. Nyní přejdeme ke klíčovým vlastnostem platformy Microsoft .NET, která tento způsob běhu aplikací přináší. Mezikód zmíněný o pár řádků výše se ve světe této platformy nazývá MSIL, tedy Microsoft Intermediate Language. Tento jazyk relativních adres je spouštěn klíčovou součástí .NET frameworku pojmenovanou CLR (Common Language Runtime neboli společné běhové prostředí) a firma Microsoft jej dala ke standardizaci organizaci ECMA. V prostředí CLR existuje věc, která programátorům velmi usnadňuje práci s operační pamětí – Garbage Collector. Jedná se o sadu složitých algoritmů pro uvolňování nepotřebných programových objektů z paměti. Díky Garbage Collectoru se již vývojáři nemusejí starat o přiřazování nebo uvolňování operační paměti a odpadá tak riziko již zmíněné nekorektní práce s ní, která ve většině situací končí pádem aplikace. Velmi důležitou vlastností, kterou dal Microsoft své platformě do vínku, je CLS – Common Language Specification (Společná jazyková specifikace). S ní souvisí CTS neboli Common Type System (společný typový systém). Výsledkem použití CLS a CTS je rovnocennost programovacích jazyků. Jinými slovy – pro vývoj .NET aplikací je možné použít jeden z několika programovacích jazyků vyšší úrovně. Může se jednat například o: • • • • C#, nový jazyk vyvinutý pro .NET, Visual Basic .NET, nová generace oblíbeného jazyku Visual Basic, J#, což je jazyk se syntaxí rozšířeného jazyka Java, managed C++, kde slovíčko managed označuje možnost psát řízený kód pro .NET dokonce i v tak od svého počátku „neřízeném“ jazyce S tím souvisí i další výhoda této platformy – výrobcům třetích stran nic nebrání ve vývoji dalších jazyků… Jediné, co stačí, je, aby tento nový jazyk měl kompilátor se schopností kompilovat zdrojové kódy do mezijazyku MSIL; to znamená splnit specifikaci CLS danou Microsoftem. Typy aplikací Nyní se pomalu dostáváme k otázce, která určitě napadla většinu čtenářů, kteří se dočetli až sem: „Co všechno můžu v .NETu vytvořit?“ Platforma Microsoft .NET vývojářům nabízí široké možnosti. Začít můžete u klasických konzolových aplikací, které pro vstup a výstup používají příkazový řádek. Daleko zajímavější jsou aplikace s využitím knihoven Windows.Forms, interně využívající Microsoft Win32 API. Výsledkem jejich použití jsou známé formulářové aplikace pro 6 Windows. Možné je také vytvořit aplikaci běžící jako proces na pozadí systému – službu Windows. Dalším odvětvím jsou webové aplikace nahrazující zastaralé ASP 2.0; a to jejich nová generace označovaná jako ASP .NET, za kterou si dle mého názoru Microsoft zaslouží velkou pochvalu, protože tím posunul tvorbu dynamických webů o pořádný kus dál. Technologií ASP .NET se budeme blíže zabývat v některém z příštích dílů tohoto seriálu. Neméně zajímavým typem aplikací jsou takzvané Webové služby, které umožňují pomocí všudypřítomného http protokolu na vzdáleném serveru volat metody. A samozřejmě nesmíme zapomenout na možnost tvorby knihoven tříd, bez které by vyspělá platforma, kterou Microsoft .NET bezesporu je, neměla velký smysl. V příštím díle našeho seriálu se začneme přímo zabývat jazykem C# a názorně si ukážeme, jak napsat první velmi jednoduchou aplikaci. Poznáváme C# a Microsoft .NET – 2. díl Po minulém jemném úvodu do světa platformy Microsoft .NET se dnes již zaměříme na programovací jazyk C#. Jde o nově vyvinutý jazyk pro .NET, který kombinuje vlastnosti známých a oblíbených programovacích jazyků a přidává k nim některé nové. I přes to, že si jsou jednotlivé programovací jazyky pro tuto platformu rovny, je C# Microsoftem prosazován jako jazyk hlavní. Vlastnosti jazyka Jak jsem napsal o pár řádků výše, C# je nově vyvinutý jazyk pro Microsoft .NET. Je navržen pro maximální využití této rychle se rozvíjející platformy. Jedná se o silně objektově orientovaný jazyk vycházející z programovacích jazyků Java a C++, takže pokud nějaký z těchto jazyků znáte, nebude pro Vás C# velký problém. Stejně jako tyto jazyky je i C# case-sensitive, což znamená, že významově odlišuje velká a malá písmena ve výrazech (zarovka a Zarovka jsou brány jako dva rozdílné pojmy). V tomto jazyce je realizováno 80% základních knihoven .NET frameworku. I přesto, že je koncipován hlavně pro psaní řízeného kódu, na jehož užití je platforma .NET postavena, lze jej v případě potřeby využít i pro tvorbu kódu neřízeného(bloky unsafe). Použití neřízeného kódu znamená, že běhové prostředí CLR neověřuje zda-li je napsaný kód bezpečný (například se neověřuje jinak vyžadovaná typová bezpečnost). Nyní si dovolím uvést výčet několika vlastností jazyku C#, které můžete při tvorbě aplikací použít. Jedná se o: • • • • • • • Třídy – základní stavební prvek při tvorbě objektově orientovaných aplikací obsahující akce (metody) a atributy Struktury – lze je chápat jako zjednodušené třídy, jejich užitím jsou nejčastěji popisovány vlastní datové struktury. Výčtové typy Vlastnosti – někdy označované jako chytré proměnné Pole a jejich „chytrá“ verze nazývaná indexery Zástupci – typově bezpečné ukazatele na funkce Události – druh zástupců sloužící ke zpracování asynchronních operací Požadavky na běh 7 Základním požadavkem pro běh .NET aplikací je samozřejmě již několikrát zmíněné běhové prostředí .NET framework, které si můžete stáhnout na adrese www.microsoft.cz/net. Pro vývoj doporučuji stáhnout také .NET framework SDK (Software Development Kit), které mimo jiné obsahuje obsáhlou dokumentace k základním třídám .NET frameworku. Vývojová prostředí Pro vývoj .NET aplikací máte na výběr z několika vývojových prostředí. Samozřejmě pokud z nějakého důvodu specializované vývojové prostředí používat nechcete, můžete psát kód aplikace klidně v poznámkovém bloku a následně jej kompilovat v prostředí příkazového řádku. To bych vám ovšem příliš nedoporučoval. Za asi nejvhodnější vývojové prostředí pokládám Visual Studio .NET respektive Visual C#, což je jeho součást. Bohužel toto vývojové prostředí není zdarma, takže pokud nemáte v plánu vyvíjet aplikace komerčně asi to pro vás nebude nejvhodnější volba. Za účelem studia vidím jako dobrou volbu C# Builder od firmy Borland, který je ve své verzi personal volně ke stažení na adrese www.borland.com. Ovšem tuto verzi není dovolené používat pro vývoj komerčních aplikací. Pokud máte zájem vyvíjet komerční aplikace a nechce se vám utrácet existuje open source vývojové prostředí SharpDevelop a to najdete na adrese www.icsharpcode.net. První program „Ahoj světe“ Je nepsaným zvykem začít učení programovacího jazyka tím nejjednodušším programem, který nedělá nic jiného než, že vypíše uživateli na obrazovku slova „Ahoj světe“. Tento prográmek se zrealizuje v jazyku C# takto: using System; namespace ukazky_zive { class AhojSveteApp { public static void Main(string[] args) { Console.WriteLine("Ahoj svete!"); Console.ReadLine(); } } } Na prvním řádku si příkazem using importujeme knihovnu System, která obsahuje třídu Console, jejíž metody budeme používat. Řádek s klíčovým slovem namespace určuje do kterého jmenného prostoru třída patří (v našem případě patří do jmenného prostoru ukazky_zive). Jmenné prostory slouží v .NETu k oddělení tříd do k sobě logicky patřících částí. Výsledkem kompilace je knihovna DLL s názvem jmenného prostoru. Řádek s klíčovým slovem class určuje název vytvářené třídy (AhojSveteApp). Po něm následuje řádek definující metodu Main s identifikátory přístupu public a static ,která nevrací žádnou hodnotu což je dáno slovíčkem void. Do metody jako parametr vstupuje pole řetězců(string [] args). Všechny tyto podmínky musí metoda splňovat, aby třída AhojSveteApp byla spustitelná konzolová aplikace. No a příkaz Console.WriteLine vypíše předaný parametr (v našem případě je to řetězec „Ahoj svete!“) na obrazovku. V některých vývojových prostředích (například Borland C# Builder) je ještě vhodné uvést příkaz Console.Readline(), který zařídí nezavření okna aplikace ihned po výpisu našeho pozdravu. V prostředích Visual C# a SharpDevelop toto nutné není, protože ty si to zařídí 8 sami. Jistě jste si všimli, že jednotlivé bloky programu jsou jako ve všech C-like jazycích uzavřeny do složených závorek. Poznámka: Pokud jste nováčci ve světe objektově orientovaného programování, pak vám jsou pojmy jako třída či identifikátor přístupu nejspíše cizí. Ale nelekejte se, hned v příštím díle seriálu si tyto pojmy rozebereme. Základní datové typy Nyní si projdeme základní datové typy, použitelné v jazyku C#. Každý datový typ v C# ,stejně jako u ostatních jazyků pro .NET, je reprezentací datového typu .NET frameworku a to kvůli splnění specifikace CTS. Jsou to tedy: • • • • • • • • • • • • • Int – 32-bitový celočíselný typ s hranicemi -2,147,483,648 až 2,147,483,647 Uint – Int bez znaménka s hranicemi 0 až 4,294,967,295 Byte – 8-bitový celočíselný typ bez znaménka s hranicemi 0 až 255 Sbyte – byte se znaménkem s hranicemi -128 až 127 Short – 16-bitový celočíselný typ s hranicemi -32,768 až 32,767 Ushort – short bez znaménka s hranicemi 0 až 65,535 Long – celočíselný typ s hranicemi -9,223,372,036,854,775,808 až 9,223,372,036,854,775,807 Ulong – long bez znaménka s hranicemi 0 až 18,446,744,073,709,551,615 Float – číselný typ s desetinnou čárkou s přesností na 7 míst Double – číselný typ s desetinnou čárkou s přesností na 15 nebo 16 míst Char – vyjadřuje Unicode znak String – reprezentuje řetězec znaků ve znakové sadě Unicode Bool – logický typ, který nabývá pouze dvou hodnot : true(pravda)/false(nepravda) Rozdělení datových typů V .NET frameworku se datové typy dělí na dvě skupiny. Jsou to: Hodnotové typy (value types) – do této skupiny patří všechny číselné datové typy, typ char a ostatní struktury. U těchto jednoduchých typů se jejich hodnota ukládá přímo do proměnné – místa v paměti určené pro uložení hodnoty. Referenční typy (reference types) – do této skupiny patří typ String a všechny třídy. Na rozdíl od hodnotových typů se jejich hodnota uloží do oblasti paměti nazývané halda. Do proměnné se uloží pouze adresa paměti, kde je hodnota uložena – reference. Příklad na konec To byl tedy výčet základních typů jazyka C# a na konec předložím velmi jednoduchý příkladek, který ilustruje použití číselného typu int a řetězce znaků (string). using System; namespace ukazky_zive { class ConsoleSecteniApp { public static void Main(string[] args) { //deklarace promennych int a; 9 int b; int v; //deklarace promenne i s jeji definici string vysl_zprava = "Vysledek scitani je "; /* Ulozeni hodnot do promennych * ze standartniho vstupu pomoci metody ReadLine * objektu Console ze systemove knihovny trid System * a nasledne pouziti metody Int32.Parse pro prevod * retezce na cislo */ Console.Write("Prvni cislo:"); a = Int32.Parse(Console.ReadLine()); Console.Write("Druhe cislo:"); b = Int32.Parse(Console.ReadLine()); /*secteni hodnot promennych a,b a ulozeni vysledku * operace do promenne v */ v = a + b; Console.WriteLine(vysl_zprava + v); } } } V příštím díle našeho seriálu se budeme zaobírat základy objektového programování a jeho aplikací v C#. Poznáváme C# a Microsoft .NET – 3. díl V tomto díle se začneme zaobírat základy objektového programování a jeho aplikací v jazyku C#. Vysvětlíme si co je to třída , její instance a základní pojmy s tím související. Je důležité tyto pojmy a s tím spojené principy chápat, protože na nich je postaven vývoj aplikací pro platformu Microsoft.NET. Proč objekty? Objektově orientovaný přístup k vývoji softwaru je v dnešní době velmi využívaný. Na těchto principech staví své systémy a technologie všechny moderní společnosti IT světa. Důvody použití tohoto přístupu na rozdíl od přístupu strukturovaného, používaný například v jazycích Pascal nebo C, mimo jiné jsou: • • přehlednější zdrojové kódy vyšší znovupoužitelnost vytv Objekty a třídy Objekty ve světě vývoje aplikací často představují abstrakci objektů reálného světa . To s sebou přináší, větší možnosti při návrhu struktury jednotlivých částí aplikace. Každý objekt je instancí neboli výskytem třídy. Třídu lze chápat jako šablonu pro vytváření objektů. Pro lepší pochopení si jako třídu lze představit například auto a jako objekt – její instanci třeba Audi A4, Volvo S40 nebo Ford Mondeo. Všechny tyto objekty jsou konkrétní typy aut. To nám přináší důležitou informaci a tou je, že při vlastním programování nepíšeme přímo objekty, ale třídy a podle ní pak budou jednotlivé objekty vytvářeny. 10 Každá třída může obsahovat definici pro: • • metody – operace, které může třída nebo pomocí ní vytvořený objekt provádět atributy – představují stavy/data třídy nebo vytvořeného objektu Zapouzdření objektů Zapouzdření je jednou ze základních vlastností objektově orientovaného přístupu a existuje z důvodu, že data a operace objektu se vzájemně ovlivňují – tvoří nedělitelný celek. Zapouzdření znamená, že objekt má některé své členy (metody/atributy) před okolím skryty. Členy přístupné okolí se nazývají rozhraní objektu. 11 Se zapouzdřením souvisí i vlastnosti nazývané: • • selektory – slouží k získání hodnoty nějakého skrytého atributu, ale neumožňují tuto hodnotu modifikovat modifikátory – slouží ke změně hodnoty nějakého skrytého atributu Implementace v C# Na ukázku si v jazyku C# napíšeme jednoduchou třídu představující žárovku, která bude mít atributy identifikující její výkon a stav zda-li svítí. Konstruktor Každá třída musí mít konstruktor, který představuje předpis na vytvoření nového objektu. Konstruktor nejčastěji obsahuje kód pro inicializaci (první nastavení hodnoty) některých, nebo všech atributů nově vznikajícího objektu. Pokud ho ve své třídě neuvedeme, použije se implicitní (výchozí) konstruktor, který žádné takovéto „přípravné“ operace neprovádí. Zápis definice konstruktoru je následující: public Nazev_Tridy() { //kod operaci, ktere se provedou vzdy pri vytvareni noveho objektu podle teto tridy } Konstruktoru mohou být také předány parametry se kterými bude při vytváření nového objektu pracovat. Definice konstruktoru očekávajícího parametry se zapisuje takto: public Nazev_Tridy(datový_typ Název_parametru, datový_typ Název_parametru2) { //kod operaci, ktere se provedou vzdy pri vytvareni noveho objektu podle teto tridy a vyuzivaji predane //parametry } Atributy a vlastnosti Atributy třídy se v C# definují zápisem: Specifikátor_přístupu datový_typ Nazev_Atributu; V našem ukázkovém příkladu má atribut výkon implementován selektor i modifikátor a atribut svítí pouze selektor. K deklaraci (vyjádření) selektorů a modifikátorů slouží v jazyku C# takzvané vlastnosti (properties). Vlastnosti tedy určují co se provede za akce při čtení atributu (blok get) a při jeho modifikaci (blok set). Jejich zápis je ve tvaru: Specifikátor_přístupu datový_typ Nazev_Vlastnosti { get { // v tomto bloku se může nalézat provádění operací pred vrácenim hodnoty //například nějaký výpočet, tento blok musí obsahovat klíčove slovo return, //které určuje, že specifikátor vrátí výslednou hodnotu return výsledná_hodnota; } 12 set { //v tomto bloku se nacházejí operace potřebné k nastavení hodnoty skrytého //atributu, klíčové slovo value představuje přiřazovanou hodnotu název_atributu = value; } } Získání hodnoty se následně provádí zápisem: nazev_promenne = Nazev_Objektu.Nazev_Vlastnosti; Nastavení hodnoty pak: Nazev_Objektu.Nazev_Vlastnosti = hodnota; Metody Výsledkem toho, že atribut svítí nebude mít uveden modifikátor, nebude moci být pomocí vlastnosti nastaven. Změna atributu svítí bude implementována v metodách (operacích) rozsvítit a zhasnout. Zápis metod vypadá takto: Specifikátor_přístupu datový_typ Nazev_metody() { //kód metody return návratová_hodnota; } V případě, že je jako datový typ metody uveden void, metoda žádnou hodnotu nevrací a slovíčko return není poviné. Také je možné, aby byli metodě při volání předány nějaké parametry. Zápis metody by v tomto případě byl následovný: Specifikátor_přístupu datový_typ Nazev_metody(datový_typ Nazev_parametru, datový_typ Nazev_parametru2) { //kód metody return návratová_hodnota; } Specifikátory přístupu Pro určení viditelnosti členů vně třídy nebo vytvořeného objektu slouží specifikátory přístupu, které jsou v C# představovány klíčovými slovy: • • • private – specifikuje, že člen je privátní (soukromý) - přístupný pouze uvnitř třídy public – specifikuje, že člen je veřejný – přístupný z jiných tříd protected – specifikuje, že člen je přístupný pouze potomkům třídy Specifikátor protected souvisí s problematikou dědičnosti, kterou si probereme v příštím díle. Ve výčtu je tento specifikátor přístupu uveden pouze pro úplnost seznamu základních specifikátorů. Na obrázku níže je zobrazena třída Žárovka pomocí jazyku UML (Unified Modeling Language), což je jazyk užívaný pro vizuální vyjádření objektových konceptů. 13 Ukázková třída žárovka Následující zdrojový kód implementuje třídu Žárovka a její členy. namespace priklady_zive { public class Zarovka { // deklarace soukromych atributu tridy private int vykon; private bool sviti; //deklarace vlastnosti Vykon pres kterou lze precist ci nastavit //atribut vykon public int Vykon { //definice selektoru get { return vykon; } //definice modifikatoru set { vykon = value; } } //deklarace vlastnosti Sviti pres kterou lze pouze precist atribut sviti public bool Sviti { //definice selektoru get { return sviti; } } //konstruktor bez vstupnich parametru tridy zarovka - pri vytvoreni je zarovka //zhasnuta public Zarovka() { sviti = false; } //metoda, ktera nastavi atribut sviti na hodnotu true public void Rozsvitit() 14 { sviti = true; } //metoda, ktera nastavi atribut sviti na hodnotu false public void Zhasnout() { sviti = false; } } } Tímto máme vytvořenou naši novou třídu a teď budeme potřebovat vytvořit instanci této třídy – konkrétní objekt. Vytvoření nové instance a uložení reference na ni do proměnné se provádí v C# následujícím způsobem: Datový_typ Nazev_promenne = new Datový_typ(); Tento řádek zařídí zavolání bezparametrického konstruktoru třídy. Zdrojový kód konzolové aplikace s vytvořením nové instance třídy žárovka a využitím členů vzniklého objektu by tedy mohl vypadat takto: namespace priklady_zive { class ZarovkaTest { public static void Main(string[] args) { //zavolani konstruktoru pro vytvoreni instance tridy zarovka Zarovka naseZarovka = new Zarovka(); //nastaveni atributu vykon přes vlastnost Vykon naseZarovka.Vykon = 120; Console.WriteLine("Vykon zarovky je " + naseZarovka.Vykon); Console.WriteLine("Atribut sviti zarovky = " + naseZarovka.Sviti); //zavolání metody Rozsvitit naseZarovka.Rozsvitit(); Console.WriteLine("Atribut sviti zarovky po zavolani metody Rozsvitit = " + naseZarovka.Sviti); //zavolání metody Zhasnout naseZarovka.Zhasnout(); Console.WriteLine("Atribut sviti zarovky po zavolani metody Zhasnout = " + naseZarovka.Sviti); Console.ReadLine(); } } } Výstup aplikace by vypadat takto: Vykon zarovky Atribut sviti Atribut sviti Atribut sviti je 120 zarovky = False zarovky po zavolani metody Rozsvítit = True zarovky po zavolani metody Zhasnout = False Příště si rozebereme problematiku dědičnosti tříd v C#, blíže se podíváme se na metody a konstruktory s parametry a také se dozvíme co se skrývá pod pojmem přetížení. 15 Poznáváme C# a Microsoft .NET – 4. díl Dědičnost a s ním spojené řízení verzí jsou klíčové vlastnosti v objektově orientovaném přístupu k vývoji aplikací. Pojmy jako předek, potomek či překrytí jsou pojmy které s dědičností souvisí a v tomto díle se seznámíme s jejich významem. Také se dozvíme co to je přetěžování metod a jak je možné jej naimplementovat v C#. Jednoduchá dědičnost v C# Pojem dědičnost v oblasti objektově orientovaného programování znamená možnost odvodit nějakou třídu z třídy jiné. Potřeba využít dědičnosti nastává tehdy, je-li jedna třída speciálním případem jiné třídy. Třída, která slouží jako základ pro odvozenou třídu nese označení bázová třída. Bázová třída je také často nazývána předkem odvozené třídy. V jazyku C# může být pro každou třídu určen pouze jeden předek – jednoduchá dědičnost. To je rozdíl oproti jazyku C++, kde existovala několikanásobná dědičnost. Autoři jazyka C# se ale několikanásobné dědičnosti vyvarovali, kvůli problémům, které při jejím užívání vznikaly. Nepřítomnost několikanásobné dědičnosti řeší jazyk C# stejně jako Java použitím rozhraní, kterými se budeme zabývat později. Využití dědičnosti Představme si nějaký fiktivní informační systém, kde vytvoříme například třídu zaměstnanec. Tato třída bude implementovat základní operace a vlastnosti společné pro všechny druhy zaměstnanců ve firmě. Ve firmě pracuje několik druhů zaměstnanců. V té naší fiktivní to jsou programátor, traktorista a účetní (přiznávám, že je to zajímavá kombinace, ale v tomto případě to nijak nevadí). Každý vykonává pro svou profesi specifické operace a proto je zde vhodné vytvořit pro jejich reprezentaci vlastní třídy odvozené od třídy Zaměstnanec. Diagram tříd v jazyku UML by pro tuto situaci vypadal následovně. Diagram zobrazuje bázovou třídu Zaměstnanec, která má atribut HodinovaSazba datového typu int, vlastnost, která k němu zprostředkovává přístup, pak operaci VypisTyp která ve formě řetězce vrací typ zaměstnance a nakonec operaci VypoctiMzdu, která spočte mzdu podle počtu odpracovaných hodin a hodinové mzdy. Počet odpracovaných hodin bude metodě VypoctiMzdu předán ve formě vstupního parametru. 16 Odvozené třídy Programator, Traktorista a Ucetni mají operace patřící k jejich pracovnímu zařazení. Z důvodu, že tyto třídy dědí z třídy Zamestnanec obsahují také všechny její členy. Jinými slovy můžeme říci, že tyto členy zdědily po svém předkovi. Implementace dědičnosti v C# Zápis definice třídy, která dědí od jiné třídy se zapisuje v C# následujícím způsobem: public class NazevOdvozeneTrid y: NazevBazoveTridy { //implementace tridy } Zápis konstruktoru, který volá konstruktor implementovaný v bázové třídě takto: public NazevOdvozeneTridy() : base() { //implemetace dodatecnych operaci } Pokud v metodě, která je implementována potřebujeme zavolat metodu implementovanou na bázové třídě, opět využijeme klíčového slova base a to tímto způsobem: public NazevMetody () { base.NazevBazoveMetody(); } Specifikátor přístupu protected S dědičností souvisí specifikátor přístupu protected. Tento specifikátor přístupu použijeme v případě, kdy chceme, aby daný člen byl přístupný pouze odvozeným třídám. V našem příkladu se zaměstnanci je tento specifikátor přístupu použit u atributu hodinovaSazba. Byl použit místo specifikátoru private, protože privátní členy se nedědí a bylo by nutné tento atribut ve všech třídách znovu deklarovat. Virtuální metody a jejich překrytí pomocí new a override S implementací dědičnosti se často setkáváme s potřebou předefinovat funkčnost nějaké metody v odvozené třídě. Tomuto předefinování se také říká překrytí. V našem příkladě, jak níže uvidíme, překrýváme u každé odvozené třídy metodu VypisTyp. Pokud máme v úmyslu nějakou metodu ve třídě později v odvozených třídách překrývat, musíme tuto metodu nadefinovat jako virtuální. K tomu slouží v C# klíčové slovo virtual. Zápis virtuální metody tedy vypadá takto: specifikátor_přístupu virtual datový_typ NazevMetody() { //vykonny kod metody } K překrývání metod v odvozených třídách slouží klíčová slova override a new. Override použijeme v případě, kdy nová metoda v odvozené třídě plní logicky stejnou funkci , má stejný specifikátor přístupu a stejný návratový typ jako metoda v bázové třídě. Slovíčko new logicky použijeme v opačném případě, tedy když nová metoda plní logicky jinou funkci nebo mění specifikátor přístupu a nebo má jiný návratový typ. Používání těchto 17 slov správně také velmi zvýší čitelnost zdrojového kódu, protože bude zřejmé jestli se jedná o překrytí a jaký smysl toto překrytí má. Metody a konstruktory se vstupními parametry V minulém díle jsem se zmiňoval o možnostech implementovat metody se vstupními parametry. Nejjednodušší na pochopení bude, když uvedu příklad metody pro součet dvou čísel typu int. Zápis metody by mohl vypadat takto: public int Secti(int a, int b) { int vysledek = a + b; return vysledek; } Volání metody by pak probíhalo tímto způsobem: int soucet = Nazev_tridy.Secti(5,6); Uvnitř metody pracujeme s hodnotami předaných parametrů pomocí proměnných s názvy deklarovanými v hlavičce metody (v ukázkové metodě sečti jsou to proměnné a, b). Proměnné s těmito hodnotami jsou platné pouze v rámci metody, stejně jako pomocné proměnné definované v ní ( v našem případě je to proměnná výsledek). Stejně tak je to i konstuktorů s parametry. Přetěžování metod a konstruktorů Metody a konstruktory mohou být v rámci třídy definovány v několika verzích, do kterých vstupuje rozdílný počet parametrů. Takovéto metody/konstruktory jsou nazývány přetížené. V připadě metod musejí mít všechny verze stejný návratový typ. Pro pochopení si přetížíme naši metodu Secti. Po tomto přetížení budeme moci při volání této metody použít dva nebo tři vstupní parametry. public int Secti(int a, int b) { int vysledek = a + b; return vysledek; } public int Secti(int a, int b, int c) { int vysledek = a + b + c; return vysledek; } Příklad se zaměstnanci v C# Po vysvětlení potřebných pojmů se vrátíme k našemu příkladu se zaměstnanci. Výše jsme mohli vidět UML diagram tříd popisující naši modelovou situaci, teď zbývá k tomuto diagramu ještě přidat zdrojový kód. using System; namespace PrikladyZive { public class Zamestnanec 18 { protected int hodinovaSazba; //konstuktor bez parametru, ktery nastavi atribut hodinovaSazba //vuzitim klicoveho slova thi0073, //ktere oznacuje aktualne vytvarenou instanci public Zamestnanec() { this.hodinovaSazba = 60; } //vlastnost zajistujici pristup k atributu hodinovaSazba public int HodinovaSazba { get { return hodinovaSazba; } set { hodinovaSazba = value; } } //virtualni metoda ktera vrati ve forme retezce typ zamestance, //v tomto pripade vraci nazev obecneho zamestance public virtual string VypisTyp() { return "Obecny zamestnanec"; } //metoda pro vypocet mzdy - pocet odpracovanych hodin je //predavan metode formou vstupni parametru public int VypoctiMzdu(int pocetHodin) { //definice promenne pro ulozeni vysledku - tato promenna //se po vykonani metody odstrani z pameti int vysledek = pocetHodin * hodinovaSazba; return vysledek; } } } using System; namespace PrikladyZive { public class Traktorista : Zamestnanec { //volani konstruktoru predka, ktery zaridi nastaveni hodnoty //atributu hodinovaSazba na 60 public Traktorista() : base() {} //prekryti metody VypisTyp public override string VypisTyp() { return "Traktorista"; } public void OrejPole() { 19 //zde by se nachazel vykonny kod } } } using System; namespace PrikladyZive { public class Ucetni : Zamestnanec { public Ucetni() { hodinovaSazba = 100; } public override string VypisTyp() { return "Ucetni"; } public void ZauctujPolozku(string polozka) { //zde by se nachazel vykonny kod } } } Nyní už zbývá jen vytvořit nějakou jednoduchou testovací aplikaci na demonstraci, toho jak dědičnost funguje. public class ZamestnanciTestApp { public static void Main(string[] args) { Zamestnanec zam = new Zamestnanec(); //pouziti konstruktoru s parametrem Programator prog = new Programator(150); Traktorista trak = new Traktorista(); Ucetni ucetni = new Ucetni(); Console.WriteLine(zam.VypisTyp() + " - Hodinova sazba: " + zam.HodinovaSazba); Console.WriteLine(prog.VypisTyp() + " - Hodinova sazba: " + prog.HodinovaSazba); Console.WriteLine(trak.VypisTyp() + " - Hodinova sazba: " + trak.HodinovaSazba); Console.WriteLine(ucetni.VypisTyp() + " - Hodinova sazba: " + ucetni.HodinovaSazba); Console.WriteLine("Mzdy po 120 odpracovanych hodinach"); Console.WriteLine(zam.VypisTyp() + " - Mzda: " + zam.VypoctiMzdu(120)); Console.WriteLine(prog.VypisTyp() + " - Mzda: " + prog.VypoctiMzdu(120)); Console.WriteLine(trak.VypisTyp() + " - Mzda: " + trak.VypoctiMzdu(120)); Console.WriteLine(ucetni.VypisTyp() + " - Mzda: " + ucetni.VypoctiMzdu(120)); Console.ReadLine(); } } Výstup demonstrativní aplikace by měl vypadat následovně: 20 Obecny zamestnanec – Hodinova sazba: 60 Programator – Hodinova sazba: 150 Traktorista – Hodinova sazba: 60 Ucetni – Hodinova sazba: 100 Mzdy po 120 odpracovanych hodinach Obecny zamestnanec – Mzda: 7200 Programator – Mzda: 18000 Traktorista – Mzda: 7200 Ucetni – Mzda: 12000 V příštím díle se dozvíme co to jsou statické členy a polymorfizmus. Poznáváme C# a Microsoft .NET – 5. díl Po dědičnosti a řízení verzí s tímto dílem přichází další problematika spojená s objektově orientovaným přístupem k vývoji aplikací. Jedná se o statické členy s jejichž použitím se v tomto díle seznámíme. Také si vysvětlíme pojem polymorfizmus, který je také s objektovým programováním silně spjat. Statické členy tříd V minulých dílech, ve kterých jsme se zaobírali modelováním tříd, jsme se dozvěděli, že třídy mohou mít atributy a operace. Zatím jsme v našich příkladech používali pouze členy, které mohli být využity až po vytvoření instance třídy – objektu. Tyto členy jsou nazývány instanční a jsou tedy svázány až s konkrétním objektem. Existují však ještě členy třídy, které využijeme v případě, když potřebujeme, aby nebyly spojeny s nějakou konkrétní instancí dané třídy, ale s třídou jako takovou. Takovéto členy označujeme jako členy statické. K těmto členům potom přistupujeme nikoliv prostřednictvím jména instance, ale prostřednictvím jména třídy. Statické atributy Nejjednodušším druhem statického člena je statický atribut třídy. Statický atribut se v jazyku C# deklaruje klíčovým slovem static. Školáckým příkladem využití statického atributu třídy je atribut pro ukládání počtu vytvořených instancí třídy. Zdrojový kód by mohl vypadat takto: using System; namespace PrikladyZive5 { public class NaseTrida { private static int pocetInstanci; //definice vlastnosti, která zpristupnuje atribut pocetInstanci public static int PocetInstanci { get{return pocetInstanci;} } public NaseTrida() { //pri kazdem vytvareni noveho objektu podle nasi tridy se // hodnota atributu zvysi pocetInstanci++; } } } 21 Poznámka: Výraz pocetInstanci++ , zařídí zvýšení hodnoty proměnné pocetInstanci o jedna. Je to stejné jako kdybychom napsali : pocetInstanci = pocetInstanci + 1; Analogicky k tomu by výraz pocetInstanci-- zařídil to samé jako výraz pocetInstanci = pocetInstanci – 1; Jak jsem zmínil výše, k statickým členům přistupujeme prostřednictvím jména třídy. Takže přečtení atributu počtu instancí, uložení této hodnoty do proměnné a její vypsání na obrazovku bysme zapsali takto: public class Zive5TestApp { public static void Main(string[] args) { NaseTrida nase1 = new NaseTrida(); NaseTrida nase2 = new NaseTrida(); int pocet = NaseTrida.PocetInstanci; Console.WriteLine(pocet); Console.ReadLine(); } } Statické metody Stejně jako atributy můžeme i metody pomocí klíčového slova static označit jako statické. Použití statickým metod s sebou nese jedno nezanedbatelné omezení a tím je, že tyto metody nemohou být virtuální. Pokud bychom se statickou metodu pokusili nadefinovat jako virtuální, kompilátor zahlásí chybu. Zápis naší ukázkové třídy obohacené o statickou metodu by vypadal takto: using System; namespace PrikladyZive5 { public class NaseTrida { private static int pocetInstanci; public static int PocetInstanci { get{return pocetInstanci;} } public NaseTrida() { pocetInstanci++; } public static string VypisPozdrav() { return "Ahoj"; } } } Následné vyvolání statické metody a uložení její návratové hodnoty do proměnné by se zapsalo tímto způsobem : string textpozdravu = NaseTrida.VypisPozdrav(); 22 Statické konstruktory Tak jako mohou existovat i jiné druhy statických členů, mohou existovat i statické konstruktory. Statické konstruktory se využívají ke spuštění inicializačních operací pracujícími se statickými členy. Tyto konstruktory jsou volány před vytvořením první instance třídy nebo před prvním použitím jiného statického členu. Zdrojový kód naší ukázkové třídy se statickým konstruktorem: public class NaseTrida { private static int pocetInstanci; private static string jmeno; public static string Jmeno { get{return jmeno;} } //staticky konstruktor static NaseTrida() { jmeno = "Petr"; } … Pokud se pokusíme přečíst hodnotu atributu jméno, aniž bychom ho předtím nastavovali, dostaneme hodnotu Petr. To je zapříčiněno právě tím, že před prvním přístupem k němu je zavolán statický konstruktor. Polymorfismus Polymorfismus, neboli mnohotvárnost je spolu se znovuvyužitelností kódu nejdůležitější aspekt v objektově orientovaném programování. Polymorfismus znamená možnost použít mnoho druhů typů se stejným rozhraním (veřejně přístupnými operacemi a atributy) bez ohledu na detaily jejich implementace. Polymorfismus tedy zajiš�uje to, že nám stačí znát rozhraní bázové třídy a použitím tohoto rozhraní volat operace a atributy, které jsou různě naimplementovány v odvozených třídách. Z tohoto tvrzení plyne, že polymorfismus je svázán s použitím dědičnosti. Použití polymorfizmu si demonstrujeme na jednoduchém příkladu z obrazci. Nadefinujeme si třídu Obrazec, která bude implementovat virtuální metodu pro vykreslení. Třída Obrazec bude sloužit jako bázová pro třídy Čtverec, Obdélník a Kruh. Tyto třídy budou překrývat metodu Vykresli svoji odpovídající verzí a navíc implementují metodu DejObsah. Diagram příkladu s použitím známé notace UML je realizován takto: 23 Implementace těchto tříd není ničím významným zajímavá, protože využívá stejné principy jako minulý příklad se zaměstnanci. Zajímavější je ovšem spustitelná třída, která je využívá. V ní totiž využijeme výhody polymorfismu a to tak, že deklarujeme referenční proměnnou ( = proměnná pro referenční typ) pro objekt typu Obrazec, která bude odkazovat na objekt typu Kruh. Obrazec obrazecPromenna = new Kruh(); Kompilátor neohlásí chybu, že se snažíme referenční proměnnou odkazovat na objekt jiného typu, protože v objektově orientovaném programování platí, že potomek může nahradit předka, což se v tomto případě děje. Kruh tedy může nahradit obrazec, nebo� je jeho potomkem. Polymorfismus zajistí, že pokud zavoláme metodu Vykresli, bude zavolána její verze implementovaná ve třídě Kruh a ne implementace ve třídě Obrazec jak by jistě mnozí čekali. Analogické chování bychom zaznamenali, pokud by tato proměnná odkazovala na objekt typu Obdélník nebo Čtverec. Takže pokud spustíme tento kód: Obrazec obrazecPromenna = new Kruh(); Console.WriteLine(obrazecPromenna.Vykresli()); ..výstup bude „Kruh“. Tento příklad demonstruje základní myšlenku polymorfismu a to tím, že každý typ obrazce umí provést operaci Vykresli(), ale každý z nich ji činí jinak – má jinou implementaci metody. Následující obrázek zjednodušeně zobrazuje, jak tato situace vypadá v paměti. 24 To, že jsme nechali referenční proměnnou předka odkazovat na objekt potomka se v terminologii označuje jako upcasting. Jinými slovy se automaticky provedlo takzvané implicitní přetypování, které zapříčiní, že budeme moci na nově vzniklé instanci volat pouze členy tvořící rozhraní předka. To znamená,že členy, které jsou na odvozené třídě navíc (například metoda DejObsah), by v tomto případě nebyly k dispozici. To, které členy jsou k dispozici totiž závisí na typu referenční proměnné. Pokud bychom chtěli na této instanci volat všechny členy její třídy (v našem případě kruhu), museli bychom provést downcasting a to pomocí explicitního přetypování. Při explicitním přetypování musíme, na rozdíl od přetypování implicitního, uvést na jaký typ chceme objekt převést. Zápis by byl následující. Kruh kruhPromenna = (Kruh)obrazecPromenna; Po tomto kroku bude možno na instanci zavolat i metodu DejObsah. Ovšem pozor, pokud bychom se pokoušeli takto přetypovat instanci, která není typu Kruh, běh programu by skončil chybou. Uvedeného volání členů přes rozhraní jejich společného předka je hojně využíváno za účelem odstínění klienta využívajícího našich knihoven od skutečné implementace operací na potomcích. Jinak řečeno klienta vůbec nemusí zajímat, která konkrétní implementace metody se zavolá, protože díky polymorfismu bude použita ta správná. V příštím díle se budeme zaobírat abstraktními třídami a objasníme si princip použití rozhraní. Poznáváme C# a Microsoft.NET – 6. díl Vysvětlení pojmu rozhraní a jeho principu v objektově orientované tvorbě aplikací v jazyku C# je to, co nás čeká v tomto díle. Spolu s tím se seznámíme s abstraktními třídami, jejichž význam je z rozhraními velmi úzce spojen. Abstraktní třídy Abstraktní třídy využijeme v situaci, kdy potřebujeme definovat “dohodu” o tom jaké členy bude odvozená třída muset implementovat. Abstraktní třídy se chovají stejně jako normální třídy až na to, že mají jednu nebo více členských metod definované jako abstraktní. Abstraktní metody jsou reprezentovány pouze hlavičkou funkce, tedy definicí jejího návratového typu, počtu vstupních parametrů a jejich typů. Tělo abstraktních metod je prázdné a jejich implementace je ponechána na odvozené třídy. Jelikož funkčnost abstraktních metod není naimplementována, není možné abstraktní třídy instancovat (nelze vytvořit objekt tohoto typu). Takže pokud bychom se o to pokusili: AbstraktniTrida abstrObj = new AbstraktniTrida(); 25 při kompilaci programu by došlo k chybě. Poznámka: Přestože abstraktní třídy nemouhou být instancovány, nic vám nebrání v definici jejich konstruktoru, poněvadž implicitní konstruktor (konstruktor bez parametrů) je volán na všech předcích instancované třídy. K definice třídy nebo její metody jako abstraktní slouží v jazyku C# klíčové slovo abstract. //definice abstraktni tridy public abstract class AbstaktniTrida { //tato metoda musi byt v odvozene tride naimplementovana public abstract void AbstraktniMetoda(); public void NormalniMetoda() { //implementace metody } } Z kódu je patrné, že metoda AbstraktniMetoda() je definována jako abstraktní a tudíž má pouze hlavičku a žádné tělo. V případě, že se pokusíme vytvořit odvozenou třídu v níže uvedené podobě, tedy bez implementace metody AbstraktniMetoda(), projekt nepůjde zkompilovat. public class OdvozenaTrida : AbstraktniTrida { public OdvozenaTrida(){} } Popis kompilační chyby by vypadal takto: PrikladyZive6\OdvozenaTrida.cs(5): `PrikladyZive6.OdvozenaTrida` does not implement inherited abstract member `PrikladyZive6.AbstraktniTrida.AbstraktniMetoda()` Takže pokud bychom chtěli dát vše do pořádku třída OdvozenaTrida by měla vypadat následujícím způsobem: public class OdvozenaTrida : AbstraktniTrida { public OdvozenaTrida(){} public override void AbstraktniMetoda() { //implementace metody } } Vyjádření této situaci by v notaci vizuálního modelovacího jazyku UML vypadalo následovně: 26 Rozhraní Rozhraní, podobně jako abstraktní třídy, umožňují vytvořit jakousi dohodu o tom, které členy bude muset nově vytvářená třída definovat. K tomu, aby nově vytvářená třída byla nucena definovat členy rozhraní, ,musíme uvést, že třída konkrétní rozhraní implementuje. Na rozdíl od abstraktních tříd musí třída, která nějaké rozhraní implementuje definovat všechny členy uvedené v rozhraní. Z toho plyne, že rozhraní také můžeme chápat jako abstraktní třídu, která má všechny členy abstraktní. Pro vytváření rozhraní existuje v jazyku C# klíčové slovo interface. Definice rozhraní se zapisuje takto: public interface INaseRozhrani { void PrvniMetodaRozhrani(); void DruhaMetodaRozhrani(int ciselnyParametr); } Jistě jste si všimli, že u metod rozhraní není uveden žádný specifikátor přístupu. Je tomu tak z důvodu, že u rozhraní se toto neprovádí, protože neslouží k ničemu jinému, než k definici metod přístupných na implementujících třídách. Zápis, že třída nějaké rozhraní implementuje je naprosto stejný jako zápis pro určení bázové třídy. public class ImplementujiciTrida : INaseRozhrani { public void PrvniMetodaRozhrani() { // Implementace metody } public void DruhaMetodaRozhrani(int ciselnyParametr) { // Implementace metody } } Všechny definice metod, které jsou předepsány v rozhraní, musí mít v implementující třídě specifikátor přístupu public, jinak kompilátor při kompilaci ohlásí chybu. 27 Vyjádření v UML se realizuje následujícím způsobem: Když třída implementuje nějaké rozhraní je možné získat odkaz(referenční proměnnou) na rozhraní přetypováním instance třídy na toto rozhraní. Prostřednictvím tohoto odkazu se pak mohou volat metody z rozhraní. INaseRozhrani objekt = new ImplementujiciTrida(); Nejedná se o nic jiného než o použití polymorfismu, o kterém jsem se zmínil v předchozím díle. Rozhraní a dědičnost Při přetypování instance třídy na rozhraní se při volání metod prochází celá hierarchie dědičnosti, dokud není nalezena poslední třída implementující toto rozhraní. Nestačí tedy, když nějaký objekt pouze obsahuje správné metody samy o sobě. Uveďme si příklad: public class ImplementujiciTrida : INaseRozhrani { public void PrvniMetodaRozhrani() { Console.WriteLine("ImplementujiciTrida.PrvniMetodaRozhrani()"); } } public class NeimplementujiciTrida : ImplementujiciTrida { public void PrvniMetodaRozhrani() { Console.WriteLine("NeimplementujiciTrida.PrvniMetodaRozhrani()"); } } Máme zde dvě třídy. Třídu ImplementujiciTrida, která implementuje rozhraní INaseRozhrani a tím pádem i jeho metodu. A třídu NeimplementujiciTrida, která dědí z třídy ImplementujiciTrida, implementuje metodu uvedenou v rozhraní INaseRozhrani, ale nemá uvedeno, že toto rozhraní implementuje. Zkusme spustit následující kód: NeimplementujiciTrida neimpl = new NeimplementujiciTrida(); neimpl.PrvniMetodaRozhrani(); INaseRozhrani nase = (INaseRozhrani) neimpl; nase.PrvniMetodaRozhrani(); 28 Dostaneme výstup, který bude vypadat takto: NeimplementujiciTrida.PrvniMetodaRozhrani() ImplementujiciTrida.PrvniMetodaRozhrani() Vidíme, že pokud zavoláme prostřednictvím referenční proměnné rozhraní metodu PrvniMetodaRozhrani(), tak se nezavolá verze metody ze třídy NeimplementujiciTrida, a to přesto, že tato třída obsahuje tuto funkci ve správném tvaru. Děje se tak z důvodu, že třída NeimplementujiciTrida příslušné rozhraní neimplementuje. Vícenásobná implementace Hlavním důvodem pro vznik rozhraní bylo nahrazení vícenásobné dědičnosti, která je přítomna například v jazyku C++. Proto, na rozdíl od dědičnosti objektů, může třída implementovat více rozhraní. Následující příklad ukazuje využití násobné implementace rozhraní. public interface PrvniRozhrani { void Pracuj1(); } public interface DruheRozhrani { void Pracuj2(); } public class NasobImpl : PrvniRozhrani,DruheRozhrani { public void Pracuj1() { // Implementace metody } public void Pracuj2() { // Implementace metody } } Příště se seznámíme s uzavřenými třídami, vnořenými třídami, konstantami a proměnnými pouze pro čtení. Poznáváme C# a Microsoft .NET – 7. díl V tomto díle si ještě doplníme znalosti související s modelováním tříd a seznámíme se s konstantami a proměnnými pouze pro čtení. Uzavřené třídy Uzavřené třídy se používají v případě, chceme-li zabránit tomu, aby třída byla použita jako bázová třída. Smyslem jejich použití je zabránit nežádoucímu odvozování nových tříd. Uzavřená třída se deklaruje pomocí klíčového slova sealed. public sealed class UzavrenaTrida { //definice třídy } 29 V případě pokusu o odvození nové třídy z této třídy dojde při kompilaci k chybě. //chybny priklad public class PotomekUzavrene : UzavrenaTrida { //definice třídy } Privátní konstruktory Použití privátních konstruktorů představuje v jazyku C# cestu k zabránění vytvoření instance dané třídy. To se může hodit v situacích kdy třída obsahuje pouze statické členy, protože pokud tomu tak je, není vůbec třeba instance takovéto třídy vytvářet. Následující příklad ukazuje možné využití. public class MatematickeKonstanty { public static double Pi = 3.1415926535; //tim ze implicitni konstruktor deklarujeme jako privatni, //nebude mozne vytvaret instance teto tridy private MatematickeKonstanty(){} } Následný pokus o vytvoření instance této třídy by skončil chybou. //chyba MatematickeKonstanty instanceMatem = new MatematickeKonstanty(); Poznámka: Další účinek použití privátního konstruktoru je nemožnost použít danou třídu jako bázovou, poněvadž každá třída při svém instancování volá implicitní konstruktor svých předků, což by v tomto případě nebylo možné. Specifikátor přístupu internal Specifikátor přístupu internal poskytuje možnost jak zviditelnit třídu nebo členy třídy širší množině tříd a současně zamezit viditelnosti pro všechny třídy. Tento specifikátor přístupu je předurčen pro psaní pomocných tříd respektive členů, které by měli být skryty koncovému uživateli tříd. Použití specifikátoru přístupu internal má za následek to, že třída nebo člen jsou viditelné pouze v rámci assembly, což je, jak jsem dříve uvedl v některém z předchozích dílů, knihovna DLL, v níž se ve zkompilované podobě (do metajazyku CIL) nacházejí třídy patřící do stejného jmenného prostoru. //deklarace interni tridy internal class InterniTrida { } public class TridaInterniClen { //deklarace interniho clenu internal int interniClen; public int VerejnyClen; } V případě, že použijeme tento specifikátor přístupu v kombinaci se specifikátorem protected bude člen třídy viditelný všem odvozeným třídám ve stejné assembly. 30 Vnořené třídy V některých případech může být užitečné vložit nějakou třídu dovnitř jiné třídy. Je to zpravidla vhodné, když se jedná o nějakou pomocnou třídu, která má být využita pouze ve třídě ve které je vložena. Za tímto účelem nám jsou v jazyku C# k dispozici vnořené třídy. Vnořené třídy také, kromě výše uvedeného důvodu, zvyšují čitelnost kódu a poskytují prostředek k lepší organizaci hierarchie tříd. Následující příklad ukazuje jak by se problém pomocné třídy řešil bez použití vnořené třídy. public class HlavniTrida { private PomocnaTrida instancePomocna1; } public class PomocnaTrida { //definice pomocne tridy } Toto řešení by samozřejmě bylo funkční, ale nebylo by správné, protože pomocná třída by byla přístupná všem ostatním třídám. O něco lepší by bylo pokud by pomocná třída měla specifikátor přístupu internal. Ale i tak by byla přístupná ostatním třídám v assembly, což je také nežádoucí. Pokud pomocnou třídu vnoříme do hlavní třídy jsme schopni ji skrýt před ostatními třídami a zviditelnit ji pouze hlavní třídě, která ji jako jediná potřebuje. public class HlavniTrida { private PomocnaTrida instancePomocna1; //vnitrni trida private class PomocnaTrida { //definice pomocne tridy } } Přístup k vnořeným třídam lze podobně jako u členů řídit pomocí specifikátorů přístupu. Díky tomu tedy lze deklarovat vnořenou pomocnou třídu jako soukromou(private) a zamezit viditelnosti z ostatních tříd. Pokud chceme z nějakého důvodu zviditelnit vnořenou třídu určité množině ostatních tříd, použijeme odpovídající specifikátor přístupu. Takže změníme-li příklad do této podoby: public class HlavniTrida { private PomocnaTrida instancePomocna1; //deklarace vnorene tridy public class PomocnaTrida { //definice pomocne tridy } } ..budeme schopni k této třídě přistupovat jako ke každému veřejnému členu. HlavniTrida.PomocnaTrida instancePomocna = new HlavniTrida.PomocnaTrida(); 31 Vyjádření vnitřních tříd v notaci vizuálního modelovacího jazyku UML je znázorněno na obrázku níže. Třídy nejsou jediný typ který lze vnořovat. Kromě tříd lze vnořovat i rozhraní, struktury a výčtové typy. Poznámka: Pojmy struktura a výčtový typ nebyli v seriálu ještě uvedeny a budou vysvětleny později. Konstanty V jazyku C# je možné definovat hodnoty jako konstanty. Aby hodnota mohla být konstantou, musí být v takovém tvaru, který lze zapsat v podobě konstanty. To s sebou přináší omezení v podobě možnosti definovat konstanty pouze pro vestavěné typy, které takto mohou být vyjádřeny. K vyjádření konstanty slouží v jazyku C# klíčové slovo const. Konstantám je při jejich deklaraci přiřazena hodnota, která později nemůže být změněna. Každá konstanta je automaticky statický člen. Příklady konstant: public class TridaKonstanty { public const int ciselnaKonstanta = 10; public const string retezcovaKonstanta = "slovo"; } Proměnné pouze pro čtení Kvůli tomu, že použití konstant je omezeno pouze na několik vestavěných typů, nelze konstanty v řadě situací použít. Mějme například třídu zaměstnanec. Pokud bysme v nějaké třídě chtěli nadefinovat konstantu tohoto typu, překlad programu by skončil chybou, protože třída zaměstnanec nemůže být vyjádřena jako konstanta. public class Zamestanec { //definice tridy zamestnanec } //nefunkcni priklad public class TridaKonst { 32 public const Zamestanec zamKonst = new Zamestanec(); } Pro tyto situace je v jazyku C# možnost definovat takzvané read-only proměnné, neboli proměnné pouze pro čtení. Ty s sebou omezení konstant již nenesou. Těmto proměnným lze hodnotu nastavit v konstruktoru nebo v inicializační deklaraci, ale později ji nelze změnit. Použití proměnné pouze pro čtení demonstruje následující příklad: public class Zamestanec { //definice tridy zamestnanec } public class TridaReadOnly { public static readonly Zamestanec zam; //staticky konstruktor static TridaReadOnly() { //prirazeni hodnoty, pozdeji ji jiz nelze zmenit zam = new Zamestanec(); } } V příštím díle se budeme zaobírat logickými operátory a příkazy pro větvení programu. Poznáváme C# a Microsoft.NET – 8. díl V tomto díle se zaměříme na použití příkazů pro větvení toku programu, které nám jsou v jazyku C# k dispozici. Spolu s tím se něco dozvíme o relačních a logických operátorech, které jsou s jejich užíváním spojeny a také o přetěžování operátorů pro konkrétní třídy. Relační operátory Relační operátory využijeme v případech, kdy potřebujeme porovnat nějaké dvě hodnoty. Všechny operace, prováděné použitím relačních operátorů, mají výsledek logického typu bool. Typ bool může nabývat pouze dvou hodnot a to true (pravda) nebo false (nepravda). Jazyk C# definuje následující relační operátory: Operace Výsledek a == b true, pokud se hodnota a rovná hodnotě b a != b true, pokud se hodnota a nerovná hodnotě b a<b true, pokud hodnota a je menší než hodnota b a <= b true, pokud hodnota a je menší než hodnota b, nebo je rovna hodnotě b a>b true, pokud hodnota a je větší než hodnota b 33 a >= b true, pokud hodnota a je vetší než hodnota b, nebo je rovna hodnotě b Výsledek operace můžeme znegovat použitím operátoru logické negace, který je v jazyku C# představován vykřičníkem (!). Negace znamená, že z hodnoty true se stane false a naopak. Následující příklad demonstruje možné použití operátoru logické negace. bool pokus = !(3 > 2); //vrati false Operátor == u odkazových typů, pokud ho daná třída nepřetěžuje , vrací true v případě, že se jedná o tentýž objekt a neporovnává jestli mají objekty stejnou hodnotu, jak se tomu děje v případě hodnotových typů. Pokud třída operátor přetěžuje, kompilátor použije jeho implementaci pro danou třídu. Logické operátory Logické operátory se používají k provádění logických nebo bitových operací nad hodnotami. Jazyk C# definuje následující logické operátory: Operátor Popis & bitový součin obou operandů | bitový součet obou operandů ^ bitový výlučný součet obou operandů (XOR) && logický součin dvou operandů || logický součet dvou operandů Operátor && se hodí, pokud potřebuje otestovat, zda oba výrazy splňují podmínku. int a = 2; int b = 3; int c = 4; int d = 5; bool vysledek = ( (a < b) && (d > c) ); //vysledek je true bool vysledek2 =( (a > b) && (d > c) ); //vysledek je false Operátor || využijeme pokud mám stačí, že alespoň jeden výraz ze dvojice splňuje podmínku. Tím pádem, kdybychom v předchozím příkladu místo operátoru && použili operátor ||, oba výsledky by nabývali hodnoty true. bool vysledek2 =( (a > b) || (d > c) ); //vysledek je true Operátory && a || se od svých jednoznakových verzí liší také tím, že provádějí takzvané zkrácené vyhodnocování mezi výrazy. To znamená, že ve výrazu: a && b bude výraz b vyhodnocen pouze v případě, že je splněn výraz a. A ve výrazu: 34 a || b bude výraz b vyhodnocen pouze pokud výraz a není splněn. Podmínkové příkazy Podmínkové příkazy se používají pro vykonání nějakých operací na základě hodnoty nějakého výrazu. Pro podmíněné vykonání operací slouží v jazyku C# příkazy if a switch. Příkaz if Podmínkový příkaz if je jeden z nejpoužívanějších příkazů. Jazyk C# disponuje možností jej použít jak v neúplné podmínce, kde je použito pouze klíčové slovo if, tak v úplné podmínce s použitím klíčových slov if a else. Parametrem příkazu if je booleovský výraz a pokud je tento výraz splňen jsou provedeny požadované operace. Syntaxe je: if (booleovský výraz) { příkazy, které jsou provedeny pouze je li podmínka splněna } Pokud má být za splněné podmínky vykonán pouze jeden příkaz, nejsou složené závorky nutné. Chceme-li zapsat úplnou podmínku, zápis bude vypadat následovně: if (booleovský výraz) { příkazy, které jsou provedeny pouze je li podmínka splňena } else { příkazy, které jsou provedeny pouze není li podmínka splňena } Následující jednoduchý příklad demonstruje použití příkazu if. if (a == 1) { Console.WriteLine("Hodnota je rovna 1"); } else { Console.WriteLine("Hodnota neni rovna 1"); } V případě, že potřebujeme definovat odlišné chování pro více hodnot výrazu, než jen jednu, můžeme využít příkazu else if, jak je znázorněno níže. if (a == 1) { //prikazy, //promenne } else if(a == { //prikazy, //promenne ktere budou provedeny pokud hodnota a je rovna 1 2) ktere budou provedeny pokud hodnota a je rovna 2 35 } else { //prikazy, ktere budou provedeny, pokud hodnota //promenne a neni rovna ani 1 ani 2 } Ternární operátor Pomocí ternárního operátoru je nám umožněno v jazyku C# zapsat podmíněný výraz. Ternární operátor můžeme chápat jako zjednodušený zápis pro if-else, ale s tím rozdílem, že se jedná o výraz a nikoli příkaz jako je tomu u zmíněného if-else. Tím pádem je použití ternárního operátoru v některých situacích vhodnější. Syntaxe vypadá takto: identifikátor = (booleovský výraz) ? výraz_při_splnění : výraz_při_nesplnění; Takže použití může vypadat následovně: int i = 4; //promenna stav nabude hodnoty "Je mensi" string stav = (i < 5) ? "Je mensi" : "Je vetsi nebo rovno"; Příkaz switch Switch je příkaz pro mnohonásobné větvení programu. Když chceme definovat chování pro větší počet hodnot výrazu, je příkaz switch vhodnější než použití odpovídající posloupnosti příkazů if. V příkazu switch jsou jednotlivé větve pro hodnoty výrazu definovány pomocí klíčového slova case. Každá větev musí být ukončena příkazem break nebo goto. Příkaz break definitivně ukončí provádění příkazu switch a příkaz goto umožňuje skok na jiný blok case uvnitř příkazu switch. Také je možné použít větev default, která je provedena, když žádná z definovaných větví case nevyhovuje. Zápis je následující: switch (výraz) { case hodnota_1 : prikazy pro hodnotu 1 break; ... case hodnota_n : prikazy pro hodnotu n break; default : prikazy pro ostatni hodnoty break; } Výraz podle kterého je rozhodováno musí být číselného typu nebo typu char a nebo typu string. Možné použití demostruje tento příklad: switch (a) { case 1: case 2: //tento blok zpracovava hodnoty 1 i 2 Console.WriteLine("Hodnota je 1 nebo 2"); 36 break; case 3: Console.WriteLine("Hodnota je 3"); break; case 4: Console.WriteLine("Střední číslo"); //prikaz goto provede skok do vetve //pro hodnotu 3 goto case 3; default: Console.WriteLine("Jina hodnota"); break; } Přetěžování operátorů V jazyku C# je umožněno přetěžování operátorů, jehož prostřednictvím lze definovat novou funkčnost operátorů nad třídami, takže je možné určité funkce zapisovat pomocí operátorů. Přetěžování operátorů je vhodné použít u takových datových typů u kterých je zřejmé, co daný operátor provádí. Výsledkem přetěžování operátorů je možnost pozdějšího úsporného vyjádření. Ne všechny operátory je možné ve třídě přetížit. Lze přetížit pouze některé unární, bitové nebo relační operátory. Operátor se ve třídě definuje použitím klíčového slova operator. Definice operátoru ve třídě musí být vždy označena jako statická mít specifikátor přístupu public. Následující třída představující komplexní číslo implementuje některé přetížitelné operátory. public class KomplexniCislo { private int realnaCast; private int imaginarniCast; public KomplexniCislo(int aRealnaCast, int aImaginarniCast) { realnaCast = aRealnaCast; imaginarniCast = aImaginarniCast; } public int RealnaCast { get{return realnaCast;} set{realnaCast = value;} } public int ImaginarniCast { get{return imaginarniCast;} set{imaginarniCast = value;} } //definice operatoru pro scitani dvou komplexnich cisel public static KomplexniCislo operator + (KomplexniCislo komplexni1, KomplexniCislo komplexni2) { int lNovaRealnaCast = komplexni1.realnaCast + komplexni2.realnaCast; int lNovaImaginarniCast = komplexni1.imaginarniCast + komplexni2.imaginarniCast; return new KomplexniCislo(lNovaRealnaCast,lNovaImaginarniCast); } //definice operatoru ekvivalence public static bool operator == (KomplexniCislo komplexni1, KomplexniCislo 37 komplexni2) { return komplexni1.realnaCast == komplexni2.realnaCast && komplexni1.imaginarniCast == komplexni2.imaginarniCast; } //definice operatoru non-ekvivalence public static bool operator != (KomplexniCislo komplexni1, KomplexniCislo komplexni2) { return komplexni1.realnaCast != komplexni2.realnaCast && komplexni1.imaginarniCast != komplexni2.imaginarniCast; } //prekryti metody ToString tridy object public override string ToString() { return (String.Format("{0} {1}i",realnaCast,imaginarniCast)); } } Později je možné přetížené operátory použít při práci s instancemi třídy KomplexniCislo, jak demonstruje následující příklad. KomplexniCislo lKomplexniCislo1 = new KomplexniCislo(4,4); KomplexniCislo lKomplexniCislo2 = new KomplexniCislo(3,2); KomplexniCislo lSoucet = lKomplexniCislo1 + lKomplexniCislo2; Console.WriteLine("Soucet : " + lSoucet); Console.WriteLine("Ekvivalence : " + (lKomplexniCislo1 == KomplexniCislo2)); Poznámka: Při přetěžování relačních operátorů, musí být nová implementace uskutečněna v párech. To znamená, že při přetěžování operátoru == musí být naimplementován i operátor != a naopak. Stejné pravidlo platí i pro přetěžované operátory < , > a pro <=,=>. V příštím díle na nás čekají cykly. Poznáváme C# a Microsoft.NET – 9. díl Příkazy pro vytváření cyklů je problematika na kterou se zaměříme v tomto díle spolu s příkazy break a continue, které se při psaní cyklů nezřídka využívají. Zmíněn bude také rozdíl mezi prefixovým a postfixovým zápisem pro inkrementaci či dekrementaci hodnot proměnných. Prefixový versus postfixový zápis inkrementace/dekrementace Pro kratší zápis inkrementace nebo dekrementace hodnoty číselné proměnné se hojně používají speciální unární operátory složené ze dvou stejných znaků: ++ inkrement -- dekrement Oba tyto operátory mohou být použity v prefixovém zápisu (před operandem) nebo postfixovém zápisu (za operandem) . Rozdíl mezi těmito dvěma druhy zápisu je v čase provedení inkrementace respektive dekrementace. ++operand – hodnota je zvětšena o jedničku a je vrácena operand++ - hodnota je nejdříve vrácena a potom je zvětšena o jedničku 38 Příklady: int x = 5; int y = 2; x++; //x bude 6 y = ++x; // y bude 7, x bude 7 x = y++; // x bude 7, y bude 8 Příkazy pro vytváření cyklů Jazyk C# nabízí k využití tří příkazy pro vytváření cyklů, kterými jsou while, for a dowhile. S těmito příkazy jsou spojeny také příkazy break a continue, které slouží k ovlivnění průběhu cyklů. Příkazy break a continue Jak jsem zmínil výše tyto příkazy lze použít ve všech konstrukcích cyklu a ovlivňují jeho standardní průběh. break – tento příkaz ukončuje nejvnitřnější neuzavřenou smyčku cyklu a ihned opouští cyklus continue – zapříčiní skok na konec nejvnitřnější neuzavřené smyčky, což znamená, že zbytek těla cyklu je vynechán, a je pokračováno další iterací cyklu Příkaz while Tento iterační příkaz testuje výraz s návratovou hodnotou bool vždy před průchodem cyklu. Pokud je výraz splněn, tedy jeho výsledek je true, pak je cyklus vykonán.Tím, že je testování logické podmínky prováděno na začátku, nemusí být cyklus vykonán ani jednou. Syntaxe příkazu while je: while (booleovský výraz) { tělo cyklu } Pokud je tělo cyklu tvořeno pouze jedním příkazem, tak složené závorky nejsou nutné. Následující příklad ukazuje použití cyklu while pro výpis čísel od 0 do 9. public static void VypisCisla() { int x = 0; while (x < 10) { Console.WriteLine(x++); } } Pokud chceme aby v nějakém případě byl zbytek těla cyklu přeskočen použijeme příkaz continue. public static void VypisCisla() { int x = 0; while (x < 10) { //pokud je x rovno 5, skoci se 39 //na konec cyklu a bude se pokracovat //dalsi iteraci if (x == 5) { x++; continue; } Console.WriteLine(x++); } } V tomto příkladu, se na obrazovku vypíšou všechna čísla od 0 do 9 kromě čísla 5, z důvodu použití příkazu continue. Kdybychom příkaz continue nahradili příkazem break, tak by se celý cyklus ukončil když bude hodnota proměnné x rovna 5 a následkem toho by se vypsala pouze čísla od 0 do 4. Protože je řídíci výraz cyklu typu bool, je možné vytvořit nekonečnou smyčku použitím hodnoty true. V tomto případě je nutné použít pro ukončení průběhu cyklu příkaz break. while (true) { //telo cyklu } Příkaz do-while Tento příkaz použijeme pro konstrukci cyklu s řídící podmínkou na konci. Protože je podmínka testována až pro průchodu cyklem, je zajištěno, že cyklus proběhne nejméně jednou. Stejně jako u příkazu while, cyklus probíha dokud je řídící výraz hodnoty true. Zápis příkazu do-while je: do { tělo cyklu } while(booleovský výraz); Následující příklad opět vypíše čísla od 0 do 9, ale nyní s využitím příkazu do-while. public static void VypisCisla() { int i = 0; do { Console.WriteLine(i++); } while(i < 10); } Poznámka: Pokud znáte programovací jazyk Pascal, dejte si pozor na důležitý rozdíl v implementaci cyklu s podmínkou na konci, který je v Pascalu vyjadřován příkazem repeat-until. V repeat-until je cyklus ukončen v případě splnění řídící podmínky a v C# u do-while je tomu naopak. Příkaz for 40 Vytváření cyklů pomocí příkazu for je vhodné v případě, kdy potřebujeme definovat počáteční hodnotu, ukončující podmínku a způsob změny řídící proměnné cyklu. Příkaz for nám umožňuje, na rozdíl od cyklu while, tyto věci zadat přehledně na jedno místo. Tak jako u obou předchozích konstrukcí cyklu je nutné tělo cyklu uzavřít do složených závorek, pokud obsahuje více než jeden příkaz. Obecný zápis konstrukce cyklu pomocí příkazu for je následující: for (start. výraz; booleovský výraz; změna řídící proměnné) tělo cyklu Typické použití příkazu for demonstruje následující příklad pro výpis čísel: public static void VypisCisla() { for (int i = 0; i < 10; i++) Console.WriteLine(i); } Při použití příkazu for je zpočátku vyhodnocen startovní výraz, který se používá pro inicializaci řídící proměnné. Dále se vyhodnotí booleovský výraz, který představuje řídící podmínku cyklu, provede se tělo cyklu a nakonec je vyhodnocen výraz pro změnu řídící proměnné. Při druhé a dalších iteracích cyklu se již začíná vyhodnocením booleovského výrazu. I přestože to není nutné, je dobrým zvykem deklarovat řídící proměnnou přímo v hlavičce příkazu for. Následující příklad by byl také funkční, ale proměnná i by byla platná i mimo cyklus což není úplně správné, protože je vhodné, aby proměnná byla platná pouze tam, kde je skutečně potřeba tj. v cyklu. public static void VypisCisla() { //zde deklarovana promenna bude //platna i mimo cyklus int i; for (i = 0; i < 10; i++) Console.WriteLine(i); } Do hlavičky cyklu není nutné zadávat všechny ze trojice výrazů, ale je nutné za chybějícím výrazem uvést středník aby bylo jednoznačně určeno, který výraz byl vynechán. Takovéto použití ovšem nepatři k těm nejšťastnějším, protože ubírá příkazu for jednu z jeho hlavních výhod, kterou je přehlednost. Použití příkazu for s vynecháním startovního výrazu můžete vidět na tomto příkladu: public static void VypisCisla () { int i = 0; for ( ; i < 10; i++) Console.WriteLine(i); } Také je možné uvést více než jeden startovní výraz a více než jeden výraz pro změnu řídící proměnné. Jednotlivé výrazy se od sebe oddělují pomocí čárky. Uvést více výrazů pro definici podmínky není možné. Ani toto použití příkazu for mu na jeho přehlednosti nepřidá, spíše naopak. Více výrazů do hlavičky příkazu může vypadat takto: 41 public static void VypisCisla4() { int i, soucet; for (i = 0, soucet = 0; i < 10;i++, soucet += i) Console.WriteLine(soucet); } Příště se něco dozvíme o polích a strukturách. Poznáváme C# a Microsoft.NET – 10. díl Tento díl bude věnován implementaci struktur a výčtových typů v jazyku C#. Struktury v C# Jak bylo řečeno dříve, v prostředí .NET frameworku jsou typy děleny do dvou skupin na hodnotové typy a referenční typy. Pokud vytváříme strukturu, vytváříme nový hodnotový typ. Použití struktur je vhodné při vytváření reprezentace jednoduchých objektů. Hlavní výhoda hodnotových typů je v jejich rychlé alokaci paměti a v nepřítomnosti režie, která je vlastní typům referenčním. Pokud totiž máme proměnnou referenčního typu, je jako její hodnota, která je alokována v části paměti jménem zásobník, pouze odkaz na objekt, který se nachází v části pamětí nazývané hromada. U hodnotových typu tomu tak není a hodnota je uložena přímo v proměnné, tedy na zásobníku, což má za následek vyšší rychlost práce s nimi. Zjednodušeně řečeno, alokace místa pro objekt na hromadě zabere běhovému prostředí další čas a také s objekty na hromadě je spojena další režie pro její údržbu. Poznámka: Předchozí výklad rozdílu mezi hodnotovými a referenčními (odkazovými) typy je zjednodušený a je zde pro pochopení toho nejdůležitějšího rozdílu, kterým tedy je, že použití struktur místo tříd s sebou přináší méně režie běhového prostředí. Podrobněji se problematikou referenčních a hodnotových typů zabýval kolega Milan Petřík v díle svého seriálu o VB.NET - Programujeme ve Visual Basic .NET - 18. díl - přehled datových typů. Jelikož proměnné pro hodnotové typy neobsahují žádný odkaz na objekt, ale hodnotu nemohou nabývat hodnoty null, které mohou nabývat proměnné pro referenční typy. Hodnota null znamená, že referenční proměnná neukazuje na žádnou instanci třídy. K vytváření struktur použijeme v jazyce C# klíčové slovo struct. Realizace struktury bod by mohla vypadat takto: public struct Bod { public int x,y; public Bod(int x, int y) { //jelikoz jsou parametry konstruktoru //pojmenovany stejne jako clenske promenne //je nutne pouzit klicove slovo this, ktere //predstavuje aktualni instanci objektu this.x = x; this.y = y; } } 42 Vytvořili jsme strukturu představující bod. Kromě toho, že je místo klíčového slova class použito slovo struct se implementace v tomto případě na pohled nijak neliší od implementace referenčního typu. Struktury s sebou, na rozdíl od tříd, nesou několik omezení. Struktury totiž nelze od žádného typu odvodit a žádný typ nemůže být odvozen od nich. Struktury a konstruktory Konstruktory u struktur fungují poněkud odlišně od konstruktorů u tříd. V případě tříd je to tak, že dokud nevytvoříme novou instanci pomocí new a zavoláním příslušného konstruktoru, referenční proměnná má hodnotu null, tedy na žádnou instanci neukazuje a tím pádem není možné v danou chvíli objekt použít. Protože u struktur neexistují odkazy, tak zavolání implicitního (bezparametrického) konstruktoru pomocí new v jejich případě zajistí vytvoření nové instance, která má všechny své datové členy vynulovány. To znamená, že není nutné použít new před použitím struktury, ale pokud jej nepoužijeme, je nutné, aby datové členy struktury byli inicializovány předtím než jsou použity, jinak program nebude možné zkompilovat. Bod b1; //zavolani konstruktoru neni nutne, //ale datove cleny musí pred pouzitim byt inicializovany b1.x = 4; b1.y = 5; //souradnice x je 4 Console.WriteLine(b1.x); //volani implicitniho konstruktoru //zapricini vytvoreni nove instance //s vynulovanymi datovymi cleny b1 = new Bod(); //souradnice x je nyni 0 Console.WriteLine(b1.x); Definovat implicitní konstruktor struktury je v .NET frameworku zakázáno a to z důvodu, že by bylo možné datovým členům struktury nastavit odlišné výchozí hodnoty, než je počáteční vynulovaný stav. Pokud definujeme konstruktor struktury, tak tento konstruktor musí inicializovat všechny datové členy struktury. Pokud tak neučiníme, nebude možné kód přeložit. Je tomu tak proto, aby bylo zajištěno, že po zavolání konstruktoru je instance struktury plně připravena k použití. Výčtové typy Dalším druhem hodnotového typu jsou výčtové typy, jejichž využití je vhodné v případech, kdy potřebujeme definovat výčet nějakých konkrétních hodnot, které jsou představovány konstantami a proměnné v programu budou nabývat pouze hodnot těchto konstant. Pro vytváření výčtových typů je v C# k dispozici klíčové slovo enum. Následující příklad ukazuje možné použití výčtového typu pro výčet dnů v týdnu. public enum DnyTydnu { Pondeli, Utery, Streda, Ctvrtek, Patek, Sobota, Nedele } 43 Později je možné deklarovat proměnnou typu DnyTydnu , která bude moci nabývat pouze stanovené hodnoty. DnyTydnu den = DnyTydnu.Nedele; Také je samozřejmě možné napsat metodu jejímž parametrem bude nějaký výčtový typ. public static void NejakaMetoda(DnyTydnu den) { //implementace metody } Číselná reprezentace a bázové typu výčtů Každý výčtový typ je založen na jednom ze základních celočíselných typů. Výchozím bázovým typem pro výčtové typy je int (neboli typem Int32 v CTS). První hodnota výčtu je implicitně představována hodnotou nula, která je vždy s definicí další konstanty o jedničku vyšší. V příkladu se dny v týdnu je tedy vlastně pondělí představováno nulou, úterý jedničkou a tak dále. O tom se může přesvědčit použitím explicitního přetypováni na typ int. int intDen = (int)DnyTydnu.Nedele; //vystup bude 6 Console.WriteLine(intDen); Počáteční hodnotu číselné reprezentace výčtu je možné změnit následujícím způsobem: public enum DnyTydnu { Pondeli = 1, Utery, Streda, Ctvrtek, Patek, Sobota, Nedele } Po této změně bude číselná reprezentace výčtu začínat hodnotou 1. Možné je specifikovat nejen počáteční hodnotu číselné reprezentace výčtu, ale i reprezentaci pro další hodnoty výčtu. public enum DnyTydnu { Pondeli = 1, Utery = 3, Streda = 5, Ctvrtek = 7, Patek = 9, Sobota = 11, Nedele = 13 } Pokud chceme ušetřit paměť nebo nám pro náš výčet nestačí počet hodnot typu int je možné explicitně definovat jiný bázový typ pro výčet. Možné bázové typy, které lze pro výčtové typy použít jsou pouze celočíselné typy. 44 Pokud bychom tedy chtěli snížit náročnost našeho výčtu na paměť použili bychom jako bázový například typ byte. public enum DnyTydnu : byte { Pondeli, Utery, Streda, Ctvrtek, Patek, Sobota, Nedele } V příštím díle seriálu se budeme zabývat vytvářením a použitím polí v C#. Poznáváme C# a Microsoft.NET – 11. díl Dnešní díl bude věnován hojně využívané datové struktuře, kterou je pole. Naučíme se jak pole vytvářet a jak je možné jej využít. Co je pole? Pole je datová struktura, která obsahuje určitý počet proměnných. Tyto proměnné jsou nazývány prvky pole. Prvky pole jsou indexovány a pomocí těchto indexů je později možné prvky z pole získávat. V jazyku C# jsou prvky pole indexovány od nuly. Všechny prvky pole jsou stejného typu. V prostředí .NET frameworku je pole objektem referenčního typu, který je potomkem třídy System.Array. Prvky jsou v poli uloženy, podle toho jakého jsou typu. Jestliže jsou prvky představovány objekty referenčního typu (například typu string), jsou v poli uloženy odkazy na objekty. V případě, že prvky pole jsou hodnotového typu (například typu int), pole obsahuje přímo hodnoty objektů. Deklarace pole Předtím než je možné pole používat, musíme si jej nejdříve založit. Deklarace pole je složena ze dvou částí, které představují název pole a jeho typ. datový_typ[] identifikátor; Tímto jsme vytvořili referenční proměnnou typu pole, která má nyní hodnotu null, protože jsme objekt pole ještě nevytvořili. Pro vytvoření instance pole slouží, stejně jako u všech referenčních typů, operátor new, jehož pomocí jsme schopni inicializovat pole o specifikované velikosti. identifikátor = new datový_typ[velikost_pole]; Počáteční hodnota prvků pole po jeho inicializaci zavisí na tom, jestli se jedná o pole objektů hodnotového typu nebo referenčního typu. V případě objektů hodnotového typu inicializace pole zapříčiní vytvoření objektů o specifikovaném počtu a pokud se jedná o objekty referenčního typu jsou vytvořeny pouze odkazy s hodnotou null. Je také možné provést inicializaci pole už při jeho deklaraci. 45 datový_typ[] identifikátor = new datový_typ[velikost_pole]; Pokud tedy chceme vytvořit pole, jehož prvky budou představovat 32-bitová celá čísla (int) provedeme to následujícím způsobem : int[] ciselnePole = new int[4]; Jak bylo zmíněno, tak k prvkům pole se přistupuje pomocí jejich indexu, který je u prvního prvku roven nule. ciselnePole[0] ciselnePole[1] ciselnePole[2] ciselnePole[3] = = = = 1; 2; 3; 4; Pro lepší pochopení je možné si tuto datovou strukturu a její indexování představit, tak jak ukazuje následující obrázek: Další možnosti inicializace Někdy můžeme chtít určit hodnoty prvků pole už při jeho inicializaci. Když určujeme hodnoty prvků pole už při jeho inicializaci, není nutné specifikovat velikost pole. V jazyku C# je toto proveditelné takto: int[] ciselnePole = new int[] {1,2,3,4}; … a v případě pole obsahujícího řetězce takto: string[] dnyTydnu = new string[] {"Po","Ut","St","Ct","Pa","So","Ne"}; Zápis lze provést ještě kratší cestou: int[] ciselnePole = {1,2,3,4}; string[] dnyTydnu = {"Po","Ut","St","Ct","Pa","So","Ne"}; Hodnoty prvků inicializovaného pole tímto způsobem, lze samozřejmě později normálně měnit. 46 Vlastnost Length Třída System.Array, tedy i každé námi vytvořené pole obsahuje vlastnost Length, která může být pouze čtena a je pomocí ní možné zjistit délku pole. int[] ciselnePole = new int[4]; int delkaPole = ciselnePole.Length; //je vracena hodnota 4 Následující příklad ukazuje vypsání všech prvků pole použitím známého cyklu for, kde je v jeho řídící části použita vlastnost Length. public static void VypisObsahPole() { int[] ciselnePole = {1,2,3,4}; for (int i = 0; i < ciselnePole.Length; i++) Console.WriteLine((i+1) + ". prvek pole - " + ciselnePole[i]); } Vícerozměrná pole Pole mohou nabývat více rozměrů než pouze jednoho. Následující deklarace vytvoří dvourozměrné pole o čtyřech řádcích a třech sloupcích: int[,] dvouRozmernePole = new int[4,3]; K prvku, který se nachází na prvním řádku a druhém sloupci přistoupíme tímto způsobem: int prvek = dvouRozmernePole[0,1]; Pole o více než dvou rozměrech by bylo deklarováno analogicky: int[,,] viceRozmernePole = new int[2,3,4]; Podobně jako u jednorozměrných polí je možné specifikovat počáteční hodnoty prvků pole při jeho inicializaci. int[,] dvouRozmernePole = new int[,]{{1,2},{3,4}}; … nebo ještě kratší cestou: int[,] dvouRozmernePole = {{1,2},{3,4}}; K získávání délky rozměrů ve vícerozměrných polí již nepoužijeme vlastnost Length, protože ta v případě vícerozměrných polí vrací součin délek jednotlivých rozměrů, ale využijeme metody GetLength, jejímž argumentem je číslo rozměru, jehož délku požadujeme získat. Rozměry jsou číslovány od nuly, takže když potřebujeme zjistit délku druhého rozměru, zápis bude vypadat následovně: int delkaDruhehoRozmeru = dvouRozmernePole.GetLength(1); Kód, zajišťující vypsání všech prvků dvourozměrného pole pomocí vnořeného cyklu for a metody GetLength by mohl vypadat takto: 47 public static void VypisObsahPole() { int[,] viceRozmernePole = new int[3,3]; viceRozmernePole[0,0] = 1; viceRozmernePole[1,1] = 1; viceRozmernePole[2,2] = 1; for (int i = 0; i < viceRozmernePole.GetLength(0); i++) { for (int j = 0; j < viceRozmernePole.GetLength(1); j++) Console.Write(viceRozmernePole[i,j]); Console.WriteLine(); } } Pole polí Pole polí, která jsou někdy nazývána „zubatá“ jsou pole jejichž prvky jsou pole. Pole tvořící prvky tohoto druhu polí mohou mít různou velikost. Vytvoření pole o velikosti 3 prvků, jehož prvky tvoří pole typu int se zapisuje takto: int[][] zubatePole = new int[3][]; Jelikož po tomto vytvoření jsou prvky pole pouze prázdné odkazy na vnořená pole je vhodné vnořená pole inicializovat. zubatePole[0] = new int[3]; zubatePole[1] = new int[4]; zubatePole[2] = new int[5]; Pokud se vám zdá tento postup příliš zdlouhavý, existuje zde i možnost vnořená pole inicializovat a to včetně jejich počátečních hodnot. int new new new }; [][] zubatePole = new int[][]{ int[]{1,2}, int[]{1,2,3}, int[]{1,2,3,4} Přiřazování hodnot prvkům vnořených polí se provádí dvojicí hranatých závorek, kde první označuje index vnořeného pole a druhá index prvku vnořeného pole. zubatePole[0][1] = 5; zubatePole[1][3] = 10; zubatePole[2][0] = 15; Velikost vnořených polí je možné zjistit pomocí vlastnosti Length takto: int delkaVnorenehoPole = zubatePole[0].Length; Uvedený příklad ukazuje možný způsob, kterým lze vypsat všechny prvky vnořených polí: public static void VypisObsahPole () { int [][] zubatePole = new int[][]{ new int[]{1,2}, new int[]{1,2,3}, 48 new int[]{1,2,3,4} }; for (int i = 0; i < zubatePole.Length; i++) { for (int j = 0; j < zubatePole[i].Length; j++) Console.WriteLine((j + 1) + ".prvek v " + (i + 1) + ".vnorenem poli - " + zubatePole[i][j]); } } Vnořená pole mohou být i vícerozměrná. Kolik mají vnořená pole rozměrů určíme při deklaraci pole. int[][,] zubatePole = new int[5][,]; //inicializace prvku zubatePole[0] = new int[3,4]; Pole jako parametr metody Jak jsme si řekli, pole jsou v prostředí .NET frameworku objekty referenčního typu, tudíž nám nic nebrání v jejich předání metodám ve formě parametru. Jelikož se jedná o referenční typ, je metodě předán pouze odkaz na existující objekt a není do proměnné parametru metody zkopírována jeho hodnota (obsah), jak se tomu děje v případě hodnotových typů. Následující příklad ukazuje použití pole jako parametru metody: public class PrikladParametr { public static void VypisObsahPole(string[] poleProVypsani) { for (int i = 0; i < poleProVypsani.Length; i++) Console.WriteLine((i+1) + ". prvek pole - " + poleProVypsani[i]); } public static void NaplnPole(string[] poleProNaplneni) { for (int i = 0; i < poleProNaplneni.Length; i++) poleProNaplneni[i] = (i+1) + ".prvek"; } } class Zive11Test{ public static void Main(string[] args) { //deklarace a inicializace pole string[] pole = new string[10]; //predani pole jako parametru do metody pro jeho naplneni PrikladParametr.NaplnPole(pole); //predani pole jako parametru do metody pro jeho vypsani PrikladParametr.VypisObsahPole(pole); Console.ReadLine(); } } V příštím díle si doplníme naše znalosti o polích a blíže se podíváme na třídu System.Array. 49 Poznáváme C# a Microsoft.NET – 12. díl Po minulém seznámení s poli se pokusím v tomto díle osvětlit třídu System.Array a její užitečné metody. Pozornost bude také věnována použití cyklu for each, který se pro procházení polí nezřídka užívá. System.Array Jak jsem zmínil v předcházejícím díle, každé námi vytvořené pole je instance třídy, která je odvozena od třídy System.Array. Díky tomu je nám umožněno na vzniklých instancích používat členy, definované v této třídě. Mimo to, obsahuje tato třída i několik statických metod, které nám v mnoha situacích mohou být nápomocné pří užívání polí. Na některé užitečné statické i instanční členy se nyní podíváme blíže. Zjištění počtu rozměrů pole Někdy při práci s poli může být užitečné zjistit počet rozměrů daného pole. K tomuto účelu slouží instanční vlastnost Rank. int[,,] triRozmernePole = new int[2,3,4]; //vysledek je tri Console.WriteLine(triRozmernePole.Rank); Další možnost vytvoření pole Protože jsou pole objekty (stejně jako vše ostatní v .NET), je možné vytvořit instanci pole jinak než použitím zápisu pro deklaraci pole v daném jazyku. Můžeme toho dosáhnout použitím statické metody CreateInstance. //vytvoreni pole o delce 5 prvku int[] mojePole = (int[])Array.CreateInstance(typeof(Int32),5); //vytvoreni trirozmerneho pole int[] lDelkyRozmeru = new int[3]{4,3,2}; int[,,] triRozmernePole = (int[,,])Array.CreateInstance(typeof(Int32),lDelkyRozmeru); Při vytváření pole touto cestou, je nutné použít odpovídající explicitní přetypování na konkrétní typ pole. Typ pole, které má být vytvořeno je metodě CreateInstance předán jako parametr. Poznámka: Datový typ je v prostředí .NET frameworku představován třídou System.Type. Právě instanci této třídy očekává mimo jiné metoda CreateInstance třídy System.Array. operátoru typeof jak je ukázáno v ukázce. Převrácení pořadí prvků pole V případě, že z nějakého důvodu potřebujeme převrátit pořadí prvků pole, použijeme statickou metodu Reverse, jíž je jako parametr předána proměnná pole. int[] ciselnePole = new int[]{1,2,3,4,5}; Array.Reverse(ciselnePole); for (int i = 0; i < ciselnePole.Length;i++) Console.WriteLine(ciselnePole[i]); Výstup bude vypadat takto: 50 5 4 3 2 1 Získáni indexů okrajů pole V určitých případech může být užitečné mít k dispozici indexy spodního nebo vrchního okraje pole. Pro tyto případy tu jsou instanční metody GetLowerBound a GetUpperBound, jimž se ve formě parametru předá číslo rozměru pole, jehož spodní respektive vrchní index potřebujeme zjistit. Následující příklad demonstruje možné použití těchto metod pro výpis pole pomocí cyklu for. int[] ciselnePole = new int[]{1,2,3,4,5}; int spodek = ciselnePole.GetLowerBound(0); int vrsek = ciselnePole.GetUpperBound(0); for (int i = spodek; i <= vrsek;i++) Console.WriteLine(ciselnePole[i]); Mělká kopie pole Při použití takzvané mělké kopie pole je do nově vzniklé kopie pole zkopírován pouze obsah prvků originálu. Následkem tohoto jevu jsou v případě klonování polí, jejichž prvky představují objekty referenčního typu, zkopírovány pouze reference ukazující na tentýž objekt na což je třeba brát ohledy. V případě polí obsahující prvky hodnotových typů jsou do kopie pole zkopírovány objekty. K vytvoření mělké kopie slouží instanční metoda Clone. public static void CloneIntPriklad() { int[] prvniPole = new int[5]{1,2,3,4,5}; int[] druhePole = new int[5]; druhePole = (int[])prvniPole.Clone(); //jelikoz jsou prvky hodnotoveho typu //zmena prvku v prvnim poli neovlivni //hodnotu prvku druheho pole prvniPole[0] = 10; for (int i = 0; i < druhePole.Length;i++) Console.WriteLine(druhePole[i]); } Změna hodnoty prvku prvního pole po uskutečnění klonování neovlivnila hodnotu v druhém poli, jak je možno vidět na výstupu: 1 2 3 4 5 Jinak je to ovšem u prvků referenčního typu. public static void CloneRefPriklad() { //vytvoreni poli zarovek Zarovka[] poleZarovek = new Zarovka[5]; Zarovka[] druhePoleZarovek = new Zarovka[5]; 51 //vytvoreni zarovky s vykonem 120 W //jako prvku prvniho pole poleZarovek[0] = new Zarovka(120); druhePoleZarovek = (Zarovka[])poleZarovek.Clone(); //zmena vykonu zarovky pres prvek prvniho pole poleZarovek[0].Vykon = 100; //vykon zarovky v druhem poli bude take 100 //protoze prvek referencuje tu samou instanci Console.WriteLine("Vykon zarovky v druhem poli : " + druhePoleZarovek[0].Vykon); } Vykon zarovky v druhem poli: 100 Vyhledání prvku v jednorozměrném poli Pokud potřebujeme vyhledat určitý prvek jednorozměrného pole nabízí se nám možnost použití statické metody BinarySearch. Tato metoda vrací index hledaného prvku nebo nulu, když pole hledaný prvek neobsahuje. V ukázce můžete vidět použití jedné z přetížených verzí metody BinarySearch. public static void BinarySearchPriklad() { int[] mojePole = new int[5]; //prirazeni hodnot prvku pole pouzitim //metody SetValue mojePole.SetValue(1,0); mojePole.SetValue(2,1); mojePole.SetValue(3,2); mojePole.SetValue(4,3); mojePole.SetValue(5,4); NajdiPrvek(mojePole,3); } private static void NajdiPrvek(Array pole, Object objektProNalezeni) { //prvni parametr je pole ve kterem se ma hledat //a druhy hledany objekt int lIndex = Array.BinarySearch(pole,objektProNalezeni); if (lIndex == 0) Console.WriteLine("Prvek nebyl v poli nalezen"); else Console.WriteLine("Prvek {0} se v poli nachazi na indexu {1}",objektProNalezeni,lIndex); } Výstup: Prvek 3 se v poli nachazi na indexu 2 Cyklus foreach Cylkus foreach je v jazyku C# předurčen k procházení polí a kolekcí. Z tohoto důvodu se o něm zmiňuji až v tomto díle a neuvedl jsem ho v díle, který pojednával o cyklech. Použití cyklu foreach s poli je jednoduché: foreach(datový_typ identifikátor in pole) { 52 operace } Cyklus provede definované operace pro každý prvek pole. K prvku je v těle cyklu přistupováno prostřednictvím identifikátoru, který je platný pouze v rámci cyklu. Pro pochopení použití uvedu příklad. public static void VypisPole(int[] poleProVypsani) { foreach(int prvek in poleProVypsani) { //každý prvek bude vypsán Console.WriteLine(prvek); } } Tento krátký zápis bude mít stejnou funkčnost jako tento cyklus for: public static void VypisPoleFor(int[] poleProVypsani) { for (int i = 0; i < poleProVypsani.Length;i++) Console.WriteLine(poleProVypsani[i]); } Je patrné, že použití cyklu foreach pro procházení polí je elegantnější a méně náchylné k chybám. V příštím díle se zaměříme na výjimky. Poznáváme C# a Microsoft.NET 13. díl – výjimky V tomto díle je mým úkolem objasnit princip systému výjimek, který je použit v prostředí MS .NET framework. Vysvětlíme si co se pod pojmem výjimka skrývá a jakým způsobem se výjimky používají. Co jsou výjimky? V prostředí Microsoft.NET frameworku je pro zpracovávání výjimečných - chybových stavů použit systém výjimek. Protože, jak již bylo několikrát poznamenáno, je vše v .NET frameworku reprezentováno objekty, výjimky na tom nejsou jinak. Výjimka je objekt alokovaný na hromadě, který nese informaci o chybovém stavu, který v aplikaci nastal a pomocí zachycení tohoto objektu je možné nastalý stav nějakým způsobem zpracovat. Proč výjimky? Mnohé z Vás, kteří již v nějakém jazyce, který výjimky nepodporoval, vytvářeli aplikace možná napadlo proč používat výjimky, když je možné chybové stavy zjistit pomocí návratové hodnoty funkce (metody). Tedy v podáni jazyku C# například nějak takto : static void Main(string[] args) { bool lResult = NejakaMetoda(); } Tímto způsobem samozřejmě můžeme zjistit jestli v metodě nedošlo k chybě, ale při použití takovéhoto přístupu je nutné testovat návratovou hodnotu každé metody. 53 Výsledkem toho bude špatně čitelný kód, kde je logika smíchána se zjišťováním chyb. Jedním z hlavních důvodů pro použití systému výjimek je skutečnost, že chybové stavy jsou zjišťovány – odchytávány v přesně vymezeném prostoru. Vyhození výjimky Pokud v průběhu programu dojde k výjimečné situaci, pro kterou v daném kontextu nemáme dostatek informací, abychom ji mohli hned na místě vyřešit a pokračovat v právě prováděném bloku programu, vyhodíme výjimku. Vyhození výjimky znamená ukončení prováděného bloku programu a možnost výjimku ve volajícím bloku, kde již mohou být informace v dostačujícím množství pro napravení chyby, ošetřit. Všechny výjimky v prostředí .NET frameworku mají společného předka, který je představován třídou System.Exception. Každá odvozená třída představuje konkrétnější výjimečný stav. Například třída DivideByZeroException představuje výjimku dělení nulou. Pro vyhozeni výjimky slouží klíčové slovo throw, kterému je předána instance výjimky. Následující příklad ukazuje vyhození výjimky při pokusu dělit číslo nulou. public class DeleniNulou { public static float Vydel(int a,int b) { if (b == 0) //vyhozeni vyjimky throw new DivideByZeroException(); return a / b; } } Jak je možno ze zdrojového kódu vyčíst, v případě, že je dělitel roven nule vytvoří se nová instance třídy DivideByZeroException, která je následně pomocí throw vyhozena. Odpovědnost za její zpracování je ponechána volajícímu kódu, ale jakým způsobem se výjimka zpracovává? Chráněné bloky a handlery výjimek Odpověď na otázku položenou v předchozím odstavci představuje použití chráněných bloků v kombinaci s takzvanými handlery výjimek, které jsou předurčeny k zachycení určitého typu výjimky a provedení příslušných operací. Chráněný blok, jež se v jazyku C# uvozuje klíčovým slovem try, reprezentující blok ve kterém „zkoušíme“ volat metody, které mohou vyhodit výjimku. Bezprostředně po tomto chráněném bloku může následovat jeden nebo více handlerů výjimky. Blok handleru je uvozen klíčovým slovem catch. U každého handleru je navíc uveden typ výjimky pro kterou je daný handler určen. try { //kod, ktery muze vyhodit vyjimku } //handler vyjimky catch(TypVyjimky identifikator) { //kod reakce na vyjimku } Takže jak by vypadalo zpracováni výjimky z předchozího příkladu můžete vidět níže. 54 public static void MetodaVolajiciVydel() { try { float lVysledek = Vydel(4,0); //nasledujici radky se pri vyvolani //vyjimky v metode Vydel neprovedou Console.WriteLine("Vysledek deleni: " + lVysledek); } catch(DivideByZeroException ex) { Console.WriteLine("Pri deleni doslo k deleni nulou! " + ex); } } Handlery pro více typů výjimek Za blokem try může následovat i více než jeden handler výjimky a to když v bloku try voláme metody, které mohou vyhazovat více typů výjimek. Běhové prostředí při vyhození výjimky použije nejvhodnější handler v závislosti na typu vyvolané výjimky. try { //chraneny blok } catch(typVyjimky1 identifikator) { //obsluzny blok pro typVyjimky1 } catch(typVyjimky2 identifikator) { //obsluzny blok pro typVyjimky2 } Takže pokud je vyvolána výjimka typu typVyjimky1 bude použit handler pro tento typ výjimky a při vyvoláni výjimky typu typVyjimky2 bude použit analogicky druhý handler. Také je možné místo vytváření handlerů pro jednotlivé typy výjimek, které provádějí tytéž operace vytvořit handler zachytávající jejich společného předka. A jelikož je společným předkem všech výjimek v .NET frameworku třída System.Exception nabízí se nám možnost vytvořit handler pro tento typ výjimky a tím pádem zachytit všechny vzniklé výjimky na jednom místě. try { //chraneny blok } catch(System.Exception ex) { //spolecny obsluzny blok pro //vsechny vyvolane vyjimky } Závěrečné bloky V řadě případů je vhodné využít takzvané závěrečné bloky. Na rozdíl od kódu uvnitř chráněného bloku try, jehož provádění se při vyvolání výjimky přeruší, tak závěrečné bloky jsou provedeny vždy, tedy ať k výjimce dojde nebo nikoliv. Z tohoto důvodu se v 55 těchto blocích nejčastěji vyskytují operace potřebné k uvedení objektu do korektního stavu, například uzavření spojení k datovému zdroji (relační databáze, soubor..). K uvozeni závěrečného bloku použijeme klíčové slovo finally. try { //kod, ktery muze vyhodit vyjimku } //handler vyjimky catch(TypVyjimky identifikator) { //kod reakce na vyjimku } finally { //zaverecne operace, jez //jsou provedeny vzdy } V příštím díle se budeme dále zabývat problematikou výjimek. Dozvíme se například jakým způsobem lze vytvořit vlastní typ výjimky a co je předávání výjimek. Poznáváme C# a Microsoft.NET 14. díl – výjimky po druhé V dnešním díle si rozšíříme znalosti o tématu, které jsem načal v předchozím díle, což byl systém výjimek v prostředí MS .NET framework. Mimo jiné se můžete těšit na seznámení s tvorbou vlastních typů výjimek. Předávání výjimek Systém předávání (propagace) výjimek umožňuje nezachycovat výjimku ihned při jejím vyhození volanou metodou. Tedy metoda ve které se výjimka vyskytla vlastně ponechává odpovědnost na volajících metodách. Vzniklá výjimka bude „probublávat“ výše v hierarchii volaných metod dokud nedojde k jejímu zachycení. Pokud výjimka námi není nikde v hierarchii zachycena, je zachycena běhovým prostředím a uživateli se zobrazí okno s hlášení o vzniklé výjimce. Následující příklad demonstruje jak je možné předávat výjimky. Poznámka: To, že se při nezpracované výjimce zobrazí okno .NET frameworku s popisem výjimky je nejběžnější reakce, ale není jediná možná. Co bude provedeno při výskytu nezpracované výjimky je možné ovlivnit. public class DeleniNulou { public static float Vydel(int a,int b) { if (b == 0) //vyhozeni vyjimky throw new DivideByZeroException(); return a / b; } public static void MetodaVolajiciVydel() { //dojde k vyhozeni vyjimky //ktera bude predana volajici metode 56 float lVysledek = Vydel(4,0); Console.WriteLine("Vysledek deleni: " + lVysledek); } } class App { static void VolejVydel() { //pripadna vyjimka neni zachytavana //zadnym handlerem DeleniNulou.MetodaVolajiciVydel(); } static void Main(string[] args) { try { VolejVydel(); } //zde dojde ke zpracovani pripadne vyjimky //vyvolane metodou MetodaVolajiciVydel catch (DivideByZeroException) { Console.WriteLine("V metode VolejVydel nastala chyba, deleni nulou"); } Console.ReadLine(); } } Jak je vidět ve třídě DeleniNulou, případná výjimka vyvolaná metodou Vydel není zachytávána ihned v metodě MetodaVolajiciVydel. Metodu MetodaVolajiciVydel volá aplikační třída App ve své metodě VolejVydel, kde také k zachycení nedojde, jelikož není přítomný žádný handler. Případně vyvolaná výjimka je zachycena až v metodě Main aplikační třídy App, která volá metodu VolejVydel, kde se nalézá handler přímo pro konkrétní typ výjimky DivideByZeroException. Z toho je zřejmé jak je vyvolaná výjimka předávána volajícím metodám, kde může být zpracována. Vlastnost Message třídy System.Exception Jak již víme s předchozího dílu, výjimka je objekt nesoucí informace o vzniklém výjimečném stavu. Informace o nastalém stavu je možné z objektu výjimky získat přečtením jeho vlastností. Často používanou vlastností je vlastnost Message, jejímž přečtením můžeme získat řetězec znaků představující vysvětlení důvodu nastalého stavu. Vlastnosti Message můžeme využít při vyhazování nové výjimky a to použitím přetížené verze konstruktoru třídy System.Exception, do kterého jako parametr zadáme řetězec představující tuto vlastnost. Po rozšíření našich vědomostí o tuto skutečnost napíšeme příklad s výjimkou představující dělení nulou třeba takto: public class DeleniNulou { public static float Vydel(int a,int b) { if (b == 0) //vyhozeni vyjimky pouzitim pretizene //verze konstruktoru pro zadani //vlastnosti Message throw new DivideByZeroException("Delitel je roven nule!"); return a / b; 57 } … class App { static void Main(string[] args) { try { DeleniNulou.Vydel(5,0); } catch (DivideByZeroException ex) { //vypsani vlastnosti Message nastale vyjimky Console.WriteLine(ex.Message); } Console.ReadLine(); } } Vlastnost StackTrace Přečtením této vlastnosti objektu výjimky získáme trasování zásobníku, což je hierarchický výpis volaných metod, který začíná u původce výjimky. Na základě této informace jsme schopni lépe odhalit vzniklý problém, protože vidíme ve které metodě byla výjimka vyhozena a jak byla předávána až k jejímu zachycení. Takže pokud bychom v předchozím příkladu použili místo výpisu vlastnosti Message vlastnost StackTrace vidělo bychom toto: at PrikladyZive14.DeleniNulou.Vydel(Int32 a, Int32 b) in c:\documents and settings\petr puš\dokumenty\visual studio projects\prikladyzive\prikladyzive14\deleninulou.cs:line 13 at Zive14TestApp.App.Main(String[] args) in c:\documents and settings\petr puš\dokumenty\visual studio projects\prikladyzive\zive14testapp\app.cs:line 19 Vytváření vlastních výjimek Postupem času, se při programování s používáním výjimek s velkou pravděpodobností dostanete do situace, kdy vám vestavěné typy výjimek .NET frameworku přestanou stačit. Zajisté budete chtít vytvořit vlastní typ výjimky, který se pro danou výjimečnou událost hodí více než jakákoli z vestavěných, nebo se mezi nimi dokonce žádná, ani vzdáleně podobná, nevyskytuje. Toho lze později využít při vytváření handlerů pro více typů výjimek, protože na naši vlastní výjimku můžeme vytvořit speciální handler. Vlastní výjimku v .NET frameworku vytvoříme odvozením od nějaké existující třídy reprezentující výjimku. Ve velkém počtu případů nám vhodně jako bázová třída poslouží společný předek všech výjimek, tedy třída System.Exception. Pokud je to však možné, tak použijte existujícího typu výjimky, který je významově blízký nově vytvářené výjimce. Jelikož hlavní důvod pro vytvoření vlastní výjimky je nejčastěji v získání nového typu, který bude později handlery zachytáván, stačí pouze uvést při deklaraci třídy předka a nepřekrývat žádné členy. public class MojeVyjimka : System.Exception {} 58 Po takovémto vytvoření vlastní výjimky ji již můžeme v našich třídách nesměle používat jako kteroukoliv jinou, jak zobrazuje následující příklad. class MojeVyjimkaPouziti { public static void MetodaVyvolavajiciMojiVyjimku() { //nejaky kod metody throw new MojeVyjimka(); } static void MetodaHandler() { try { MetodaVyvolavajiciMojiVyjimku(); } catch(MojeVyjimka ex) { //kod pro zpracovani vyjimky typu MojeVyjimka } catch(Exception ex) { //kod pro zpracovani ostatnich vyjimek } } } Omezení při definici více handlerů V .NET frameworku není povoleno při vytváření více handlerů, definovat handler pro obecnější typy výjimek nad handlery pro konkrétnější typy výjimek. Je to logické zabránění chybnému zachytávání výjimek, kde by handler pro obecnější typ výjimky zpracoval i výjimky, které by měly být správně zpracovány handlerem pro konkrétnější typ. Pokud bychom předcházející příklad změnili do následující podoby, program by nešel zkompilovat. static void MetodaHandler() { try { MetodaVyvolavajiciMojiVyjimku(); } //tento handler by zabranil provedeni //konkretnejsiho handleru catch(Exception ex) { //kod pro zpracovani ostatnich vyjimek } catch(MojeVyjimka ex) { //kod pro zpracovani vyjimky typu MojeVyjimka } } Opakované vyhození výjimky 59 Občas můžeme potřebovat při zachycení výjimky provést pouze určitou část akcí a zbylou část operací ponechat na volajících metodách. Toho lze docílit pomocí takzvaného opakovaného vyhození výjimky, kdy použijeme slovíčko throw na již zachycenou instanci výjimky. Tím dosáhneme požadovaného výsledku. public class Rethrow { public static void MetodaVolajiciVyjimku() { //nejaky kod metody throw new MojeVyjimka(); } public static void MetodaHandler() { try { MetodaVolajiciVyjimku(); } catch(MojeVyjimka ex) { //provedeni urcite casti akci //opakovane vyhozeni throw ex; } } } Zabalení výjimky Kromě opakovaného vyhození výjimky je také možné vzniklý objekt výjimky zabalit do jiného objektu výjimky a ten následně vyhodit. To je vhodné v případě, když chceme k výjimce přidat nějaké dodatečné informace a vyhodit nový objekt výjimky, který může být i třeba jiného typu. K zabalení výjimky použijeme jednu z přetížených verzí konstruktoru, který je definován na třídě System.Exception. Tento konstruktor od nás očekává řetězec představující vlastnost Message a zabalovanou(vnitřní) výjimku. public class ExceptionBoxing { public static void MetodaVolajiciVyjimku() { try { DeleniNulou.Vydel(5,0); } catch(DivideByZeroException ex) { //zabaleni vyjimky throw new Exception("Delitel je roven nule!", ex); } } } class App { static void Main(string[] args) { try { 60 ExceptionBoxing.MetodaVolajiciVyjimku(); } catch (Exception ex) { //zobrazeni vyjimky pomoci metody ToString Console.WriteLine(ex.ToString()); } Console.ReadLine(); } } Pro výpis výjimky jsem záměrně použil metodu ToString, která je překryta takovým způsobem, že nám v tomto případě zobrazí informace o přítomné vnitřní výjimce. Uvedený příklad tedy vygeneruje následující výstup: System.Exception: Delitel je roven nule! ---> System.DivideByZeroException: Delitel je roven nule! at PrikladyZive14.DeleniNulou.Vydel(Int32 a, Int32 b) in c:\documents and settings\petr puš\dokumenty\visual studio projects\prikladyzive\prikladyzive14\deleninulou.cs:line 13 at PrikladyZive14.ExceptionBoxing.MetodaVolajiciVyjimku() in c:\documents and settings\petr puš\dokumenty\visual studio projects\prikladyzive\prikladyzive14\exceptionboxing.cs:line 11 --- End of inner exception stack trace --at PrikladyZive14.ExceptionBoxing.MetodaVolajiciVyjimku() in c:\documents and settings\petr puš\dokumenty\visual studio projects\prikladyzive\prikladyzive14\exceptionboxing.cs:line 16 at Zive14TestApp.App.Main(String[] args) in c:\documents and settings\petr puš\dokumenty\visual studio projects\prikladyzive\zive14testapp\app.cs:line 19 Pokud bychom z nějakého důvodu potřebovali získat objekt vnitřní výjimky použijeme vlastnost InnerException na zachyceném objektu výjimky. class App { static void Main(string[] args) { try { ExceptionBoxing.MetodaVolajiciVyjimku(); } catch (Exception ex) { Exception lInnerEx = ex.InnerException; Console.WriteLine(lInnerEx.ToString()); } Console.ReadLine(); } } Příště se seznámíme s velmi zajímavou vlastností jazyku C#, kterou jsou delegáti. Poznáváme C# a Microsoft.NET 15. díl – delegáty V tomto díle se zaměřím na, dle mého názoru, velmi zajímavou vlastnost jazyku C#, pomocí které je nám při vývoji aplikací umožněno tvořit objekty reprezentující reference na metody. Tyto objekty jsou v jazyce C# nazývány delegáty. 61 K čemu delegáty? Jak jsem nastínil o pár řádek výše, instance delegátů slouží k reprezentaci reference na metodu. Takto referencované metody mohou být jak instanční tak statické. Pokud vytváříme typ delegáta, vytváříme vlastně předpis pro signaturu metody. Při vytváření instance delegáta předáme konstruktoru naimplementovanou metodu, která má stejnou signaturu jakou předepisuje delegát. To nám umožňuje, podobně jako u rozhraní, oddělit specifikaci od vlastní implementace. My tedy při deklaraci delegáta tedy pouze specifikujeme požadovaný tvar a na uživateli našich knihoven je vytvoření příslušné metody požadovaného tvaru a její následné obsazení. Delegáty jsou někdy označovány jako „bezpečné ukazatele na funkce“, avšak na rozdíl od ukazatelů na funkce, známých z jazyku C++, jsou delegáty objektově orientované a typově bezpečné. Deklarace delegáta Chceme-li v námi vytvářeném jmenném prostoru respektive třídě, deklarovat delegáta použijeme k tomu klíčové slovo delegate. Specifikátor_přístupu delegate návratový_typ identifikátor(seznam_formálních_parametrů) Tímto vytvoříme nový typ delegáta. V případě, že máme v úmyslu vytvořit delegáta představující libovolnou matematickou operaci, která očekává dva parametry a její návratový typ je double uskutečníme tak tímto způsobem: public delegate double MatematickaOperace(int a, int b); Vytvoření instance delegáta Instanci námi deklarovaného delegáta vytvoříme stejně jako u všech ostatních objektů pomocí operátoru new. Při vytvoření objektu delegáta jej musíme asociovat s konkrétní metodu o předepsané signatuře. typ_delegáta identifikátor = new typ_delegáta(asociovaná_metoda); Takže pokud bychom chtěli vytvořit objekt delegáta MatemetickaOperace musíme mít k dispozici nějakou metodu, kterou bychom s instancí delegáta asociovali. Pro naše ukázkové účely bude vhodné napsat třídu obsahující základní matematické operace. public class MathOps { public static double Soucet(int a, int b) { return a + b; } public static double Rozdil(int a, int b) { return a - b; } public static double Soucin(int a, int b) { 62 return a * b; } public static double Podil(int a, int b) { if (b == 0) throw new DivideByZeroException("Pokus o deleni nulou"); return a / b; } } Jak můžete pozorovat, tak všechny metody implementované ve třídě MathOps mají signaturu odpovídající delegátovy MatematickaOperace. V tuto chvíli již máme splněny podmínky potřebné k vytvoření instance delegáta. Takže vytvoření zapíšeme následujícím způsobem. MatematickaOperace objektDelegata = new MatematickaOperace(MathOps.Soucet); Tímto zápisem tedy došlo k vytvoření objektu delegáta MatematickaOperace. Jelikož jsou všechny metody ve třídě MathOps deklarovány jako statické, není přístupu k nim potřebné vytvořit instanci třídy MathOps. V případě, že by metody byly deklarovány jako instanční postup vytvoření delegáta by se změnil do této podoby: MathOps instanceMathOps = new MathOps(); MatematickaOperace objektDelegata = new MatematickaOperace(instanceMathOps.Soucet); Poznámka: Uvedený způsob přístupu k typu delegáta je možné použít pouze v případě, že je delegát deklarován na úrovni jmenného prostoru. Delegáty je možné deklarovat i na úrovni konkrétní třídy a v těchto případech je k nim přistupováno stejně jako k vnitřním třídám, tedy jako by to byly statické členy. V případě deklarace na úrovni třídy, tedy logicky nemůže být delegát označen jako statický. Jak se nově vzniklá instance dá využít se dozvíme v následujícím odstavci. Volání delegáta Delegáty velmi často využijeme jako parametry nějaké metody. Metoda jednoduše očekává předání delegáta určitého typu a na nás tedy je objekt delegáta, asociovaného s nějakou metodou, předat. Mějme pro náš příklad třídu Calculator, která obsahuje metodu, jejímž parametrem je delegát typu MatematickaOperace. public class Calculator { public void ProvedOperaci(int a, int b, MatemetickaOperace operace) { Console.WriteLine(operace(a,b)); } } } Metoda ProvedOperaci tedy kromě dvou operandů očekává objekt delegáta představující Matematickou operaci. Jediné co metoda provede je, že vypíše výsledek asociované matematické operace na konzoli a to pomocí volání delegáta skrze formální parametr metody: operace(a,b) 63 Jak tedy metodě předáme konkrétní matematickou operaci názorně ukazuje metoda Test ve třídě CalculatorApp, která vytvoří instanci třídy Calculator, následně je vytvořen objekt delegáta a ten je dosazen metodě ProvedOperaci. public class CalculatorTest { public static void Test() { Calculator lCalculator = new Calculator(); //vytvoreni instance delegata a asociace s metodou //Soucet tridy MathOps MatematickaOperace objektDelegata = new MatematickaOperace(MathOps.Soucet); lCalculator.ProvedOperaci(10,5,objektDelegata); } } Skládání delegátů Delegáty disponují jednou výbornou vlastností, kterou představuje možnost složit delegáta ze dvou nebo více existujících delegátů. Takto vytvořená instance delegáta svým voláním zapříčiní volání všech metod asociovaných s instancemi delegátů, z nichž byla složena. Pro složení delegátů použijeme operátor + . Skládání delegátů lze použít pouze na delegáty stejného typu. Tak jako mohou být delegáty komponovány pomocí operátoru + , mohou být i dekomponovány a to použitím operátoru - . Následující příklad ukazuje možné použití složeného delegáta. class MultiCast { public delegate void PozdravDelegate(string jmeno); public static void Ahoj(string jmeno) { Console.WriteLine("Zdravime " + jmeno); } public static void Nashledanou(string jmeno) { Console.WriteLine("Nashledanou " + jmeno); } } public class MultiCastTest { public static void RunMultiCast() { MultiCast.PozdravDelegate del1,del2,del3,del4; del1 = new MultiCast.PozdravDelegate(MultiCast.Ahoj); del2 = new MultiCast.PozdravDelegate(MultiCast.Nashledanou); //slozeni delegatu del3 = del1 + del2; del3("Petr"); //dekomponovani delegatu del4 = del3 - del1; del4("Petr"); } } 64 Instanci delegáta s identifikátorem del3 jsme vytvořili kompozicí instancí delegátů del1 a del2. Tím pádem se při volání tohoto delegáta zavolají dvě metody, jedna, která byla asociována s instancí delegáta del1 (MultiCast.Ahoj) a s ní také metoda MultiCast.Nashledanou, protože byla asociována s instancí del2. Po zavolání složeného delegáta del3 vypíše tento výstup: Zdravime Petr Nashledanou Petr Instance delegáta del4 byla vytvořena odebráním instance del1 od složeného delegáta del3, po této operaci tím pádem instance del4 obsahovala pouze instanci del2 a proto se nám po jejím zavolání objeví následující výstup: Nashledanou Petr V příštím díle se seznámíme s událostmi u kterých se nám nabyté informace o delegátech budou velmi hodit. Poznáváme C# a Microsoft.NET 16. díl – události Po delegátech se budu v tomto díle věnovat další velmi zajímavé vlastnosti .NET frameworku, která se často používá při zpracovávání asynchronních operací v tomto prostředí. K tomu nám v .NET frameworku a tím pádem i v jazyku C# slouží členy třídy zvané události. Co jsou události? Události představují v jazyku C# cestu, která umožňuje třídě upozornit jinou třídu nebo třídy, že v ní došlo k něčemu zajímavému, na což by mohla tato třída respektive třídy určitým způsobem zareagovat. Časté použití můžeme nalézt v implementaci grafických uživatelských prostředí pro upozornění, že uživatel provedl nějakou akci, na níž by mohla být implementována reakce. Samozřejmě to není jediné možné použití události. Události se hodí všude tam, kde je potřeba upozornit okolí na to, že se v objektu respektive třídě došlo k nějaké změně stavu. Pro definici metod, které mohou být použity pro reakci na vzniklou událost slouží delegáty, kterými jsem se zaobíral v minulém díle, a proto by bylo vhodné abyste měli v implementaci delegátů jasno. Deklarace události Pro deklaraci nové události ve třídě použijeme klíčové slovo event. Musíme také určit typ delegáta, který asociujeme s touto události. To má za následek to, že třídy, které budou chtít na vzniklou událost reagovat, musí delegáta tohoto typu vytvořit a k tomuto delegátu asociovat obslužnou metodu(y). modifikátory event typ_delegáta identifikátor_události; Vyvolání události Tím, že jsme ve třídě deklarovali událost, je možné k ní přistupovat jako ke členu delegáta určeného typu. Pokud tento člen nabývá hodnoty null, znamená to, že žádná jiná třída si tuto událost takzvaně nepředplatila, jinými slovy, že nehodlá na nově vzniklou událost jakýmkoli způsobem reagovat. Proto je vhodné před vyvoláním události vždy zkontrolovat jestli člen události nenabývá hodnoty null. Jelikož se tedy událost vyvolá stejně jako volání delegáta obecný způsob vyvolání vypadá takto: 65 if (identifikátor_události != null) identifikátor_události (seznam_případných_parametrů); Ukázková třída vyvolávající událost Po lehkém obecném úvodu bych rád demonstroval použití události na konkrétním případu. Na ukázku definujeme třídu Žárovka, která bude, na rozdíl od její v implementace v jednom s prvních dílů tohoto seriálu, při svém rozsvícení či zhasnutí vyvolávat událost oznamující změnu stavu. //deklarace delagata, ktery bude slouzit jako predpis metody //predstavujici reakci na udalost (handler) public delegate void ZmenaStavuHandler(); /// <summary> /// Trida predstavujici zarovku, ktera vyvolava udalost /// </summary> public class Zarovka { private int vykon; private bool sviti; //deklarace udalosti public event ZmenaStavuHandler Zmeneno; public int Vykon { get { return vykon; } set { vykon = value; } } public bool Sviti { get { return sviti; } } public Zarovka(int vykon) { this.vykon = vykon; } //tato metoda vyvolava udalost protected void PriZmene() { //kontrola, zda existuji klientske objekty //ktere si udalost predplatitili if (Zmeneno != null) //vyvolani udalosti Zmeneno(); } 66 public void Rozsvitit() { sviti = true; //zavolani metody, ktera vyvolava udalost PriZmene(); } public void Zhasnout() { sviti = false; //zavolani metody, ktera vyvolava udalost PriZmene(); } } Na úrovni jmenného prostoru je deklarován delegát ZmenaStavuHandler, který je použit pro určení typu delegáta události Zmeneno ve třídě Žárovka. Delegát předepisuje, že obslužná metoda nebude vracet hodnotu a bude bez vstupních parametrů. Za zmínku stojí metoda PriZmene, která je určena k vyvolání události Zmeneno. Metoda zkontroluje jestli existuje nějaký klientský objekt, který si předplatil událost a pokud ano, dojde k vyvolání události. Metoda PriZmene je volána metodami Rozsviti a Zhasnout, které mění hodnotu soukromého atributu sviti. Obsluha událostí Pokud chceme nastalou událost nějaké instance třídy obsloužit, musíme nějakou třídu učinit takzvaným předplatitelem události. Zvenčí, lze k události třídy nebo její instance přistoupit jako ke členu, ale použití tohoto členu je velmi omezené. Jediné co je .NET frameworkem dovoleno s členy typu událost provést je přidání instance delegáta do seznamu delegátů pro její obsluhu nebo naopak instancí delegáta z tohoto seznamu vyjmout. K přidání instance delegáta slouží operátor += a k jeho vyjmutí operátor -= . Následující kód třídy ZarovkaTest ukazuje způsob obsluhy událostí ve třídě: public class ZarovkaTest { public static void Test() { //vytvoreni instance tridy Zarovka Zarovka mojeZarovka = new Zarovka(100); //vytvoreni instanci delegatu pro obsluhu //a jejich nasledne prirazeni do seznamu //instanci delegatu pro obsluhu udalosti mojeZarovka.Zmeneno += new ZmenaStavuHandler(StavZarovkyZmenenObsluha); mojeZarovka.Zmeneno += new ZmenaStavuHandler(StavZarovkyZmenenObsluha2); //metoda zapricini vyvolani udalosti mojeZarovka.Rozsvitit(); } private static void StavZarovkyZmenenObsluha() { Console.WriteLine("Stav zarovky byl zmenen"); } private static void StavZarovkyZmenenObsluha2() { Console.WriteLine("Druha metoda obsluhujici nastalou zmenu stavu zarovky"); } } 67 Jediné co se v metodě Test této třídy děje, je vytvoření instance třídy žárovka, následné vytvoření instancí delegátů typu ZmenaStavuHandler, které jsou asociovány s obslužnými metodami a ihned připojeny k obsluze události. Potom dochází k volání metody Rozsvit, která zapříčiní vyvolání události. Výstup po zavolání této metody bude tedy vypadat následovně, jak jistě mnozí předpokládají. Stav zarovky byl zmenen Druha metoda obsluhujici nastalou zmenu stavu zarovky Události a dědičnost Jednou ze specifických vlastností událostí, je že nemohou být přímo vyvolány z žádné jiné třídy, než ve které byly deklarovány, to znamená ani ve třídách odvozených, takže v případě, že bychom se pokusili zkompilovat následující kód, nebyli bychom úspěšní. public class BarevnaZarovka : Zarovka { public BarevnaZarovka(int vykon) : base(vykon){ } public BarevnaZarovka(int vykon,System.Drawing.Color barva) : base(vykon) { this.barva = barva; } private System.Drawing.Color barva; public System.Drawing.Color Barva { get { return barva; } set { //chyba - udalost neni mozne v teto //tride udalost vyvolat Zmeneno(); barva = value; } } } Vytvořil jsem třídu reprezentující barevnou žárovku, která je odvozena od třídy Zarovka. Nicméně pokud se v ní pokusím vyvolat událost Zmeneno, překlad programu díky tomu skončí chybou. Řešení v těchto situacích se naskýtá ve formě zavolání metody implementované na bázové třídě, která danou událost vyvolá. Pochopitelně tato metoda musí mít takový specifikátor přístupu, aby k ni bylo možné v odvozené třídě přistoupit. V našem případě se jedná o metodu PriZmene. Takže funkční řešení by vypadalo takto: public class BarevnaZarovka : Zarovka { public BarevnaZarovka(int vykon) : base(vykon){ } public BarevnaZarovka(int vykon,System.Drawing.Color barva) : base(vykon) { this.barva = barva; } private System.Drawing.Color barva; 68 public System.Drawing.Color Barva { get { return barva; } set { PriZmene(); barva = value; } } } Tím, že metodu vyvolávající událost zpřístupníme odvozeným třídam, tedy dosáhneme toho, že odvozené třídy budou pomocí této metody schopny danou událost vyvolat. Odvozeným třídám můžeme nechat i vetší volnost při vyvolávání události a to tím, že metodu vyvolávající událost definujeme jako virtuální a tím pádem budou odvozené třídy tuto metodu překrýt a naimplementovat po svém. V příštím díle si ještě prohloubíme znalosti o používání událostí v .NET frameworku. Poznáváme C# a Microsoft.NET 17. díl – události podruhé Po seznámení s událostmi, které proběhlo v minulém díle, se budu v tomto díle zabývat doporučeným způsobem pro implementaci tříd v .NET frameworku, které mají vyvolávat události. Doporučený způsob implementace tříd Jak již víme, událost může mít libovolný typ delegáta, který předepisuje signaturu metody sloužící k reakci na ni. Ovšem v .NET frameworku existuje určitý doporučený způsob implementace tříd, které mají vyvolávat události. Jedním z těchto doporučení je typ delegáta, který by měl být použit pro události. Podle tohoto doporučení by měl delegát pro událost předepisovat dva formální parametry. Prvním z nich by měl být objekt, který vyvolal událost a druhý má za úkol představovat objekt nesoucí dodatečné informace o vzniklé události. Objekt nesoucí ony dodatečné informace by měl podle těchto doporučení být instancí třídy, která je odvozena od třídy System.EventArgs. Delegát typu System.EventHandler Pro události, při jejichž vyvolání neexistuje potřeba předat jakékoliv dodatečné informace existuje v .NET frameworku delegát typu System.EventHandler, který by měl být v těchto situacích použit. Tento typ delegáta předepisuje jako druhý parametr metody pro reakci na událost typ System.EventArgs. Jak bylo řečeno třída System.EventArgs je určena pro odvozování tříd pro reprezentaci dodatečných informací o nastalé události, takže instance této třídy žádné informace nenesou. Pro lepší pochopení jsem upravil třídu Zarovka, která se objevila v minulém díle. V této verzi třídy je jako typ delegáta události Zmeneno použit System.EventHandler, takže již splňuje ono výše uvedené doporučení. Zdrojový kód tedy vypadá následovně: public class Zarovka { private int vykon; private bool sviti; 69 //deklarace udalosti public event EventHandler Zmeneno; public int Vykon { get { return vykon; } set { vykon = value; } } public bool Sviti { get { return sviti; } } public Zarovka(int vykon) { this.vykon = vykon; } //tato metoda vyvolava udalost protected virtual void PriZmene(EventArgs e) { //kontrola, zda existuji klientske objekty //ktere si udalost predplatitili if (Zmeneno != null) //vyvolani udalosti - je predana //reference na aktualni instanci a //instance tridy EventArgs Zmeneno(this,e); } public void Rozsvitit() { sviti = true; //zavolani metody, ktera vyvolava udalost PriZmene(EventArgs.Empty); } public void Zhasnout() { sviti = false; //zavolani metody, ktera vyvolava udalost PriZmene(EventArgs.Empty); } } Metoda PriZmene, sloužící k vyvolání události nyní očekává v podobě formálního parametru instanci třídy System.EventArgs, který je následně v jejím těle použit pro vyvolání události, spolu s referencí na aktuální instanci. Jelikož instance třídy System.EventArgs nemůže nést dodatečné informace, tak metody Rozsvitit a Zhasnout použijí pro vytvoření její instance statickou vlastnost Empty, která zapříčiní to samé jako zavolání implicitního konstruktoru. 70 Přenášení dodatečných údajů o události Pokud námi vytvářená třída vyvolává událost, u které je možné, že při jejím zpracovávání budou potřené nějaké dodatečné informace k nastalé události, musíme vytvořit třídu, jejíž instance tyto data ponesou. Vytvoříme tedy novou třídy, která je potomkem třídy System.EventArgs a definujeme členy pro přenos informací. Potom je ještě potřeba definovat delegáta, který jako druhý parametr pro přenos informací o události očekává právě instanci námi vytvořené třídy. Tím dosáhneme toho, že reakční metody budou mít k těmto dodatečným informacím přístup. Jako demonstrační příklad použiji novou verzi třídy BarevnaZarovka, kterou jste také mohli naleznout v příkladech z minulého dílu. Kromě této třídy definuji ještě třídu StavBarevneZarovkyZmenenEventArgs, jejíž instance budou představovat objekty nesoucí ony dodatečné informace. Instance této třídy budou ve formě druhého formálního parametru očekávány delegátem ZmenaBarevneZarovkyEventHandler. public class StavBarevneZarovkyZmenenEventArgs : EventArgs { string kdoZmenil; public StavBarevneZarovkyZmenenEventArgs(string kdoZmenil) { this.kdoZmenil = kdoZmenil; } public string KdoZmenil { get { return kdoZmenil; } } } Jak je ze zdrojového kódu třídy zřejmé, jediná dodatečná informace, kterou její instance ponesou bude řetězec představující jméno člověka, který žárovku přebarvil. Následující kód již představuje novou verzi třídy BarevnaZarovka. public class BarevnaZarovka : Zarovka { private System.Drawing.Color barva; public System.Drawing.Color Barva { get { return barva; } } //definice delegata pro zpracovani udalosti public delegate void ZmenaBarevneZarovkyEventHandler (object sender, StavBarevneZarovkyZmenenEventArgs e); //definice udalosti public event ZmenaBarevneZarovkyEventHandler BarvaZmenena; public BarevnaZarovka(int vykon) : base(vykon){} 71 //metoda vyvolavajici udalost protected void PriZmeneBarvy(StavBarevneZarovkyZmenenEventArgs e) { if (BarvaZmenena != null) BarvaZmenena(this,e); } //metoda slouzici ke zmene barvy zarovky public void Obarvit(System.Drawing.Color novaBarva,string kdoBarvi) { barva = novaBarva; //vytvoreni instance tridy pro neseni udaju o udalosti StavBarevneZarovkyZmenenEventArgs lArgs = new StavBarevneZarovkyZmenenEventArgs(kdoBarvi); //volani metody zapricini vyvolani udalosti PriZmeneBarvy(lArgs); } } Metoda Obarvi pomocí, které je umožněno žárovku přebarvit, očekává kromě nové barvy také řetězec indikující kdo je za změnu barvy žárovky zodpovědný. Na základě této informace je vytvořena instance třídy StavBarevneZarovkyZmenenEventArgs a ta je po té předána jako parametr metodě PriZmeneBarvy, která vyvolává událost. Tak třídy, které budou pomocí svých metod reagovat na tuto událost, budou schopny získat informaci o tom kým byla žárovka přebarvena. To, jakým způsobem je toto realizovatelné můžete shlédnout v následujícím zdrojovém kódu. public class BarevnaZarovkaTest { public static void Test() { BarevnaZarovka mojeBarevnaZarovka = new BarevnaZarovka(80); mojeBarevnaZarovka.BarvaZmenena += new BarevnaZarovka.ZmenaBarevneZarovkyEventHandler(ReakceNaZmenuBarvy); mojeBarevnaZarovka.Obarvit(System.Drawing.Color.Red, "Petr"); } public static void ReakceNaZmenuBarvy(object sender, StavBarevneZarovkyZmenenEventArgs e) { //z instance tridy StavBarevneZarovkyZmenenEventArgs //zjistime kdo zarovku prebarvil Console.WriteLine("Barvu zarovky zmenil " + e.KdoZmenil); } } Další díl se budu zaobírat další zajímavou vlastností jazyku C#, kterou jsou indexery. Poznáváme C# a Microsoft.NET 18. díl – indexery Dnešní díl seriálu bych chtěl věnovat popisu specifické vlastnosti jazyku C#, kterou jsou indexery, které se často používají v případech kdy v sobě třída obsahuje určitý počet objektů a umožňují nám k těmto objektům přistupovat jako v případě polí. 72 Indexery Někdy můžeme chtít dát možnost přistupovat k určitým členům třídy, způsobem, který známe s polí, tedy skrze indexy. K tomu, aby to bylo možné existují v jazyku C# takzvané indexery. Indexery se na úrovni třídy definují velmi podobně jako vlastnosti, takže umožňují definovat akce, které se mají provést při čtení indexované hodnoty respektive při jejím zápisu. Obecný zápis deklarace indexeru vypadá takto: Modifikátory datový_typ this[index(y)] Pro demonstraci si přestavme situaci, ve které máme za úkol naimplementovat jednoduchou datovou strukturu představující list osob. Pokud budeme chtít uživateli našich knihoven zpřístupnit osoby obsažené v listu, místo metody, právě zmíněným indexem, nadefinujeme ve třídě indexer, který osobu na požadovaném indexu zpřístupní. Implementace takovéhoto listu, kde je použit indexer by mohla vypadat následovně: public class Osoba { private string jmeno; private string prijmeni; public Osoba(string jmeno, string prijmeni) { this.jmeno = jmeno; this.prijmeni = prijmeni; } public string Jmeno { get { return jmeno; } set { jmeno = value; } } public string Prijmeni { get { return prijmeni; } set { prijmeni = value; } } //prekryti metody ToString pro vraceni //textove reprezentace public override string ToString() { return "Osoba: " + jmeno + " " + prijmeni; } } 73 public class ListOsob { System.Collections.ArrayList osoby = new System.Collections.ArrayList(); public void PridejOsobu(Osoba osobaProPridani) { osoby.Add(osobaProPridani); } public void OdeberOsobu(Osoba osobaProOdebrani) { osoby.Remove(osobaProOdebrani); } //deklarace indexeru public Osoba this[int indexOsoby] { //definice akci pri cteni indexovane hodnoty get { return (Osoba)osoby[indexOsoby]; } //definice akci pri zapisu indexovane hodnoty set { osoby.Add(value); } } } Nadefinoval jsem jednoduchou třídu pro reprezentaci osoby a třídu ListOsob, která slouži k jejich ukládání. Kromě metod, pro přidání a odebrání osoby z listu je zde deklarován indexer pro přístup k jednotlivým osobám v něm obsažených, pomocí celočíselného indexu. Jak můžete vidět, tak pomocí tohoto indexeru je možné hodnoty jak číst, tak zapisovat, protože obsahuje blok get i set. Poznámka: Prosím, nelekněte se toho, že pro ukládání osob je ve třídě ListOsob interně použita kolekce ArrayList, která na rozdíl od pole mění svou velikost dynamicky, podle potřeby. Kolekcím se budu určitě věnovat v jednom z příštích dílů tohoto seriálu. Protože, třída obsahuje indexer, tak nám již nebrání k tomu, abychom k agregovaným osobám přistupovali pomocí indexu, tak jak ukazuje následující zdrojový kód. public class ListOsobTest { public static void Test() { //vytvoreni instance listu ListOsob list = new ListOsob(); //vytvoreni objektu osob Osoba Petr = new Osoba("Petr","Pus"); Osoba Janna = new Osoba("Jana","Spanhielova"); Osoba Marketa = new Osoba("Marketa","Kecurova"); //pridani osob do listu list.PridejOsobu(Petr); list.PridejOsobu(Janna); list.PridejOsobu(Marketa); //vyzvednuti druhe osoby v poradi pomoci indexu Osoba lHledanaOsoba = list[1]; 74 Console.WriteLine(lHledanaOsoba); } } Použití cyklu foreach se třídou s indexery Pokud lze k agregovaným objektům ve struktuře přistupovat jako v poli, je vhodné zajistit, aby bylo možné k procházení obsahu struktury použít oblíbený cyklus foreach. K tomu, aby tento cyklus mohl být k procházení agregovaných objektů použit, musí třída implementovat rozhraní IEnumerable z jmenného prostoru System.Collections. Toto rozhraní předepisuje pouze jednu metodu, kterou je GetEnumerator. Tato metoda musí vrátit odkaz na instanci třídy implementující rozhraní IEnumerator, které se taktéž nachází ve jmenném prostoru System.Collections. Metody této třídy pak slouží k procházení jednotlivými prvky datové struktury. To pro nás znamená, že pokud chceme, aby bylo možné k procházení naší datové struktury použít cyklu foreach, musíme implementovat třídu, které implementuje rozhraní IEnumerator a instance této třídy vracet pomocí metody GetEnumerator naší třídy. Pro pochopení této problematiky jsem upravil třídu ListOsob do podoby, která již splňuje dané podmínky a tudíž ji bude možné procházet pomocí cyklu foreach. public class ListOsobEnumerable : System.Collections.IEnumerable { System.Collections.ArrayList osoby = new System.Collections.ArrayList(); //metoda predepsana v rozhrani IEnumerable //vraci instanci IEnumerator public System.Collections.IEnumerator GetEnumerator() { return new ListOsobEnumerator(this); } //Implementace rozhrani IEnumerator, slouzici k prochazeni //jendotlivymi prvky listu osob class ListOsobEnumerator : System.Collections.IEnumerator { ListOsobEnumerable listOsob; int index; //parametr konstruktoru predstavuje list osob //ktery ma byt prochazen public ListOsobEnumerator(ListOsobEnumerable list) { index = -1; listOsob = list; } //vlastnost pro ziskani objektu na aktualnim indexu public Object Current { get { return listOsob[index]; } } //Metoda, ktera zajisti posunuti indexu a //vraci true pokud je na danem indexu k dipozici prvek public bool MoveNext() { 75 index++; return (index < listOsob.osoby.Count); } //Metoda, ktera nastavi index na pocatecni pozici public void Reset() { index = -1; } } public void PridejOsobu(Osoba osobaProPridani) { osoby.Add(osobaProPridani); } public void OdeberOsobu(Osoba osobaProOdebrani) { osoby.Remove(osobaProOdebrani); } //definice indexeru public Osoba this[int indexOsoby] { get { return (Osoba)osoby[indexOsoby]; } set { osoby.Add(value); } } } Třída ListOsobEnumerator, která je implementována jako vnitřní soukromá třída, obsahuje implementaci tří metod, předepsaných v rozhraní IEnumerator, které zajistí, že pomocí instancí této třídy bude možné naším listem procházet. Vlastnost Current této třídy, slouží k navrácení aktuálního objektu. Pro posunutí se na další prvek listu slouží metoda MoveNext, která vrací hodnotu true pokud, se na další pozici nachází nějaký prvek. A konečně metoda Reset, zajišťuje nastavení pomyslného ukazatele na začátek listu. Podmínky dané .NET frameworkem máme úspěšně splněny a je tedy možné listem osob iterovat pomocí pohodlného cyklu foreach, jak můžete vidět na zdrojovém kódu třídy ListOsobEnumerableTest. public class ListOsobEnumerableTest { public static void Test() { //vytvoreni instance listu ListOsobEnumerable list = new ListOsobEnumerable(); //vytvoreni objektu osob Osoba Petr = new Osoba("Petr","Pus"); Osoba Janna = new Osoba("Jana","Spanhielova"); Osoba Marketa = new Osoba("Marketa","Kecurova"); //pridani osob do listu list.PridejOsobu(Petr); list.PridejOsobu(Janna); list.PridejOsobu(Marketa); foreach(Osoba aktualniOsoba in list) { 76 Console.WriteLine(aktualniOsoba); } } } Jelikož, máme k dispozici objekt sloužící k procházení naší struktury (ListOsobEnumerator), můžeme s jeho pomocí k procházení použít i jiné cykly, například cyklus while. Mimochodem nějaký takovýto kód je vytvořen kompilátorem jazyka C# při použití cyklu foreach: System.Collections.IEnumerator enumerator = list.GetEnumerator(); while(enumerator.MoveNext()) { Osoba aktualniOsoba = (Osoba) enumerator.Current; Console.WriteLine(aktualniOsoba); } V následujícím dílu si povíme více o převodech mezi číselnými typy. Poznáváme C# a Microsoft.NET 19. díl – převody číselných typů Dnešní díl bude podrobněji pojednávat o převodech mezi číselnými typy. Vysvětlíme si rozdíl mezi implicitním a explicitním převodem a jaká jsou možná rizika při jejich užití. Převody nebo jinými slovy konverze používáme v případech kdy z hodnoty proměnné nebo konstanty určitého typu potřebujeme udělat proměnnou jiného datového typu. Některé hodnoty totiž mohou být vyjádřeny pomocí několika typů a jediný rozdíl je ve způsobu uložení této hodnoty v paměti. Například číslo 5 může být uloženo jako 16bitová hodnota bez znaménka ,jako 32-bitová hodnota se znaménkem nebo i jako hodnota s plovoucí čárkou. Konverze v C# V jazyku C# jsou konverze rozděleny na dva druhy a to na implicitní a explicitní. Implicitní konverze jsou takové konverze, při kterých není možné, aby došlo ke změně hodnoty, protože je hodnota konvertována na typ s větším rozsahem. Naproti tomu při explicitní konverzi je hodnota převáděna na typ s menším rozsahem hodnot a tím pádem může ke změně hodnoty dojít. Pro explicitní konverzi se v jazyku C# používá operátor přetypování, implicitní konverze jsou prováděny automaticky. /*vsechny tyto prevody jsou implicitni protoze je hodnota prevadena na typ s vetsim rozsahem*/ sbyte b = 100; short s = b; int i = s; long l = i; /*prevod na datovy typ s mensim rozsahem musi byt vyjadreny explicitne pomoci operatoru pretypovani*/ i = (int) l; s = (short) i; b = (sbyte) s; Explicitní číselné konverze 77 U explicitních číselných konverzí, tedy u těch, které používají operátor přetypování v případech, kdy se hodnota původního typu “nevejde” do proměnné nového typu dojde ke změně hodnoty. Nová hodnota nabude podoby přebytku do velikosti rozsahu hodnot nového typu, popřípadě násobků velikosti tohoto rozsahu. Pro lepší pochopení se podívejme na tento příklad: public class ExplicitNumberReTyping { public static void Retype() { int intHodnota = 355; //po pretypovani je hodnota 99 (355 - 256(pocet hodnot rozsahu typu byte)) byte byteHodnota = (byte) intHodnota; Console.WriteLine("Nova hodnota : {0}",byteHodnota); } } Po explicitní konverzi hodnoty typu int na typ byte je hodnota 355 změněna na hodnotu 99, což je dáno právě tím, že je do nové hodnoty uložen pouze přebytek nad velikost rozsahu hodnot typu byte. Takže původní hodnota byla 355 a od ní byla odečtena velikost rozsahu hodnot nového typu, což je v případě typu byte 256, tím pádem je nová hodnota rovna číslu 99. Klíčové slovo checked a kontrolované převody V určitých případech můžeme potřebovat kontrolovat, jesti při explicitním přetypování nedojde k tomu co jsme mohli vidět v předchozím případě, tedy k přetečení hodnoty. Jazyk C# nám jako cestu k řešeni tohoto problému nabízí kontrolované převody, které se realizuji použitím klíčového slova checked. Toto klíčové slovo umožňuje definovat kontrolovaný blok, v němž jsou všechny explicitní konverze kontrolovány, nebo je možné jeho pomocí učinit kontrolovanou pouze jednu explicitní konverzi. Následující příklad demonstruje použití klíčového slova checked pro definici kontrolovaného bloku. public static void CheckedBlockRetype() { //definice kontrolovaneho bloku checked { int intHodnota = 355; byte byteHodnota = (byte) intHodnota; } } V případě, že je přetypovávaná hodnota vyšší, než rozsah nového typu a dané explicitní přetypování je prováděno v kontrolovaném bloku dojde k vyvoláni výjimky System.OverflowException. Tím se nám samozřejmě nabízí, pomocí nám již dobře známe konstrukce try – catch, na tuto situaci nějakým způsobem reagovat. public static void CheckedRetypeWithCatch() { try { checked { int intHodnota = 355; byte byteHodnota = (byte) intHodnota; 78 } } catch(System.OverflowException) { //zpracovani preteceni Console.WriteLine("Pri konverzi doslo k preteceni"); } } Předchozí příklady ukazovaly použití klíčového slova checked k definici kontrolovaného bloku, avšak je možné toto klíčové slovo použít i jako operátor pro explicitní konverzi, takže pokud v takovéto konverzi dojde k přetečení je opět vyvolána výjimka System.OverflowException. public static void CheckedOperatorRetype() { int intHodnota = 355; byte byteHodnota = checked ((byte) intHodnota); } Kompilátor jazyku C# nám umožňuje zajistit, aby všechny explicitní konverze v programu byly kontrolované. Lze toho docílit použitím přepínače /checked. Zajisté vás napadlo, jestli je v případě použití tohoto přepínače možné nějaké explicitní konverze definovat naopak jako nekontrolované. Samozřejmě, že to možné je a to pomocí klíčového slova unchecked. public static void UncheckedRetype() { int intHodnota = 355; //tato konverze nebude kontrolovana ani //pri pouziti prepinace kompilatoru /checked byte byteHodnota = unchecked ((byte) intHodnota); } Konverze pomocí třídy System.Convert Ke konverzím mezi číselnými typy lze použít také třídu System.Convert, která nabízí kompletní sadu metod pro podporované konverze. Je určena především pro konverze nezávislé na konkrétním použitém programovacím jazyce. V různých programovacích jazycích pro .NET se totiž mohou techniky, použité pro jednotlivé konverze, lišit. Pomocí metod této třídy, lze provádět jak zužující či rozšiřující konverze tak i konverze nesouvisejících datových typů. Například lze provést konverzi z řetězce na číselnou hodnotu nebo, chcete-li, na logickou hodnotu. Je potřeba poznamenat, že všechny konverze prováděné pomocí této třídy jsou kontrolované, takže při zužujících konverzích může výt vyvolána výjimka. public class SystemConvertRetyping { public static void ToInt32Test() { decimal decHodnota = System.Decimal.MaxValue; //bude vyvolana vyjimka int intHodnota = System.Convert.ToInt32(decHodnota); Console.WriteLine(intHodnota); } 79 //prevod z retezce na logickou hodnotu public static void FromStringToBoolean() { string stringHodnota = "true"; //po provedeni teto konverze bude //hodnota promenne boolHodnota True bool boolHodnota = System.Convert.ToBoolean(stringHodnota); Console.WriteLine(boolHodnota); } public static void FromInt64ToInt32() { long int64Hodnota = 1234567; //vysledek je stejny jako pri pouziti implicitni konverze int intHodnota = System.Convert.ToInt32(int64Hodnota); Console.WriteLine(intHodnota); } //prevod retezce na ciselnou hodnotu public static void FromStringToInt32() { string stringHodnota = "12345678"; int intHodnota = System.Convert.ToInt32(stringHodnota); Console.WriteLine(intHodnota); } } Příští díl se zaměřím na konverze mezi strukturami a referenčními typy. Poznáváme C# a Microsoft.NET 20. díl – konverze referenčních typů V tomto díle se zaměřím na popis možných konverzí mezi referenčními typy. Dozvíme se o možnostech explicitního přetypování mezi instancemi a také o užitečných operátorech, které při konverzích nezřídka využijeme. V jazyce C# jsou možnosti přetypování spojeny s hierarchií dědičnosti použitých tříd. Stejně jako u číselných typů jsou konverze rozděleny na implicitní a explicitní. Rozdíl mezi nimi je v případě referenčních typů takový, že implicitní konverzi můžeme použít v případě, že chceme přetypovat nějaký konkrétnější typ na nějaký obecnější. Jinými slovy tento typ konverze použijeme, když chceme, aby na určitou instanci odkazovala referenční proměnná typu jejího předka. Tak jako u číselných typů v případě implicitních konverzí nehrozí, že by konverze proběhla neúspěšně. To je samozřejmě logické, neboť jelikož je referencovaná instance potomkem, lze na ní zavolat všechny veřejné metody a použít všechny veřejné vlastnosti definované na předkovi. Pokud je třída, jejíhož typu je referenční proměnná polymorfická, tedy má nějaké, metody definovány jako virtuální, bude při volání takovéto metody skrze referenční proměnnou použita verze metody definovaná na třídě odkazované instance. //referencni promenna typu Object bude odkazovat //na pole celych cisel Object lObject = new int[5]; 80 Jelikož je třída System.Object předkem všech tříd v MS.NET frameworku, není problém nechat referenční proměnnou tohoto typu odkazovat na jakoukoli instanci. V uvedeném přikladu referenční proměnná tohoto typu odkazuje na pole čísel typu int. Protože je referenční proměnná typu Object budeme moci používat pouze členy definované na ní. Jak ale dosáhneme toho, abychom k instanci mohli přistupovat opět jako k poli? Explicitní konverze K tomu abychom mohli instanci používat jako pole, musíme odkaz na Object explicitně přetypovat na referenční proměnnou typu pole. Zápis explicitní konverze známe již z minulého dílu. int[] lPole = (int[]) lObject; Console.WriteLine(lPole.Length); Po té, co se na instanci odkazujeme pomocí referenční proměnné typu int[] již můžeme využívat všech členů tohoto typu. Explicitní konverzi je nutné použít všude tam, kde se snažíme referenční proměnnou obecnějšího typu převést na referenční proměnnou specifičtějšího typ. Tedy když chceme z reference na předka udělat referenci na potomka za účelem použití specifických členů, které na předkovi nejsou k dispozici. U explicitních konverzí , na rozdíl od konverzí implicitních, ovšem existuje riziko, že neproběhne korektně. To se stane v případě, kdy chceme nechat referenční proměnnou odkazovat na instanci, ke které ve skutečnosti nemůže být přistupováno pomocí referenční proměnné onoho typu. K této situaci dojde ve chvíli, kdy instance není typu referenční proměnné, ani není potomkem tohoto typu a v případě pokusu přetypování na typ rozhraní, typ instance toto rozhraní neimplementuje. Pokud tedy tato situace nastane, běhové prostředí na ni zareaguje vyhozením výjimky System.InvalidCastException. Object lObject = new int[5]; //tato konverze skonci chybou string lRetezec = (string)lObject; Tím, že spustíme uvedený příklad se o tom přesvědčíme. Referenční proměnná odkazuje na pole a pokus o její převedení na referenční proměnnou typu string je na už první pohled nesmyslný a samozřejmě skončí vyhozením zmiňované výjimky. Příklady s obrazci Pro lepší pochopení problematiky konverzí mezi referenčními typy použijeme jednoduchý příklad s obrazci. Implementaci tříd v článku nehledejte, můžete si ji však stáhnout. Uvedu zde pouze UML digram tříd. 81 Máme zde tři třídy představující určité obrazce, které mají společného předka představovaného abstraktní třídou Tvar. Třída Tvar obsahuje abstraktní metodu DejObsah pro vrácení obsahu konkrétního tvaru. Všechny tři třídy implementují rozhraní IVykreslitelne, takže musejí implementovat metodu Vykresli, pomocí které je konkrétní objekt vykreslen. Přetypování na proměnnou rozhraní Referenční proměnná nemusí být jen typu třídy, ale může být i typu rozhraní. Pomocí takovéto referenční proměnné můžeme odkazovat na jakoukoli instanci třídy, která toto rozhraní implementuje. Skrze tuto referenční proměnnou potom můžeme volat metody definované na rozhraní na konkrétní referencované instanci, která je implementuje. Takže v našem případě můžeme do proměnné typu IVykreslitelne obsadit, kterýkoli ze tří konkrétních tvaru a pomocí této proměnné na nich volat metodu vykresli, jak ukazuje následující příklad. public class RozhraniPriklad { public static void PretypovaniTest() { IVykreslitelne lVykreslitelnyObjekt; 82 Ctverec lCtverec = new Ctverec(5); Kruh lKruh = new Kruh(5); Obdelnik lObdelnik = new Obdelnik(3,4); lVykreslitelnyObjekt = lCtverec; lVykreslitelnyObjekt.Vykresli(); lVykreslitelnyObjekt = lKruh; lVykreslitelnyObjekt.Vykresli(); lVykreslitelnyObjekt = lObdelnik; lVykreslitelnyObjekt.Vykresli(); } } Výstup po spuštění bude vypadat takto: Kreslim ctverec Kreslim kruh Kreslim obdélník Referenční proměnná typu abstraktní třída Referenční proměnná může bez problému být typu abstraktní třída, i když jak víme instanci abstraktních tříd nelze vytvořit. Do takovéto referenční proměnné můžeme díky existenci implicitní konverze obsadit každou instanci třídy, která je od ní odvozena a tím pádem můžeme využívat rozhraní typu abstraktní třídy včetně abstraktních metod, které již jsou na potomkovi naimplementovány. public class AbstraktniPriklad { public static void PretypovaniTest() { Tvar lTvar; Ctverec lCtverec = new Ctverec(5); Kruh lKruh = new Kruh(5); Obdelnik lObdelnik = new Obdelnik(3,4); lTvar = lCtverec; Console.WriteLine(lTvar.DejObsah()); lTvar = lKruh; Console.WriteLine(lTvar.DejObsah()); } } lTvar = lObdelnik; Console.WriteLine(lTvar.DejObsah()); Po spuštění tohoto příkladu budou vypsány obsahy jednotlivých instancí tvarů. Operátor is Operátor is nám umožňuje zjistit jestli lze referenční proměnnou odkazující na existující instanci převést na určitý typ. To se může hodit v situacích, kdy potřebujeme před uskutečněním konverze vědět, jestli je vůbec možná, aby později nedošlo k neplatnému 83 přetypování. Následující příklad demonstruje použití tohoto operátoru v kontextu našeho příkladu. public class OperatorIsPriklad { public static void Vykresli(Object ObjektNaVykresleni) { //v pripade ze predany objekt implementuje //rozhrani, bude vykreslen if (ObjektNaVykresleni is IVykreslitelne) ((IVykreslitelne) ObjektNaVykresleni).Vykresli(); else throw new Exception("Objekt nelze vykreslit"); } } Metoda vykresli pomocí operátoru is zkontroluje jestli předaná instance implementuje rozhraní IVykreslitelne a pokud ano dojde k přetypování a zavolání metody Vykresli. Operátor as Operátor as je podobný operátoru is, avšak na rozdíl od operátoru is pouze nezkontroluje jestli lze instanci na daný typ převést, ale navíc ono přetypování provede. Pokud existující instance nelze na daný typ převést, výsledek použití tohoto operátoru pro přetypování má hodnotu null, místo toho, aby byla vyhozena výjimka, jak je tomu v případě klasické explicitní konverze. Použití operátoru as je efektivnější než použití operátoru is spolu s následným přetypováním. Takto by mohla vypadat ukázková metoda z předchozího příkladu s použitím operátoru as : public class OperatorAsPriklad { public static void Vykresli(Object ObjektNaVykresleni) { IVykreslitelne lVykreslitelnyTvar = ObjektNaVykresleni as IVykreslitelne; //pokud predany objekt neimplementuje rozhrani, //obsahuje promenna hodnotu null if (lVykreslitelnyTvar != null) lVykreslitelnyTvar.Vykresli(); else throw new Exception("Objekt nelze vykreslit"); } } Poznáváme C# a Microsoft.NET 21. díl – komentování a dokumentace Dnešní díl se seznámíme s možnostmi komentování kódu napsaným v jazyce C# a také s možnosti vytváření dokumentace. Pro nováčky se mimo jiné pozastavím nad tím co vlastně komentování zdrojového kódu znamená a proč se dělá. Komentování kódu Ve zdrojovém kódu se kromě výkonného kódu tj. kódu, který vykonává operace, mohou nacházet i dodatečné informace, které slouží pouze pro lidi, které daný kód čtou. Tyto informace jsou nazývány komentáře a pokud tento seriál sledujete, tak se komentáře vyskytovali ve všech zdrojových kódech příkladů. 84 Komentáře, jak název napovídá, slouží k okomentování určité části kódu, k popisu toho co daný úsek kódu provádí, abychom my, nebo někdo jiný, kdo po nás zdrojový kód bude číst, funkčnost onoho bloku rychleji pochopil. Při kompilaci jsou komentáře pochopitelně ignorovány. Je známým programátorským zlozvykem přístup typu „tohle okomentuji později, až to pořádně otestuji“ nebo v tom horším případě „tohle si budu pamatovat a komentovat to nebudu“. Bohužel výsledek prvního rozhodnutí je mnohdy stejný jako u rozhodnutí druhého a tím výsledkem je, že kód zůstane neokomentovaný, což ovšem není dobře, protože si představte situaci, kdy napíšete nějakou třídu, jejíž metody neprovádějí zrovna triviální operace a vy si po půl roce, kdy tuto třídu budete chtít upravit budete říkat „co jsem to tady jenom dělal“ a nějaký čas vám pravděpodobně zabere, než onu funkčnost opět stoprocentně pochopíte. Nemluvě o projektech, na kterých pracuje více jak jeden člověk. Běžné komentáře v C# V C# jsou základní komentáře buď jednořádkové nebo víceřádkové. Jednořádkové komentáře: K vytvoření komentáře, který zabere pouze jeden řádek použijeme symbol dvou lomítek. //jednoradkovy komentar Víceřádkové komentáře: Pokud chceme vytvořit komentář, který zabere více než jeden řádek a nechceme před každý řádek komentáře psát ona dvě lomítka, použijeme víceřádkový komentář. /* komentar na vice radku */ Takto by tedy mohlo vypadat použití těchto komentářů v praxi: /* Tato metoda vraci soucet dvou cisel typu int */ public int Secti(int a, int b) { //secteni hodnot a vraceni vysledne hodnoty return a + b; } Dokumentace v XML Kromě komentování kódu je také možné vytvořit dokumentaci k námi vytvořeným třídám. K tomuto účelu jazyk C# podporuje dokumentační formát založený XML. Pro ty co o XML slyší poprvé uvedu jen, že se jedná o zkratku eXtensible Markup Language (Rozšířitelný značkovací jazyk). Je to značkovací jazyk na první pohled podobný jazyku 85 HTML, ale s podstatným rozdílem v tom, že XML definuje význam jednotlivých částí dokumentu, kdežto HTML definuje jejich vzhled. Kompilátor pro C# umožňuje s použitím parametru /doc vygenerovat XML dokument obsahující dokumentaci našim třídám. Některé tagy (značky) jsou při kompilaci kontrolovány, ostatní představují pouze doporučení dané .NET frameworkem a při kompilaci jsou ignorovány. Výsledný XML dokument je velmi často pomocí XSL (eXtensible stylesheet language – jazyk určený k převodu XML do jiného formátu) transformace převeden na HTML stránku. XML dokument je také často využíván vývojovými prostředími pro zobrazování kontextových popisků při psaní zdrojového kódu. Následující tabulka obsahuje seznam používaných tagů pro vytváření XML dokumentačních komentářů v C#. c Text, který představuje zdrojový kód code Text, který představuje více řádek zdrojového kódu example Pro uvedení příkladu použití členu popř. třídy exception Slouží k uvedení výjimek, které mohou ve třídě nastat. include Umožňuje se referencovat na jiný soubor s XML dokumentací, kde existuje popis k typům v našem zdrojovém kódu. list Používá se pro uvedení výčtu hodnot para Umožňuje strukturování text. Měl by se nacházet uvnitř tagů summary, remarks nebo returns param Slouží k popisu parametru paramref Indikuje, že uvedené slovo označuje parametr permission Určuje viditelnost člena remarks Umožňuje uvést doplňující informace o typu nebo jeho členovi. returns Tato značka by měla uvádět popis návratové hodnoty see Definuje odkaz na jiný programový element seealso Definuje odkat na jiný programový element. Tento odkaz by měl být uveden v sekci See Also. summary Uvádí základní informace o typu nebo jeho členovi. value Používá se popisu hodnoty vlastnosti. Značky exception, include, param, paramref, permission, see a seealso nebo přesněji některé jejich atributy jsou při kompilaci kontrolovány, ostatní jsou součástí doporučení pro dokumentaci v .NET frameworku. U značek exception, permission, see a seealso je kompilátorem kontrolován atribut cref, který referencuje nějaký typ a kompilátor zjišťuje, zda takovýto typ existuje. Při použití značek param a paramref je kontrolováno zda existuje daný parametr a u značky include kompilátor ověří existenci referencovaného XML dokumentu. Pokud tomu tak není kompilace neskončí chybou, ale kompilátor vás na tuto skutečnost upozorní varováním. Jako příklad pro lepší pochopení si uveďme jednoduchou třídu zaměstnanec, na které demonstruji použití některých XML dokumentačních komentářů. using System; namespace PrikladyZive21 { /// <summary> 86 /// Trida predstavujici zamestnance /// slouzici k demonstraci XML /// dokumentacnich komentaru /// </summary> public class Zamestnanec { private int vek; private string jmeno; private string prijmeni; private int hodinovaSazba; /// <value> /// Vek zamestance /// </value> public int Vek { get { return vek; } set { vek = value; } } /// <value> /// Jmeno zamestnance /// </value> public string Jmeno { get { return jmeno; } set { jmeno = value; } } /// <value> /// Prijmeni zamestnance /// </value> public string Prijmeni { get { return prijmeni; } set { prijmeni = value; } } /// <value> /// Hodinova sazba zamestance /// </value> public int HodinovaSazba { get { 87 return hodinovaSazba; } set { hodinovaSazba = value; } } /// <summary> /// Konstruktor ocekavajici jmeno a prijmeni zamestance. /// Parametr <paramref name="jmeno">jmeno</paramref> i <paramref name="prijmeni">prijmeni</paramref> /// jsou typu <see cref="String"/> /// </summary> /// <param name="jmeno">Jmeno zamestance</param> /// <param name="prijmeni">Prijmeni zamestance</param> public Zamestnanec(string jmeno, string prijmeni) { this.jmeno = jmeno; this.prijmeni = prijmeni; } /// <summary> /// Metoda pro vypocet mzdy zamestance. /// <example> /// Priklad pouziti : /// <code> /// Zamestnanec instance = new Zamestnanec("Tomas","Kutin"); /// instance.HodinovaSazba = 65; /// int lMzda = instance.VypocetMzdy(100); /// </code> /// </example> /// </summary> /// <param name="pocetOdpracovanychHodin">Pocet odpracovanych hodin</param> /// <returns>Vysledna mzda</returns> public int VypocetMzdy(int pocetOdpracovanychHodin) { return pocetOdpracovanychHodin * hodinovaSazba; } } } Jak jsem psal, tak některá vývojová prostředí umí vygenerovaného XML souboru s dokumentací využít pro zobrazování kontextové nápovědy při tvorbě kódu. Na obrázku můžete vidět, jak je vytvořená dokumentace zobrazována v prostředí Visual C# .NET. Kontextová nápověda ve Visual C# .NET K vygenerování dokumentace pro uživatele našich tříd nejčastěji použijeme XSL transformaci do formátu HTML. Sobory XSL můžete nalézt na internetu, nebo si je vytvořit sami. V souborech pro stažení jeden takovýto soubor naleznete. Na obrázku níže vidíte jak může vypadat XML soubor s naší dokumentaci po XSL transformaci do HTML. Další díl seriálu věnuji uživatelsky definovaným konverzím 88 Poznáváme C# a Microsoft.NET 22. díl – uživatelsky definované konverze Tento díl bude věnován seznámení se s možností definovat vlastní konverze mezi jednotlivými typy. Jazyk C# nám toto nabízí v podobě uživatelsky definovaných konverzí. Uživatelské konverze V jazyce C# je nám umožněno definovat vlastní konverze mezi třídami nebo strukturami, takže je potom možné typy mezi sebou převádět stejně jako některé věstavěné typy v .NET frameworku. Jelikož jsou základní typy v .NET frameworku implementovány použitím struktur (hodnotových typů), je tím pádem bez problému možné definovat konverzi mezi základním typem a typem námi vytvořeným a naopak. Konverze jsou na úrovni typu definovány jako operátory, tedy jako statické funkce s využitím klíčového slova operator, jejichž název je stejný jako název typu, pro který je konverze definována a jejich vstupní parametr je hodnota typu ze kterého má být konverze provedena. Jak víme z jednoho z minulých dílů, tak konverze jsou v C# dvou druhů - implicitní a explicitní. Z tohoto důvodu je nám umožněno pomocí klíčových slov implicit a explicit určit jestli daná konverze je implicitní respektive explicitní. Implicitní konverze jsou prováděny automaticky a konverze explicitní vyžadují použít syntaxi přetypování. Při návrhu našeho typu definujeme jako explicitní ty konverze, při kterých může docházet ke zkrácení hodnoty, nebo při nichž může dojít k vyvolání výjimky. Následující příklad obsahuje definici pro strukturu představující jedno celé číslo s hodnotou 0 – 9 a jsou na jeho úrovni definovány uživatelské konverze pro převod tohoto typu z/na základní typ byte. /// <summary> /// Strukutra predstavujici jedno cislo (0 - 9) /// </summary> public struct Cislo { private byte hodnota; public byte Hodnota { get { return hodnota; } set { hodnota = value; } } public Cislo(byte HodnotaCisla) { if (HodnotaCisla > 9) throw new ArgumentException("Cislo nemuze nabyvat vetsi hodnoty nez 9"); hodnota = HodnotaCisla; } 89 /// <summary> /// Explcitni konverze pro prevod hodnoty /// typu byte na hodnotu typu Cislo /// </summary> public static explicit operator Cislo(byte ByteHodnota) { return new Cislo(ByteHodnota); } /// <summary> /// Implicitni konverze pro prevod hodnoty /// typu Cislo na hodnotu typu byte /// </summary> public static implicit operator byte(Cislo Cislo) { return Cislo.hodnota; } /// <summary> /// Prekryti metody ToString pro adekvatni /// retezcovou reprezentaci instance typu Cislo /// </summary> public override string ToString() { return "Cislo s hodnotou " + hodnota; } } V konstruktoru struktury je kontrolována jestli hodnota čísla není vyšší než hodnota 9, pokud tato situace nastane je vyhozena výjimka System.ArgumentException . Protože je tento konstruktor volán při provádění explicitní konverze z typu byte na typ Cislo, je zajištěno, že pokud se pokusíme převést hodnotu typu byte větší než 9 na hodnotu typu Cislo, dojde k vyhození oné výjimky při provádění konverze. Po definici těchto konverzí již můžeme bez problémů konvertovat náš typ Cislo na typ byte a naopak jak můžete vidět v tomto zdrojovém kódu : /// <summary> /// Trida pro demonstraci funkcnosti uzivatelsky /// definovanych konverzi na strukture Cislo. /// </summary> public class CisloTest { public static void CisloExplicitTest() { byte lBCislo = 8; Cislo lCislo = (Cislo) lBCislo; Console.WriteLine(lCislo.ToString()); } public static void CisloExplicitExceptionTest() { byte lBCislo = 10; try { //bude vyhozena vyjimka Cislo lCislo = (Cislo) lBCislo; Console.WriteLine(lCislo.ToString()); } catch(ArgumentException ex) { 90 Console.WriteLine("Pri konverzi doslo k chybe : " + ex.Message); } } public static void CisloImplicitTest() { Cislo lCislo = new Cislo(5); byte lBCislo = lCislo; Console.WriteLine(lCislo.ToString()); } } Konverze mezi třídami a strukturami Uživatelsky definované konverze, které na rozdíl od základních typů pracují se třídami nebo strukturami, jsou vytvářeny obdobně, jen je třeba si uvědomit, že konverze může být definována jak pro výchozí tak pro cílový typ a tuto skutečnost brát v potaz při návrhu. Pro lepší pochopení a procvičení použití konverzí jsem vytvořil jednoduchý příklad, ve kterém figuruje i dříve použitá struktura Cislo. V příkladu existuje třída CiselnaHodnota, která se skládá z instancí typu Cislo. Třetím typem použitým v příkladu je struktura RimskeCislo, která obsahuje definici pro implicitní konverzi z hodnoty typu CiselnaHodnota a také pro opačný směr převodu. Následující UML diagram vizuálně popisuje tento příklad. Zdrojové kódy dvou nových typů vypadají takto: /// <summary> /// Trida predstavujici ciselnou hodnotu slozenou /// z instanci typu Cislo. /// </summary> public class CiselnaHodnota { 91 private Cislo[] cisla; private int index; public CiselnaHodnota(int DelkaCiselneHodnoty) { cisla = new Cislo[DelkaCiselneHodnoty]; index = -1; } /// <summary> /// Metoda pro pridani cisla do ciselne hodnoty /// </summary> /// <param name="CisloProPridani">Instance typu cislo pro pridani do /// ciselne hodnoty</param> public void PridejCislo(Cislo CisloProPridani) { cisla[++index] = CisloProPridani; } /// <summary> /// Explicitni konverze hodnoty typu CiselnaHodnota /// na hodnotu typu int. /// </summary> public static explicit operator int(CiselnaHodnota HodnotaProPrevod) { return Int32.Parse((string)HodnotaProPrevod); } /// <summary> /// Implicitni konverze hodnoty typu int /// na hodnotu typu CiselnaHodnota /// </summary> public static implicit operator CiselnaHodnota(int HodnotaProPrevod) { string lHodnotaStr = HodnotaProPrevod.ToString(); CiselnaHodnota lVysledek = new CiselnaHodnota(lHodnotaStr.Length); foreach (char lPismeno in lHodnotaStr) { Cislo lCislo = new Cislo(Convert.ToByte(lPismeno.ToString())); lVysledek.PridejCislo(lCislo); } return lVysledek; } /// <summary> /// Implicitni konverze hodnoty typu CiselnaHodnota /// na hodnotu typu string /// </summary> public static implicit operator string(CiselnaHodnota HodnotaProPrevod) { string lVysledek = String.Empty; for (int i = 0; i <= HodnotaProPrevod.index; i++) { lVysledek += HodnotaProPrevod.cisla[i].Hodnota; } return lVysledek; } } /// <summary> /// Struktura predstavujici rimske cislo. 92 /// </summary> public struct RimskeCislo { private int hodnota; public int Hodnota { get { return hodnota; } set { hodnota = value; } } public RimskeCislo(int CiselnaHodnota) { this.hodnota = CiselnaHodnota; } /// <summary> /// Imiplicitni konverze hodnoty typu RimskeCislo na /// hodnotu typu String /// </summary> public static implicit operator string (RimskeCislo RimskaHodnota) { string lVysledek = String.Empty; lVysledek += RimskaHodnota.CiselnyRetezec(1000,`M`); lVysledek += RimskaHodnota.CiselnyRetezec(500,`D`); lVysledek += RimskaHodnota.CiselnyRetezec(100,`C`); lVysledek += RimskaHodnota.CiselnyRetezec(50,`L`); lVysledek += RimskaHodnota.CiselnyRetezec(10,`X`); lVysledek += RimskaHodnota.CiselnyRetezec(5,`V`); lVysledek += RimskaHodnota.CiselnyRetezec(1,`I`); return lVysledek; } /// <summary> /// Explicitni konverze hodnoty typu CiselnaHodnota /// na hodnotu typu RimskeCislo. /// <remarks> /// Protoze konverze typu ciselna hodnota na typ int muze vyvolat /// vyjimku je tato konverze explicitni. /// </remarks> /// </summary> public static explicit operator RimskeCislo(CiselnaHodnota CiselnaHodnotaProPrevod) { //hodnota je nejdrive prevedena na typ int a po te predana konstruktoru return new RimskeCislo((int)CiselnaHodnotaProPrevod); } /// <summary> /// Imiplicitni konverze hodnoty typu RimskeCislo na /// hodnotu typu CiselnaHodnota /// </summary> public static implicit operator CiselnaHodnota(RimskeCislo HodnotaProPrevod) 93 { return ((int)HodnotaProPrevod.hodnota); } private string CiselnyRetezec(int rad, char pismeno) { string lVysledek = String.Empty; while (hodnota >= rad) { hodnota -= rad; lVysledek += pismeno; } return lVysledek; } } Třída CiselnaHodnota obsahuje metodu PridejCislo slouží k přidání čísla do číselné hodnoty. Kromě této metody obsahuje také definici konverze na typ int, která je explicitní z důvodu, že hodnota představovaná instancí třídy CiselnaHodnota může být vyšší než je maximální možná hodnota typu Int32, což by při volání metody Parse, která je při konverzi použita, zapříčinilo vyvolání výjimky. Této konverze využívá uživatelská konverze definovaná ve struktuře RimskeCislo, která umožňuje převod z hodnoty typu CiselnaHodnota na hodnotu typu RimskeCislo. Tato konverze totiž nejdříve převede hodnotu typu CiselnaHodnota na typ int a tuto hodnotu předá konstruktoru struktury RimskeCislo. Kromě této konverze obsahuje struktura RimskeCislo i uživatelskou konverzi na typ String, která vrací řetězcovou reprezentaci konkrétní instance struktury RimskeCislo. Poznáváme C# a Microsoft.NET 23. díl – direktivy pre-procesoru Dnešní díl se seznámíme s vlastností jazyku C#, pomocí které můžeme do jisté míry ovlivňovat kompilaci zdrojových kódů. Jedná se o takzvané direktivy pre-procesoru, s nimiž jste se v trochu jiné podobě mohli setkat v jazycích C či C++. Direktivy preprocesoru v C# Direktivy preprocesoru nám dávají možnost určit, které části zdrojových kódů budou kompilovány , produkovat chybová hlášení či upozornění a nebo umožňují rozdělit náš zdrojový kód do několika sekcí – takzvaných regionů. Programátoři v jazycích C nebo C++ tento termín jistě znají. Ovšem chtěl bych upozornit na skutečnost, že onen termín je v jazyku C# použit pouze kvůli lepší návaznosti na programovací jazyky C/C++. V C# totiž žádný pre-procesor před kompilací spouštěn není, direktivy pre-procesoru jsou zpracovávány jako součást fáze lexikální analýzy. Pre-procesorové direktivy v jazyku C# zabírají vždy jeden řádek, který začíná znakem #, za kterým následuje název direktivy pre-procesoru. Ve zdrojovém kódu v jazyce C# můžeme použít následující direktivy pre-procesoru : #define, #undef, #if, #elif, #else, #endif, #line, #error, #warning, #region a #endregion. Direktivy určené k podmíněnému vykonání určité sekce kódu Použitím direktiv #define, #undef, #if, #elif, #else a #endif můžeme docílit toho, že některé části zdrojového kódu budou kompilovány a naopak. 94 Direktiva #define slouží k definici určitého symbolu, který později můžeme testovat za pomocí direktiv #if nebo #elif. Pokud je symbol definován a je testován ve výrazu s použitím direktiv #if nebo #elif je hodnota takovéhoto výrazu vyhodnocena jako true. Tímto způsobem je tedy možné vytvářet podmíněné provádění kompilace kódu, protože je-li výraz vyhodnocen kladně, tak je zkompilována určitá větev „obalená“ do uvedených direktiv. Direktivu #define pro definici symbolu musíme použít před jakýmkoli výkonným kódem. Následující příklad demostruje použití těchto direktiv. #define LADENI #define VYPISY using System; namespace PrikladyZive23 { /// <summary> /// Ukazkova trida s pouzitim direktiv preprocesoru /// pro vytvoreni podminecne kompilace kodu /// </summary> public class ConditionalPreDirecrivesExam { public static void NejakaMetoda() { #if (LADENI && VYPISY) Console.WriteLine("Aplikace je v ladicim rezimu a jsou zapnuty vypisy"); #elif (LADENI || VYPISY) Console.WriteLine("Aplikace je v ladicim rezimu nebo jsou zapnuty vypisy"); #elif !(LADENI && VYPISY) Console.WriteLine("Ladeni ani vypisy nejsou zapnuty"); #endif } } } Výstup po spuštění uvedeného zdrojového kódu v této podobě, tedy když jsme definovali oba dva symboly bude následující: Aplikace je v ladicim rezimu a jsou zapnuty vypisy Direktiva #define není jediným způsobem, kterým lze definovat symbol. Druhou možností, která je nám k dispozici se nabízí v podobě přepínače /define kompilátoru. Direktivu #undef užitečně využijeme právě při použití přepínače kompilátoru, kdy na úrovni jednoho zdrojového souboru určitý symbol oddefinujeme. Direktiva #line Použitím této direktivy můžeme dosáhnout toho, že kompilátor bude používat námi určené číslování řádek. To se hodí zejména u automaticky generovaných zdrojových kódů, kde chceme aby měl určitý řádek nebo skupina řádků námi určená čísla. Vlastní číslování uvedeme použitím této direktivy ve tvaru: #line číslo řádku Od uvedení direktivy budou řádky číslovány od inkriminovaného čísla dokud neuvedeme direktivu pro návrat k výchozímu číslování, která se zapisuje ve tvaru: 95 #line default Použití této direktivy ukazuje následující příklad: public class LinePreDirectiveExam { public static void VypisObracenePole(Array Pole) { //od tohoto radku jsou kompilatorem //radky pocitany od cisla 300 #line 300 Array.Reverse(Pole); foreach(object lPrvek in Pole) { String lPrvekStr = lPrvek.ToString(); //od tohoto radku je kompilatorem opet pouzito //vychozi radkovani #line default Console.WriteLine(lPrvekStr); } } } Od řádku Array.Reverse(Pole); budou řádky číslovány od čísla 300 i přes to, že skutečná pozice řádku je 15. Výchozí číslování bude použito od řádku Console.WriteLine(lPrvekStr); , který již bude kompilátorem brán skutečně jako řádek s číslem 22. Takže pokud bychom například na řádku String lPrvekStr = lPrvek.ToString(); udělali nějakou chybu, kompilátor by nám zahlásil, že došlo k chybě na řádku 303. Další možností použití direktivy #line je „skrýt“ určitou skupinu řádků před debuggerem a to použitím této direktivy ve tvaru: #line hidden public static void VypisObracenePoleHidden(Array Pole) { //nasledujici radky budou debugerem ignorovany #line hidden Array.Reverse(Pole); foreach(object lPrvek in Pole) { String lPrvekStr = lPrvek.ToString(); //od tohoto radku budeme moci debugerem //opet krokovat #line default Console.WriteLine(lPrvekStr); } } Pokud bychom chtěli uvedenou metodu pomocí debuggeru krokovat, skočili bychom až na řádek nacházejícím se za direktivou #line default. Dokonce ani kdybychom do onoho “skrytého” bloku mezi direktivami umístili breakpoint, kýženého výsledku v podobě možnosti krokování bychom nedosáhli. Náš breakpoint by totiž byl debuggerem ignorován. 96 Diagnostické direktivy Do této skupiny direktiv pre-procesoru patří direktivy #warning a #error. Při použití direktivy #warning bude kompilátorem vygenerováno varovné hlášení k určitému řádku a jak název napovídá, tak direktiva #error zapříčiní, že kompilace skončí chybou. /// <summary> /// Ukazka pouziti direktivy #warning /// </summary> public class WarningPreDirectiveExam { public static void NejakaMetoda() { #warning Zkonzultovat s kolegou //nejaky vykonny kod metody } } Stejným způsobem jakým je v uvedeném případě použita direktiva #warning pro generování upozornění, že je potřeba kód zkonzultoval s kolegou by se použila direktiva #error pro vygenerování chyby při překladu. Direktivy pro určení specifických bloků kódu Tato skupina direktiv je tvořena dvojicí direktiv #region a #endregion a používají se pro rozdělení našeho zdrojového kódu na určité části. Direktiva #region označuje začátek bloku a direktiva #endregion jeho konec. Tuto možnost oceníte ze jména ve chvíli, kdy programujete v nějakém vývojovém prostředí, které tyto direktivy bere v potaz a reaguje na jejich výskyt tím, že nám nabídne možnost takto označený blok sbalit a tím zvýšit přehlednost kódu. /// <summary> /// Ukazka pouziti direktiv #region a #endregion /// </summary> public class RegionPreDirectiveExam { #region Deklarace atributu private int prvniAtribut; private int druhyAtribut; #endregion #region Definice metod private void PrvniMetoda() { } private void DruhaMetoda() { } #endregion } Na obrázku níže můžete vidět jak na tyto direktivy reaguje vývojové prostředí Visual C#. NET od Microsoftu. 97 Poznáváme C# a Microsoft .NET 24. díl – speciální případy metod Dnešní díl bych rád věnoval seznámení s použitím a deklarací speciálních případů metod. Mezi takovéto druhy metod, které zatím nebyly v tomto seriálu představeny se řadí metody s proměnným počtem parametrů a metody, u jejichž parametrů je uveden modifikátor ref nebo out. Metody s proměnným počtem parametrů Někdy se může stát, že potřebujeme definovat metodu, u které chceme nechat počet jejích parametrů variabilní. K tomu, abychom tohoto cíle dosáhli nám jazyk C# nabízí k použití modifikátor params. Tento modifikátor, který je povoleno uvést pouze před posledním vstupním parametrem metody. Za tímto modifikátorem vstupního parametru se uvádí jako typ parametru pole určitého typu, který představuje typ vstupních parametrů o variabilním počtu. Z tohoto důvodu je pak v těle metody možné ony parametry získávat normální iterací pole. V deklaraci metody je povolen pouze jeden tento modifikátor. Následující ukázková třída Scitac, demonstruje použití modifikátoru params. /// <summary> /// Ukazka na pouziti metod s promennym poctem parametru /// </summary> public class Scitac { public static int Secti(int a, int b) { return a + b; } //do teto metody muze vstoupit promenny pocet //parametru typu int. public static int Secti(params int[] Cisla) { int lSuma = 0; for(int i = 0; i < Cisla.Length; i++) { lSuma += Cisla[i]; } return lSuma; } } 98 První verze metody Secti očekává standardně dva parametry a vrátí jejich součet na tom není nic nového. Ale druhá přetížená verze této metody již používá onen „kouzelný“ modifikátor params, který zařídí to, že uživatel naší třídy bude moci metodě Secti předat libovolný počet hodnot typu int, tak jak ukazuje následující zdrojový kód. public class ScitacTest { public static void Test() { Console.WriteLine(Scitac.Secti(5,6)); //pouziti verze metody s promennym poctem parametru Console.WriteLine(Scitac.Secti(1,2,3,4,5)); } } V případě, že předaný počet parametrů metodě při jejím volání se přesně shoduje s nějakou deklarovanou verzí metody, je zavolána tato verze, pokud ovšem kompilátor nenalezne žádnou takovou verzi metody, která očekává tento přesný počet parametrů, použije verzi metody s modifikátorem params, samozřejmě pokud nějaká takováto verze metody existuje. Takže konkrétně v našem ukázkovém příkladě se při prvním zavoláním metody Secti použije verze deklarovaná právě pro dva vstupní parametry a při druhém volání metody, kdy jí je předáno parametrů pět, kompilátor nenalezne v definici třídy Scitac žádnou takovou verzi metody Secti, která by očekávala pět vstupních parametrů a zkusí najít nějakou verzi metody, kde se vyskytuje modifikátor params a pokud ji nalezne, tak ji použije. Modifikátor ref S velkou pravděpodobností se někdy při svém programování v C# dostanete do situace, kdy budete chtít, aby parametr hodnotového typu byl metodě předán nikoliv zkopírováním hodnoty (předání hodnotou), ale stejně jako v případě referenčních typů, tedy aby se změna hodnoty provedená v těle metody volaného objektu projevila i všude jinde. Pokud tedy chceme parametr hodnotového typu předat jakoby odkazem, použijeme modifikátor vstupního parametru metody ref. Po použití tohoto modifikátoru bude parametr metody reflektovat na stejnou proměnnou, která byla metodě předána při jejím volání. Toho se hodí využít v případech, kdy chceme aby naše metoda vracela více než jednu hodnotu. Samozřejmě metoda s těmito parametry může mít normální návratovou hodnotu. Pokud je v deklaraci metody uveden u nějakého parametru modifikátor ref, jsme nuceni při každém volání takovéto metody, také u předávaného parametru explicitně uvést modifikátor ref. Pokud předáváme metodě parametr s použitím modifikátoru ref, musíme předávanou proměnnou nejdříve inicializovat, jinak překlad programu skončí chybovým hlášením. V deklaraci metody je možné použít tento modifikátor i pro více než jeden vstupní parametr. Použití modifikátoru ref by mohlo vypadat například takto: /// <summary> /// Ukazka pouziti modifikatoru vstupniho parametru metody ref /// </summary> public class ScitacRef { public static void Secti(int a, int b, ref int soucet) { soucet = a + b; 99 } } Metoda Secti v tomto příkladu očekává kromě dvou hodnot pro sečtení i třetí parametr s modifikátorem ref do kterého bude uložen výsledek součtu. public class ScitacRefTest { public static void Test() { int lSoucet = 0; ScitacRef.Secti(5,4, ref lSoucet); //Bude vypsana hodnota 9 Console.WriteLine(lSoucet); } } Jak bylo napsáno, tak i při volání metody je potřeba uvést modifikátor ref, pokud je parametr metody s tímto modifikátorem uveden v deklaraci metody. Proměnná musela být před předáním metodě inicializována a její hodnota se po zavolání metody změnila na 9. Tím, že při deklaraci metody u nějakého parametru použijeme modifikátor ref, vytvoříme její novou verzi, jinými slovy ji přetížíme. Z toho plyne, že je možné provést něco takovéhoto: public class RefOverloadExam { public void NejakaMetoda(int Cislo) { //implementace metody } public void NejakaMetoda(ref int Cislo) { //implementace metody } } Modifikátor out Tento modifikátor vstupního parametru je velmi podobný modifikátoru ref, ale je zde jistý rozdíl v jejich užití. Zatímco u modifikátoru ref bylo požadováno, aby proměnná předávaná jako parametr s modifikátorem ref byla nejprve inicializována, tak u modifikátoru out toto nutné není. Avšak je nutné, aby parametr s modifikátorem out byl v metodě přiřazen. Taktéž jako u modifikátoru ref, tak i u použití tohoto modifikátoru dochází k vytvoření nové přetížené verze konkrétní metody, ale nelze vytvořit přetížení na základě toho, že se v metodě místo modifikátoru ref objeví modifikátor out a naopak. Jinak řečeno to znamená, že nemůžete provést něco takovéhoto: public class BadOverloadExam { public void NejakaMetoda(ref int Cislo) { //implementace metody } public void NejakaMetoda(out int Cislo) { 100 //implementace metody } } Poznáváme C# a Microsoft.NET 25. díl - třídy kolekcí Tento díl bude úvodem do problematiky o třídách představujících datové struktury pro ukládání různých hodnot, které nám jsou k dispozici v základní knihovně tříd prostředí Microsoft.NET framework. Tyto třídy jsou navrženy pro ulehčení práce programátorů a nazývají se třídy kolekcí. Dnes si povíme o co se vlastně jedná a seznámíme se s hojně využívanou třídou ArrayList. Třídy kolekcí a jmenný prostor System.Collections Během našeho poznávání světa Microsoft.NET frameworku jsme doposud narazili pouze na jedinou datovou strukturu, kterou jsme mohli využít pro ukládání různých druhů objektů a to pod takzvanými indexy. Nemám na mysli nic jiného, než základní datovou strukturu - pole. Určitě vás napadlo, že byste při vašem vývoji použili i nějaké jiné, řekněme flexibilnější, datové struktury do kterých byste si „nastrkali“ objekty, a to ne nutně tak, aby byli přístupné pomocí indexů. Základní knihovna tříd (Base Class Library) prostředí Microsoft.NET frameworku v podobě tříd kolekcí, které jsou obsaženy ve jmenném prostoru System.Collections. V tomto jmenném prostoru tedy nalezneme třídy rozhraní představující rozličné listy, fronty, bitová pole, hešové tabulky a takzvané slovníky. Na třídách kolekcí lze provádět různá řazení hodnot v nich obsažených a také je nám umožňeno definovat vlastní způsoby řazení. Třída ArrayList Tato kolekce reprezentuje list hodnot, jehož kapacita je automaticky zvětšována podle potřeby. To znamená, že pokud přidáváme prvek do této struktury a stávající kapacita listu již není dostačující, tak je kapacita zvětšena. Třída ArrayList mimo jiné implementuje rozhraní IList z jmenného prostoru System.Collections, které definuje vlastnosti a operace pro kolekce objektů, k nimž lze přistupovat individuálně podle indexů. Počáteční kapacita může být určena při vytváření instance této třídy formou parametru konstruktoru, pokud tak neučiníme bude pro kapacita použita výchozí hodnota, která je 16. Kapacita listu může také být později explicitně nastavena využitím instanční vlastnosti Capacity. S kapacitou ArrayListu souvisí také metoda TrimToSize, která kapacitu listu změní na velikost potřebnou pro agregované prvky. Prvky jsou do této struktury přidávány pomocí metody Add, která očekává jako parametr instanci typu System.Object, což znamená, že tato datová struktura může obsahovat objekty jakéhokoli typu. K získávání, ale i k modifikaci, agregovaných objektů slouží vlastnost Item, která je v jazyce C# implementována jako indexer. Indexy jsou, stejně jako u pole, číslovány od nuly. Použitím metody Add jsou prvky přidávány na konec listu. Pokud potřebujeme vložit prvek na specifikovaný index, naskýtá se nám možnost použít metodu Insert, které kromě vkládaného prvku předáme onen index. Následující příklad ukazuje základní použití datové struktury ArrayList. public class ArrayListExam { public static void Priklad() 101 { file://inicialize listu bez udani kapacity ArrayList lMujList = new ArrayList(); lMujList.Add("Poznavame "); lMujList.Add(".NET"); file://vlozeni prvku na urcity index lMujList.Insert(1,"Microsoft"); VypisHodnotyPomociFor(lMujList); } /// <summary> /// Vypise vsechny prvky listu pouzitim cyklu for a indexeru /// definovaneho na tride ArrayList /// </summary> public static void VypisHodnotyPomociFor(ArrayList ListProVypsani) { for (int i = 0; i < ListProVypsani.Count;i++) Console.Write(ListProVypsani[i]); } } V uvedeném přikladu je obsah ArrayListu vypsán pomocí metody, která ho iteruje pomocí cyklu for a využívá zmíněného indexeru. Jelikož tato kolekce implementuje rozhraní System.Collections.IEnumerable je možné použít, dle mého názoru, elegantnější metodu průchodu a použít cyklu foreach. public class VypisKolekci { public static void VypisHodnoty(IEnumerable KolekceProVypsani) { foreach(object lZaznam in KolekceProVypsani) Console.Write("{0}, ",lZaznam); Console.WriteLine(); } } Využití metody VypisHodnoty této miniaturní třídy tedy představuje ono druhé, elegantnější řešení. Mimo jiné je možné, díky tomu, že metoda očekává parametr typu rozhraní , tuto metodu použít pro výpis obsahu jakékoli kolekce, která toto rozhraní implementuje. Prvky ArrayListu jsou odebírany použitím metody Remove, které jako vstupní parametr předáme objekt, který má být z instance listu vyjmut. Přesněji řečeno, bude z ArrayListu odebrán první výskyt specifikovaného objektu. V případě, že budeme chtít z listu odebrat prvek na požadovaném indexu použijeme k tomu metodu RemoveAt, která tento index očekává jako parametr. Metoda Clear této datové struktury odebere všechny elementy, které obsahuje, takže není nutné použití průchodu nějakým cyklem. Užitečná je také metoda Contains, vracející hodnotu typu bool, která je true v případě, že objekt předaný této metodě je v listu obsažen. public class ArrayListExam2 { public static void Priklad() { file://pocatecni kapacitu listu nastavime na 5 ArrayList lMujList = new ArrayList(5); NaplnList(lMujList); lMujList.Add("Dalsi prvek"); lMujList.Add("A zase dalsi prvek"); 102 VypisKolekci.VypisHodnoty(lMujList); lMujList.Remove("Dalsi prvek"); lMujList.RemoveAt(0); Console.WriteLine("Obsah listu po vyjmuti prvku :"); VypisKolekci.VypisHodnoty(lMujList); file://odebrani vsech prvku z kolekce lMujList.Clear(); } public static void NaplnList(ArrayList ListProNaplneni) { for(int i = 0; i < ListProNaplneni.Capacity; i++) ListProNaplneni.Add("Prvek " + i); } } Po spuštění uvedeného příkladu uvidíme následující výstup: Prvek 0, Prvek 1, Prvek 2, Prvek 3, Prvek 4, Dalsi prvek, A zase dalsi prvek, Obsah listu po vyjmuti prvku: Prvek 1, Prvek 2, Prvek 3, Prvek 4, A zase dalsi prvek, V příštím díle se seznámíme s dalšími třídami kolekcí. Poznáváme C# a Microsoft.NET 26. díl – třídy kolekcí II. Po minulém seznámení se s kolekcemi a hojně používaným ArrayListem, se dnes podíváme na další užitečné třídy kolekcí. Například na takové, které představují známé datové struktury typu fronta nebo zásobník. Seznámíme se také se s pojmem slovník a s jeho asi nejpoužívanější implementací v podobě hešové tabulky. Třída Stack Třída Stack je implementací jedné ze základních datových struktur – zásobníku. Pro ty co o ni ještě neslyšely, tak tato struktura pracuje takzvaným principem last-in-first-out, což v překladu znamená, že prvek, který vložíte jako poslední, bude vyjmut jako první. Prvky jsou do této struktury přidávány pomocí metody Push, a získávány za použití metody Pop. Metoda Pop, kromě zmíněného získání posledně vloženého prvku, tento prvek ze zásobníku vyjme. Pokud je zásobník prázdný a pokusíme se zavolat tuto metodu, bude vyhozena výjimka System.InvalidOperationException. V situaci, kdy chceme získat posledně vložený prvek, ale nechceme aby tento prvek byl ze zásobníku vyjmut použijeme metodu Peek. Stejně jako u všech tříd kolekcí v základní knihovně tříd .NET frameworku, lze počet obsažených prvků zjistit pomocí instanční vlastnosti Count, která je předepsána v rozhraní ICollection. Následující kód ukazuje použití třídy Stack. /// <summary> /// Priklad pouziti tridy Stack. /// </summary> public class StackPriklad { public static void Priklad() { Stack lMujZasobnik = new Stack(); 103 //pridani prvku do zasobniku lMujZasobnik.Push("Prvni"); lMujZasobnik.Push("Druhy"); lMujZasobnik.Push("Treti"); VypisZasobnik(lMujZasobnik); } /// <summary> /// Tato metoda pouze pomoci metody Pop vyjme a vypise /// obsah zasobniku. /// </summary> /// <param name="Zasobnik"></param> public static void VypisZasobnik(Stack Zasobnik) { for (int i = 0; i < Zasobnik.Count;) Console.Write("{0}, ",Zasobnik.Pop()); Console.WriteLine(); } } Výstup bude vypadat takto: Treti, Druhy, Prvni, Poznámka: Určitě jste zaznamenali, že implementace metody VypisZasobnik není úplně nejšťastnější, protože při vypisování vyjme všechny prvky ze zásobníku. V Příkladu mi šlo jen u demonstraci funkčnosti metody Pop. Rozumnější řešení by bylo pro výpis obsahu zásobníku využit toho, že třída Stack implementuje rozhraní IEnumerable. Třída Queue Tato třída představuje implementaci další známe datové struktury, kterou je takzvaná fronta. Tato datová struktura je založena na principu first-in-first-out, což znamená, že první vložený prvek do struktury bude také jako první vrácen. Prvky jsou do instance této třídy zařazovány metodou Enqueue a k jejich vyřazování slouží metoda Dequeue, která zároveň vrací právě vyřazovaný objekt. Stejně jako u zásobníku, tak v případě, že tuto metodu zavoláme, když je fronta již prázdná, bude vyhozena výjimka System.InvalidOperationException. Pokud chceme jen přečíst prvek, který je na začátku fronty a nechceme jej vyjmout, tak zavoláme metodu Peek. Následující příklad demonstruje použití této kolekce. /// <summary> /// Priklad na pouziti kolekce Queue /// </summary> public class QueuePriklad { public static void Priklad() { Queue lMojeFronta = new Queue(); lMojeFronta.Enqueue("Prvni prvek"); lMojeFronta.Enqueue("Druhy prvek"); lMojeFronta.Enqueue("Treti prvek"); VypisKolekci.VypisHodnoty(lMojeFronta); string lPrvek = (string) lMojeFronta.Dequeue(); Console.WriteLine("Prvni ziskany prvek z fronty je : {0}", lPrvek); } } 104 Po spuštění tohoto kódu byste měli vidět takovýto výstup: Prvni prvek, Druhy prvek, Treti prvek, Prvni ziskany prvek z fronty je : Prvni prvek K výpisu obsahu fronty jsem použil třídu VypisKolekci, známou s minulého dílu, která využívá implementace rozhraní IEnumerable na třídách kolekcí. Slovníky a rozhraní IDictionary Pod kolekci typu slovník si lze představit datovou strukturu, která uchovává své prvky v podobě dvojice klíč-hodnota. Jako společné rozhraní pro tento typ kolekcí je v .NET frameworku použito rozhraní IDictionary. Ve slovnících je dvojice klíč-hodnota použita tak, že se pod unikátním klíčem, nachází určitá hodnota. Z toho logicky plyne, že hodnota ,která v oné dvojici představuje klíč, nemůže nabývat hodnoty null. Nicméně část hodnota ve dvojici může hodnoty null bez problémů nabývat. Rozhraní IDictionary, mimo jiné obsahuje předpis pro vlastnost Item, která je v jazyce C# implementována formou indexeru. Tato vlastnost zpřístupňuje hodnotu asociované dvojice po zadání klíče, pod kterým je hodnota uložena. Rozhraní IDictionary předepisuje metodu GetEnumerator, kterou již známe s rozhraní IEnumerable. Ostatně rozhraní IDictionary toto rozhraní rozšiřuje. Avšak třídy implementující rozhraní IDictionary, vracejí po zavolání metody GetEnumerator instanci třídy implementující rozhraní IDictionaryEnumerator, které rozšiřuje rozhraní IEnumerator. Jeho instanční vlastnost Current nyní vrací instance třídy DictionaryEntry, které představují konkrétní asociovanou dvojici. V instanci třídy DictionaryEntry využijeme vlastnost Key pro získaní klíče a vlastnost Value pro získání hodnoty dvojice. Třída HashTable Tato třída je asi nejpoužívanější implementací výše zmíněného rozhraní. Organizace asociovaných dvojicí hodnot je založena na hešovém kódu hodnoty představující klíč. Možná se právě někteří z vás pozastavili nad pojmem hešový kód. Hešový kód je číselná hodnota představující instanci objektu. Mělo by platit, že pokud jsou dvě instance stejné, měli by mít stejný hešový kód. Dobře naimplementovaná hešová funkce by měla zajistit, aby měla každá různá instance i různý hešový kód. Základní implementace tvorby hešového kódu pro instanci se nachází na třídě System.Object a to ve formě metody GetHashCode. Jelikož je tato metoda virtuální, je možné a dokonce doporučené tuto metody na odvozených třídách překrývat vlastní implementací. HashTable neboli hešová tabulka vyhledává právě na základě hešových kódů objektů uložených jako klíče, umožňuje jí to tak efektivní a rychlý způsob vyhledávání, protože se vyhledává podle čísel. Nové prvky jsou do této struktury přidávány pomocí metody Add, která jako první parametr očekává instanci představující klíč a jako druhý hodnotu, která má být pod klíčem uchována. Odebráni prvku z této kolekce je realizovatelné pomocí metody Remove, které předáme klíč prvku dvojice, kterou chceme odstranit. Užitečné metody jsou také ContainsKey a ContainsValue, pomocí kterých, jak název napovídá, jsme schopni zjistit jestli se v hešové tabulce nachází určitý klíč respektive hodnota. Následující příklad demonstruje použití hešové tabulky. /// <summary> /// Ukazka pouziti tridy HashTable 105 /// </summary> public class HashTablePriklad { public static void Priklad() { Hashtable lMojeTable = new Hashtable(); //pridani prvku do tabulky lMojeTable.Add("Prvni", "Prvni prvek"); lMojeTable.Add("Druhy", "Druhy prvek"); lMojeTable.Add("Treti", "Treti prvek"); VypisTabulku(lMojeTable); //ziskani hodnoty ulozene pod klicem Prvni String lPrvniPrvek = (string)lMojeTable["Prvni"]; Console.WriteLine("Pod klicem Prvni je v tabulce ulozena hodnota : {0}",lPrvniPrvek); Console.WriteLine("V tabulce existuje hodnota pod klicem Druhy : {0}",lMojeTable.ContainsKey("Druhy")); } public static void VypisTabulku(Hashtable Tabulka) { //Vlastnost Current pouziteho enumeratoru vraci instance typu DictionaryEntry foreach(DictionaryEntry lZaznam in Tabulka) { Console.WriteLine("Klic : {0} - Hodnota : {1}", lZaznam.Key,lZaznam.Value); } } } Výstup: Klic : Treti - Hodnota : Treti prvek Klic : Druhy - Hodnota : Druhy prvek Klic : Prvni - Hodnota : Prvni prvek Pod klicem Prvni je v tabulce ulozena hodnota : První prvek V tabulce existuje hodnota pod klicem Druhy : True Z důvodu, že hešová tabulka je slovníkem (implementuje rozhraní IDictionary), při průchodu pomocí enumerátoru, tedy i v případě průchodu pomocí cyklu foreach, dostáváme instance třídy DictionaryEntry. V příkladu byl jako typ klíče použit typ String, jehož použití je jednoduché a navíc je na třídě System.String implementována dobrá hešovací funkce. Příště se něco dozvíme o použití vlastních poskytovatelů hešových kódů a definici vlastního řazení pro námi definované třídy. Poznáváme C# a Microsoft.NET 27. díl – třídy kolekcí III. V tomto dílu seriálu, který je opět spojen se třídami kolekcí, se podíváme na možnost definice vlastních porovnávacích metod, které ve výsledku ovlivní i způsoby řazení v kolekcích. Definice vlastního porovnání instance Zajisté se někdy dostanete do situace, kdy bude potřeba porovnat dvě instance tříd. Můžeme to například provést implementací nějaké vlastní instanční metody, která nám to 106 zajistí. Lepší volba ovšem je nechat naší třídu, která má mít námi určený způsob porovnávání, implementovat rozhraní IComparable, které se nachází v námi již známém jmenném prostoru System.Collections. To nám s sebou přinese mnohé výhody o kterých se dozvíme dále. Rozhraní IComparable nám jednoduše předepisuje naimplementovat metodu CompareTo, která přijímá parametr typu object a vrací celé číslo (int). Pokud je výsledek této metody menší než nula, znamená to, že je naše instance „menší“ než porovnávaný objekt, který byl předán jako parametr. V opačném případě je naše instance „větší“. Metoda by měla vracet nulu pouze v případě, že jsou si instance rovny. Následující zdrojový kód obsahuje definici třídy Osoba, která implementuje rozhraní IComparable a tím pádem i zmiňovanou metodu. /// <summary> /// Trida predstavujici osobu, s definici /// vlastniho porovnavani instanci /// </summary> public class Osoba : IComparable { private string jmeno; private string prijmeni; public Osoba(string Jmeno, string Prijmeni) { this.jmeno = Jmeno; this.prijmeni = Prijmeni; } #region IComparable Members public int CompareTo(object obj) { if (!(obj is Osoba)) throw new ArgumentException("Predany objekt neni typu osoba"); Osoba lOsoba = (Osoba) obj; return String.Compare(this.prijmeni,lOsoba.prijmeni); } #endregion public override string ToString() { return jmeno + " " + prijmeni; } } Jak můžete vidět, tak tato implementace metody, porovnává na základě příjmení daných instancí osob. Pokud se jí pokusíme ve formě parametru předat instanci, která není typu osoba, k porovnání nedojde a bude vyhozena výjimka. K porovnání příjmení jsem použil statickou metodu Compare třídy System.String. Onu, o pár řádek výše, zmiňovanou výhodu použití implementace rozhraní IComparable k definici porovnávání, představuje to, že naše implementace metody CompareTo, bude 107 použita při řazení v nějaké kolekci. Třída ArrayList obsahuje instanční metodu Sort, která zařídí seřazení obsažených prvků. A jelikož naše třída Osoba má definováno porovnávání na základě příjmení budou prvky ArrayListu po zavolání metody Sort seřazeny podle příjmení. /// <summary> /// Priklad, ukazujici nasledek implementace rozhrani /// IComparable na tride Osoba. /// </summary> public class IComparablePriklad { public static void Priklad() { Osoba lMichal = new Osoba("Michal","Hynek"); Osoba lMartin = new Osoba("Martin","Pesek"); Osoba lTomas = new Osoba("Tomas","Berger"); ArrayList lOsoby = new ArrayList(); lOsoby.Add(lMichal); lOsoby.Add(lMartin); lOsoby.Add(lTomas); lOsoby.Sort(); VypisKolekci.VypisHodnoty(lOsoby); } } Výstup bude následující: Tomas Berger, Michal Hynek, Martin Pesek, Definice více možných způsobů porovnání Možná Vás napadlo, že zmíněný způsob implementace porovnávání instancí našich tříd je sice fajn, ale až do té doby, kdy budeme chtít použít více než jeden způsob porovnávání. Předchozí způsob je vhodný pouze pro definici výchozího způsobu porovnávání, protože rozhraní lze implementovat pouze jednou. Jak ale tedy zařídit, aby bylo možné konkrétní způsob řazení zvolit? Odpověď na tuto otázku za nás vyřešili návrháři knihoven .NET frameworku a to v podobě použití rozhraní IComparer. Toto rozhraní totiž předepisuje metodu Compare, přijímající dva parametry typu object, které mají být mezi sebou porovnány. Tato metoda by měla vracet celočíselnou hodnotu, která je menší než nula pokud je první předaný objekt menší než druhý a analogicky větší než nula v případě, že je první objekt větší. Nula by měla být vráceno, když jsou si objekty rovny. Takže za záměrem umožnit porovnávání a tedy i následné řazení v kolekcích jak podle jména tak podle příjmení osob přidáme do třídy Osoba definice dvou vnitřních tříd implementující rozhraní IComparer. public class Osoba : IComparable { private string jmeno; private string prijmeni; public Osoba(string Jmeno, string Prijmeni) { this.jmeno = Jmeno; this.prijmeni = Prijmeni; } 108 public static System.Collections.IComparer RazeniPodleJmena { get { return new PorovnaniPodleJmena(); } } public static System.Collections.IComparer RazeniPodlePrijmeni { get { return new PorovnaniPodlePrijmeni(); } } #region Definice tridy PorovnaniPodleJmena private class PorovnaniPodleJmena : System.Collections.IComparer { #region IComparer Members public int Compare(object obj1, object obj2) { Osoba lOsoba1 = (Osoba) obj1; Osoba lOsoba2 = (Osoba) obj2; return String.Compare(lOsoba1.jmeno,lOsoba2.jmeno); } #endregion } #endregion #region Definice tridy PorovnaniPodlePrijmeni private class PorovnaniPodlePrijmeni : System.Collections.IComparer { #region IComparer Members public int Compare(object obj1, object obj2) { Osoba lOsoba1 = (Osoba) obj1; Osoba lOsoba2 = (Osoba) obj2; return String.Compare(lOsoba1.prijmeni,lOsoba2.prijmeni); } #endregion } #endregion #region IComparable Members public int CompareTo(object obj) { if (!(obj is Osoba)) throw new ArgumentException("Predany objekt neni typu osoba"); Osoba lOsoba = (Osoba) obj; return String.Compare(this.prijmeni,lOsoba.prijmeni); } 109 #endregion public override string ToString() { return jmeno + " " + prijmeni; } } Do naší třídy tedy přibyly dvě vnitřní třídy. První definuje porovnání podle jména a druhá podle příjmení. To jakým způsobem budou objekty porovnávány respektive řazeny určíme například u ArrayListu tak, že využijeme přetížení metody Sort. Některé její verze totiž očekávají instanci třídy implementující rozhraní IComparer. Mimo dvou vnitřních tříd přibyly do třídy Osoba i dvě nové statické vlastnosti (RazeniPodleJmena, RazeniPodlePrijmeni), které vracejí instance oněch vnitřních tříd a to hlavně proto, aby bylo určování způsobu porovnávání o něco příjemnější (jinak bychom vždy museli psát v kódu new pro vytváření instancí vnitřních tříd). /// <summary> /// Priklad pouziti razeni v ArrayListu podle /// parametru typu IComparer /// </summary> public class IComparerPriklad { public static void Priklad() { Osoba lMichal = new Osoba("Michal","Hynek"); Osoba lMartin = new Osoba("Martin","Pesek"); Osoba lTomas = new Osoba("Tomas","Berger"); ArrayList lOsoby = new ArrayList(); lOsoby.Add(lMichal); lOsoby.Add(lMartin); lOsoby.Add(lTomas); //serazeni osob podle jmena lOsoby.Sort(Osoba.RazeniPodleJmena); VypisKolekci.VypisHodnoty(lOsoby); //serazeni osob podle prijmeni lOsoby.Sort(Osoba.RazeniPodlePrijmeni); VypisKolekci.VypisHodnoty(lOsoby); } } Výstup po spuštění tohoto příkladu by měl vypadat takto: Martin Pesek, Michal Hynek, Tomas Berger, Tomas Berger, Michal Hynek, Martin Pesek, Poznáváme C# a Microsoft.NET 28. díl – HashProvidery a Klonování Dnešní díl bude ještě z části souviset se třídami kolekcí. Dozvíme se totiž, co to jsou poskytovatelé hešových kódů (HashProvidery) a jak je možné tyto poskytovatele použít. V druhé části se budu zaobírat možností definovat způsob vytvoření kopie instance námi vytvářené třídy. 110 Komplexnější využití hešových tabulek a vlastní HashProvidery V jednom z předchozích dílů pojednávajících o třídách kolekcí v .NET frameworku jsem popsal datovou strukturu HashTable neboli hešovou tabulku. Víme tedy, že hodnota asociované dvojice je vyhledána pomocí hešového kódu klíče, který by měl být pokud možno různý pro každou různou instanci třídy. Nemusí tomu tak ovšem vždy být a nějaké instance i přesto, že jsou logicky různé vrátí hešový kód stejný. Právě proto se při vyzvedávání hodnoty z tabulky ještě po zavolání metody GetHashCode na instanci představující klíč zavolá metoda Equals, která by měla v případě nálezu více odpovídajících prvků určit ten správný – námi hledaný. My ovšem můžeme chtít, aby pro získání hešového kódu instance představující klíč v asociované dvojici byla použita jiná metoda než implicitně volaná GetHashCode. Řešení takovéto situace se naskýtá v podobě vytvoření třídy implementující rozhraní System.Collections.IHashProvider. Ve třídě naimplementujeme metodu GetHashCode, která přijímá jako parametr objekt, ke kterému má vytvořit hešový kód. Instanci takovéto třídy pak můžeme předat jedné z přetížených verzí konstruktoru třídy HashTable, čímž zajistíme, že bude vyhledáváno na základě námi určeného hešového kódu. Spolu s touto instancí můžeme konstruktoru předat i instanci třídy, která implementuje nám již známe rozhraní IComparer pokud nechceme, aby se pro porovnávání instancí při vyhledávání a přidávání nepoužívala implicitní metoda Equals. Pro příklad vzpomeňme na třídu Osoba z minulého dílu. Řekněme, že její instance chceme využít jako klíče v hešové tabulce a také chceme mít možnost se rozhodnout zda bude hešový kód z dané instance získáván na základě jména nebo příjmení osoby. Zdrojový kód by po obohacení třídy o dva HashProvidery mohl vypadat následovně: public class Osoba : IComparable { private string jmeno; private string prijmeni; public Osoba(string Jmeno, string Prijmeni) { this.jmeno = Jmeno; this.prijmeni = Prijmeni; } public static System.Collections.IComparer PorovnavaniPodleJmena { get{return new PorovnaniPodleJmena();} } public static System.Collections.IComparer PorovnavaniPodlePrijmeni { get{return new PorovnaniPodlePrijmeni();} } public static System.Collections.IHashCodeProvider HashPodlePrijmeni { get{return new HashProviderPodlePrijmeni();} } public static System.Collections.IHashCodeProvider HashPodleJmena { get{return new HashProviderPodleJmena();} } private class PorovnaniPodleJmena : System.Collections.IComparer 111 { public int Compare(object obj1, object obj2) { Osoba lOsoba1 = (Osoba) obj1; Osoba lOsoba2 = (Osoba) obj2; return String.Compare(lOsoba1.jmeno,lOsoba2.jmeno); } } private class PorovnaniPodlePrijmeni : System.Collections.IComparer { public int Compare(object obj1, object obj2) { Osoba lOsoba1 = (Osoba) obj1; Osoba lOsoba2 = (Osoba) obj2; return String.Compare(lOsoba1.prijmeni,lOsoba2.prijmeni); } } public int CompareTo(object obj) { if (!(obj is Osoba)) throw new ArgumentException("Predany objekt neni typu osoba"); Osoba lOsoba = (Osoba) obj; return String.Compare(this.prijmeni,lOsoba.prijmeni); } /// <summary> /// Vraci hesovy kod pro hodnotu jmena instance tridy osoba /// </summary> private class HashProviderPodleJmena : System.Collections.IHashCodeProvider { public int GetHashCode(object obj) { if (!(obj is Osoba)) throw new ArgumentException("Predany objekt neni typu osoba"); Osoba lOsoba = (Osoba) obj; return lOsoba.jmeno.GetHashCode(); } } /// <summary> /// Vraci hesovy kod pro hodnotu prjmeni instance tridy osoba /// </summary> private class HashProviderPodlePrijmeni : System.Collections.IHashCodeProvider { public int GetHashCode(object obj) { if (!(obj is Osoba)) throw new ArgumentException("Predany objekt neni typu osoba"); Osoba lOsoba = (Osoba) obj; return lOsoba.prijmeni.GetHashCode(); } } public override string ToString() { return jmeno + " " + prijmeni; } } 112 Do třídy přibyly dvě vnitřní třídy implementující rozhraní System.Collections.IHashProvider, které jsou pro pohodlnější využití zpřístupnění pomocí statických vlastností. Implementace metody GetHashCode je v případě první vnitřní třídy taková, že vrací hešový kód na základě jména osoby a v případě třídy druhé na základě jejího příjmení. Jak se použití konkrétního HashProvideru projeví ukazuje tento příklad: public class IHashProviderPriklad { public static void Priklad() { //urcime ze hash bude ziskavan podle jmena Hashtable Adresy = new Hashtable(Osoba.HashPodleJmena,Osoba.PorovnavaniPodleJmena); Osoba lMichal = new Osoba("Michal","Hynek"); Osoba lMartin = new Osoba("Martin","Pesek"); Osoba lTomas = new Osoba("Tomas","Berger"); Adresy.Add(lMichal,"Michalova adresa"); Adresy.Add(lMartin,"Martinova adresa"); Adresy.Add(lTomas,"Tomasova adresa"); Osoba lNejakyMichal = new Osoba("Michal","Novak"); //jelikoz je vyhledavano podle jmena bude vrace Michalova adresa Console.WriteLine(Adresy[lNejakyMichal]); } public static void Priklad2() { //urcime ze hash bude ziskavan podle prijmeni Hashtable Adresy = new Hashtable(Osoba.HashPodlePrijmeni,Osoba.PorovnavaniPodlePrijmeni); Osoba lMichal = new Osoba("Michal","Hynek"); Osoba lMartin = new Osoba("Martin","Pesek"); Osoba lTomas = new Osoba("Tomas","Berger"); Adresy.Add(lMichal,"Michalova adresa"); Adresy.Add(lMartin,"Martinova adresa"); Adresy.Add(lTomas,"Tomasova adresa"); Osoba lNejakyPesek = new Osoba("Petr","Pesek"); //jelikoz je vyhledavano podle prijmeni bude vrace Martinova adresa Console.WriteLine(Adresy[lNejakyPesek]); } } Kopírování instancí a Rozhraní ICloneable Občas můžeme potřebovat vytvořit novou instanci, která je kopií nějaké instance stávající. V některých případech může stačit využití protected metody MemberwiseClone třídy System.Object. Ovšem při jejím používání musíme být obezřetní a následující ukázka nastiňuje proč. public class ZapouzdrenaHodnota { private int hodnota; public ZapouzdrenaHodnota(int Hodnota) { this.hodnota = Hodnota; } public int Hodnota { get{return hodnota;} set{hodnota = value;} 113 } } public class MojeTrida { private ZapouzdrenaHodnota cislo; public MojeTrida(ZapouzdrenaHodnota Cislo) { this.cislo = Cislo; } public MojeTrida Klon() { return (MojeTrida)MemberwiseClone(); } public ZapouzdrenaHodnota Cislo { get{return cislo;} set{cislo = value;} } } Důvodem k obezřetnosti je skutečnost, že metoda MemberwiseClone, vytváří takzvanou mělkou kopii objektu. To znamená, že pokud jsou atributy kopírované třídy referenčního typu, jsou zkopírovány pouze hodnoty referencí, ale referencované instance zkopírovány nejsou. Takže pokud spustíme následující příklad: public class MojeTridaPriklad { public static void Priklad() { MojeTrida lMujObjekt = new MojeTrida(new ZapouzdrenaHodnota(5)); MojeTrida lMujObjekt2 = lMujObjekt.Klon(); //pokud zmenime vnitrni hodnotu prvniho objektu, zmena se projevi //i u druheho objektu lMujObjekt.Cislo.Hodnota = 11; Console.WriteLine(lMujObjekt2.Cislo.Hodnota); } } bude vypsáno číslo 11. Takže je vhodné v takovýchto případech, kdy jsou atributy klonované třídy referenčního typu, naimplementovat takzvanou hlubokou, nebo také úplnou kopii. A právě k tomuto účelu je určeno rozhraní ICloneable, které předepisuje jednu jedinou metodu Clone. Takže implementace třídy z předchozího příkladu, by se mohla změnit do této podoby: public class MojeKlonovatelnaTrida : ICloneable { private ZapouzdrenaHodnota cislo; public MojeKlonovatelnaTrida(ZapouzdrenaHodnota Cislo) { this.cislo = Cislo; } public ZapouzdrenaHodnota Cislo { get{return cislo;} set{cislo = value;} } #region ICloneable Members 114 public object Clone() { return new MojeKlonovatelnaTrida(new ZapouzdrenaHodnota(cislo.Hodnota)); } #endregion } Nyní je již při klonování vytvořena i nová instance třídy ZapouzdrenaHodnota. public class MojeKlonovatelnaTridaPriklad { public static void Priklad() { MojeKlonovatelnaTrida lMujObjekt = new MojeKlonovatelnaTrida(new ZapouzdrenaHodnota(5)); MojeKlonovatelnaTrida lMujObjekt2 = (MojeKlonovatelnaTrida) lMujObjekt.Clone(); lMujObjekt.Cislo.Hodnota = 11; Console.WriteLine(lMujObjekt2.Cislo.Hodnota); } } Takže po spuštění tohoto příkladu uvidíme na výstupu očekávané číslo 5. Poznáváme C# a Microsoft.NET 29. díl – řetězce Po několika dílech, které pojednávali o třídách kolekcí bych dnešní díl rád věnoval bližšímu pohledu na řetězce, se kterými jsme se během našeho poznávání již několikrát setkali. Bližší pohled na třídu System.String Klasické řetězce jsou v prostředí MS. NET framework realizovány instancemi třídy System.String. Obsah jednotlivé instance této třídy je představován kolekcí, která obsahuje sekvenci objektů System.Char, tedy objektů jednotlivých znaků. /// <summary> /// Ukazuje, ze je instance tridy string slozena z jednotlivých /// objektu System.Char /// </summary> public static void VypisForEach() { string s = "ahoj, jak se mate?"; foreach(System.Char znak in s) { Console.WriteLine(znak); } } Důležité je mít na paměti, že instance třídy System.String jsou takzvaně immutabilní, neboli neměnné. To, že jsou instance neměnné znamená, že po jejím vytvoření nemůže být její hodnota změněna a všechny operace, které jsou nad ní provedeny původní instanci nemění a vrací novou modifikovanou instanci. Takže důsledek této neměnnosti je takový, že pokud například pomocí operátoru += k existujícímu objektu řetězce nějaký jiný řetězec přidáváte, tak se hodnota původní instance nezmění, ale je vytvořena nová instance s novou hodnotu, na kterou je nasměrován odkaz referenční proměnné. 115 string retezec = "Ahoj"; //bude vytvorena nova instance tridy String //s hodnotou "Ahoj svete" a ref. promenna //retezec bude odkazovat na ni retezec += " svete"; Třída System.String obsahuje definice pro následující porovnávací a vyhledávací metody: Metoda Popis Compare Porovná dva řetězce CompareOrdinal Porovná dva řetězce s využitím ordinálního porovnání CompareTo Porovná aktuální instanci třídy System.String s instancí jinou EndsWith Určí, zda aktuální instance končí zadaným řetězcem. StartsWith Určí, zda aktuální instance začíná zadaným řetězcem. IndexOf Určí pozici prvního výskytu určitého řetezce nebo znaku v aktuální instanci LastIndexOf Určí pozici posledního výskytu určitého řetezce nebo znaku v aktuální instanci Porovnání instancí řetězců může být lingvistické (jazykové) nebo ordinální (číselné). V případě ordinálního porovnávání jsou porovnávány číselné hodnoty objektů Char (takzvaný Unicode codepoint) v instanci. Následující příklad ukazuje rozdíl mezi těmito způsoby porovnávání: public static void PorovnavaniPriklad() { string lMala = "abc"; string lVelka = "ABC"; string lVysledek1 = (String.Compare(lMala,lVelka) < 0) ? "je mensi nez" : "je vetsi nez"; //hodnota Unicode codepointu je pro a vetsi nez pro A string lVysledek2 = (String.CompareOrdinal(lMala,lVelka) < 0) ? "je mensi nez" : "je vetsi nez"; Console.WriteLine("Lingvisticke porovnani: {0} {1} {2} ",lMala,lVysledek1,lVelka); Console.WriteLine("Ordinalni porovnani: {0} {1} {2} ",lMala,lVysledek2,lVelka); } Výstup bude následovný: Lingvisticke porovnani: abc je mensi nez ABC Ordinalni porovnani: abc je vetsi nez ABC Kromě porovnávacích a vyhledávacích metod ještě třída System.String nabízí následující modifikační metody: Metoda Popis 116 Concat Spojí dvě nebo více instancí dohromady. V případě že jsou parametry představovány objekty, je na nich zavolána metoda ToString. CopyTo Zkopíruje určitý počet znaků z řetězce na zadané pozice pole znaků. Insert Vytvoří novou instanci řetězce, do které je na určenou pozici vložen zadaný řetězec Join Umožňuje vytvořit novou instanci řetězce spojením určených elementů pole řetězců, přičemž mezi jednotlivé elementy je ve výsledném řetězci vložen zadaný oddělovač. PadLeft Zapříčiní vytvoření nové instance řetězce o specifikované délce, kde je původní řetězec zarovnán doleva. Také umožňuje zvolit znak, který bude použit pro vyplnění volného místa v novém řetězci. PadRight Provádí to samé jako metoda PadLeft s tím rozdílem, že původní řetězec je zarovnán doprava. Remove Odstraní určitý počet znaků z instance řetězce o zadané pozice. Split Vytvoří pole řetězců, které obsahuje podřetězce vniklé rozdělením na místech zadaného znaku nebo znaků, představujích oddělovače. SubString Vytvoří novou instanci řetězce, jejíž hodnota je představována podřetězcem hodnoty instance na které byla zavolána. ToLower Vytvoří řetězec představující kopii instance, na které byla zavolána, kde jsou všechny znaky řetězce malá písmena. ToUpper Vytvoří řetězec představující kopii instance, na které byla zavolána, kde jsou všechny znaky řetězce velká písmena. Trim Odstraní z řetězce prázdné znaky TrimStart Odstraní specifikované znaky ze začátku řetězce TrimEnd Odstraní specifikované znaky z konce řetězce Následující zdrojový kód představuje příklad, který ukazuje použití některých metod třídy System.String: public static void Priklady() { string lMujRetezec = "Ahoj, jak se vsichni mate?"; Console.WriteLine("Puvodni retezec : {0}",lMujRetezec); Console.WriteLine("Vysledek pouziti metody Concat: {0}",String.Concat(lMujRetezec,"..doufam, ze fajn :-)")); Console.WriteLine("Vysledek pouziti metody Insert: {0}", lMujRetezec.Insert(12," tady")); Console.WriteLine("Vysledek pouziti metody Join: {0}", String.Join("",new String[2]{lMujRetezec,"Pridano"})); Console.WriteLine("Vysledek pouziti metody Remove: {0}", lMujRetezec.Remove(12,8)); Console.WriteLine("Vysledek pouziti metody Substring: {0}", lMujRetezec.Substring(0,4)); Console.WriteLine("Vysledek pouziti metody ToLower: {0}", lMujRetezec.ToLower()); Console.WriteLine("Vysledek pouziti metody ToUpper: {0}", lMujRetezec.ToUpper()); Console.WriteLine("Vysledek pouziti metody PadLeft : {0}",lMujRetezec.PadLeft(30,`_`)); Console.WriteLine("Vysledek pouziti metody PadRight : {0}",lMujRetezec.PadRight(30,`_`)); Console.WriteLine("Vysledek pouziti metody Split:"); foreach(string lPrvek in lMujRetezec.Split(` `)) Console.WriteLine(lPrvek); } 117 Po vykonání této metody uvidíme následující výstup: Puvodni retezec : Ahoj, Vysledek pouziti metody :-) Vysledek pouziti metody Vysledek pouziti metody Vysledek pouziti metody Vysledek pouziti metody Vysledek pouziti metody Vysledek pouziti metody Vysledek pouziti metody Vysledek pouziti metody Vysledek pouziti metody Ahoj, jak se vsichni mate? jak se vsichni mate? Concat: Ahoj, jak se vsichni mate?..doufam, ze fajn Insert: Ahoj, jak se tady vsichni mate? Join: Ahoj, jak se vsichni mate?-Pridano Remove: Ahoj, jak se mate? Substring: Ahoj ToLower: ahoj, jak se vsichni mate? ToUpper: AHOJ, JAK SE VSICHNI MATE? PadLeft : ____Ahoj, jak se vsichni mate? PadRight : Ahoj, jak se vsichni mate?____ Split: Poznáváme C# a Microsoft.NET 30. díl – StringBuilder a Regulární výrazy Po minulém hlubším pohledu na třídu System.String představující řetězce v rámci .NET, bych dnes rád čtenáře seznámil s užitečnou třídou využívanou pro řetězcové hodnoty, které budou měněny. Mimo to poodhalím použití regulárních výrazů v C#. Třída StringBuilder V minulém díle jsme se dozvěděli, že instance třídy System.String jsou takzvaně neměnné a také jakým způsobem jsou vytvářeny nové hodnoty těchto instancí. Zajisté vás napadla otázka, jak si tedy poradit v situaci, kdy potřebujeme měnit hodnotu instance obsahující nějaký text? V takovýchto situacích je vhodné použít třídu StringBuilder, která je obsažena ve jmenném prostoru System.Text základní knihovny tříd .NET frameworku. Tato třída je k takovýmto operacím přímo předurčena, protože obsahuje instanční metody, které nějakým způsobem mění hodnotu konkrétní instance, bez toho, aby byla vytvořena instance nová. Mimo to, je přímo doporučeno tuto třídu používat u textových hodnot, které se budou častěji měnit, protože úpravy textových hodnot reprezentovaných instancemi třídy StringBuilder jsou mnohem méně náročnější na režii, než je tomu u hodnot reprezentovaných instancemi třídy System.String. Instance této třídy je vždy vytvořena s určitou kapacitou, která říká, na jaký počet znaků je v paměti vyhrazeno místo. Číslo představující tuto kapacitu je možné zadat jako parametr některé z přetížených verzí konstruktoru, nebo ho explicitně nastavit pomocí instanční vlastnosti Capacity. Mezi základní metody třídy StringBuilder patří: Metoda Popis Append Přidá textovou reprezentaci objektu na konec konkrétní instance Insert Vloží textovou reprezentaci objektu na specifikovanou pozici v instanci Remove Od určité pozice vyjme určený 118 počet znaků z instance Replace Nahradí specikovaný znak nebo řetězec v instanci jiným specikovaným znakem nebo řetězcem. EnsureCapacity Zjistí jestli je kapacita instance alespoň taková jaká je požadovaná hodnota, předaná ve formě parametru. Pokud tomu tak není je paměť pro instanci přealokována Následující příklad demonstruje použití této třídy: /// <summary> /// Ukazka pouziti tridy StringBuilder /// </summary> public class StringBuilderPriklady { private static void ZpracujText(StringBuilder Text) { int lDelka = Text.Length; Text.Append("-delka puvodniho textu byla : "); Text.Append(lDelka); } public static void Priklad() { //vytvoreni instance StringBuilderu, ktery ma kapacitu 50 znaku //a obsahuje text `Nejaky textik` StringBuilder lText = new StringBuilder("Nejaky textik",50); Console.WriteLine("Puvodni text : {0}",lText); ZpracujText(lText); Console.WriteLine("Text po zavolani metody ZpracujText : {0} ",lText); Console.WriteLine("Text po pouziti metody Remove tridy StringBuilder : {0}",lText.Remove(0,7).ToString()); } } Po spuštění tohoto příkladu byste měli vidět takovýto výstup: Puvodni text : Nejaky textik Text po zavolani metody ZpracujText : Nejaky textik-delka puvodniho textu byla : 13 Text po pouziti metody Remove tridy StringBuilder : textik-delka puvodniho textu byla : 13 Úvod do regulárních výrazů v C# V .NET frameworku a tedy i v jazyce C# je možné využívat takzvaných regulárních výrazů. Regulární výrazy jsou výborní pomocníci při různých pracích s textem. Umožňují nám text různě přetvářet, vyhledávat v něm a zobrazovat pouze informace, které jsou pro nás zajímavé. Regulární výrazy jsou vlastně samy o sobě speciálním jazykem, kterým vyjadřujeme naše požadavky na konkrétní zpracování daného textu a jejich použití v jednotlivých programovacích jazycích se nijak zásadně neliší. V prostředí .NET frameworku jsou třídy pro práci s regulárními výrazy obsaženy ve jmenném prostoru System.Text.RegularExpressions. 119 Regulární výraz je v .NET frameworku reprezentován instancí třídy RegEx. Nejzákladnějším použitím regulárního výrazu je vyhledání výskytu nějakého znaku či řetězce. U takovýchto použití můžeme na regulární výraz nahlížet jako na vzor, kterému by měl text vyhovět. V případě hledání výskytu určitého znaku nebo znaků bude vzor regulárního výrazu tvořen pouze hledanými znaky. Jak bychom tento jednoduchý regulární výraz použili v jazyce C# ukazuje následující zdrojový kód: /// <summary> /// Uvodni priklad do problematiky reg. vyrazu /// </summary> public class RegexPriklady { private static int ZjistiIndex(string RetezecNaProhledani,string HledanyRetezec) { //vytvoreni reg. vyrazu Regex lRegEx = new Regex(HledanyRetezec); //zkusime ziskat vyhovujici vysledek z urciteho retezce Match lVyhovujici = lRegEx.Match(RetezecNaProhledani); //pokud vyhledavani uspelo vratime index //vyskytu v prohledavanem retezci if (lVyhovujici.Success) { return lVyhovujici.Index; } return -1; } public static void PrikladIndex() { int lIndex = ZjistiIndex("uiabcadfa","abc"); if (!lIndex.Equals(-1)) Console.WriteLine("Retezec abc se vyskytuje na pozici {0}",lIndex); else Console.WriteLine("Retezec nebyl nalezen"); } } V příkladu jsem vytvořil instanci třídy RegEx, představující regulární výraz (v našem konkrétním přikladu se jednalo o regulární výraz „abc“) a potom na instanci zavolal metodu Match, která slouží pro vyhledání výskytu řetězce zadaného konstruktoru třídy RegEx ve specifikovaném řetězci, který je metodě předán jako parametr. Tato metoda vrací instanci třídy Match, která reprezentuje výsledek hledání pomocí regulárního výrazu. Ke zjištění toho jestli bylo vyhledávání pomocí regulárního výrazu úspěšné použijeme instanční vlastnost Success třídy Match. Pokud tato vlastnost nabývá hodnoty true, hledání výskytu bylo úspěšné a index výskytu hledaného řetězce zjistíme pomocí vlastnosti Index. Příští díl bude věnován bližšímu pohledu na regulární výrazy. Poznáváme C# a Microsoft. NET 31. díl – regulární výrazy Minule jsme se seznámili s pojmem regulární výraz. V tomto díle se na možné použití regulárních výrazů a jejich aplikaci v rámci .NET podíváme trochu blíže. V minulém úvodu do světa regulárních výrazů jsme se zatím naučili zjistit, jestli řetězec obsahuje nějaký podřetězec a zjistit jeho pozici v prohledávaném řetězci. Kdyby byly 120 možnosti regulárních výrazů takto omezené, nebyl by smysl k jejich užití. Regulární výrazy nám, ale nabízejí spoustu užitečných funkcí. Libovolné znaky v regulárních výrazech V regulárním výrazu máme možnost určit, že na nějakých místech v řetězci mohou být libovolné znaky. K vyjádření libovolného znaku slouží zástupný symbol . (tečka). Tento symbol nám dovoluje vyjádřit, že na daném místě se může vyskytovat jeden libovolný znak. Pokud je naše přání takové, že chceme povolit neomezený počet určitých znaků použijeme symbolu * (hvězda) . Následující příklad ukazuje možné použití těchto symbolů v regulárním výrazu, jehož pomocí je zjišťováno, zda řetězec představuje emailovou adresu. /// <summary> /// Zjisti, zda-li predany retezec predstavuje emailovou adresu /// pouzitim regularniho vyrazu /// </summary> /// <param name="Retezec">Vstupni retezec</param> public static bool JeEmail(string Retezec) { Regex lRegEx = new Regex(@".*@.*\..*"); return lRegEx.IsMatch(Retezec); } public static void PrikladEmail() { string[] lHodnoty = {"[email protected]","jouda#joudove.cz","spatna@adresa"}; foreach(string lHodnota in lHodnoty) { if (JeEmail(lHodnota)) Console.WriteLine("Retezec {0} predstavuje emailovou adresu.",lHodnota); else Console.WriteLine("Retezec {0} nepredstavuje emailovou adresu.",lHodnota); } } Regulární výraz .*@.*\..* určuje, že řetězec může začínat libovolným počtem libovolných znaků (.* - tečka je libovolný znak a hvězda říká, že jich může být libovolný počet), po té musí následovat znak zavináče, po něm opět libovolný počet znaků, potom tečka a zase libovolné znaky. Tento příklad mimo jiné demonstruje způsob použití znaku tečka jako součásti regulárního výrazu. Pokud chceme určit, že se má v řetězci vyskytovat tečka jako znak, nemůžeme jednoduše napsat tečku do výrazu protože by byla chápána jako možnost libovolného znaku. Proto je pro vyjádření tečky použito zpětného lomítka (\.). Z důvodu výskytu zpětného lomítka musíme zabránit tomu, aby tato část řetězce nebyla brána jako tzv. escape-sekvence a to provedeme použitím vlastnosti jazyka C#, kterou je použití znaku zavináč před řetězcem. V takto označeném řetězci pak nejsou escape-sekvence zpracovávány. Intervaly znaků Další možností je specifikovat množinu určitých znaků v regulárním výrazu. K tomuto účelu slouží dvojice hranatých závorek mezi kterými je uveden námi požadovaný výčet znaků nebo je také možné specifikovat interval, kde uvedeme počáteční a koncový znak 121 onoho intervalu a oddělíme je pomlčkou. To znamená, že regulárnímu výrazu ve tvaru [aeiou] vyhoví pouze znaky a,e,i,o,u a pokud využijeme druhé možnosti, tj. určení intervalu tak výrazu [a-e] vyhoví pouze znaky a až e naší abecedy. Následující příklad zjišťuje, jestli řetězec představuje číslo a to právě pomocí definovaného intervalu znaků. /// <summary> /// Zjisti, zda-li predany retezec predstavuje cislo /// pouzitim regularniho vyrazu /// </summary> /// <param name="Retezec">Vstupni retezec</param> public static bool JeCislo(string Retezec) { //pokud je v retezci jediny znak ruzny od cisla vyhovi regularnimu vyrazu Regex lReg = new Regex("[^0-9]"); //pokud se v retezci nevyskytl zadny znak ruzny od cisla je to v poradku return !lReg.IsMatch(Retezec); } public static void PrikladCislo() { string[] lHodnoty = {"0123455789","12ab12"}; foreach(string lHodnota in lHodnoty) { if (JeCislo(lHodnota)) Console.WriteLine("Retezec {0} predstavuje cislo.",lHodnota); else Console.WriteLine("Retezec {0} nepredstavuje cislo.",lHodnota); } } Výstup po spuštění tohoto příkladu by měl vypadat takto: Retezec 0123455789 predstavuje cislo. Retezec 12ab12 nepredstavuje cislo. V regulárním výrazu, který je uveden v příkladu se objevuje znak ^ (stříška), který v jazyku regulárních výrazů znamená negaci. Takže použití regulárního výrazu [^0-9] ve výsledku zapříčiní, že pokud se v řetězci nachází alespoň jeden znak různý od čísel nula až devět, bude tento řetězec regulárnímu výrazu vyhovovat. Skupiny v regulárních výrazech V regulárních výrazech se nám nabízí využití skupin ve vyhledávaném výrazu. Tyto skupiny využijeme v případě, že potřebujeme získat pouze část nalezeného řetězce. Skupiny jsou v regulárních výrazech tvořeny pomocí závorek. Následující příklad ukazuje použití skupin v regulárním výrazu pro získání a zvýšení hodnoty v korunách, která je představována řetězcem. /// <summary> /// Zjisti zda predany retezec predstavuje hodnotu v korunach a umoznuje /// k teto hodnote pridan urcity pocet korun. /// </summary> public static void PrikladKoruny(string Retezec,int PridanaHodnota) { Regex lRegEx = new Regex(@"\b(\d+)\.(\d\d)Kc\b"); Match lMatch = lRegEx.Match(Retezec); if (lMatch.Success) { GroupCollection lGroupCol = lMatch.Groups; int lNovaHodnota = Int32.Parse(lGroupCol[1].Value) + PridanaHodnota; 122 Console.WriteLine("Hodnota po pricteni {0} korun je {1},{2}",PridanaHodnota,lNovaHodnota,lGroupCol[2]); } else Console.WriteLine("Retezec nevyhovuje"); } Regulární výraz \b(\d+)\.(\d\d)Kc\b , který je použit v tomto příkladu definuje dvě skupiny. První skupina je tvořena libovolným počtem číslic (\d+ - symbol \d znamená totéž jako interval [0-9] a ono plus říká, že jich může být libovolný počet) představující koruny a druhá dvěma libovolnými číslicemi představující haléře. Hodnota korun a haléřů musí být oddělena tečkou. Dalším dosud nevysvětleným symbolem, který je v příkladu použit je symbol \b a ten značí pomyslné hranice řetězce. Protože pokud bych tento znak ve výrazu nepoužil, tak by před nebo za hodnotou mohli být jakékoli jiné znaky a řetězec by stejně výrazu vyhověl, poněvadž k vyhovění regulárního výrazu stačí, aby řetězec obsahoval podřetězec splňující vzor. Tímto symbolem tedy v jazyce regulárních výrazů řekneme, že před ani za řetězcem splňující námi dané podmínky se již nesmí nic jiného nacházet. Třídu Match ze jmenného prostoru System.Text.RegularExpressions známe již z minulého dílu. Novinkou při použití skupin v regulárních výrazech je ovšem třída GroupCollection , která představujíce kolekci všech skupin z vyhovujícího řetězce. V našem ukázkovém příkladu to budou skupiny tři (první skupina je celý vyhovující výraz, druhá jsou koruny a třetí haléře). Každá skupina je reprezentována instancí třídy Group a v příkladu je získána použitím indexeru definovaným na třídě GroupCollection. Pro kýžené přidání hodnoty je tedy vzata hodnota korun, převedena na typ Int32 a k ní je po té přičteno. Poznáváme C# a Microsoft.NET 32. díl – I/O a streamy Cílem tohoto dílu je pro mě seznámit čtenáře se základy realizace vstupně/výstupních operací v .NET frameworku. Dozvíme jaký obecný koncept pro tyto operace je v MS.NET použit a naučíme se různými způsoby pracovat se soubory. I/O operace v rámci .NET V prostředí MS. NET frameworku jsou všechny vstupně/výstupní operace realizovány pomocí takzvaných datových proudů, neboli streamů. Stream je abstrakce určité sekvence bytů z nějakého zařízení, souboru, paměti nebo soketu TCP/IP. V rámci .NET je takovýto datový proud představován abstraktní třídou Stream. Tato třída je bázovou třídou pro všechny datové proudy pracující s konkrétními objekty (souboru, I/O, zařízení, paměť). Třídy představující konkrétní datové proudy implementují její abstraktní členy potřebné pro uskutečnění požadovaného přenosu dat. To pro nás programátory znamená velkou výhodu v podobě zobecnění přístupu k různým objektům, na kterých chceme provádět I/O operace, protože použití tříd je díky společnému rozhraní pořád stejné a tím pádem se vůbec nemusíme zabývat implementačními detaily související s přístupem ke konkrétnímu druhu objektu. Všechny třídy související s problematikou vstupně/výstupních operací nalezneme ve jmenném prostoru System.IO základní knihovny tříd .NET frameworku. Na jednotlivých implementacích datových proudů se nacházejí implementace metod Read a Write, které použijeme ke čtení bytů respektive k jejich zápisu. Obou metodám je ve formě parametru předán odkaz na pole bytů, představující buffer (vyrovnávací paměť). V případě metody Write je obsah tohoto bufferu zapsán do datového proudu a v případě metody Read je tomu naopak a data z proudu jsou do bufferu ukládána. 123 Některé datové proudy podporují náhodný přístup. Podpora náhodného přístupu u proudu znamená, že v dané sekvenci bytů se můžeme nastavit na libovolnou pozici a ne jen číst sekvenci od začátku. To jestli konkrétní proud, který chceme použit pro přístup k objektu, náhodný přístup podporuje, zjistíme přečtením instanční vlastnost CanSeek. Pokud proud tuto vlastnost podporuje, můžeme požadovanou pozici nastavit pomocí vlastnosti Position. Operace se soubory Jak jsme se dozvěděli výše, tak pro přístup ke konkrétnímu objektu na kterém chceme provádět I/O operace použijeme k tomu určenou implementaci abstraktní třídy Stream. V případě přístupu k souborům použijeme třídu FileStream. Následující příkladu ukazuje jak je pomocí této třídy možné zapsat byty do určitého souboru. /// <summary> /// Ukazka zapisu do souboru pomoci metody Write tridy Stream /// </summary> public static void ZapisBytuDoSouboru() { FileStream lStream = null; try { //vytvoreni streamu lStream = new FileStream(@"C:\pokus.txt",FileMode.Create); //vytvoreni bufferu bytu, ze ktereho bude zapsano byte[] lBuffer = new byte[]{1,2,3,4}; //pokyn k zapisu lStream.Write(lBuffer,0,lBuffer.Length); //vyprazdneni bufferu a provedeni vsech neprovedenych //zapisu do zarizeni (souboru) lStream.Flush(); } finally { //uzavreni streamu lStream.Close(); } } V příkladu jsem vytvořil proud k souboru C:\pokus.txt, a uvedl jsem pomocí výčtového typu FileMode, že chci tento soubor vytvořit a pokud existuje, tak jej chci přepsat. Následně jsem vytvořil pole dat, které bude zapsáno (čísla 1,2,3,4). Po té jsem dal pokyn k zápisu pomocí metody Write, které jsem pomocí parametrů sdělil, že chci zapsat všechna data z bufferu a že je chci zapsat od jeho začátku. Zavolání metody Flush na instanci streamu zapříčiní vyprázdnění všech bufferů (pokud nějaké stream používá) a zapsaní všech dat do souboru. Nakonec je stream pomocí metody Close uzavřen. Jak by se zapsaná data do tohoto souboru opět v programu načetla, demonstruje následující příklad. /// <summary> /// Ukazka nacteni souboru pomoci metody Read tridy Stream /// </summary> public static void NacteniBytuZeSouboru() { FileStream lStream = null; try { 124 //vytvoreni streamu lStream = new FileStream(@"C:\pokus.txt",FileMode.Open); //vytvoreni bufferu do ktereho budou zapsana nactena data //ze zarizeni byte[] lBuffer = new byte[4]; //nacteni dat ze souboru do bufferu lStream.Read(lBuffer,0,lBuffer.Length); //vypsani nactenych dat for(int i = 0; i < lBuffer.Length; i++) Console.WriteLine(lBuffer[i]); } finally { //uzavreni streamu lStream.Close(); } } V příkladu je opět vytvořen stream a pomocí něj jsou data ze souboru načtena do pole bytů, které je následně vypsáno pomocí cyklu for. Třídy BinaryWriter a BinaryReader Jistě vás napadlo, že pokud je jediný možný způsob jak číst a zapisovat data pomocí bytů, tak práce s IO v .NET není zrovna nejpříjemnější. Samozřejmě tomu tak není a návrháři .NET frameworku vymyslely třídy BinaryReader a BinaryWriter. Tyto třídy jsou velmi užitečné, protože jsou schopny číst respektive zapisovat základní datové typu z/do sekvence bytů tedy do proudu. Takže tyto třídy samy o sobě vytvořit proud vedoucí ke konkrétnímu objektu, ale umí již existující proud využít a zpříjemnit nám s ním práci. Jak použít třídu BinaryWriter pro zápis dat do souboru ukazuje tento příklad. /// <summary> /// Ukazka pouziti tridy BinaryWriter pro /// zapis dat do souboru /// </summary> public static void ZapisPomociBinaryWriteru() { FileStream lStream = null; try { //vytvoreni streamu lStream = new FileStream(@"C:\pokus.txt",FileMode.Create); //vytvoreni BinaryWriteru, ktery bude vyuzivat //nas proud k souboru BinaryWriter lWriter = new BinaryWriter(lStream); //zapsani retezce lWriter.Write("Retezec"); //zapsani cisla int lWriter.Write(69); //zapsani cisla double lWriter.Write(69.69); lWriter.Flush(); lWriter.Close(); } finally { lStream.Close(); } } 125 V příkladu jsem vytvořil proud k souboru, který jsem následně předal konstruktoru třídy BinaryWriter a tím jsem zajistil, že data budou zapsána do mého proudu. Potom jsem pomocí různých přetížení metody Write pomocí BinaryWriteru zapsal nějaká data do proudu a tedy i do souboru. Jak takto zapsaná data ze souboru získáme, pomocí třídy BinaryReader je ukázáno v tomto příkladu. /// <summary> /// Ukazka nacteni dat ze souboru pomoci tridy BinaryReader /// </summary> public static void CteniPomociBinaryReaderu() { FileStream lStream = null; try { lStream = new FileStream(@"C:\pokus.txt",FileMode.Open); BinaryReader lReader = new BinaryReader(lStream); Console.WriteLine(lReader.ReadString()); Console.WriteLine(lReader.ReadInt32()); Console.WriteLine(lReader.ReadDouble()); } finally { lStream.Close(); } } V příštím dílu se dozvíme, co ještě skrývá jmenný prostor System.IO. Poznáváme C# a Microsoft.NET 33. díl – I/O podruhé Po seznámení se s principem streamů, které proběhlo v minulém díle, se dnes seznámíme s několika dalšími třídami ze jmenného prostoru System.IO. Díky těmto třídám jsme schopni například lépe pracovat s textovými soubory nebo zjišťovat informace o souborech. Třídy TextReader a TextWriter TextReader a TextWriter jsou abstraktní třídy, určené pro pohodlné čtení respektive zápis sekvence znaků. Každá z této dvojice abstraktních tříd má ve jmenném prostoru System.IO základní knihovny tříd .NET frameworku dva potomky. Pro třídu TextReader to jsou StringReader a StreamReader a pro třídu TextWriter to jsou analogicky třídy StringWriter a StreamWriter. StringReader a StringWriter jsou implementace TextReaderu, které čtou/zapisují z/do prostého řetězce. Na rozdíl od této dvojice StreamReader a StreamWriter jsou určeny pro čtení respektive zápis z/do datového proudu bytů. Na třídě TextReader jsou implementovány užitečné metody ReadLine a ReadToEnd. První zmiňovaná načte jeden řádek textu ze určitého zdroje a posune pozici čtení na další řádek a metoda ReadToEnd slouží k načtení všech znaků ze zdroje a vrátí je jako souvislý řetězec. Třída TextWriter obsahuje definici metody WriteLine, která, jak již název napovídá, umí zapsat jeden řádek textu do zdroje. Následující příklad ukazuje použití StreamReaderu a StreamWriter pro práci s textovým souborem. /// <summary> /// Ukazka pouziti tridy StreamWriter pro zapis textu do souboru 126 /// </summary> public static void ZapisPomociStreamWriteru() { Stream lStream = null; try { lStream = new FileStream(@"C:\pokus.txt",FileMode.Create); TextWriter lWriter = new StreamWriter(lStream); lWriter.WriteLine("Prvni radek"); lWriter.WriteLine("Druhy radek"); lWriter.Close(); } finally { lStream.Close(); } } /// <summary> /// Ukazka pouziti tridy StreamReader pro cteni textu do souboru /// </summary> public static void CteniPomociStreamReaderu() { Stream lStream = null; try { lStream = new FileStream(@"C:\pokus.txt",FileMode.Open); TextReader lReader = new StreamReader(lStream); string lRadek; while((lRadek = lReader.ReadLine()) != null) Console.WriteLine(lRadek); } finally { lStream.Close(); } } Práce s pamětí pomocí proudů Stejně jako k souboru nebo síťovému připojení je možné přistupovat pomocí datových proudů i k paměti. Implementace abstraktní třídy Stream je pro tento typ úložiště představována třídou MemoryStream ze základní knihovny tříd .NET frameworku. Tuto třídu pravděpodobně nejčastěji použijete v případech, kdy potřebujete pracovat pouze s dočasnými daty, která není třeba uchovávat v nějakém persistentním úložišti. Následující jednoduchý příklad obsahuje ukázku použití datového proudu do paměti. /// <summary> /// Ukazka pouziti tridyMemoryStream pro praci s pameti pomoci proudu /// </summary> public static void PraceSMemoryStreamem() { MemoryStream lStream = null; try { lStream = new MemoryStream(); BinaryWriter lWriter = new BinaryWriter(lStream); lWriter.Write("Data zapsana proudem do pameti"); //nastaveni pozice ve streamu na zacatek lStream.Position = 0; 127 //precteni zapsanych dat BinaryReader lReader = new BinaryReader(lStream); Console.WriteLine(lReader.ReadString()); } finally { lStream.Close(); } } Třída File Základní knihovna tříd .NET frameworku obsahuje třídu File, která obsahuje pouze statické metody, určené pro práci se soubory jako například vytváření, kopírování, mazání, přesouvání, zjištění zda soubory existují nebo jejich otevírání. Mimo tyto základní operace je možné její pomocí zjišťovat nebo i nastavovat například čas vytvoření, poslední modifikace, posledního přístupu. Také je nám umožněno číst i nastavovat atributy konkrétního souboru. Jak může vypadat použití této třídy, demonstruje tento příklad. /// <summary> /// Priklad ukazujici nektere moznosti tridy System.IO.File /// </summary> public static void FileExam() { string lPath = @"C:\test.txt"; //pokud soubor na dane ceste neexistuje vytvorime jej if (!File.Exists(lPath)) { Stream lStream = File.Create(lPath); lStream.Close(); } StreamWriter lWriter = File.AppendText(lPath); lWriter.WriteLine("Radek textu"); lWriter.Close(); Console.WriteLine("Cas vytvoreni : {0}", File.GetCreationTime(lPath)); //nastaveni atributu archivace File.SetAttributes(lPath,FileAttributes.Archive); Console.WriteLine("Attributy : ",File.GetAttributes(lPath)); } Trochu si příklad rozebereme. Pomocí metody Exists zjistíme, jestli soubor na dané cestě, která je předána formálním parametrem, již existuje. V případě, že tomu tak není, využijeme metody Create, která soubor vytvoří a jako výsledek vrací odkaz na instancí třídy FileStream, pomocí něhož je možné s nově vytvořeným souborem pracovat. My tento Stream v příkladu pouze zavřeme, aby bylo možné s ním dále pracovat jinými postupy. Pokud bychom tak neučinili, tak by další příkazy skončili vyhozením výjimky. Následuje volání metody AppendText, která vrátí odkaz na instanci StreamWriteru asociovaného s určeným souborem, jehož pomocí zapíšeme do souboru nějaký text. Další akcí je vypsání času vytvoření souboru, čehož je dosaženo použitím metody GetCreationTime. Souboru potom pomocí metody SetAttribute nastavíme atribut archivace. Jako reprezentace atributů souboru je v základní knihovně tříd .NET frameworku použit výčet (enum) FileAttributes jehož použití můžete v příkladu vidět. A na konec jsou voláním metody GetAttributes zjištěny všechny nastavené atributy souboru a vypsány. 128 Poznáváme C# a Microsoft.NET 34. díl – informace o adresářích a sledování souborového systému Tento díl si bere za své seznámit vás s možnostmi zjišťování informací o adresářích a ve své druhé části také se zajímavým způsobem sledování změn souborového systému. Třída Directory První část tohoto dílu věnuji navázání na problematiku, kterou jsem zakončil díl minulý a tou byla třída File pro práci se soubory. Stejně jako je třída File určena pro manipulaci se soubory, tak třída Directory slouží pro takovéto operace s adresáři. Tyto operace jsou stejně jako u třídy File implementovány ve formě statických metod. Použití některých z těchto metod by mohlo vypadat nějak takto: /// <summary> /// Ukazka pouziti vybranych metod tridy Directory /// </summary> public class DirectoryExam { public static void Example() { string lDirPath = @"C:\testdir"; //pokud dany adresar existuje, tak jej smazeme if (Directory.Exists(lDirPath)) { Console.WriteLine("Mazu stary adresar.."); Directory.Delete(lDirPath); } Console.WriteLine("Vytvarim novy adresar.."); //vytvoreni noveho adresare Directory.CreateDirectory(lDirPath); //nastaveni casu vytvoreni adresare - 5 let po soucasnosti DateTime lCreationTime = DateTime.Now.AddYears(5); Console.WriteLine("Nastavuji cas vytvoreni adresare {0} na {1}",lDirPath,lCreationTime); Directory.SetCreationTime(lDirPath, lCreationTime); } } Třídy FileInfo a DirectoryInfo Třídy File a Directory nepředstavují jediný způsob, jak zjišťovat informace o souborech respektive adresářích nebo s nimi manipulovat. Druhou možností jsou třídy FileInfo a DirectoryInfo, které se taktéž nacházejí ve jmenném prostoru System.IO. Na rozdíl od dříve zmiňovaných tříd je potřeba pro provádění operací pomocí této dvojice tříd vytvořit jejich instance namísto volání statických metod. To s sebou přináší výhodu v případech, kdy se určitou instanci chystáme použít vícekrát a to z důvodu, že při používání statických metod tříd File a Directory jsou vždy ověřována přístupová práva k objektu. Naproti tomu jsou v případě tříd FileInfo a DirectoryInfo přístupová práva kontrolována pouze u některých metod a při vytváření instance. Jinak jsou poskytované operace této dvojice tříd velmi podobné s operacemi nabízenými třídami File a Directory. Na ukázku je zde tento příklad, který obsahuje použití třídy DirectoryInfo. /// <summary> /// Ukazka pouziti tridy DirectoryInfo 129 /// </summary> public class DirectoryInfoExam { public static void Example() { string lDirPath = @"C:\testdir"; DirectoryInfo lTestInfo = new DirectoryInfo(lDirPath); if (lTestInfo.Exists) { Console.WriteLine("Jmeno : {0}",lTestInfo.Name); Console.WriteLine("Korenovy adresar : {0}", lTestInfo.Root); string lSubDirName = @"testsubdir"; DirectoryInfo[] lDirs = lTestInfo.GetDirectories(); //pokud existuji nejake podadresare tak je vypiseme if (lDirs.Length > 0) { Console.WriteLine("Obsazene podadresare : "); foreach(DirectoryInfo lCurrentInfo in lDirs) Console.WriteLine(lCurrentInfo.Name); Console.WriteLine("Vytvarim podadresar {0}..",lSubDirName); } lTestInfo.CreateSubdirectory(lSubDirName); } else Console.WriteLine("Adresar {0} neexistuje",lDirPath); } } Sledování změn souborového systému V základní knihovně tříd prostředí .NET framework existuje zajímavá třída, která nám umožňuje sledovat změny v souborovém systému. Jedná se o třídu FileSystemWatcher a je obsažena ve jmenném prostoru System.IO. Této třídě pomocí její instanční vlastnosti Path nastavíme adresář jehož obsah chceme sledovat. Po tomto přiřazení si můžeme pomocí instancí delegátů FileSystemEventHandler a RenamedEventHandler předplatit události které tato třída zveřejňuje. Mezi tyto události patří Changed, Created, Deleted a Renamed. Událost Changed je vyvolána v případě změny obsaženého souboru či adresáře jako je změna velikosti, atributů nebo nastavení zabezpečení, událost Created zase v případě vytvoření nového souboru či adresáře ve sledovaném adresáři, událost Delete je vyvolána když je ve sledovaném adresáři nějaký soubor nebo adresář smazán a událost Renamed když je soubor nebo adresář přejmenován. Důležitou instanční vlastností je také vlastnost EnableRaisingEvents, pomocí které nastavujeme jestli má instance třídy FileSystemWatcher již reagovat na změny v souborovém systému vyvoláváním výše zmíněných událostí. Další booleanovská vlastnost, která nese název IncludeSubdirectories indikuje jestli má být reagováno i na změny v podadresářích sledovaného adresáře. Následující příklad sleduje změny v určitém adresáři pomocí zmiňované třídy FileSystemWatcher. /// <summary> /// Priklad na sledovani zmen v adresari pomoci tridy FileSystemWatcher /// </summary> public class FileSystemWatcherExam { public static void WatchFile(string path) { FileSystemWatcher lWatcher = new FileSystemWatcher(); 130 //nastavime adresar, ktery ma byt sledovan lWatcher.Path = path; //vytvorime instance delegatu lWatcher.Changed += new FileSystemEventHandler(FSChanged); lWatcher.Deleted += new FileSystemEventHandler(FSDeleted); lWatcher.Renamed += new RenamedEventHandler(FSRenamed); lWatcher.Created += new FileSystemEventHandler(FSCreated); //nastavime, ze maji byt sledovany i zmeny ve vnorenych adresarich lWatcher.IncludeSubdirectories = true; //spustime sledovani lWatcher.EnableRaisingEvents = true; Console.WriteLine("Sleduji adresar {0}..",path); Console.ReadLine(); } private static void FSChanged(object sender, FileSystemEventArgs e) { Console.WriteLine("Objekt {0} v adresari byl zmenen {1}",e.Name,e.ChangeType); } private static void FSDeleted(object sender, FileSystemEventArgs e) { Console.WriteLine("V adresari byl smazan objekt {0}",e.Name); } private static void FSRenamed(object sender, RenamedEventArgs e) { Console.WriteLine("V adresari byl objekt {0} prejmenovan na {1}",e.OldFullPath,e.Name); } private static void FSCreated(object sender, FileSystemEventArgs e) { Console.WriteLine("V adresari byl vytvoren objekt {0}",e.Name); } } Zajímavou vlastností této třídy, kterou bych rád zmínil je vlastnost NotifyFilter, které pomocí výčtu NotifyFilters můžeme specifikovat skupinu změn souborového systému pří kterých má být vyvolána událost Changed. Takže pokud bychom do příkladu přidali následující řádek: lWatcher.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.Security; Bude událost Changed vyvolána pouze v případech, že je změněn atribut nějakého obsaženého objektu nebo je změněno jeho nastavení zabezpečení. Poznáváme C# a Microsoft.NET 35. díl – izolovaná úložiště Dnešní díl bych vás rád seznámil s problematikou takzvaných izolovaných úložišt (Isolated storages) a také se způsoby jakými lze s tímto druhem úložiště pracovat. Kromě tohoto tématu se také zmíním o jiné implementaci tříd TextWriter a TextReader než jsou StreamWriter a StreamReader. 131 Třídy StringReader a StringWriter V jednom z minulých dílu tohoto seriálu věnovaných otázce vstupně výstupních operací jsme se seznámili s abstraktními třídami TextReader a TextWriter a také s jejich hojně využívanými implementacemi pro čtení a zápis z/do souborů kterými jsou StreamReader a StreamWriter. Kromě těchto implementací tříd TextReader a TextWriter se v základní knihovně tříd .NET frameworku nachází ještě druhá dvojice implementací, kterou je implementace pro ty nejjednodušší druhy úložišť, kterými jsou instance třídy String nebo StringBuilder. Tyto třídy nesou jméno StringReader a StringWriter. Pro tu první, tedy pro třídy StringReader, je objekt, ze kterého jsou čtena data pomoci rozhraní, které je definováno třídou TextReader, představován instancí třídy String. /// <summary> /// Ukazka pouziti tridy StringReader /// </summary> public class StringReaderExam { public static void RunExam() { string lStr = "Prvni radek textu"; //pridame znak noveho radku lStr += Environment.NewLine; lStr += "Druhy radek textu"; lStr += Environment.NewLine; lStr += "Treti radek textu"; //vytvorime instanci tridy StringReader a nechame na ni //odkazovat ref. promennou typu TextReader TextReader lReader = new StringReader(lStr); string lLine = null; //vypiseme text po radkach while( (lLine = lReader.ReadLine()) != null) Console.WriteLine(lLine); } } Jak můžeme vidět, tak je práce s objekty typu StringReader, pro mnoho úloh stejná jako při použití třídy StreamReader a to díky použití rozhraní definované třídou TextReader. Poznámka: Jelikož jsem chtěl ukázat použití metody Readline, musel jsem vytvořit víceřádkový řetězec. Jedním ze způsobů jak dostat do řetězce znak nového řádku je použití statické vlastnosti NewLine třídy Environment. Tak jako je třída StringReader implementací TextReaderu pro nejjednodušší datové úložiště v podobě řetězce, tak je třída StringWriter implementací TextWriteru a nyní je datové úložiště představováno instancí třídy StringBuilder. /// <summary> /// Ukazka pouziti tridy StringWriter /// </summary> public class StringWriterExam { public static void RunExam() { StringBuilder lBuilder = new StringBuilder(); //vytvorime instanci tridy StringWriter asociovanou s konkretni //instanci tridy StringBuilder 132 } TextWriter lWriter = new StringWriter(lBuilder); //zapiseme data lWriter.Write(5.4); lWriter.WriteLine(); lWriter.Write("Text"); lWriter.Close(); //vypiseme data Console.WriteLine(lBuilder.ToString()); } Izolovaná úložiště V některých případech je vhodné pracovat s daty, o kterých máme jistotu, že s nimi žádná jiná aplikace či uživatel manipulovat. Toho je možné dosáhnout použitím nějakých vlastních technik, které ale v určitých případech už mohou být příliš složité a zaberou při vývoji aplikace mnoho času. Právě pro ušetření času s vymýšlením vlastních technik pro dosažení výše zmíněného cíle nám Microsoft. NET framework nabízí k použití takzvaná izolovaná úložiště. Tato úložiště jsou vyčleněna pro uživatele a pro assembly, takže je zaručena dostatečná úroveň izolace od ostatních aplikací se kterými pracují jiní uživatelé. Předpokládejme příklad, kdy určitý uživatel (např. Jarda) používá .NET aplikaci (např. apl.exe), ukládající svá data ve formě souborů a adresářů, která jsou uložena právě v izolovaném úložišti. Běhové prostředí .NET zaručuje, že jakákoliv jiná .NET aplikace , i když je spuštěna pod tímto uživatelem s těmito daty nebude nijak manipulovat. Navíc .NET runtime zaručuje, že pokud aplikaci apl.exe spustí nějaký jiný uživatel, stejně nebude moci pracovat se soubory, které aplikace vytvořila, když byla spuštěna uživatelem Jarda. A kde že to izolované úložiště ve vašem systému vlastně je? Odpověď na tuto otázku závisí na tom jaký operační systém používáte. Pokud používáte Microsoft Windows ve verzi 2000 nebo XP naleznete jej na cestě <systémový disk>\Documents and Settings\<uživatel>\Local Settings\Application Data v případě nepřenášeného profilu nebo na cestě <systémový disk>\Documents and Settings\<uživatel> \Application Data pokud pužíváte profíl cestovní. Pokud vás zajímaji jiné verze systému Windows odkážu vás na SDK pro .NET framework. Pro práci s izolovanými úložišti je v základní knihovně .NET frameworku určen jmenný prostor System.IO.IsolatedStorage. Pro abstrakci oblasti izolovaného úložiště slouží třída IsolatedStorageFile. Pomocí instance této třídy jsme schopni zjistit obsažené soubory a adresáře v izolovaném úložišti, nebo adresáře vytvářet čí odstraňovat. Následující příklad ukazuje jak je možné v izolovaném úložišti vytvořit soubory a adresáře. public static void WritingExam() { //ziskani izolovaneho uloziste IsolatedStorageFile lIsolatedFile = IsolatedStorageFile.GetUserStoreForAssembly(); //vytvoreni adresare lIsolatedFile.CreateDirectory("IsolovanyAdresar"); //vytvoreni noveho souboru v izolovanem ulozisti a zapis dat IsolatedStorageFileStream lIsolatedStream = new IsolatedStorageFileStream("test.txt",FileMode.Create,lIsolatedFile); lIsolatedStream.Write(new byte[3]{1,2,3},0,3); lIsolatedStream.Close(); 133 //vytvoreni souboru do adresare IsolatedStorageFileStream lIsolatedFileStream2 = new IsolatedStorageFileStream("IsolovanyAdresar",FileMode.Create); lIsolatedFileStream2.Close(); } Na začátku si pomocí statické metody GetUserStoreForAssembly získáme odkaz na isolované úložiště vyhrazené pro kombinaci aktuální uživatel a assembly. Pomocí metody CreateDirectory vytvoříme v oblasti izolovaného úložiště adresář. Po té vytvoříme instanci třídy IsolatedStorageFileStream, což je potomek třídy FileStream určený pro práci se soubory v izolovaném úložišti. Stejným způsobem jako s instancí třídy FileStream vytovříme soubor a zapíšeme do něj nějaká data. A jak se soubory či adresáře s konkrétního izolovaného úložiště odstraní? Tak to ukazuje tento příklad: public static void DeletingExam() { string lFileName = "temp.tmp"; string lDirName = "tempdir"; IsolatedStorageFile lIsolatedFile = IsolatedStorageFile.GetUserStoreForAssembly(); Console.WriteLine("Vytvarim adresar {0}", lDirName); lIsolatedFile.CreateDirectory(lDirName); Console.WriteLine("Vytvarim soubor {0}",lFileName); //vytvorime stream asociovany s konkretnim souborem uloziste a jeho pomoci vytvorime soubor IsolatedStorageFileStream lIsolatedStream = new IsolatedStorageFileStream(lFileName,FileMode.Create,lIsolatedFile); lIsolatedStream.Close(); Console.WriteLine("Mazu soubor {0}",lFileName); lIsolatedFile.DeleteFile(lFileName); Console.WriteLine("Mazu adresar {0}",lDirName); } Získat všechny oblasti izolovaných úložišť je možné použitím enumerátoru, který získáme zavoláním metody GetEnumerator na třídě IsolatedStorageFile. Toho mimo jiné využívá i následující příklad. public static void EnumeratingExam() { //ziskame vsechny soubory predstavujici isolovane uloziste IEnumerator lEnum = IsolatedStorageFile.GetEnumerator(IsolatedStorageScope.User); while(lEnum.MoveNext()) { IsolatedStorageFile lFile = (IsolatedStorageFile) lEnum.Current; //ziskani informaci o assembly Url lUrl = (Url)lFile.AssemblyIdentity; Console.WriteLine("Assembly : {0}", lUrl.Value); //vypsani velikost souboru izolovaneho uloziste Console.WriteLine("Velikost : {0}: ", lFile.CurrentSize); //ziskani souboru obsazenych v izol. ulozisti string[] lFiles = lFile.GetFileNames("*"); Console.WriteLine("Obsazene soubory :"); for(int i = 0; i < lFiles.Length; i++) { Console.WriteLine(lFiles[i]); } 134 } } Jak můžete v příkladu vidět, tak cestu k assembly ke které je izolované úložiště asociováno, je možné získat přečtením vlastnosti AssemblyIdentity na instanci třídy IsolatedStorageFile. Seznam adresářů a souborů v konkrétním izolovaném úložišti je možné získat pomocí metody GetFileNames, jíž ve formě parametru předáme řetězec představující filtr. Poznáváme C# a Microsoft.NET 36. díl – úvod do reflexe V tomto dílu se začneme věnovat dle mého názoru velmi zajímavou problematikou, kterou je takzvaná reflexe. Pomocí reflexe je totiž možné za běhu programu dynamicky zjišťovat informace o existujících typech. Aplikace v .NET frameworku Předtím než se podíváme přímo na problematiku reflexe v .NET bude dobré, když se alespoň stručně zmíním o tom, z jakých částí jsou aplikace vlastně tvořeny. Jak již bylo v seriálu zmíněno základním zaveditelným prvkem aplikace pro .NET framework je Assembly (Sestava). Assembly mimo to také tvoří základní jednotku pro správu verzí, jednotné rozlišování typů a specifikaci bezpečnostních oprávnění. Každá aplikace pro .NET framework je tvořena přinejmenším jednou assembly a ta je zase tvořena čtyřmi částmi, kterými jsou: • • • • manifest, který obsahuje metadata o assembly metadata o typech obsažených v assembly kód v jazyce MSIL, který je spuštěn prostředím .NET runtime zdroje (resources) Metadata o obsažených typech a MSIL kód spolu tvoří takzvaný modul, což je přenositelný spustitelný (PE – portable executable) soubor. Nejjednodušší assembly jsou složeny z manifestu a jediného modulu s typy aplikace. I když to není časté, tak je možné vytvořit i sestavu s více moduly. Jednotlivé moduly s typy jsou pak představovány soubory s příponou .netmodule. Takovéto assembly jsou většinou tvořeny z optimalizačních důvodů, protože prostředí .NET runtime nahrává moduly pouze v případě potřeby typu v nich obsažených. Možná se někteří z vás pozastavili na pojmem metadata. Nejedná se o nic jiného než o data, která nesou popisné informace o assembly či typu. Například manifest obsahuje mimo jiné tato metadata: • • • • • jednoduchý název assembly číslo verze assembly veřejný klíč tvůrce a hešový kód assembly (volitelně) seznam souborů, které tvoří danou assembly a jejich hešové kódy seznam typů, které tvoří assembly a informaci ke kterému modulu v assembly je konkrétní typ připojen Reflexe v .NET Jedním z důsledků použití mechanismu metadat pro všechny typy v prostředí .NET frameworku je možnost tyto typy v našem programu prozkoumávat a tato věc je nazývána reflexe. Jmenný prostor, který obsahuje nemalý počet tříd, jež námi mohou být 135 použity pro manipulaci s danými elementy konkrétní aplikace nese název System.Reflection. Mechanismus reflexe nám tedy umožňuje procházení a manipulaci s objektovým modelem představující konkrétní aplikaci. Metadata, která jsou reflexí využívána jsou obvykle vytvářena kompilátorem při překladu aplikace. Kromě tohoto obvyklého způsoby tvorby metadat k elementům aplikace, je možné metadata vytvořit pomocí tříd, které se nacházejí ve jmenném prostoru System.Reflection.Emit. Každá aplikace pro .NET framework běží v nějaké aplikační doméně, představující izolované běhové prostředí. Aplikační doménu je možné brát jako obdobu procesu ze světa programování ve Win32. Aplikační doména sama o sobě není popsána metadaty. Aplikační doména je představována třídou AppDomain, která nám pomocí své statické vlastnosti CurrentDomain předloží přístup k aktuální aplikační doméně aplikace a pokud na ní zavoláme metodu GetAssemblies získáme v podobě pole všechny instance třídy Assembly, která reflektuje objekt assembly v aplikační doméně. A stejně jako třída Assembly reflektuje assembly v aplikační doméně, tak ve jmenném prostoru System.Reflection existují i další třídy, které reflektují ostatní elementy aplikace .NET . Pro lepší pochopení vztahů mezi těmito reflektujícími třídami jsem zhotovil jednoduchý UML diagram, který tyto třídy a jejich vztahy zobrazuje. Jak můžeme vidět, tak třemi základní typy v reflexi jsou třídy Module, MemberInfo a Assembly. Co reflektují třídy Module a Assembly je jasné již z jejich názvu .Třída MemberInfo je třídou abstraktní a představuje předka pro třídy, které poskytují informace o členech třídy. Jedním z potomků této třídy je další abstraktní třída 136 MethodBase, která je předkem pro třídy ConstructorInfo a MethodInfo, které, jak název napovídá, poskytují informace o konstruktorech respektive metodách třídy. Třída Type ze jmenného prostoru System, která je taktéž jedním z potomků abstraktní třídy MemberInfo a je asi nejzákladnějším typem reflexe. Tato třída představuje informaci o daném typu, jinými slovy představuje jeho metadata. Na úvod do mechanismu reflexe jsem vytvořil jednoduchý příklad, ve kterém je pouze získána instance třídy Type a pomocí ní jsou zjištěny všechny členy. Abych na ukázku nepoužíval pouze vestavěné typy .NET frameworku, tak jsem kvůli příkladu vytvořil ještě jednoduchou třídu Osoba. /// <summary> /// Trida predstavujici osobu, ktera bude pouzita v /// uvodnim prikladu na pouziti reflexe /// </summary> public class Osoba { private string jmeno; private string prijmeni; private DateTime datumNarozeni; public string Jmeno { get{return jmeno;} set{jmeno = value;} } public string Prijmeni { get{return prijmeni;} set{prijmeni = value;} } public DateTime DatumNarozeni { get{return datumNarozeni;} set{datumNarozeni = value;} } public Osoba(){} public int VypocistVek() { TimeSpan lSpan = DateTime.Now - datumNarozeni; return lSpan.Days / 365; } } /// <summary> /// Uvodni priklad pouziti reflexe /// </summary> public class PrikladReflexe { public static void VypisCleny(Type Typ) { Console.WriteLine("Vypisuji vlastnosti typu {0}",Typ.Name); //ziskani vsech clenu MemberInfo[] lSeznamClenu = Typ.GetMembers(); foreach(MemberInfo lInfo in lSeznamClenu) { 137 Console.WriteLine("Nazev clenu = {0} - druh clenu = {1}", lInfo.Name,lInfo.MemberType); } } public static void Priklad() { Osoba lOsoba = new Osoba(); //ziskani instance tridy Type pomoci metody GetType Type lTypOsoba = lOsoba.GetType(); //ziskani instance tridy Type pomoci staticke Metody //GetType tridy type Type lTypInt32 = Type.GetType("System.Int32"); //ziskani instance tridy Type pouzitim operatoru typeof Type lTypString = typeof(string); VypisCleny(lTypOsoba); VypisCleny(lTypInt32); VypisCleny(lTypString); } } Jak můžeme v příkladu vidět, instanci třídy System.Type je možné získat třemi způsoby. Tím prvním je zavoláním metody GetType na konkrétní instanci třídy jejíž popis chceme získat. Druhým způsobem je použití statické metody GetType třídy System.Type, jíž jako parametr zadáme řetězec představující plně kvalifikovaný název třídy (to znamená název třídy včetně jmenného prostoru do něhož spadá). Třetím a podle mých informací i drobek rychlejším řešením získání instance třídy System.Type je použítí operátoru typeof jazyka C#. Příští díl se již podrobněji podíváme na jednotlivé reflektující třídy. Poznáváme C# a Microsoft.NET 37. díl – použití reflexe Po minulém úvodu do světa reflexe se v tomto díle budeme věnovat vybraným reflektujícím typům. Dozvíme se jak je například možné pomocí reflexe za běhu dynamicky vyhledávat a vykonávat metody nebo jakým způsobem za běhu vytvoříme instanci třídy na základě jejích metadat. Třída System.Type Instance této představují abstrakci deklarace určitého typu ať už se jedná o třídu nebo strukturu, výčet (enum) či rozhraní. Tato třída je základní cestou k metadatům typu v mechanismu reflexe. Instanci této třídy lze získat několika způsoby, základní tři byly zmíněny v minulém díle (Type.GetType ve svých dvou verzích a operátor typeof). Pomocí instančních vlastností a metod této třídy můžeme zjišťovat rozličné informace o daném typu, včetně seznamu jeho členů, které mohou být jak víme datové členy, vlastnosti, metody, konstruktory a události. Na ukázku použití je zde následující příklad, který zjistí některé informace o typu Osoba, který byl použit již v minulém díle (jediná změna je, že byla do třídy přidána definice metody ToString, která při svém zavolání vrátí jméno a příjmení dané osoby) Zdrojový kód třídy Osoba nebudu v článku uvádět, můžete jej v případě potřeby nalézt v přiložených zdrojových kódech. /// <summary> /// Priklad na zjisteni informaci z metadat pomoci tridy Type 138 /// </summary> public class TypePriklad { /// <summary> /// Vypise par informaci z metadat /// </summary> public static void VypisInfo(Type Typ) { Console.WriteLine("Kvalifikovany nazev typu : {0} ",Typ.FullName); Console.WriteLine("Kvalifikovany nazev assembly do niz spada : {0}",Typ.AssemblyQualifiedName); Console.WriteLine("Nazev predka : {0}",Typ.BaseType.FullName); Console.WriteLine("Je hodnotovy typ : {0}",Typ.IsValueType); Console.WriteLine("Trida je zapecetena : {0}",Typ.IsSealed); Console.WriteLine("Trida je verejna : {0}",Typ.IsPublic); } public static void Priklad() { //ziskame assembly s nazvem PrikladyZive37 Assembly lZiveAssembly = Assembly.Load("PrikladyZive37"); //ziskame vsechny typy, ktere se v ni nachazeji Type lOsobaType = lZiveAssembly.GetType("PrikladyZive37.Osoba"); VypisInfo(lOsobaType); } } Na přikladu můžete vidět další způsob získání instance třídy Type a to použitím metody GetType na instanci třídy Assembly, kterou jsme získali zavoláním metody Load, jíž jsme předali název assembly, kterou chceme reflektovat. Následně je získaný typ předán metodě VypisInfo, který vypíše pár informací o něm. Dynamické vytvoření instance třídy Občas můžeme chtít vytvořit instanci třídy k níž máme k dispozici informace právě v podobě instance třídy Type. Jedna z cest jak tohoto dosáhnout je využít třídu Activator ze jmenného prostoru System. Jeho metoda CreateInstance v jedné ze svých mnoha přetížených verzí očekává ve formě parametru instanci třídy Type. V tomto příkladu je třída Activator použita k vytvoření instance třídy za běhu programu. /// <summary> /// Ukazka pouziti tridy Activator pro dynamicke vytvoreni instance tridy /// </summary> public class ActivatorPriklad { public static void CreateInstancePriklad() { //ziskame pole instanci tridy Assembly, ktere nalezi do aplikacni domeny Assembly[] lAssemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach( Assembly lAssembly in lAssemblies) { //ziskame vsechny typy z assembly Type[] lTypes = lAssembly.GetTypes(); Console.WriteLine("Prohledavam assembly {0}", lAssembly.FullName); foreach(Type lType in lTypes) { //pokud se jedna o instanci tridy Type predstavujici typ tridy //osoba, tak vytvorime jeji instanci 139 if ( lType.Name.Equals("Osoba") ) { Console.WriteLine("Nalezena metadata pro tridu Osoba"); Osoba lInstance = (Osoba) Activator.CreateInstance(lType); //nyni muzeme provadet libovolne operace s istanci tridy osoba lInstance.Jmeno = "Jan"; lInstance.Prijmeni = "Novak"; Console.WriteLine(lInstance.ToString()); } } } } } Kromě použití třídy Activator jsem chtěl v tomto příkladu uvést i to jak je možné získat seznam assemblies (metoda GetAssemblies na instanci AppDomain) z aplikační domény a také jak získat informace o všech typech v konkrétní assembly (metoda GetTypes na instanci Assembly). V příkladu je procházeno polem instancí třídy Type z každé assembly nahrané v aplikační doméně a pokud je jednoduchý název typu shodný s názvem třídy Osoba, tak je dynamicky vytvořena její instance. V tomto konkrétním příkladu bude seznam získaných assembly obsahovat pouze dvě instance třídy Assembly. První z nich bude představovat informace o assembly mscorlib.dll, což je assembly .NET frameworku samotného a druhá bude představovat assembly naši tj. PrikladyZive37.dll. Poznámka: Možná teď někteří z vás kroutí hlavou a diví se, že jsem pro získání instance třídy Type popisující typ Osoba nepoužil jednoduše Type.GetType, ale jak jsem psal, tak v tomto příkladu jsem chtěl také demonstrovat možné získání seznamů aplikačních elementů. Reflexe datových členů Reflektujícím typem datových členů třídy nebo struktury je třída FieldInfo. Jejím využitím je například možné zjistit modifikátor přístupu daného datového členu a snadné je i zjištění jeho datového typu. To nám ukazuje první příklad použití tohoto typu. public static void VypisInfo() { Type lOsobaType = typeof(Osoba); //ziskani vsech instancnich dat. clenu FieldInfo[] lFields = lOsobaType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach(FieldInfo lField in lFields) { Console.WriteLine("Nazev clenu : {0}",lField.Name); Console.WriteLine("Typ datoveho clenu : {0}", lField.FieldType); Console.WriteLine("Clen je privatni : {0}" ,lField.IsPrivate); Console.WriteLine("Clen je verejny : {0}", lField.IsPublic); Console.WriteLine("Clen je staticky : {0}", lField.IsStatic); } } Při získávání informací o datových členech typu si musíme dát pozor na použití příznaků BindingFlags, které se používají k určení výběru členů a to nejen v metodě GetFields, ale obecně u všech metod, které z metadat získávají instance potomků třídy MemberInfo. U reflexe datových členů je to však o to důležitější neboť pokud tyto příznaky neuvedeme korektně, tak námi získané pole bude prázdné. 140 Druhý příklad použití třídy FieldInfo je zde pro ukázku toho, jak je možné pomocí reflexe nastavit hodnotu datového členu. public static void NastaveniHodnotyPriklad() { //vytvoreni instance Osoba lOsoba = new Osoba(); lOsoba.Jmeno = "Jan"; Console.WriteLine("Jmeno osoby : {0}", lOsoba.Jmeno); Type lOsobaType = typeof(Osoba); //ziskani datoveho clenu jmeno FieldInfo lJmenoField = lOsobaType.GetField("jmeno",BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); //nastaveni datoveho clenu jmena instance lOsoba lJmenoField.SetValue(lOsoba,"Karel"); Console.WriteLine("Jmeno osoby : {0}", lOsoba.Jmeno); } Pro přiřazení hodnoty datového členu konkrétní instance je potřeba použít metody SetValue, jíž předáme instanci, na které chceme hodnotu členu pro něž máme získanou instanci třídy FieldInfo nastavit a samozřejmě kýženou hodnotu. Zajímavé na použití reflektivního přiřazování hodnot je fakt, že hodnotu lze nastavit nezávisle na modifikátoru přístupu datového členu, takže i když jsou všechny datové členy definované na třídě Osoba soukromé, je možné jim bez problému nastavit hodnotu. Reflexe je holt mocná zbraň. Obdobně jako se pracuje s instancemi třídy FieldInfo se pracuje i s instancemi třídy PropertyInfo, které slouží k reflexi přístupových vlastností. Reflexe metod K reflexi metod definovaných na jednotlivých typech je pro nás v rámci .NET připravena třída MethodInfo. Stejně jako u ostatních reflektujících typů je možné o dané metodě zjistit mnoho zajímavých informací, jako je její návratový typ, zda-li je virtuální atd. public static void VypisInfo() { Type lOsobaType = typeof(Osoba); MethodInfo[] lMethods = lOsobaType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public); foreach(MethodInfo lMethod in lMethods) { Console.WriteLine("Nalezena informace o metode {0}", lMethod.Name); Console.WriteLine("Metoda je virtualni : {0}", lMethod.IsVirtual); Console.WriteLine("Metoda je staticka : {0}", lMethod.IsStatic); Console.WriteLine("Navratovy typ metody : {0}", lMethod.ReturnType); Console.WriteLine("Metoda je verejna : {0}",lMethod.IsPublic); } } Jak můžeme vidět, tak pro získání metod stačí na instanci třídy Type zavolat metodu GetMethods, popřípadě specifikovat hledané metody pomocí výčtového příznaku BindingFlags. Přetížená verze této metody bez parametru vyhledá pouze veřejné instanční metody. Samozřejmě je kromě pouhého zjišťování informací o metodě i danou metodu zavolat. Za tímto účelem je na třídě MethodBase (společný předek tříd MethodInfo a ConstructorInfo) definována metoda Invoke, jejíž použití ukazuje následující příklad. 141 public static void VyvolaniPriklad() { Type lOsobaType = typeof(Osoba); //ziskani metody ToString MethodInfo lToStringMethod = lOsobaType.GetMethod("ToString"); //vytvoreni instance pomoci reflexe Osoba lInstance = (Osoba) Activator.CreateInstance(lOsobaType); lInstance.Jmeno = "Jan"; lInstance.Prijmeni = "Novak"; Console.WriteLine("Vyvolavam metodu ToString.."); //vyvolani metody na instanci object lResult = lToStringMethod.Invoke(lInstance,null); //vypsani vysledku volani metody Console.WriteLine(lResult); } Instancí třídy MethodInfo reflektující metodu ToString jsme získali použitím metody GetMethod, které jsme předali název požadované metody. Prvním parametrem metody Invoke je objekt instance na kterém má být konkrétní metoda zavolána a druhým je pole objektů typu System.Object představující formální parametry metody (v tomto případě jsme metodě žádné parametry nepředávali). Jak by volání metody s parametry pomocí reflexe vypadalo? To se snaží ukázat příklad, který následuje. public static void VyvolaniParametryPriklad() { Type lStringType = typeof(string); //vytvoreni parametru pro konstruktor char[] lParam = new char[4]{`T`,`e`,`x`,`t`}; //vytvoreni instance pomoci reflexe string lInstance = (string) Activator.CreateInstance(lStringType,new object[1]{lParam}); //vytoreni sady parametru pro metodu, aby nedoslo k nejednoznacnosti Type[] lMethodParams = new Type[1]; lMethodParams [0] = typeof(string); //ziskani metody IndexOf ve verzi prijimajici jeden parametr typu string MethodInfo lIndexOfMethod = lStringType.GetMethod("IndexOf",lMethodParams); object lResult = lIndexOfMethod.Invoke(lInstance,new object[1]{"x"}); Console.WriteLine(lResult); } Jelikož je metoda IndexOf, která je definována na třídě String přetížená musíme přesně zvolit tu verzi, která má být reflektována, jinak by při jejím získávání pomocí metody GetMethod došlo k nejednoznačnosti a byla by vyvolána výjimka AmbiguousMatchException. Jak se takovéto situaci vyhnout? Odpověď na tuto otázku přichází v podobě použití přetížené verze metody GetMethod a to verze, která kromě názvu metody přijímá ještě druhý parametr a tím je pole objektů typu Type. V podobě tohoto pole totiž řekneme jaké parametry má hledaná metoda přijímat. V našem případě je toto pole pouze jednoprvkové a obsahuje pouze instanci třídy Type pro třídu String. Tím říkáme, že chceme získat verzi metody očekávající právě jeden parametr a ten má být typu String. Také konstruktor třídy String je přetížený a to se projevilo při dynamickém vytváření instnance pomocí třídy Activator, kde musíme zadat ve formě pole objekty představující parametry konstruktoru (zde konkrétně voláme verzi konstruktoru s parametrem typu pole znaků). 142 Poznáváme C# a Microsoft.NET 38. díl – atributy a jejich reflexe Tento díl bude věnován seznámení s možností definice deklarativních informací k jednotlivým elementům aplikace, jež se v prostředí .NET frameworku provádí užitím atributů. Také se naučíme vytvářet naše vlastní atributy a pomocí reflexe je i získávat. Proč atributy? V každém programovacím jazyce, pomocí specifických konstrukcí, které daný jazyk nabízí, deklarujeme u jednotlivých elementů programového kódu řadu informací. Například pokud vytváříme třídu, můžeme uvést, která všechna rozhraní jsou touto třídou implementována nebo u jednotlivých členů našeho typu specifikujeme modifikátory přístupu pro určení jejich viditelnosti. Omezením tohoto přístupu je fakt, že takto zadávané informace k elementům kódu jsou omezeny na použití předem určených konstrukcí konkrétního jazyka. V prostředí .NET frameworku je možné pro zadání dodatečných informací k elementu programu použít takzvané atributy. Atributy jsou tedy jazykovými konstrukcemi, které mohou doplnit elementy programového kódu (assembly, moduly, typy, členy, návratové hodnoty a parametry) o specifické doplňující informace. Jak atributy použít? Stejně jako vše ostatní, tak i atributy jsou v prostředí .NET frameworku objekty. Každý použitý atribut je instancí třídy, která buď přímo či nepřímo dědí od třídy System.Attribute. V jazyce C# jsou atributy specifikovány uvedením jména atributu do hranatých závorek nad konkrétním elementem programového kódu. V uvedeném příkladu je použití atributů demonstrováno na označení třídy za zastaralou pomocí atributu Obsolete. /// <summary> /// Ukazka pouziti atributu /// </summary> [Obsolete("Tato trida by jiz nemela byt dale vyuzivana",false)] public class StaraTrida { //nejaka implementace tridy } Podle konvence by měl název každé třídy představující třídu atributu končit slovem Attribute , aby třída byla odlišena od ostatních tříd. Ale při použití jednotlivých atributů nejsme nuceni specifikovat celý název atributu. Nemusíme tedy všude uvádět onu příponu Attribute, což jste vlastně mohli vidět na předchozím příkladu s použitím třídy atributu, jejíž celý název je ve skutečnosti ObsoleteAttribute. To znamená, že použití atributu v předchozím příkladu bylo ve výsledku shodné s tímto použitím: [System.ObsoleteAttribute("Tato trida by jiz nemela byt dale vyuzivana",false)] public class StaraTrida { //nejaka implementace tridy } 143 Samozřejmě lze k elementu přiřadit i více než jeden atribut a to tak ,že jednotlivé atributy od sebe oddělíme čárkami. [Obsolete(),Serializable()] public class TridaViceAtributu { //nejaka implementace tridy } Poziční a pojmenované parametry atributů Atributy mohou přebírat parametry, které rozšiřují množství doplňujících informací při jejich použití. Při používání atributů se používají dva typy předávaných parametrů. Prvním z nich jsou parametry poziční, které odpovídají parametrům předávaným veřejným konstruktorům typu daného atributu. Naproti tomu, použití parametrů pojmenovaných představuje cestu, nastavení veřejných datových členů nebo vlastností typu daného atributu. Pojmenované parametry se narozdíl od pozičních paremetrů, musejí specifikovat zadaním jména datového členu nebo vlastnosti, kterou nastavují. V následujícím příkladu je použit atribut DllImport sloužící k použítí metod v DLL knihovnách napsaných v neřízeném jazyce. Pozičním parametrem atributu předáváme název knihovny (“user32.dll”) a pomocí pojmenovaného parametru CharSet sdělujeme jaká znaková sada má být použita pro řetězce. public class NejakaTrida { [System.Runtime.InteropServices.DllImport("user32.dll",CharSet=System.Runti me.InteropServices.CharSet.Ansi)] public static extern void NejakaLinkovanaMetoda(); } Cílené použití atributů V některých situacích může při použití atributu docházet k nejednoznačnosti. Například použijeme-li atribut pro metodu, může to znamenat, že atribut náleží k metodě nebo k její návratové hodnotě. class NejednoznacnePouziti { [NejakyAttribut()] public int NejakaMetoda() { return 0; } } I přes tuto nejednoznačnost půjde takovýto kód zkompilovat, protože C# má pro každý možný typ nejednoznačné deklarace atributu určený výchozí element pro který má atribut použít. V tomto konkrétním příkladu by to byla právě metoda. Pokud chcete vědět všechny výchozí elementy pří nejednoznačných deklaracích, odkážu vás na přehlednou tabulku uvedenou v .NET framework SDK. Pokud nechceme, aby při takovéto deklaraci použití atributu byl atribut použit pro výchozí element, můžeme ono výchozí použití překrýt a to právě použitím cíleného použití atributu. Obecně se cílené použití atributu zapisuje takto : 144 [cil : seznam_atributů] Cíl může být jeden z těchto : assembly, field, event, method, module, param, property, return, type. class CilenePouziti { [return: NejakyAttribut()] public int NejakaMetoda() { return 0; } } Poznámka: Využití atributů aplikovaných pro návratové hodnoty metod nalezneme především při interakci s neřízeným kódem. Vytváření vlastních atributů Vytvářet vlastní atributy je velice snadné. Jediné co nám k vytvoření atributu stačí je vytvoření třídy, která přímo nebo nepřímo dědí ze střídy System.Attribute. Takže pokud se rozhodneme, že vytvoříme atribut nesoucí doplňující informace o autorovi nějakého elementu programového kódu, mohla by implementace vypadat nějak takto: /// /// /// /// <summary> Trida predstavujici uzivatelsky atribut slouzici k definici autora programoveho elementu </summary> //specifikujeme na kterych elementech muze byt atribut pouzit [AttributeUsage(AttributeTargets.Class|AttributeTargets.Struct|AttributeTar gets.Method)] public class AuthorAttribute : Attribute { private string jmeno; private string prijmeni; private double verze; public AuthorAttribute(string jmeno, string prijmeni) { this.jmeno = jmeno; this.prijmeni = prijmeni; this.verze = 1.00; } public string Jmeno { get{return jmeno;} } public string Prijmeni { get{return prijmeni;} } //tato vlastnost bude moci byt prirazena pomoci //pojmenovaneho parametru 145 public double Verze { get{return verze;} set{verze = value;} } } Při použití atributu bude zadáváno jméno a příjmení autora a to pomocí pozičních parametrů, protože veřejný konstruktor typu tyto parametry očekává. Volitelně bude možné použitím pojmenovaného parametru specifikovat verzi elementu, protože vlastnost Verze je deklarována jako veřejná nastavitelná vlastnost. Třídě atributu jsme definovali atribut AttributeUsage, jehož použitím specifikujeme na které elementy programového kódu bude možné atribut použít. V našem případě půjde atribut použít na třídy, struktury a metody, což je uvedeno pomocí výčtu AttributeTargets. Získání uživatelských atributů při použití reflexe Nyní, když už umíme atributy vytvářet ještě potřebujeme zjistit zda je pro programový element konkrétní atribut definován. Teď když jsme již vybaveni znalostmi mechanismu reflexe si musíme naše znalosti o ní mírně rozvinout a to právě proto, že pomocí reflexe jsme schopni přítomnost atributu na elementu zjistit. V následujícím příkladu je u pár ukázkových tříd použit námi vytvořený atribut AuthorAttribute a použitím reflexe jsou pro jednotlivé třídy zjištěny jejich autoři, případně jsou zjištěny i autoři jednotlivých metod. /// <summary> /// Priklad na pouziti atributu AuthorAttribute a pouziti reflexe atributu /// </summary> public class AtributyPriklad { static void VypisAutory(Type Typ) { Console.WriteLine("Zpracovavam typ {0}",Typ.Name); //zjistime, zda je pro dany typ definovan atribut AuthorAttribute if ( Attribute.IsDefined(Typ,typeof(AuthorAttribute)) ) { //ziskame atribut AuthorAttribute lAuthorAttr = (AuthorAttribute) Attribute.GetCustomAttribute(Typ,typeof(AuthorAttribute)); Console.WriteLine("Autor typu : {0}, Verze : {1}",lAuthorAttr.Prijmeni + lAuthorAttr.Jmeno, lAuthorAttr.Verze); } //ziskame seznam verejnych instnancnich metod MethodInfo[] lMethods = Typ.GetMethods(BindingFlags.Public|BindingFlags.Instance); foreach(MethodInfo lMethod in lMethods) { //pokud je pro metodu definovan atribut typu AuthorAttribute vypiseme info if ( Attribute.IsDefined(lMethod, typeof(AuthorAttribute)) ) { AuthorAttribute lMethodAuthor = (AuthorAttribute) Attribute.GetCustomAttribute(lMethod,typeof(AuthorAttribute)); Console.WriteLine("Autor metody {0} : {1}, Verze : {2}", lMethod.Name, lMethodAuthor.Prijmeni + lMethodAuthor.Jmeno, lMethodAuthor.Verze); 146 } } } } public static void SpustPriklad() { VypisAutory(typeof(PrvniTrida)); VypisAutory(typeof(DruhaTrida)); VypisAutory(typeof(TretiTrida)); } [Author("Jiri","Joudek")] class PrvniTrida { //nejaka implementace } [Author("Michal","Racek",Verze=1.2)] class DruhaTrida { [Author("Michal","Racek",Verze=1.1)] public void NejakaMetoda() { //implementace metody } //dalsi implementace tridy } [Author("Jiri","Joudek",Verze=1.3)] class TretiTrida { //nejaka implementace } Přítomnost deklarace atributu pro konkrétní element zjistíme pomocí statické metody IsDefined třídy Attribute, které v jedné z přetížených verzí předáme instanci potomka třídy MemberInfo představující element u něhož přítomnost atributu zjišťujeme a druhým parametrem je typ atributu který hledáme. Pro získání atributu použijeme další statickou metodu třídy Attribute, kterou je metoda GetCustomAttribute, jíž v našem příkladu předáme stejné parametry jako v případě metody IsDefined. Jelikož metoda GetCustomAttribute vrací odkaz na objekt typu Attribute, tak pokud chceme používat členy definované na třídě AuthorAttribute musíme provést explicitní přetypování. Poznáváme C# a Microsoft.NET 39. díl – další použití reflexe Dnešní díl, který je posledním dílem, ve kterém se budu zabývat reflexí, bude věnován použití reflexe pro vytváření instancí za běhu jiným způsobem než použitím třídy Activator. Také si ukážeme funkci polymorfizmu v reflexi a nakonec poodhalím některé třídy ze jmenného prostoru System.Relfection.Emit pro vytváření nových typů za běhu. Použití třídy ConstructorInfo Dosud jsme v našich příkladech, kde jsme vytvářeli instance jednotlivých typů pomocí reflexe používali třídu Activator a její metodu CreateInstance. Kromě tohoto způsobu, můžeme využít i třídu ConstructorInfo, která slouží k reflexi konstruktorů daného typu. Jelikož je tato třída potomkem abstraktní třídy MethodBase obsahuje, stejně jako nám již známá třída MethodInfo, metodu Invoke, která v případě třídy ConstructorInfo vyvolá 147 určitý konstruktor (pokud je takový nalezen). Následující příklad ukazuje použití této třídy na ukázkové třídě Zamestnanec. /// <summary> /// Ukazkova trida, ktera bude pozdeji instancovana pomoci reflexe /// </summary> public class Zamestnanec { private string jmeno; private string prijmeni; private int hodinovaMzda; public string Jmeno { get{return jmeno;} } public string Prijmeni { get{return prijmeni;} } public int HodinovaMzda { get{return hodinovaMzda;} set{hodinovaMzda = value;} } public Zamestnanec(string jmeno, string prijmeni, int hodinovaMzda) { this.jmeno = jmeno; this.prijmeni = prijmeni; this.hodinovaMzda = hodinovaMzda; } public void VypisMzdu(int pocetHodin) { Console.WriteLine(pocetHodin * hodinovaMzda); } } /// <summary> /// Ukazka vytvoreni instance pomoci reflexe pouzitim tridy ConstructorInfo /// </summary> public class ConstructorInfoPriklad { public static void VytvoritZamestnance() { Type lZamType = typeof(Zamestnanec); //vytvoreni seznamu typu parametru pro nalezeni konkretniho konstruktoru Type[] lParamTypes = new Type[3]; lParamTypes[0] = typeof(string); lParamTypes[1] = typeof(string); lParamTypes[2] = typeof(int); //hledame instancni, verejne konstruktory BindingFlags lFlags = BindingFlags.Instance | BindingFlags.Public; ConstructorInfo lConstInfo = lZamType.GetConstructor(lFlags,null,lParamTypes,null); //vyvolani kostruktoru spolu s predanim parametru Zamestnanec lInstance = (Zamestnanec) lConstInfo.Invoke(new object[]{"Jan","Novak",100}); lInstance.VypisMzdu(160); 148 } } Vyvolání konstruktoru použitím metody Invoke je v zásadě stejné jako v případě použití u třídy MethodInfo, takže musíme pomocí pole instancí třídy Type specifikovat jakou verzi konstruktoru hledáme a pak pomocí pole objektů předat metodě Invoke hodnoty těchto parametrů. Reflexe a polymorfizmus Polymorfizmus je jayzce C# využíván nejen v případě standardních volání, ale i při používání reflexe. V následujícím příkladu je toto ukázáno na vytvoření abstraktní třídy Osoba, která obsahuje definici pro jednu abstraktní metodu. Třída má dva potomky, které její abstraktní metodu implementují. Zdrojový kód ukázkových tříd vypadá takto: /// <summary> /// Bazova trida pro osoby /// </summary> public abstract class Osoba { public abstract void RekniPozdrav(); } public class CeskaOsoba : Osoba { public override void RekniPozdrav() { Console.WriteLine("Ahoj lidi"); } } public class AnglickaOsoba : Osoba { public override void RekniPozdrav() { Console.WriteLine("Hello people"); } } Vlastní příklad získá za běhu assembly, která obsahuje definici výše uvedených typů, projde všechny typy v ní obsažené a zkoumá, zda-li je konkrétní typ potomkem třídy Osoba. V případě, že je tomu tak, je vytvořena instance typu a zavolána na něm metoda RekniPozdrav. Zdrojový kód je takovýto : /// <summary> /// Ukazka pouziti polymorfismu (pozdni vazby) v reflexi /// </summary> public class PolymofismusPriklad { public static void Pozdravy() { //ziskani nasi assembly Assembly lAssembly = Assembly.Load("PrikladyZive39"); foreach(Type lType in lAssembly.GetTypes()) { 149 } //pokud je aktualni typ potomkem tridy Osoba vyvolame //na jeho instanci metodu RekniPozdrav if (lType.IsSubclassOf(typeof(Osoba)) ) { Osoba lInstance = (Osoba) Activator.CreateInstance(lType); MethodInfo lMethod = lType.GetMethod("RekniPozdrav"); lMethod.Invoke(lInstance,null); } } } Výstup, jež po spuštění uvidíte a který jistě očekáváte je samozřejmě tento: Ahoj lidi Hello people Dynamické vytváření nových elementů aplikace pomocí reflexe Na závěr našeho několikadílného povídání o reflexi, bych zde rád čtenářům poskytl náhled na o něco složitější funkci reflexe, kterou je dynamické vytváření jednotlivých elementů aplikace (assembly, moduly, typy, členy typů) za jejího běhu. Jmenný prostor, který obsahuje třídy umožňující vytvářet nová metadata a kód MSIL nese název System.Reflection.Emit. Nejprve uvedu příklad, který v aktualní aplikační doméně vytvoří novou assembly, modul, v modulu dynamický typ a v něm metodu, potom příklad podrobněji popíšu. /// <summary> /// Ukazka vytvoreni novych elementu aplikace za behu /// </summary> public class EmitPriklad { public static void VytvoreniTypu() { //Ziskani aplikacni domeny AppDomain lCurrentDomain = AppDomain.CurrentDomain; //vytvoreni jmena pro assembly AssemblyName lAssemblyName = new AssemblyName(); lAssemblyName.Name = "DynamickaAssembly"; //vytvoreni dynamicke assembly AssemblyBuilder lAssemblyBuilder = lCurrentDomain.DefineDynamicAssembly(lAssemblyName,AssemblyBuilderAccess.Ru n); //vytvoreni modulu ModuleBuilder lModuleBuilder = lAssemblyBuilder.DefineDynamicModule("DynamickyModul"); //vytvoreni typu TypeBuilder lTypeBuilder = lModuleBuilder.DefineType("DynamickyTyp",TypeAttributes.Public); //vytvoreni metody MethodBuilder lMethodBuilder = lTypeBuilder.DefineMethod("DynamickaMetoda",MethodAttributes.Public | MethodAttributes.Virtual,typeof(void),null); //vytvoreni IL kody predstavujici telo metody ILGenerator lGenerator = lMethodBuilder.GetILGenerator(); lGenerator.EmitWriteLine("Vypsani pomoci dynamicke metody"); lGenerator.Emit(OpCodes.Ret); //finalni vytvoreni typu 150 } } lTypeBuilder.CreateType(); //ziskani instance System.Type pro nas typ Type lDynamicType = lModuleBuilder.GetType("DynamickyTyp"); object lInstance = Activator.CreateInstance(lDynamicType); //vyvolani metody lDynamicType.GetMethod("DynamickaMetoda").Invoke(lInstance,null); Předtím než se pustíme do vytváření nové dynamické assembly v aplikační doméně, musíme nejdříve pomocí instance třídy AssemblyName vytvořit její název. Pokud nám stačí assembly přiřadit pouze jednoduchý název, využijeme k tomu implicitní konstruktor a po té nastavíme instanční vlastnosti Name. Pokud jsme tak učinili, můžeme již použitím metody DefineDynamicAssembly na instanci třídy AppDomain na základě jména vytvořit novou assembly. Kromě jména assembly metodě ještě formou parametru předáme přístupový mód možné použití dynamické assembly a to pomocí výčtu AssemblyBuilderAccess. V našem příkladu je použita hodnota Run, která vytvářenou assembly umožňuje pouze spouštět, nikoliv ji však uložit na disk. Metoda DefineDynamicAssembly vrací odkaz na objekt typu AssemblyBuilder, který představuje dynamickou assembly. Na tomto objektu je následně zavoláním metody DefineDynamicModule vytvořen nový modul se specifickým jménem. Zavoláním této metody dostaneme odkaz na instanci třídy ModuleBuilder, pomocí níž analogickým způsobem vytvoříme novou definici typu (metoda DefineType) a použitím výčtu TypeAttributes řekneme, že se jedná o typ veřejně přístupný. Na instanci třídy TypeBuilder, pomocí metody DefineMethod definuje v typu novou metodu a výčtem MethodAttributes specifikujeme, že metoda je veřejná a virtuální. Po té získáme generátor mezikódu IL (ILGenerator) pro definici těla metody. Pomocí metody EmitWriteLine zajistíme vygenerování IL kódu, který odpovídá příkazu Console.WriteLine v C#. Pro ukončení metody musíme vygenerovat odpovídající IL kód. To zajistíme metodou Emit třídy ILGenerator, jíž předáme instanci třídy OpCodes, která ve formě svých statických datových členů poskytuje prostředek pro reprezentaci IL kódu pro jednotlivé akce. V našem příkladu je použit statický datový člen OpCodes.Ret, který reprezentuje IL kód právě pro konec těla metody. Pro ukončení vytvoření nového typu použijeme metodu CreateType třídy TypeBuilder. Nakonec je získána instance třídy System.Type, vytvořena instance a zavolána definovaná metoda. Poznáváme C# a Microsoft.NET 40. díl – serializace Dnešní díl se začneme věnovat serializaci. Vysvětlíme si co tento pojem znamená, jak vytvářet serializovatelné typy a jakými způsoby tyto typy serializovat. Co je to serializace? Mnoho instancí, které při svém programování používáme mají celkem krátký „život“. Jsou nějakou cestou (konstruktorem, skrze reflexi, tovární metodou…) vytvořeny, po té jsou nějakým způsobem použity a pak když nejsou potřeba (neexistuje na ně jakákoliv reference) jsou při úklidu (Garbage collection) Garbage collectorem odstraněny z paměti. Ovšem, ne všechny instance jsou používány takto krátce a některé instance nesoucí potřebná data potřebujeme zachovat na delší dobu. To dokážeme zařídit tak, že 151 onu instanci uložíme do nějakého perzistentního úložiště (relační databáze, soubor..). Takovémuto uložení instance do perzistentního úložiště se říká Serializace. V podstatě jde o to, že instance je převedena na Stream, tím pádem může být, jak již bylo řečeno, uložena, ale také díky tomu, že je Streamem, i poslána např. pomocí HTTP protokolu na zcela jiný počítač. Serializovatelné typy, musí být i ty, které jsou používány při takzvaném remotingu, což je možnost používat objekty mezi různými aplikačními doménami. Opačnému procesu, tedy procesu pří kterém je z nějakého proudu vytvořena instance, se naopak říká Deserializace. Jak učinit typy serializovatelnými? K tomu, abychom naše typy učinili serializovatelnými (schopnými se serializovat) vedou dvě cesty. První cestou je použití atributu System.Serializable, což je cesta jednodušší a v obvyklých aplikacích častěji používaná. Druhým způsobem je nechat námi vytvářený typ implementovat rozhraní System.ISerializable. My se dnes budeme zabývat použitím atributu Serializable. V případě použití tohoto atributu na náš typ se o průběh serializace instance tohoto typu kompletně postará běhové prostředí .NET runtime. Takže pokud bychom chtěli mít třídu představující osobu a chtěli bychom, aby bylo možné instance této třídy serializovat, provedeme to jednoduše takto : /// <summary> /// Ukazkova trida predstavujici osobu, jejiz instance mohou byt serializovany /// </summary> [Serializable] public class Osoba { private DateTime datumNarozeni; private string jmeno; private string prijmeni; public Osoba(string Jmeno, string Prijmeni, DateTime DatumNarozeni) { this.jmeno = Jmeno; this.prijmeni = Prijmeni; this.datumNarozeni = DatumNarozeni; } //zbytek tridy } V případě této výchozí serializace ( = použití atributu Serializable) jsou serializovány všechny datové členy daného typu a to včetně těch soukromých. Je důležité vědět, že všechny typy, kterých jsou datové členy daného typu musí být taktéž serializovatelné. Pokud by tomu tak nebylo a pokusili bychom se o serializaci, došlo by k vyhození výjimky System.Runtime.Serialization.SerializationException. /// <summary> /// Tato trida by nemohla byt serialozovana protoze obahuje datovy clen /// ktery nelze serializovat /// </summary> [Serializable] public class NemoznaSerializace { NeserializovatelnyTyp clen; } 152 public struct NeserializovatelnyTyp { //implementace typu } I když je třída označena atributem Serializable, její datový člen není a proto ji nelze serializovat. V případě naší ukázkové třídy Osoba, není se serializací problém, protože všechny typy datových členů (String a DateTime), jejichž definici obsahuje, jsou označeny jako serializovatelné. Jak instanci serializovat? Nyní když víme jakým způsobem učinit typ serializovatelným a také jaké je potřeba dodržet pravidla, potřebujeme ještě vědět jak vlastní serializaci provedeme. Ve jmenném prostoru System.Runtime.Serialization se nachází rozhraní IFormatter, které předepisuje funkcionalitu pro formátování serializovaných objektů. Přesněji, obsahuje předpis pro metody Serialize a Deserialize, které zařizují, jak název napovídá, vlastní serializaci respektive deserializaci. V základní knihovně tříd .NET frameworku jsou pro naše použití k dispozici dvě základní implementace tohoto rozhraní a to třídy BinaryFormatter a SoapFormatter. BinaryFormatter je implementace, která serializuje a desearilizuje objekty v binárním formátu a nalezneme ji ve jmenném prostoru System.Runtime.Serialization.Formatters.Binary. SoapFormatter zase serializuje a deserializuje objekty do formátu SOAP, což je v podstatě implementace XML a nachází se ve jmenném prostoru System.Runtime.Serialization.Formatters.Soap. Následující příklad demostruje použití serializace instance třídy Osoba do binárního formátu, tedy pomocí třídy BinaryFormatter. public static void Serializace() { DateTime lDate = new DateTime(1984,2,9); Osoba lOsoba = new Osoba("Jan","Novak",lDate); Stream lStream = new FileStream("C:/test.bin",FileMode.Create); IFormatter lFormatter = new BinaryFormatter(); try { lFormatter.Serialize(lStream,lOsoba); 153 } finally { lStream.Close(); } } Metodě Serialize předáme ve formě parametrů referenci na Stream, který vede ke konkrétnímu úložišti a samotnou instanci, kterou chceme serializovat. V našem případě jsme nechali instanci serializovat do souboru. A jak instanci deseralizujeme? To už ukazuje tento příklad : public static void Deserializace() { IFormatter lFormatter = new BinaryFormatter(); Stream lStream = new FileStream("C:/test.bin",FileMode.Open); Osoba lOsoba = null; try { lOsoba = (Osoba) lFormatter.Deserialize(lStream); Console.WriteLine("Deserializovana osoba je {0} {1}",lOsoba.Jmeno,lOsoba.Prijmeni); } finally { lStream.Close(); } } Deserializace instance je také velmi jednoduchá záležitost. Jediné co metodě Deserialize musíme předat je odkaz na Stream vedoucí k úložišti instance. Pokud bychom chtěli instanci serializovat do formátu SOAP, jediné co bude ve zdrojovém kódu jiné bude záměna použité instance třídy BinaryFormatter za instanci třídy SoapFormatter. Obecně platí, že pro serializaci do souboru je vhodnější použít binární formát, protože výsledný soubor je o dost menší a mimo to také výrazně rychlejší. Příští díl bude opět pojednávat o serializaci. Dozvíme se jak mít serializaci více pod kontrolou a zjistíme, že není nutné serializovat všechny datové členy instance typu. Poznáváme C# a Microsoft.NET 41. díl - pokročilé využití serializace Po minulé díle, který byl věnován úvodu do použití serializace bych na toto téma dnes rád navázal a to povídáním o dalších způsobech využití tohoto zajímavého mechanismu v rámci .NET. Selektivní serializace členů Možná některé z vás, při čtení minulého dílu o serializaci, napadlo jestli není nějakým způsobem možné zajistit selektivní serializaci datových členů. Jinými slovy, jestli lze určit nějaké datové členy, jejichž hodnoty nebudou při serializaci přidány do datového proudu vedoucího k cíli serializace. Obvyklým případem takovýchto datových členů, jsou členy, jejichž hodnoty nemají smysl na jiném počítači či v jiném čase. Dalším případem těchto 154 „přechodných“ členů jsou datové členy, které mohou být vypočteny z jiných datových členů. Tohoto cíle je nám umožněno v .NET frameworku dosáhnout velmi jednoduše a to použitím atributu System.NonSerialized na konkrétní datový člen. Následující zdrojový kód představuje známou třídu Osoba, která má od minulého dílu navíc datový člen vek, který je pomocí zmíněného atributu označen, aby nebyl serializován. [Serializable] public class Osoba { private DateTime datumNarozeni; private string jmeno; private string prijmeni; //datovy clen vek nebude serializovan [NonSerialized] private int vek; public Osoba(string Jmeno, string Prijmeni, DateTime DatumNarozeni) { this.jmeno = Jmeno; this.prijmeni = Prijmeni; this.datumNarozeni = DatumNarozeni; //vek je vypocitan z datumu narozeni this.vek = (DateTime.Now - datumNarozeni).Days/365; } //zbytek tridy } To, že datový člen vek není při serializaci přidán do serializačního streamu, logicky znamená, že při deserializaci instance, nemůžeme očekávat obnovení hodnoty členu vek. O tom nás ostatně přesvědčuje následující příklad. static void DeserializacePriklad() { DateTime lDate = new DateTime(1984,2,9); Osoba lOsoba = new Osoba("Jan","Novak",lDate); Console.WriteLine("Vek osoby pred serializaci : {0}", lOsoba.Vek); IFormatter lFormatter = new BinaryFormatter(); Stream lStream = null; try { lStream = new FileStream("C:/novak.bin",FileMode.Create); lFormatter.Serialize(lStream,lOsoba); lStream.Position = 0; Osoba lDesOsoba = (Osoba) lFormatter.Deserialize(lStream); Console.WriteLine("Vek deserializovane osoby je : {0}", lDesOsoba.Vek); } finally { lStream.Close(); } } Zmíněný příklad by měl na vaší obrazovku vytvořit následující výstup: Vek osoby pred serializaci : 21 Vek deserializovane osoby je : 0 155 Rozhraní IDeserializationCallback Věřím, že spoustu z vás po přečtení předchozích řádků napadlo, jestli je správné, že hodnota datového členu vek je nulová, když má být vlastně vypočítána z hodnoty datového členu vypodídajícím o datu narození. Vždyt to přeci znamená porušení takzvaných neměných podmínek třídy. Bylo by tedy dobré mít nějakou možnost mít deserializační proces do určité míry pod kontroulou a správnost neměných podmínek daných třídou zajistit. Dosáhnout této kontroly nad deserializačním procesem instancí dané třídy je možné zajistit implementaci rozhraní System.Runtime.Serialization.IDeserializationCallback. [Serializable] public class Osoba : System.Runtime.Serialization.IDeserializationCallback { private DateTime datumNarozeni; private string jmeno; private string prijmeni; [NonSerialized] private int vek; public Osoba(string Jmeno, string Prijmeni, DateTime DatumNarozeni) { this.jmeno = Jmeno; this.prijmeni = Prijmeni; this.datumNarozeni = DatumNarozeni; this.vek = (DateTime.Now - datumNarozeni).Days/365; } #region IDeserializationCallback Members public void OnDeserialization(object sender) { this.vek = (DateTime.Now - datumNarozeni).Days/365; } #endregion //zbytek implementace tridy } Zmíněne rozhraní předepisuje jedinou metodu, kterou je OnDeserialization v jejímž tělě můžeme zajistit požadované operaci při deserializačním procesu. V našem případě opět vypočítáme věk instance třídy Osoba, pomocí hodnoty datového členu datumNarozeni, který je v tuto chvíli již deserializován. Vlastní řízení serializace Jak již víme, tak každá třída, která má podporovat serializaci, musí být označena atributem Serializable. I když je pomocí atributu NonSerialized a implementace rozhraní IDeserializationCallback možné dosáhnout poměrně solidní kontroly nad serializačním a deserializačním procesem, v některých případech potřebujeme mít kontrolu ještě větší. Za tímto účelem je možné, kromě označení třídy atributem Serializable, nechat třídu implementovat rozhraní System.Runtime.Serialization.ISerializable. To nám umožňuje mimo jiné přesně definovat jaké informace budou serializovány. Pro lepší představu uvažujme následující příklad s mou oblíbenou ukázkovou třídou Zarovka. [Serializable] public class Zarovka 156 { private bool sviti; private DalsiInfo info = new DalsiInfo(); public Zarovka(){} public DalsiInfo Info { get{return info;} set{info = value;} } public bool Sviti { get{return sviti;} } public void Rozsvitit() { sviti = true; } public void Zhasnout() { sviti = false; } } //neserializovatelna trida pouzita jako clen typu Zarovka public class DalsiInfo { private string hodnota; public DalsiInfo(){} public DalsiInfo(string hodnota) { this.hodnota = hodnota; } public string Hodnota { get{return hodnota;} set{hodnota = value;} } } Třída Zarovka evidentně nemůže být serializována, protože má jeden ze svých datových členů typu DalsiInfo, který není serializovatelný. Pokud bychom byli autory třídy DalsiInfo, nebyl by to až takový problém, protože bychom měli možnost tuto třídu jako serializovatelnou jednoduše označit. Představme si, ale situaci, kdy nejsme autory této třídy a tedy možnost označit ji atributem Serializable tím pádem nemáme. A to je právě jeden z případů na použítí rozhraní ISerializable. Rozhraní definuje metodu GetObjectData příjímající parametry typu SerializationInfo a StreamingContext. Pro nás je zajímavý hlavně první parametr, tedy ten typu SerialzationInfo, pomocí kterého přenášíme potřebná data k serializaci a následně i deserializaci instance. Metoda GetObjectData je volána při serializaci konkrétní 157 implementací IFormatteru. Samozřejmě je také potřeba definovat ještě způsob deserializace, tedy operace, které se provedou při vytváření deserializované instance. To se v případě vlastní serializace učiní implementaci speciálního deserializačního konstruktoru. Nyní se podívejme na řešení našeho problému použitím implementace rozhraní ISerializable třídou Zarovka. /// <summary> /// Ukazkova trida na vlastni implementaci serializace /// </summary> [Serializable] public class Zarovka : ISerializable { private bool sviti; private DalsiInfo info = new DalsiInfo(); public Zarovka(){} public DalsiInfo Info { get{return info;} set{info = value;} } public bool Sviti { get{return sviti;} } public void Rozsvitit() { sviti = true; } public void Zhasnout() { sviti = false; } #region ISerializable Members public void GetObjectData(SerializationInfo serInfo, StreamingContext context) { serInfo.AddValue("infoHodnota",info.Hodnota); serInfo.AddValue("sviti",sviti); } #endregion //deserializacni konstruktor private Zarovka(SerializationInfo serInfo,StreamingContext context) { this.sviti = serInfo.GetBoolean("sviti"); this.info = new DalsiInfo(serInfo.GetString("infoHodnota")); } } V metodě GetObjectData jsou do instance třídy SerializationInfo, pomocí různých přetížených verzi metody AddValue, která pod určitým názvem uchová v páru určitou, 158 hodnotu přidány data, která chceme serializovat. Tato metoda ve všech svých přetížených verzí příjímá jako parametr představující hodnotu nějaký ze základních typů, které jsou všechny označeny jako serializovatelné. Jak jste si jistě všimli, název pod kterým je hodnota uchovávána, nemusí vůbec odpovídat názvu datového členu typu. My jsme v našem příkladu využili tohoto způsobu serializace v tom, že jsme místo pokusu o serializaci celé třídy DalsiInfo serializovali pouze její vnitřní hodnotu, která je typu String, takže již žádné problémy nevznikají. Uchování vnitřní hodnoty nám totiž k zachování stavu této třídy úplně stačí. Tak jako jsme v metodě GetObjectData specifikovali data k serializaci, tak si v deserializačním konstruktoru, který je při deserializaci vždy volán, z parametru SerializationInfo tato data opět vyzvedneme a provedeme potřebné navázání instančních členů. Takže si pomocí metody GetBoolean vyzvedneme hodnotu atributu sviti a vytvoříme instanci třídy DalsiHodnota s použitím získané vnitřní hodnoty (metoda GetString). Poznámka : Při implementaci rozhraní ISerializable je vhodné deserializační konstruktor implementovat s omezenou viditelností, aby jej nemohla zavolat kdejaká třída. A jelikož v přítomnosti nějakého uživatelského konstruktoru .NET framework již nevytváří implicitnítní konstruktor, je často potřeba implicitní konstruktor nadefinovat, aby bylo vůbec možné z uživatelského kódu, instance naší třídy vytvářet. Poznáváme C# a Microsoft.NET 42. díl – úvod do použití XML Tento díl je začátkem našeho zaobírání se prací se soubory XML v prostředí .NET frameworku. V tomto prvním díle se naučíme procházet obsah XML pomocí třídy XMLReader. XML? Co je to? Věřím, že se mezi čtenáři naleznou i tací, kteří se s XML ještě neměli tu čest setkat a proto se v krátkosti o tomto jazyku zmíním. XML, což znamená eXtensible Markup Language (rozšířitelný značkovací jazyk) je značkovací jazyk, který je podmnožinou historicky známého jazyku SGML (Standard Generalized Markup Language). Jazyk XML mimo jiné odstraňuje přílišnou složitost SGML. Když jste uviděli slovíčko značkovací zajisté se mnohým z vás vybaví velmi známý značkovací jazyk pro tvorbu webových stránek, kterým je HTML. XML je tedy také jazyk, který používá tagy, atributy atd. Mezi XML a HTML je ale podstatný rozdíl a to v tom, že HTML slouží pouze k definici vzhledu pomocí striktně definovaných tagů, ale použití XML je mnohem širší. Protože XML je založen na tvorbě vlastních tagů je využíván hlavně pro přenos informací, které mohou být různými způsoby zobrazeny nebo dále zpracovány. Lze i jinými slovy říci, že HTML je jazyk, který určuje jak něco zobrazit a XML spíše říká co zobrazit (nebo jakkoliv jinak zpracovat). Jedním z hlavním důvodů pro vznik jazyku XML byla potřeba vymyslet společný formát pro komunikaci mezi různými systémy. Jak vypadá velmi jednoduchý XML dokument, který eviduje informace o zaměstnancích můžete vidět níže. <?xml version="1.0" encoding="utf-8" ?> <zamestnanci> <zamestnanec> <jmeno>Jan</jmeno> <prijmeni>Novak</prijmeni> </zamestnanec> 159 <zamestnanec> <jmeno>Jiri</jmeno> <prijmeni>Joudek</prijmeni> </zamestnanec> </zamestnanci> Tento dokument je tak jednoduchý, že jediné co v něm můžeme vidět je použití deklarace dokumentu, ve kterém je určena verze XML spolu s použitým znakovým kódováním, a jinak pouze tagy, které uvozují jednotlivé části uchovávané informace. Jednotlivé dvojice tagů (počáteční a konečný) tvoří takzvané uzly. Takže kořenový uzel je uzel zamestnanci, jehož vnořenými uzly (často nazývány dětmi) jsou uzly zamestnanec. A informace o konkrétním zaměstnanci, jsou opět tvořeny vnořenými uzly uzlu zamestnanec. Z toho můžeme vidět, že informace jsou v případě XML uchovávány v hierarchické struktuře. Jak na zpracování XML v .NET frameworku Všechny třídy, které bychom mohli při našem programování s XML v .NET potřebovat jsou začleněny do jmenného prostoru System.Xml. V zásadě jsou při zpracovávání XML v .NET k dispozici dva odlišné způsoby zpracování. Prvním z nich je zpracování pomocí DOM. Pod touto zkratkou se ukrývá Document Object Model a o tomto zpracování si v tomto díle povíme jen to, že se jedná o standard vydaný konsorciem W3C, který nám umožňuje využít onu zmíněnou hierarchickou strukturu XML dokumentu. My si dnes v tomto díle povíme něco o přístupu druhém, kterým je zpracování XML dokumentu pomocí jednosměrného čtení, které nám přináší oproti mechanismu DOM o mnoho menší paměťové nároky, ale zase u něj nevyužíváme výhod hierarchie XML dokumentu. Jednosměrné čtení XML dokumentu K jednosměrnému čtení od začátku XML dokumentu až po jeho konec nám jsou v .NET frameworku k dispozici třídy, které jsou potomky abstraktní třídy XMLReader. Konkrétně se jedná o třídy XMLTextReader, XMLNodeReader a XMLValidatingReader. O posledních dvou vám v tomto díle sdělím pouze to, že XMLNodeReader je pro použití v kombinaci s modelem DOM a umožňuje projít pouze určitou část dokumentu a XMLValidatingReader slouží k validaci XML dokumentu oproti DTD (Document Type Definition – definice typu dokumentu) nebo oproti XSD (XML Schema Definition). Jak DTD tak XSD jsou způsoby, kterými určujeme jaké tagy, atributy atd. mohou nebo mají být na konkrétním místě v XML dokumentu použity. Použití třídy XMLTextReader XMLTextReader je asi nejpoužívanější implementace abstraktní třídy XMLReader. Tato třída dokáže procházet XML dokumentem, který je k parsování (zpracování XML) dodán ve formě streamu. Následující příklad ukazuje průchod XML dokumentem, kterým je náš ukázkový soubor obsahující informace o zaměstnancích, takže zpracovává pouze některé z mnoha typů uzlů v procházeném XML. /// <summary> /// Ukazka na pruchod jednoduchym XML dokumentem /// </summary> public class PrikladZamestnanci { public static void VypisXML() { XmlReader lReader = new XmlTextReader("C:\\Zamestnanci.xml"); while (lReader.Read()) { 160 //rozhodneme se podle typu uzlu jak zareagujeme switch(lReader.NodeType) { //pocatecni tag case XmlNodeType.Element : Console.Write("<{0}>",lReader.Name); break; //vnitrek tagu case XmlNodeType.Text : if (lReader.HasValue) { Console.Write(lReader.Value); } break; //konecny tag case XmlNodeType.EndElement : Console.WriteLine("</{0}>",lReader.Name); break; } } } } Jak můžete vidět, tak použití XMLReaderu je celkem jednoduché. Metoda Read nám indikuje, že ještě existují nějaké elementy k přečtení. Pomocí vlastnosti NodeType zjistíme jaký je aktuální typ uzlu, protože nám vrací výsledek typu XMLNodeType, jehož položky reprezentují všechny možné typy uzlů. Takže hodnota Element nám označuje, že jsme narazili na počáteční tag, hodnota Text, že jsme narazili na textový uzel, což je vlastně vnitřek uzlu a hodnota EndElement indikuje, že aktuální čtená část dokumentu je konečný tag. Pokud bychom se pokoušeli procházet chybně vytvořený XML dokument, tak by při volání metody Read byla vyhozena výjimka XMLException. Jak jsem psal, tak XMLTextReader zpracovává XML ve formě streamu. Konstruktorů této třídy je celá řada. Já jsem použil tu nejpohodlnější verzi, které pouze předáme cestu k souboru, což je ale ekvivalentní k použití konstruktoru očekávající instanci třídy System.IO.TextReader (přesnění jejího potomka). Takže jsem to mohl napsat i takto: XmlReader lReader = new XmlTextReader(new StreamReader("C:\\zamestnanci.xml")); Příští díl na tento naváže, takže bude opět o použití XML. Mimo jiné se například dozvíme jak do XML dokumentu zapisovat. Poznáváme C# a Microsoft.NET 43. díl – práce s XML Po minulém úvodu do jazyka XML a jeho využití v prostředí .NET na toto téma dnes navážeme a kromě rozšíření znalostí o možnostech čtení XML dokumentu pomocí XMLReaderu si ukážeme jak XML dokument programově vytvořit. Jak na atributy v XML V minulém díle jsme měli pro naše zkušební účely vytvořen jednoduchý XML dokument, který obsahoval data o zaměstnancích. Jeho obsah byl tvořen pouze elementy. Možné ovšem je údaje o konkrétním zaměstnanci uchovávat v podobě atributů elementů, takže například nějak takto: 161 /// <summary> /// Ukazka pouziti XMLReaderu pro precteni /// obsahu atributu jednotlivych elementu /// </summary> public class XMLReaderPriklady { public static void VypisZamestnance() { XmlReader lReader = new XmlTextReader("C:/zamestnanci.xml"); try { while(lReader.Read()) { switch(lReader.NodeType) { //zajima nas pouze start elementu case XmlNodeType.Element : //pokud je jmeno elementu zamestnanec a element ma nejake atributy if (lReader.Name.Equals("zamestnanec") && lReader.HasAttributes) { //vypiseme atributy Console.WriteLine("Jmeno : {0}", lReader["jmeno"]); Console.WriteLine("Prijmeni : {0}", lReader["prijmeni"]); Console.WriteLine("Pozice : {0}", lReader["pozice"]); Console.WriteLine(); } break; } } } finally { lReader.Close(); } } } V zásadě by pro vás po přečtení minulého dílu nemělo být v tomto příkladu moc novinek. Jednoduše se při zpracovávání počátečního tagu uzlu podíváme, zda-li jeho jméno není zamestnanec a pokud ano, tak se ještě pomocí metody HasAttributes ujistíme, jestli element obsahuje nějaké atributy. V případě, že jsou splněny obě výše uvedené podmínky, tak si pomocí vlastnosti Item instance třídy XMLReader, která je pro nás v jazyce C# implementována formou indexeru vyzvedneme hodnoty jednotlivých atributů. Toto ovšem není jediná cesta, kterou se můžeme vydat ke zjištění názvů a hodnot atributů jednotlivého elementu. XMLReader umožňuje i průchod všemi atributy právě čteného uzlu, jak ukazuje následující příklad. /// <summary> /// Ukazka prochazeni atributy cteneho uzlu /// </summary> public static void VypsaniAtributu() { XmlReader lReader = new XmlTextReader("C:/zamestnanci.xml"); try { while (lReader.Read()) { 162 //zjistime, jestli ma aktualni uzel nejake atributy if (lReader.HasAttributes) { //vypiseme nazev elementu Console.WriteLine("Element : {0}",lReader.Name); for(int i = 0; i < lReader.AttributeCount;i++) { //posuneme pozici readeru na atribut a vypiseme jej lReader.MoveToAttribute(i); Console.WriteLine("Nazev : {0} - Hodnota : {1}",lReader.Name, lReader.Value); } } } } finally { lReader.Close(); } } V příkladu zkontrolujeme jestli právě čtený uzel má nějaké a atributy a v případě, že ano, tak použijeme k průchodu cyklus for, kde využijeme instanční vlastnost AttributeCount, která nám vrátí počet přítomných atributů na uzlu. Průchod probíhá tím stylem, že použitím metody MoveToAttribute posuneme XMLReader z pozice uzlu na pozici konkrétního atributu, tudíž vlastnosti Name a Value budou po tomto posunutí vracet název a hodnotu atributu (před posunutím vlastnost Name vrací jméno čteného uzlu, jak je v příkladu ukázáno). Zápis do XML dokumentu Tak jako jsou implementace třídy XMLReader používány za účelem jednosměrného čtení XML dokumentu, tak jsou konkrétní implementace asbtraktní třídy XMLWriter použity k zápisu jednotlivých částí XML dokumentu a opět pouze jedním směrem od shora dolů. V základní knihovně třídy .NET frameworku se na rozdíl od třídy XMLReader pro XMLWriter nachází pouze její jediná implementace a to třída XMLTextWriter, pro kterou je zdrojem dat v jazyce XML nějaký datový proud. Třida XMLWriter nám tedy nabízí jednoduché a příjemné rozhraní pro vytváření XML Dokumentů. A jak může vypadat její použití? To můžete vidět níže. /// <summary> /// Priklad na vytvoreni XML dokumentu pomoci tridy XMLWriter /// </summary> public class XMLWriterPriklady { public static void ZapisZamestnance() { ArrayList lZamestnanci = new ArrayList(); lZamestnanci.Add(new Zamestanec("Jan","Novak","Analytik")); lZamestnanci.Add(new Zamestanec("Jiri","Joudek","Vyvojar")); XmlTextWriter lWriter = null; try { lWriter = new XmlTextWriter(new StreamWriter("C:/zamestnanci2.xml")); //zapiseme start dokumentu lWriter.WriteStartDocument(); //zapiseme korenovy element lWriter.WriteStartElement("zamestnanci"); //pro kazdeho zamestnance vytvorime element z odpovidajicimi atributy 163 foreach(Zamestanec lZamestnanec in lZamestnanci) { lWriter.WriteStartElement("zamestnanec"); lWriter.WriteAttributeString("jmeno",lZamestnanec.Jmeno); lWriter.WriteAttributeString("prijmeni",lZamestnanec.Prijmeni); lWriter.WriteAttributeString("pozice",lZamestnanec.Pozice); lWriter.WriteEndElement(); } //uzavreme dokument lWriter.WriteEndDocument(); } finally { lWriter.Close(); } } } Jak vidíte, tak použití je vcelku jednoduché. V příkladu je vytvářen úplně nový dokument, takže první co je po vytvoření instance třídy XMLTextWriter učiněno je zavolání metody WriteStartDocument, která zařídí zapsaní deklarace XML dokumentu (<?xml version="1.0" encoding="utf-8"?>). Po té pro každého zaměstnance z kolekce vytvoříme pomocí metody WriteStartElement počáteční tag elementu a před zapsáním jeho konce, což se provádí metodou WriteEndElement provedeme zapsání jednotlivých atributů elementu, které nám představují údaje o zaměstnanci. Atribut je do „rozepsaného“ uzlu zapsán za pomoci metody WriteAttributeString, jíž v přetížené verzi, která je v příkladu použita, předáme pouze název a hodnotu vytvářeného atributu. Poznáváme C# a Microsoft.NET 44. díl – zpracování XML pomocí DOM Náš dnešní díl bude opět pojednávat o zpracování XML v .NET. Nyní se však seznámíme s mnohem zajímavějším přístupem ke zpracování XML dokumentů, než tomu bylo doposud pomocí tříd XMLReader nebo XMLWriter. Document Object Model Během minulých dvou dílů jsme se seznámili se způsobem zpracování XML dat pomocí jednosměrných průchodů použitím implementací tříd XMLReader a XMLWriter. Tento způsob byl bezesporu zajímavý a vcelku příjemný, nicméně nám skrýval jednu z nejpodstatnějších vlastností, kterou XML dostalo při svém vzniku do vínku. Touto vlastností nemám na mysli nic jiného, než je schopnost uchovávat data ve formátu XML v hierarchické struktuře. Této vlastnosti využijeme při druhém přístupu ke zpracování XML v .NET frameworku, jež nám je k dispozici, kterým je DOM (Document Object Model), což je standard definovaný známým světovým konsorciem W3C. V případě použití tohoto přístupu nám vznikne v paměti počítače reprezentace struktury daného XML dokumentu, na rozdíl od přístupu dříve zmiňovaného (XMLReader/XMLWriter), který byl založen necachovaných streamech. To s sebou v důsledku nese možnost jak dokument číst nebo s ním manipulovat (což je primární účel DOMu) o mnoho pohodlnějším způsobem, než jedním směrem. Je nám tedy umožněno libovolně přistupovat k libovolné části XML dokumentu. Připomeňme si náš známý ukázkový dokument, který bude dále opět použit v implementačních příkladech. 164 <?xml version="1.0" encoding="utf-8"?> <zamestnanci> <zamestnanec jmeno="Jan" prijmeni="Novak" pozice="Analytik" /> <zamestnanec jmeno="Jiri" prijmeni="Joudek" pozice="Vyvojar" /> </zamestnanci> Použití DOM v prostředí .NET V případě použití DOMu je každá složka XML dokumentu představována instancí určité třídy. Základem těchto tříd je abstraktní třída XMLNode, která představuje jejich společného předka. Tak například instance třídy XMLDocument reprezentují vlastní XML dokument, XMLElement představuje element, XMLAttribute reprezentuje jeden atribut elementu, XMLDocumentType deklaraci dokumentu atd. Třída XMLDocument obsahuje důležité metody, jejichž užitím je nám umožněno vytvořit požadovanou reprezentaci hierarchické struktury XML dokumentu v paměti a to jak např. ze souboru tak z jednoduchého řetězce. Samozřejmě je také možné pomocí instancí této třídy vykonstruovanou hierarchii z paměti někam uložit (nejčastěji do souboru). Po několika řádkách teorie již následuje první příklad, který se snaží ukázat, jak je pomocí použití DOMu vypsat všechny zaměstnance z našeho dokumentu. /// <summary> /// Vypise vsechny zamestance z XML pomoci DOM /// </summary> public static void VypisZamestnance() { //vytvoreni instance dokumentu XmlDocument lDoc = new XmlDocument(); //nahrani XML dokumentu ze souboru do pameti lDoc.Load("C:/zamestnanci.xml"); //ziskani korenoveho elementu XmlNode lRoot = lDoc.DocumentElement; //pruchod vnorenymi uzly korenoveho elementu foreach(XmlNode lNode in lRoot.ChildNodes) { if (lNode.Name.Equals("zamestnanec")) { //ziskani atributu konkretniho elementu XmlAttributeCollection lAtts = lNode.Attributes; foreach(XmlAttribute lAttribute in lAtts) { //vypsani nazvu a hodnoty attributu Console.WriteLine("{0} - {1}", lAttribute.Name,lAttribute.Value); } } Console.WriteLine(); } } Jak vidíte, zmiňované nahrání dokumentu do paměti se provádí pomocí metody Load, jíž v přetížené verzi, která je použita v tomto příkladu, je pouze předána cesta k souboru, který obsahuje XML data. V případě, že bychom chtěli vytvořit reprezentaci hierarchické struktury dokumentu z obyčejného řetězce, použili bychom buď jinou přetíženou verzi této metody (jedna z verzí očekává typ TextReader tudíž můžeme použít instanci typu StringReader) nebo by přišla vhod metoda LoadXML. Získání kořenového elementu dokumentu, kterým je v případě našeho dokumentu element zamestanci použijeme instanční vlastnosti DocumentElement třídy 165 XMLDocument. Pokud chceme získat seznam dětských (vnořených) uzlů konkrétního uzlu, využijeme k tomu vlastnost ChildNodes, která je definována již na třídě XMLNode. Tato vlastnost nám vrací seznam reprezentovaný třídou XMLNodeList, která implementuje rozhraní IEnumerable, takže seznam můžeme pohodlně proházet oblíbeným cyklem forech. Kdybychom se chtěli ujistit, jestli nějaký uzel vůbec nějaké vnořené uzly má, můžeme tak učinit vlastností HasChildNodes. K získání seznamu atributů konkrétního elementu se používá vlastnost třídy XMLNode s názvem Attributes, jež navrací XMLAttributeCollection a ta obsahuje instance tříd XMLAttribute, reprezentující jednotlivé atributy uzlu. Díky vlastnostem této třídy zjistíme název (Name) a hodnotu (Value) konkrétního atributu. Modifikace XML pomocí DOM Jak jsem psal, tak je přístup pomocí DOM možný použít jak pro čtení XML dat, ale také hlavně pro jejich modifikaci. Jak na to demonstruje následující příklad. /// <summary> /// Prida zamestnance do XML dokumentu /// </summary> /// <param name="Zam">Zamestanec, ktery ma byt pridan</param> internal static void PridejZamestnance(Zamestanec Zam) { XmlDocument lDoc = new XmlDocument(); string lPath = "C:/zamestnanci.xml"; lDoc.Load(lPath); XmlNode lRoot = lDoc.DocumentElement; //vytvoreni elementu XmlElement lNewElem = lDoc.CreateElement("zamestnanec"); //vytvoreni atributu XmlAttribute lJmenoAttr = lDoc.CreateAttribute("jmeno"); XmlAttribute lPrijmeniAttr = lDoc.CreateAttribute("prijmeni"); XmlAttribute lPoziceAttr = lDoc.CreateAttribute("pozice"); //prirazeni hodnot atributu lJmenoAttr.Value = Zam.Jmeno; lPrijmeniAttr.Value = Zam.Prijmeni; lPoziceAttr.Value = Zam.Pozice; //pridani atributu k elementu lNewElem.Attributes.Append(lJmenoAttr); lNewElem.Attributes.Append(lPrijmeniAttr); lNewElem.Attributes.Append(lPoziceAttr); //pridani elementu do dokumentu lRoot.AppendChild(lNewElem); //ulozeni dokumentu lDoc.Save(lPath); Console.WriteLine("Zamestanec {0} {1} byl uspesne pridan",Zam.Jmeno,Zam.Prijmeni); } public static void PridaniZamestance() { Zamestanec lZam = new Zamestanec("Michal","Racek","Vyvojar"); PridejZamestnance(lZam); } Za vytváření instancí tříd, které reprezentují jednotlivé druhy uzlů v XML dokumentu je zodpovědná instance třídy XMLDocument. V příkladu můžeme vidět použití dvou vytvářecích metod, kterými jsou CreateElement pro vytvoření uzlu typu element a 166 metoda CreateAttribute pro vytvoření atributu. Po vytvoření instancí tříd XMLAttribute jim jsou nastaveny hodnoty instanční vlastností Value a následně jsou skrze vlastnost Attributes elementu přidány do kolekce XMLAttributeColection. Nový dětský uzel se vytváří užitím metody AppendChild, která je definována na třídě XMLNode. Po přidání nového elementu, můžeme reprezentaci dokumentu nechat uložit do XML a to provedeme metodou Save. Poznáváme C# a Microsoft.NET 45. díl – validace XML dokumentů V dnešním díle, který bude opět pojednávat o práci s XML dokumenty se zaměříme na jednu důležitou úlohu ve světě XML, kterou je validace XML dokumentů podle XML schémat a její aplikací ve světě .NET. XML schémata Zatím jsme se zabývali pouze čtením nebo tvorbou XML dokumentů nebo jejich částí a to jak pomocí jednosměrných čtení či zápisů (XMLReader/XMLWriter) tak s využitím modelu DOM. Tyto akce jsou jistě v řadě případů dostačující avšak můžeme potřebovat definovat i pravidla určující jakou strukturu mají nově vznikající XML dokumenty mít. K tomu nám ve světě XML v zásadě slouží dva způsoby. Prvním je DTD (Dokument Type Definition), což je starší způsob, který při kýžené definici nepoužívá syntaxi jazyka XML. Druhým způsobem jsou takzvaná XML Schémata (XSD - XML Schema Definition), která představují modernější způsob definice pravidel pro XML dokumenty a tento způsob je na rozdíl od DTD realizován pomocí XML syntaxe. My se v našem dnešním díle budeme zabývat použitím XML schémat, přesněji validací dokumentů podle nich v .NET frameworku. Zprvu si uvedeme jednoduché XML schéma a pro ty, kteří s touto věcí ještě nepřišli do styku uvedené schéma alespoň stručně popíšu. <?xml version="1.0" encoding="utf-8" ?> <xs:schema id="Zamestnanci" targetNamespace="http://tempuri.org/Zamestnanci.xsd" elementFormDefault="qualified" xmlns="http://tempuri.org/Zamestnanci.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="zamestnanci"> <xs:complexType> <xs:sequence maxOccurs="unbounded"> <xs:element name="zamestnanec"> <xs:complexType> <xs:sequence> <xs:element name="jmeno" type="xs:string"></xs:element> <xs:element name="prijmeni" type="xs:string"></xs:element> <xs:element name="vek" type="xs:byte"></xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> Elementy používané pro definici XML schémat se nacházeji ve XML jmenném prostoru http://www.w3.org/2001/XMLSchema, a v dokumentu je pro ně použit prefix xs. Pomocí atributu targetNamespace elementu schema určíme do jakého XML jmenného prostoru budou námi definované typy (elementy) patřit. Schéma předepisuje strukturu 167 dokumentu, ve které se bude nacházet element s názvem zamestnanci, což je v jazyce XML schémat komplexní typ a to znamená, že může obsahovat atributy a vnořené elementy narozdíl od takzvaných jednoduchých typů, které mohou obsahovat pouze text. A v elementu zamestanci se může nacházet libovolný počet elementů zamestnanec, což je definováno atributem maxOccurs, který má hodnotu unbounded. A konečně v elementu zamestnanec by se měli nacházet elementy jmeno, prijmeni a vek. Nyní ještě potřebujeme pokusný dokument, který je podle tohoto schématu vytvořen, aby jsme jej později v C# validovat (= zkontrolovat jestli splňuje strukturu danou schématem). <?xml version="1.0" encoding="utf-8" ?> <zamestnanci xmlns="http://tempuri.org/Zamestnanci.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tempuri.org/Zamestnanci.xsd Zamestnanci.xsd"> <zamestnanec> <jmeno>Petr</jmeno> <prijmeni>Pus</prijmeni> <vek>21</vek> </zamestnanec> <zamestnanec> <jmeno>Jirka</jmeno> <prijmeni>Mecner</prijmeni> <vek>23</vek> </zamestnanec> </zamestnanci> To, že je tento dokument podle našeho schématu, je uvedeno určením výchozího jmenného prostoru (bez prefixu) pomocí atributu xmlns a lokace asociovaného schématu se určuje pomocí atributu schemaLocation z XML jmenného prostoru http://www.w3.org/2001/XMLSchema-instance. Validace podle XSD v .NET frameworku Po letmém vysvětlení použití XML schémat, bych rád přešel k tomu nejdůležitějšímu v dnešním článku, čímž je pro mě vysvětlení způsobu validace XML dokumentu v .NET. Věřím, že si vzpomínáte na jeden z nedávných dílů tohoto seriálu, kde jsme se zabývali parsingem XML pomocí implementací třídy XMLReader. A právě jednou z těchto implementací této třídy je třída XMLValidatingReader, která nám poslouží pro naše dnešní záměry. Tak se konečně podívejme jak pomocí této třídy na to. public static void ValidaceDokumentu() { XmlTextReader lReader = new XmlTextReader("C:/vyvojari.xml"); XmlValidatingReader lValReader = new XmlValidatingReader(lReader); //urcime ze se provadi validace podle XML schematu lValReader.ValidationType = ValidationType.Schema; //priradime validacni handler lValReader.ValidationEventHandler += new ValidationEventHandler(lValReader_ValidationEventHandler); //provedeme vlastni validacni cteni while (lValReader.Read()){} Console.WriteLine("Validace XML dokumentu dokoncena"); } private static void lValReader_ValidationEventHandler(object sender, ValidationEventArgs e) { 168 //vypsani zavaznosti Console.WriteLine(e.Severity); //vypsani duvodu chyby Console.WriteLine(e.Message); } Po té co inicializujeme novou instanci třídy XMLValidationReader, specifikujeme pomocí instanční vlastnosti ValidationType jakým způsobem bude probíhat validace XML dokumentu. V našem případě je to samozřejmě podle XML schématu. Další důležitou věcí, kterou bysme měli při validování dokumentu učinit je definovat metodu, která bude instancí delegáta ValidationEventHandler. Tento handler slouží k získavání informací o validačních chybách. Pomocí metody Read, jednoduše projedeme celý dokument, což v případě použití třídy XMLValidationReader zajistí vlastní validaci. Poznámka : Schválně si vyzkoušejte změnit náš pokusný XML dokument do podoby, ve které nebude splňovat pravidla definovaná schématem (stačí změnit například jména tagů), abyste viděli, jaké validační chyby jsou hlášeny. V tomto případě, kdy je umístění schématu (schemaLocation) v XML dokumentu definováno, XMLValidatingReader při validaci schémá z této lokace použije. Ovšem v situaci, kdy lokace XML schématu, není v dokumentu určena, máme samozřejmě možnost v našem kódu určit schéma nebo přesněji seznam schémat , podle kterých bude daný dokument validován. public static void ValidaceDokumentUrcenimSchemat() { XmlValidatingReader lValReader = new XmlValidatingReader(new XmlTextReader("C:/vyvojari_bezXSD.xml")); lValReader.ValidationType = ValidationType.Schema; //urceni lokace XML schematu pro dany jmenny prostor lValReader.Schemas.Add("http://tempuri.org/Zamestnanci.xsd","C:/zamestnanc i.xsd"); lValReader.ValidationEventHandler += new ValidationEventHandler(lValReader_ValidationEventHandler); while(lValReader.Read()){} Console.WriteLine("Validace XML dokumentu dokoncena"); } Programové určení schémat je realizováno pomocí instanční vlastnosti Schemas třídy XMLValidatingReader. Tato vlastnost je typu XMLSchemaCollection, která pomocí svého rozhraní umožňuje definovat seznam XML schémat, která mají být pro validaci použita. Pomocí jedné z přetížených verzí metody Add je do kolekce přidáno naše schéma pro daný XML jmenný prostor. Poznáváme C# a Microsoft. NET 46. díl – použití XPath Další díl tohoto seriálu bude opět poletovat okolo problematiky XML. Dnes se podíváme na možné použití jazyka XPath v prostředí .NET. Jazyk XPath Tento jazyk byl jako standard vydán v roce 1999 světovým konsorciem W3C. Tento jazyk zjednodušeně řečeno, mimo jiné, umožňuje pomocí své dotazové syntaxe vyjádřit, která množina uzlů má být získána. Jinak řečeno, pomocí použití tohoto jazyku jsme schopni vybrat určitou množinu dat na základě specifických podmínek. 169 Jazyk XPath je kromě jiného jednou z největších „pomocníků“ při používání XSL transformací. Nechci se v tomto díle zabývat popisem tohoto ne zrovna triviálního jazyku a proto případné zájemce o učení se odkážu na jeden výborný tutoriál, který naleznete na adrese http://www.w3schools.com/xpath/. Užití XPath v .NET Pro naše ukázkové příklady budeme opět používat náš známý XML dokument obsahující data o zaměstnancích. <?xml version="1.0" encoding="utf-8" ?> <zamestnanci> <zamestnanec jmeno="Michal" prijmeni="Racek" pozice="Vyvojar"/> <zamestnanec jmeno="Jan" prijmeni="Novak" pozice="Analytik"/> <zamestnanec jmeno="Jiri" prijmeni="Joudek" pozice="Vyvojar"/> </zamestnanci> Zkusme se zamyslet nad situací, kdy chceme najít nějakého konkrétního zaměstnance, tedy například podle příjmení. Bez použití jazyku XPath s použitím přístupu přes DOM bychom takovýto požadavek splnili možná podobným způsobem jako je ten následující. /// <summary> /// Vyhleda uzel zamestnance podle prijmeni /// </summary> /// <param name="Prijmeni">Prijmeni hledaneho zamestnance</param> internal static void HledaniZamestnanceBezXPath(string Prijmeni) { //ziskani cesty odkud je aplikace spustena string lPath = AppDomain.CurrentDomain.BaseDirectory + "zamestnanci.xml"; XmlDocument lDoc = new XmlDocument(); lDoc.Load(lPath); XmlNode lRootElem = lDoc.DocumentElement; foreach(XmlNode lNode in lRootElem) { if (lNode.Name.Equals("zamestnanec")) { foreach (XmlAttribute lAttribute in lNode.Attributes) { if (lAttribute.Name.Equals("prijmeni") && lAttribute.Value.Equals(Prijmeni)) { Console.WriteLine("Hledany zamestanec : {0} {1} - {2}", lNode.Attributes["jmeno"].Value, lNode.Attributes["prijmeni"].Value, lNode.Attributes["pozice"].Value); } } } } } public static void HledaniBezXPath() { HledaniZamestnanceBezXPath("Joudek"); } Tento příklad samozřejmě bude funkční, ale je poněkud složitý a navíc takovýto přístup k vyhledávání uzlů je v případě, kdy jsou hledané uzly závislé na složité hierarchii rodičovských uzlů, také dost náchylné k chybám. Zkusme tento požadavek naimplementovat nějak elegantněji. Samozřejmě mám na mysli s použitím XPath. 170 public static void HledaniXPath() { HledaniZamestnanceXPath("Joudek"); } /// <summary> /// Vyhleda uzel zamestnance podle prijmeni pouzitim XPath /// </summary> /// <param name="Prijmeni">Prijmeni hledaneho zamestnance</param> internal static void HledaniZamestnanceXPath(string Prijmeni) { string lPath = AppDomain.CurrentDomain.BaseDirectory + "zamestnanci.xml"; XmlDocument lDoc = new XmlDocument(); lDoc.Load(lPath); //sestaveni XPath dotazu string lXPathQuery = "descendant::zamestnanec[@prijmeni=`" + Prijmeni + "`]"; //vyvolani XPath dotazu XmlNodeList lResult = lDoc.SelectNodes(lXPathQuery); if (lResult.Count > 0) { XmlNode lResultNode = lResult[0]; Console.WriteLine("Hledany zamestanec : {0} {1} - {2}", lResultNode.Attributes["jmeno"].Value, lResultNode.Attributes["prijmeni"].Value, lResultNode.Attributes["pozice"].Value); } } Po sestavení XPath dotazu, k jeho spuštění a získání výsledku v podobě XMLNodeListu, použijeme metodu SelectNodes, která je definována již na předkovi třídy XMLDocument, tedy na třídě XMLNode. Po té již jednoduše procházíme výsledný list uzlů. Rozhraní XPath a třída XPathNavigator Kromě výše zmíněného způsobu spuštění XPath dotazu, existuje ještě jedna cesta, která je představována použitím nějaké implementace rozhraní IXPathNavigable. Toto rozhraní je implementováno také třídou XMLNode. Rozhraní předepisuje jednu jedinou metodu, kterou je CreateNavigator. Tato metoda vrací odkaz na instanci třídy XPathNavigator, která umožňuje číst data pomocí kurzorového modelu (posuny kurzoru na určité části dat). Následující příklad ukazuje jak na to pomocí užití výše zmíněné třídy. /// <summary> /// Ukazka pouziti dotazu XPath pomoci tridy XPathNavigator /// </summary> public class XPathNavigatorExam { /// <summary> /// Vyhleda uzel zamestnance podle pozice pouzitim XPath /// </summary> /// <param name="Pozice">Pozice hledanych zamestnancu</param> internal static void HledaniZamestnance(string Pozice) { string lPath = AppDomain.CurrentDomain.BaseDirectory + "zamestnanci.xml"; XmlDocument lDoc = new XmlDocument(); lDoc.Load(lPath); XPathNavigator lNavigator = lDoc.CreateNavigator(); string lXPathQuery = "descendant::zamestnanec[@pozice=`" + Pozice + 171 "`]"; //zkompilujeme XPath dotaz XPathExpression lCompiledQuery = lNavigator.Compile(lXPathQuery); //provedeme dotaz XPathNodeIterator lIterator = lNavigator.Select(lCompiledQuery); //zobrazime vysledky while(lIterator.MoveNext()) { //vytvorime klon navigatoru XPathNavigator lClonedNavigator = lIterator.Current.Clone(); //posun na prvni uzel lClonedNavigator.MoveToFirstChild(); //posun na atribut jmeno lClonedNavigator.MoveToAttribute("jmeno",""); string lJmeno = lClonedNavigator.Value; //posun na dalsi atribut, kterym je prijmeni lClonedNavigator.MoveToNextAttribute(); string lPrijmeni = lClonedNavigator.Value; Console.WriteLine("{0} {1}", lJmeno, lPrijmeni); } } public static void NajdiZamestnance() { HledaniZamestnance("Vyvojar"); } } Po získání odkazu na instanci třídy XPathNavigator, můžeme XPath dotaz zkompilovat pomocí jeho metody Compile, jejíž návratová hodnota je typu XPathExpression, která slouží právě k reprezentaci kompilovaných výrazů jazyku XPath. Po zavolání metody Select na instanci třídy XPathNavigator získáme odkaz na instanci třídy XPathNodeIterator, který následně použijeme k průchodu vybranými uzly. Při každé iteraci listem, vytvoříme klon instance třídy XPathNavigator a to z důvodu abychom posouvali kurzor na speciální instanci. Po té na tomto klonu voláme metody (MoveToFirstChild, MoveToAttribute, MoveToNextAttribute) k zajištění posunu kurzoru na námi chtěné pozice. Obsah kurzoru si na dané pozici přečteme skrze instanční vlastnost Value třídy XPathNavigator. Poznáváme C# a Microsoft.NET 47. díl – použití XSL transformací Tento díl, který je pravděpodobně posledním dílem, ve kterém se budeme zabývat použitím technologií, které souvisí se zpracováním XML v .NET frameworku, bude pojednávat o aplikaci výkonné transformační technologie XSLT. XSL transformace? O co jde? Nejprve se velmi krátce zmíním o tom, jakou technologii to vlastně v tomto díle budeme aplikovat. V případě, že se budete chtít o této zajímavé technologii dozvědět něco více nebo se ji naučit , pokud ji ještě neovldádáte, tak na českém webu jistě naleznete spoustu kvalitních informaci o ní.Technologie XSLT neboli XSL Transformations je standardem světoznámého konsorcia W3C a je částí jazyka XSL (eXtensible Stylesheet language), což je původem stylovací jazyk založený na XML. Pomocí XSLT jsme schopni uskutečnit transformaci XML dokumentu na jiný XML dokument, nebo i dokument zcela jiného formátu (velmi často to bývá HTML). Jsme také schopni vynechat některé elementy nebo jejich atributy, ale naopak jsme shopni jak 172 elementy tak ostatní uzly do výsledného proudu proudícího z XSLT procesoru přidat. Při definici XSLT šablon se pro výběr částí dokumentu ke zpracování využívá jazyku XPath. Pro naše ukázkové příklady v tomto díle bude, stejně jako v těch předchozích, použit XML dokument s údaji o zaměstnanci. <?xml version="1.0" encoding="utf-8" ?> <zamestnanci> <zamestnanec jmeno="Michal" prijmeni="Racek" pozice="Vyvojar"/> <zamestnanec jmeno="Jan" prijmeni="Novak" pozice="Analytik"/> <zamestnanec jmeno="Jiri" prijmeni="Joudek" pozice="Vyvojar"/> </zamestnanci> Pro tento XML dokument jsem vytvořil jednoduchou ukázkovou šablonu pro XSLT, která vypadá následovně : <?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" /> <!-- korenovy uzel --> <xsl:template match="/"> <xsl:element name="HTML"> <xsl:element name="BODY"> <xsl:element name="HEAD"> <xsl:element name="TITLE"> XSLT V .NET - PRIKLADY ZIVE </xsl:element> </xsl:element> <!-- aplikace sablon na vnitrek uzlu --> <xsl:apply-templates/> </xsl:element> </xsl:element> </xsl:template> <!-- element zamestnanci --> <xsl:template match="zamestnanci"> <xsl:element name="H1"> Seznam zamestnancu : </xsl:element> <xsl:element name="UL"> <xsl:apply-templates/> </xsl:element> </xsl:template> <!-- element zamestnanec --> <xsl:template match="zamestnanec"> <xsl:element name="LI"> <!-- vypsani hodnot atributu elementu --> <xsl:value-of select="@prijmeni"/> <!-- vypsani entity  _u32 ?--> <xsl:text disable-output-escaping="yes">&nbsp;</xsl:text> <xsl:value-of select="@jmeno"/> - <xsl:value-of select="@pozice"/> </xsl:element> </xsl:template> </xsl:stylesheet> Pro ty co XSLT dnes vidí poprvé se alespoň pokusím vysvětlit co má daná šablona se vstupním XML dokumentem provádět. Uvedená šablona má za úkol převést naše data o zaměstancích do HTML stránky zobrazitelné v browseru. XSLT šablony jsou zpracovávány 173 od shora dolů, takže při výskytu kořenového uzlu XML dokumentu se zajistí zapsání úvodních HTML tagů (HTML, HEAD...) do výsledného datového proudu. Pokud se narazí na element s názvem zamestnanci je vygenerován nadpis H1 po té ještě element UL. Pomocí elementu <xsl:apply-templates> zajistíme použití šablon na vnitřek elementu zamestnanci. A to zajistí, že pro každý element zamestanec je vygenerován element LI. Použití XSLT v .NET frameworku Stěžejní třídou pro provádění XSL transformací je v základní knihovně tříd .NET frameworku třída XslTransform, kterou nalezneme ve jmenném prostoru System.Xml.Xsl. Zdroje pro transformaci jsou představovány instancemi tříd XMLDocument, XMLDataDocument nebo XPathDocument, které všechny implementují rozhraní IXPathNavigable. Jako nejvhodnější pro klasické transformace se jeví použití instancí třídy XPathDocument, které by mělo zajišťovat nejvyšší výkon. Následující příklad již obsahuje C# kód, který transformaci pomocí zmíněné třídy XSLTransform provádí. /// <summary> /// Ukazka jednoduche transformace, kde je zdroj XPathDocument /// </summary> public static void DoTransform() { //definice cest string lDocPath = AppDomain.CurrentDomain.BaseDirectory + "zamestnanci.xml"; string lStyleSheetPath = AppDomain.CurrentDomain.BaseDirectory + "ZamestnanciToHtml.xslt"; string lOutputPath = AppDomain.CurrentDomain.BaseDirectory + "zamestanci.html"; //nahrani dokumentu XPathDocument lDoc = new XPathDocument(lDocPath); XslTransform lTransform = new XslTransform(); StreamWriter lWriter = new StreamWriter(lOutputPath); try { //nahrani sablony lTransform.Load(lStyleSheetPath); //provedeni transformace lTransform.Transform(lDoc, null, lWriter, null); Console.WriteLine("Transformace byla dokoncena"); } catch(Exception ex) { Console.WriteLine("Nastala vyjimka {0} ", ex.ToString()); } } Jak vidíte, tak provedení transformace není v .NET vůbec složité. Po vytvoření instance třídy XPathDocument na základě našeho cvičného XML dokumentu stačí vytvořit instanci třídy XslTransform pomocí její instanční metody Load nahrát požadovanou šablonu. A k provedení samostatné transformace použijeme jednu z mnoha přetížených verzí metody Transform, které v našem případě předáme pouze zdroj ve formě instance třídy XPathDocument a výstupní proud zaobalený do instance třídy StreamWriter. 174 Po úspěšném provedení transformace byste měli dostat takovýto výsledný HTML dokument: <HTML> <BODY> <HEAD> <META http-equiv="Content-Type" content="text/html; charset=utf-8"> <TITLE> XSLT V .NET - PRIKLADY ZIVE </TITLE> </HEAD> <H1> Seznam zamestnancu : </H1> <UL> <LI>Racek Michal - Vyvojar</LI> <LI>Novak Jan - Analytik</LI> <LI>Joudek Jiri - Vyvojar</LI> </UL> </BODY> </HTML> Předávání parametrů transformačnímu jádru Možnosti transformačního jádra .NET frameworku jdou ještě dál. Kromě takovýchto jednoduchých transformací je možné provádět i transformace při nichž jsou používány uživatelské parametry, které definujeme v našem kódu. K dosažení tohoto, jistě v mnohým situacích užitečného, výsledku je potřeba použít třídu XsltArgumentList, která slouží k uchovávání uživatelských parametrů a jejich následné předání transformačnímu jádru .NET. Pro ukázku funkčnosti této vlastnosti jsem vytvořil následující demostranční XSLT šablonu: <?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" /> <xsl:param name="sorting"/> <!--koren uzlu --> <xsl:template match="/"> <xsl:element name="HTML"> <xsl:element name="HEAD"> <xsl:element name="TITLE"> XSLT V .NET - PRIKLADY ZIVE </xsl:element> </xsl:element> <xsl:element name="BODY"> <xsl:element name="H1"> Serazeny seznam zamestnancu : </xsl:element> <xsl:element name="UL"> <!-- pruchod vsemi uzly zamestnanec obsazenymi uzlu zamestnanci -> <xsl:for-each select="zamestnanci/zamestnanec"> <!-- jako parametr pro razeni bude pouzit vnejsi parametr --> <xsl:sort select="@*[local-name() = $sorting]"/> <xsl:element name="LI"> <xsl:value-of select="@prijmeni"/> <!-- vypsani entity  _u32 ?--> <xsl:text disable-output-escaping="yes">&nbsp;</xsl:text> <xsl:value-of select="@jmeno"/> 175 - <xsl:value-of select="@pozice"/> </xsl:element> </xsl:for-each> </xsl:element> </xsl:element> </xsl:element> </xsl:template> </xsl:stylesheet> Uživatelský parametr je v šabloně definován ve vrchní části pomocí elementu <xsl:param> a jeho hodnota je použita v XPath výrazu pro určení parametru pro řazeni (<xsl:sort select="@*[local-name() = $sorting]"/>). Jak jsem již napsal, tak k předání parametrů slouží třída XslArgumentList a kód, který ji využívá následuje. /// <summary> /// Ukazka jednoduche transformace pri ktere jsou transformacnimu /// jadru predany parametry /// </summary> public static void DoTransformWithArgs() { string lDocPath = AppDomain.CurrentDomain.BaseDirectory + "zamestnanci.xml"; string lStyleSheetPath = AppDomain.CurrentDomain.BaseDirectory + "ZamestnanciToHtmlWithSorting.xslt"; string lOutputPath = AppDomain.CurrentDomain.BaseDirectory + "zamestanci.html"; XPathDocument lDoc = new XPathDocument(lDocPath); XslTransform lTransform = new XslTransform(); StreamWriter lWriter = new StreamWriter(lOutputPath); //vytvoreni argumentu pro transformacni jadro XsltArgumentList lArgs = new XsltArgumentList(); lArgs.AddParam("sorting","","prijmeni"); try { lTransform.Load(lStyleSheetPath); //predni argumentu metode lTransform.Transform(lDoc ,lArgs ,lWriter, null); Console.WriteLine("Transformace byla dokoncena"); } catch(Exception ex) { Console.WriteLine("Nastala vyjimka {0} ", ex.ToString()); } } Kód se od přechozího v podstatě změnil pouze o použití zminěné třídy XslArgumentList, do jejíž instance jsou pomoci metody AddParam vloženy parametry pro transformační jádro. Instance této třídy je následně předána metodě Transform třídy XslTransform. Výsledné HTML by mělo být stejně jako v předchozím případě, s tím rozdílem, že záznamy budou seřazeny (v našem příkladu podle příjmení). Poznáváme C# a Microsoft. NET 48. díl – úvod do použití vláken Více vláknové aplikace jsou tématem, do kterého se čtenáře budu snažit uvést v tomto díle seriálu. Objasníme si co to vlastně vlákna jsou, proč se používají a uvedeme si jejich základní použití v prostředí .NET. 176 Vlákna? O co jde? Žijeme v době, kdy současné operační systémy umožňují běh dvou a více programů najednou a uživatelům na to dnes asi nic zvláštního nepřipadne. A tak vás možná při psaní vašich programů napadlo, zda – li je možné učinit něco takového, co dělá operační systém s programy i v naší aplikaci s jednotlivými operacemi. Odpověď zní jednoznačně ANO, něčeho takového jsme opravdu schopni v naší aplikaci dosáhnout. Rozdělit některé operace prováděné v rámci běžícího programu, je možné pomocí takzvaných „light-weight“ procesů, které jsou v terminologii prostředí .NET, stejně jako v jazyku Java, označeny jako vlákna. Použitím vláken je nám tedy umožněno vytvořit aplikaci, kde budou některé operace prováděny paralelně. Ono použité slovo paralelně není ve většině případů pravdivé. Ve skutečnosti je to tak, že je čas procesoru jednotlivým vláknům, které provádějí konkrétní operace, předáván střídavě, ale s takovou rychlostí, že v lidském měřítku se prováděné operace skutečně jeví jako paralelní. Ale abychom byli terminologicky správní měli bychom pro provádění operací užitím vláken místo slova paralelní použít slovo pseudoparalelní. Samozřejmě, ne všechny aplikace potřebují pro svůj běh použití vláken. Spíše je to naopak. Velká část programů je schopna reagovat operacemi na podněty uživatele dostatečně rychle a proto by použití vláken nepřineslo užitek. Užitek nám vlákna přinesou v opačných situacích, tedy v situacích, kdy jsou prováděné operace natolik výpočetně (a tím pádem i časově pro procesor) náročné, kdy by uživatel musel na dokončení operace čekat. V takovýchto případech se použijí vlákna například tak, že jedno vlákno vykonává operace s daty získanými uživatelem a druhé vlákno je určeno pro získávání dalších uživatelských vstupů do aplikace. Toto je samozřejmě jeden z mnoha případů smysluplného použití techniky vláken. Jedním z dalších známých příkladů na použití vláken je čekání na uživatelské vstupy, kdy uživatel zadává nějaké vstupy, což je z pohledu počítače velmi pomalá operace (čas procesoru není zdaleka plně využit) a mezitím další vlákno tyto vstupy již nějakým způsobem zpracovává (známá kontrola pravopisu z textových procesorů). A v případě komplexnějších aplikací je někdy spuštěno hned několik vláken zajišťujících specifické operace a tyto vlákna spolu kooperují. Použití vláken v .NET Po úvodním vysvětlení smyslu vlákno a jeho smyslu při programování nyní přejdu k tvorbě více vláknových aplikací v prostředí .NET. Pokud budeme chtít učinit naši aplikaci více vlákenní, budeme k tomu potřebovat elementy z jmenného prostoru System.Threading. Nejzajímavější třídou ze zmíněného jmenné prostoru je třída Thread, jejíž instance slouží k reprezentaci jednotlivých vláken. Zdrojový kód, který následuje, ukazuje použití příklad, ve kterém je vytvořeno a spuštěno další vlákno. /// <summary> /// Uvodni priklad na vyuziti vlaken /// </summary> public static void SpustPriklad() { //pomoci delegata asociujeme metodu, ktera bude vlaknem spustena ThreadStart lStartInfo = new ThreadStart(Bez); //vytvoreni dalsiho vlakna 177 Thread lThread = new Thread(lStartInfo); //spusteni dalsiho vlakna lThread.Start(); //zavolani metody z aktualniho vlakna Bez(); } static void Bez() { for (char znak = `a`; znak <= `z`; znak++) { Console.Write(znak); } } K určení metody, která bude spuštěna v nově vytvářeném vlákně slouží delegát ThreadStart, který předepisuje metodu bez návratové hodnoty a bez vstupních formálních parametrů. Instanci tohoto delegáta, který je v případě našeho příkladu asociován s metodou Bez následně předáme konstruktoru třídy Thread. Tím, že vytvoříme instanci třídy Thread, jsme ještě nové vlákno nespustili. K tomu, abychom vlákno spustili, slouží instanční metoda Start. Hned po spuštění nově vytvořeného vlákna, zavoláme metodu Bez i z aktuálního vlákna, tedy z vlákna, které je jako jediné spuštěno s vytvořením aplikační domény našeho programu. Po dokončení běhu tohoto ukázkového programu byste měli vidět výstup podobný tomuto: aabcdefghijklmnopqrstuvwxyzbcdefghijklmnopqrstuvwxyz Jak můžeme vidět, tak střídání výstupů jednotlivých vláken není ani zdaleka pravidelný. Je tomu tak z důvodu, že je bez jakékoli synchronizace vláken přistupováno ke sdílenému prostředku, kterým je v našem případě výstupní proud systémové konzole. Celkem zajímavého výsledku dosáhneme modifikací prvního příkladu do následující podoby: /// <summary> /// Priklad na pouziti vlaken s vypisem jmen /// jednotlivych vlaken /// </summary> public static void SpustPrikladSNazvy() { //pomoci delegata asociujeme metodu, ktera bude vlaknem spustena ThreadStart lStartInfo = new ThreadStart(Bez); //aktualnimu vlaknu priradime jmeno Thread.CurrentThread.Name = "V1"; //vytvoreni dalsiho vlakna Thread lThread = new Thread(lStartInfo); //prirazeni jmena novemu vlaknu lThread.Name = "V2"; //spusteni dalsiho vlakna lThread.Start(); //zavolani metody z aktualniho vlakna Bez(); } static void BezSNazvy() { for (char znak = `a`; znak <= `z`; znak++) { 178 } Console.Write("[" + Thread.CurrentThread.Name + "]:" + znak); } V příkladu jsme využili instanční vlastnost třídy Thread, kterou je vlastnost Name, představující název konkrétního vlákna, takže nyní obdržíme výstup podobný tomuto: [V1]:a[V1]:b[V1]:c[V1]:d[V1]:e[V2]:a[V2]:b[V2]:c[V2]:d[V2]:e[V2]:f[V2]:g[V2 ]:h[V 2]:i[V2]:j[V2]:k[V2]:l[V2]:m[V2]:n[V2]:o[V2]:p[V2]:q[V2]:r[V2]:s[V2]:t[V2]: u[V2] :v[V2]:w[V2]:x[V2]:y[V2]:z[V1]:f[V1]:g[V1]:h[V1]:i[V1]:j[V1]:k[V1]:l[V1]:m[ V1]:n [V1]:o[V1]:p[V1]:q[V1]:r[V1]:s[V1]:t[V1]:u[V1]:v[V1]:w[V1]:x[V1]:y[V1]:z Zajímavé je, že i přestože bylo spuštění druhého vlákna zavoláno dříve než volání metody BezSNazvem z aktuálního vlákna, tak několik prvních výstupů není zapříčiněno druhým vláknem, ale naopak vláknem aktuálním, tedy dříve než je druhé vlákno plně spuštěno. V dalším díle se budeme opět zabývat problematikou vláken a to konkrétné možností předat vláknu nějaké parametry. Poznáváme C# a Microsoft .NET – 49. díl – použití vláken V minulém díle seriálu jsme se seznámili s pojmem vlákno a více vláknová aplikace. V dnešním díle na tyto znalosti navážu a pokusím se je rozšířit o další informace týkající se použití vláken v .NET. Zjištění informací o vláknu Z předchozího dílu víme, že jednotlivá vlákna, která jsou spouštěna v prostředí CLR, náležící do určité aplikační domény, jsou nám programátorům přístupna skrze instance třídy System.Threading.Thread. Také bychom si měli pamatovat, že k instanci představující aktuální vlákno, v němž se nacházíme, přistoupíme užitím statické vlastnosti CurrentThread na této třídě. Po použití této vlastnosti jsme schopni na odkazu na vlákno zjistit pomocí jeho instančních vlastností různé informace. Použití pár těchto vlastností je ukázáno v tomto zdrojovém kódu. /// <summary> /// Ukazka pouziti nekterych vlastnosti tridy Thread pro zjisteni /// uzitecnych informaci. /// </summary> public class ThreadInfoExam { internal static void Run() { Console.WriteLine("ID vlakna : {0}", Thread.CurrentThread.GetHashCode()); Console.WriteLine("Aplikacni domena do ktere vlakno nalezi : {0}", Thread.GetDomain().FriendlyName); Console.WriteLine("ID aplikacni domeny do ktere vlakno nalezi : {0}", Thread.GetDomainID()); Console.WriteLine("Priorita vlakna : {0}", Thread.CurrentThread.Priority); Console.WriteLine("Stav vlakna : {0} ", Thread.CurrentThread.ThreadState); 179 Console.WriteLine("Kultura vlakna : {0}", Thread.CurrentThread.CurrentCulture.DisplayName); } } Po spuštění příkladu byste na svých obrazovkách měli vidět výstup velmi podobný tomu následujícímu. ID vlakna : 2 Aplikacni domena do ktere vlakno nalezi : PrikladyZive49.exe ID aplikacni domeny do ktere vlakno nalezi : 1 Priorita vlakna : Normal Stav vlakna : Running Kultura vlakna : Czech (Czech Republic) Takže jak můžeme vidět, jsme schopni zjistit, jaké je ID vlákna i v jaké aplikační doméně je dané vlákno spuštěno. Kromě toho jsme schopni zjistit jaká je priorita vlákna, čehož jsme dosáhli přečtením vlastnosti Priority. Tato informace je důležitá pro část operačního systému nesoucí jméno plánovač. Plánovač přiřazuje jednotlivým vláknům časové úseky procesoru a právě na prioritě vlákna závisí to, jak velké tyto úseky budou. Každé nově spuštěné vlákno v běhovém prostředí CLR je spuštěno s prioritou Normal, což je jedna z hodnot výčtu ThreadPriority. Vlastnost Priority na instanci vlákna můžeme nejen číst, ale také v případě potřeby nastavit na jednu z hodnot zmíněného výčtu. Každé vlákno se nachází v nějakém stavu, který jsme schopní zjistit přečtením vlastnosti ThreadState. Tato vlastnost vrací hodnotu patřící do stejnojmenného výčtového typu tedy také ThreadState. Běžící vlákno má hodnotu této vlastnosti Running. Uspání vlákna V případě potřeby můžeme zajistit, že na nějakou dobu nebude určitému vláknu přidělen plánovačem žádný časový úsek procesoru, jinak se také tomuto jevu říká uspání. Pokud chceme tak učinit, můžeme použít statické metody Sleep, která zapříčiní toto uspání na aktuálním vláknu na námi specifikovanou dobu. Kdybychom chtěli vlákno tímto způsobem zablokovat na nekonečnou dobu, předali bychom metodě jako parametr konstantu System.Threading.Timeout.Infinite. /// <summary> /// Ukazka pouziti metody Sleep tridy Thread /// </summary> public class SleepingExam { internal static void Run() { Console.WriteLine("Vlakno si dava pauzu"); Console.WriteLine(DateTime.Now.ToString()); //zablokujeme nase vlakno na dobu 3 vterin Thread.Sleep(3000); Console.WriteLine("Vlakno opet bezi"); Console.WriteLine(DateTime.Now.ToString()); } } 180 Vlákna s parametry V mnoha případech při vývoji našich aplikací zajisté budeme potřebovat nejen spustit nějakou operaci ve vlastním vláknu, ale také budeme potřebovat předat k provádění této operace nějaké parametry. Dobrá, jakým způsobem, ale tuto situaci vyřešíme, když delegát ThreadStart, předepisuje metodu o nulovém počtu formálních parametrů? Řešení takovýchto situací se nám nabízí v podobě vytvoření třídy, jejíž instanční datové členy budou uchovávat hodnoty parametrů a také bude obsahovat metodu asociovatelnou s instancí delegáta ThreadStart, tedy metodu s návratovým typem void a žádnými formálními parametry. Následující zdrojový kód demonstruje použití tohoto přístupu v praxi. /// <summary> /// Trida jejiz instancni metoda bude spustena v jinem vlakne /// v ukazkovem prikladu. /// </summary> public class ThreadWithParams { //datove cleny, ktere, ktere budou vyuzity //metodou spoustenou asynchronne (separatnim vlaknem) private int offset; private int length; public ThreadWithParams(int Offset, int Length) { this.offset = Offset; this.length = Length; } /// <summary> /// Metoda vykonavajici operace, ktere je potreba ucinit /// v separatnim vlaknu /// </summary> public void PerformActions() { Console.WriteLine("Pouzivam parametry : Offset = {0}, Delka = {1}", offset, length); for (int i = offset; i <= offset + length; i++) { Console.WriteLine("Vystup vlakna {0} : {1}", Thread.CurrentThread.Name, i); } } } Tato třída obsahuje instanční metodu PerformActions, která bude asociována s delegátem ThreadStart. Metoda využívá hodnot dvou instančních parametrů offset a length, které určují způsob běhu for cyklu, který metoda obsahuje. Takže k předání hodnot, které budou představovat hodnoty těchto parametrů, musíme před spuštěním vlákna vytvořit instanci této třídy pomocí implementovaného parametrického konstruktoru a po té asociovat zmíněnou metodu PerformActions s instancí delegáta ThreadStart. Ono by to dříve než po vytvoření instance ani nešlo , jelikož je metoda instanční a z důvodu existence jediné verze konstruktoru, kterou je verze s parametry je zajištěno, že hodnoty kýženým parametrům budou přiřazeny vždy před spuštěním metody PerformActions ve vlastním vláknu. Vlastní spuštění vlákna tedy proběhne následujícím způsobem. 181 /// <summary> /// Priklad na spusteni vlakna s predanim parametru /// </summary> public class ThreadWithParamsExam { internal static void Run() { //vytvoreni instance tridy, ktera obsahuje metodu //spoustenou vlaknem ThreadWithParams lWorker = new ThreadWithParams(5,100); //asociace instancni metody s instanci delegata Thread lSecondThread = new Thread(new ThreadStart(lWorker.PerformActions)); lSecondThread.Name = "Druhe vlakno"; Console.WriteLine("Spoustim druhe vlakno.."); //spusteni vlakna lSecondThread.Start(); } } Na vašich obrazovkách byste měli vidět takovýto výstup. Spoustim druhe vlakno.. Pouzivam parametry : Offset = 5, Delka = 100 Vystup vlakna Druhe vlakno : 5 Vystup vlakna Druhe vlakno : 6 Vystup vlakna Druhe vlakno : 7 Vystup vlakna Druhe vlakno : 8 Vystup vlakna Druhe vlakno : 9 Vystup vlakna Druhe vlakno : 10 … Vystup vlakna Druhe vlakno : 100 Vystup vlakna Druhe vlakno : 101 Vystup vlakna Druhe vlakno : 102 Vystup vlakna Druhe vlakno : 103 Vystup vlakna Druhe vlakno : 104 Vystup vlakna Druhe vlakno : 105 Následující díl seriálu bude opět pojednávat o práci s vlákny. Poznáváme C# a Microsoft.NET – 50. díl – použití vláken II. Spojení vláken a hlavně jejich synchronizace jsou témata, jejichž vysvětlení jsem si položil za cíl v padesátém díle mého seriálu o programování v prostředí .NET. Hlavně se tedy dnes dozvíme, na jaké problémy můžeme při použití vláken narazit a také jak těmto problémům předejít. Spojení vláken Předtím, než se začnu zaobírat hlavním tématem tohoto dílu, zmíním se o jedné užitečné možnosti při použití vláken. Tuto možnost využijeme v případě, kdy chceme jedno vlákno nechat čekat na dokončení práce vlákna jiného. Představme si následující příklad. public class WorkerThread { public void PerformActions() { for (int i = 0; i < 1000; i++) 182 { Console.WriteLine("Vystup vlakna {0} : {1}", Thread.CurrentThread.Name, i); } } } public class ThreadsJoiningExam { public static void RunExamWithoutJoining() { Thread.CurrentThread.Name = "Vychozi vlakno"; WorkerThread lWorker = new WorkerThread(); ThreadStart lWorkerStartInfo = new ThreadStart(lWorker.PerformActions); Thread lSecondThread = new Thread(lWorkerStartInfo); lSecondThread.Name = "Druhe vlakno"; lSecondThread.Start(); System.Windows.Forms.MessageBox.Show("Zdravi vas vlakno " + Thread.CurrentThread.Name); } } Metoda PerformActions třídy WorkerThread je vykonávána asynchronně a její tělo zajišťuje vypsaní čísel od 0 do 999, což aplikaci chviličku zabere. Po spuštění vlákna používající výše zmíněnou metodu se z výchozího vlákna pomocí metody Show třídy MessageBox zobrazí informační box s nějakým textem. Pokud si tento příklad spustíme běh programu bude vypadat tak, že zatímco jsou na konzoli vypisována čísla, zobrazí se také informační box. Samozřejmě vlákna se za tímto účelem, tedy k realizaci (pseudo) paralelních operací používají, ale v určitých případech potřebujeme počkat na dokončení práce nějakého vlákna, abychom mohli pokračovat v práci prováděné vláknem jiným. To se hodí v situacích, kdy je jedno vlákno závislé na dokončení operací prováděných jiným vláknem. Za tímto účelem využijeme metody Join třídy Thread. Takto vypadá náš příklad po menší úpravě, která je představována právě použitím zmíněné metody. public static void RunExamWithJoining() { Thread.CurrentThread.Name = "Vychozi vlakno"; WorkerThread lWorker = new WorkerThread(); ThreadStart lWorkerStartInfo = new ThreadStart(lWorker.PerformActions); Thread lSecondThread = new Thread(lWorkerStartInfo); lSecondThread.Name = "Druhe vlakno"; lSecondThread.Start(); //zablokujeme vychozi (prvni) vlakno dokud druhe vlakno neskonci svou cinnost lSecondThread.Join(); System.Windows.Forms.MessageBox.Show("Zdravi vas vlakno " + Thread.CurrentThread.Name); } Nyní bude chování programu po spuštění jiné a to v tom, že vlákno, ze kterého je metoda Join zavolána počká na dokončení vlákna provádějící výpis čísel na konzoli. Synchronizace vláken Doposud jsme si ukazovali pouze jednoduché vícevláknové aplikace. Jednoduché myslím v tom, že si nikdy nekonkurovali v přístupu k nějakým zdrojům. Pokud takovouto aplikaci 183 vytvoříme, můžeme narazit na nepříjemné problémy. Na ukázku toho, jaké problémy můžou při používání takovýchto aplikací nastat, jsem vytvořil jednoduchý příklad, který by to měl jasně demonstrovat. /// <summary> /// Priklad ukazujici mozne problemy pristupu nekolika vlaken /// ke sdilenym datum /// </summary> public class UnsychronizedExam { static int count; static int count2; public static void Run() { Thread lCheckerThread = new Thread(new ThreadStart(DoCheck)); lCheckerThread.Start(); //vytvoreni deseti vlaken, ktera budou //asynchronne vykonavat metodu DoSomeWork for (int i = 0; i < 10; i++) { Thread lThread = new Thread(new ThreadStart(DoSomeWork)); lThread.Start(); } //ujistime se, ze vsechna vlakna skonci Thread.Sleep(500); //ukonceni cinnosti kontrolujiciho vlakna lCheckerThread.Abort(); Console.WriteLine("Hotovo"); } internal static void DoSomeWork() { for (int i = 0; i < 5; i++) { count++; //simulace nejake casove narocne cinnosti Thread.Sleep(10); count2++; } } /// <summary> /// Metoda, ktera kontroluje jestli jsou hodnoty obout dat. clenu /// shodne. Je volana vlaknem pro provadeni teto kontroly /// </summary> internal static void DoCheck() { while(true) { if (count != count2) { Console.WriteLine("Hodnoty nejsou synchronizovany !!"); } } } } V příkladu je vytvořeno deset vláken, které spouští metodu DoSomeWork. Metoda DoSomeWork zařizuje inkrementaci obou statických datových členů s tím, že mezi 184 jednotlivými inkrementacemi je pomocí metody Sleep simulována nějaká činnost. Řekněme, že pro korektní běh aplikace je nezbytné, aby hodnoty obou statických datových členů byly shodné. Pro kontrolu této podmínky je v příkladu metoda DoCheck, která je spouštěna separátním vláknem, aby byla kontrola prováděna „neustále“. V případě, že je zjištěno, že hodnoty datových členů nesouhlasí je o tom vypsána hláška na systémovou konzoli. A pokud si tento příklad zkusíte spustit, uvidíte, že tato hláška se vám na obrazovce objeví hned několikrát. Proč je tomu tak? Jelikož úsek kódu, který je obsažen v metodě DoSomeWork, je vykonáván asynchronně a také je v metodě DoCheck asynchronně vykonáváno čtení datových členů se kterými kód v metodě DoSomeWork manipuluje, nastanou situace, ve kterých, když jsou hodnoty kontrolovány (DoCheck), tak je jiné vlákno právě „uprostřed“ provádění změn hodnot datových členů (DoSomeWork). Jak takovýmto nekontrolovaným přístupům vlákny k jednotlivým hodnotám zabránit? To zařídíme pomocí takzvané synchronizace. Každý objekt v prostředí .NET může mít svůj monitor, který umožňuje řídit přístup ke specifickým částem zdrojového kódu. Za účelem využití tohoto monitoru je nám k dispozici třídy System.Threading.Monitor a její metody. Pomocí metod této třídy jsme schopni uzamknout část zdrojového kódu a tím zabránit vykonání tohoto kódu jiným vláknem. Tím pádem jsme schopni zabránit situacím, podobným té z předchozího příkladu, kdy jsou hodnoty datových členů porovnávány ve chvíli, kdy jiné vlákno právě provádí změny jejich hodnot. Náprava tohoto problému by mohla vypadat následovně. /// <summary> /// Ukazka zakladniho pouziti tridy Monitor /// </summary> public class SynchronizingExam { private int count; private int count2; public static void Run() { SynchronizingExam lInstance = new SynchronizingExam(); Thread lCheckerThread = new Thread(new ThreadStart(lInstance.DoCheck)); lCheckerThread.Start(); //spustime deset vlaken a nechame je vykonavat cinnost //metody DoSomeWork for (int i = 0; i < 10; i++) { Thread lThread = new Thread(new ThreadStart(lInstance.DoSomeWork)); lThread.Start(); } //ujistime se, ze vsechna vlakna skonci svou praci Thread.Sleep(500); //ukoncime cinnost kontrolniho vlakna lCheckerThread.Abort(); Console.WriteLine("Hotovo"); } internal void DoSomeWork() { //zamkneme instanci Monitor.Enter(this); 185 count++; //simulace nejake casove narocne cinnosti Thread.Sleep(10); count2++; //odemkneme instanci Monitor.Exit(this); } internal void DoCheck() { while(true) { Monitor.Enter(this); if (count != count2) { Console.WriteLine("Hodnoty nejsou synchronizovany !!"); } Monitor.Exit(this); } } } Po spuštění tohoto příkladu byste zprávu o chybné synchronizaci hodnot již vidět neměli. Je tomu samozřejmě díky použití třídy Monitor a to tak, že v metodě DoSomeWork, která mění hodnoty datových členů, si hned na jejím začátku získáme zámek na aktuální instanci, což zařídíme pomocí metody Enter, jíž parametrem předáme, pro který objekt chceme zámek získat. Dokud zámek neuvolníme pomocí metody Exit, je zaručeno, že žádné jiné vlákno nezačne provádět kód metody DoSomeWork, poněvadž nebude schopno získat zámek pro instanci. Do té doby, než bude zámek pomocí metody Exit uvolněn, ostatní vlákna, chtějící vykonat synchronizovaný kód (ten mezi Enter a Exit), budou čekat ve frontě na uvolnění zámku objektu. Jelikož je synchronizován i kód metody DoCheck, nemůže se stát, že by byla splněna podmínka nerovnosti hodnot datových členů, protože zámek instance není nikdy v metodě DoSomeWork uvolněn dříve, než proběhne změna hodnoty obou dvou datových členů. Tudíž se nikdy nezačne provádět kód metody DoCheck v době kdy nějaké jiné vlákno pracuje s metodou DoSomeWork a to proto, že se vláknu volající metodu DoCheck nedostane zámku a bude muset čekat na jeho uvolnění. V příkladu ukazujícím synchronizaci jsem již učinil datové členy a metody instančními a to proto, abych mohl získávat a uvolňovat zámek na specifické instanci. Získání a uvolňování zámků je možné i v případě používaný statických členů. V těchto případech se doporučuje používat jako objekt pro použití zámku instance třídy System.Type, získaná pomocí klíčového slova typeof. Takže by se získávání zámku v našem příkladu změnilo do následující podoby. internal void DoSomeWork() { Monitor.Enter(typeof(SynchronizingExam)); count++; Thread.Sleep(10); count2++; Monitor.Exit(typeof(SynchronizingExam)); } Dalším možným objektem pro práci se zámky je datový člen pouze pro čtení (readonly). 186 Klíčové slovo lock V jazyku C# můžeme namísto volání metod Enter a Exit třídy Monitor použít klíčové slovo lock. Použití tohoto slova ve výsledku stejně znamená jeho přeložení na volání výše zmíněných metod v kombinaci, ale navíc s bloky try a finally. Takže následující kód: //ziskani zamku lock(this) { count++; Thread.Sleep(10); count2++; } //uvolneni zamku ..se vlastně přeloží do této podoby: Monitor.Enter(this); try { count++; Thread.Sleep(10); count2++; } finally { Monitor.Exit(this); } Takže pokud chcete pracovat se zámky objektů, je lepší pokud budete používat právě toto klíčové slovo jazyku C#, které vám ušetří psaní zdrojového kódu. A pokud se z nějakého důvodu rozhodnete používat místo slova lock volání metod třídy Monitor, vždy se ujistěte, jestli není vhodné použít bloky try a finally pro případ, že by kód v synchronizovaném bloku zapříčinil vyhození výjimky, poněvadž pokud by k tomu došlo, nebyl by zámek vůbec uvolněn. Takže myslím, že to často vhodné bude. Atomické operace Jedním z dalších problémů, které se mohou vyskytnout při používání více vláken v aplikaci je problém nazývaný „Race condition“ nebo také „Data race“. O co jde? Těmito slovy je označována situace, kdy výstup programu závisí na tom, které ze dvou nebo více vláken vykoná určitou část kódu dříve. To znamená, že pokud spustíme program vícekrát, dostaneme různé výsledky. Jedním z příkladů této situace je manipulace s hodnotou nějaké proměnné. Pokud se například rozhodneme inkrementovat hodnotu nějaké proměnné, běhové prostředí vnitřně provede tři operace. První operace je představována načtením hodnoty proměnné do registru, po té je hodnota v registru zvýšena a jako třetí operace je tato hodnota zapsána zpět do proměnné. Takže se v určitých situacích může stát, že před tím, než jedno vlákno, po provedení prvních dvou operací, stačí zapsat novou hodnotu do proměnné, tak jiné vlákno si zatím přečte starou hodnotu z proměnné, tu inkrementuje a hodnotu později zapsanou prvním vláknem přepíše tou svou. To ve výsledku znamená, že místo aby se hodnota inkrementovala například o hodnotu dvě, zvýší se pouze o hodnotu jedna. Jednu simulaci tohoto problému jsem vytvořil jako příklad, jehož zdrojový kód zde v článku neuvedu, ale všem je k dispozici v příkladech ke stažení. Jak takovémuto problému předejít? 187 Řešení tohoto problému představuje třída System.Threading.Interlocked, která je určena k provádění atomických operací. Jako atomické jsou zde prováděny operace jako zvýšení či snížení hodnoty proměnné. /// <summary> /// Ukazka pouziti tridy Interlocked /// </summary> public class InterlockedExam { private static int sharedInt; public static void Run() { Thread lSecondThread = new Thread(new ThreadStart(DoSomeWork)); lSecondThread.Start(); DoSomeWork(); lSecondThread.Join(); Console.WriteLine("Konecna hodnota je {0}", sharedInt); } internal static void DoSomeWork() { for(int i = 0; i < 5; i++) { //zvyseni hodnoty pomoci tridy Interlocked Interlocked.Increment(ref sharedInt); } } } Třída Interlocked mimo jiné nabízí metodu pro inkrementaci hodnoty proměnné a právě tato metoda, jak můžete vidět, je použita v ukázkovém příkladu, kde jí pomocí klíčového slova ref předáme odkaz na proměnnou, jejíž hodnota má být zvýšena jako atomická operace. Příští díl seriálu bude pojednávat opět o práci s vlákny a mimo jiné se podíváme na další využití třídy Monitor. Poznáváme C# a Microsoft.NET – 51.díl – použití vláken III. Po seznámení se s pojmem synchronizace vláken a s tím související třídou System.Threading.Monitor bych v dnešním díle rád představil další možné využití této třídy. Test na získání zámku objektu V minulém díle, ve kterém jsme se seznámili s třídou Monitor a jejími metodami Enter a Exit, některé z vás možná napadlo, jestli je nějakým způsobem možné zjistit, zda-li je možné získat exklusivní zámek toho čí onoho objektu. Pokud bychom chtěli tuto informaci získat a v případě, že je zámek k dispozici, tento zámek použít k synchronizaci použijeme k tomu metodu TryEnter třídy Monitor. /// <summary> /// Ukazka pouziti metody TryEnter tridy Monitor /// </summary> internal class TryEnterExam { //kolekce, ke ktere je vlakny pristupovano synchronizovane 188 private ArrayList elements; internal TryEnterExam() { elements = new ArrayList(); } private bool TryToAddElement(object Element) { //otestuje zda je mozne ziskat zamek objektu elements if (Monitor.TryEnter(elements)) { //pokud se podari ziskat zamek pridame element elements.Add(Element); return true; } else { return false; } } } V příkladu se zkouší získat zámek pro objekt kolekce za účelem přidání dalšího prvku. Pokud se podaří daný zámek získat, je další objekt do kolekce přidán, v opačném případě se do kolekce nic nepřidá a je vrácena hodnota false, aby volající byl informován o tom, že se přidání prvku zdařilo. V uvedeném zdrojovém kódu je použita nejjednodušší přetížená verze metody TryEnter , které je předán pouze objekt, pro který chceme zkusit zámek získat. Ovšem kromě této verze je nám k dispozici ještě jedna zajímavá verze této metody a ta kromě objektu přijímá ještě čas, po který má vlákno čekat na získání zámku. Takže pokud bychom řádek s voláním metody TryEnter změnili do této podoby, vlákno by zkusilo získat zámek objektu a pokud by se tak hned nepovedlo, počkalo by ještě jednu vteřinu, jestli nebude zámek uvolněn. private bool TryToAddElement(object Element) { /*otestuje zda je mozne ziskat zamek objektu elements, * pokud ne, bude se vterinu cekat, jestli nedojde k jeho * uvolneni*/ if (Monitor.TryEnter(elements, 1000)) { //pokud se podari ziskat zamek pridame element elements.Add(Element); return true; } else { return false; } } Notifikace vláken a čekání na zámek Zatím jsme se zabývali pouze tím, jak je možné získávat či uvolňovat zámky objektů a jejich uvolňováním, takže jsme v našich příkladech získali zámek, provedli požadované operace a po té zámek uvolnili. Podívejme se na následující zdrojový kód, který by již pro nás měl být lehce pochopitelný. 189 internal class NotPulsingExam { internal static void Run() { Thread.CurrentThread.Name = "Prvni vlakno"; Thread lSecond = new Thread(new ThreadStart(DoSomeWork)); lSecond.Name = "Druhe vlakno"; lSecond.Start(); DoSomeWork(); } internal static void DoSomeWork() { //ziskani zamku lock(typeof(NotPulsingExam)) { for (int i = 0; i < 3; i++) { Console.WriteLine("{0} - {1}", Thread.CurrentThread.Name, i); } } //uvolneni zamku } } Předpokládejme, že bychom chtěli ,aby se tato dvě vlákna ve výpisu na systémovou konzoli střídala po vypsání jednotlivého znaku. K tomu abychom tohoto dosáhli, potřebujeme po vypsání jednotlivého znaku probudit další vlákno, které čeká na uvolnění zámku objektu a také je potřeba, aby vlákno, které již vypsalo znak, uvolnilo držený zámek. Jak na to se snaží ukázat tento příklad. /// <summary> /// Ukazkovy priklad na notifikaci vlaken, pomoci metody Pulse /// tridy Monitor /// </summary> internal class PulsingExam { internal static void Run() { Thread.CurrentThread.Name = "Prvni vlakno"; PulsingExam lInstance = new PulsingExam(); Thread lSecond = new Thread(new ThreadStart( lInstance.DoSomeWork)); lSecond.Name = "Druhe vlakno"; lSecond.Start(); lInstance.DoSomeWork(); } internal void DoSomeWork() { lock(this) { for(int i = 0; i < 3; i++) { Console.WriteLine("{0} - {1}", Thread.CurrentThread.Name, i); Monitor.Pulse(this); //zabraneni blokaci zamku (deadlocku) if (i < 2) { Monitor.Wait(this); } 190 } } } } K tomu, abychom dosáhli výše zmíněného požadavku, použijeme metody Pulse a Wait třídy Monitor. Zavoláním metody Pulse totiž zařídíme, že je vzbuzeno další vlákno čekající na zámek objektu, jakmile jej aktuální vlákno uvolní. K tomu, aby aktuální vlákno uvolnilo držený zámek objektu slouží metoda Wait, která kromě operace uvolnění zámku zapříčiní i uspání aktuálního vlákna (bude čekat na opětovné uvolnění zámku vláknem jiným). Zámek držený aktuálním vláknem je nutné uvolnit, aby mohlo probuzené vlákno vstoupit do synchronizovaného bloku kódu. Možná se podivujete nad podmínku kontrolující hodnotu proměnné i, která je použita v metodě DoSomeWork. K tomu, abychom mohli problém, který by mohl vzniknout, lépe pochopit se zkusme podívat na následující příklad. /// <summary> /// Ukazka mozneho zpusobeni blokace zamku metodou Pulse. /// Spusteni tohoto prikladu zpusobi, ze program neskonci. /// </summary> internal class PulsingWithDeadlockExam { internal static void Run() { Thread.CurrentThread.Name = "Prvni vlakno"; PulsingWithDeadlockExam lInstance = new PulsingWithDeadlockExam(); Thread lSecond = new Thread(new ThreadStart( lInstance.DoSomeWork)); lSecond.Name = "Druhe vlakno"; lSecond.Start(); lInstance.DoSomeWork(); } internal void DoSomeWork() { lock(this) { for (int i = 0; i < 3; i++) { Console.WriteLine("{0} - {1}", Thread.CurrentThread.Name, i); //probuzeni dalsiho vlakna Monitor.Pulse(this); //vzdani se zamku k objektu Monitor.Wait(this); } Console.WriteLine("Vlakno {0} skoncilo svou cinnost", Thread.CurrentThread.Name); } } } Pokud si zkusíte spustit tento příklad, zjistíte, že je běh programu, po vypsání čísel na systémovou konzoli, zablokován. Proč je tomu tak? Je to z důvodu, že při běhu tohoto příkladu dojde ke vzniku situace zvané deadlock (zablokované zámky) a to protože dojde k situaci, ve které jedno vlákno čeká na probuzení jiným vláknem, což se ale nikdy nestane, protože vlákno, které by mělo zavolat probuzení je již ukončeno. Tomu je právě předcházeno použitím podmínky v předchozím příkladu, která zajistí, že pokud je prováděna poslední iterace práce vlákna, není již zavolána metoda Wait, aby 191 nedošlo k nechtěnému zablokování (vlákno již nepotřebuje být znovu probuzeno, protože má svou práci hotovou a může skončit). Dalším možným řešením tohoto problému je v určitých situacích použití přetížené verze metody Wait, které předáme čas, po který má čekat na probuzení jiným vláknem. Po vypršení tohoto času se totiž blokované vlákno přesune do aktivního stavu za účelem získaní zámku a pokračování v činnosti. A jelikož mu v našem příkladu v získání zámku již nebude jiné vlákno bránit (druhé vlákno již svou činnost skončilo), spuštění příkladu již neskončí zablokováním. Schválně si zkuste v příkladu ukazující vznik zablokování, změnit řádek s voláním metody Wait do této podoby. Monitor.Wait(this, 1000); Poznáváme C# a Microsoft. NET 52. díl – ThreadPool Dnešní díl navazuje na použití vláken, které je již několik dílů seriálů probíráno a bude se zabývat zajímavou cestou k jednodušší implementaci asynchronních operací prostředí .NET. Vlákna typu démon Jelikož se v tomto díle budu zaobírat použitím třídy ThreadPool, dozvíme se co jsou vlákna typu démon a jakým způsobem tyto vlákna vytvořit, protože to s využitím třídy ThreadPool úzce souvisí. Vlákna typu démon nejsou nic složitého ani nic nového, co by se poprvé objevilo s příchodem platformy .NET. Vlákna můžeme s určitého pohledu dělit na dva druhy, kde prvním jsou vlákna hlavní a druhým jsou právě vlákna typu démon. Hlavní vlákna zařizují hlavní operace aplikace, a pokud je alespoň jedno hlavní vlákno běžící, aplikace neskončí. Přesným opakem jsou vlákna typu démon, která jsou určena k provádění pomocných operací jakoby „na pozadí“ a tyto operace nejsou natolik důležité, aby kvůli nim nemohla aplikace skončit. To ve výsledku znamená, že pokud skončí poslední běžící hlavní vlákno, tak je běh aplikace ukončen bez ohledu na to, kolik ještě běží vláken typu démon. A jak takováto vlákna v prostředí .NET vytvořit, ukazuje následující zdrojový kód. /// <summary> /// Priklad na pouziti vlaken typu demon /// </summary> internal class BackgroundThreads { internal static void Run() { Thread lThread = new Thread(new ThreadStart(DaemonMethod)); //nastavime vlaknu, ze je demon lThread.IsBackground = true; lThread.Start(); System.Windows.Forms.MessageBox.Show("Pro skonceni behu hlavniho vlakna stisknete OK"); } static void DaemonMethod() { for (char ch = `a`; ch < `z`; ch++) { Console.WriteLine("DaemonMethod : " + ch); 192 } } Thread.Sleep(400); } Je to velmi snadné, protože ve skutečnosti využijeme pouze instanční vlastnost třídy Thread, kterou je IsBackground, které pokud nastavíme hodnotu true, tak s ní .NET runtime bude nakládat jako s vláknem typu démon. Takže pokud pokud je běh hlavního vlákna dokončen, tak bez ohledu na to jestti námi spuštěné druhé vláknoještě běží je aplikace ukončena. Použití třídy ThreadPool Během několika předchozích dílů jsme se zabývali vytvářením nových vláken, jejich spouštěním a v neposlední řadě, také jejich řízením. .NET framework nám umožňuje se velké části těchto operací nevěnovat a nechat ji na starost běhovému prostředí. Samozřejmě není vždy možné se těmto operacím nevěnovat, protože jistě narazíte na spoustu situací, ve kterých bude potřeba si řízení vláken obstarat způsobem, který jsem popisoval v předchozích dílech. Ale v případě jednoduchých situací, můžeme ponechat detaily na běhovém prostředí a směle využít třídu ThreadPool. Třída ThreadPool nám reprezentuje pool, nebo jinými slovy,“zásobník” vláken, které řídí běhové prostředí a my pomocí rozhraní této třídy běhovému prostředí pouze sdělujeme, že bychom chtěli určitou metodu zavolat asynchronně, jakmile to bude možné. Možné to bude v případě, že se v poolu nachází nějaké vlákno, které je volné, což znamená, že právě nezpracovává nějakou operaci. ThreadPool je jedinečný pro proces CLR, ve kterém jsou obsaženy aplikační domény a to mimo jiné umožňuje běhovému prostředí optimalizovat použití vláken z poolu jednotlivými .NET aplikacemi. To pro nás znamená, že v mnoha případech, kdy potřebujeme implementovat provádění asynchronních operací, představuje použití ThreadPoolu nejjednodušši a nejlepší cestu. Po teoretickém představení ThreadPoolu se nyní podívejme na jeho použití v praxi. /// <summary> /// Pouziti tridy ThreadPool pro snadnejsi praci s vlakny /// </summary> internal class ThreadPoolExample { internal static void Run() { WaitCallback lCallBack = new WaitCallback(ThreadMethod); //sdelime systemu, ze metodu asociovanou //s predanym delegatem ma spustit asynchronne ThreadPool.QueueUserWorkItem(lCallBack, "At zije ThreadPool :-)"); //vyckame, nez je systememem spusteno //druhe vlakno a provede svou praci Thread.Sleep(1000); Console.WriteLine("Hlavni vlakno konci svou cinnost"); } static void ThreadMethod(object data) { Console.WriteLine("Metoda ThreadMethod byla zavolana s daty : {0}", data); } } 193 Použití třídy ThreadPool pro spuštění metody asynchronně v jiném vláknu je velmi jednoduché, což je také jejím posláním (odstítnit od problematiky řízení vláken, aby se mohl vývojář věnovat implementaci aplikační logiky). K realizaci vyčlenení vlastního vlákna pro provedení operace, pouze využijeme metodu QueueUserWorkItem, která přijímá instanci delegáta WaitCallBack, který předepisuje rozhraní metody na návratový typ void a jeden vstupní parametr obecného typu object, jež se používá k předání dat metodě spuštěné vláknem. K předání dat do metody asociované s delegátem WaitCallBack použijeme druhý parametr metody QueueUserWorkItem. V případě našeho příkladu jsou metodě spouštěné v separátním vláknu předána data v podobě řetězce "At zije ThreadPool :-)". Možná se divíte, proč je v příkladu použita metoda Sleep pro počkání na provedení asynchronních operací vykonávaných metodou ThreadMethod. Je tomu tak z důvodu, že by byla aplikace ukončena dříve, než by běhové prostředí zařídilo vykonání metody ThreadMethod v novém vlákně a to protože jsou všechna vlákna spouštěná pomocí třídy ThreadPool typu démon, tudíž se na dokončení činnosti, kterou tato vlákna vykonávají, vůbec nečeká. Informace o thread poolu Jistě mnohé z vás napadlo, kolik je tedy vlastně v poolu běhového prostředí k dispozici vláken nebo jaký je maximální počet vláken, které mohou být v poolu. Odpověď na obě tyto otázky vám zodpoví metody GetAvailableThreads a GetMaxThreads, které jsou použity v následujícím příkladu. /// <summary> /// Ukazka zjisteni informaci o poctu vlaken ThreadPoolu /// </summary> internal class ThreadPoolInfo { internal static void Run() { int lAvailThreads; int lAvailIOThreads; int lMaxThreads; int lMaxIOThreads; ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadMethod)); //pockame nez runtime spusti vlakno Thread.Sleep(300); ThreadPool.GetAvailableThreads(out lAvailThreads,out lAvailIOThreads); ThreadPool.GetMaxThreads(out lMaxThreads, out lMaxIOThreads); Console.WriteLine("Pocet volnych vlaken : {0}, Pocet vlaken pro provadeni asynn. IO operaci : {1}", lAvailThreads, lAvailIOThreads); Console.WriteLine("Max. pocet vlaken : {0}, Max.pocet vlaken pro provadeni asynn. IO operaci : {1}", lMaxThreads, lMaxIOThreads); Thread.Sleep(1000); Console.WriteLine("Cinnost hlavniho vlakna skoncila"); } static void ThreadMethod(object data) { Console.WriteLine("Metoda ThreadMethod byla zavolana."); Thread.Sleep(1000); } } Nás hlavně zajímá první údaj vracený výstupním parametrem obou metod, který říká kolik je v danou chvíli k dispozici volných vláken respektive, jaký je maximální počet 194 vláken. Jelikož je v příkladu spuštěna jedna asychronní úloha, tak by se měl údaj o max. počtu vláken a počtu vláken k dipozici lišit. Poznáváme C# a Microsoft.NET – 53. díl – Timer a asynchronní delegáti Tento díl je poslední, který se zaobírá realizací asynchronních operací v prostředí .NET. Dozvíme se, co se skrývá pod pojmem asynchronní delegát a jak je možné jej použít. Kromě toho se podíváme jak je pomocí třídy Timer možné zařídit opakování nějaké operace v určitém čase. Asynchronní delegáti Co jsou delegáti už bychom měli nějakou dobu vědět. V rámci .NET ale ještě existuje speciální typ delegátů, který je určen ke spouštění asociovaných metod asynchronně a tento typ delegátů je příznačně označován jako asynchronní delegáti. Hlavní rozdíl v použití oproti nám již známým synchronním delegátům je zavolání delegáta. Pokud chceme, aby byl náš delegát spuštěn asynchronně, učiníme to zavoláním metody BeginInvoke na jeho instanci. Tím zapříčiníme, že běhové prostředí, místo toho, aby zavolala asociovanou metodu s instancí delegáta v tom samém vlákně, jak je tomu obvykle, spustí tuto metodu ve vlákně vlastním. Přesněji je použito vlákno poskytnuté komponentou ThreadPool, se kterou jsme se seznámili v minulém díle, to znamená, že metody asynchronních delegátů jsou spouštěny ve vláknech typu démon. Jelikož delegáti můžou (a také jsou tak často vytvářeni) vracet nějaký výsledek, určitě vás napadlo, jakým způsobem je možné si tento výsledek vyzvednout, když je metoda spuštěna v jiném vlákně. K tomu musíme pomocí delegáta AsyncCallback definovat metodu, která je zavolána po dokončení práce metody asociované s asynchronním delegátem. Následující příklad ukazuje použití asynchronního delegáta. /// <summary> /// Ukazka asynchroniho provadeni metod pomoci /// asynchronich delegatu /// </summary> public class AsynchronousDelegates { public delegate string Compute(int count); internal static void Run() { //vytvoreni instance delegate pro provadeni asynchronni operace Compute lCompute = new Compute(AsynMethod); //vytvoreni instance delegata pro zpracovani //vysledku asynchronni operace AsyncCallback lAsyncHandler = new AsyncCallback(ShowAsyncResult); //zavolani zpracovani metody asociovane s nasim //delegatem asynchronne lCompute.BeginInvoke(10, lAsyncHandler, "Dalsi data.."); Console.WriteLine("Asynchronni operace byla zapocata.."); } static void ShowAsyncResult(IAsyncResult result) { Thread.Sleep(1000); 195 //prevzeti vysledku asynchronni operace AsyncResult lAsResult = (AsyncResult) result; //ziskani reference na naseho delegata Compute lDelegate = (Compute)lAsResult.AsyncDelegate; //ziskani hodnoty, ktera byla predana jako treti //parametr pri volani delegata string lData = (string) lAsResult.AsyncState; //ziskani vysledku metody string lResult = lDelegate.EndInvoke(lAsResult); Console.WriteLine("Vysledek je : {0}, Predana data : {1}", lResult, lData); } static string AsynMethod(int count) { System.Text.StringBuilder lResult = new System.Text.StringBuilder(count); for(int i = 0; i < count; i++) { lResult.Append(i); } return lResult.ToString(); } } V příkladu jsem definoval typ delegáta Compute, který předepisuje rozhraní asociované metody na jeden vstupní parametr typu int a na návratovou hodnotu typu string. Klíčové metodě instance delegáta, která z něj učiní asynchronního delegáta, tj. metodě BeginInvoke, předáme kromě parametrů předepsaných delegátem, také referenci na instanci delegáta AsyncCallback a v případě potřeby také nějaká dodatečná data. Delegát AsyncCallback předepisuje metodu bez návratové hodnoty s jedním parametrem typu rozhraní IAsyncResult. V našem příkladu je z instancí tohoto delegáta asociována metoda ShowAsyncResult. Tato metoda je běhovým prostředím zavolána v době, kdy je práce metody asociované s asynchronním delegátem dokončena. Jelikož je tato metoda běhovým prostředí zavolána s implementací rozhraní IAsyncResult určenou pro použití s asynchronními delegáty, kterou je třída AsyncResult, provedeme přetypování vstupního parametru za účelem přístupu ke všem členům třídy AsyncResult. Po tomto přetypování jsme schopni pomocí instanční vlastnosti AsyncDelegate získat referenci na instanci asynchronního delegáta, jehož výsledek chceme zpracovat. Získání návratové hodnoty zařídíme zavoláním metody EndInvoke na získané instanci asynchronního delegáta. V případě, že jsme při volání metody BeginInvoke, pro spuštění asynchronního volání použili nějaká dodatečná data (poslední parametr metody), můžeme si je vyzvednout pomocí vlastnosti AsyncState. Poznámka: Volaní metody Thread.Sleep v metodě ShowAsyncResult není samozřejmě nutné a použil jsem ho jen na důkaz toho, že je volání prováděno asynchronně. Třída Timer Předtím, než v rámci tohoto seriálu definitivně uzavřu část věnovanou asynchronním operacím, si dovolím vás seznámit ještě s jedním zajímavým přístupem jak tyto operace realizovat. Možná se pří svém programování ve světe .NETu dostanete do situace, kdy potřebujete spustit nějakou asynchronní operaci, která bude, na rozdíl od těch, které jsem zatím popsal, v určitém časovém intervalu opakována. Hezkým příkladem této situace, je že ve vámi tvořeném emailovém klientovi, chcete v určeném intervalu kontrolovat jestli nepřišla nová pošta. Pokud se před nějakým takovýmto problémem 196 octnete, můžete směle využit třídy Timer ze známého jmenného prostoru System.Threading. A jak na to? Například tímto způsobem: /// <summary> /// Ukazka pouziti tridy Timer /// </summary> internal class TimerExam { internal static void PrintTime(object data) { Console.WriteLine("Aktualni cas je : {0}", DateTime.Now.ToString()); } internal static void RunPrintingTime() { TimerCallback lTimerMethod = new TimerCallback(PrintTime); Timer lTimer = new Timer(lTimerMethod, null, 0, 1000); } } Jak vidíte, tak základní použití této třídy je velmi snadné. Jediné co potřebujeme je vytvoření instance delegáta TimerCallback, kterou asociujeme s metodou, jež má být opakovaně volána. Pak už stačí jen vytvořit instanci třídy Timer a kromě delegáta předat čas za který má být opakované spouštění započato a také čas intervalu pro opakování. V našem případě tedy pouštíme bez jakékoli úvodní prodlevy metodu PrintTime a ta bude následně volána každou vteřinu. Delegát TimerCallback předepisuje metodu bez návratového typu a jeden parametr typu předka všech typů v .NET, tedy typu System.Object, který může posloužit k předání nějakých informací volané metodě. Následující příklad demonstruje použití tohoto parametru k uchování informaci o stavu časovače. /// <summary> /// Predstavuje stav timeru. /// </summary> internal class TimerState { private int count; private int endCount; private Timer timer; /// <summary> /// Urcuje kolikrat byla metoda timeru spustena /// </summary> internal int Count { get{return count;} set{count = value;} } /// <summary> /// Pocet kolikrat ma byt metoda timeru vykonana /// </summary> internal int EndCount { get{return endCount;} set{endCount = value;} } 197 } /// <summary> /// Odkaz na timer (bude mozne s nim manipulovat v timer metode) /// </summary> internal Timer Timer { get{return timer;} set{timer = value;} } internal class TimerWithStateExam { internal static void RunTimer() { TimerState lState = new TimerState(); //chceme, aby metoda byla vyvolana 5 krat lState.EndCount = 5; TimerCallback lTimerMethod = new TimerCallback(DoWork); //predame referenci na instanci tridy ThreadState Timer lTimer = new Timer(lTimerMethod, lState, 0, 1000); lState.Timer = lTimer; //dokud beh timeru ukoncen cekame while (lState.Timer != null) { Thread.Sleep(100); } Console.WriteLine("Priklad na timer konci.."); } internal static void DoWork(object state) { TimerState lState = (TimerState) state; lState.Count++; Console.WriteLine("Aktualni stav citace je : {0}", lState.Count); if (lState.Count == lState.EndCount) { Console.WriteLine("Ukoncuji beh timeru"); lState.Timer.Dispose(); lState.Timer = null; } } } K uchování stavu časovače je v příkladu definována třída TimerState, jejíž instance udržují informace o tom kolikrát už byla konkrétní metoda časovačem spuštěna (vlastnost Count), při kolikátém spuštění má být běh časovače ukončen (vlastnost EndCount) a k tomu, aby mohl být časovač v metodě ukončen je potřeba na něj mít referenci (Vlastnost Timer), této vlastnosti je využito i v metodě která Timer spouští (RunTimer) a pomocí ní zjišťuje jestli ještě Timer běží. Užitečnou metodou třídy Timer je metoda Change, kterou zapříčiníme restart Timeru po určeném čase (první parametr) a nastavíme mu interval pro opakování metody (druhý parametr). Takže pokud doplníme předchozí příklad o využití této metody, budeme schopní měnit časový interval pro volání metody DoWork. internal class TimerStateWithPeriod { ... /// <summary> 198 /// Cas pro opakovani /// </summary> internal int Period { get{return period;} set{period = value;} } ... } internal class TimerChangeExam { internal static void RunTimer() { TimerStateWithPeriod lState = new TimerStateWithPeriod(); lState.EndCount = 8; lState.Period = 1000; TimerCallback lTimerMethod = new TimerCallback(DoWork); Timer lTimer = new Timer(lTimerMethod, lState, 0, lState.Period); lState.Timer = lTimer; while (lState.Timer != null) { Thread.Sleep(100); } Console.WriteLine("Priklad na Timer konci"); } internal static void DoWork(object state) { TimerStateWithPeriod lState = (TimerStateWithPeriod) state; lState.Count++; Console.WriteLine("Aktualni stav citace je : {0}", lState.Count); if (lState.Count == lState.EndCount) { Console.WriteLine("Ukoncuji cinnost timeru.."); lState.Timer.Dispose(); lState.Timer = null; } else if ( (lState.Count % 3) == 0) { int lNewPeriod = lState.Period - 250; Console.WriteLine("Menim cas opakovani na : {0}", lNewPeriod); lState.Timer.Change(lState.Period, lNewPeriod); lState.Period = lNewPeriod; } } } V příkladu byla do třídy TimerState přidána vlastnost Period, která uchovává informaci o aktuálním intervalu pro opakování a tato vlastnost je spolu s metodou Change využita v metodě DoWork, která je spouštěna časovačem a to tak, že pokud je hodnota vlastnosti Count dělitelná třemi je zkrácen interval pro opakování volání metody. 199
Podobné dokumenty
Moduly a namespace
Kód v F#pu je rozložen do jmenných prostorů a modulů:
♣ jmenný prostor může obsahovat typy a moduly
♣ modul může obsahovat cokoliv, tj. typy, hodnoty, funkce a další moduly
Pokud kompilujete zdrojo...
SÍDLIŠTĚ MAMUTÍHO LIDU U MILOVIC POD
X. SEZONALITA GRAVETTSKÉHO SÍDLIŠTĚ V MILOVICÍCH
PODLE ANALÝZ PŘÍRŮSTKU ZUBNÍHO CEMENTU
X.l Metoda
X.2 Výsledky
X.3 Závěry
X. SEASONALITY OF THE MILOVICE GRAVETTIAN SITE BASED
ON DENTAL CEMENT INCR...
7. Statické prvky třídy
Obrázek 7.1 Znázornění vztahu mezi instancemi třídy a statickým datovým atributem
Jak již bylo řečeno, statická proměnná existuje v paměti jen jednou a „vědí“ o ní všechny instance.
Zjednodušeně je...