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 &nbsp_u32 ?-->
<xsl:text disable-output-escaping="yes">&amp;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&nbsp;Michal
- Vyvojar</LI>
<LI>Novak&nbsp;Jan - Analytik</LI>
<LI>Joudek&nbsp;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 &nbsp_u32 ?-->
<xsl:text disable-output-escaping="yes">&amp;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

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

Více

SÍDLIŠTĚ MAMUTÍHO LIDU U MILOVIC POD

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

Více

7. Statické prvky třídy

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

Více