You are on page 1of 397

Wstp do programowania w jzyku C#

Autor: Adam Boduch


ISBN: 83-246-0523-1
Format: B5, stron: 392
Data wydania: 10/2006
Wstp do programowania w jzyku C#
Autor: Adam Boduch
Copyright Helion 2006

Wszelkie prawa zastrzeone. Rozpowszechnianie caoci lub fragmentw niniejszej publikacji


w jakiejkolwiek postaci, bez zgody autora jest zabronione. Wszystkie znaki wystpujce w
tekcie s zastrzeonymi znakami firmowymi bad towarowymi ich wacicieli. Autor oraz
Wydawnictwo Helion dooyli wszelkich stara, by zawarte w tej ksice informacje
byy kompletne i rzetelne. Nie bior jednak adnej odpowiedzialnoci ani za ich
wykorzystanie, ani zwizane z tym ewentualne naruszenie praw patentowych lub autorskich.
Autor oraz wydawnictwo Helion nie ponosz rwnie adnej odpowiedzialnoci za
ewentualne szkody wynike z wykorzystanie informacji
zawartych w ksice.

Wstp w wersji elektronicznej

Prace nad ksik Wstp do programowania w jzyku C# rozpoczem pod koniec roku 2005,
a zakoczyem pod w marcu 2006 roku. W tym samym roku ukazaa si ona nakadem
wydawnictwa Helion. W zamyle miaa ona stanowi wprowadzenie do jzyka C# i mam
nadzieje, e dziki niej - drogi
Czytelniku - zapoznasz si z podstawami jzyka C#.
Ksika zostaa oparta o najnowszy wwczas framework - 2.0, dlatego te informacje
w niej si znajdujce mog by nieaktualne. Mam nadzieje, e mimo wszystko publikacja ta
stanie si przydatna dla osb, ktre chc nauczy si programowania w jzyku C#.
Korzystajc z tego chciabym zaapelowa do Czytelnikw: jeeli znajdziecie w ksice jaki
bd. Czy to logiczny, czy literwk. Prosz - poprawcie go! Mechanizm witryny
4programmers.net umoliwia edycj kadego z tekstw. Nawet jeli to, co napisaem jest
ewidentnym kamstwem wynikajcym z mej niewiedzy. Nie boj si edytowa artykuw.
Wszystkie zmiany s zapisywane na serwerze.
Nie przecigajc. ycz miej lektury!

Przedmowa
Pamitam, jak wiele lat temu uczc si programowania, miaem trudnoci ze zrozumieniem
niektrych poj i zagadnie znajdujcych si w rnego rodzaju literaturze. Nie dlatego, e
autorzy byli sabymi programistami wrcz przeciwnie! Byli na tyle dowiadczeni, e
zapominali, jak to jest by pocztkujcym, i nie potrafili w umiejtny sposb przekaza
swojej wiedzy. Wiem rwnie, e wiele pocztkujcych osb ma problemy ze zrozumieniem
nieco trudniejszych aspektw programowania. Zamierzam wobec tego zaprezentowa swoj
wiedz w jak najprostszy sposb, tak jakbym to ja uczy si programowa.
Aby zacz pisa wasne programy, nie musisz posiada adnych nadzwyczajnych
umiejtnoci, potnego komputera czy matematycznego umysu. Chcc programowa w
jzyku C#, nie potrzebujesz take adnych drogich narzdzi kade oprogramowanie
potrzebne do tego, aby zacz programowa, moesz cign za darmo z internetu
.
Zakadam drogi Czytelniku, e jeste osob pocztkujc, dopiero raczkujc w tej tematyce.
By moe jeste osob, ktra miaa ju wczeniej styczno z programowaniem, lecz chce lub
musi nauczy si jzyka C#. Tak czy inaczej, w niniejszej ksice postaram si przedstawi
swoj wiedz tak, jakby nigdy wczeniej nie mia stycznoci z jzykiem C#. Sukcesywnie
bd prezentowa kolejne partie materiau oraz objania coraz trudniejsze pojcia. Mam
nadziej, e czytanie tej ksiki bdzie dla Ciebie przyjemnoci, a programowanie w jzyku
C# stanie si Twoj pasj i na dugo zapamitan przygod.

Rozdzia 1

Wprowadzenie
Wielu ludzi tak naprawd ma mylne wyobraenie o programowaniu. Brana ta przesiknita
jest wieloma mitami, jakby byo to zajcie niezwykle trudne, ktrym trudni si moe jedynie
geniusz matematyczny. Nic bardziej mylnego! Programowanie nie jest wcale trudne. W dobie
jzykw programowania tzw. wysokiego poziomu naley opanowa pewne podstawy, nauczy
si mylenia programistycznego, a poznawanie bardziej zaawansowanych technik jest
kwesti czasu i dobrych chci.
W tym rozdziale zaprezentuj kilka kluczowych poj, ktrymi posuguj si programici, a
ktrych Ty rwnie bdziesz uywa czsto i po prostu musisz je zna. Czy zastanawiae si
kiedy, jak to si dzieje, e aplikacje s uruchamiane, wykonuj jakie czynnoci? Jak to si
dzieje, e dziaaj? Jak powstaj programy? Jeeli nie znasz odpowiedzi na te pytania, ten
rozdzia jest dla Ciebie obowizkow lektur przed przystpieniem do faktycznej nauki
jzyka.

Czym jest programowanie


Czym jest programowanie komputerowe? Na pewno spotkae si z tym okreleniem nie raz
czy to podczas ogldania telewizji, czy przegldajc pras, czy te w yciu codziennym. Z
drugiej strony wiele osb w ogle nie wie, co to pojcie tak naprawd oznacza. Przecitny
czowiek moe nieco mylnie wyobraa sobie programist jako czowieka w grubych
okularach, ktry cae dnie spdza przed komputerem oboony fachow literatur. Na pewno
zdarzaj si i tacy zapalecy, by moe dlatego, e programowanie jest ciekawe! Tak, jest
ciekawe, ale z drugiej strony trzeba mie predyspozycje do takiej pracy. Jeeli kto zabiera si
za programowanie z przymusu, to pewnie nie bdzie mia z tego adnej przyjemnoci. Jednak
jeli samodzielnie napisany program (choby nie wiem jak by skromny) przyniesie swojemu
twrcy poczucie satysfakcji i dumy zapewne programowanie wcignie go jeszcze bardziej.
Programowanie, oglnie mwic, polega na rozwizywaniu pewnych problemw. Jest to
dziedzina zajmujca si tworzeniem oprogramowania. Niezym przykadem jest liczydo.
Liczydo suy do wspomagania czowieka w powaniejszych obliczeniach matematycznych.
Celem programisty jest napisanie programu, ktry wspomoe czowieka przy wykonywaniu
naprawd trudnych oblicze.
Programista, jeli chce zrealizowa zadanie, musi zapisa pewien algorytm, korzystajc z
cigu instrukcji rozpoznawalnych przez komputer. Wszelkie polecenia w danym jzyku
programowania s oczywicie wpisywane przy uyciu klawiatury.
Algorytm to zestaw instrukcji, czynnoci, ktre naley wykona, aby zrealizowa pewne
zadanie w skoczonej liczbie krokw.
Oczywicie nie mog to by dowolne polecenia i instrukcje, lecz takie, ktre mog by
rozpoznane przez kompilator. Jzyk programowania jest wic sposobem na przekazanie
maszynie pewnych polece do wykonania. Kompilator natomiast jest narzdziem
wspomagajcym programist w jego cikiej pracy. Tumaczy on polecenia jzyka

programowania na instrukcje procesora (szczegowo o tym powiem w dalszej czci


rozdziau). Mwic oglnie na podstawie tych instrukcji tworzy aplikacj wykonywaln.
Obecnie istnieje wiele narzdzi, ktre wspomagaj programist podczas tworzenia aplikacji.
Jak to wyglda w rodowisku MS Windows? Przede wszystkim potrzebujemy edytora tekstu.
Edytory wspomagajce cay proces programowania przewanie s podobne do typowych
edytorw tekstu systemu Windows programw z oknami, menu oraz ikonami. W owym
edytorze wpisujemy jakie polecenia dajmy na to w jzyku C#. Oczywicie nie mog to
by byle jakie polecenia, lecz takie, ktre bd zrozumiae dla kompilatora.
Kod rdowy, potocznie zwanym rdem, zazwyczaj jest tekstem zawierajcym rne
polecenia jzyka programowania. Kody rdowe s czytelne dla czowieka, programisty,
ktry moe je w dowolny sposb modyfikowa.
Kompilator moe by zintegrowany z edytorem, ale moe to te by zewntrzna aplikacja.

Mnogo jzykw programowania


Istniej setki rnych jzykw programowania (kilka z nich pokrtce omwi w dalszej
czci rozdziau), mniej lub bardziej popularnych. Niektre z nich prawie w ogle nie s
uywane, inne natomiast s tak znane i powszechne, e kady szanujcy si programista
powinien zna przynajmniej ich podstawy.
Mona zada sobie pytanie, po co istnieje tyle jzykw programowania. W kocu byoby
znacznie atwiej, gdyby istnia jeden jzyk, ktrego uywaliby wszyscy programici.
Ewolucja w wiecie komputerw wymagaa jednak nowych narzdzi prostszych,
szybszych i potniejszych. Ludzie z natury s leniwi i lubi upraszcza sobie ycie, tworzc
coraz lepsze rozwizania, w tym wypadku pozwalajce atwiej i szybciej programowa.
Generalnie jzyki programowania mona podzieli na kilka kategorii w zalenoci od stopnia
trudnoci, moliwoci czy przeznaczenia. Np. do tworzenia aplikacji na stronach WWW (tzw.
skryptw) bardziej nadaje si jzyk PHP, gdy jest prosty, szybki i zapewnia duo
moliwoci. Jzyk Perl, pomimo e rwnie si do tego nadaje, jest trudniejszy,
programowanie w nim nie jest tak szybkie i przyjemne, aczkolwiek mona go uy do innych
czynnoci. Jzyk C natomiast daje wiksz moliwo manipulacji komputerem, jest bardziej
zaawansowany. Nie wspominajc ju o Asemblerze, ktry daje nam niesamowite moliwoci,
lecz jest bardzo trudny. Jak widzisz, jzyk jest tylko narzdziem w rkach programisty, ktry
musi go odpowiednio wykorzysta zalenie od sytuacji.
Od razu uprzedz Twoje pytanie. Nie ma najlepszego jzyka programowania, uniwersalnego,
atwego, szybkiego i potnego. Zdolny programista wybierze odpowiedni jzyk (narzdzie)
w zalenoci od swoich potrzeb.

Edytory kodu

Edytorw kodu jest mnstwo. Kod moesz pisa nawet w najprostszej wersji Notatnika z
systemu Windows. Na rynku istnieje wiele aplikacji (zarwno darmowych, jak i
komercyjnych) wspomagajcych proces powstawania programw, udostpniajcych wygodne
funkcje przydatne przy edycji kodu.
Bardzo zaawansowany, umoliwiajcy pisanie i kompilowanie aplikacji w C# jest pakiet
Borland Developer Studio firmy Borland (www.borland.pl). Jest to produkt komercyjny
umoliwiajcy rwnie pisanie aplikacji w jzyku Delphi oraz C++.
Innym rodowiskiem popularnym wrd programistw C# jest narzdzie Visual Studio 2005.
Umoliwia programowanie i kompilacj aplikacji napisanych w jzykach C++, C# oraz J#.
To narzdzie rwnie jest komercyjne. Firma Microsoft udostpnia jednak wersje ubosze,
lecz darmowe, ktre mona cign za darmo ze strony firmy Microsoft
(http://msdn.microsoft.com/vstudio/express/default.aspx).
Na rysunku 1.1 zaprezentowano rodowisko Visual C# 2005 Express Edition, dziki ktremu
z powodzeniem mona pisa aplikacje w jzyku C#.

Rysunek 1.1. rodowisko Visual C# Express Edition

Kompilatory
Obowizkowym narzdziem programisty jest kompilator. To on umoliwia generowanie
aplikacji wykonywalnych na podstawie kodu jzyka. rodowisko Visual C# Express Edition
(ale rwnie Borland Developer Studio) ma wbudowan obsug kompilatora jzyka C#.
Napisaem wbudowan obsug, gdy kompilator C# jest dostarczany za darmo wraz ze
rodowiskiem .NET

Framework (o platformie .NET opowiem w kolejnym rozdziale). Jest to aplikacja o nazwie


csc.exe, wykonywalna z linii polece, dlatego te dosy niewygodna.
W zwizku z tym polecam rodowisko Visual C# Express Edition, ktre umoliwia proste
kompilowanie napisanej aplikacji. Jego take bd uywa w trakcie prezentowania
przykadw w niniejszej ksice.

Mity zwizane z programowaniem


W ludzkiej wiadomoci istnieje wiele mitw i przekama zwizanych z programowaniem.
Na samym pocztku nauki pragn wyjani pewne wtpliwoci, ktre mog zrodzi si w
Twojej gowie.

Nauka programowania zajmuje wiele lat


Kady z nas ma indywidualn zdolno do nauki. Zaley rwnie, kiedy mog powiedzie, e
znam dany jzyk. Czy umiejtno pisania prostych programw wystarcza, abym mg o
sobie powiedzie, e jestem programist? To rwnie sprawa indywidualna. Chocia
programuj wiele lat, wci si ucz, poznaj nowe techniki oraz technologie.
Jeeli mwimy o umiejtnoci pisania aplikacji na wasne potrzeby, to to nieprawda, e nauka
programowania zajmuje wiele lat. Obecne jzyki programowania s tak skonstruowane, aby
mona byo jak najszybciej wykorzysta ich moliwoci.
Jeeli mwimy o dowiadczeniu, ktre naley naby poprzez cige pisanie aplikacji, to tak
nauka trwa dugo. Dobrym programist zostaje si po kilku latach dowiadczenia.

Narzdzia programistyczne s drogie


Fakt s drogie, lecz istniej darmowe alternatywy. Np. bardzo popularny zestaw
kompilatorw GCC jest zupenie darmowy, rwnie do zastosowa komercyjnych. Niektre
firmy, takie jak Microsoft czy Borland, maj zaawansowane, drogie narzdzia
programistyczne, lecz udostpniaj rwnie ich ubosze wersje darmowe (nie do zastosowa
komercyjnych).

Wszystkie jzyki s podobne

To nieprawda. Sama idea programowania jest taka sama, w wielu jzykach stosuje si
podobne mechanizmy, ale stwierdzenie, e wszystkie s podobne, jest duym uoglnieniem.
Jeeli pytasz, czy atwiej nauczy si programowania, majc ju dowiadczenie w innym
jzyku, to tak jest to prawda. Szczeglnie jzyki Java, JavaScript, C, C++ oraz C# s do
siebie na tyle podobne, e nauka drugiego, kiedy znamy ju jeden, nie powinna stanowi
problemu.

Jzyki programowania
Jak ju powiedziaem, kod programu musi by zapisany w okrelonym jzyku
programowania (np. w tej ksice prezentuj programy w utworzone w C#). Istniej dziesitki
jzykw programowania bardziej lub mniej popularne oraz bardziej lub mniej
skomplikowane. Kady z tych jzykw wyrnia si inn skadni i innym sposobem
dziaania. W swojej karierze moesz si spotka z nastpujcymi jzykami:

Ada,
Asembler,
Algol,
Basic,
QBasic,
QuickBasic,
Turbo Basic,
Visual Basic,
C,
C#,
C++,
Clipper,
COBOL,
Fortran,
Haskel,
Java,
JavaScript,
LISP,
Logo,
PHP,
Pascal,
Perl,
Python,
Prolog,
Smalltalk,
Tcl.

To jest tylko przykadowa lista dostpnych jzykw programowania. Pokrtce omwi te z

nich, ktre miay duy wpyw na rozwj informatyki.

Asembler
Asembler jest niskopoziomowym jzykiem programowania zaprojektowanym w latach 1947
1952 przez Grace Hopper. Asembler by w gruncie rzeczy rewolucyjny uwalnia
programist od koniecznoci zapisywania programw w formie binarnej. W tym jzyku jedno
polecenie praktycznie odpowiada jednemu rozkazowi procesora. Obecnie Asembler jest
uwaany za trudny jzyk programowania (w porwnaniu np. z Delphi czy C#), ale wci
bywa uywany. Podstawow zalet Asemblera jest to, e daje niesamowite moliwoci w
manipulowaniu komputerem. Wikszo jzykw wysokiego poziomu pozwala na wczanie
wstawek Asemblera do swojego kodu. W praktyce wyglda to tak, e podczas pisania kodu
np. w Pascalu moemy do tworzonego programu doczy fragment kodu Asemblera.
W ten sposb wikszo obecnych programw nie jest pisana w caoci w Asemblerze (ze
wzgldu na do skomplikowan skadni), ale zawiera wstawki tego jzyka. Przykadowo,
system operacyjny Linux by pisany w jzyku C, mimo to nie obeszo si bez zastosowania
fragmentw kodu Asemblera.
W powyszym opisie znajduj si dwa pojcia jzyk wysokiego oraz niskiego poziomu. W
praktyce czynnikiem rozrniajcym jzyk poziomu wysokiego od niskiego jest zoono.
Przykadowo, Pascal jest jzykiem wysokiego poziomu, bo zawiera gotowe funkcje i
procedury (np. do wywietlania tekstu na ekranie suy komenda writeln) i dziki temu jest
atwy do nauczenia si. Rwnie projektowanie aplikacji w jzykach wysokiego poziomu jest
o wiele szybsze ni w Asemblerze. W Asemblerze w celu wywietlenia tekstu na ekranie nie
obejdziemy si bez obsugi odpowiedniego przerwania.
Pojcie przerwania zwizane jest z systemem operacyjnym, a konkretnie z procesorem.
Przerwanie powoduje wstrzymanie aktualnie wykonywanego programu i wykonanie przez
procesor kodu procedury obsugi. Jest to pojcie zwizane z bardziej zaawansowanymi
aspektami programowania, nie musisz teraz si tym przejmowa.

Fortran
Nazwa Fortran jest utworzona od angielskich sw Formula Translator. Fortran jest uwaany
za pierwszy jzyk wysokiego poziomu. Zosta zaprojektowany w latach 50. XX wieku przez
Johna Backusa i zesp IBM. Fortran jest wykorzystywany przede wszystkim do pisania
programw sucych do analizy numerycznej i do wykonywania skomplikowanych oblicze
naukowych. Jzyk ten zyska niesamowit popularno i jest uywany do dzisiaj (jako nowy,
ulepszony jzyk programowania Fortran 90, Fortran 95 oraz Fortran 2000).

Rok 1969 by rewolucyjny w informatyce. Wszystko za spraw dwch osb Kena


Thompsona oraz Dennisa Ritchiego. Zaczli oni prac nad nowym systemem operacyjnym
UNIX. Przy tej okazji Dennis Ritchie na bazie jzyka B opracowa jzyk C, ktry mia
uatwi pisanie tego systemu operacyjnego. W 1973 w jzyku C utworzono jdro systemu
UNIX, co byo wielkim sukcesem. Jzyka C uywano na pocztku jedynie w firmie Bell Labs
(gdzie powsta), jednak wkrtce sta si dominujcym jzykiem programowania,
wykorzystywanym na caym wiecie i tak jest do dzisiaj. Jzyk C charakteryzuje si
wysok wydajnoci, jest stosunkowo prosty (o wiele prostszy od Asemblera), a jednoczenie
niezwykle elastyczny.

C++
W roku 1983 Bjarne Stroustrup zmodyfikowa jzyk C, dodajc do niego nowe funkcje.
Przede wszystkim rewolucyjn zmian w porwnaniu z C jest moliwo programowania
obiektowego. To zagadnienie zostanie dokadniej omwione w dalszej czci ksiki. C++ (na
pocztku mia nosi nazw C z klasami) jest rwnie niezwykle popularnym jzykiem
programowania, czsto stosowanym przez programistw na caym wiecie. Jzyki C oraz
C++ s do siebie bardzo podobne, wic jeli znamy jeden z nich, atwiej przyjdzie nam nauka
drugiego.

Perl
Perl (ang. Practical Extraction and Report Language) jest interpretowanym jzykiem
programowania autorstwa Larrego Walla, bardzo czsto wykorzystywanym jako jzyk
skryptowy na stronach WWW. W zamyle autora jzyk Perl mia pomc w rozwizywaniu
trudnych zada oraz przyspiesza prac nad programem.
Perl nie jest kompilowanym jzykiem programowania. Oznacza to, e nie nastpuje
kompilacja programu, czyli przeksztacanie kodu napisanego przez programist do postaci
pliku wykonywalnego. Zamiast tego w komputerze musi by zainstalowany tzw. interpreter,
ktry bdzie odczytywa instrukcje jzyka i odpowiednio na nie reagowa.
Naturalnym rodowiskiem Perla jest system UNIX, aczkolwiek jzyk ten jest take dostpny
w wersjach przeznaczonych na inne platformy. Bardzo czsto jest wykorzystywany podczas
tworzenia skryptw CGI przeznaczonych dla stron WWW. Obecnie Perl jest wypierany przez
modszy i prostszy jzyk PHP, cho wietnie si nadaje si do pisania skryptw
manipulujcych plikami tekstowymi.
Obecnie najnowsz, wci rozwijan wersj tego jzyka jest Perl 6.
CGI, czyli Common Gateway Interface, jest obecny praktycznie od pocztku istnienia
internetu

i stron WWW. CGI umoliwia dynamiczne generowanie stron WWW, czyli w praktyce pisanie
tak rnych skryptw jak ksigi goci, fora dyskusyjne itp.

PHP
PHP (angielski akronim rekurencyjny PHP Hypertext Preprocessor) jest jzykiem
programowania, ktry w ostatnich latach robi oszaamiajc karier. Autorem tego jzyka jest
Rasmus Lerdorf, ktry napisa pierwsz jego wersj w roku 1995. PHP podobnie jak Perl
jest interpretowanym jzykiem programowania, wykorzystywanym gwnie do
generowania dynamicznych stron WWW. Jego zalet jest niezwyka prostota i jednoczenie
due moliwoci. Oglnie rzecz biorc, PHP jest jednym z najprostszych jzykw
programowania.
PHP jest darmowy, oparty na licencji GPL (ang. GNU Public License) i rozwijany w dalszym
cigu przez setki programistw z caego wiata.

Turbo Pascal
Pocztki Pascala jako jzyka programowania sigaj pocztku lat 70. XX wieku. Opracowano
go specjalnie do nauki programowania. Jest to bardzo prosty jzyk, dajcy jednoczenie due
moliwoci. W roku 1983 firma Borland wypucia na rynek komercyjny produkt o nazwie
Turbo Pascal i ta data jest uwaana za przeomow. Turbo Pascal natychmiast zyska
popularno, ktr w dalszym cigu utrzymuje.
Take dzisiaj Turbo Pascal jest zalecany do nauki programowania dla pocztkujcych: uczy
mylenia programistycznego. Jest wykorzystywany rwnie na rnych szczeblach
nauczania: na studiach czy w szkoach rednich do rozwizywania problemw i tworzenia
algorytmw.

Java
Jzyk Java zosta opracowany przez firm Sun Microsystems. Kady z pewnoci nieraz
sysza o tym jzyku, gdy ostatnio sta si on bardzo popularny, gwnie dziki niezalenoci
od systemu operacyjnego. Z reguy programy napisane i skompilowane dla platformy
Windows nie bd dziaay w innych systemach, np. w Linuksie. W przypadku Javy jest
inaczej, poniewa kod rdowy nie jest kompilowany na kod maszynowy, tak jak to ma
miejsce w innych jzykach programowania, ale zostaje poddany procesowi translacji na kod
poredni. Ten z kolei zostaje poddany procesowi interpretacji przez tzw. maszyn wirtualn,
ktra musi by zainstalowana w systemie.

Mona powiedzie, e maszyna wirtualna jest programem, ktry wykonuje inne programy. W
przypadku Javy maszyna wirtualna interpretuje kod poredni.
Maszyna wirtualna Javy zostaa zaimplementowana dla wikszoci systemw operacyjnych i
dziki temu ten jzyk programowania jest tak uniwersalny.
O maszynach wirtualnych opowiem w kolejnych rozdziaach tej ksiki.

Jzyk maszynowy
Na ostatnich stronach pado wiele nowych poj, takich jak kompilator, kompilacja,
translacja czy jzyk interpretowany. Aby nieco usystematyzowa t wiedz, powiem kilka
sw o tym, jak waciwie dziaaj aplikacje i czym jest jzyk maszynowy.
Typowy program przeznaczony na platform Windows ma rozszerzenie .exe i zwyko si o
nim mwi plik wykonywalny. Jest to plik w formacie PE (ang. Portable Executable)
zawierajcy m.in. skompilowan wersj kodu rdowego, tzw. kod maszynowy. Kod
maszynowy jest postaci gotow do bezporedniego (lub poredniego) wykonania przez
procesor. Oprcz kodu maszynowego plik wykonywalny zawiera rwnie informacje
charakterystyczne dla systemu, std niemoliwe jest uruchomienie takiej aplikacji na innych
systemach operacyjnych.
Typowym daniem uruchomienia aplikacji jest podwjne kliknicie ikony reprezentujcej
dany program. Wwczas system operacyjny aduje tak aplikacj do pamici, skd jest
wykonywana przez procesor.
W kadym bd razie najwaniejsze jest, aby zapamita, i kod maszynowy to
skompilowana wersja kodu rdowego gotowa do wykonania. Istnieje rwnie pojcie
jzyka maszynowego. Jest to rwnie jzyk programowania polegajcy na zapisywaniu
instrukcji jako liczb, ktre s rozkazami pobieranymi przez procesor. Teoretycznie rzecz
biorc, istnieje moliwo operowania na skompilowanym kodzie maszynowym. W taki
sposb powstaway pierwsze aplikacje, gdy nie istniay kompilatory (pierwszy kompilator
musia zosta napisany w kodzie maszynowym). Dawniej wic programowanie byo profesj,
ktr zajmowali si najlepsi, gdy bya to czynno niesamowicie trudna. Taka technika
nie jest ju wykorzystywana (lub szczeglnie rzadko), nie bdziemy ju do niej wraca w
dalszej czci ksiki.

Dziaanie kompilatorw
Wiesz ju, czym s kompilatory. Wane dla zrozumienia dalszej czci ksiki jest poznanie
zasad ich dziaania.

Obecnie kompilatory s niezwykle szybkie pozwalaj na skompilowanie programu nawet


w kilka milisekund (w zalenoci od rozmiarw kodu). Jednym z najpopularniejszych na
wiecie darmowych kompilatorw jest GCC (ang. GNU Compiler Collection), czyli darmowa
kolekcja kompilatorw.
Popularno GCC jest na pewno zwizana z kilkoma czynnikami:

cen (kompilator jest darmowy),


niezawodnoci (kompilator jest tworzony od ponad 10 lat przez rzesz
programistw),
dostpnoci
dla rnych platform (Windows, Linux, DOS, UNIX),

moliwoci kompilacji kodu napisanego w rnych jzykach (Java, C, C++ i inne).

Pierwsza wersja GCC, napisana przez Richarda Stallmana, ukazaa si w 1987 roku. Obecna
wersja, oznaczona jako 3.3.2, jest udostpniona do pobrania ze strony http://gcc.gnu.org/.
Mona by zapyta, w jakim celu pisz o GCC czy o jzyku C przecie nie jest to tematem
niniejszej ksiki. To prawda, wspominam o tym tylko dlatego, e dziaanie kompilatora
jzyka C# jest nieco inne, ale o tym opowiem w dalszej czci ksiki.

Tworzenie kodu rdowego


To jest praca, ktra naley do programisty. Przed kompilacj programu trzeba napisa jego
kod, uywajc danego jzyka programowania. Trzeba przy tym uywa polece, ktre
zostan rozpoznane przez kompilator. Przed faktycznym momentem skompilowania
programu kompilator sprawdza jego skadni jeeli napotka na nieznane mu polecenie,
wywietli bd i zakoczy dziaanie.
Skadnia to dzia gramatyki. Bada zasady czenia poszczeglnych wyrazw w zdania.
Programici pod tym pojciem rozumiej kod rdowy aplikacji, poszczeglne elementy,
instrukcje, ktre cz si w jedn cao.

Prekompilacja
Ten proces polega na usuwaniu komentarzy z kodu rdowego. Teraz nastpuje rwnie
odczytanie makrodefinicji jzyka C, ktre zaczynaj si od sowa #define. Nie jest to jednak
tematem tej ksiki i nie bdziemy si tym szczegowo zajmowa.

Czytelnik, ktry mia wczeniej styczno z jzykiem C, zapewne wie, czym s komentarze czy
makrodefinicje. Tematem komentarzy w jzykach programowania zajmiemy si w dalszej
czci ksiki.

Kompilacja do kodu Asemblera


Proces ten polega na wyszukiwaniu sw kluczowych dla danego jzyka programowania.
Potem nastpuje przeksztacenie tego kodu na instrukcje Asemblera. W tym czasie, jeeli
kompilator wykryje jakie bdy zwizane z kodem rdowym, wstrzyma dziaanie i
zasygnalizuje bd.

Optymalizacja kodu
Nastpnie kompilator analizuje kod Asemblera i poddaje go optymalizacji. Jest to opcjonalna
funkcja w kompilatorach GCC, lecz warto j wykorzystywa, gdy czsto jest przydatna.
Naley te wspomnie, e moliwo optymalizacji kodu jest do zaawansowan funkcj
kompilatora.
Optymalizacja jest procesem poprawiania kodu programu, tak aby jego wykonywanie byo
jak najbardziej efektywne, a sam program zajmowa moliwie jak najmniej pamici.
Optymalizacja powinna nalee do programisty jako wyrobiony nawyk pisania efektywnych
programw, lecz w obecnych czasach maszyna znacznie uatwia t cz pracy.

Asemblacja
Zoptymalizowany kod Asemblera moe zosta poddany asemblacji. Proces asemblacji jest
zwizany z przeksztacaniem kodu Asemblera na kod maszynowy.

Konsolidacja
Konsolidator, nazywany te czsto linkerem, tworzy aplikacj wykonywaln, ktr mona
uruchomi w sposb niezaleny od kompilatora i przykadowo przetestowa jej
dziaanie. Innymi sowy, linker tworzy plik .exe gotowy do uycia.

Jzyki interpretowane
Istniej jzyki, do ktrych nie ma kompilatorw. Takie aplikacje nie s kompilowane do pliku
.exe. Program jest cay czas przechowywany w postaci kodu rdowego. Na maszynie, na
ktrej taka aplikacja ma zosta uruchomiona, musi by zainstalowany tzw. interpreter.
Kadorazowo sprawdza on skadni programu i na bieco wykonuje instrukcje zapisane w
kodzie rdowym. Przykadem jzykw interpretowanych jest PHP i Perl.

Jzyk C#
Jzyk C# 1 (czytaj: si szarp) jest nowym jzykiem programowania zaprojektowanym przez
firm Microsoft i cile zwizanym z platform .NET. czy w sobie zalety jzykw C++
oraz Java, jest do nich bardzo podobny, co uatwia jego nauk osobom, ktre wczeniej miay
do czynienia ze wspomnianymi jzykami.
Gwnym architektem jzyka jest Anders Hejlsberg, duski programista. Wczeniej pracowa
on dla firmy Borland, gdzie uczestniczy w tworzeniu rodowiska Turbo Pascal oraz Delphi.
C# jest do specyficznym jzykiem, jak wspomniaem cile zwizanym z platform
.NET. Aplikacje pisane w tym jzyku wymagaj rodowiska uruchomieniowego CLR, ktre
zostanie omwione w dalszej czci ksiki.

Instalacja rodowiska
W trakcie pisania przykadw do tej ksiki bd korzysta z darmowego rodowiska Visual
C# 2005 Express Edition, ktre moesz pobra ze strony
http://msdn.microsoft.com/vstudio/express/visualcsharp/download/.
Po pobraniu i uruchomieniu pliku instalacyjnego (o rozmiarach kilku MB) nasz system
zostanie przygotowany do cignicia rodowiska .NET Framework 2.0 i Visual C# Express
Edition oraz systemu pomocy. W sumie bdzie to nieco ponad 300 MB. Po cigniciu
wszystkich potrzebnych plikw uruchomiony zostanie instalator, ktry powinien
bezproblemowo zainstalowa wszelkie aplikacje potrzebne do tego, aby zacz programowa.

Jak si uczy

Jak si uczy programowania? Kady ma inne sposoby, mniej lub bardziej skuteczne. Wzie
t ksik do rki zapewne dlatego, e interesujesz si przedstawion w niej tematyk i dziki
temu nauka bdzie przychodzia Ci atwiej. Gorzej, jeeli musisz nauczy si programowania
w C#, poniewa tak nakaza Ci szef w takich przypadkach nauka bdzie trudniejsza. To
naturalne oswojenie si z tym, co si lubi, co sprawia przyjemno, jest w gruncie rzeczy
atwe. Tak samo jest z C#. Jeeli interesujesz si programowaniem, to nauka tego jzyka
bdzie przyjemnym dowiadczeniem.

Nie ucz si na pami!


Na pewno nie naley wkuwa wszystkiego i lepiej nie stara si zapamita caego
prezentowanego tu materiau w kadym momencie mona przecie cofn si do
poprzedniego rozdziau!

Pocztkowe trudnoci
Jeeli jeste naprawd pocztkujcy w tej dziedzinie, to przez jaki czas moe Ci by ciko.
Nie mwi tego, aby kogokolwiek zniechca, lecz raczej aby pocieszy tak jest w wielu
przypadkach, wic nie ma si czym przejmowa. Z czasem wiele kwestii stanie si bardziej
czytelnych i atwiejszych. Moe si bowiem zdarzy, i na pocztku tej ksiki bd
wspomina o sprawach, ktre zostan omwione dopiero w dalszych rozdziaach (aczkolwiek
bd si stara unika niezrozumiaych poj), tak wic nie naley si przejmowa, jeli co
wydaje si niejasne dalej znajd si odpowiedzi.

Pomoc systemowa
Zawsze mona skorzysta z systemu pomocy! Najwaniejszym klawiszem skrtu jest F1,
ktry powoduje uruchomienie systemu pomocy. Niestety, pomoc jest w caoci napisana w
jzyku angielskim, tak wic jest niezbdna jest przynajmniej podstawowa umiejtno
czytania w tym jzyku.

Praktyka
Praktyka czyni mistrza! Kady na pewno nieraz sysza to przysowie. Warto si do niego
stosowa. Po zapoznaniu si z jak now funkcj warto sprawdzi jej dziaanie w praktyce

jest to dobry sposb na opanowanie nowego materiau.

Pierwsza aplikacja
Nasz pierwszy program nie bdzie realizowa adnych konkretnych zada. Bdzie to typowe
okno systemu Windows z przyciskiem. Po naciniciu przycisku wywietlone zostanie nowe
okno z napisem Hello World.
Uruchom rodowisko Visual C# 2005 Express Edition (jeeli chcesz, moesz skorzysta z
bardziej zaawansowanych wersji Visual Studio).
1.
2.
3.
4.

Z menu File wybierz New Project.


Zaznacz ikon Windows Application.
W polu Name wpisz HelloApp.
Nacinij OK.

W tym momencie zosta utworzony nowy projekt typu Windows Forms. Okno o nazwie
Form1 to okno naszej aplikacji. W trakcie projektowania programu mamy moliwo
obserwacji, jak taka aplikacja bdzie wygldaa w trakcie dziaania.
Po lewej stronie ekranu znajduje si pozycja Toolbox. Gdy naprowadzimy na ni myszk,
rozwinie si okno Toolbox zawierajce spis tzw. komponentw (rysunek 1.2) lub kontrolek
(takie okrelenie rwnie jest czsto stosowane).

Rysunek 1.2. Lista komponentw


Odnajd, a nastpnie kliknij raz pozycj Button, aby j podwietli. Teraz wystarczy raz
klikn obszar formularza (Form1), aby umieci w naszej aplikacji przycisk (rysunek 1.3).

Rysunek 1.3. Przycisk umieszczony na formularzu


Komponent (owy przycisk) mona przesuwa oraz zmienia jego rozmiar, chwytajc za jego
naroniki.
Z menu Format wybierz pozycj Center in Form, a nastpnie Horizontally, co spowoduje
umieszczenie przycisku centralnie po rodku formularza.

Kompilacja i uruchamianie
Na razie nasz program skada si jedynie z przycisku. Poza tym nic nie robi, bo nacinicie
przycisku nie powoduje adnej reakcji. Jednak ju teraz moesz uruchomi nasz projekt i
sprawdzi jego dziaanie.
Jest to bardzo proste. Wystarczy z menu Debug wybra Start Debugging lub nacisn
przycisk F5. rodowisko Visual C# Express Edition oprcz edytora kodu i edytora
formularzy zawiera rwnie debuger oraz kompilator. Nacinicie klawisza F5 spowoduje
skompilowanie programu, a nastpnie jego uruchomienie. Po chwili nasza aplikacja w postaci
okna z przyciskiem powinna zosta uruchomiona. Naturalnie jest to bardzo prosty program,
ktry nie realizuje adnego zadania. Zwr uwag, e zaprojektowalimy (stworzylimy)
program, nie piszc ani jednej linii kodu!

Komponenty
W zaprezentowanym przykadzie skorzystalimy z komponentu Button. W tym miejscu
nale Ci si wyjanienia, czym waciwie jest komponent. Typ aplikacji, ktry przed chwil

stworzylimy, nazywany jest aplikacj typu Windows Forms. Wicej na ten temat dowiesz si
z lektury rozdziau 2. oraz 10. Istotne jest to, e projektowanie aplikacji tego typu umoliwia
stosowanie podstawowych kontrolek systemu Windows, takich jak przyciski, listy rozwijane,
pola edycyjne, panele itp. Umoliwia to projektowanie interfejsu aplikacji. Mona wic
powiedzie, e komponenty s klockami umoliwiajcymi szybkie projektowanie aplikacji.

Waciwoci komponentw
Kada kontrolka (komponent) posiada waciwoci, ktre umoliwiaj ustalenie jej
specyficznych cech, takich jak pooenie, kolor, czcionka itp. Spis waciwoci komponentw
znajduje si w oknie Properties (rysunek 1.4), ktre standardowo umiejscowione jest w
prawym dolnym rogu rodowiska Visual C# Express Edition.

Rysunek 1.4. Spis waciwoci z zaznaczon cech Text


Okno Properties podzielone jest na dwie kolumny. Kolumna po lewej zawiera nazw
waciwoci, a ta po prawej jej warto.
Wykonajmy proste wiczenie polegajce na zmianie wartoci waciwoci Text komponentu
Button.
1. Zaznacz komponent Button (pojedynczym klikniciem), ktry w poprzednim
wiczeniu umiecie na formularzu.
2. Na licie waciwoci odszukaj Text, ktra okrela tekst wywietlany na przycisku.
3. Kliknij podwjnie nazw waciwoci. Spowoduje to zaznaczenie kolumny po prawej,
ktra umoliwia wprowadzenie nowej wartoci.
4. Wpisz w polu tekst Kliknij mnie i nacinij Enter.

Jeeli wszystko wykonae zgodnie z opisem, tekst znajdujcy si na przycisku zosta


zmieniony.

Piszemy kod
Mamy ju formularz, przycisk, lecz brakuje nam reakcji na nacinicie go. Musimy wobec
tego oprogramowa odpowiednie zdarzenie (reakcje) na wcinicie kontrolki. Kliknij
dwukrotnie komponent Button. Zostaniesz przeniesiony do edytora kodu, gdzie naley wpisa
kod, ktry zostanie wykonany, w momencie gdy uytkownik
nacinie przycisk. My chcemy, aby wywietlone zostao okienko, co realizuje poniszy kod:
MessageBox.Show("Hello World");

Cay kod rdowy naszego programu prezentuje listing 1.1.


Listing 1.1. Kod rdowy pierwszej aplikacji
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace HelloApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Hello World");
}
}
}

Teraz moesz ju uruchomi program klawiszem F5 i przetestowa jego dziaanie. Rysunek


1.5 prezentuje aplikacj w trakcie dziaania.

Rysunek 1.5. Pierwsza aplikacja Windows Forms

Zapisywanie projektu
Moje gratulacje! Wanie napisae swj pierwszy program w C#. Myl, e warto by byo
zapisa go na pamitk. Aby zapisa projekt, z menu File wybierz Save All. W tym momencie
musisz wybra katalog, w ktrym zapisane zostan pliki projektu (przycisk Browse).
Zapisanie projektu nastpi po naciniciu przycisku Save. W tym momencie w wybranym
katalogu zostanie utworzony szereg plikw i folderw. Ich zawarto w tym momencie jest
nieistotna. Najwaniejsze s pliki z rozszerzeniem *.cs, ktre zawieraj rzeczywisty kod
rdowy naszego programu.

Podsumowanie
Po przeczytaniu tego rozdziau powiniene mie podstawow wiedz na temat
programowania. Najwaniejsze jest, aby wiedzia ju, czym jest kompilator, kod rdowy
czy proces kompilacji. Powiniene mie zarys tego, jak dziaaj kompilatory. W tym rozdziale
zarysowaem jedynie tematyk programowania oraz poruszyem podstawowe pojcia, ktre
bd rozwija w dalszej czci ksiki.
[1] Powszechnie nazw jzyka zapisuje si jako C#, czyli uywa si znaku # (ang. hash), a nie
znaku krzyyka (ang. sharp) uywanego w notacji muzycznej. Spowodowane jest to brakiem
znaku sharp w wielu czcionkach.

Rozdzia 2
Podstawy platformy .NET
W poprzednim rozdziale opisaem, na czym polega programowanie. Nadszed czas, aby
zgbi t wiedz i dowiedzie si, czym jest programowanie dla platformy .NET. Nazwa
wydaje si do dziwna, niewtpliwie kojarzy si z internetem. I prawidowo! Ta technologia
ma sporo wsplnego z globaln sieci.
Internet odgrywa coraz wiksz rol w naszym yciu. Nie suy ju tylko do przegldania
statycznych stron WWW. Teraz internet to gwnie komunikatory oraz dynamiczne strony
WWW, ktre umoliwiaj wiksz interakcj z uytkownikiem. Przykadowo, po otwarciu
strony traktujcej o gospodarce uzyskujemy na bieco wgld w najnowsze notowania akcji,
kursy walut czy ciekawostki. Po rejestracji w takim serwisie mona otrzymywa codzienne
informacje kierowane na wskazany telefon komrkowy
czy skrzynk e-mail. Strony WWW dostarczaj teraz coraz bardziej interesujcej rozrywki
dziki animacjom czy grom wykonanym za pomoc technologii Flash. Wiele serwisw
umoliwia rejestracj, w ktrej mona ustala swoje preferencje, takie jak wygld strony czy
korzystanie z rozmaitych opcji.
Obecnie nie istnieje jednak technologia pozwalajca na swobodne komunikowanie si ze sob
serwisw internetowych. Np. przegldajc oferty
sklepu muzycznego, chcielibymy zamwi bilet na koncert ulubionego zespou. Takie
zamwienia obsuguje jednak inna witryna. Oczywicie sklep muzyczny moe oferowa
swoim klientom
kupno biletu na koncert poprzez podanie np. odnonika do strony, na ktrej mona zamwi
bilet, lub poredniczenie w sprzeday. Nie jest to jednak idealne i wygodne rozwizanie. Czy
nie lepiej byoby, gdyby witryny mogy si ze sob komunikowa? Wyobramy sobie taki
scenariusz: nasza strona sklep muzyczny wysya zapytanie do serwisu organizatora
koncertu i sprawdza dostpno biletw na wystp danego wykonawcy wraz z cenami oraz
terminami. W takim przypadku wszystko odbywaoby si automatycznie nie jest potrzebna
jakakolwiek ingerencja czowieka, gdy informacje s stale pobierane ze strony organizatora
koncertu.
Midzy innymi takie scenariusze zakada platforma .NET. Chodzi o integracj usug, sprztu i
innych urzdze, takich jak palmtopy i telefony komrkowe. Microsoft chce, aby aplikacje
mona byo uruchamia w telefonach, aby programy te mogy pobra aktualne dane z
internetu (na przykad aktualne terminy koncertw). O tym jednak opowiem nieco dalej
najpierw przecie naley wyjani, w jaki sposb odbywa si programowanie dla systemu
Windows.

Interfejs programistyczny
Kolejnym pojciem, ktre trzeba zna, jest API, czyli programistyczny interfejs aplikacji

(ang. Application Programming Interface). To pojcie bdzie si czsto pojawiao w tej


ksice.
Mwic o API, mam na myli interfejs w kontekcie systemu Windows. Wyobramy sobie,
e chcemy w naszym programie pobra wsprzdny wskanik myszy. System Windows
udostpnia w tym celu funkcj GetCursorPos. Moemy j wykorzysta w kadej aplikacji,
ktra bdzie dziaa w systemie Windows. Aby w danym momencie pobra wsprzdne
wskanika myszy, program zgasza si do systemu operacyjnego z prob o udostpnienie
tych informacji. W takim przypadku sprawa jest prosta wystarczy jedno polecenie, aby
otrzyma wsprzdne wskanika myszy. Nie trzeba programowa wasnych funkcji i gowi
si nad sposobem pobrania owych wsprzdnych.
API jest zestawem funkcji, polece, ktre udostpnia aplikacja (w tym przypadku system
Windows). Owe funkcje mog by nastpnie wykorzystywane przez inne, take pisane przez
uytkownika, programy. Przykadowo, API systemu Windows jest zestawem wielu polece,
ktre zawarte s w bibliotekach DLL. Nasza aplikacja moe wykorzystywa te funkcje, dziki
czemu programowanie staje si prostsze.

API systemu Windows


Aby uatwi nam pisanie programw dla systemu Windows, programici Microsoftu
udostpniaj biblioteki DLL (z ktrych najwaniejszymi s: user32.dll, kernel32.dll,
gdi32.dll). Biblioteki DLL z kolei zawieraj funkcje, ktre moemy wykorzystywa.
Oczywicie trzeba te wiedzie, jakie nazwy nosz owe funkcje oraz w jaki sposb mona z
nich skorzysta. Informacje te umieszczono w
dokumentacji udostpnionej na witrynie firmy Microsoft (http://msdn.microsoft.com).
API systemu Windows nosi nazw WinAPI (ang. Windows Application Programming
Interface). Na pocztku, w systemach Windows 3.1., by to interfejs Win16 (systemy
Windows 3.1. byy 16-bitowe), a pniej Win32 dla systemw Windows
95/98/NT/XP/2000. Interfejs Win32 jest wykorzystywany do dzisiaj przez wielu
programistw i wiele aplikacji.
Podsumowujc:

Windows API (WinAPI) to programistyczny interfejs (ang. application


programming interface) uywany w systemie operacyjnym Microsoft Windows.
Innymi sowy, jest to specyfikacja procedur i funkcji sucych do komunikowania si
programw z systemem operacyjnym.
Win16 16-bitowa wersja WinAPI.
Win32 32-bitowa wersja WinAPI uywana w systemach Windows do dzisiaj. Jest
to zbir funkcji napisanych w jzyku C, ktre s umiejscowione w bibliotekach DLL
np. kernel32.dll, user32.dll itd.

Teraz najwaniejsze: programujc w C#, zasadniczo nie korzysta si z tych funkcji

zgromadzonych w bibliotekach DLL (WinAPI). WinAPI systemu Windows uwaane jest


obecnie za przestarzae, w kolejnych systemach operacyjnych Microsoftu pozostanie jedynie
ze wzgldw kompatybilnoci. Wszak aplikacje, ktre programici pisz obecnie, korzystaj z
tych funkcji, zgromadzonych w licznych bibliotekach DLL. Gdyby zabrako ich w nowych
wersjach systemu, takie programy zwyczajnie przestayby dziaa. W nowym systemie
operacyjnym, nazwanym Longhorn 1 (nazwa kodowa ), obecny WinAPI zostanie zastpiony
nowoczesnym
, bardziej przyjaznym i potniejszym interfejsem WinFX. Platforma .NET ma by
cznikiem pomidzy obecnym modelem programowania (WinAPI) a nowym interfejsem
WinFX. Ma stopniowo, powoli przyzwyczaja programistw do nowego stylu
programowania.
Skoro w C# nie korzystamy z WinAPI, to czemu o tym wspominam? Chc, aby mia
odpowied na pytanie, skd si bior te wszystkie funkcje, ktre udostpnia jzyk
programowania. Jak to jest, e dane polecenie umoliwia pobranie wsprzdnych myszy, a
inne wyczenie wygaszacza ekranu. W rzeczywistoci nie s to funkcje/polecenia, ktre
udostpnia jzyk programowania, lecz te udostpniane przez system operacyjny! Te same
polecenia mona wykorzysta w innych jzykach programowania.

Wizja .NET
W lipcu 2000 roku Microsoft zwoa konferencj programistw, na ktrej zademonstrowano
.NET. W zamierzeniu .NET jest platform nowej generacji, przeznaczon do tworzenia
aplikacji Windows. Bill Gates w swoim przemwieniu porwna powstanie platformy .NET
do przejcia z systemu DOS do Windows 3.1., a pniej z przejciem do Windows 95. W
rzeczywistoci .NET nie jest nowym systemem operacyjnym ani jzykiem programowania.
Jest podstaw, pewna ide, sposobem budowania aplikacji dla systemu Windows. Platforma
.NET to po prostu nowe podejcie do tworzenia oprogramowania.
Jednym z zaoe platformy .NET jest porzucenie dotychczasowego modelu Win32 i
zastpienie go cakowicie nowym, prostszym oraz bardziej zaawansowanym.
W poprzednim rozdziale opisaem proces instalowania platformy .NET. W rzeczywistoci
podczas tej operacji instalator kopiuje na dysk twardy komputera szereg bibliotek DLL,
aplikacji dla programistw oraz dokumentacj. Dziki temu mona korzysta z nowych
narzdzi oferowanych przez Microsoft. Windows 2003 standardowo jest wyposaony w
platform .NET, wic nie ma potrzeby ponownej instalacji.
Zgodnie z definicj firmy Microsoft:
.NET jest stworzonym przez firm Microsoft rozwizaniem dla usug sieciowych, stanowi
now generacj oprogramowania czcego si z naszym wiatem informacji, urzdzeniami i
uytkownikami w jednolity, spersonalizowany sposb.
Taka definicja zapewne wydaje si troch niejasna, dlatego w tym rozdziale postaram si

objani kilka kwestii zwizanych z platform .NET. Istotnymi zaoeniami, ktre


zdecydoway o powstaniu platformy .NET, byy:

prostsze tworzenie oprogramowania,


moliwo komunikowania si aplikacji,
uproszczone wdraanie aplikacji.

Skadniki platformy .NET


Platforma .NET skada si z kilku skadnikw:

rodowiska .NET Framework,


narzdzia dla programistw,
serwerw .NET Enterprise.

rodowisko .NET Framework


Bardzo wana uwaga: programy pisane w C# wymagaj, aby na komputerze, na ktrym bd
uruchamiane, byo zainstalowane rodowisko .NET Framework. Jeeli taki program dziaa na
danym komputerze, to znaczy, e zainstalowano pakiet .NET Framework albo pecet pracuje
pod kontrol Windows 2003, gdzie .NET Framework jest zainstalowany domylnie.
rodowisko .NET Framework jest podstawowym skadnikiem platformy .NET. Mwic
najprociej, s to biblioteki, technologie wspierajce dziaanie programu, odpowiedzialne za
zarzdzanie pamici aplikacji itp.
Naley w tym miejscu zaznaczy, e programowanie dla platformy .NET rni si od
standardowego modelu programowania dla Win32. rodowisko .NET Framework zawiera
nowe biblioteki, funkcje, ktre znaczco rni si od tych udostpnianych przez Win32.
Mona powiedzie, e programowanie dla .NET jest prostsze od programowania dla Win32,
gdy platforma .NET odcia programist od zada, ktre wczeniej spoczyway na jego
gowie (np. zwalnianie pamici), lecz o tym bdziemy mwili w dalszej czci ksiki.
Platforma .NET przyciga coraz wiksz liczb programistw, ktrzy chc sprbowa swoich
si i modyfikuj swoje programy, aby dziaay pod kontrol rodowiska .NET Framework.
Zdobywanie popularnoci przez rodowisko jest jednak procesem dugofalowym. Minie
jeszcze sporo czasu, zanim wikszo porzuci standardowy model programowania pod
Win32.

Narzdzia dla programistw


Wraz z dostarczeniem technologii, jak jest platforma .NET, firma Microsoft musiaa
udostpni programistom take narzdzia, za pomoc ktrych mogli oni produkowa swoje
aplikacje. W zwizku z tym powsta pakiet dla programistw o nazwie Visual Studio.NET.
Dotychczas jzykiem programowania popieranym przez Microsoft by Visual Basic, przez
wielu uwaany za prosty i mao profesjonalny. Jzyk ten zosta wzbogacony o nowe funkcje i
moliwo obsugi platformy .NET (teraz Microsoft promuje go pod nazw Visual
Basic.NET).
Moliwoci jzyka Visual Basic zostay rozszerzone z myl o programistach, ktrzy ju od
dawna uywali tego narzdzia. Nie mona byo o nich zapomnie, lecz trzeba byo da im ten
sam produkt (poszerzony o now funkcjonalno
), ktry pozwalaby na tworzenie programw dla platformy .NET. Jzykiem programowania,
ktry powsta specjalnie na potrzeby platformy .NET, jest C#. Jest to jzyk zbudowany na
bazie C++ oraz Javy (o czym wspominaem w poprzednim rozdziale). Umoliwia obsug
wszystkich technologii oferowanych przez .NET Framework.
Podstawy jzyka C# zostan opisane w kolejnym rozdziale niniejszej ksiki.
Narzdzia dla programistw to nie tylko jzyki programowania. To rwnie nowa baza
danych MS SQL Server oraz technologia bazodanowa ADO.NET.
Jeeli chodzi o technologie zwizane z internetem, do dyspozycji pozostaje technologia
tworzenia dynamicznych stron WWW ASP.NET. Jest to technologia powstaa na bazie
ASP (ang. Active Server Pages). Strony ASP.NET mog by pisane przy uyciu C#, Visual
Basic.NET oraz jakiegokolwiek jzyka obsugujcego platform .NET.

Kompilator csc.exe
Pamitasz, jak w poprzednim rozdziale wspominaem o kompilatorze jzyka C#
udostpnianym za darmo wraz ze rodowiskiem .NET Framework? Nie miaem jeszcze okazji
zaprezentowa sposobu dziaania tego programu. W poprzednim rozdziale wspominaem
rwnie, i do programowania nie jest potrzebny aden specjalistyczny edytor. Wystarczy
zwyky, najprostszy edytor tekstu, jak np. Notatnik z systemu Windows.
Uruchom Notatnik i wpisz nastpujc tre programu:
using System;
class Welcome
{
static void Main()
{
Console.WriteLine("Cze!");
Console.ReadLine();

}
}

Plik o tej treci zapisz w katalogu, w ktrym znajduje si kompilator csc.exe. W moim
wypadku jest to katalog C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727. Plik zapisz
pod nazw welcome.cs. Ten prosty program wywietla na ekranie konsoli tekst Cze!.
1. Z menu Start wybierz Programy/Akcesoria/Wiersz polece.
2. Wpisz komend cd C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727, ktra
spowoduje otwarcie katalogu z kompilatorem.
3. Nastpnie wpisz polecenie csc welcome.cs, ktre spowoduje skompilowanie
programu.

Jeeli wszystko poszo dobrze, czyli kod rdowy nie zawiera bdw, na ekranie konsoli
powinien pojawi si tekst:
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights
reserved.

Teraz mona w katalogu C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 odszuka


aplikacj welcome.exe i sprbowa j uruchomi. Moje gratulacje! Wanie wsplnie
napisalimy pierwszy program konsolowy dziaajcy w rodowisku .NET Framework w
jzyku C# (rysunek 2.1). Program mona zamkn, naciskajc klawisz Enter.

Rysunek 2.1. Program konsolowy napisany w C#

Opisujc to wiczenie, zaoyem, e kompilator csc.exe znajduje si w katalogu


C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727, jednak wcale nie musi tak by we
wszystkich komputerach.

Serwery .NET Enterprise


Serwer .NET Enterprise jest oprogramowaniem, ktre moe zosta uyte wraz z aplikacjami
.NET. Dobrym przykadem jest baza SQL Server, ktra pozwala na wymian informacji
pomidzy aplikacj a serwerem. Dane np. informacje o klientach mog by
zgromadzone na jednym komputerze (serwerze). Baza danych bdzie obsugiwana przez
serwer SQL Server. Klientem jest aplikacja, ktra czy si z serwerem i pobiera aktualn
list klientw oraz umoliwia pobranie szczegowych informacji na temat jednego z nich.
Do innych serwerw .NET Enterprise zaliczamy m.in. oprogramowanie BizTalk Server czy
Exchange. W tej ksice nie bd jednak szczegowo opisywa serwerw Enterprise.

Konkluzja
Podsumujmy pokrtce, czym jest .NET:

.NET jest now strategi firmy Microsoft.


.NET wytycza nowe standardy programowania.
.NET bazuje na infrastrukturze zwizanej z internetem.
.NET ma w zaoeniu dziaa na kadym systemie lub platformie 2 .
.NET to nie system operacyjny.

C# a .NET
Podsumujmy wic nasz wiedz dotyczc jzyka C#:

C# jest jzykiem programowania na platform .NET.


Programujc w C#, korzystamy z funkcji, jakie oferuje rodowisko .NET Framework.
Aplikacje napisane w C# wymagaj, aby na komputerze zainstalowane byo
rodowisko .NET Framework.

Chcc pisa aplikacj na platformie .NET, nie jeste skazany jednie na jzyk C#. Platforma
.NET wspiera rwnie inne jzyki, takie jak C++, Visual Basic.NET, Delphi i inne.

Rodzaje aplikacji
W poprzednim rozdziale stworzye swoj pierwsz aplikacj w C#. Jak pamitasz, program
skada si z jednego formularza (okna). W takim wypadku mwimy o wykorzystaniu
biblioteki Windows Forms. Owa biblioteka, bdca oczywicie czci platformy .NET,
umoliwia uycie tzw. komponentw, takich jak przyciski, listy rozwijane, pola edycyjne.
Przed chwil pokazaem, jak mona stworzy aplikacj konsolow.

Aplikacje konsolowe
Aplikacje konsolowe to programy uruchamiane w oknie konsoli. Tego rodzaju aplikacje nie
posiadaj interfejsu uytkownika i s przydatne, jeeli chodzi o prosty program, ktry bdzie
uywany przez wsk grup ludzi posiadajcych wiedz potrzebn do uruchomienia go i
sterowania nim z linii polece.
Przykad aplikacji konsolowej zosta przedstawiony w tym rozdziale wczeniej, gdy
zaprezentowaem moliwo skompilowania prostego programu napisanego w C#.

Windows Forms
W rozdziale 1. pokazaem, w jaki sposb utworzy prosty program wizualny na podstawie
formularza i z wykorzystaniem komponentu. Po uruchomieniu takiego programu uytkownik
widzi standardowe okno Windows wraz z etykiet porodku. Tego typu aplikacje bd dziaa
jedynie w przypadku, gdy na komputerze jest zainstalowane rodowisko .NET Framework.
Windows Forms (czsto nazywana w skrcie WinForms) jest bibliotek wizualn,
umoliwiajc zaprojektowanie graficznego interfejsu uytkownika (GUI, czyli Graphical
User Interface). WinForms jest czci rodowiska .NET Framework, tak wic takie
komponenty jak Button (uyty w przykadzie z rozdziau 1.) mog zosta uyte zarwno w
Delphi, jak i w C# czy innym jzyku obsugiwanym przez platform .NET (np. Visual
Basic.NET).
O bibliotece Windows Forms bdzie mowa w rozdziale 10.

Formularze Web Forms


Technologia .NET umoliwia tworzenie dynamicznych stron WWW z uyciem technologii
ASP.NET. Chciabym teraz wspomnie o czym takim jak formularze Web Forms.
Tworzenie dynamicznych stron ASP.NET odbywa si w C# w sposb wizualny. Do
dyspozycji pozostaje formularz (ktry odzwierciedla przysz stron WWW) oraz zestaw
kontrolek. Wykorzystanie owych komponentw opiera si na dynamicznym generowaniu
kodu HTML dla strony. Projektowanie strony odbywa si wizualnie programista
umieszcza odpowiedni element na stronie (np. komponent tabel), a rodowisko Visual
Studio w tle generuje odpowiedni kod HTML, ktry na kocu bdzie przetwarzany przez
przegldark.
W tej ksice nie bdziemy zajmowa si tworzeniem dynamicznych stron WWW przy pomocy
ASP.NET. Czytelnika zainteresowanego t technologi odsyam do dokumentacji firmy
Microsoft lub do ksiek traktujcych o tej technologii.

Skadniki .NET Framework


W tym miejscu mona by si zatrzyma i zapyta: Zaraz, zaraz wczeniej bya mowa o
skadnikach platformy .NET, a teraz o skadnikach platformy .NET Framework?. Trzeba
wic wyjani, e zagadnienia zwizane z .NET s bardzo rozlege i caociowe ich
omwienie w tej ksice jest po prostu niemoliwe. Chciabym jednak poruszy kilka
najwaniejszych kwestii zwizanych z t platform. Nie mona przy tym pomin
najwaniejszego, czyli rodowiska .NET Framework.
rodowisko .NET Framework jest podstawow technologi budowania i uruchamiania
aplikacji.
Szczegowo omwimy j w rozdziale 4. Tutaj chciabym wspomnie jedynie o kilku jej
skadnikach:

Class Libraries (CL) biblioteki klas. S to gotowe komponenty, funkcje i


biblioteki, ktre mona wykorzysta we wasnym programie. Wizualna biblioteka
WinForms jest take czci CL, podobnie jak szereg innych funkcji, ktre stopniowo
bd omawia w tej ksice.
Common Language Runtime (CLR) wsplne rodowisko wykonywalne (albo
wsplne rodowisko uruchomieniowe). Technologia CLR odpowiada za uruchamianie
aplikacji .NET oraz ich prawidowe dziaanie, a take za zwalnianie pamici czy inne
skomplikowane czynnoci. Szerzej o CLR opowiem w rozdziale 4.
Common Type System (CTS) wsplny system typw. Dziki tej technologii jest
moliwa wsppraca pomidzy rnymi aplikacjami na platformie .NET. Na razie nie
musimy zaprzta sobie tym gowy. Wystarczy tylko wiedzie, e co takiego istnieje.

Usugi sieciowe
Usugi sieciowe s aplikacjami udostpnianymi poprzez interfejs WWW. Nie chodzi tutaj
bynajmniej o strony WWW, gdy aplikacje sieciowe mog komunikowa si ze sob za
pomoc mechanizmu zdalnego wywoywania procedur (ang. Remote Procedure Calls
RPC), przewanie uywajc do tego protokou HTTP.
Dobrym przykadem jest wypoyczalnia DVD. Szef takiej firmy moe podpisa umow z
wacicielami sklepw z pytami DVD, ktrzy na swoich stronach bd oferowali pyty z
wypoyczalni. Wwczas serwisy wacicieli sklepw mog komunikowa si z usug
sieciow znajdujc si na serwerze przynalenym do wypoyczalni w celu pobrania
aktualnej oferty, sprawdzenia, czy dany tytu jest ju wypoyczony i kiedy ma zosta
zwrcony. Podobnie oprogramowanie zainstalowane w placwce/placwkach wypoyczalni
moe komunikowa si z serwerem, pobierajc aktualne dane o klientach czy wypoyczonych
tytuach. Wszystkie dane znajduj si na jednym serwerze i s udostpniane przez usug
sieciow. Wszelkie zmiany w tytuach bd produktach bd przeprowadzane jedynie na
centralnym serwerze nie zachodzi wwczas potrzeba aktualizacji oprogramowania w
placwkach.
Usugi sieciowe mona take sprzedawa. Jeeli kto napisze innowacyjn usug
wykorzystujc niepowtarzalny, nowatorski algorytm szyfrujcy, moe j udostpnia w
internecie. Klienci natomiast mog dostosowa odpowiednio swoje oprogramowanie, tak aby
umoliwi wykorzystanie tej nowej usugi. W takim przypadku wszelkie poprawki czy
naprawa bdw bd dotyczyy tylko tej jedynej usugi, a klienci nie musz nawet o tym
wiedzie. Warunkiem jest jedynie zapewnienie dostpnoci takiej aplikacji przez internet.
Inny przykad: autorzy popularnej wyszukiwarki Google udostpnili programistom jej
interfejs. Dziki usugom sieciowym mona wykorzysta mechanizmy Google do
przeszukiwania zasobw internetu. Nastpnie wyniki takiego wyszukiwania mona
zaprezentowa w swoim programie lub stronie internetowej.
Usugi sieciowe s uniwersalnym sposobem wymiany informacji pomidzy aplikacjami czy
stronami WWW. Teraz aplikacja nie musi skada si z tylko jednego moduu (pliku
wykonywalnego .exe), ale moe dzieli si na kilka mniejszych, ktre zostan rozmieszczone
na kilku serwerach. Centralna aplikacja, znajdujca si na komputerze klienta, moe
bezproblemowo czy si z serwerami i wymienia dane oraz realizowa zadania.

Niezaleno
Bardzo wanym aspektem usug sieciowych jest ich niezaleno od platformy jzykowej.
Wane jest, aby usuga dziaaa pod kontrol .NET Framework, jednak nieistotne jest to, w
jakim jzyku zostaa napisana. Moe to by wic C#, Visual Basic.NET czy Delphi.

Aplikacja, ktra korzysta z danej usugi, rwnie moe by napisana w jakimkolwiek jzyku
obsugiwanym przez .NET.

Uniwersalno
A zatem ju wiadomo, e usuga napisana w C# moe by wykorzystywana przez aplikacj
napisan w Delphi. Jak to si wic dzieje, e pomidzy usug a uywajc jej aplikacj
zachodzi swobodna komunikacja
? Chciabym zasygnalizowa kilka podstawowych poj zwizanych nie tylko z usugami,
lecz oglnie z platform .NET.
Komunikacja z usugami sieciowymi odbywa si poprzez sie WWW, a konkretniej przez
wykorzystywany tutaj protok HTTP. Aplikacja moe wysa do usugi sieciowej zapytanie
w formacie XML, a nastpnie otrzyma odpowied rwnie w tym formacie. Kluczow
rol w procesie komunikowania si z usugami sieciowymi odgrywa wanie jzyk XML
opracowany przez konsorcjum W3C.
Konsorcjum W3C (ang. World Wide Web Consorcium) jest organizacj wyznaczajc
standardy technologii zwizanych z internetem (specyfikacje, wytyczne aplikacji itp.).
Organizacja W3C jest odpowiedzialna m.in. za opracowanie standardu HTML, ktry
obowizuje do dzisiaj. Stanowi niewtpliwy autorytet w dziedzinie definiowania standardw
sieciowych. Wicej informacji na temat konsorcjum mona znale na stronie www.w3c.org.
Z usugami sieciowymi wie si kilka nastpnych poj:

XML ang. eXtensible Markup Language. Jest to uniwersalny jzyk znacznikw


przeznaczony do publikowania danych. Jego uniwersalno, niezaleno od
platformy oraz coraz wiksza popularno czyni go idealnym formatem do
przesyania danych pomidzy usugami sieciowymi.
SOAP ang. Simple Object Access Protocol. SOAP jest jzykiem opartym na XML,
wykorzystywanym przez usugi sieciowe do wywoywania procedur. Protok SOAP
okrela format przesyanych danych, nazwy parametrw itp.
WSDL ang. Web Service Description Language. WSDL jest rwnie opartym na
XML formatem opisu usugi sieciowej. WSDL suy do opisywania usugi, dostarcza
uytkownikom informacji o jej przeznaczeniu i sposobie wykorzystania.
UDDI ang. Universal Description, Discovery and Integration. Jest to spis usug
sieciowych udostpnianych publicznie.

Podsumowanie
Uf! W tym rozdziale zaprezentowaem kolejn dawk teoretycznej wiedzy z zakresu

platformy .NET. Bardziej szczegowo o rodowisku .NET Framework opowiem w rozdziale


4., teraz jednak powiniene zapamita pewne podstawowe pojcia, takie jak CLR czy CLS.
Trzeba rwnie odrnia model programowania dla Win32 od modelu .NET.

[1] Nazwy kodowe nadawane s projektom we wczesnym stadium rozwoju, wtedy gdy nie
wiadomo jeszcze, jaka bdzie rzeczywista nazwa produktu.
[2] Odnios si w tym momencie do starego powiedzenia. Owszem, .NET ma dziaa na
kadej platformie, ale pod warunkiem e bdzie to produkt firmy Microsoft. Powstaje jednak
darmowy projekt (Open Source), ktrego zaoeniem jest stworzenie platformy zintegrowanej
z .NET dla systemu Linux. Projekt nosi nazw dotGNU, a jego strona internetowa to
www.dotgnu.org.

Rozdzia 3
Podstawy jzyka C#
Zawsze, na kadym kroku staram si podkrela, i tworzenie aplikacji nie opiera si jedynie
na ukadaniu klockw (komponentw) w oknie formularza. Oczywicie, nowoczesne jzyki
programowania (takie jak C#) oraz rodowiska tworzenia aplikacji (Visual C# Express
Edition) daj nam moliwo szybkiego oraz przyjemnego projektowania aplikacji, lecz nie
na tym polega programowanie! Naley mie wiedz na temat podstawowych elementw
jzyka programowania oraz podstawowych polece sucych do sterowania prac programu.
W tym rozdziale zajmiemy si wanie jzykiem C#. Celowo podkreliem sowo jzyk,
gdy omwi podstawowe terminy programistyczne oraz elementy kadego
wysokopoziomowego jzyka programowania. Odstawimy w tym miejscu na chwil
przyjemne i adne projektowanie wizualne (przy pomocy komponentw) na rzecz aplikacji
konsolowych. Wszystko dlatego, i moim zdaniem prociej jest nauczy si danego jzyka na
przykadach zawierajcych jak najmniejsz ilo kodu, tak jak to ma miejsce w przypadku
programw konsolowych.
Skadnia to specyficzne sowa kluczowe (elementy danego jzyka suce do sterowania prac
programu) oraz znaki, ktre musz zosta zapisane w okrelonym porzdku.

Podstawowa skadnia
Kod rdowy musi skada si z polece zakoczonych okrelonymi znakami. Nie mona
pozostawi w kodzie adnego baaganu nawet pominicie jednego znaku czy zwyka
literwka mog spowodowa brak moliwoci uruchomienia programu. Tak jak w jzyku
polskim, pominicie odpowiedniego znaku interpunkcyjnego jest bdem. Takie banalne z
pozoru bdy mog by prawdziw udrk dla pocztkujcego programisty i w konsekwencji
spowodowa spowolnienie prac nad programem. Z czasem wyrabia si pewien nawyk, dziki

ktremu popenia si coraz mniej bdw, a nawet jeli to s one dla bardziej
zaawansowanego programisty atwiejsze do wykrycia.
Na szczcie nowoczesne kompilatory potrafi bardzo precyzyjnie wskaza rdo bdu wraz
ze szczegowym komunikatem oraz numerem linii, w ktrej on wystpi.
Zacznijmy wic. Utwrz swj pierwszy projekt aplikacji konsolowej.
1. Z menu File wybierz New Project.
2. Zaznacz ikon Console Application.
3. Kliknij przycisk OK.

W tym momencie rodowisko utworzy nowy projekt aplikacji konsolowej (rysunek 3.1). W
edytorze kodu zostanie automatycznie wpisany startowy kod naszego programu. Taki kod
moesz w tym momencie skompilowa nie zawiera on adnych bdw i jest to
praktycznie punkt startowy do rozwijania aplikacji.

Rysunek 3.1. rodowisko Visual C# z otwartym projektem


Jak widzisz, kod rdowy skada si ze specyficznych sw (np. using, namespace),
symboli (np. nawiasy kwadratowe oraz okrge). Wszystko tworzy jedn spjn cao i
nazywane jest skadni programu.

Najprostszy program
Napiszmy najprostszy program w jzyku C#. W swoim projekcie musimy pozostawi pewne
niezbdne elementy programu. Kod rdowy najprostszego programu moe wyglda
nastpujco:
class Foo
{
static void Main(string[] args)

{
}
}

Oczywicie taka aplikacja nic nie robi, zaraz po uruchomieniu (klawisz F5) zostanie
zamknita.

Jak kompilatory czytaj kod


Podczas kompilowania programu kompilator sprawdza najpierw, czy kod rdowy nie
zawiera bdw. Kod jest czytany linia po linii, poczwszy od gry, tak wic instrukcje s
wykonywane w takiej kolejnoci, w jakiej zostay zapisane.

Wielko znakw
Kompilator jzyka C#, podobnie jak C++ czy Javy, rozrnia wielko znakw. Przykadowo
polecenie Foo nie jest rwne poleceniu foo z punktu widzenia kompilatora to dwa rne
polecenia. Jeeli wic wczeniej programowae w Delphi lub Turbo Pascalu (ktre nie
rozrniay wielkoci znakw), musisz przywykn do tego, e kompilator C# rozrnia
wielko znakw, i powica duo uwagi temu, co piszesz.
Dla przykadu doprowad wygld naszej prostej aplikacji do takiego ksztatu:
class Foo
{
static void main(string[] args)
{
}
}

Po dokadnej analizie moesz zauway, e dokonaem jednej maej poprawki. Zamieniem


nazw funkcji z Main na main. Podczas prby skompilowania takiego programu wywietlony
zostanie bd: Program 'ConsoleApplication1.exe' does not contain a static
'Main' method suitable for an entry point.
Komunikat mwi o tym, e aplikacja nie zawiera metody o nazwie Main, ktra jest punktem
startu programu. Jest to pierwsza regua charakterystyczna dla programw pisanych w C#. A
mianowicie:

Program musi posiada metod Main


Dla prawidowej terminologii uywam tutaj pojcia metoda, mimo i nie wyjaniem, czym
waciwie jest metoda! Na razie nie przejmuj si tym zostanie to omwione dalej.
Zapamitaj jedynie, e metoda Main jest obowizkowym elementem programu. To od niej
program rozpoczyna swe dziaanie i na niej je koczy. Zmodyfikuj nasz program do takiej
postaci:
class Foo
{
static void Main(string[] args)
{
System.Console.WriteLine("Witaj wiecie!");
}
}

Po uruchomieniu takiego programu (F5) na ekranie konsoli zostanie wywietlony tekst: Witaj
wiecie!. Jest to potwierdzeniem moich sw o tym, i to wanie w metodzie Main
rozpoczyna si waciwe dziaanie programu, czyli wykonanie instrukcji:
System.Console.WriteLine("Witaj wiecie!");

Taki kod powoduje wywietlenie na ekranie konsoli tekstu zapisanego pomidzy apostrofami.
Nie moesz zapomina o obowizkowych nawiasach, bez ktrych aplikacja nie zostanie
skompilowana.
Na szybkim komputerze program moe zosta skompilowany i uruchomiony z tak prdkoci,
i nie dostrzeemy w ogle jego dziaania, gdy od razu zostanie zamknity. Abymy to my
mogli decydowa, kiedy aplikacja zostanie zamknita, naley doda do programu
odpowiednie instrukcje. Jakie? Wskazwki znajdziesz w dalszej czci rozdziau.

rednik koczy instrukcj


Bardzo wana sprawa, czsto staje si rdem bdw chodzi o rednik koczcy
instrukcj. Kompilator jest tylko programem, ktry m.in. analizuje nasz kod rdowy. Piszc
kod, musimy poinstruowa kompilator, i w tym miejscu nastpuje zakoczenie danej
instrukcji. Oznaczamy to, stawiajc na kocu danego wyraenia rednik (taka zasada
obowizuje w wikszoci nowoczesnych jzykw programowania).
Zwr uwag na powysz instrukcj wywietlajc tekst na konsoli. Jest ona zakoczona
rednikiem. Gdyby go nie byo, kompilator w trakcie kompilacji wywietliby bd: ;

expected.

Jest to bardzo wana zasada i musisz o niej pamita. Nie martw si po jakim czasie znak
rednika na kocu wyraenia bdziesz stawia automatycznie.

Program musi posiada klas


Kolejne pojcie, jakie naley wprowadzi, to klasa. O klasach szczegowo opowiem w
rozdziale 5., jednak aby kontynuowa nauk, musisz chocia wiedzie, czym one s. Kada
klasa musi mie nazw poprzedzon sowem kluczowym class:
class Foo

Nazw klasy od sowa kluczowego musi dzieli co najmniej jedna spacja. Klasy mog
zawiera m.in. metody, takie jak metoda Main. Zawarto klasy musi mieci si pomidzy
klamrami { oraz }:
class Bar { }

Tak naprawd kada aplikacja jzyka C# musi posiada przynajmniej jedn klas lub
struktur! O strukturach opowiem w dalszej czci ksiki; teraz jedynie zaznaczam ten fakt,
gdy moe on mie znaczenie dla osb, ktre programoway wczeniej np. w jzyku C++.

Wcicia, odstpy
Z punktu widzenia kompilatora nie jest istotne, jak pisany jest kod, czy zawiera odstpy oraz
wcicia. Rwnie dobrze moe by pisany w jednej linii:
class Foo { static void Main(string[] args) {
System.Console.WriteLine("Witaj wiecie!");
} }

Czytelno takiego kodu pozostawia jednak wiele do yczenia i dlatego taki sposb pisania
nie jest zalecany.

Sowa kluczowe

Kady jzyk programowania posiada specyficzne elementy, tzw. sowa kluczowe. Mog one
oznacza rozkaz czy instrukcj, ktre s w dany sposb interpretowane przez kompilator.
Standardowo w rodowisku Visual C# Express Edition sowa kluczowe wyrniane s
kolorem niebieskim. Do sw kluczowych C# mona zaliczy m.in. class, void, static,
string. Przykadowo, sowo class oznacza deklaracj klasy o danej nazwie. Deklaracja w
kontekcie jzyka programowania moe zwyczajnie oznacza utworzenie danego elementu
(np. klasy).

Symbole
Symbole to pojedyncze znaki, ktre wraz ze sowami kluczowymi tworz skadni.
Przykadowo, znak cudzysowu (") okrela pocztek oraz koniec acucha tekstu. Sprbuj
usun cudzysowy z instrukcji wypisujcej tekst na konsoli:
System.Console.WriteLine(Witaj wiecie!);

Podczas prby kompilacji kompilator zasygnalizuje bd, poniewa nieznane s dla niego
instrukcje Witaj oraz wiecie, ktre wraz z cudzysowem tworz acuch, a zwarto
acucha nie jest przez niego sprawdzana pod wzgldem wystpienia danych polece.

Komentarze
Najprostszym elementem kadego jzyka programowania s komentarze. W kodzie
rdowym moesz dodawa wzmianki informacje absolutnie niemajce wpywu na
dziaanie programu. W trakcie kompilacji s one usuwane przez kompilator i nie znajduj si
w pliku
wynikowym .exe. Oczywicie kompilator nie usuwa tych komentarzy fizycznie, tzn. ze rda
programu.
Komentarze maj ogromny wpyw na proces powstawania aplikacji, szczeglnie jeeli
pracujesz w grupie. W kodzie moesz zawrze informacje przeznaczone dla innych
czytajcych go programistw, o tym jak on dziaa i co robi.
Podstawowym typem w jzyku C# s komentarze jednej linii w stylu jzyka C++. Przykad:
class Foo
{
// to jest komentarz
// metoda Main tutaj zaczynamy dziaanie!
static void Main(string[] args)

{
System.Console.WriteLine("Witaj wiecie!");
}
}

Tekst znajdujcy si po znakach // nie bdzie brany pod uwag przez kompilator. W
nowoczesnych edytorach kodu (takich jak w rodowisku Visual C#) komentarze s specjalnie
wyrniane (w moim przypadku kolorem zielonym). Tego typu komentarze nazywane s
komentarzami jednej linii, z tego wzgldu i obowizuj jedynie w linii, ktra rozpoczyna si
od znakw //:
// tu jest komentarz
ale tutaj ju nie obowizuje

W C# dostpne s take komentarze w stylu jzyka C, dziki ktrym moesz skomentowa


wiele linii tekstu:
/*
Tutaj jest komentarz
Tutaj rwnie...
*/

Rozpoczcie oraz zakoczenie bloku komentarza okrelaj znaki /* oraz */.


Komentarze mog by w sobie zagniedane, przykadowo:
/* komentarz w stylu C
// komentarz jednej linii
*/
Istnieje jeszcze jeden typ komentarza zwizanego z dokumentacj jzyka XML, lecz omwi
to w 13. rozdziale ksiki.

Podzespoy, metody, klasy


Powiedzielimy sobie ju o symbolach oraz sowach kluczowych, ktrymi mona si
posuy w trakcie pisania programw. Nim przejdziemy dalej, musz Ci wyjani kilka
dodatkowych poj. W poprzednim rozdziale wspominaem o rodowisku .NET Framework
oraz bibliotece klas jako o gwnym narzdziu projektowania aplikacji.
Chciabym w tym momencie wyjani pewn kwesti. Ot rodowisko .NET
Framework udostpnia programistom szereg klas i bibliotek, ktre uatwiaj proces
programowania. Jest to co na wzr WinAPI, o ktrym wspominaem w poprzednim

rozdziale.
Gwn bibliotek w .NET Framework jest mscorlib.dll. Zawiera ona przestrze nazw
System, ktra z kolei zawiera klas Console! Klasy z kolei zawieraj metody, m.in.
WriteLine, ktra suy do wypisywania tekstu na konsoli. Jak widzisz, system zalenoci jest
do skomplikowany, a wielo poj, jakie trzeba opanowa, moe przyprawi o bl gowy!
Dlatego na razie nie bd Ci zadrcza skomplikowanymi definicjami oraz opisami tych
poj bdziesz je poznawa stopniowo w trakcie czytania tej ksiki.
Na tym etapie wane jest aby wiedzia, e istniej metody, ktre na wzr funkcji
realizuj gotowe zadania takie jak np. wypisywanie tekstu na konsoli. Mona powiedzie, e
s to polecenia, ktre mona wykorzysta w aplikacji, chocia taka terminologia nie jest
raczej dopuszczalna. Nie zagbiajc si w szczegy, postaram si opisa kilka
podstawowych elementw jzyka programowania w C#, ktre do czsto bd
wykorzystywane w dalszej czci ksiki.

Funkcje
Funkcje jako takie nie istniej w C#! Zamiast tego mwimy o wspomnianych ju w tej
ksice metodach. Idea jest w zasadzie identyczna, ale aby unikn nieporozumie, bd si
stara nie uywa sowa funkcja. Z tym sowem spotkasz si zapewne nie raz w swojej
karierze, gdy mechanizm funkcji jest obecny w wielu jzykach programowania.
Funkcje to wydzielony blok kodu realizujcy jakie zadanie.
Chciabym w paru sowach przybliy ide tzw. programowania proceduralnego.
Idea programowania proceduralnego zacza si pojawia wraz z bardziej zaawansowanymi
aplikacjami. Tradycyjny modu projektowania nie sprawdza si dobrze, gdy programy
zaczy by bardziej skomplikowane wwczas ich konserwacja i naprawianie bdw byy
niezwykle trudne.
Kto mdry wymyli wtedy, e mona by byo dzieli program na mniejsze czci tzw.
procedury. Przykadowo, jeeli napisano kod, ktry wywietla pewien komunikat i koczy
dziaanie programu, a w fragment jest uywany wiele razy w tej aplikacji, to naleaoby go
dublowa wiele razy. Powoduje to nie tylko zwikszenie objtoci kodu, ale rwnie potguje
podatno na bdy. Bo co si stanie, jeeli wanie w tym maym, wielokrotnie powtrzonym
w aplikacji fragmencie, wystpi bd? Naleaoby wwczas przeszuka cay kod i w kadym
miejscu poprawia usterk.
Teraz, w nowoczesnych jzykach programowania mona umieci pewien fragment kodu w
procedurze i wywoa j za kadym razem, kiedy zajdzie potrzeba jego wykonania!
Generalnie w jzykach takich jak C++, PHP, Java nie istniej procedury, lecz funkcje. Sama
idea jest identyczna, ale z uwagi na to, i w C# procedury nie istniej, nie bd o nich wicej
wspomina.

Metody
Mam nadziej, e masz ju w gowie pewien zarys tego, czym jest metoda. Ju wkrtce
nauczysz si pisa wasne metody oraz klasy. Aktualnie jedyne, co musisz wiedzie, to to, e
metody mog posiada tzw. parametry. Spjrz na poprzedni przykad uycia metody
WriteLine(). Parametrem tej metody by tekst Hello World!. Innymi sowy
przekazujemy metodzie tekst do wywietlenia na konsoli. Moesz myle o parametrach jak o
danych wejciowych przekazujesz metodzie dane, na ktrych ona operuje.
Obowizkowym elementem kadej metody s nawiasy, w ktrych podaje si parametry.
Jednak nie wszystkie metody w rodowisku .NET Framework maj parametry w takim
wypadku pozostawiamy pusty nawias, np.:
System.Console.Read();

Klasy
O klasach mona powiedzie, i jest to zestaw metod. Przykadowo, klasa Console zawiera
zestaw metod sucych do operowania na konsoli. Myl o klasach jak o przyborniku,
paczuszce zawierajcej przydatne narzdzia.
rodowisko .NET Framework udostpnia szereg klas gotowych do uycia. Przykadowo,
chcemy napisa program, ktry obliczy logarytm z danej liczby. Zamiast samemu mczy si
z pisaniem odpowiedniego kodu, moemy wykorzysta klas Math, ktra jest czci
rodowiska .NET Framework, i udostpniane przez ni mechanizmy.

Przestrzenie nazw
rodowisko .NET Framework jest na pierwszy rzut oka do specyficzne dla osb, ktre
programoway wczeniej na platformie Win32 lub dopiero si ucz. Bo c oznacza zapis:
System.Console.WriteLine("Witaj wiecie!");

Przede wszystkim jest strasznie dugi! Mamy tutaj kilka instrukcji oddzielonych znakiem
kropki. Czy nie atwiej i krcej byoby pisa po prostu sam nazw metody?

WriteLine("Witaj wiecie!");

rodowisko .NET Framework jest o wiele bardziej rozbudowane od swojego poprzednika


systemu WinAPI. Zawiera tysice klas i innych typw, kady posiada inn nazw. W
rodowisku Win32 nie mogo si zdarzy, e istniay dwie funkcje o takiej samej nazwie 1 , co
jest moliwe w .NET Framework.
Przykadowo, metoda WriteLine() wypisuje tekst, ale istnieje rwnie metoda
WriteLine(), ktra zapisuje go do pliku (metoda klasy TextWriter). Ich jednoczesna
obecno jest niewykluczona, poniewa nale do innych klas.
Podobnie jest w przypadku przestrzeni nazw (ang. namespace). W obrbie kilku przestrzeni
nazw mog istnie klasy o tej samej nazwie. Np.:
namespace Bar
{
class Hello
{
}
}
namespace Foo
{
class Hello
{
}
}

Operator kropki
Pojcie operator zostanie wprowadzone w dalszej czci rozdziau. Znak kropki (.) stanowi
separator pomidzy nazw przestrzeni nazw, klas a nazw metody. Przy pomocy tego
operatora otrzymujemy dostp do elementw danej klasy czy przestrzeni nazw.
Uywajc rodowiska Visual Studio.NET (jak rwnie Visual C# Express Edition), moesz
korzysta z narzdzi wspomagajcych tworzenie aplikacji. Wykonaj mae dowiadczenie. W
edytorze napisz sowo System., koczc je kropk. Po chwili powinna pojawi si rozwijana
lista zawierajca list klas oraz innych zagniedonych przestrzeni nazw, ktre moemy
wykorzysta (rysunek 3.2).

Rysunek 3.2. Lista klas oraz przestrzeni nazw w obrbie przestrzeni System

Sowo kluczowe using


Pisanie za kadym razem nazwy przestrzeni nazw, a nastpnie klasy i metody moe by nieco
mczce. Dlatego te mona wykorzysta sowo kluczowe using, ktre informuje
kompilator, e w programie bdziemy korzystali z klas znajdujcych si w danej przestrzeni
nazw (np. System):
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}

Zwr uwag, e sowo kluczowe using znajduje si jeszcze przed klas Program.

Zmienne
W kadym programie, ktry ma wicej ni kilkanacie linijek kodu, zachodzi konieczno
przechowywania tymczasowych danych w pamici komputera. Takie dane przechowywane s
jedynie w trakcie dziaania programu.
Zmienne definiujemy jako obszar w pamici komputera, ktry suy do przechowywania
danych tymczasowych (obecnych w pamici do czasu wyczenia programu), majcych posta
liczb, tekstu itp.
Zapisywanie danych w pamici komputera w obecnych jzykach programowania jest bardzo
proste. Naley okreli unikaln nazw, pod ktr bdziemy uzyskiwa dostp do danych.

Deklarowanie zmiennych
Operacja utworzenia zmiennej nazywana jest deklaracj zmiennej. Musimy okreli unikaln
nazw zmiennej, ktra nie moe si powtarza w obrbie danej klasy czy metody. Musimy
rwnie okreli typ danej zmiennej, czyli zidentyfikowa dane, jakie bdziemy
przechowywa w pamici (tekst, liczby itp.).
Przykadowe zadeklarowanie zmiennej przedstawiem poniej:
class Foo
{
static void Main(string[] args)
{
string Bar;
}
}

W powyszym programie utworzyem zmienn o nazwie Bar oraz typie string. Jak
widzisz, sposb deklaracji jest bardzo prosty, schemat mona przedstawi w ten sposb:

Nazw od typu musi dzieli co najmniej jedna spacja.

Deklaracja kilku zmiennych


Najczciej bywa tak, i w aplikacji potrzebujemy wielu zmiennych. Przykadowo, jedna
zmienna przechowuje pobrany login uytkownika, druga imi uytkownika, a trzecia
jego nazwisko.
Potrzebujemy wobec tego trzech zmiennych. Jeeli wszystkie zmienne s tego samego typu
(string), moemy zadeklarowa je w ten sposb:
string Login, FName, LName;

Nazwy zmiennych musimy oddzieli znakiem przecinka. Z punktu widzenia kompilatora nie
ma znaczenia to, w jaki sposb deklarujesz zmienne, wic rwnie dobrze moesz je
zadeklarowa w ten sposb:
string Login;
string FName;
string LName;

Zwr uwag, e instrukcja deklarowania zmiennej zakoczona jest znakiem rednika!

Przydzia danych
Zadeklarowalimy ju zmienn, lecz nie przypisalimy do niej adnych danych, wic jej
zawarto jest pusta. Przypisanie danych do zmiennej jest rwnie proste jak deklaracja. W
tym celu uywamy operatora przypisania (=):
class Foo
{
static void Main(string[] args)
{
string Bar = "Hello World";
}
}

W tym momencie zawarto zmiennej Bar to tekst Hello World.

Kady tekst zadeklarowany w ramach zmiennej musi by ujty w cudzysowie.


Zmodyfikujemy nieco nasz ostatni program, tak aby tekst wywietlany w oknie konsoli by
odczytywany ze zmiennej. Spjrz na poniszy kod:
using System;
class Foo
{
static void Main(string[] args)
{
string Bar = "Hello World";
Console.WriteLine(Bar);
Console.Read(); // czekaj na reakcj uytkownika!
}
}

W metodzie WriteLine zamiast tekstu umiecilimy nazw zmiennej. W trakcie dziaania


programu odczytywana jest zawarto zmiennej Bar (czyli Hello World) i przekazywana jest
metodzie WriteLine, co oczywicie skutkuje wypisaniem tekstu na konsoli.
Zwr uwag, e w programie wykorzystaem metod Read, ktra w tym wypadku czeka na
reakcj uytkownika. Program zostanie zamknity wwczas, gdy uytkownik nacinie klawisz
Enter.
Jak sama nazwa wskazuje, zmienne (lub raczej ich zawarto) mog by modyfikowane w
trakcie dziaania programu. To, e przypisalimy zawarto zmiennej ju w momencie jej
deklaracji, nie oznacza, e nie mona tego zmieni:
string Bar;
Bar = "Hello World";
Bar = "Hello my darling!";
Console.WriteLine(Bar);

Jak widzisz, tutaj napisaem zawarto zmiennej; na skutek tego na konsoli wywietlony
zostanie tekst Hello my darling!.

Typy danych
Do tej pory deklarowalimy zmienne typu string, co oznaczao, i su one do

przechowywania tekstu. Jzyk C# oferuje kilka innych typw danych, ktrymi moemy
posugiwa si we wasnych aplikacjach. Przykadowo, typ int suy do przechowywania
liczb cakowitych:
int X = 10;

Jest to bardzo popularny typ danych. Typy danych jzyka C# rni si od siebie tzw.
zakresem. Np. maksymalna warto, jaka moe zosta przypisana do zmiennej typu int, to
2,147,483,647, natomiast maksymalna moliwa warto typu byte to 255.
Typy danych rni si od siebie rwnie iloci pamici, jak pochaniaj. Np. typ int
zajmuje w pamici 4 bajty, a typ byte jedynie 1. W tabeli 3.1 zaprezentowane zostay
wbudowane typy danych jzyka C# wraz z maksymalnym zakresem.
Tabela 3.1. Wbudowane typy danych jzyka C#
Typ danych
Zakres
byte
od 0 do 255
sbyte
od 128 do 127
short
od 32,768 do 32,767
int
od 2,147,483,648 do 2,147,483,647
uint
od 0 do 4,294,967,295
long
od 9,223,372,036,854,775,808 do 9,223,372,036,854,775,807
ulong
od 0 do 18,446,744,073,709,551,615
float
od 3.402823e38 do 3.402823e38
double
od 1.79769313486232e308 do 1.79769313486232e308
decimal
od 79228162514264337593543950335 do 79228162514264337593543950335
char
Pojedynczy znak
string
acuch znakw typu char
bool
true lub false

Jak widzisz, mamy do dyspozycji ca gam typw, zarwno stao-, jak i


zmiennoprzecinkowych. Specyficznym typem danych jest bool, ktry moe przybiera
jedynie dwie wartoci true lub false. Jednake jest on do czsto wykorzystywany
przez programistw, o czym przekonasz si w trakcie czytania tej ksiki. Przykad uycia
typu bool:
bool MyBool;
MyBool = true;

Restrykcje w nazewnictwie

Nie jest do koca prawd, e nazwa zmiennej moe by zupenie dowolna. Niestety, istniej
pewne restrykcje, o ktrych trzeba wiedzie. Na przykad pierwszym znakiem nazwy
zmiennej nie moe by cyfra nazwa ta musi rozpoczyna si od litery.
Nazwa zmiennej moe jednak zawiera na pocztku _, ale ju inne znaki, takie jak ( ) * & ^
% # @ ! / = + - [ } ] } ' " ; , . czy ?, nie s dozwolone.
Platforma .NET oraz sam jzyk C# obsuguj standard kodowania znakw Unicode, dlatego
w nazwach zmiennych mona uywa polskich znakw lub jakichkolwiek innych
wchodzcych w skad Unicode:
byte _gegka = 234;

Stae
Stae od zmiennych odrnia to, e zawarto przydzielana jest jeszcze w trakcie pisania kodu
i nie ulega pniejszym zmianom. Zawartoci staych nie mona zmienia w trakcie dziaania
aplikacji. Raz przypisana warto pozostaje w pamici komputera a do zakoczenia
dziaania aplikacji:
const double Version = 1.0;
Version = 2.0; // w tej linii kompilator wskae bd

Stae deklarowane s prawie identycznie jak zmienne. Jedyna rnica to konieczno


poprzedzenia deklaracji sowem kluczowym const.
Do czego mog przyda si stae? Przykadowo, w staej Version moesz przechowywa
numer wersji swojej aplikacji. Taki numer wersji moesz wywietla w oknie O programie
oraz w innych miejscach kodu rdowego. Jeeli uznasz, e naley zmieni numer wersji
aplikacji, po prostu zmodyfikujesz zawarto staej. Nie musisz kadorazowo zmienia
fragmentw kodu, w ktrych wywietlasz wersj programu.

Operacje na konsoli
Wiesz ju, czym s aplikacje konsolowe. Nie posiadaj one adnych okien, kontrolek itp.,
interakcja z uytkownikiem jest wic saba. Program moe jedynie wypisywa tekst na
konsoli (WriteLine()) lub odczyta tekst wpisany przez uytkownika. Moge zauway, e
program, ktry napisalimy wczeniej, zamyka si zaraz po uruchomieniu i wywietleniu
tekstu. To dlatego, e nie nakazalimy mu czeka na pozwolenie uytkownika.

Metoda ReadLine() umoliwia odczytanie tekstu wpisanego w oknie konsoli. Program


zostaje wwczas wstrzymany do czasu nacinicia klawisza Enter. Napiszmy prost
aplikacj, ktra pobierze od uytkownika jego imi, a nastpnie zapisze je w zmiennej. Kod
rdowy takiego programu prezentuje listing 3.1.
Listing 3.1. Program pobierajcy imi uytkownika
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Cze, jak masz na imi?");
string name; // deklaracja zmiennej
name = Console.ReadLine(); // pobranie tekstu
wpisanego przez uytkownika
Console.WriteLine("Mio mi " + name + ". Jak si
masz?");
Console.ReadLine();
}
}

Moesz skompilowa, a nastpnie uruchomi tak aplikacj. Umoliwia ona wpisanie


imienia, ktre nastpnie wywietli. Przeanalizujmy ten program. Wikszo zawartych
instrukcji powinna by ju dla Ciebie znana, objanienia wymaga linia pobierajca imi
uytkownika:
name = Console.ReadLine();

name to nazwa uprzednio zadeklarowanej zmiennej typu string. Taka konstrukcja nakazuje
przypisanie do zmiennej wartoci tekstu wpisanego i pobranego w oknie konsoli. Innymi
sowy, zmienna name zawiera imi pobrane przez uytkownika.
Kolejna linia kodu, ktr trzeba objani, to:
Console.WriteLine("Mio mi " + name + ". Jak si masz?");

Jak widzisz, acuch tekstu zosta tutaj rozdzielony za pomoc znakw + poczono
go w jedn cao. W trakcie wykonywania instrukcji w miejsce name zostanie podstawione
imi pobrane od uytkownika.

Metody klasy Console


Do tej pory poznae dwie metody klasy Console WriteLine oraz ReadLine. W
rzeczywistoci klasa udostpnia wicej metod, z ktrych najwaniejsze zaprezentowaem w
tabeli 3.2.
Tabela 3.2. Wybrane metody klasy Console
Metoda
Opis
Beep
Umoliwia odegranie dwiku z gonika systemowego.
Clear
Czyci ekran konsoli.
ResetColor
Ustawia domylny kolor ta oraz tekstu.
SetCursorPosition Ustawia pozycj kursora w oknie konsoli.
SetWindowPosition Umoliwia ustawienie pooenia okna konsoli.
SetWindowSize
Umoliwia okrelenie rozmiaru okna konsoli.

Oto prosta aplikacja demonstrujca wykorzystanie tych metod:


using System;
class Program
{
static void Main(string[] args)
{
// ustaw rozmiar okna
Console.SetWindowSize(60, 30);
// ustaw pooenie tekstu
Console.SetCursorPosition(10, 10);
Console.WriteLine("Hello World!");
Console.ReadLine();
Console.Clear();
Console.Beep();
}
}

Naley zaznaczy, e parametry tych metod nie oznaczaj rozmiaru przedstawionego w


pikselach! Przykadowo, pierwszym argumentem metody SetWindowSize jest szeroko okna
konsoli wyraona w iloci kolumn tekstu. Natomiast drugi parametr okrela wysoko okna
wyraon w iloci wierszy (jedna linia tekstu rwna si jednemu wierszowi).

Waciwoci klasy Console

Poznae ju pojcie metoda klasy, potrafisz korzysta z metod, jak rwnie przypisywa oraz
odczytywa dane ze zmiennych. Waciwoci klasy umoliwiaj okrelenie pewnych
zachowa danej klasy.
Przykadowo, klasa Console posiada waciwo BackgroundColor, ktra umoliwia nadanie
koloru ta w oknie konsoli. Ta sama klasa posiada waciwo Title, do ktrej moemy
przypisa tekst, jaki bdzie umieszczony na belce tytuowej okna konsoli.
Myl o waciwociach jak o zwykych zmiennych, do ktrych mona przypisywa dane, a
take je odczytywa. Oto przykadowe uycie dwch wspomnianych waciwoci:
using System;
class Program
{
static void Main(string[] args)
{
// nadanie wartoci dla waciwoci
Console.Title = "Hello World";
// okrelenie koloru ta (ciemny ty)
Console.BackgroundColor = ConsoleColor.DarkYellow;
// odczyt wartoci waciwoci
Console.WriteLine("Tytu tego okna to: " +
Console.Title);
Console.ReadLine();
}
}

Po uruchomieniu takiego programu w oknie konsoli wypisany zostanie tekst: Tytu tego
okna to Hello World. Na samym pocztku przypisaem warto do waciwoci Title
(warto Hello World), a nastpnie okreliem to tekstu. Niezrozumiaa dla Ciebie moe by
operacja okrelenia ta tekstu:
Console.BackgroundColor = ConsoleColor.DarkYellow;

Dlaczego robimy to w ten, a nie inny sposb? Ot waciwo BackgroundColor wymaga,


aby przypisywane do niej dane byy typu ConsoleColor. Tak jak w przypadku zmiennych:
zmiennej typu string nie moemy przypisa liczby i odwrotnie.
ConsoleColor nie jest klas, a tzw. wyliczeniem. To pojcie wyjani w dalszej czci ksiki.

Operatory
W kadym jzyku programowania wysokiego poziomu istniej symbole suce do
sterowania programem. Takie znaki nazywane s przez programistw operatorami. W trakcie
czytania tej ksiki miae okazj zastosowa operator przypisania (=). W rzeczywistoci
istnieje jednak o wiele wicej operatorw, ktre naley omwi.
Symbole operatorw w jzyku C# s praktycznie identyczne z tymi z jzyka C++ oraz Java.
Jeeli wic programowae wczeniej w jednym z tych jzykw, nie bdziesz mia problemu z
zapamitaniem poszczeglnych symboli.

Operatory porwnania
Czynno porwnywania stosujemy w naszym codziennym yciu. Jestemy w stanie na
przykad okreli, ktra z dwch osb jest wysza. Na podstawie liczby koni mechanicznych
silnikw jestemy w stanie oceni, ktry z nich ma wiksz moc.
W matematyce rwnie obowizuj takie znaki porwnania jak > (znak wikszoci) i < (znak
mniejszoci). Identyczne symbole s wykorzystywane w jzykach programowania (patrz
tabela 3.3).
Tabela 3.3. Operatory porwnania
Operator
Jzyk C#
Nierwnoci
!=
Rwnoci
==
Wikszoci
>
Mniejszoci
<
Wiksze lub rwne >=
Mniejsze lub rwne <=

W C# moemy porwnywa wartoci typw np. wartoci zmiennych typu int. Przykady
porwnywania wartoci poka podczas omawiania tzw. instrukcji warunkowych w dalszej
czci tego rozdziau.

Operatory arytmetyczne
Nauka, jak jest matematyka, dawno temu wyksztacia umowne symbole opisujce pewne
dziaania, jak np. dodawanie czy odejmowanie. Oczywicie komputer jako maszyna
umoliwia wykonywanie tych czynnoci w bardzo prosty sposb przy wykorzystaniu symboli

identycznych jak w matematyce (tabela 3.4).


Tabela 3.4. Operatory arytmetyczne
Operator
Jzyk C#
Dodawanie
+
Odejmowanie
Mnoenie
*
Dzielenie rzeczywiste /
Dzielenie cakowite /
Reszta z dzielenia
%

Mona tu zauway pewn rnic w porwnaniu z tym, czego uczylimy si w szkole


podstawowej. W szkole bowiem jako znaku mnoenia uywalimy kropki, natomiast
komputerowym symbolem mnoenia jest gwiazdka (*). To samo dotyczy dzielenia w
szkole znak :, a w C# symbol /.
Jak widzisz, w tabeli 3.4 wyszczeglniem pozycje: Dzielenie rzeczywiste oraz Dzielenie
cakowite. Te operacje realizuje ten sam operator, ale to, czy zostanie zachowana reszta z
dzielenia, czy te nie, zaley od typu danych. Spjrz na poniszy przykad:
double d = 5.0;
int i = 5;
Console.WriteLine(5.0 / 5); // 1
Console.WriteLine(-i / 2); // -2
Console.WriteLine(-d / 2); // -2.5

W komentarzach zawarem rezultat zwracany przez te operacje. Zwr uwag, i zmienna i


jest typu int i pomimo e 5 dzielone przez 2 wynosi 2.5, reszta z dzielenia zostanie usunita.
Warto wspomnie kilka sw o operatorze dodawania (+). Jak zapewne zwrcie uwag, we
wczeniejszej czci tego rozdziau by on uywany rwnie do czenia ze sob dwch
acuchw. Oto inne przykady wykorzystania operatora:
Console.WriteLine(2 + 2); // rezultat: 4
Console.WriteLine(2 + "2"); // rezultat: 22
Console.WriteLine(2 + .2); // rezultat: 2.2

Jak widzisz, operator dziaa rnie, zalenie od sytuacji, w jakiej zosta uyty.

Operator inkrementacji oraz dekrementacji

W jzykach takich jak C#, C/C++, Java czy PHP istniej bardzo wygodne operatory
pozwalajce na szybk inkrementacj (zwikszenie wartoci danej zmiennej) oraz
dekrementacj (zmniejszenie wartoci danej zmiennej).
Umieszczajc przed zmienn lub po niej symbol ++, powodujemy zwikszenie jej wartoci o
1. Analogicznie operator -- zmniejsza zawarto zmiennej o 1. Spjrz na poniszy fragment
programu:
int i = 5;
Console.WriteLine(i++);
Console.WriteLine(++i);

Jak mylisz, co zostanie wywietlone w oknie konsoli po wykonaniu takiego kodu? Cyfra 5
oraz 7. Pooenie operatora (przed lub po nazwie zmiennej) ma ogromne znaczenie. W
pierwszym przypadku metoda WriteLine() wywietli warto zmiennej i (czyli 5), a
nastpnie zwikszy j o 1. W drugim przypadku zwikszenie wartoci zmiennej i nastpi
przed wysaniem jej do metody WriteLine().
Oczywicie aby zwikszy warto danej zmiennej, mona zastosowa operator arytmetyczny
wraz z przypisaniem:
int x = 5;
x = x + 1; // rezultat: 6

Operatory logiczne
Operatory logiczne czsto s nazywane operatorami boolowskimi (ang. Boolean operators).
Wynika to z tego, e realizuj one operacje waciwe dla algebry Boolea.
Faktycznym zastosowaniem tych operatorw jest testowanie kilku warunkw. Wemy jaki
przykad z ycia codziennego: Jeeli bd mia 18 lat i 20 tysicy z, kupi sobie samochd.
W tym zdaniu operatorem jest i. Do spenienia kryterium (kupna samochodu) jest zatem
niezbdne spenienie cznie dwch warunkw (posiadania 20 tysicy z oraz ukoczenia 18
lat). Jeeli ktry z tych warunkw nie bdzie prawdziwy kryterium, czyli kupno
samochodu, nie zostanie spenione.
Podobny przykad mona przenie na platform programow. Na przykad jeeli zmienna X
posiada warto 20, a zmienna Y warto 10, to zrb to i tamto. Takie przykady bdziemy
realizowa nieco dalej tymczasem przyjrzyjmy si dostpnym w C# operatorom logicznym
(tabela 3.5).
Tabela 3.5. Operatory logiczne

Operator Jzyk C#
Logiczne i &&
Logiczne lub ||
Zaprzeczenie !

Operatory bitowe
Operatory bitowe oferuj nieco bardziej zaawansowane dziaania na liczbach binarnych. Nie
bdziemy si tym zajmowa wane, aby wiedzie, e takie manipulacje s w C# moliwe,
lecz wymagana jest do tego wiksza wiedza na temat dziaania komputera oraz rnych
systemw liczbowych (dziesitny, dwjkowy itp.).
Operatory bitowe prezentuje tabela 3.6.
Tabela 3.6. Operatory bitowe
Jzyk C#
Operator
Koniunkcja
&
Zaprzeczenie
~
Alternatywa
|
Dysjunkcja
^
Przesunicie w lewo <<
Przesunicie w prawo >>

Operatory przypisania
Sprawa jest dosy prosta. Przy pomocy tego operatora do zmiennej lub waciwoci
przypisujemy okrelone dane:
foo = 4;
bar = foo;

Po lewej stronie od znaku = musi znajdowa si nazwa zmiennej, a po prawej


przypisywana warto, ktra rwnie moe znajdowa si w zmiennej.
Oprcz tego prostego operatora jzyk C# udostpnia wiele innych, ktre umoliwiaj
przypisanie jakiej wartoci wraz z wykonaniem na niej danej czynnoci. Przykadowo:

x += 2;

Oznacza to: do wartoci zmiennej x dodaj cyfr 2. Rwnie dobrze mona to wykona w
nastpujcy sposb:
x = x + 2;

Jednak poprzednie rozwizanie jest czciej uywane i przejrzystsze.


Pozostae operatory przypisania to: -=, *=, /=, %=, &=, |=, ^=, <<=, >>=.

Inne operatory
Naturalnie pokrtce omwione przeze mnie grupy operatorw to nie wszystko, co oferuje
jzyk C#. Na tym etapie nauki nie ma jednak sensu prezentowanie bardziej zaawansowanych
operatorw, gdy zwizane s one z pojciami, ktrych jeszcze nie omwiem. Kolejne
operatory bd omawia w dalszej czci ksiki, wraz z prezentowaniem nowych porcji
zagadnie.

Instrukcje warunkowe
Przed chwil bya mowa o operatorach jzyka C#, jednak bez sensu byoby opisywanie ich
bez wzmianki o instrukcjach warunkowych. S to konstrukcje, ktre su do sprawdzania,
czy dany warunek zosta speniony. Jest to praktycznie podstawowy element jzyka
programowania dziki instrukcjom warunkowym moemy odpowiednio zareagowa na
istniejce sytuacje i sterowa prac programu.
Przykadowo: uytkownik musi wpisa swoje imi na samym pocztku dziaania programu.
Moe si jednak zdarzy, e uytkownik specjalnie lub omykowo wpisze liczb. Jeeli
programista nie uwzgldni tej moliwoci i nie wprowadzi odpowiednich zabezpiecze, moe
si to skoczy le dla programu lub (w przypadku wikszych aplikacji) spowodowa bdy
zwizane z bezpieczestwem systemu!
Ostatnimi czasy w przegldarce Internet
Explorer wykryto powane bdy zwizane z bezpieczestwem, ich powodem by pasek
adresw. Zagroenie powstawao w chwili wpisania w pasku adresw odpowiedniego cigu
znakw. Oczywiste jest, jak wane jest sprawdzanie danych dostarczanych przez
uytkownika. Podstawowa zasada brzmi: nie wolno ufa danym podawanym aplikacji przez
uytkownika zawsze naley je sprawdza przed dalszym dziaaniem programu.

Instrukcja if
Podstawow instrukcj warunkow w jzyku C# jest if. Sowo if oznacza w jzyku angielskim
jeeli. Ta instrukcja warunkowa umoliwia przetestowanie jakiego warunku (np.
porwnanie, czy warto zmiennej X jest wiksza ni zmiennej Y) i zaprogramowanie
odpowiedniej reakcji, jeeli zostanie on speniony.
Programujc, uywamy instrukcji warunkowych cay czas. Wyobra sobie, e chcesz napisa
program, ktry pyta uytkownika o imi. Jeeli imi koczy si liter a, moemy z duym
prawdopodobiestwem stwierdzi, e jest ono kobiece. W takim wypadku program wywietla
na ekranie konsoli tekst: Witaj koleanko!. W przeciwnym wypadku wywietlamy tekst:
Witaj kolego!. Do tego wanie su instrukcje warunkowe.
Oto przykadowy program wykorzystujcy instrukcj warunkow if:
using System;
class Program
{
static void Main(string[] args)
{
int x = 5;
if (x == 5)
{
Console.WriteLine("Zmienna x ma warto 5!");
}
Console.ReadLine();
}
}

W tym wypadku instrukcja warunkowa sprawdza, czy warto zmiennej x rwna si 5.


Przypomn, i operator == suy do sprawdzania, czy obie wartoci s sobie rwne.
Oglna budowa instrukcji if wyglda nastpujco:
if (warunek do spenienia)
{
operacje do wykonania, jeeli warunek jest prawdziwy;
}
Obowizkowym elementem kadej instrukcji if s nawiasy okrge, w ktrych musi znale
si warunek do sprawdzenia. Warunek moe zosta speniony (czyli wynik pozytywny) albo
nie. Od tego zaley, czy wykonany zostanie kod znajdujcy si pomidzy klamrami.
Klamry w jzyku C# s odpowiednikiem bloku begin oraz end z jzyka Pascal.

Klamry nie s obowizkowym elementem instrukcji if, aczkolwiek zalecanym ze wzgldu na


przejrzysto kodu. Jeeli pomidzy klamrami znajduje si tylko jedna instrukcja do
wykonania, spokojnie mona je usun:
if (x == 5)
Console.WriteLine("Zmienna x ma warto 5!");

Generalnie jednak zalecam stosowanie klamer niezalenie od tego, ile instrukcji mamy do
wykonania.
Klamry w jzyku C# peni rol swego rodzaju pojemnika; okrelaj np. pocztek oraz
zakoczenie metody czy klasy. Tak samo oznaczaj rozpoczcie oraz zakoczenie bloku
instrukcji warunkowej.

Instrukcje zagniedone
Instrukcje if mona dowoli zagnieda:
if (x == 5)
{
Console.WriteLine("Zmienna x ma warto 5!");
if (y >= 10)
{
Console.WriteLine("Drugi warunek rwnie speniony");
}
}

Jeeli pierwszy warunek zostanie speniony, wykonany zostanie kod wywietlajcy


wiadomo na ekranie konsoli. Nastpnie sprawdzony zostanie kolejny warunek i po raz
kolejny jeeli zostanie on speniony, wykonany zostanie odpowiedni kod.
Uwaga! Kada klamra, ktra zostanie otwarta ({), musi by zakoczona w dalszej czci
programu (}).

Kilka warunkw do spenienia


Zaprogramujmy teraz co trudniejszego. Przypomnij sobie temat operatorw, a konkretnie
operatorw logicznych. Umoliwiaj one sprawdzanie dwch warunkw w jednej instrukcji
warunkowej. Przykadowo, chcemy wywietli na konsoli tekst Dobry wieczr koleanko,

ale tylko wtedy, gdy uytkownik poda kobiece imi. Dodatkowo musimy sprawdzi aktualn
godzin, powiedzmy, witamy si zwrotem dobry wieczr, jeeli jest po godzinie 18:00.
Mamy wic dwa problemy. Po pierwsze musimy sprawdzi, jaka jest ostatnia litera imienia
podanego przez uytkownika. Po drugie musimy pobra aktualn godzin. By moe
wybiegam nieco w przyszo i nie powinienem na tym etapie prezentowa tak trudnych
przykadw, ale pragn urozmaici Ci nauk.
Pierwszy problem mona rozwiza nastpujco. Ot przy pomocy symbolu [ oraz ]
moemy odwoa si do dowolnego miejsca w acuchu tekstowym (np. zmiennej typu
string). Przykadowo:
string Foo = "Adam";
Console.WriteLine(Foo[1]);

Znaki w acuchu tekstowym numerowane s od zera. Tak wic imi Adam ma dugo 4
znakw, z tym e pierwszy z nich posiada indeks 0:
----------------Indeks
| Numer
A
| 0
d
| 1
a
| 2
m
| 3

Powyszy kod wywietli wic na konsoli liter d. W naszym programie musimy wic
sprawdzi, jaka jest ostatnia litera acucha (naszego imienia). No dobrze, ale skd program
ma wiedzie, jak dugie imi poda uytkownik? Nie wie! Dlatego musimy uy waciwoci
Length, ktra pobiera i zwraca dugo danego acucha (dugo liczona w iloci znakw w
tekcie). Oto rozwizanie problemu:
string Foo;
int FooLength;
Foo = "Adam";
FooLength = Foo.Length; // mamy dugo acucha
Console.WriteLine(Foo[FooLength -1]);

W takim programie zmienna FooLength bdzie zawiera dugo acucha Foo. Poniewa
chcemy pobra ostatni znak w tekcie, od tej dugoci musimy odj cyfr 1.
Zajmijmy si drugim problemem. Jak pobra aktualn godzin? W tym celu musimy
skorzysta z dobrodziejstw klas .NET Framework, a konkretnie z klasy DateTime. Posiada
ona waciwo Now, ktra zawiera informacje o aktualnej godzinie, dniu, tygodniu itd. Ta z
kolei posiada kolejn waciwo Hour, ktra zwraca aktualn godzin w formie liczby

cakowitej typu int.


Jak wspomniaem, w instrukcji warunkowej za jednym razem, moemy porwnywa kilka
warunkw. Spjrz na poniszy przykad:
string imie = Console.ReadLine();
if (imie[imie.Length - 1] == 'a' && DateTime.Now.Hour >= 18)
{
Console.WriteLine("Dobry wieczr koleanko!");
}

Uyem tutaj operatora && (logiczne i) do porwnania dwch warunkw. Innymi sowy,
sprawdzamy, czy ostatni liter zmiennej imie jest a oraz czy jest po godzinie 18:00. Jeeli te
dwa warunki zostan spenione, dopiero wwczas bdzie mg zosta wykonany odpowiedni
kod instrukcji (w tym wypadku wywietlenie komunikatu w oknie konsoli).
Zwr uwag, e w ostatnim przykadzie zamiast cudzysowu uyem apostrofw, aby
sprawdzi, czy ostatnim znakiem tekstu jest litera a. Wicej informacji na temat acuchw
znajdziesz w rozdziale 9.
W taki sposb moemy uywa dowolnych operatorw logicznych. Przykadowo, moesz
zastpi && operatorem ||:
if (imie[imie.Length - 1] == 'a' || DateTime.Now.Hour >= 18)
{
// dalszy kod
}

Teraz do wykonania kodu z ciaa instrukcji wystarczy spenienie jednego z dwch warunkw.
Innymi sowy: albo imi bdzie si koczy liter a, albo jest po godzinie 18:00.

Zastosowanie nawiasw
Na lekcjach matematyki w szkole podstawowej uczye si zapewne, e nawiasy okrge maj
najmniejsze znaczenie i ustpuj wanoci nawiasom kwadratowym oraz klamrom.
Zapomnij o tej regule w trakcie programowania. W jzyku C# w instrukcjach warunkowych
posugujemy si jedynie nawiasami okrgymi. Nie oznacza to jednak, e nie moemy
sterowa priorytetem operacji. Spjrz na przykad:
int x, y, z;
x = 2;
y = 4;
z = 10;

if ((x == 2 || y == 4) && z > 20)


{
Console.WriteLine("Yes, yes, yes!");
}

Zastosowaem tutaj podwjny nawias. Nakazaem tym samym, aby najpierw sprawdzony
zosta warunek z wewntrznego nawiasu. OK, jest on speniany, poniewa zmienna x zawiera
cyfr 2 i to wystarczy. Nastpnie sprawdzany jest drugi warunek, ktry nie zostaje speniony,
poniewa zmienna z ma warto 10, a wymagane jest, aby bya to liczba wiksza od 20.
Poniewa operator && wymaga, aby obydwa warunki zostay spenione, kod z ciaa instrukcji
if nie zostanie wykonany.
Podobnie jest w przypadku oblicze matematycznych. Jeeli chcemy wymusi wano
danego obliczenia, stosujemy nawiasy:
x = (2 + 2) * 2;
Console.WriteLine(x);

Wskutek takiego dziaania najpierw zostanie obliczone dziaanie w nawiasie (czyli


dodawanie), a nastpnie jego wynik (czyli 4) zostanie pomnoony przez 2.

Sowo kluczowe else


Powrmy jeszcze raz do poprzedniego przykadu, w ktrym prezentowaem sposb na
pobranie ostatniej litery acucha oraz aktualnej godziny. Jeeli warunek zosta speniony,
wywietlalimy tekst Dobry wieczr koleanko. Co zrobi w przypadku, gdy chcemy
wywietli tekst Dzie dobry koleanko, jeeli jest przed godzin 18:00? Moemy
zastosowa drug instrukcj if:
// warunek sprawdza, czy jest po godzinie 18:00
if (DateTime.Now.Hour >= 18)
{
Console.WriteLine("Dobry wieczr koleanko!");
}
// warunek sprawdza, czy jest przed godzin 18:00
if (DateTime.Now.Hour < 18)
{
Console.WriteLine("Dzie dobry koleanko!");
}

atwiej w takim wypadku korzysta z instrukcji else, ktra oznacza w przeciwnym

wypadku.
Jest ona do czsto wykorzystywana przez programistw:
// warunek sprawdza, czy jest po godzinie 18:00
if (DateTime.Now.Hour >= 18)
{
Console.WriteLine("Dobry wieczr koleanko!");
}
// warunek sprawdza, czy jest przed godzin 18:00
else
{
Console.WriteLine("Dzie dobry koleanko!");
}

Jeeli warunek w instrukcji if nie zostanie speniony, wykonany zostanie kod z ciaa
instrukcji else. Na listingu 3.2 zaprezentowany zosta peny kod programu.
Listing 3.2. Kod programu stosujcego instrukcje warunkowe
using System;
class Program
{
static void Main(string[] args)
{
string imie;
Console.WriteLine("Cze! Jak masz na imi?");
imie = Console.ReadLine();
// sprawdzamy, czy imi jest kobiece
if (imie[imie.Length - 1] == 'a')
{
// warunek sprawdza, czy jest po godzinie 18:00
if (DateTime.Now.Hour >= 18)
{
Console.WriteLine("Dobry wieczr koleanko!");
}
// warunek sprawdza, czy jest przed godzin 18:00
else
{
Console.WriteLine("Dzie dobry koleanko!");
}
}
else
{
Console.WriteLine("Cze Kolego!");
}

Console.ReadLine();
}
}

Jak widzisz, mamy tutaj kilka instrukcji warunkowych if oraz else zagniedonych w sobie,
co jest oczywicie dozwolone. Jeeli uytkownik poda imi mskie, pierwszy warunek nie
zostanie speniony, czyli zostanie wykonany kod z ciaa instrukcji else (na konsoli ujrzymy
tekst Cze Kolego!).

Instrukcja else if
Istnieje moliwo poczenia instrukcji if i else. Przypumy, e chcemy wywietli
wiadomo w oknie konsoli w zalenoci od aktualnej godziny. Spjrz na poniszy fragment
kodu:
int X = DateTime.Now.Hour;
if (X == 12)
{
Console.WriteLine("Jest poudnie!");
}
else if (X > 12 && X < 21)
{
Console.WriteLine("No c... ju po 12:00");
}
else if (X >= 21)
{
Console.WriteLine("Oj... to to rodek nocy");
}
else
{
Console.WriteLine("Dzie dobry");
}

Jeeli pierwsza instrukcja nie zostanie speniona, program przejdzie do sprawdzania kolejnej.
Jeeli i ta nie zostanie speniona, sprawdzi kolejn i jeszcze nastpn. W ostatecznoci
program wykona kod z ciaa instrukcji else.

Instrukcja switch
Jest to kolejna instrukcja warunkowa jzyka C#. Umoliwia sprawdzanie wielu warunkw.

Spjrz przez chwil na poprzedni przykad. Poczona instrukcja if-else if sprawdzaa warto
zmiennej X i wykonywaa odpowiedni kod w zalenoci od jej wartoci. Uycie instrukcji
switch to dobry pomys na zastpienie wielu instrukcji if.
Skadnia takiej instrukcji jest do specyficzna. Spjrz na poniszy przykad:
i
nt X = 10;
switch (X)
{
case 1:
// kod nr 1
break;
case 5:
// kod nr 2
break;
}

Sprawdzamy tutaj warto zmiennej X. Jeeli ma ona warto 1, wykonujemy odpowiedni


kod i koczymy dziaanie instrukcji warunkowej. Skadnia instrukcji switch jest do
specyficzna. Bardzo wanym elementem jest sowo kluczowe break, ktre nakazuje
wyskoczenie z instrukcji warunkowej, jeeli warunek zostanie speniony. Przykadowo:
int Mandat = 50;
switch (Mandat)
{
case 10:
Console.WriteLine("10 z mog zapaci");
break;
case 20:
Console.WriteLine("Oj, 20 z to troszk duo");
break;
}

Jeeli warto zmiennej Mandat wynosi 10, zostanie wykonany odpowiedni kod (w tym
wypadku wywietlenie komunikatu), po czym instrukcja switch zostanie zakoczona
(pozostae warunki po sowach kluczowych case nie bd sprawdzane). Usunicie sowa
break spowoduje bd przy kompilacji: Control cannot fall through from one case
label ('case 10:') to another.

Warto domylna
W ostatnim przykadzie moesz zauway, i zmiennej Mandat przypisaem warto 50. W
takim wypadku aden z warunkw nie zostanie speniony, poniewa w instrukcji sprawdzamy
jedynie, czy wartoci jest 10 lub 20. W jzyku C# moemy uy sowa kluczowego
default, aby odpowiednio zareagowa, gdy aden ze wczeniejszych warunkw nie zostanie
speniony:
int Mandat = 50;
switch (Mandat)
{
case 10:
Console.WriteLine("10 z mog zapaci");
break;

case 20:
Console.WriteLine("Oj, 20 z to troszk duo");
break;
default:
Console.WriteLine("Niezidentyfikowana suma");
break;
}

Po uruchomieniu takiego programu na ekranie konsoli wywietlony zostanie tekst:


Niezidentyfikowana suma.

Instrukcja goto
Sowo kluczowe break nakazuje zakoczenie instrukcji switch. Istnieje moliwo
przeskoczenia do innej etykiety case lub default przy pomocy instrukcji goto. Oto
przykad:
int Mandat = 50;
switch (Mandat)
{
case 10:
Console.WriteLine("10 z mog zapaci");

goto case 20;

case 20:
Console.WriteLine("Oj, 20 z to troszk duo");
break;
default:
Console.WriteLine("Niezidentyfikowana suma");
goto case 10;
}

Przeanalizujmy taki kod. Poniewa warto zmiennej Mandat to 50, wykonany zostanie blok
z etykiety default. Zawarty w niej kod nakazuje przeskok do etykiety case 10, a ten z kolei
do case 20. Wskutek tak sformuowanego kodu na ekranie konsoli zostanie wywietlony
tekst:
Niezidentyfikowana suma
10 z mog zapaci
Oj, 20 z to troszk za duo

Uwaga! W ten sposb mona zaptli program. Programici okrelaj tym mianem
sytuacj, w ktrej w programie nie znajdzie si instrukcja nakazujca zakoczenie danego
kodu, co skutkuje cigym jego wykonywaniem. Oto przykad prezentujcy tak sytuacj:
case 10:
Console.WriteLine("10 z mog zapaci");
goto case 20;

case 20:
Console.WriteLine("Oj, 20 z to troszk duo");
goto case 10;
default:
Console.WriteLine("Niezidentyfikowana suma");
goto case 10;

W kadej etykiecie znajduje si instrukcja goto, ktra nakazuje skok do innej etykiety case.

Ptle
W wiecie programistw pod sowem ptla kryje si pojcie oznaczajce wielokrotne

wykonywanie tych samych czynnoci. Jest to bardzo wany element kadego jzyka
programowania, dlatego konieczne jest zrozumienie istoty jego dziaania.
Wyobramy sobie sytuacj, w ktrej trzeba kilka razy wykona t sam czynno. Moe to
by na przykad wywietlenie kilku linii tekstu. Zamiast wielokrotnie uywa
Console.WriteLine(), mona skorzysta z ptli. Za chwil przekonamy si, e
zastosowanie ptli w programie wcale nie jest trudne.

Ptla while
Ptla while umoliwia wielokrotne wykonywanie tego samego kodu, dopki nie zostanie
speniony warunek jej zakoczenia. W tym momencie musimy ponownie posuy si
operatorami logicznymi oraz porwnania. Oglna budowa ptli while wyglda nastpujco:
while (warunek zakoczenia)
{
// kod do wykonania
}

Napiszmy jaki prosty program, ktry bdzie wywietla w ptli dowolny tekst, powiedzmy
dziesiciokrotnie. Spjrz na poniszy kod:
using System;
class Program
{
static void Main(string[] args)
{
int X = 1;
while (X <= 10)
{
Console.WriteLine("Odliczanie..." + X);
++X;
}
Console.Read();
}
}

Warunek w ptli while sprawdza, czy warto zmiennej X jest mniejsza lub rwna liczbie 10.
Jeeli tak, zostanie wykonany kod z ciaa ptli, a nastpnie jej kolejna iteracja.
Iteracj nazywamy kolejne wykonanie ptli.

Zwr uwag na to, e oprcz wywietlania stosownego komunikatu za kadym razem


zwikszamy warto zmiennej X o 1. Gdybym pomin ten kod, doszoby do zaptlenia
programu, poniewa warto zmiennej X stale wynosiaby 1, tak wic warunek zakoczenia
ptli (czyli warto zmiennej X wiksza od 1) nigdy nie zostaby speniony.
Zaprezentuj teraz nieco trudniejszy przykad z zastosowaniem ptli while oraz instrukcji
warunkowej if. Zamy, e chcemy, aby uytkownik na starcie programu poda imi
kobiece. Musimy sprawdzi, czy rzeczywicie mamy do czynienia z takim imieniem, a jeeli
nie wywietli odpowiedni komunikat, po czym ponownie poprosi o podanie imienia.
Na samym pocztku zadeklarujmy zmienn typu bool, ktra na starcie bdzie miaa warto
false:
bool Done = false;

Jeeli uytkownik poda prawidowe imi, zmienimy warto tej zmiennej na true
(prawidowe imi); warunek zakoczenia ptli while to podanie prawidowego imienia:
while (Done == false)

Taki warunek zakoczenia ptli oznacza ni mniej, ni wicej, tylko: kontynuuj dziaanie ptli,
dopki warto zmiennej Done wynosi false. Listing 3.3 zawiera peny kod rdowy
programu.
Listing 3.3. Zastosowanie ptli while
using System;
class Program
{
static void Main(string[] args)
{
bool Done = false;
string name;
while (Done == false)
{
Console.WriteLine("Podaj imi kobiece: ");
name = Console.ReadLine();
if (name[name.Length - 1] == 'a')
{
Done = true; // uytkownik poda prawidowe
imi
Console.WriteLine("Cze " + name);
}
else
{

Console.WriteLine("Podae imi mskie :[");


}
}
Console.Read();
}
}

Moesz uruchomi tak aplikacj, aby sprawdzi jej dziaanie. Pozostae elementy
zastosowane w tym kodzie powinny by Ci znane z lektury tego rozdziau.
Warunki logiczne w ptlach
~~~~~~~~~~~~~~~~
Programujc, musisz przestawi si na mylenie logiczne. Musisz analizowa program,
mylc, jak go zanalizuje komputer. Naley wiedzie, e kady warunek, czy jest to warunek
zakoczenia ptli, czy warunek w instrukcji warunkowej, moe by albo speniony, albo nie.
S tylko dwie moliwoci tak lub nie.
W programie na listingu 3.3 warunek zakoczenia ptli by taki:
while (Done == false)

A waciwie trafniejszym okreleniem byoby: jest to warunek kontynuowania ptli. Mona


go jednak zapisa rwnie w inny sposb np.:
while (Done != true)

Oznacza on dokadnie to samo, nakazuje kontynuowanie ptli, jeli warto Done jest rna
od true. Skrcony zapis takiego warunku z zastosowaniem operatorw logicznych:
while (!Done)

Przypominam, i operator ! oznacza logiczne nie. Z punktu widzenia aplikacji oznacza to:
kontynuuj dziaanie ptli, dopki warto zmiennej Done jest rna od true.

Ptla do-while
Ptla do-while jest bardzo podobna do while. Waciwie oprcz budowy rni je tylko jeden
szczeg. Mianowicie ptla do-while zostanie wykonana co najmniej raz, poniewa warunek
jej zakoczenia jest sprawdzany po wykonaniu kodu z jej ciaa. Porwnaj dwie ptle
przedstawione poniej:

int X = 20;
while (X < 20)
{
Console.WriteLine("Ptla while: to nie powinno by
wykonane");
X++;
}

do
{
Console.WriteLine("Ptla do-while, no c to si
wykona");
X++;
}
while (X < 20);

Przypisaem zmiennej X warto 20. W ptli while warunkiem kontynuowania jest to, aby
warto zmiennej X bya mniejsza od 20. Taka ptla nie zostanie wykonana, poniewa
warunek nie zostanie speniony.
Warunek zakoczenia do-while jest taki sam jak w przypadku pierwszej ptli. Mimo tego po
uruchomieniu programu kod z ptli zostanie wykonany raz.
Budowa do-while jest podobna do zwykej ptli while:
do
{
// kod ptli
}
while (warunek zakoczenia);

Ptla for
Dla pocztkujcych programistw ptla for moe wyda si najtrudniejsza do opanowania,
posiada bowiem najtrudniejsz konstrukcj. Ta ptla rni si od wspomnianych wczeniej
tym, i naley zadeklarowa ilo jej iteracji. Uywamy jej zawsze wtedy, gdy wiemy, ile
powtrze danej ptli chcemy wykona.
Oto przykad prostej ptli for:
for (int i = 1; i <= 10; i++)
{

Console.WriteLine("Odliczanie..." + i);
}

Kod z ciaa ptli zostanie wykonany dziesiciokrotnie. Budow ptli mona zaprezentowa
nastpujco:
for (warto_startowa; warto_kocowa; licznik_ptli)
{
// kod ptli
}

Kadej ptli towarzyszy tzw. licznik


ptli. Jest to zmienna, ktr mona zadeklarowa w nagwku ptli lub przed jej uyciem:
int i;
for (i = 1; i <= 10; i++) // dobrze
for (int i = 1; i <= 10; i++) // dobrze

Podsumujmy:

Po kadej iteracji zwikszany zostaje licznik ptli (czyli warto zmiennej i).
Pocztkowa warto zmiennej i jest przypisywana w nagwku (w naszym przykadzie
pocztkow wartoci jest 1).
W nagwku okrelamy warunek zakoczenia dziaania ptli (ptla bdzie
kontynuowana, dopki warto zmiennej i jest mniejsza lub rwna 10).

W wikszoci przypadkw licznik ptli jest zwikszany kadorazowo o 1. Oczywicie jeeli


istnieje potrzeba, mona to zmieni. Np. poniszy przykad zwiksza licznik o 2, wskutek
czego zostanie wykonanych jedynie 10 iteracji:
for (int i = 1; i <= 20; i += 2)
{
Console.WriteLine("Odliczanie..." + i);
}

Odliczanie od gry do dou


W prezentowanych przykadach zawsze zwikszaem licznik ptli przy pomocy operatora ++.
Nic nie stoi na przeszkodzie, aby uy innego operatora:

for (int i = 20; i > 0; i--)


{
Console.WriteLine("Odliczanie..." + i);
}

Wartoci startow dla tej ptli jest liczba 20. Ptla bdzie wykonywana, dopki warto
zmiennej i bdzie wiksza od 0. Kada iteracja spowoduje zmniejszanie licznika o 1.

Parametry opcjonalne
Zwr uwag, e poszczeglne informacje w nagwku ptli s oddzielone od siebie znakiem
rednika.
Ptla for w jzyku C# jest na tyle elastyczna, e pozwala na pominicie dowolnych
elementw nagwka.
Oto przykad:
for (int i = 20; i > 0; )
{
Console.WriteLine("Odliczanie..." + i);
i -= 2;
}

Zwr uwag, e licznik ptli zmniejszamy w jej ciele. W nagwku ta informacja zostaa
pominita.
Istnieje moliwo pominicia jakiegokolwiek elementu z nagwka:
for (; ; )

W ten sposb tworzymy tzw. ptl forever, ktra bdzie wykonywana w nieskoczono.

Instrukcja break
Zastosowanie sowa kluczowego break poznae przy okazji omawiania instrukcji switch.
Ta instrukcja ma rwnie inne zastosowanie w poczeniu z ptlami umoliwia ich
natychmiastowe zakoczenie (wyjcie z nich).
Oto przykad zastosowania tego sowa kluczowego w poczeniu z ptl for, typu forever:

using System;
class Program
{
static void Main(string[] args)
{
int i = 0;
for (; ; )
{
++i;
Console.WriteLine("Odliczanie..." + i);
if (i == 20)
{
break;
}
}
Console.Read();
}
}

Warto zmiennej i jest zwikszana po kadej iteracji ptli. Mamy rwnie instrukcj
warunkow, ktra porwnuje warto zmiennej i. Jeeli osignie ona warto 20, nastpuje
zakoczenie ptli.

Instrukcja continue
Instrukcja break umoliwia natychmiastowe wyskoczenie z ptli, a continue
przeskoczenie do nastpnej iteracji. Spjrz na poniszy listing:
using System;
class Program
{
static void Main(string[] args)
{
int i = 0;
for (; ; )
{
++i;

// pomijamy wartoci parzyste


if (i % 2 == 0)
{
continue;
}
Console.WriteLine("Odliczanie..." + i);
// pomijamy wartoci parzyste,
// warunkiem jest osignicie wartoci 101
if (i == 101)
{
break;
}
}
Console.Read();
}
}

Zastosowaem ptle typu forever, w ktrej licznik jest zwikszany w jej ciele. Zastosowaem
rwnie instrukcj warunkow, ktra sprawdza, czy mamy do czynienia z parzyst wartoci
zmiennej i. Jeeli tak program pomija wykonywanie dalszego kodu i przeskakuje do
kolejnej iteracji.
Dla przypomnienia: operator % zwraca reszt z dzielenia. Jeeli po podzieleniu danej liczby
przez dwa reszta z dzielenia wynosi 0, mamy do czynienia z liczb parzyst.

Operator warunkowy
Jeeli jestemy przy temacie instrukcji warunkowych, warto wspomnie o operatorze
warunkowym ?:, ktry przypomina instrukcj if-else. Czsto zastosowanie tego operatora
zwiksza czytelno kodu przy prostych instrukcjach.
Budowa tego operatora jest do specyficzna:
warunek ? (pierwsze wyraenie) : (drugie wyraenie);

Jeeli warunek zostanie speniony, rezultatem takiej operacji jest wyraenie po znaku ?. W
przeciwnym wypadku rezultatem jest wyraenie po znaku :.
Oto przykad zastosowania tego operatora:

string S;
S = 10 > 20 ? "To nie zostanie wywietlone nigdy" : "To
zostanie wywietlone";
Console.WriteLine(S);

W zalenoci od wyniku warunku do zmiennej S przypisany zostanie dany tekst. W tym


przykadzie rezultat bdzie zawsze ten sam, poniewa liczba 10 nie jest wiksza od 20.
Wskutek tego do zmiennej S przypisany zostanie tekst To zostanie wywietlone.
Oczywicie w warunku mona stosowa operatory logiczne, tak jak w zwykej instrukcji
warunkowej if:
string S, S1, S2;
int X, Y;
X = 10;
Y = 10;
S1 = "Tak";
S2 = "Nie";
S = (X > 5 && Y == 10) ? S1 : S2;
Console.WriteLine(S);

Konwersja danych
Na pocztku tego rozdziau wspominaem o typach jzyka C#. Nie wspomniaem jednak o
tym, i niektre typy s ze sob niekompatybilne. Przykadowo, nie mona do zmiennej typu
int przypisa zawartoci zmiennej typu string. Tak samo nie mona do zmiennej typu int
przypisa wartoci zmiennej typu double.
Aby poradzi sobie z tego typu problemem, moemy skorzysta z klasy Convert, ktra
udostpnia odpowiednie metody suce do konwersji danych. Oto przykadowy fragment
programu:
string S;
int I;
S = "18";
I = Convert.ToInt32(S);

I *= 2;
S = Convert.ToString(I);
Console.WriteLine(S);

Mimo i do zmiennej S przypisalimy liczb, nadal jest to acuch otoczony znakami


cudzysowu. W kolejnej linii konwertujemy ten acuch do postaci liczby typu int. W dalszej
czci kodu mnoymy uzyskan warto, aby ponownie skonwertowa j na typ string.
Tabela 3.7 zawiera metody suce do konwersji na rne typy danych.
Tabela 3.7. Metody konwersji
Metoda
Opis
ToBoolean Konwertuje podan warto na typ bool.
ToByte
Konwertuje podan warto na typ byte.
ToChar
Konwertuje podan warto na pojedynczy znak char. </td
ToIn16
Konwertuje na 16-bitow liczb cakowit.
ToInt32 Konwertuje na 32-bitow liczb cakowit.
ToInt64 Konwertuje na 64-bitow liczb cakowit.
ToSingle Konwertuje na warto zmiennoprzecinkow.
ToString Konwertuje dane na acuch typu string.

Rzutowanie
Rzutowanie to sposb na oszukanie kompilatora, ktry jednoczenie daje moliwo
konwersji na rne typy danych. Oczywicie najprociej wytumaczy to na przykadzie.
Spjrz na poniszy kod:
char C;
byte B;
C = 'A';
B = C;

Taki kod nie zostanie prawidowo skompilowany, gdy prbujemy do zmiennej typu byte
przypisa warto zmiennej char, a to s zupenie rne typy danych! Rzutowanie to
mechanizm udostpniany przez jzyk programowania, polegajcy na zmianie typu danej
zmiennej:
B = (byte)C;

Jak widzisz, skadnia jest do charakterystyczna. Przed nazw zmiennej w nawiasach naley
poda nazw typu, na ktry bdziemy rzutowali. W konsekwencji wywoania takiego kodu
zmienna B bdzie zawieraa warto 65, co odpowiada numerowi ASCII litery A.
Rzutowanie to rwnie dobry sposb na zaokrglanie, a raczej obcinanie czci liczb
zmiennoprzecinkowych. Spjrz na poniszy fragment:
double d;
int i;
d = 454.54;
i = (int)d;

Wskutek dziaania takiego kodu do zmiennej i przypisana zostanie warto 454.

Przykadowa aplikacja
Poznae ju podstawowe elementy i mechanizmy jzyka programowania. Wystarczy to do
napisania prostej gry. Zasady s proste. Komputer losuje liczb, a zadaniem uytkownika jest
odgadnicie tej prawidowej. Gra dwch uytkownikw, a po kadym strzale (turze)
program podpowiada, czy naley celowa wyej, czy niej.
Wykorzystamy ptl for oraz instrukcje warunkowe. Zanim jednak przejdziemy do
kodowania caego mechanizmu, naley pobra od uytkownikw ich imiona:
Console.Write("Podaj imi gracza nr 1:
player1 = Console.ReadLine();

");

Console.Write("Podaj imi gracza nr 2:


player2 = Console.ReadLine();

");

Zarwno player1, jak i player2 to zmienne typu string.


Losowanie liczby to nieco trudniejsza sprawa. W .NET realizuje to klasa Random, ktr
naley zainicjowa. O tym jeszcze nie wspominaem, tak wic pozostawi na razie ten
fragment bez komentarza:
// tworzenie nowej instancji klasy
Random RandomObj = new Random();

// wylosowanie liczby z zakresu 0-999


int X = RandomObj.Next(1000);

Cay kod rdowy naszej gry zaprezentowaem na listingu 3.4.


Listing 3.4. Kod rdowy programu
using System;
class Program
{
static void Main(string[] args)
{
// imiona graczy
string player1, player2;
// gra zakoczona (true) czy nie (false)
bool Done = false;
// liczba podana przez uytkownika
int Number;
Console.Write("Podaj imi gracza nr 1:
player1 = Console.ReadLine();

");

Console.Write("Podaj imi gracza nr 2:


player2 = Console.ReadLine();

");

// tworzenie nowej instancji klasy


Random RandomObj = new Random();
// wylosowanie liczby z zakresu 0-999
int X = RandomObj.Next(1000);
// zmienna przechowuje imi gracza, ktry wykonuje
ruch
string player = player1;
while (!Done)
{
// zamiana graczy
player = player == player1 ? player2 : player1;
Console.WriteLine(player + ", strzelaj!");
// naley przekonwertowa tekst na liczb
Number = Convert.ToInt32(Console.ReadLine());
if (X > Number)
{
Console.WriteLine("Strzelaj wyej!");
}
else if (X < Number)

{
Console.WriteLine("Strzelaj niej!");
}
else
{
Console.WriteLine("Gratulacje! Wygra gracz "
+ player);
Console.Beep();
Done = true;
}
}
Console.Read();
}
}

Dyrektywy preprocesora
Preprocesor to mechanizm jzyka C#, ktry przetwarza kod rdowy programu przed jego
kompilacj. Dyrektywy preprocesora to specjalne sowa kluczowe, ktre analizowane s
jak sobie powiedzielimy przed waciw kompilacj.
Dziki dyrektywom moemy wpyn na zawarto kodu, ktry bdzie kompilowany.
Podczas pisania programu cay czas testujemy jego dziaanie poprzez kompilowanie i
uruchamianie go. W kodzie rdowym moemy zawrze instrukcje, ktre pomocne nam
bd w trakcie dziaania programu, np. w celu zidentyfikowania ewentualnych bdw. Ten
dodatkowy kod moemy wyczy w momencie, gdy kompilujemy finaln wersj aplikacji
gotow do dystrybucji.
Dziaanie dyrektyw preprocesora jzyka C# jest podobne do tej z jzyka C++.
Dyrektywy kompilatora poprzedzane s symbolem # i nie s zakaczane znakiem rednika.
Np.:
#define DEBUG

Dyrektywa #define suy do deklarowania symbolu, w tym przypadku DEBUG. O


dyrektywie #define moesz myle jak o staych jzyka C#, zasada dziaania jest podobna.
Jeeli uywamy dyrektywy #define, musi si ona znale u gry kodu rdowego, przed
waciwymi instrukcjami jzyka C#.
Lista dyrektyw preprocesora jzyka C# jest nastpujca:

#if

#else
#elif
#endif
#define
#undef
#warning
#error
#line
#region
#endregion
#pragma
#pragma warning
#pragma checksum

Deklarowanie symboli
Jak wspomniaem, dyrektywa #define suy do deklarowaniu symbolu, ktry jest
odpowiednikiem staych jzyka C#. Przy pomocy dyrektywy #if moesz sprawdza, czy
dany symbol jest zadeklarowany, czy te nie:
#if DEBUG
Console.WriteLine("Symbol DEBUG jest zadeklarowany");
#endif

Symbole mona deklarowa rwnie w trakcie kompilacji programu, uywajc np.


kompilatora csc.exe.
Naley wwczas kompilowa z poziomu linii polece, doczajc parametr /define:
csc.exe foo.cs /define DEBUG

Dyrektywa #undef suy do usuwania symbolu. Ta dyrektywa rwnie musi by zawarta na


samej grze kodu rdowego, jeszcze przed waciwymi instrukcjami programu.

Instrukcje warunkowe
Dyrektywy #if, #elif, #else oraz #endif su do budowania instrukcji warunkowych
preprocesora. Dziki nim moemy ustali, jaki kod zostanie skompilowany. Instrukcje

warunkowe wykorzystywane s w poczeniu z wczeniej wspomnianymi symbolami.


Dyrektywa #elif jest odpowiednikiem konstrukcji else if z jzyka C#, natomiast #else
jest odpowiednikiem sowa kluczowego else. Instrukcja warunkowa musi by zakoczona
dyrektyw #endif.
Co ciekawe, w instrukcjach warunkowych preprocesora mona uywa operatorw
logicznych. Oto przykad:
#define DEBUG
using System;
namespace FooApp
{
class Program
{
static void Main(string[] args)
{
#if (DEBUG && FOO)
Console.WriteLine("DEBUG i FOO s zadeklarowane");
#elif (DEBUG)
Console.WriteLine("DEBUG jest zadeklarowany");
#else
Console.WriteLine("Nie wiem nic");
#endif
Console.Read();
}
}
}

Bdy i ostrzeenia
Dyrektywy #error oraz #warning su do generowania bdw i ostrzee w trakcie
kompilacji programu. Rzadko s stosowane, najczciej w poczeniu z instrukcjami
warunkowymi:
#define DEBUG
using System;
namespace FooApp
{
class Program
{

static void Main(string[] args)


{
#if DEBUG
#warning Upss...
#error DEBUG jest zadeklarowane
#else
Console.WriteLine("Nie wiem nic");
#endif
Console.Read();
}
}
}

Po dyrektywie #error lub #warning naley wpisa komunikat bdu/ostrzeenia, ktry


zostanie wywietlony w trakcie kompilacji programu.
Ciekaw dyrektyw jest natomiast #pragma warning, ktra pozwala na wyczenie
wywietlania niektrych ostrzee. Przykadowo, deklaracja zmiennej i nieprzypisanie do niej
adnej wartoci spowoduje wywietlenie ostrzeenia w trakcie kompilacji. Moemy temu
zapobiec. Przykadowo:
#pragma warning disable 0168
int i; // <-- brak ostrzeenia
#pragma warning restore 0168
int d; // <-- jest ostrzeenie

Wraz z dyrektyw naley okreli, czy wyczamy pokazywanie jakiego bdu (disable), czy
aktywujemy
(restore). Naley rwnie poda numer bdu.
Moemy jednorazowo wyczy lub wczy pokazywanie wielu bdw. W takim przypadku
numery bdw naley wymieni po przecinku. Dyrektywa #pragma warning disable (bez
podania numeru bdw) dezaktywuje wywietlanie wszelkich ostrzee.

Podsumowanie
Pocztki s zawsze trudne czy to pocztki nauki jzyka obcego, czy nowa praca.
Spotykamy si z rzeczami nowymi, do ktrych musimy przywykn. Identycznie jest z nauk
jzyka programowania. W tym rozdziale zasypaem Ci spor dawk informacji na temat
jzyka C#, ktre mog by dla Ciebie nie do koca zrozumiae. Niestety na tym etapie
naleao wprowadzi kilka do trudnych poj, ktrymi posuguj si programici, a ktre
szczegowo zostan objanione w dalszej czci ksiki.

Na podstawie wiedzy zdobytej w tym rozdziale jeste ju w stanie pisa proste aplikacje.
Poznae bowiem podstawowe mechanizmy suce do sterowania prac programu, takie jak
instrukcje warunkowe oraz ptle. W dalszej czci ksiki stopniowo bd zapoznawa Ci z
trudniejszymi elementami jzyka C#.

[1] Funkcje WinAPI s umieszczone w bibliotekach DLL. Teoretycznie wic mogyby istnie
dwie funkcje o tej samej nazwie, tyle e w rnych bibliotekach DLL. Mogoby to jednak
utrudnia prac programicie zastanawiaby si on, z ktr z funkcji ma do czynienia i jak
j wykorzysta.

Rozdzia 4
Przegld .NET Framework
W rozdziale 2. omwiem podstawowe aspekty platformy .NET. Powiniene ju zna
podstawowe pojcia zwizane z t technologi, a take umie pisa proste aplikacje w C#.
W tym rozdziale skupi si przede wszystkim na rodowisku .NET Framework. Poniewa
rodowisko .NET Framework obejmuje wiele poj oraz technologii, naley je opisa na tyle
szczegowo, aby wiedzia, jak dziaaj aplikacje w rodowisku .NET.
rodowisko .NET Framework obejmuje swym zakresem wszystkie warstwy tworzenia
oprogramowania: od systemu operacyjnego po bibliotek klas (jak np. Windows Forms). Co
to oznacza? Ot rodowisko .NET Framework zainstalowane na komputerze czuwa nad
aplikacj, odpowiada za odpowiednie zarzdzanie pamici oraz obsug bdw. Oczywicie
Ty jako programista nie musisz si tym przejmowa. Wszystko odbywa si automatycznie.

rodowisko CLR
Wsplne rodowisko uruchomieniowe (CLR) stanowi podstawowy element platformy .NET
Framework. W momencie uruchomienia aplikacji .NET CLR odpowiada za jej sprawne
wykonanie (zaadowanie do pamici), przydzia pamici, obsug bdw itp. W
standardowym modelu programowania Win32 za tego typu czynnoci odpowiada
system operacyjny (Windows). Po zainstalowaniu na komputerze rodowiska .NET
Framework odpowiednie biblioteki systemu pozwalaj na rozpoznanie, czy dany program jest
aplikacj Win32, czy te .NET. Jeeli jest to aplikacja .NET, uruchomione zostaje rodowisko
CLR, pod ktrego kontrol dziaa program. To, co dzieje si w tle, nas nie interesuje, nie
trzeba si tym przejmowa.
W jaki jednak sposb system operacyjny rozpoznaje, ktry program jest aplikacj .NET?
Dzieje si tak dlatego, e aplikacja wykonywalna .NET (plik .exe) jest inaczej zbudowana ni
standardowe programy Win32. T kwesti postaram si wyjani w paru kolejnych

podrozdziaach.

Kod poredni IL
Kompilatory dziaajce pod kontrol systemu Windows kompiluj kody rdowe do postaci
32-bitowego kodu maszynowego. W efekcie otrzymujemy aplikacje .exe czy biblioteki .dll.
Taki sposb uniemoliwia przeniesienie aplikacji na urzdzenia czy systemy, ktre dziaaj
pod kontrol innych procesorw. Moe jednak take stwarza problemy z innymi wersjami
systemu operacyjnego (w tym wypadku Windows). Dua w tym rola API systemu, z ktrego
korzystaj aplikacje. Wiadomo, e np. w systemie Linux nie istniej biblioteki DLL,
odpowiednik WinAPI systemu Windows 1. Z tego wzgldu takie programy nie mog zosta
uruchomione na systemach innych ni Windows.
Rozwizaniem tego problemu jest kompilacja programu do kodu poredniego nazywanego
Common Language Infrastructure (z ang. architektura wsplnego jzyka, CLI).
CLI na platformie .NET jest czsto nazywany MSIL (ang. Microsoft Intermediate Language)
lub po prostu IL.
Kod poredni IL przypomina kod jzyka Asembler:
.method family hidebysig virtual instance void
Dispose(bool Disposing) cil managed
{
// Code size
30 (0x1e)
.maxstack 2
IL_0000: ldarg.1
IL_0001: brfalse.s IL_0016
IL_0003: ldarg.0
IL_0004: ldfld
class
[System]System.ComponentModel.Container
WinForm.TWinForm1::Components
IL_0009: brfalse.s IL_0016
IL_000b: ldarg.0
IL_000c: ldfld
class
[System]System.ComponentModel.Container
WinForm.TWinForm1::Components
IL_0011: callvirt
instance void
[System]System.ComponentModel.Container::Dispose()
IL_0016: ldarg.0
IL_0017: ldarg.1
IL_0018: call
instance void
[System.Windows.Forms]System.Windows.Forms.Form::Dispose(bool)
IL_001d: ret
} // end of method TWinForm1::Dispose

W takiej postaci kod IL jest kompilowany do kodu maszynowego, ktry moe ju zosta
uruchomiony. Tak wanie dziaa jzyk Java, z ktrego pomys zaczerpn Microsoft,
projektujc platform .NET. Jeeli wic kompilujesz swoje aplikacje napisane w C#, w
rzeczywistoci s one kompilowane do kodu poredniego IL, a nie do kodu maszynowego.
Wszystko to jest moliwe dziki tzw. maszynom wirtualnym, czyli aplikacjom
przystosowanym do konkretnej wersji systemu/procesora. rodowisko CLR odpowiada za
kompilacj kodu poredniego na maszynowy w trakcie uruchamiania aplikacji. Dzieje si to
jednak na tyle szybko, e jest niezauwaalne dla uytkownika.
Moliwe jest rwnie jednorazowe skompilowanie danego programu od razu na kod
maszynowy. Dziki temu przy kadym uruchamianiu programu s oszczdzane zasoby
systemowe potrzebne do uruchomienia kompilatora JIT (ang. Just-In-Time).

Kod zarzdzany i niezarzdzany


Platforma .NET definiuje dwa nowe pojcia kod zarzdzany (ang. managed code) oraz
niezarzdzany (ang. unmanaged code) ktre s istotne z punktu widzenia CLR. Kod
niezarzdzany jest zwykym kodem wykonywanym poza rodowiskiem .NET, zatem
okrelenie to oznacza stare aplikacje kompilowane dla rodowiska Win32. Natomiast jak
nietrudno si domyli kod zarzdzany jest wykonywany pod kontrol CLR.
Zmiany w .NET w porwnaniu z Win32 s na tyle due, e problemem staje si
wspdziaanie obu rodzajw aplikacji (aplikacji .NET oraz Win32), jak rwnie korzystanie
z zasobw starszych aplikacji Win32, np. bibliotek DLL. Dlatego te w .NET w tym celu
wykorzystuje si mechanizm zwany marshalingiem 2 , ktry jest zwizany z okreleniem
sposobu, w jaki dane maj by przekazywane z kodu niezarzdzanego do zarzdzanego. Tym
jednak nie naley si teraz przejmowa jest to ciekawostka dla osb, ktre wczeniej
programoway w Win32.

Modu zarzdzany
Kady modu zarzdzany jest przenonym plikiem systemu Windows. Moduy zarzdzane s
generowane przez kompilatory zgodne z platform .NET, a w ich skad wchodz nastpujce
elementy:

nagwek PE (ang. PE Header) standardowy nagwek pliku wykonywalnego


systemu Windows;
nagwek CLR (ang. CLR Header) dodatkowe informacje
, charakterystyczne dla danego rodowiska CLR;

metadane (ang. metadata) informacje o typach moduw uywanych w programie


oraz ich wzajemnych powizaniach;
kod zarzdzany (ang. managed code) wsplny kod generowany przez kompilatory
.NET (kod IL).

Informacje o metadanych znajduj si w 11. rozdziale tej ksiki.

Podzespoy
To jest bardzo wane pojcie, ktrym nieraz bd si posugiwa w tej ksice. W
najprostszym ujciu podzespoem (ang. assembly) nazywamy kad aplikacj dziaajc pod
kontrol .NET.
Kady modu zarzdzany wymaga podzespou. Jeden podzesp moe zawiera jeden lub
wicej moduw zarzdzanych. Podzesp moe zawiera rwnie inne pliki, takie jak
dokumenty, grafik itp.
Na tym etapie zagadnienie to moe wyda Ci si niezwykle skomplikowane. Podzespoy
mog zawiera jeden lub wicej moduw zarzdzanych, w ktrych z kolei znajduj si kod
zarzdzany oraz metadane.

Dziaanie CLR
Wspomniaem wczeniej, e CLR zajmuje si uruchamianiem aplikacji napisanej w .NET
oraz oglnie zarzdzaniem procesem jej dziaania. Przeledmy proces uruchamiania
aplikacji w .NET. Oto kilka etapw, ktre przechodzi aplikacja od momentu, gdy uytkownik
zarzdzi jej uruchomienie:

adowanie klasy (ang. Class Loader),


weryfikacja,
kompilacja.

Class Loader
Dotychczas jedynym formatem rozpoznawanym przez systemy Windows jako aplikacja
wykonywalna by stary format PE. Stary format PE zawiera skompilowany kod maszynowy
aplikacji. Kiedy instalujemy bibliotek .NET w systemie, dodawane jest uaktualnienie

mwice o nowym formacie PE, dziki czemu system jest w stanie rozpozna rwnie nowy
format plikw wykonywalnych.
Skoro teraz aplikacja .NET jest rozpoznawana przez system, ten w momencie jej
uruchamiania oddaje sterowanie do CLR. CLR odczytuje zawarto pliku oraz list klas
uywanych w programie. Lista klas jest odczytywana z przernych miejsc gwnie jest to
manifest oraz metadane, a take plik .config, ktry moe by doczany do programu. Po
odczytaniu klas nastpuje obliczenie iloci pamici potrzebnej do ich zaadowania. Nastpnie
klasy s adowane do pamici
.

Weryfikacja
Po zaadowaniu klas do pamici zawarto programu, czyli metadane oraz kod IL, zostaje
poddana weryfikacji. Jest to wany etap, gdy w przypadku niepowodzenia kod IL nie
zostanie przekazany kompilatorowi JIT.

Kompilator JIT
Kompilator JIT odgrywa znaczc rol w procesie uruchamiania aplikacji. Kod po
weryfikacji jest sprzekazywany do kompilatora, ktry kompiluje go do kodu maszynowego, a
nastpnie gotowy program zostaje zaadowany do pamici. Znajduje si on w tej pamici do
czasu zakoczenia dziaania aplikacji.

System CTS
Wiadomo ju, czym s typy jzyka programowania. Podstawowe typy, takie jak int, string,
omwiem w poprzednim rozdziale.
Wsplny system typw (ang. Common Type System) jest bogatym zbiorem typw
opracowanych przez firm Microsoft na potrzeby .NET. Programujc w C#, korzystamy z
tych typw cay czas! W rzeczywistoci bowiem np. typ int odpowiada typowi
System.Int32 z platformy .NET! Spjrz na poniszy przykad:
int Foo;
System.Int32 Bar;
Foo = 23;
Bar = Foo;

Jak widzisz, zadeklarowaem w programie dwie zmienne typu cakowitego, ktre s sobie
rwnowane.
W rzeczywistoci Int32 czy String to klasy znajdujce si w przestrzeni nazw System.
Mimo e typy jzyka C# wskazuj na odpowiedniki rodowiska .NET Framework, atwiej jest
stosowa skrcone wersje, takie jak int czy string. Takie kroki zostay poczynione z myl o
programistach jzyka C/C++, w ktrym obecne s takie wanie typy.
Tabela 4.1 zawiera list wbudowanych typw jzyka C# wraz z ich odpowiednikami CTS.
Tabela 4.1. Typy CTS
Typ C# Typ .NET Framework
bool
byte
sbyte
char
decimal
double
float
int
uint
long
ulong
object
short
ushort
string

System.Boolean
System.Byte
System.SByte
System.Char
System.Decimal
System.Double
System.Single
System.Int32
System.UInt32
System.Int64
System.UInt64
System.Object
System.Int16
System.UInt16
System.String

System CTS jest skadnikiem


CLR, odpowiada za weryfikowanie i zarzdzanie tymi typami. Wszystkie typy, rwnie te
przedstawione w tabeli 4.1, wywodz si z gwnej klasy System.Object.

Specyfikacja CLS
Do tej pory moliwoci w komunikowaniu si pomidzy aplikacjami byy nieco ograniczone.
Kod aplikacji w rodowisku Win32 moe by dzielony na biblioteki DLL. Po umieszczeniu
funkcji w bibliotece DLL istnieje moliwo ich eksportu, co z kolei pozwala na ich
wykorzystanie przez aplikacje EXE.
W rodowisku .NET aplikacje maj pen zdolno do komunikowania si. Jeden podzesp
moe wykorzystywa funkcje drugiego i na odwrt. Wszystko to dziki kompilacji do kodu

poredniego IL, do ktrego s kompilowane wszystkie aplikacje, bez wzgldu na to, czy s
pisane w C#, czy w Delphi. Tak wic jeeli napisano program do szyfrowania danych, mona
(jeeli tylko programista tego zechce) udostpni jego funkcjonalno
innym podzespoom. Praktyczne przykady tego zagadnienia zaprezentuj w rozdziale 11.
Nie tylko jzyk poredni ma tu znaczenie, ale rwnie specyfikacja CLS (ang. Common
Language Specification, czyli wsplna specyfikacja jzykw). Jest to zestaw regu
okrelajcych nazewnictwo oraz inne kluczowe elementy jzyka programowania. Jeli
projektanci jzyka programowania, ktry docelowo ma dziaa dla .NET, chc, aby by on
kompatybilny z CLS oraz mia zdolno do komunikowania si z pozostaymi podzespoami,
musz dostosowa swj produkt do okrelonych wymaga.
Dodatkowe informacje na temat specyfikacji CLS mona take znale na stronach firmy
Microsoft:
http://msdn.microsoft.com/net/ecma/.

Biblioteka klas
Czytelnik wci spotyka si ze sowami: klasa, obiekt, ktrych zreszt czsto uywam w tym
rozdziale. Wspominaem wczeniej, e na platformie Win32 aplikacje korzystay z WinAPI,
czyli z zestawu funkcji pomocnych przy programowaniu. Wkrtce po tym pojawiy si takie
biblioteki jak MFC (w rodowisku Visual Studio) czy VCL (w rodowisku Delphi), ktre
jeszcze bardziej uatwiay programowanie.
Biblioteka klas .NET Framework (ang. .NET Framework Class Library) stanowi zestaw setek
klas, bibliotek, interfejsw, typw, ktre maj zastpi WinAPI. W zaoeniu FCL ma
poczy funkcjonalno WinAPI oraz dodatkowych bibliotek, takich jak VCL czy MFC
(ang. Microsoft Fundation Classes).
W jzyku C# korzystamy jedynie z klas .NET Framework, nie uywamy w ogle funkcji
WinAPI. Poniewa s setki klas, polecam korzystanie z internetowej dokumentacji
rodowiska .NET Framework (dostpnej na stronie firmy Microsoft), ktra zawiera opis
wszystkich klas, metod itp.
Powiedzielimy, e do programowania na platform .NET mona uywa wielu rnych
jzykw programowania. Piszc swj program, np. w Delphi, mog uywa tych samych klas
co w jzyku C#:
Console.WriteLine("Hello World"); // C#

Console.WriteLine('Hello World'); // Delphi

Moduy, przestrzenie nazw


Nieco wczeniej, w poprzednim rozdziale wspomniaem o programowaniu proceduralnym,
ktre jest znacznym uatwieniem pracy podczas pisania programw.
Podzia na procedury i funkcje jednak przesta wystarcza, gdy programy staway si coraz
dusze. Wwczas kto wpad na pomys, aby czci kodu rdowego podzieli na mniejsze
pliki. Tak moliwo wprowadzono take w jednej z wczeniejszych wersji Turbo Pascala.
Ide dzielenia kodu na mniejsze pliki nazwano programowaniem strukturalnym.
Modu (ang. unit) jest plikiem tekstowym zawierajcym (posiadajcym rozszerzenie *.cs)
polecenia przetwarzane przez kompilator. Inaczej mwic, jest to fragment kodu rdowego.
Jakie s zalety programowania strukturalnego (modularnego)? Ot programista ma
moliwo umieszczenia czci kodu (np. kilku klas) w osobnych moduach. Wyobramy
sobie, e tworzymy do spory program i cay kod rdowy jest zawarty w jednym pliku i
podzielony na poszczeglne klasy. W takim pliku trudno si bdzie pniej odnale
przede wszystkim ze wzgldu na jego dugo. Koncepcja programowania strukturalnego
polega na podzieleniu takiego programu na kilka plikw i wczaniu ich po kolei do
gwnego pliku rdowego.
Dziki temu moemy w jednym pliku, np. interfaces.cs, zawrze jedynie klasy zwizane z
tworzeniem interfejsu uytkownika, a z kolei w pliku db.cs umieci procedury zwizane z
obsug bazy danych.
Wczenie nowego pliku do projektu C# jest dosy proste, jeeli korzystamy z edytora
takiego jak Visual C# Express Edition. Z menu Project wybierz Add New Item. Zaznacz ikon
Code File i nacinij przycisk OK. Utworzona zostanie nowa zakadka z edytorem kodu. Zwr
uwag na okno Solution Explorer, w ktrym pojawia si nowa pozycja CodeFile1.cs
(rysunek 4.1). W trakcie kompilacji plik ten bdzie wczany do projektu.

Rysunek 4.1. Okno Solution Explorer


Aby przetestowa moliwo korzystania z kodu znajdujcego si w innym pliku, w nowym
utwrz przykadow klas zawierajc prost metod:
class Foo
{
public static void Bar()
{
System.Console.WriteLine("Jestem kodem znajdujcym si
w innym pliku!");
}
}

W module gwnym wystarczy wywoa odpowiedni metod z klasy Foo:


using System;
class Program
{
static void Main(string[] args)
{
Foo.Bar();
Console.Read();
}
}

Ani rodowisko Visual C# Express Edition, ani kompilator nie maj informacji

, ktry z plikw rdowych projektu jest plikiem gwnym. Po prostu ktry z moduw
musi zawiera miejsce startowe w postaci metody Main(). Jeeli kompilator odnajdzie
wicej ni jedn metod Main(), nawet jeeli bd one w rnych klasach i przestrzeniach
nazw, zasygnalizuje bd.

Wieloznaczno
Podzia projektu na poszczeglne pliki rdowe nie ma w C# takiego znaczenia jak podzia
na przestrzenie nazw (ang. namespaces).
Zamy, e mam firm. Pisz swoje aplikacje, a nastpnie udostpniam je innym. Pamitasz,
jak mwiem, e skompilowane podzespoy mog by w .NET wykorzystywane przez inne
aplikacje? Napisaem wietn aplikacj suc do kompresji dwiku. Mog w niej
zadeklarowa klas Console czy nawet DateTime, ktre potencjalnie mog kolidowa z
klasami o tych samych nazwach z przestrzeni System. Jednak tak nie jest, gdy swoje klasy
umieszczam w przestrzeni nazw Boduch:
namespace Boduch
{
class Foo
{
}
class Bar
{
}
class Console
{
}
}

Teraz zewntrzne aplikacje odwoujce si do moich klas bd musiay poprzedza nazw


klasy nazw przestrzeni nazw:
Boduch.Console.Nazwa_Metody();

Chodzi o to, e dana klasa moe mie zupenie inne znaczenie w zalenoci od tego, w jakiej
przestrzeni nazw zostaa zadeklarowana. W jednej aplikacji mog mie wiele przestrzeni
nazw. Nie powinna wic wydarzy si sytuacja, w ktrej jakie nazwy si dubluj w obrbie
jednej przestrzeni.
Kada przestrze nazw moe zawiera kolejne, zagniedone przestrzenie.

Gwne przestrzenie nazw


Biblioteka klas, dostpna w pakiecie .NET Framework, skada si z klas, typw i staych.
Elementy biblioteki s pogrupowane w hierarchiczn struktur, czyli wspominan przestrze
nazw. Dziki temu mog istnie dwie klasy o tej samej nazwie, jednak pod warunkiem e
zostay zadeklarowane w rnych przestrzeniach nazw. Nazwy przestrzeni nazw
dostarczanych przez Microsoft zawsze zaczynaj si od sowa System lub Microsoft.
Przykadowo, przestrze nazw System.IO dostarcza mechanizmw umoliwiajcych prac z
plikami.
Dodatkowo, przestrzenie nazw wprowadzaj pewn organizacj, hierarchi i porzdek.
Przykadowo, dany producent moe udostpni bibliotek, np. Firma.dll, ktra zawiera
bdzie przestrzenie nazw Firma.Biuro oraz Firma.Biuro.Komputer udostpniajce
dodatkowe klasy. Tabela 4.2 przedstawia gwne przestrzenie nazw uywane w .NET.
Tabela 4.2. Gwne przestrzenie nazw .NET
Przestrze nazw
Opis
Zawiera klasy, ktre s uywane w kadym programie m.in.
System
Int64, String, Char.
Udostpnia klasy do manipulacji strumieniami wejcia i wyjcia, a
System.IO
take suce do tworzenia oraz odczytu plikw.
Przestrze udostpnia szereg klas sucych do bardziej
System.Threading
zaawansowanego procesu polegajcego na projektowaniu aplikacji
wielowtkowych.
System.Reflection
Dostarcza klasy do inspekcji podzespow, metod itp.
Przestrze dostarcza klasy zwizane z bezpieczestwem
System.Security
oraz kryptografi.
Ta przestrze udostpnia klasy umoliwiajce programowanie
System.Net
sieciowe oraz dostp do wielu usug m.in. DNS czy HTTP.
System.Data
Udostpnia klasy zwizane z dostpem do baz danych.
Oglnie pojte klasy oraz inne przestrzenie zwizane z dostpem do
System.Web
sieci, usugami sieciowymi oraz protokoem HTTP.
Przestrze zwizana z bibliotek Windows Forms oraz
System.Windows.Forms
projektowaniem wizualnym.

Wicej informacji na temat przestrzeni nazw oraz zawartych w niej klas mona znale na
stronie http://msdn.microsoft.com.
Wczenie danej przestrzeni nazw nastpuje przy pomocy sowa kluczowego using, ale
powiniene ju o tym wiedzie z lektury poprzedniego rozdziau.

Podsumowanie
W tym rozdziale zasypaem Ci spor dawk teoretycznych informacji odnonie rodowiska
.NET Framework. Wydaje mi si, e programista piszcy swoje aplikacje w jzyku C#, a
wic cile zwizanym z platform .NET, powinien zna zasady dziaania na niej tych
programw. Podstawowym skadnikiem platformy i idei .NET jest wanie rodowisko .NET
Framework, ktre to definiuje wiele poj zwizanych z procesem wytwarzania
oprogramowania. Ja zaprezentowaem jedynie podstawowe zwizane z tym pojcia. Jeeli
jeste zainteresowany szczegow dokumentacj technologii CLR czy CLS, odsyam Ci do
stron internetowych firmy Microsoft.

[1] Od wielu lat nieustannie trwaj prace nad odpowiednikiem WinAPI na system Linux.
Projekt Wine, bo o nim tutaj mowa, jest ju na tak wysokim stadium zaawansowania, e
umoliwia uruchamianie niektrych aplikacji systemu Windows na Linuksie.
[2] Niestety nie znalazem dobrego polskiego odpowiednika tego sowa, ktre w peni
oddawaoby istot rzeczy.

Rozdzia 5
Programowanie obiektowe
Wiele do tej pory mwiem o klasach, metodach i waciwociach. Unikaem jednak
stosowania skomplikowanych poj, ktre nie zostay wczeniej objanione. W tym rozdziale
zamierzam objani, na czym polega technika programowania zwana programowaniem
obiektowym.
Idea programowania obiektowego staje si coraz powszechniejsza, gdy aplikacje staj si
coraz bardziej skomplikowane i zoone. Nie wystarcza ju tylko podzia aplikacji na moduy,
taki kod naley podzieli na poszczeglne klasy. Starsze jzyki programowania, takie jak np.
C, nie umoliwiaj programowania obiektowego taka moliwo pojawia si dopiero u
nastpcy wspomnianego jzyka C++. Omawiany tutaj jzyk, czyli C#, jest w peni
obiektowy, dlatego dla programisty wrcz niezbdne jest poznanie, czym jest obiektowo i
jak tworzy wasne klasy. Tym wanie zajmiemy si w tym rozdziale.

Na czym polega programowanie obiektowe


Programy rozrastaj si coraz bardziej i bardziej. Tak samo jak kiedy nie wystarczaa idea
programowania proceduralnego, teraz nie wystarcza ju programowanie strukturalne.
Koncepcja programowania obiektowego pojawia si ju w latach 60. za spraw jzyka

Simula 67, zaprojektowanego przez naukowcw z Oslo w celu przeprowadzania symulacji


zachowywania si statkw. Jednake idea programowania obiektowego swoj popularyzacj
zawdzicza jzykowi SmallTalk. Poowa lat 80. to czas, kiedy programowanie obiektowe
stawao si dominujc technik gwnie za spraw C++. Wtedy to te w wielu innych
jzykach pojawia si moliwo tworzenia obiektw.
Mona powiedzie, e klasa jest rodzajem zbioru, pewnym elementem programu, ktry
wykonuje jakie zadania. Klasa zawiera metody (funkcje) wspdziaajce ze sob w celu
wykonania jakiego zadania. Programowanie obiektowe przyczynio si do tego, e podobnie
jak moduy, klasy mog by wykorzystywane w wielu innych projektach uatwia to
jeszcze bardziej zarzdzanie kodem i jego konserwacj, a take przenoszenie go pomidzy
rnymi projektami.
Zamy, e napisano klas do obsugi poczty (wysyanie i odbieranie). Klasa moe zawiera
metody Connect (pocz), SendMail (wylij e-mail), Disconnect (rozcz). Z kolei metoda
Connect moe wywoywa inn, np. Error (ktra te jest metod znajdujc si w klasie),
wywietlajc komunikat o bdzie w razie niepowodzenia i zapisujc odpowiedni
informacj w dzienniku programu (czyli, inaczej mwic, w logach plikach z
rozszerzeniem *.log). Teraz tak klas mona wykorzysta w wielu innych aplikacjach
wystarczy skopiowa fragment kodu i ju gotowa jest obsuga bdw, czenie itp. Tak
klas mona rwnie udostpni innym uytkownikom lub swoim wsppracownikom. Inny
uytkownik nie musi wiedzie, jak dziaa klasa dla niego jest wane jej dziaanie (np.
wysyanie e-maili). Uytkownik musi jedynie wiedzie, e istnieje metoda Connect, ktra
poczy go z danym serwerem, oraz musi mie wiadomo obecnoci kilku innych funkcji.
To wszystko nie interesuje go obsuga bdw, nie musi nawet zdawa sobie sprawy z jej
istnienia.
Mona by oczywicie utworzy nowy modu, a w nim umieci take procedury Connect,
SendMail oraz Disconnect, Error i reszt potrzebnego kodu. Jednak w takim przypadku
metody i zmienne (zmienne take mog by elementami danej klasy) nie oddziauj na siebie
w takim stopniu jak w przypadku programowania proceduralnego. Przykadowo, uytkownik
korzystajcy z takiego kodu bdzie mia dostp do tych zmiennych, do ktrych nie powinien
mie dostpu. Bdzie mg te wywoa swobodnie procedur Error a nie powinien, bo
moe to spowodowa niepodane skutki. Dziki klasom mona sprawi, i procedura Error
nie bdzie dostpna poza klas. Jej elementy (zmienne) te nie bd mogy by odczytane
przez przyszego uytkownika.
OK, by moe ta argumentacja Ci nie przekonuje. Zamy, e chcesz w swojej aplikacji
pobiera nagwki RSS z wiadomociami z danej witryny WWW. Zlecasz wic komu (np.
koledze z zespou projektu informatycznego, w ktrym uczestniczysz), aby napisa taki
modu. W swojej aplikacji potrzebujesz rwnie kodu, ktry przelicza rne istniejce
jednostki miar i wag. Doczasz obydwa moduy do swojej aplikacji po to, aby wykorzysta
ich moliwoci. Tak si skada, e w obydwu moduach znajduj si zmienne o tej samej
nazwie. Jest problem, poniewa koliduj one ze sob, co moe wpyn na ze
funkcjonowanie programu. Co gorsza, obydwa moduy zawieraj funkcj o tej samej nazwie!
Kiedy uywasz takiej funkcji, nie jeste pewien, z ktrego moduu ona pochodzi. Masz
problem.
Oczywicie przedstawiona tutaj sytuacja jest daleka od tego, co moe przydarzy si w
rzeczywistoci, z jednego prostego powodu: w C# nie ma moliwoci programowania

strukturalnego, poniewa jest to jzyk typowo obiektowy. Taka sytuacja jak przedstawiona
powyej nie ma prawa si wydarzy.

Podstawowy kod formularza WinForms


Utwrz nowy projekt aplikacji typu Windows Forms (omawiaem to w rozdziale 1.).
Oczywicie istnieje moliwo projektowania aplikacji wizualnych przy pomocy zwykego
edytora tekstu. Jednake rodowiska takie jak Visual C# Express Edition niezwykle uatwiaj
projektowanie tego typu aplikacji. S one okrelane mianem RAD, czyli Rapid Application
Development (szybkie projektowanie aplikacji). Projektowanie interfejsw takich programw
polega na umieszczaniu w odpowiednich miejscach formularza komponentw za pomoc
techniki drag & drop (przecignij i upu).
Biblioteka Windows Forms dostarcza nam caego zestawu komponentw, dziki ktrym
moemy zapewni interakcj programu z uytkownikiem (przyciski, listy rozwijane, pola
edycyjne). W trakcie projektowania interfejsu rodowisko RAD w tle tworzy kod
odpowiedzialny za utworzenie komponentw w momencie uruchamiania programu.
Zobaczmy, jak to wyglda od kuchni.
Kliknij okno formularza prawym przyciskiem myszy. Z podrcznego menu wybierz pozycj
View Code. W edytorze kodu, na osobnej zakadce wywietlona zostanie zawarto pliku
Form1.cs (przykadowo, tak nazw ma plik w moim przypadku):
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Text;
System.Windows.Forms;

namespace WindowsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}

Ten plik zawiera kod rdowy naszego formularza Windows Forms. Na samym pocztku do
programu wczanych jest kilka najpopularniejszych przestrzeni nazw. W dalszej czci kodu
zadeklarowana jest przestrze nazw dla naszej aplikacji, a w niej klasa Form1. Zawiera ona

metod o tej samej nazwie, ktra wywouje funkcj InitializeComponent(). Ta funkcja nie
jest czci rodowiska .NET Framework, lecz zostaa utworzona przez rodowisko
Visual C# Express Edition w sposb automatyczny.
Spjrz na rysunek 5.1. Okno Solution Explorer zawiera hierarchiczn struktur naszej
aplikacji.

Rysunek 5.1. Okno Solution Explorer


I tak w gazi References mamy odwoania do poszczeglnych podzespow .NET
Framework, ktre zawieraj przestrzenie nazw wykorzystywane przez nas w projekcie. W
gazi Properties znajduj si odwoania do plikw zawierajcych ustawienia naszego
projektu. Jeeli teraz zapiszesz projekt (menu File/Save All), na dysku, we wskazanej
lokalizacji zostan zapisane wszystkie pliki wyszczeglnione w oknie Solution Explorer.
Nas jednak najbardziej interesuj pliki (moduy) Form1.cs, Form1.Designer.cs oraz
Program.cs. Podwjne kliknicie danej pozycji spowoduje otwarcie zaznaczonego pliku w
edytorze kodu. Podstawowym moduem projektu jest plik Program.cs:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace WindowsApplication1
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();

Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}

Nasza aplikacja rozpoczyna swoje dziaanie od tego pliku, a konkretnie od klasy Program i
metody Main() (jak zapewne pamitasz, metoda Main() stanowi gwny element programu i
to w niej musz znajdowa si polecenia, ktre zostan wykonane w pierwszej kolejnoci). W
ciele metody znajduj si odwoania do metod klasy Application. Pierwsza z metod
EnableVisualStyles() uaktywnia wizualny tryb aplikacji. Kolejna metoda
SetCompatibleTextRenderingDefault() zwizana jest z graficzn bibliotek GDI. Nie
powiniene si teraz tym przejmowa. Najwaniejsza w tym kodzie jest metoda Run(), ktra
wywietla gwny formularz Form1. Jeeli skomentujemy t lini, program zostanie
uruchomiony, lecz aden formularz nie zostanie pokazany. W skutek tego program
natychmiast zostanie zamknity.
Jak widzisz, w parametrze metody Run() znajduje si konstrukcja new Form1(). Operator new
nakazuje utworzenie nowej instancji klasy Form1. Zostanie to omwione w dalszej czci
rozdziau.

Modu Form1.Designer.cs
Jest jeszcze jeden wany modu, o ktrym naley wspomnie, a mianowicie
Form1.Designer.cs. Zawiera on kod rdowy odpowiedzialny za zachowanie formularza
oraz wszystkich umieszczonych na nim komponentw. Zawarto czystego projektu
Windows Forms moe wyglda tak:
namespace WindowsApplication1
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components =
null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources
should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)

{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not
modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new
System.ComponentModel.Container();
this.AutoScaleMode =
System.Windows.Forms.AutoScaleMode.Font;
this.Text = "Form1";
}
#endregion
}
}

Zwr uwag, i to tutaj znajduje si metoda InitializeComponent(). Odpowiada ona za


utworzenie formularza oraz ustawienie dla niego waciwoci. W gruncie rzeczy projektujc
aplikacj w sposb wizualny, nigdy nie powiniene ingerowa w zawarto tego pliku,
poniewa jest ona generowana automatycznie przez rodowisko Visual C# Express Edition.

Generowanie kodu
Wykonajmy may test obrazujcy, w jaki sposb projektowanie wizualne oddziauje na kod
rdowy naszego projektu. Kliknij zakadk Form1.cs [Design] w oknie edytora kodu.
Dziki temu z powrotem przeczysz si do trybu projektowania wizualnego. W oknie
ToolBox znajd komponent Label, a nastpnie umie go na formularzu.
Jeeli okno ToolBox jest niewidoczne, z menu View wybierz ToolBox.
Ponownie otwrz modu Form1.Designer.cs. Zwr uwag na to, e zawarto metody
InitializeComponent() zostaa zmieniona:

private void InitializeComponent()


{
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(125, 77);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 0;
this.label1.Text = "label1";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F,
13F);
this.AutoScaleMode =
System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.label1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}

Metoda ta odpowiada za utworzenie komponentw oraz ustawienie waciwoci takich jak


pozycja oraz rozmiar poszczeglnych kontrolek. Zwr rwnie uwag, i w klasie znajduje
si nowa zmienna:
private System.Windows.Forms.Label label1;

Sowo kluczowe private to tzw. modyfikator, ale pojcie to zostanie objanione w dalszej
czci ksiki. Taki kod oznacza deklaracj zmiennej o nazwie label1, typu
System.Windows.Forms.Label.
Jak powiedziaem wczeniej, zawarto pliku Form1.Designer.cs nie powinna Ci w ogle
interesowa. rodowisko Visual C# Express Edition automatycznie generuje kod na
podstawie czynnoci, jakich dokonujemy w trakcie projektowania. Np. przesunicie
komponentu Label zaowocuje uaktualnieniem kodu w pliku Form1.Designer.cs.

Ukrywanie kodu

Edytor Visual C# Express Edition posiada moliwo ukrywania fragmentu kodu. W takim
przypadku z lewej strony edytora zostan wywietlone mae ikony, ktrych kliknicie
spowoduje rozwinicie ukrytego fragmentu. Jest to do ciekawe rozwizanie umoliwia
zwikszenie przejrzystoci kodu i schowanie fragmentu, ktry w danym momencie nie
interesuje programisty.
Wszystko to odbywa si za spraw sowa (dyrektywy) #region:
#region Windows Form Designer generated code

Nie ma to adnego wpywu na dziaanie aplikacji, jest to jedynie informacja


dla rodowiska, i ten fragment kodu mona ukry. Przykadowo, w dowolnym miejscu kodu
rdowego umie nastpujcy fragment :
#region Przykadowy region
// tutaj kod
#endregion

Zwr teraz uwag, i po lewej stronie edytora kodu znajduje si ikona, dziki ktrej ten
fragment mona schowa (rysunek 5.2).

Rysunek 5.2. Przykad zwijania kodu


Dyrektywa #region rozpoczynajca dany region musi zosta zakoczone sowem
#endregion. W rzeczywistoci jest to dyrektywa preprocesora.

Programowanie zdarzeniowe
Powiedzielimy ju sobie o programowaniu proceduralnym, strukturalnym. Tematyk tego
rozdziau jest programowanie obiektowe. Naley jeszcze wspomnie o metodzie zwanej
programowaniem zdarzeniowym. Typowa aplikacja Windows Forms po uruchomieniu jest
adowana do pamici. Nastpnie oczekuje na reakcj uytkownika nakazujc wykonywanie

okrelonych czynnoci (np. kliknicie mysz, przesunicie kursora itp.).

Generowanie zdarze
Reakcj na okrelone czynnoci, czyli zdarzenia, najprociej generowa przy uyciu
rodowiska Visual C# Express Edition. Okno Properties zawiera waciwoci danego
komponentu (pooenie, rozmiar itp.) oraz zdarzenia (rysunek 5.3).

Rysunek 5.3. Okno Properties z wywietlon list zdarze


Jeeli okno Properties jest ukryte, wystarczy uy polecenia Properties Window z menu View.
Okno Properties posiada kilka przyciskw, ktre okrelaj sposoby wywietlania. Trzeci
przycisk od lewej suy do wywietlania listy waciwoci danego komponentu; nacinicie
przycisku czwartego spowoduje wywietlenie listy zdarze.

Lista zdarze jest domylnie pogrupowana w kategorie. Jak widzisz, moemy zaprogramowa
reakcj na okrelone czynnoci, takie jak podwjne kliknicie (DoubleClick) czy uycie
klawiszw klawiatury (KeyPress).
1.
2.
3.
4.
5.

Umie na formularzu komponent Button.


Przejd do okna waciwoci (Properties) i znajd waciwo Text.
Zaznacz odszukan waciwo.
W prawej kolumnie wpisz dla waciwoci now warto Kliknij mnie.
Nacinij Enter, co spowoduje zaakceptowanie nowej wartoci.

W tym momencie ustawilimy waciwo dla komponentu. Oczywicie zostanie to


odwzorowane w zawartoci moduu Form1.Designer.cs. Kliknij teraz dwukrotnie
umieszczony przycisk. Zostaniesz przeniesiony do edytora kodu, wygenerowane zostanie
zdarzenie Click odpowiadajce za oprogramowanie kliknicia komponentu.

Przykadowy program
Do tej pory pokazywaem, w jaki sposb zmienia waciwoci komponentu jedynie z
poziomu okna Properties. Naleaoby rwnie wiedzie, e jest to moliwe take z poziomu
kodu rdowego za pomoc operatora odwoania (.).
Nasz przykadowy program bdzie zmienia pozycj przycisku na formularzu. Nowa pozycja
bdzie losowana i ustawiana za kadym razem, gdy uytkownik kliknie przycisk.
Wykorzystanie klasy losujcej Random prezentowaem ju w rozdziale 3. Problemem jest
natomiast ustawienie nowej pozycji naszego komponentu.
Pooenie kadego komponentu w Windows Forms okrela waciwo Location, ktra
wskazuje na struktur System.Drawing.Point. Struktura ta (bdziemy o tym mwi w
kolejnym rozdziale) ma dwa pola X oraz Y. Listing 5.1 zawiera kod rdowy moduu
Form1.cs.
Listing 5.1. Kod rdowy moduu Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WindowsApplication1
{
public partial class Form1 : Form

{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// tworzenie nowej instancji klasy
Random RandomObj = new Random();
// pobranie rozmiarw formularza
Point StartPoint = new Point(this.Size);
// wylosowanie nowej pozycji
int X = RandomObj.Next(1, StartPoint.X - 75);
int Y = RandomObj.Next(1, StartPoint.Y - 23);
this.button1.Location = new Point(X, Y);
}
}
}

Zacznijmy od pocztku. Jeeli chcemy oprogramowa dane zdarzenie (np. zdarzenie Click),
musimy je wygenerowa, co spowoduje utworzenie tzw. procedury zdarzeniowej. W tym
wypadku bdzie to metoda button1_Click(). Wygenerowanie zdarzenia Click nastpuje po
podwjnym klikniciu danego obiektu (w naszym przypadku komponentu Button). Spjrz
ponownie na list zdarze. Zwr uwag, e do zdarzenia Click przypisana jest metoda
button1_click (rysunek 5.4).

Rysunek 5.4. Metoda przypisana do zdarzenia

Generowanie pozostaych zdarze


Wiesz ju, jak wygenerowa zdarzenie Click. A co z pozostaymi zdarzeniami? Odszukaj i
zaznacz pozycj Move. W prawej kolumnie, z listy rozwijalnej moesz wybra procedur
zdarzeniow, ktra ma odpowiada za obsug tego zdarzenia. Moesz te wygenerowa
now. Zwyczajnie nie wybieraj adnej metody z listy, tylko dwukrotnie kliknij puste pole.
rodowisko Visual C# Express Edition automatycznie wygeneruje now procedur
zdarzeniow dla tego zdarzenia. W tym momencie moemy oprogramowa zdarzenie Move,
ktre jest wywoywane, gdy komponent zostanie przeniesiony.
Kod takiej metody moe wyglda np. tak:

private void button1_Move(object sender, EventArgs e)


{
this.Text = String.Format("Aktualna pozycja: {0} - {1}",
this.button1.Location.X, this.button1.Location.Y);
}

W momencie przesunicia komponentu Button na belce tytuowej formularza wywietlona


zostanie informacja o jego aktualnej pozycji. Nale Ci si pewne wyjanienia co do kodu,
gdy zastosowaem tutaj elementy nieomawiane wczeniej.
Po pierwsze, sowo kluczowe this umoliwia odwoanie si do danej instancji klasy.
Poniewa jest to do skomplikowane, pozwol sobie objani to szczegowo w dalszej
czci rozdziau.
Formularz, podobnie jak kady inny komponent, posiada waciwoci i zdarzenia. Np.
waciwo Text okrela tekst wywietlany na belce tytuowej okna. Tak wic kod:
this.Text = "Tytu okna";

spowoduje zmian wartoci waciwoci Text na Tytu okna. W ten sam sposb mona si
odwoa do innych komponentw umieszczonych na formularzu oraz ich waciwoci. Jak
widzisz, w dalszej czci kodu instrukcja this.button1.Location.X odwouje si do
pooenia komponentu w poziomie.
Inna rzecz, ktra wymaga objanienia, to metoda Format() z klasy String. Metoda suy do
formatowania acucha. Innymi sowy, fraza {0} zostanie zamieniona przez warto
parametru metody Format() (czyli this.button1.Location.X); {1} zostanie zamieniona
przez warto kolejnego parametru itd. Zostanie to szczegowo objanione w rozdziale 9.

Obsuga zdarze
Ta sama metoda (procedura zdarzeniowa) moe by obsugiwana przez kilka zdarze.
Przykadowo, znajd na licie zdarze pozycj MouseHover. Z listy rozwijanej wybierz
button1_click(). Od tego momentu zdarzenie MouseHover bdzie obsugiwane przez
metod button1_click(). Moesz teraz uruchomi nasz program i sprawdzi jego dziaanie.
Zdarzenie MouseHover wykrywa moment, w ktrym kursor myszy znajdzie si nad danym
komponentem. Innymi sowy, przy przesuniciu kursora nad przycisk zostanie wykonana
metoda button1_click(), co spowoduje zmian pozycji komponentu.
Czasami moe zaj potrzeba przypisania tej samej procedury zdarzeniowej do kilku zdarze
rnych komponentw. Przykadowo, jeeli umiecimy na formularzu kolejny przycisk,
moemy oprogramowa jego zdarzenie Click, uywajc procedury zdarzeniowej

button1_click().

Klasy
Klasy to podstawa projektowania aplikacji w jzyku C#. Powiedzielimy sobie ju w
rozdziale 3., i nawet najprostszy program C# musi posiada przynajmniej jedn klas.
Na klasach opiera si cae rodowisko .NET Framework, w tym biblioteka Windows Forms.
Musisz przyzwyczai si do myli, i wszystko w C# jest klas! Kady typ, komponent! Np.
typ System.Int32 rwnie jest klas, a kiedy tworzymy now klas, automatycznie staje si
ona typem! Wiem, e jest to do zawia terminologia, ale wszystko powinno si wyjani w
dalszej czci rozdziau.
Klasy posiadaj funkcje, ktre nazywane s metodami. To rwnie powiniene wiedzie z
lektury poprzednich rozdziaw. Do tej pory o klasach wspominaem do sporadycznie, na
tyle, na ile byo to konieczne. Teraz mam zamiar omwi wszystkie elementy zwizane z
tworzeniem nowych klas. Mam nadziej, e dziki temu prezentowane wczeniej elementy
kodu, ktre nie byy objaniane, bd w peni zrozumiae po lekturze tego rozdziau.

Skadnia klasy
W jzyku C# klasy deklaruje si (czyli tworzy) z uyciem sowa kluczowego class:
class Foo
{
}

Klas deklarujemy z uyciem sowa kluczowego class.


Klasa musi mie nazw.
Klasa moe by pusta, lecz naley uy klamer { oraz }.
Klasa moe by umieszczona w przestrzeni nazw, ale nie musi.

Zwr uwag, e po nazwie klasy nie stawiamy znaku rednika.

Do czego su klasy

Jzyk C# jest w peni obiektowy. W starszych jzykach, takich jak chociaby C++ czy
Delphi, nie istnieje wymg korzystania z klas. Przykadowo, prosty program w jzyku Delphi
wyglda tak:
program Foo;
begin
Writeln('Witam serdecznie!');
Readln;
end.

Nie wiem, jak dla Ciebie, ale dla mnie taka konstrukcja jest prostsza. Jzyk Delphi nie
wymusza stosowania klas, aczkolwiek umoliwia ich uycie. Dla Czytelnika, ktry wczeniej
programowa, troch niezrozumiaa moe by idea wykorzystania klas. Dlaczego C#
wymusza na nas konieczno ich stosowania? Jakie waciwie korzyci pyn z uycia klas?
Przykadowo, aby zoy komputer, nie musz wiedzie, jak dokadnie dziaa procesor i z
jakich elementw jest zbudowany. Wystarczy, e wiem, e jest to centralna jednostka
komputera i e niej nie uruchomi caoci. Musz take wiedzie, gdzie woy procesor i jak
go przymocowa.
Kierowca samochodu nie musi wiedzie, co auto ma pod mask, jakie s parametry jego
silnika, jak dziaa skrzynia biegw i co powoduje, e cao si porusza. Wystarczy e wie, i
do uruchomienia samochodu potrzebne s kluczyki musi rwnie umie posugiwa si
kierownic, dwigni zmiany biegw i pedaami.
Jeeli wraz ze swoimi wsplnikami projektujecie jak wiksz aplikacj, kady moe zaj
si przydzielonym zadaniem przykadowo, kto zajmuje si utworzeniem klasy sucej do
wyszukiwania plikw na dysku, jeszcze kto tworzeniem innej klasy, a inna osoba jedynie
wszystko koordynuje i czy w cao. Nie musi ona wiedzie, w jaki sposb dziaa klasa
wyszukujca pliki, ale musi wiedzie, jak j poczy z reszt programu, tak aby wszystko
dziaao zgodnie z oczekiwaniami. Tego z kolei mona si dowiedzie z instrukcji (czyli z
dokumentacji dostarczonej przez autora klasy).
Nie musz wiedzie, jak dziaa klasa Console, jakie jest jej wntrze. Obchodzi mnie tylko,
jak z niej korzysta, jakie metody udostpnia i jak si nimi posugiwa. Najwaniejsze jest
wic wywietlenie tekstu oraz pobranie tekstu wpisanego przez uytkownika.

Instancja klasy
Moge zauway, i w prezentowanych wczeniej kodach uywaem sowa kluczowego new.
Ten operator
(tak, jest to operator) uywany jest do tworzenia tzw. instancji klasy. W tym momencie
zostaje zarezerwowana pami potrzebna do wykonania metod znajdujcych si w tej klasie.

Istotn spraw jest to, e moe istnie wiele instancji danej klasy. Jest to przewaga w
stosunku do idei programowania strukturalnego. Kada instancja rezerwuje osobny blok
pamici. Ewentualne zmienne (pola) znajdujce si w obrbie klasy korzystaj z osobnych
przestrzeni adresowych i mog mie rne wartoci.
W niektrych przypadkach nie trzeba tworzy nowej instancji klasy. Zwr bowiem uwag,
i z klasy Console korzystaem, nie tworzc wczeniej adnej instancji, nie uywaem w
ogle operatora new. Ten aspekt zostanie wyjaniony w dalszej czci ksiki.
Spjrz na ponisz instrukcj:
System.Int32 i = new System.Int32();

Deklarujemy zmienn i typu System.Int32, a nastpnie uywamy operatora new, aby


utworzy now instancj klasy. Co prawda nie jest to konieczne, bo jzyk C# udostpnia
prostszy zapis:
int i;

Chodzi mi o sam sposb tworzenia nowej instancji klasy, ktry jest do specyficzny. Naley
si jednak nad tym zastanowi, gdy w ksice prezentowaem do tej pory skrcony zapis
tworzenia nowej instancji.
Przed uyciem klasy czy wbudowanego typu danych (np. int) naley zadeklarowa
wskazujc na nie zmienn. To ju wiesz z lektury poprzednich rozdziaw. Nastpnie naley
utworzy now instancj klasy i przypisa rezultat tej operacji do naszej zmiennej:
Foo MyFoo;
MyFoo = new Foo();

Od tej pory przy pomocy MyFoo mamy dostp do elementw klasy Foo, czyli metod oraz pl
czy waciwoci.
Instancja klasy nazywana jest obiektem.

Sowo kluczowe this


Niekiedy przegldajc kody rdowe programw napisanych w jzyku C#, moesz napotka
na uycie sowa kluczowego this. Oznacza ono odwoanie do aktualnej instancji klasy.
Innymi sowy, uywajc this, otrzymujesz dostp do elementw danej klasy, w ktrej to
sowo zostao uyte:

class Foo
{
int X, Y;
public void Bar()
{
this.X = 10;
this.Y = 20;
}
}

To sowo kluczowe moesz traktowa jako ukryty wskanik do obiektu, nie jest ono
wymagane podczas odwoania do elementw klasy:
public void Bar()
{
X = 10;
Y = 20;
}

Rozwamy jednak nastpujc sytuacj:


class Foo
{
int X, Y;
public void Bar(int X, int Y)
{
X = X;
Y = Y;
}
}

Jak widzisz, w kodzie prbujemy przypisa polom klasy wartoci przekazane w parametrze
metody Bar(). Kompilator wywietli wwczas ostrzeenie: Assignment made to same
variable; did you mean to assign something else?. Komputer to tylko maszyna i nie domyla
si, e chcemy przypisa wartoci polom, w kocu maj one takie same nazwy jak parametry.
Wtedy z pomoc przychodzi sowo kluczowe this, ktre pozwala jawnie okreli, e chodzi
nam o pola klasy:
this.X = X;
this.Y = Y;

Czasami moe zaj potrzeba przekazania w parametrze metody wskanika do instancji klasy.
Wtedy mona uy sowa kluczowego this: Foo(this);.

Klasy zagniedone
rodowisko .NET Framework dopuszcza moliwo zagniedania typw, w tym oczywicie
klas. Co to oznacza? Moliwe jest zadeklarowanie klasy wewntrz innej klasy! Oto prosty
przykad:
class Foo
{
public class Bar
{
}
}

Oczywicie przy tworzeniu instancji dla zagniedanego obiektu naley poda jego
lokalizacj, uywajc operatora odwoania:
Foo.Bar FooBar = new Foo.Bar();

Pola
Pojcie zmiennych poznae ju dawno. Zmienne w jzyku C# mog by umieszczone
jedynie w klasach lub w ich metodach. Jeeli zmienna jest umieszczona w klasie (nie
wewntrz metody), mwimy wwczas o polu klasy.
Pola po prostu s zmiennymi lub staymi, deklarowanymi na uytek klasy lub udostpnionymi
na zewntrz od niej do uytku programisty. Dostp do zawartoci pl moliwy jest dziki
operatorowi odwoania (.):
class Foo
{
public String About = "Klasa v. 1.0";
}
class Program
{
static void Main(string[] args)
{
Foo MyFoo;

MyFoo = new Foo();


Console.Write(MyFoo.About);
Console.Read();
}
}

Oczywicie dostp do pl danej klasy posiadaj rwnie metody, ktre w tej klasie si
znajduj.

Metody
Funkcje jzyka C# umieszczone wewntrz klas nazywane s metodami. Poniewa jzyk C#
nie umoliwia deklarowania funkcji poza klas, wszystkie funkcje jednoczenie s metodami.
W klasie moe by wiele metod, do ktrych dostp mamy przy pomocy operatora odwoania,
podobnie jak dostp do pl. Jedyna rnica jest taka, e odwoujc si do metody, musimy
uy symboli nawiasw okrgych, nawet wwczas, gdy metoda nie posiada adnych
parametrw np.:
Console.Read();

Najprostsza deklaracja metoda klasy moe wyglda np. tak:


void Bar()
{
}

Taki kod odpowiada za deklaracj metody Bar() nieposiadajcej adnych parametrw ani
wartoci zwrotnej. Podsumowujc:

Kada metoda musi posiada nazw.


Metoda musi posiada ciao ograniczone klamrami { oraz } nawet wwczas,
gdy nie zawiera adnego kodu.
Metoda musi zwraca jak warto.
Metoda nie musi posiada parametrw, lecz obowizkowe s nawiasy () przy jej
nazwie.

Metod mona podzieli na nagwek (sygnatur) oraz wspomniane ju ciao, czyli

znajdujcy si w niej kod. Nagwek metody to nazwa wraz parametrami oraz typem
zwrotnym:
void Bar()

Deklarujc metod, zawsze trzeba okreli jej typ zwrotny. Innymi sowy, kada metoda
(funkcja) musi zwraca jak warto. Przykadowo, moemy zadeklarowa metod Power(),
ktra podnosi dan liczb do potgi i zwraca przemnoon warto typu int. Jeeli nie
chcemy, aby metoda zwracaa jakiekolwiek wartoci, naley uy typu void, ktry oznacza
warto pust.
W jzyku Pascal/Delphi istniej procedury (ktre nie zwracaj adnej wartoci) oraz funkcje
(ktre musz zwrci jak warto). W wikszoci jzykw, np. C, C++, Java i C#, nie
istniej procedury, a jedynie funkcje, ktre zwracaj jak warto.
Typ void jest aliasem dla typu .NET Framework System.void.

Zwracana warto
Aby lepiej zaprezentowa moliwo zwracania wartoci przez metody klasy, napiszmy
prost metod i zaprezentujmy jej uycie.
W naszej przykadowej klasie Foo zadeklaruj metod Power():
public int Power(int X)
{
return X * X;
}

Jest to prosta metoda, ktra zwraca warto typu int. Metoda posiada jeden parametr X
(rwnie typu int). W ciele metody nastpuje przemnoenie wartoci parametru X i zwrcenie
rezultatu.
Sowo kluczowe public to tzw. modyfikator dostpu. Pojcie to zostanie objanione w dalszej
czci rozdziau.
Sowo kluczowe return suy przede wszystkim do zwracania wartoci. Gdy ono wystpi,
kod zawarty w dalszej czci metody nie bdzie ju wykonywany:
int Bar()
{
return 2; // zwr warto 2

Console.WriteLine("Ten kod nie bdzie wykonany");


}

Jeeli warto zwrotna metody jest typu void, mona pomin wywoanie sowa return lub
pozostawi samo sowo kluczowe:
public void Bar()
{
return;
Console.WriteLine("Ten kod nie bdzie wykonywany");
}

Parametry metod
Troch si pospieszyem i w poprzednim przykadzie skorzystaem z tzw. parametru. Piszc
wasn metod, w nawiasach okrgych moemy okreli, jakie dane wejciowe spodziewamy
si otrzyma.
Przykadowo, parametrem metody Power() jest X typu int. Jest to parametr wejciowy, na
jakim nasza metoda operuje.
Innymi sowy, chcc uy metody Power(), w nawiasach musimy wpisa jak liczb typu
int, gdy tego wymaga od nas ta metoda, a tym samym kompilator. Parametry metod
mog by dowolnego typu.
Dla zabawy moemy napisa prost aplikacj, w ktrej zadeklarujemy metod przeliczajc
powiedzmy kilometry na mile angielskie. Jedna mila angielska to 1609,344 metry, czyli
1,6 km. Nasza metoda musi posiada parametr, ktry oznacza bdzie liczb kilometrw do
obliczenia. Taki kod moe wyglda tak:
static double KmToMile(double Km)
{
return ((Km * 1000) / 1609.344);
}

Parametr naszej metody nosi nazw Km i jest typu double. W ciele metody dokonujemy
dziaa matematycznych, ktre pozwol uzyska liczb mil zwracanych przy pomocy sowa
kluczowego return.
Cay program prezentuje listing 5.2.
Listing 5.2. Kod rdowy programu

using System;
namespace Temperature
{
class Program
{
static double KmToMile(double Km)
{
return ((Km * 1000) / 1609.344);
}
static void Main(string[] args)
{
Console.Write("Podaj liczb kilometrw: ");
double Km = Convert.ToDouble(Console.ReadLine());
Console.WriteLine("Liczba mil:
Console.ReadLine();

" + KmToMile(Km));

}
}
}

Wiele parametrw metod


Do tej pory w prezentowanych metodach uywaem pojedynczych parametrw. Moliwe jest
deklarowanie metod z wieloma parametrami, ktre posiadaj rne typy. W takim przypadku
parametry naley rozdzieli znakiem przecinka:
static int Multiple(int X, int Y)
{
return X * Y;
}

Parametry danej metody niekoniecznie musz by tego samego typu jak w zaprezentowanym
przykadzie (parametry s typu int); rwnie dobrze mona uy innych typw dostpnych w
C#:
static int Multiple(int X, int Y, float Z)

Oczywicie przekazujc dane w parametrze metody, musisz zadba o to, aby byy one zgodne
z typem zadeklarowanym w jej nagwku.

Parametry domylne
Znana zapewne wielu programistom moliwo nadawania domylnych parametrw dla
metod jest w jzyku C# niedostpna. Zamiast tego naley uywa mechanizmu przeciania
metod. Jest to zapewne wana informacja, jeeli programowae wczeniej w innym jzyku.
Jeeli dana metoda wymaga podania parametru, nie mona tego pomin przy jej
wywoywaniu. W takim wypadku kompilator wywietli bd No overload for method 'Multiple'
takes '0' argument.
Mwic o wywoaniu metody, mam na myli jej uycie. Z takim pojciem bdziesz spotyka si
w dalszej czci ksiki.

Przecianie metod
Przecianie (czsto nazywane przeadowywaniem) metod jest technik bardzo uyteczn i
czsto spotykan w rodowisku .NET Framework.
W danej klasie nie mog istnie dwie metody o tej samej nazwie. Nie mog istnie, pod
warunkiem e ich nagwek jest taki sam. Nie ma przeszkd, aby zadeklarowa dwie metody
o tej samej nazwie, jeeli tylko posiadaj rn ilo parametrw (lub parametry s rnych
typw). Oto przykad:
static int Multiple(int X, int Y)
{
return X * Y;
}
static double Multiple(double X, double Y)
{
return X * Y;
}

Program bdzie w stanie stwierdzi, o ktr metod nam chodzi, na podstawie parametrw,
jakie jej przekaemy:
Multiple(23.34, 35.45); // dobrze
Multiple(112, 10); // dobrze

W pierwszym przypadku wywoa metod, ktrej parametry s typu double; w drugim t z


parametrami typu int.

Przekazywanie parametrw
Proste, jak mogoby si wydawa, przekazywanie parametrw do metod ma o wiele wiksze
zastosowanie w jzyku C#. S bowiem rne sposoby przekazywania parametrw do metod,
o czym przekonasz si po lekturze dalszej czci tego rozdziau.

Przekazywanie przez warto


Do tej pory przekazywae do metod pewne wartoci np.:
Power(2, 3);

W metodzie Power() otrzymujemy te wartoci pod postaci zmiennych (w poprzednim


przykadzie X i Y). Nic nie stoi na przeszkodzie, aby przekaza do metody wartoci w
formie zmiennej:
int X, Y;
X = 2;
Y = 3;
Power(X, Y);

Jak widzisz, zadeklarowaem w programie zmienne X i Y, nastpnie nadaem im wartoci, po


czym przekazaem je do metody Power(). Przekazywanie parametrw przez warto rwna
si utworzeniu kopii zmiennych w danej metodzie.
Jzyk C# nie zabrania nam jednak modyfikacji wartoci parametrw w ciele metody. Aby
lepiej to zrozumie, spjrz na poniszy listing:
using System;
namespace Temperature
{
class Program
{
static int Power(int X, int Y)
{
X = 5;
Y = 5;
return X * Y;

}
static void Main(string[] args)
{
int X, Y;
X = 2;
Y = 3;
int Z = Power(X, Y);
Console.Write(Z);
Console.Read();
}
}
}

Mimo i przekazalimy do metody parametry X i Y (odpowiednio cyfry 2 i 3), w ciele


metody Power() nastpio nadpisanie ich wartoci. Jednake oryginalna warto zmiennej
przekazanej do metody nie zostaje w aden sposb naruszona:
int X, Y;
X = 2;
Y = 3;
int Z = Power(X, Y);
Console.Write(Z); // 25
Console.Write(X); // 2

Przekazywanie przez referencj


Przekazywanie parametrw przez referencj umoliwia metodzie modyfikacj wartoci, jaka
zostaa do niej przekazana oryginalnie. Oczywicie najatwiej wytumaczy to na przykadzie.
Spjrz na poniszy program:
using System;
namespace Temperature
{
class Program
{
static void Foo(out string S1, out string S2)
{

S1 = "Hello ";
S2 = "World";
}
static void Main(string[] args)
{
string MyS1, MyS2;
// przekazujc parametry, nie zapomnij
// o uyciu out
Foo(out MyS1, out MyS2);
Console.WriteLine(MyS1 + MyS2);
Console.Read();
}
}

Wskutek jego uruchomienia na ekranie konsoli pojawi si napis: Hello World. Deklarujc
parametry referencyjne w nagwku metody, musimy je poprzedzi sowem kluczowym out.
Rwnie przekazujc parametry, musimy uy tego sowa.
Kiedy uywamy parametrw referencyjnych, nic nie stoi na przeszkodzie, aby w zalenoci
od potrzeb przekazywa rwnie parametry przez warto. Spjrz na poniszy kod:
static void Foo(string S, out string S1, out string S2)
{
S1 = "Hello ";
S2 = "World" + S;
}
static void Main(string[] args)
{
string MyS1, MyS2;
Foo(" my darling!", out MyS1, out MyS2);
Console.WriteLine(MyS1 + MyS2);
Console.Read();
}

Pierwszy parametr metody Foo() jest przekazywany przez warto, kolejne dwa przez
referencj.
Zwracanie danych przez referencj czsto si przydaje, gdy metoda musi zwrci wiele
informacji na temat swojego dziaania. Kada metoda moe zwrci warto owszem. Ale
co wwczas, gdy musisz zwrci do programu gwnego kilka informacji? Niezastpiona jest
wtedy metoda, ktrej parametry zwracane s przez referencj.

W jzyku C# istnieje moliwo przekazywania parametrw, rwnie przy pomocy sowa


kluczowego ref. Rnica pomidzy ref a out jest taka, i w przypadku ref zmienna musi
zosta zainicjowana (tj. dane musz zosta do niej wczeniej przypisane):
static void Foo(ref string S)
{
Console.WriteLine("Warto oryginalna: " + S);
S = "Bar";
}
static void Main(string[] args)
{
string MyS = "Foo";
Foo(ref MyS);
Console.WriteLine("Warto zastpiona: " + MyS);
Console.Read();
}

Jeeli w programie nie przypisalibymy wartoci dla zmiennej MyS, podczas kompilacji
wystpiby bd: Use of unassigned local variable 'MyS'.
W programie C# kada zmienna przed uyciem musi zosta zainicjowana (czyli najprociej
mwic musz zosta przypisane do niej jakie dane). Poniszy fragment prezentuje kod,
ktrego prba kompilacji zakoczy si bdem (Use of unassigned local variable):
int I;
Console.WriteLine(I);

Programowanie z uyciem typw referencyjnych jest wydajnym sposobem tworzenia metod,


zwaszcza w przypadku duych parametrw (np. zawierajcych spore porcje danych).
Przekazywanie przez warto powoduje utworzenie kopii zmiennej i przekazanie jej
metodzie. Ta moe na tej kopii dokonywa dowolnych operacji, a orygina i tak nie ulegnie
zmianie. W przypadku referencji do metody przekazywany jest jedynie adres komrki
pamici, w ktrej znajduje si warto (sama warto nie jest duplikowana). Celowo w
poprzednich przykadach nadawaem zmiennym rne nazwy, gdy zarwno MyS, jak i S
wskazyway na te same dane w pamici.

Dziedziczenie
Caa biblioteka Windows Forms oparta jest na dziedziczeniu. Ba caa biblioteka klas
rodowiska .NET Framework oparta jest na dziedziczeniu, ktre mona okreli jako

fundament budowania klas.


Powrmy do przykadu z silnikiem. Projektanci, chcc ulepszy dany silnik, mog nie chcie
zaczyna od zera. Byaby to zwyczajna strata czasu. Nie lepiej po prostu unowoczeni ju
istniejcy silnik?
Przykad z silnikiem mona zastosowa do klas. Aby zbudowa now, bardziej funkcjonaln
klas, mona przej moliwoci starej. Taki proces nazywamy w programowaniu
dziedziczeniem.
Przykadowo, w caym rodowisku .NET Framework gwn klas jest Object, znajdujca
si w podstawowej przestrzeni nazw System. Kolejne klasy, np. Int32, ju tylko
dziedzicz z tej podstawowej, przejmujc od niej wszystkie moliwoci. Moliwociami w
tym wypadku s metody znajdujce si w klasie Object. Przykadowo, klasa ta zawiera
metod ToString(). Klasa Int32 oraz wszystkie pozostae rwnie zawieraj t metod,
poniewa j odziedziczyy!
W takim wypadku o klasie Object mwimy jako o klasie bazowej, a Int23 nazywamy klas
potomn. Spjrz na poniszy fragment kodu, w ktrym zadeklarowano dwie klasy:
class A
{
public void Foo()
{
Console.WriteLine("Jestem metod z klasy A!");
}
}
class B : A
{
}

W klasie A znajduje si metoda Foo(). Klasa B dziedziczy po A, czyli przejmuje od niej


wszystkie metody i pola! Dziedziczenie polega na umieszczeniu klasy bazowej po znaku
dwukropka:
class Nazwa klasy : Nazwa klasy bazowej { }
Moemy teraz bez problemu utworzy instancj klasy B i wywoa metod Foo():
B MyClass = new B();
MyClass.Foo();

Klasa domylna

Kiedy deklarujemy now klas, nie ma wymogu podawania jej klasy bazowej! Jeeli nie
okrelimy klasy bazowej, to nasz typ automatycznie bdzie dziedziczy po klasie
System.Object, przejmujc od niej wszystkie metody.

Hermetyzacja
Pojcie hermetyzacji jest zwizane z ukrywaniem pewnych danych. Klasy udostpniaj na
zewntrz pewien interfejs opisujcy ich dziaanie i tylko z tego interfejsu moe korzysta
uytkownik. Klasy mog bowiem zawiera dziesitki, a nawet setki metod (funkcji), ktre
wykonuj rne czynnoci. My jako projektanci klasy powinnimy zapewni dostp jedynie
do niektrych metod, tak aby potencjalny uytkownik nie mg wykorzystywa wszystkich,
gdy moe to spowodowa nieprzewidywalne dziaanie programu, zawieszanie itp.
Wewntrz silnika samochodu te dochodzi do pewnych procesw, ale kierowca
nie musi o nich wiedzie. Informacji tych nie potrzebuje take inny element silnika, ktry si
z nim czy komunikowanie si pomidzy elementami przebiega ustalonym strumieniem i
to wystarczy.
C# pozwala na ukrywanie kodu w klasie, w tym celu stosuje si pewne klauzule nazwane w
C# modyfikatorami dostpu (ang. access modifiers).

Modyfikatory dostpu
Modyfikatory dostpu to sowa kluczowe okrelajce dostp do elementw klasy.
Elementami klasy s jej metody oraz pola, a take waciwoci (o tym za chwil).
Jzyk C# udostpnia cztery sowa kluczowe okrelajce sposb dostpu do danego czonka
klasy: private (prywatne), protected (chronione), public (publiczne) oraz internal
(wewntrzne). W zalenoci od sekcji, w ktrej metody zostan umieszczone, bd one
inaczej interpretowane przez kompilator. Modyfikator dostpu musi poprzedza deklaracj
danego elementu klasy:
class Bar
{
private string S;
public
int X, Y;

protected double D;
}

W klasie Bar pole S bdzie prywatne, natomiast pola X i Y publiczne. W klasie


zadeklarowaem jedno pole chronione D.
Gdy wystpi sowo kluczowe okrelajce modyfikator dostpu, wszystkie dalsze deklaracje
bd traktowane zgodnie z jego znaczeniem. W powyszym kodzie pola X i Y bd wic typu
publicznego. Dobr praktyk jest poprzedzanie kadej deklaracji elementu klasy
odpowiednim modyfikatorem:
class Bar
{
private int X;
private int Y;
public string S;
protected double D;
}

Sekcja private
Wszystkie pola czy metody umieszczone w klasie automatycznie s traktowane jako
prywatne. Oznacza to, e dostp do nich jest niemoliwy spoza klasy, w ktrej s
umieszczone! Oto przykad:
class MyClass
{
int X, Y;
}
class Program
{
static void Main(string[] args)
{
MyClass.X = 10; // bd!
MyClass Class = new MyClass();
Class.Y = 10; // bd!
}
}

Obydwa odwoania do pl X oraz Y s bdne. Kompilator w trakcie kompilacji wywietli

bd: 'FooConsole.MyClass.X' is inaccessible due to its protection level. Nie ma za to


problemu z dostpem do tych pl z poziomu metod znajdujcych si w tej samej klasie:
class MyClass
{
int X, Y;
void Foo()
{
X = 10;
Y = 20;
}
}

Ponisze dwie instrukcje s ze sob rwnowane:


private int X;
int X;
Jeeli nie okrelimy modyfikatora dostpu, kompilator automatycznie bdzie traktowa takie
elementy jako prywatne.

Sekcja public
Modyfikator public oznacza, i elementy klasy opatrzone t klauzul bd traktowane jako
publiczne. Do takich elementw mona odwoywa si zarwno spoza klasy, jak i z jej
wntrza. Poniszy kod jest wic jak najbardziej prawidowy:
class MyClass
{
public int X, Y;
public void Foo()
{
X = 10;
Y = 20;
}
}
class Program
{
static void Main(string[] args)
{
MyClass Class = new MyClass();

Class.Foo();
Class.X = 20;
Class.Y = 50;
}
}

Modyfikatory dostpu maj takie same dziaanie w przypadku pl oraz metod. Chciabym
podkreli, i metody rwnie mog by prywatne lub publiczne, tak samo jak pola klasy.

Sekcja protected
Elementy klasy zadeklarowane jako protected s traktowane jako chronione. Ma to zwizek
z procesem dziedziczenia klas. Chronione elementy s niedostpne poza klas, w ktrej s
zadeklarowane. S one widoczne jedynie dla klas potomnych. Spjrz na poniszy przykad:
class A
{
protected int X;
public void Foo()
{
Console.WriteLine("Jestem metod z klasy A!");
}
}
class B : A
{
private void Bar()
{
X = 10;
}
}
class Program
{
static void Main(string[] args)
{
B MyB = new B();
MyB.Foo();
A MyA = new A();
MyB.X = 10; // Bd!
MyA.X = 10; // Bd!
}
}

Spjrz na metod Main() klasy Program. Podczas prby kompilacji takiego kodu odwoanie
si do elementu X, zarwno z obiektu MyA, jak i MyB, zakoczy si niepowodzeniem.
Natomiast w klasie B moemy wykorzysta pole X, mimo i zostao ono zadeklarowane w
klasie A.

Sekcja internal
Jest to zdecydowanie najrzadziej wykorzystywany modyfikator dostpu. Okrela on, e z
danego elementu bdzie mona skorzysta jedynie w obrbie podzespou, w ktrym jest
uyty. Szczegowe omwienie podzespow .NET znajdziesz w rozdziale 11.

Konstruktor
Sama klasa nie zajmuje miejsca w pamici komputera. W momencie utworzenia nowej
instancji program rezerwuje w pamici miejsce potrzebne do wykonania danej klasy.
Konstruktor to specjalna metoda, ktra jest wywoywana automatycznie w momencie
utworzenia instancji klasy. W rzeczywistoci instrukcja new Foo() oprcz utworzenia
instancji prbuje wywoa konstruktora tej klasy. Konstruktor jest jednak elementem
opcjonalnym i jego pominicie nie jest bdem.
Konstruktor jest metod o takiej samej nazwie co klasa, w ktrej jest zadeklarowany. Spjrz
na poniszy fragment kodu:
using System;
namespace MyConsole
{
class Foo
{
public Foo()
{
Console.WriteLine("Jestem konstruktorem klasy
Foo");
}
}
class Program
{
static void Main(string[] args)
{

Foo MyFoo;
MyFoo = new Foo();
Console.Read();
}
}
}

Zwr uwag, e w klasie Foo zadeklarowaem metod Foo(), ktra od tego momentu jest
konstruktorem klasy. Instrukcja:
MyFoo = new Foo();

tworzy now instancj klasy i wywouje jej konstruktor. Moesz to sprawdzi, uruchamiajc
powyszy program.
Konstruktor musi by opatrzony klauzul public. Nie moe on rwnie zwraca adnej
wartoci. Deklarujc konstruktor, nie okrelamy jego typu zwrotnego, nawet jeli uywamy
wartoci pustej (void).
Istnieje moliwo przeciania konstruktorw, tak jak mona przecia zwyke metody
klasy. Prosz pamita o tym, e jeeli chcemy w klasie umieci wiele konstruktorw, kady
z nich musi posiada rne parametry.
Konstruktor musi posiada tak sam nazw jak klasa. Nie ma moliwoci utworzenia
konstruktor klasy, ktry posiadaby inn nazw.

Pola tylko do odczytu


Istnieje moliwo deklarowania pl jedynie do odczytu. Wartoci takich pl nie mog by
modyfikowane. Istnieje jeden wyjtek pozwalajcy na modyfikacj zawartoci takiego pola w
konstruktorze. Spjrz na poniszy kod:
class MyClass
{
private readonly int X = 10;
public MyClass()
{
X = 20;
}
public void Foo()

{
X = 30; // Bd!
}
}

Pola tylko do odczytu deklaruje si z uyciem sowa kluczowego readonly. Warto takiego
pola mona nada bezporednio w kodzie (w trakcie deklaracji), tak jak w przypadku staych,
lub w ciele konstruktora, tak jak zostao to zaprezentowane na przykadzie. Prba zmiany
wartoci pola w innym miejscu w kodzie koczy si bdem: A readonly field cannot be
assigned to (except in a constructor or a variable initializer).
Pola tylko do odczytu mog by dobrym rozwizaniem porednim pomidzy polami staymi
(const) a zwykymi.

Destruktor
Destruktor jest rwnie specjaln metod, wywoywan po zakoczeniu korzystania z danej
klasy. Przy zamykaniu aplikacji specjalny mechanizm rodowiska .NET Framework
(nazwany garbage collection) zwalnia pami zarezerwowan przez poszczeglne instancje
klas naszego programu. W momencie usuwania danej klasy z pamici wywoywany jest jej
destruktor. Jeeli programista chce jako zareagowa na moment usuwania klasy z pamici,
powinien umieci w niej destruktor.
Destruktor w klasie moe by tylko jeden i musi nosi tak sam nazw co klasa, w ktrej si
znajduje. Destruktor naley zadeklarowa, poprzedzajc jego nazw symbolem ~.
~Foo()
{
Console.WriteLine("Do widzenia...");
}

Waciwoci
Jzyk C# dopuszcza tworzenie publicznych pl. Dobr praktyk jest deklarowanie pl jako
elementw prywatnych. Klasa powinna wykorzystywa pola jako zmienne, jedynie na wasny
uytek. Do komunikowania si ze wiatem zewntrznym powinno si uywa waciwoci.
Waciwoci, podobnie jak pola (te pojcia s czsto ze sob mylone), su do gromadzenia
danych (czyli do odczytywania oraz przypisywania informacji). Oprcz tego w przypadku

waciwoci istnieje moliwo zaprogramowania dodatkowych czynnoci podczas, na


przykad, przypisywania im wartoci (danych).
Waciwoci mog by prywatne, lecz najczciej s to elementy publiczne.
Caa biblioteka klas rodowiska .NET Framework opiera si na waciwociach oraz
metodach. Pola klas tej biblioteki s polami prywatnymi, wykorzystywanymi na potrzeby
danej klasy.
Waciwo musi posiada przede wszystkim nazw oraz typ. Przykadowa deklaracja
waciwoci:
class Foo
{
private int month = 12;
public int Month
{
get
{
return month;
}
}
}

Obowizkowymi elementami waciwoci s rwnie klamry { }. Po sowie kluczowym


get naley umieci instrukcje, ktre bd wykonywane w momencie, gdy uytkownik
zada odczytu wartoci owej waciwoci. Innymi sowy: prba odczytania wartoci Month
spowoduje zwrcenie wartoci pola month.
Z waciwoci korzystamy tak jak ze zwykego pola:
Foo MyFoo = new Foo();
Console.WriteLine("Miesic: " + MyFoo.Month);

Waciwoci jzyka C# mog by jedynie do odczytu lub jedynie do zapisu (albo do odczytu i
zapisu jednoczenie). Innymi sowy, moemy zabroni przypisywania danych do
waciwoci. W powyszym przykadzie waciwo Month jest tylko do odczytu, prba
przypisania danych zakoczy si komunikatem bdu: Property or indexer
'FooConsole.Foo.Month' cannot be assigned to -- it is read only.
Sowo kluczowe get okrela akcj, jaka bdzie wykonywana w momencie odczytu danych.
Natomiast inne sowo kluczowe set umoliwia zaprogramowanie czynnoci, jakie bd
wykonywane w momencie zapisu danych.
Przykadowo: piszemy klas, ktra posiada waciwo Month (miesic). Chcemy, aby
uytkownik mg do niej przypisa liczb z zakresu od 1 do 12 (w kocu mamy 12 miesicy
w roku). Dziki waciwociom moemy dokona sprawdzenia poprawnoci danych:

class Foo
{
private int month = 12;
public int Month
{
get
{
return month;
}
set
{
if (value > 1 && value <= 12)
{
month = value;
}
}
}
}

value jest sowem kluczowym jzyka C#. Przechowuje warto przypisan do waciwoci.

Moesz sprawdzi dziaanie takiego programu. Prba przypisania liczby wikszej od 12 (lub
mniejszej od 1) zakoczy si niepowodzeniem:
Foo MyFoo = new Foo();
MyFoo.Month = 23; // warto nie zostanie przypisana!

Kolejny przykad:
class Foo
{
private int hour;
private int second;
public int Hour
{
get
{
return hour;
}
set
{
if (value >= 0 && value <= 24)
{
hour = value;
second = value * 3600;

}
}
}
public string Info()
{
return "Godzina " + hour + ", " + second + " sekunda
tej doby";
}
}

Przy ustawianiu wartoci dla waciwoci Hour obliczana jest ilo sekund. Te wartoci s
nastpnie przechowywane w polach hour oraz second (uwaga na wielko znakw!).
Publiczna metoda Info() zwraca acuch zawierajcy informacje
odnonie do liczby sekund oraz godziny.
Podsumowujc:

Waciwoci zapewniaj dogodny sposb na przypisywanie i odczytywanie danych,


ukrywajc przy tym szczegy ich weryfikacji.
Sowo kluczowe get jest uywane do zwracania wartoci, a set do ustawiania
nowych.
Waciwo nie moe pozosta pusta, tj. musi posiada blok set lub get.
Waciwoci nieposiadajce bloku set s tylko do odczytu.

Modyfikatory dostpu
Waciwoci rwnie mog posiada modyfikatory dostpu. To jest jasne. Ciekawostk jest
to, e modyfikatory dostpu mog by przypisywane blokom set oraz get:
public int Hour
{
get
{
return hour;
}
protected set
{
if (value >= 0 && value <= 24)
{
hour = value;
second = value * 3600;

}
}
}

Tak zadeklarowanej waciwoci nie mona nada wartoci, gdy blok set opatrzony jest
klauzul protected. Mona to dopiero zrobi w klasach potomnych:
class Bar : Foo
{
public Bar()
{
// przypisanie nowej wartoci
Hour = 23;
}
}

W przyszoci moesz si spotka z okreleniem akcesory w odniesieniu do blokw set


oraz get waciwoci klas.

Elementy statyczne
Chciabym w tym momencie wspomnie o istotnym elemencie programowania obiektowego;
bardzo istotnym, jeeli chodzi o rodowisko .NET Framework, w ktrym wystpuje bardzo
czsto. Mowa tutaj o elementach statycznych.
Biblioteka klas .NET Framework to setki (jeli nie tysice) klas. Zwr uwag, e moemy
uywa niektrych klas, nie tworzc nowej instancji. Dobrym przykadem jest klasa Console,
ktrej prawdopodobnie uywae najczciej, czytajc t ksik. Nie przypominasz sobie,
aby kiedykolwiek tworzy instancj tej klasy, prawda?
Biblioteka klas zawiera mnstwo typw, ktre s statyczne, opatrzone klauzul static.
Klasy statyczne (klasa te jest typem!) mog dziaa bez koniecznoci tworzenia nowego
obiektu. Ba, niekiedy nie jest wwczas moliwe utworzenie nowej instancji takiej klasy przy
uyciu sowa kluczowego new. Klasy statyczne s adowane do pamici przez CLR w
momencie, gdy program lub przestrze nazw, ktra je zawiera, jest adowana.

Metody statyczne
Sowo kluczowe static jest waciwie modyfikatorem dostpu. Statyczne mog by nie
tylko klasy, ale rwnie metody czy waciwoci. Spjrz na poniszy przykad:

class Foo
{
public Foo()
{
Console.WriteLine("Tworzenie nowego obiektu!");
}
static public void Bar()
{
Console.WriteLine("Hello from static!");
}
}

W klasie Foo utworzyem konstruktor oraz metod Bar(), ktra jest statyczn metod
publiczn. Tak metod mog wywoa, nie tworzc nowej instancji klasy:
Foo.Bar();

Nie oznacza to, e nie mog najzwyczajniej utworzy nowego obiektu:


Foo MyFoo = new Foo();

Metody statyczne rni si od normalnych pod wieloma wzgldami. Przede wszystkim


metoda statyczna nie moe by wywoywana z poziomu obiektu klasy:
Foo MyFoo = new Foo();
MyFoo.Bar(); // <-- bd

Taka konstrukcja spowoduje wywietlenie bdu kompilacji: Static member


'FooConsole.Foo.Bar()' cannot be accessed with an instance reference; qualify it with a type
name instead.
To samo jednak mona powiedzie o zwykych metodach wewntrz klasy. One z kolei nie
mog by wywoywane bez uprzedniego utworzenia instancji klasy.
Kolejne powane ograniczenie, o ktrym powiniene wiedzie, jest takie, e w metodach
statycznych nie mona uywa sowa kluczowego this. Wszystko dlatego, e this odnosi si
do danej instancji klasy. Oznacza to wic, e nie moesz si odwoywa do pl/waciwoci
klasy, ktre nie s statyczne! Jest to wane, wic radz zapamita to zdanie by moe
pozwoli to unikn problemw z kompilacj programu. Oto przykad:
static int Field;
int Field2;

static public void Bar()


{
Field = 10;
Field2 = 10; // <-- bd
}

Z metody statycznej Bar() nie moemy odwoa si do pola Field2, poniewa nie jest ono
statyczne.
Natomiast odwrotna sytuacja jest jak najbardziej dopuszczalna. Tzn. zwyke metody klas
mog odwoywa si do metod statycznych.
Klauzula static musi znajdowa si przed sowem okrelajcym typ zwrotny metody. Z kolei
pooenie modyfikatora dostpu nie ma znaczenia. Moemy wic napisa static public lub
public static.
Metody statyczne mog by prywatne lub publiczne, aczkolwiek deklarowanie prywatnej
metody statycznej mija si z celem.

Klasy statyczne
Klasa moe posiada statyczne metody, pola oraz waciwoci. Sowem kluczowym static
moemy opatrzy nawet ca klas. Wwczas wymuszamy, aby wszystkie elementy tej klasy
byy statyczne. Spjrz na poniszy kod:
static class Foo
{
public void Bar() // <-- bd
{
}
}

Mimo i klasa zostaa opatrzona klauzul static, metoda Bar() wci pozostaje zwyk
metod. Prba kompilacji takiego kodu zakoczy si bdem: 'Bar': cannot declare instance
members in a static class.
Statyczne klasy mog posiada konstruktory, lecz one take musz by statyczne i prywatne
(tj. pozbawione modyfikatora public):
static class Foo
{
static Foo()
{

Console.WriteLine("Wykryem uycie klasy Foo!");


}
public static void Bar()
{
Console.WriteLine("Statyczna metoda Bar()!");
}
}

Statyczny konstruktor nie moe posiada modyfikatorw dostpu oraz parametrw. Nie
mona go jawnie wywoa jest on wywoywany wwczas, gdy nastpi pierwsze odwoanie
do klasy:
Foo.Bar();

Podsumowujc: typy statyczne to bardzo wydajny mechanizm umoliwiajcy wykorzystanie


klas bez koniecznoci tworzenia ich instancji. Podczas projektowania wasnych klas musisz
si zastanowi, czy bdziesz potrzebowa tworzy kilka obiektw danej klasy oraz jak jest
ona skomplikowana.
Klasy statyczne mog by przydatne jako pojemnik sucy do grupowania metod
wykonujcych podobne zadania. Dobrym przykadem moe by klasa Math, ktra zawiera
metody suce do oblicze matematycznych.

Polimorfizm
Pojcie polimorfizmu w jzyku C# jest zwizane z dziedziczeniem. Jest to do
skomplikowane pojcie (szczeglnie dla pocztkujcych programistw) pozwalajce na
tworzenie zaawansowanych klas bezporednio ze sob poczonych. Ja postaram si
wytumaczy to jak najprociej, prezentujc fragmenty kodu.
Polimorfizm jest najwikszym osigniciem techniki programowania obiektowego. Sowo to
pochodzi od greckiego polmorphos oznaczajcego wielopostaciowy, co odzwierciedla
znaczenie tej techniki. W programowaniu oznacza to moliwo operowania na obiektach
nalecych do rnych klas. Oczywicie ta definicja teraz wydaje Ci si bardzo mglista, wic
zacznijmy od pocztku

Ukrywanie elementw klas


Powiedzielimy ju, e klasy potomne dziedzicz z klas bazowych ich elementy publiczne

oraz chronione. Rozwamy teraz sytuacj, w ktrej w klasie potomnej chcemy zadeklarowa
metod istniejc w klasie bazowej. Myl, e poniszy przykad zobrazuje moje zamierzenia:
class Animal
{
public void Run()
{
Console.WriteLine("Metoda Run() z klasy Animal");
}
}
class Mammal : Animal
{
public void Run()
{
Console.WriteLine("Metoda Run() z klasy Mammal");
}
}

Klasa Mammal dziedziczy po Animal. Obie posiadaj metod Run(). Jeli utworzymy now
instancj klasy Mammal i wywoamy metod Run(), wykonany zostanie kod z klasy Mammal:
Mammal MyAnimal = new Mammal();
MyAnimal.Run();

Czyli wszystko dziaa tak, jak powinno. W takim programie kompilator C# wywietli
ostrzeenie: 'FooConsole.Mammal.Run()' hides inherited member
'FooConsole.Animal.Run()'. Use the new keyword if hiding was intended. Komunikat
informuje nas, e w klasie Mammal zadeklarowalimy metod Run(), ktra przykrywa element
o tej samie nazwie, uprzednio zadeklarowany w klasie Animal.
Chocia z punktu widzenia kompilatora nie jest to bd (taki kod zostanie skompilowany),
dobr praktyk jest jawne okrelenie, e wiemy, co robimy, i chcemy przykry element
uprzednio zadeklarowany w klasie bazowej. Suy do tego sowo kluczowe new, ktre
poznae ju wczeniej. W tym kontekcie to sowo suy jako modyfikator dostpu:
new public void Run()
{
Console.WriteLine("Metoda Run() z klasy Mammal");
}

Za spraw modyfikatora new komunikat informujcy o przykrywaniu metody nie bdzie si


ju wicej pokazywa. Informujemy tym samym kompilator, i wiemy, e w klasie bazowej
istnieje metoda Run(), ale chcemy zadeklarowa metod o takiej samej nazwie w klasie
potomnej.

Takie dziaanie nie dotyczy jedynie metod klas, ale wszystkich elementw, wczajc
waciwoci i pola.
Elementy statyczne rwnie mog by przykrywane. Spjrz na poniszy przykad:
class Animal
{
public static int Age = 12;
public static void Run()
{
Console.WriteLine("Metoda Run() z klasy Animal");
}
}
class Mammal : Animal
{
new public static int Age = 100;
new public static void Run()
{
Console.WriteLine("Metoda Run() z klasy Mammal");
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Animal.Age);
Console.WriteLine(Mammal.Age);
Console.ReadLine();
}
}

W klasie Animal zadeklarowaem statyczne pole Age, ktremu nadaem warto 12. W klasie
potomnej to pole jest przykrywane i nadawana jest mu nowa warto. W skutek dziaania
takiego kodu na ekranie konsoli wywietlone zostanie:
12
100
Dla sprawdzenia zasad dziaania dziedziczenia moesz skomentowa lini odpowiedzialn za
przykrywanie elementu Age:
// new public static int Age = 100;

Teraz uruchom program ponownie. Rezultat dziaania takiego programu:


100
100
Wiesz, dlaczego tak si stao?

Sowo kluczowe base


Nie wspomniaem o tym przy okazji omawiania dziedziczenia, lecz jest to istotny element
zwizany z tym procesem. Mam tu na myli moliwo uzyskania dostpu do elementw
klasy bazowej. W C# realizujemy to przy pomocy sowa kluczowego base:
new public void Run()
{
base.Run();
Console.WriteLine("Metoda Run() z klasy Mammal");
}

W tej metodzie najpierw wykonujemy kod metody Run() z klasy bazowej, a dopiero pniej
dalsze instrukcje z ciaa metody. Nie ma tutaj wikszej filozofii. Dostp do elementw klasy
bazowej uzyskujemy przy pomocy operatora odwoania (.).
Sowo kluczowe base ma bardziej zaawansowane zastosowanie w poczeniu z
konstruktorami klas:
class Animal
{
public string Name;
public Animal()
{
this.Name = "Pet";
}
public Animal(string Name)
{
this.Name = Name;
}
}
class Mammal : Animal
{

public Mammal()
: base()
{
// dodatkowy kod
}
public Mammal(string Name)
: base("Tina")
{
// dodatkowy kod
}
public void Run()
{
Console.WriteLine("Imi zwierztka to: " + this.Name);
}
}

Zacznijmy od pocztku. W klasie Animal utworzyem dwa konstruktory. Jeden z nich posiada
parametr Name. Taka konstrukcja jest Ci znana z lektury poprzednich fragmentw rozdziau.
W klasie potomnej rwnie zadeklarowaem dwa konstruktory. Pierwszy nie posiada
parametrw i wywouje konstruktor klasy bazowej (ktry rwnie nie posiada parametrw).
Drugi konstruktor klasy Mammal wywouje konstruktor klasy bazowej z parametrem Tina. Jak
mylisz, jaki bdzie rezultat dziaania poniszego kodu?
Mammal MyAnimal = new Mammal("Jack");
MyAnimal.Run();

Na konsoli zostanie wywietlony tekst: Imi zwierztka to: Tina. Dlaczego nie Jack?
Poniewa w konstruktorze klasy Mammal podajemy parametr. Teraz zwr uwag na kod tego
konstruktora. Wywouje on konstruktor bazowy z parametrem Tina. Poprawienie tego kodu
wymaga maej poprawki:
public Mammal(string Name)
: base(Name)
{
// dodatkowy kod
}

Teraz do konstruktora bazowego przekazywany jest parametr przekazany do konstruktora


klasy Mammal.
Sowo kluczowe base nie moe by uywane w metodach statycznych.

Metody wirtualne
Wyobra sobie, e w programie tworzysz klasy odpowiadajce gatunkom zwierzt. Mamy
wic klas bazow Animal. Mamy rwnie klasy pochodne Mammal (ssaki) oraz Fish
(ryby). W klasie macierzystej mamy metod Breath() (oddychaj), ktra oczywicie jest
rwnie dostpna w klasach potomnych, tyle e moe by inaczej interpretowana w klasie
Mammal oraz Fish. Ssaki oddychaj przecie pucami, a ryby skrzelami. Moemy wic w
klasie Fish utworzy now metod Breath(), ktra przykrywa oryginaln, zadeklarowan w
klasie bazowej. Moemy rwnie zadeklarowa tzw. metod wirtualn, ktra w klasach
potomnych moe by przedefiniowana.
Te dwa pojcia s cile zwizane z polimorfizmem.
Metoda wirtualna to taka, ktra jest przygotowana do zastpienia w klasie potomnej.
Metod wirtualn tworzymy, dodajc do jej deklaracji sowo kluczowe virtual:
class Animal
{
public int Age;
public virtual void Breath()
{
Console.WriteLine("Zwierzak oddycha...");
}
}

W ten sposb dajemy kompilatorowi do zrozumienia, i metoda Breath() moe by poddana


w klasach potomnych procesowi przedefiniowania.
Metoda statyczna nie moe by opatrzona klauzul virtual.

Przedefiniowanie metod
Przedefiniowanie (ang. override) to proces polegajcy na tworzeniu nowej wersji metody w
klasie potomnej. Polega on na utworzeniu metody, ktra opatrzona bdzie sowem
kluczowym override:
class Mammal : Animal
{
public override void Breath()
{
Console.WriteLine("Ssak oddycha pucami...");

}
}
class Fish : Animal
{
public override void Breath()
{
Console.WriteLine("Ryba oddycha skrzelami...");
}
}

W tym przykadzie w klasach potomnych zmienilimy znaczenie metody Breath(). No


dobrze, ale czym to si rni od uprzednio zaprezentowanego przykrywania elementw klas?
Przyjrzyj si metodzie Main(), ktrej kod znajduje si na listingu 5.3.
Listing 5.3. Przykad wykorzystania przedefiniowanych metod
static void Main(string[] args)
{
Animal MyAnimal;
Console.WriteLine("1 - twrz obiekt klasy Animal");
Console.WriteLine("2 - twrz obiekt klasy Mammal");
Console.WriteLine("3 - twrz obiekt klasy Fish");
ConsoleKeyInfo Key = Console.ReadKey();
switch (Key.KeyChar)
{
case '1':
MyAnimal = new Animal();
break;
case '2':
MyAnimal = new Mammal();
break;
case '3':
MyAnimal = new Fish();
break;
default:
MyAnimal = new Animal();
break;
}
Console.Clear();

// wywoanie metody
MyAnimal.Breath();
Console.ReadLine();
}

Na samym pocztku zadeklarowaem zmienn MyAnimal, wskazujc na klas bazow


Animal. Nastpnie na podstawie opcji wybranej przez uytkownika utworzony zostanie
odpowiedni obiekt. Na samym kocu wywoana zostanie metoda Breath(). Dziki metodom
wirtualnym kompilator wie, z ktrej klasy kod ma zosta w danej chwili wywoany.
Moesz sprawdzi dziaanie takiego kodu, usuwajc ze rda wszelkie sowa kluczowe
virtual oraz override. Po kompilacji programu moesz zauway, e niezalenie od
wybranej opcji zostanie wykonana metoda Breath() z klasy Animal. W zwyczajnych
metodach decyzja, ktra metoda rzeczywicie ma zosta wywoana, zapada ju w trakcie
kompilacji programu. Decyduje o tym powizanie:
Animal MyAnimal;

W tym momencie okrelamy, i wszelkie wywoania metody Breath() bd odnosi si


wanie do tej klasy. Jest to tzw. wczesne powizanie (ang. early binding).
Jeeli zastosujemy metody wirtualne, kompilator wstrzyma si z decyzj, do jakiej klasy
przypisa dan metod. Ta decyzja zostanie podjta dopiero w trakcie dziaania programu.
Jak widzisz, w naszym programie podejmuje j uytkownik przy pomocy klawiszy 1, 2 lub 3.
Takie rozwizanie nazywamy pnym powizaniem (ang. late binding).
Oczywicie w metodach przedefiniowanych moemy wywoywa metody bazowe przy pomocy
sowa kluczowego base.

Elementy abstrakcyjne
W poprzednim przykadzie zadeklarowaem klas bazow Animal, ktra zawieraa metod
Breath(). Zamy, e w programie nie ma potrzeby uywania klasy Animal jedynie z
klas potomnych. Po co wwczas implementowa metody w klasie Animal? Mona opatrzy
deklaracj metody sowem kluczowym abstract. Klasa abstrakcyjna nie ma implementacji
(tj. definicji metod), ma jedynie nagwki (deklaracje):
abstract class Animal
{
public int Age;

public abstract void Breath();


}

Taka konstrukcja wymusza, aby klasy potomne posiaday metod Breath(). Klasa Animal
nie moe by w takim wypadku uywana, suy jedynie jako fundament do budowania
kolejnych klas.

Klasa zawierajca elementy abstrakcyjne rwnie musi by opatrzona klauzul


abstract.
Metody abstrakcyjne nie posiadaj kodu.
Nie ma moliwoci utworzenia instancji klasy abstrakcyjnej.
Metody abstrakcyjne nie mog by opatrzone klauzul virtual lub static.

Elementy zaplombowane
Elementy zaplombowane nie mog suy jako klasy bazowe. To jest podstawowe i jedyne
zastosowanie tego typu klas. Plombowanie klasy polega na opatrzeniu jej deklaracji sowem
kluczowym sealed:
sealed class Foo {}

Domylnie wszystkie klasy pisane przez nas powinny mie moliwo rozszerzenia jej
funkcjonalnoci
. Uywaj sowa kluczowego sealed tylko wwczas, gdy masz powany powd. Np. klasa jest
statyczna albo dziedziczy elementy zawierajce informacje wane dla bezpieczestwa. Wtedy
pamitaj, aby w zaplombowanej klasie nie umieszcza metod wirtualnych ani nie korzysta z
modyfikatora protected, gdy wtedy nie ma to sensu.

.NET Framework Class Library


Biblioteka klas rodowiska .NET Framework (ang. .NET Framework Class Library FCL)
to rozbudowana biblioteka skadajca si z setek klas, interfejsw, struktur (o tym w dalszej
czci ksiki), ktre umoliwiaj szybkie projektowanie aplikacji. O tej bibliotece
wspominaem ju niejednokrotnie w tej ksice.
Skompilowany kod tej biblioteki podzielony jest na podzespoy (ang. assembly), ktre z kolei
posiadaj przestrzenie nazw, a te skadaj si z klas i innych typw danych. Biblioteka klas
udostpnia wiele mechanizmw umoliwiajcych proste wykorzystanie nawet

skomplikowanych zada, jak np. obsugiwanie plikw XML, pocze internetowych itp.
Pomys udostpniania programistom gotowych bibliotek klas nie jest nowy. Przed tym, jak na
rynku pojawia si platforma .NET, programici mogli korzysta z interfejsu WinAPI systemu
Windows. WinAPI nie jest jednak bibliotek obiektow raczej trudnym do obsugi i
przestarzaym interfejsem. Bardzo popularna bya udostpniona przez firm Microsoft
biblioteka MFC umoliwiajca tworzenie wizualnych aplikacji w rodowisku Visual C++.
Firma Borland lider na rynku aplikacji programistycznych w swoich rodowiskach C++
Builder oraz Delphi umieszczaa bibliotek VCL (ang. Visual Class Library), rwnie
umoliwiajc proste tworzenie aplikacji wizualnych. adna z nich nie bya jednak tak
rozbudowana i uyteczna jak FCL.

Przestrzenie nazw
Wiesz ju, e przestrzenie nazw s podstawowym sposobem organizacji i grupowania klas w
.NET Framework. Biblioteka FCL udostpnia kilka podstawowych przestrzeni nazw
grupujcych klasy:

System podstawowa przestrze nazw caej biblioteki. Zawiera podstawowe klasy


oraz typy danych (np. Int32, Int16, String itp.).
System.Windows.Forms przestrze nazw zawierajca klasy oraz interfejsy suce

do projektowania interfejsu graficznego. Zawiera klasy reprezentujce podstawowe


kontrolki interakcji z uytkownikiem (przyciski, listy rozwijane, panele itp.) oraz
chyba najwaniejsz klas obsugi formularza, czyli System.Windows.Forms.Form.
System.Data przestrze nazw zawierajca klasy obsugi baz danych takie jak MS
SQL Server czy Oracle. Moliwa jest take obsuga technologii OLE DB lub ODBC.
System.XML przestrze zawiera klasy umoliwiajce obsug plikw XML
(parsowanie, tworzenie, usuwanie, edycja). Zagadnienia zwizane z obsug XML-a w
C# omwi w rozdziale 13.
System.IO klasy zawarte w tej przestrzeni nazw su do obsugi operacji wejciawyjcia. Dziki nim mona dodawa do swojej aplikacji obsug plikw, strumieni,
katalogw itp. Tym zagadnieniem zajmiemy si w rozdziale 12.
System.Web to jeden z podstawowych komponentw rodowiska .NET
Framework, czyli ASP.NET. W tej przestrzeni nazw znajduj si klasy suce do
obsugi ASP.NET oraz zawierajce komponenty typu Web Forms.
System.Reflection przestrze nazw zapewniajca obsug mechanizmu
reflection. Nie bd tutaj zagbia si w szczegy, szerzej o technologii reflection
opowiem w rozdziale 11.
System.Net w tej przestrzeni nazw znajduj si klasy odpowiedzialne za obsug
rnych protokow internetowych, takich jak HTTP, DNS czy IP.
System.Security przestrze nazw zawierajca mechanizmy zabezpiecze, klasy
implementujce rne algorytmy szyfrujce.

Klasa System.Object
Bazow klas dla kadego z typw jest System.Object. Nawet jeeli nie okrelimy klasy
bazowej naszej klasy, to kompilator automatycznie przyjmie, e jest ni System.Object.
Owa klasa dostarcza podstawowych mechanizmw korzystania z klas podstawowe metody
zostay opisane w tabeli 5.1.
Tabela 5.1. Krtki opis metod uywanych w klasie System.Object
Metoda
Opis
Porwnuje, czy dwie instancje typu object s takie same (maj tak sam
Equals
zawarto).
ReferenceEquals Porwnuje, czy dwie instancje obiektu s tego samego typu.
GetHashCode
Zwraca unikalny numer instancji obiektu.
GetType
Zwraca informacje na temat obiektu: metody, waciwoci itp.
ToString
Znakowa reprezentacja obiektu zwraca jego typ.

Listing 5.4 prezentuje przykad wykorzystania metod z klasy System.Object.


Listing 5.4. Przykad wykorzystania metod klasy System.Object
using System;
namespace FooConsole
{
class Foo
{
public Foo()
{
// pusto
}
}
class Bar
{
public Bar()
{
Foo MyFoo = new Foo();
Console.WriteLine("GetHashCode() " +
this.GetHashCode());
Console.WriteLine("GetType() " + this.GetType());
Console.WriteLine("ToString() " +
this.ToString());
Console.WriteLine("Equals() " +
this.Equals(MyFoo));
Console.WriteLine("ReferenceEquals() " +
object.ReferenceEquals(this, MyFoo));

}
}
class Program
{
static void Main(string[] args)
{
Bar MyBar = new Bar();
Console.Read();
}
}
}

Skompiluj i uruchom tak napisany program i sprawd jego dziaanie.

Opakowywanie typw
Opakowywanie typw jest specyficzn cech jzykw dziaajcych w rodowisku .NET
Framework. Opakowywanie daje moliwo traktowania typw prostych jak obiektw.
Tutaj nale Ci si wyjanienia. W C# moemy wyrni dwa rodzaje typw proste oraz
referencyjne. Wbudowane typy s prostymi typami przechowywanymi na stosie. Mona do
nich zaliczy liczby cakowite, liczby zmiennoprzecinkowe podwjnej precyzji oraz wartoci
logiczne.
Stos jest tym obszarem pamici komputera, ktry jest alokowany (przydzielany) w momencie
uruchamiania jakiego programu. System operacyjny w tym momencie musi okreli, ile
pamici bdzie potrzebowa do prawidowego dziaania programu.
Typy referencyjne s umieszczane na tzw. stercie (ang. heap). Zmienna typu referencyjnego
przechowuje jedynie adres wskazujcy na rzeczywisty egzemplarz danego typu. Kiedy
tworzymy now instancj danego typu (przy pomocy operatora new), nastpuje alokacja (czyli
rezerwacja) pamici potrzebnej na przechowywanie danego obiektu.
Sterta jest obszarem pamici operacyjnej dostpnej w trakcie dziaania programu. Kiedy
tworzymy nowy obiekt, pami dla niego jest alokowana wanie na stercie.
Konwersja ze zmiennej typu prostego na posta obiektu i konwersja obiektu na zmienn typu
prostego w terminologii przyjtej przez programistw aplikacji .NET jest okrelana
odpowiednio jako opakowywanie (ang. boxing) oraz odpakowywanie (ang. unboxing).
Poniszy przykad prezentuje domniemane opakowywanie:
int Foo = 100;
object O = Foo;

Console.WriteLine(O);

Jak widzisz, polega to jedynie na przypisaniu wartoci zmiennej Foo do zmiennej O typu
object (typ object jest odpowiednikiem typu System.Object biblioteki FCL). Nie ma
potrzeby okrelania, e opakowujemy warto typu int, poniewa kompilator jest w stanie
si tego domyli. Moliwe jest jednak sprecyzowane opakowywanie polegajce na
podaniu typu danych:
int Foo = 100;
object O = (object)Foo;

Odwrotna sytuacja, czyli odpakowywanie, jest rwnie proste:


int Bar = (int)O;

Interfejsy
Interfejsy swoim dziaaniem przypominaj klasy abstrakcyjne. Mog zawiera jedynie
deklaracje metod oraz waciwoci. Interfejsy deklaruje si z uyciem sowa kluczowego
interface:
interface IFoo
{
int Power(int X, int Y);
}

Jak widzisz, w interfejsie o nazwie IFoo znajduje si metoda Power(). W takim wypadku
mwimy, e interfejs definiuje dane metody. Zwr uwag, e w interfejsie znajduje si
nagwek metody pozbawiony ciaa (czyli kodu).
Interfejsy s kolejnym elementem uywanym w metodologii zwanej programowaniem
obiektowym, czsto wykorzystywanym w bibliotece FCL. Klasy jzyka C# mog dziedziczy
jedynie elementy pojedynczej klasy. Jak sama nazwa wskazuje, interfejs definiuje wycznie
mechanizm poredniczcy w komunikacji klienta z obiektem. Za obsug interfejsu i
odpowiedni implementacj kadej z jego funkcji odpowiada klasa.
Dana klasa moe jednake dziedziczy wiele interfejsw. Poniewa interfejs nie moe
posiada kodu metod, mwimy wwczas, i klasa implementuje elementy interfejsu. Pewnie
zastanawiasz si jakie zalety niesie za sob wykorzystanie interfejsw. Su one przede
wszystkim do wymuszania deklaracji danych elementw w klasie. Spjrz na poniszy kod:

interface IFoo
{
int Power(int X, int Y);
}
class Foo : IFoo
{
}

Mamy tutaj interfejs IFoo oraz klas Foo, ktra go implementuje. Prba kompilacji takiego
programu zakoczy si komunikatem: 'FooConsole.Foo' does not implement interface
member 'FooConsole.IFoo.Power(int, int)'. Kompilator prbuje nam powiedzie, e w klasie
Foo nie znajduje implementacji elementu Power.
Implementowana metoda musi posiada taki sam nagwek jak ta zdefiniowana w interfejsie.
Nie moe to by metoda statyczna:
class Foo : IFoo
{
public int Power(int X, int Y)
{
return X * Y;
}
}

Interfejsy nie mog definiowa pl.


Dobr praktyk jest specyficzne nazewnictwo interfejsw polegajce na dodaniu przed nazw
litery I.
Interfejsy s czsto wykorzystywane w bibliotece klas FCL. Przykadowo, klasa
System.String (o ktrej szczegowo bdzie mowa w rozdziale 9.) implementuje wiele
interfejsw, w tym IConvertible. Ten z kolei definiuje metody uywane przy konwersji
danych.

Implementacja wielu interfejsw


Jak wspomniaem, dana klasa moe implementowa wiele interfejsw. W takim przypadku
ich nazwy naley rozdzieli znakiem przecinka:
interface IFoo
{
int Power(int X, int Y);
}

interface IBar
{
double Power(double X, double Y);
}
class Foo : IFoo, IBar
{
public int Power(int X, int Y)
{
return X * Y;
}
public double Power(double X, double Y)
{
return X * Y;
}
}

Istnieje moliwo, aby obydwa interfejsy definioway metod o tej samej nazwie. Wwczas
inna jest implementacja takich metod:
interface IFoo
{
void Foo();
}
interface IBar
{
void Foo();
}
class Foo : IFoo, IBar
{
void IFoo.Foo()
{
Console.WriteLine("Metoda implementujca IFoo.Foo");
}
void IBar.Foo()
{
Console.WriteLine("Metoda implementujca IBar.Foo");
}
}

Zwr uwag na brak modyfikatorw dostpu takich implementowanych metod. Takie


rozwizania umoliwia dostp do metody IFoo.Foo() tylko poprzez interfejs IFoo:

Foo obj = new Foo();


IFoo MyFoo = (IFoo)obj;
MyFoo.Foo();

Podejrzewam jednak, e takiego rozwizania (podobnie jak i caego mechanizmu interfejsw)


nie bdziesz czsto stosowa w swoich projektach.
Podsumowujc:

Interfejsy definiuj metody oraz waciwoci.


Interfejsy nie mog zawiera pl.
Interfejsy mog dziedziczy po sobie podobnie jak klasy.
Wszystkie elementy interfejsu s domylnie traktowane jako publiczne.
Interfejs nie moe zawiera elementw statycznych.

Typy wyliczeniowe
Umoliwiaj one tworzenie nowych typw danych zawierajcych zbir elementw tego
samego typu. Jeeli programowae wczeniej w jzyku Delphi, by moe znane s Ci zbiory
(ang. set). Typy wyliczeniowe (nazywane czsto po prostu wyliczeniami) s podobnym
mechanizmem jak zbiory z jzyka Delphi.
Typy wyliczeniowe deklarujemy z uyciem sowa kluczowego enum:
enum Days { Pn, Wt, r, Czw, Pt, So, Nd };

W nawiasach klamrowych musimy zapisa list elementw, ktre bd znajdowa si w


naszym zbiorze. List elementw naley oddzieli znakiem przecinka.
Nie ma znaczenia, w jaki sposb zapisana jest deklaracja typu wyliczeniowego. Wielu
programistw, aby zwikszy czytelno, zapisuje elementy w ten sposb:
enum Days
{
Pn,
Wt,
r,
Czw,
Pt,
So,

Nd
};

Uycie typw wyliczeniowych nie wymaga od programisty utworzenia nowej instancji


takiego typu czy deklarowania zmiennej wskazujcej na nowy typ. Odwoanie do
konkretnego elementu moliwe jest przy uyciu operatora odwoania:
Console.WriteLine(Days.Pn);

Taki kod zwyczajnie wywietli na konsoli napis Pn.

Wartoci elementw
Typy wyliczeniowe s prostym mechanizmem zapewniajcym grupowanie danych. Ilo
elementw wyliczenia jest staa, nie podlega zmianom. Jedyna rzecz, ktr moemy ustali w
trakcie projektowania aplikacji, to ich warto. Ot domylnie pierwszy element wyliczenia
ma warto rwn 0, drugi 1 itd.
Moemy to prosto sprawdzi, dokonujc rzutowania:
Console.WriteLine((int)Days.Pn);

Moliwe jest jawne nadanie numeru dla elementu:


enum Days
{
Pn = 10,
Wt,
r,
Czw = 50,
Pt,
So,
Nd
};

Teraz element Pn ma warto 10, Wt 11 itd. Element Czw posiada warto rwn 50, Pt 51.
Pamitaj, e na elementach wyliczenia nie moesz dokonywa adnych dziaa
arytmetycznych (dodawanie, odejmowanie). Najpierw konieczne jest rzutowanie na typ
cakowity.

Jeeli zakres dostpny dla typu int nie wystarcza, aby nada warto dla elementu wyliczenia,
mona jawnie okreli typ:
enum Foo : long { Min = -1200034676867853, Max = 564555666 };

Pamitaj o tym, aby dokona waciwego rzutowania, jeeli chcesz odczyta warto
elementw wyliczenia:
Console.WriteLine("Min: " + (long)Foo.Min);
Console.WriteLine("Max: " + (long)Foo.Max);

Struktury
Rwnie struktury odgrywaj wan rol w rodowisku .NET Framework. Zawsze lubiem
myle o strukturach jako uproszczonych klasach, gdy s one pozbawione niektrych
zaawansowanych cech, takich jak dziedziczenie czy polimorfizm.
Struktury s zorganizowanym zbiorem danych, niekoniecznie tego samego typu. Podobnie jak
klasy mog posiada pola, metody a nawet konstruktor.
Struktury deklarujemy z uyciem sowa kluczowego struct:
public struct Contact
{
string Name;
long Phone;
byte Age;
}

W tym momencie zadeklarowaem struktur skadajc si z pl: Name (nazwa), Phone


(telefon) oraz Age (wiek), do ktrych mog przypisywa dane kontaktowe.
No dobrze, ale na razie nie ma rnicy pomidzy utworzeniem klasy o nazwie Contact, a
struktur zawierajc te same pola. Masz racj. Struktury s bardzo podobne do klas, ich
tworzenie oraz wykorzystanie jest praktycznie identyczne.
Wyobra sobie, e piszesz aplikacj suc do przechowywania informacji kontaktowych.
Moe to by ksika adresowa. Zamy rwnie, e posiadasz w ksice 100 kontaktw, co
jest bardzo prawdopodobne. Do przechowywania kontaktw wykorzystujesz klasy. Aby
odczyta dane kontaktowe i przypisa je do klasy, musisz utworzy 100 instancji klasy!
Wpynie to na wydajno programu, nie mwic ju o zuyciu pamici.

Uycie struktur nie wymaga tworzenia obiektu, a jedynie zmiennej wskazujcej na struktur:
Contact c1, c2;
c1.Name = "Jan Kowalski";
c1.Age = 23;
c1.Phone = "601-000-111";
c2.Name = "Piotr Nowak";
c2.Age = 43;
c2.Phone = "501-111-222";

Jak widzisz, w kodzie zadeklarowaem dwie zmienne wskazujce na struktur, po czym


wypeniem je danymi. Nie byo koniecznie uycie przy tym operatora new.
W rzeczywistoci programista, ktry musi przechowa w pamici dane kontaktowe 100 osb,
nie bdzie deklarowa setki zmiennych wskazujcych na struktur Contact. Jzyki
programowania oferuj inne narzdzia, takie jak np. tablice, o ktrych bdzie mowa w
rozdziale 7.
Listing 5.5. prezentuje prosty program umoliwiajcy tworzenie oraz wykorzystanie struktur.
Listing 5.5. Program wykorzystujcy struktury
using System;
namespace FooApp
{
public struct Contact
{
public string Name;
public string Phone;
public byte Age;
}
class Program
{
static void ShowInfo(Contact c)
{
Console.WriteLine("Witaj " + c.Name + ", wiem e
masz " +
c.Age + " lat, a Twj numer telefonu to: " +
c.Phone);
}
static void Main(string[] args)
{
Contact c1, c2;

c1.Name = "Jan Kowalski";


c1.Age = 23;
c1.Phone = "601-000-111";
c2.Name = "Piotr Nowak";
c2.Age = 43;
c2.Phone = "501-111-222";
ShowInfo(c2);
Console.Read();
}
}
}

Najwaniejsze w tym banalnym programie jest to, e struktur moemy przekaza jako
parametr metody, tak jak to uczyniem, deklarujc metod ShowInfo(). Wyobra sobie
sytuacj, w ktrej metoda miaaby tyle parametrw, ile elementw ma struktura Contact:
ShowInfo(string Name, string Phone, byte Age);

Struktura Contact posiada jedynie 3 parametry, ale nie trudno sobie wyobrazi sytuacj, w
ktrej ta liczba jest wiksza. Taki kod nie wygldaby zbyt efektownie, dlatego wiele
parametrw mona zastpi jednym, ktry wskazuje na struktur.

Konstruktory struktur
Sposb deklarowania konstruktorw w strukturach jest do ciekawy, dlatego warto o nim
wspomnie. Pierwsza uwaga: w strukturach nie mona deklarowa konstruktorw, ktre nie
posiadaj adnych parametrw!
Zadeklarowany konstruktor musi suy do przypisywania danych polom struktury:
public struct Contact
{
public string Name;
public string Phone;
public byte Age;
public Contact(string FName, string FPhone, byte FAge)
{
Name = FName;
Phone = FPhone;
Age = FAge;

}
}

Z ciekawoci moesz usun kod z ciaa konstruktora. Prba kompilacji nie powiedzie si,
kompilator zasygnalizuje bd: Field 'FooApp.Contact.Age' must be fully assigned before
control leaves the constructor.
Konstruktor struktury jest wywoywany przy pomocy operatora new, podobnie jak w
przypadku klas. Listing 5.6 zawiera zmodyfikowany kod z listingu 5.5 i prezentuje sposb
przypisania danych do struktury za pomoc konstruktora.
Listing 5.6. Przykad wykorzystania struktur
using System;
namespace FooApp
{
public struct Contact
{
public string Name;
public string Phone;
public byte Age;
public Contact(string FName, string FPhone, byte FAge)
{
Name = FName;
Phone = FPhone;
Age = FAge;
}
}
class Program
{
static void ShowInfo(Contact c)
{
Console.WriteLine("Witaj " + c.Name + ", wiem e
masz " +
c.Age + " lat, a Twj numer telefonu to: " +
c.Phone);
}
static void Main(string[] args)
{
Contact c1, c2, c3;
c1.Age = 23;
c1.Phone = "601-000-111";
c2.Name = "Piotr Nowak";
c2.Age = 43;

c2.Phone = "501-111-222";
c3 = new Contact("Janusz Piotrkowski", "654-435345", 12);
ShowInfo(c3);
Console.Read();
}
}
}

Podsumujmy informacje o strukturach:

Struktury, podobnie jak klasy, mog zawiera pola, metody i waciwoci.


Struktury mog implementowa interfejsy.
Struktury nie mog dziedziczy z innych klas, aczkolwiek dziedzicz metody z klasy
bazowej System.Object.
Struktury nie mog posiada metod wirtualnych.
Struktury nie mog posiada elementw statycznych.
Utworzenie nowej instancji struktury nie wymaga uycia operatora new.

Operatory is i as
Istniej dwa operatory, is i as, ktre s stosowane w poczeniu z klasami. Programici
rzadko z nich korzystaj, jednak warto powici im nieco uwagi.
Zacznijmy od prostego przykadu. Wyobra sobie, e mamy kilka komponentw tego samego
typu np. TextBox. Chcemy oprogramowa zdarzenie KeyPress kadego z nich.
Oczywicie, aby uatwi sobie prac, generujemy zdarzenie jednego komponentu, a
pozostaym przypisujemy t sam procedur zdarzeniow.
Zwr uwag, e wygenerowana procedura zdarzeniowa posiada dwa parametry:
private void textBox1_KeyPress(object sender,
KeyPressEventArgs e)
{
}

Wanym parametrem jest sender, ktry wskazuje na typ object. Parametr ten umoliwia

programicie kontrol oraz daje informacj, z jakiego komponentu pochodzi zdarzenie. Teraz
przy pomocy operatora is moemy sprawdzi typ komponentu, z ktrego pochodzi zdarzenie:
if (sender is TextBox) { }

W przypadku gdy zdarzenie pochodzi z komponentu typu TextBox (wskazuje na to parametr


sender), instrukcja if zostanie speniona. Operator is dziaa podobnie jak porwnanie za
pomoc ==. Niekiedy jednak nie mona uy operatora ==:
if (sender == TextBox) { }

Powyszy kod spowoduje wywietlenie komunikatu o bdzie:


'System.Windows.Forms.TextBox' is a 'type' but is used like a 'variable'.
Operator as natomiast suy do tzw. konwersji. Nie chodzi tutaj o konwersj typw, ktr
omawiaem poprzednio.
Powrmy do przykadu. Umieciem na formularzu trzy komponenty typu TextBox i
oprogramowaem zdarzenie KeyPress, ktre przypisaem kadej kontrolce. Chciabym
zmieni jak waciwo jednego komponentu typu TextBox, a to jest moliwe dziki
operatorowi as:
private void textBox1_KeyPress(object sender,
KeyPressEventArgs e)
{
if (sender is TextBox)
{
(sender as TextBox).Text = " ";
}
richTextBox1.Text += "Nacinito klawisz " + e.KeyChar +
", a zdarzenie pochodzi z kontrolki " + (sender as
TextBox).Name + "\n";
}

Po uruchomieniu programu i naciniciu klawisza w momencie, gdy komponent TextBox jest


aktywny, zostanie wywoane zdarzenie KeyPress, co spowoduje czyszczenie waciwoci
Text tego komponentu.
Jak wida, dziki takiemu zabiegowi moliwa jest zmiana waciwoci takiego komponentu,
nawet jeli nie znamy jego nazwy, a jedynie typ (rysunek 5.5).

Rysunek 5.5. Przykad wykorzystania operatorw is oraz

Przeadowanie operatorw
Mechanizm przeadowania operatorw (lub inaczej przeciania operatorw) by znany
ju wczeniej, m.in. programistom C++, teraz zosta wprowadzony rwnie na platformie
.NET.
Oglnie mona powiedzie, e dziki przeadowaniu operatorw mona dokonywa rnych
dziaa na obiektach klas (mnoenie, dodawanie, dzielenie itp.) tak samo jak na zmiennych,
tym samym upraszczajc nieco zapis kodu. Wyglda to tak: stosujc operator (np. +) w
obiektach klas, w rzeczywistoci wywoujemy odpowiedni metod klasy. Od projektanta
zaley, jaki kod bdzie miaa owa funkcja. Moe to wyglda np. tak:
Foo MyFoo = new Foo();
Foo MyBar = new Foo();
MyFoo = 10;
if (MyFoo != MyBar)
{
}

Na pocztku ten mechanizm moe wydawa si troch dziwny, lecz osoby majce wczeniej
styczno np. z C++ nie powinny mie problemu z jego zrozumieniem.

Sowo kluczowe operator


Przeadowanie operatora polega na utworzeniu w klasie metody, okrelenie jej sowem
kluczowym operator oraz symbolem operatora, ktry ma zosta przeadowany. Dla
zobrazowania tego problemu przeadujmy operator ++ w przykadowej klasie Foo:
class Foo
{
private int X;
public Foo(int X)
{
this.X = X;
}
public int GetValue
{
get
{
return (this.X);
}
}
public static Foo operator ++(Foo FooObj)
{
++FooObj.X;
return FooObj;
}
}

Klasa jest banalna i waciwie nie spenia adnego okrelonego dziaania. Liczba podana w
parametrze klasy zostaje przypisana do pola X. Nie to jest jednak wane. Najwaniejsza dla
nas jest statyczna metoda na samym kocu tego kodu. Nie posiada nazwy, a jej skadnia jest
do specyficzna:
operator ()
{
}
Oto przykad wykorzystania klasy:
Foo MyFoo = new Foo(5);

// inkrementacja
MyFoo++;
Console.WriteLine(MyFoo.GetValue); // rezultat: 6

W kodzie inkrementujemy warto zmiennej MyFoo. W rzeczywistoci wywoywana zostaje


wwczas odpowiednia metoda, ktra obsuguje przeadowanie tego operatora. Kod metody
moe by dowolny np.:
public static Foo operator ++(Foo FooObj)
{
FooObj.X += 2;
return FooObj;
}

W parametrze tej metody przekazywana jest instancja klasy Foo. Do wartoci pola X tej
instancji dodajemy cyfr 2. Nastpnie zwracamy instancj. Poniewa tematyka przeadowania
operatorw moe nie by zbyt klarowna na samym pocztku, rozbudujmy nasz program i
przeadujmy kolejny operator +. Oto rozwizanie:
public static Foo operator +(Foo Obj1, Foo Obj2)
{
return new Foo(Obj1.X + Obj2.X);
}

Jak widzisz, tutaj wymagane s dwa parametry, gdy dodajemy do sobie wartoci dwch
obiektw:
MyFoo = Foo1 + Foo2;

Zakadajc, e wszystkie obiekty s typu Foo, musimy obsuy proces przypisywania danych
do obiektu:
public static implicit operator Foo(int Obj1)
{
return new Foo(Obj1);
}

Jak widzisz, konstrukcja obsugi procesu przypisania rni si nieco od przedstawionych


poprzednio. Oto kod wykorzystujcy przeadowane operatory:

Foo MyFoo, Foo1, Foo2 = new Foo(5);


Foo1 = 10;
MyFoo = Foo1 + Foo2;
Console.WriteLine(MyFoo.GetValue); // rezultat: 15

W jzyku C# nie mona przeadowa operatorw &&, || i new.


Uycie sowa kluczowego implict pozwala unikn niepotrzebnego rzutowania typw.
Przykad:
public static implicit operator int(Foo Obj1)
{
return Obj1.X;
}
// ...
Foo MyFoo = new Foo(10);
int i = 20;
i = MyFoo;

W tym przykadzie do zmiennej MyFoo przypisalimy warto zmiennej i. Przeciwiestwem


sowa kluczowego implict jest explict:
public static explicit operator int(Foo Obj1)

Po tej kosmetycznej poprawce kod przedstawiony powyej nie ma prawa si skompilowa.


Wywietlony zostanie bd: Cannot implicitly convert type 'FooApp.Foo' to 'int'. An explicit
conversion exists (are you missing a cast?). Trzeba wwczas uy rzutowania:
i = (int)MyFoo;

Dzielenie klas
Gdy na pocztku rozdziau prezentowaem kod rdowy formularza, ktry generowany jest
automatycznie przez rodowisko Visual C# Express Edition, moge zauway, e stosowane
jest tam sowo kluczowe partial. W przypadku duych projektw moesz podzieli kod
klasy na kilka rnych moduw. Wwczas konieczne jest uycie sowa kluczowego
partial:

partial class Foo


{
public Foo()
{
}
}
partial class Foo
{
public void Bar()
{
}
}

Taki kod w trakcie kompilacji jest czony w jedn klas. Takie rozwizanie jest stosowne w
projektach typu Windows Forms. Np. kod, ktry odpowiada za tworzenie formularza oraz
komponentw, umieszczony jest w osobnym module *.designer.cs, co znacznie uatwia prac
nad projektem.

Podsumowanie
To by bardzo wany okres nauki jzyka C#. Zagadnienia zwizane z podstawami
programowania obiektowego s bardzo wane, a to dlatego, e jzyk C# jest w peni
obiektowy. Jednoczenie zdaj sobie spraw, e tematyka programowania obiektowego jest
trudna do zrozumienia dla pocztkujcych programistw i musi min troch czasu, aby
zostaa w peni zrozumiana.

Rozdzia 6
Delegaty i zdarzenia
W poprzednim rozdziale powiedzielimy sobie sporo o programowaniu obiektowym. Jest to
temat bardzo rozlegy, szczeglnie w kontekcie programowania w jzyku C#, ktry jest
cakowicie obiektowy. Nim przejdziemy do dalszego omawiania jzyka C#, chciabym
wspomnie o dwch mechanizmach, ktrych wyjanienie musi znale si w tej ksice,
mimo i s one nieco bardziej zaawansowane. Mowa bdzie o delegatach oraz zdarzeniach.
Na tym etapie nauki zapewne nie bdziesz mia okazji lub potrzeby uywania mechanizmw,
o ktrych traktuje ten rozdzia. Jest to jednak rzecz warta uwagi i dlatego postanowiem
przeznaczy kilka najbliszych stron na jej omwienie. By moe gdy osigniesz wyszy

poziom umiejtnoci programowania, dostrzeesz zalety pynce z uywania delegatw.


Wwczas bdziesz mg powrci do tego rozdziau, aby dowiedzie si czego wicej na
temat tych technik
.

Delegaty
Delegaty s czsto porwnywane do wskanikw funkcji z jzyka C++. Faktem jest, e C#
czerpie wiele z jzyka C++ i Javy, jednak wiele rzeczy jest moim zdaniem ulepszonych i
prostszych w uyciu, tak jak delegaty.
Pamitasz, jak opisywaem zjawisko polimorfizmu? Pisaem wwczas o pnym powizaniu,
o tym, i decyzja, jaki obiekt ma zosta utworzony, moe zapa w trakcie dziaania
programu. Teraz wyobra sobie sytuacj, w ktrej nie jeste pewien, jaka metoda ma zosta
wykonana, decyzja ta zostanie podjta w trakcie dziaania aplikacji, np. w zalenoci od
decyzji uytkownika.
Np. uyty algorytm zostanie wybrany przez program w trakcie dziaania na podstawie
zanalizowanych danych. Owszem moesz wykorzysta instrukcj if lub switch, ale przy
bardziej rozbudowanych aplikacjach praktyczniejsze bdzie uycie delegatw.
Mona powiedzie, e delegaty przechowuj referencj (adres w pamici) do danych metod.
Korzystajc z delegatw, moesz wykona kod znajdujcy si w metodach.

Tworzenie delegatw
Delegaty to w rzeczywistoci nowe typy danych. Mog by zadeklarowane w klasie jako typ
zagniedony lub poza ni. Oto przykad:
public delegate int Foo(int X);

Tworzenie nowych delegatw jest proste, wyglda jak deklarowanie nowej metody. Jedyn
rnic jest swko kluczowe delegate.
Delegaty s nowymi typami danych. Aby je wykorzysta, tworzymy wic nowe zmienne
wskazujce na te typy, do ktrych moemy przypisa metody odpowiadajce sygnaturze
delegatu.
Delegaty nie posiadaj implementacji, jedynie sygnatur (nagwek) zakoczon znakiem
rednika.
Sygnatur okrelaj parametry delegatu oraz typ zwrotny.
To, e w poprzednim zdaniu napisaem o przypisywaniu metody do zmiennej, nie byo

pomyk, o czym za chwil si przekonasz. Poniewa do zmiennej delegatu mona przypisa


jedynie metod, ktra posiada taki sam nagwek, naley w metody wczeniej utworzy:
static int Power(int X)
{
return X * X;
}
static int Div(int X)
{
return X / 2;
}

Jak widzisz, powysze metody posiadaj jeden parametr typu int oraz zwracaj warto
rwnie w postaci typu int, czyli identycznie jak nasz delegat Foo.
Delegaty mog zosta zainicjowane, zanim zostan uyte, tak jak to ma miejsce w przypadku
zwykych klas. Oznacza to, e naley utworzy obiekt delegatu i jako parametr poda w nim
nazw metody, na ktr ma wskazywa:
Foo MyFoo = new Foo(Power);

Od teraz uycie MyFoo() jest rwnoznaczne z wywoaniem metody Power() (listing 6.1).
Istnieje rwnie moliwo utworzenia zmiennej wskazujcej na delegat i przypisanie do niej
wartoci:
Foo MyFoo = Power;

Listing 6.1. Przykad uycia delegatw


using System;
namespace DelegateApp
{
public delegate int Foo(int X);
class Program
{
static int Power(int X)
{
return X * X;
}
static int Div(int X)
{
return X / 2;

}
static void Main(string[] args)
{
Foo MyFoo = new Foo(Power);
Console.WriteLine("10x10 = " + MyFoo(10));
MyFoo = Div;
Console.WriteLine("10/2
Console.Read();

= " + MyFoo(10));

}
}
}

Zwr uwag na przypisanie:


MyFoo = Div;

Obiektowi MyFoo przypisaem metod Div(). Oznacza to, i od tej chwili uycie MyFoo()
rwna si bedzie uyciu metody Div().

Uycie delegatw
Jak wspomniaem, przy pomocy delegatw moemy decydowa w trakcie dziaania
programu, jaka metoda zostanie wykonana. Rysunek 6.1 prezentuje prosty program
wykorzystujcy mechanizm delegatw.

Rysunek 6.1. Program prezentujcy dziaanie delegatw


W tej prostej aplikacji uytkownik decyduje, jakie dziaanie ma zosta wykonane (mnoenie

czy dzielenie). Dane wpisane w kontrolkach TextBox s przekazywane do obiektu delegatu,


ktry wykonuje odpowiednie dziaanie (listing 6.2).
Listing 6.2. Prezentacja dziaania delegatw
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace DelegateApp
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private int Multiple(int X, int Y)
{
return X * Y;
}
private int Div(int X, int Y)
{
return X / Y;
}
private void btnDoIt_Click(object sender, EventArgs e)
{
Math MyMath;
if (radioMultiple.Checked)
{
MyMath = new Math(Multiple);
}
else
{
MyMath = new Math(Div);
}
lblResult.Text = "Suma: " +
Convert.ToString(MyMath(Int32.Parse(textX.Text),
Int32.Parse(textY.Text)));
lblResult.Visible = true;

}
}
public delegate int Math(int X, int Y);
}

Owszem, kto moe powiedzie, e to samo zadanie mona zrealizowa, uywajc instrukcji
warunkowej:
Math MyMath;
int Result;
if (radioMultiple.Checked)
{
Result = Multiple(Int32.Parse(textX.Text),
Int32.Parse(textY.Text));
}
else
{
Result = Div(Int32.Parse(textX.Text),
Int32.Parse(textY.Text));
}
lblResult.Text = "Suma: " + Convert.ToString(Result);

Zgadza si, taki kod rwnie jest poprawny, ale moim zdaniem przejrzystszy jest jednak ten z
uyciem delegatw.
OK, zadanie z listingu 6.2 rzeczywicie mona zrealizowa przy pomocy instrukcji
warunkowej, wywoujc po prostu odpowiedni metod. Delegaty maj jednak wiksze
zastosowanie, o czym przekonasz si ju za chwil.

Funkcje zwrotne
Kolejnym zastosowaniem delegatw s funkcje zwrotne. Parametr danej metody moe
wskazywa na delegat. Spjrz na poniszy przykad:
static void Info(int X, int Y, Foo MyFoo)
{
MyFoo("Rezultat: " + (X + Y));
}

Konstrukcja jest do dziwna, wywoujemy bowiem metod MyFoo(), ktra w rzeczywistoci


wskazuje na delegat:
public delegate void Foo(string Message);

W rzeczywistoci wic wywoujemy metod, do ktrej odnosi si delegat. Aby lepiej to


zrozumie, spjrz na listing 6.3, gdzie umieciem cay kod programu prezentujcy
zastosowanie funkcji
zwrotnych.
Listing 6.3. Program prezentujcy zastosowanie funkcji zwrotnych
using System;
using System.IO;
namespace DelegateApp
{
public delegate void Foo(string Message);
class Program
{
private static readonly bool Write = false;
static void OutputToConsole(string Message)
{
Console.WriteLine(Message);
}
static void OutputToFile(string Message)
{
// zapisywanie do pliku
}
static void Info(int X, int Y, Foo MyFoo)
{
MyFoo("Rezultat: " + (X + Y));
}
static void Main(string[] args)
{
Foo MyFoo;
if (Write)
{
MyFoo = OutputToFile;
}
else
{
MyFoo = OutputToConsole;
}

Info(2, 3, MyFoo);
Console.Read();
}
}
}

W metodzie Main() zadeklarowaem zmienn wskazujc na delegat, do ktrej przypisaem


metod, w zalenoci od wyniku instrukcji warunkowej. Nastpnie wywoywana zostaje
metoda Info(), w ktrej w jednym z parametrw jest zmienna wskazujca na delegat.

Delegaty zoone
Bardzo fajn moliwoci jest przypisywanie do delegatu wicej ni jednej metody. Wwczas
przy wywoaniu delegatu wywoane zostan wszystkie przypisane do niego metody.
Przypisywanie dodatkowych metod do delegatu jest bardzo proste. Mona do tego
wykorzysta operatory
+= oraz -=. Zmodyfikujmy nieco program z listingu 6.3:
Foo MyFoo;
MyFoo = OutputToConsole;
MyFoo += OutputToFile;
MyFoo("Hello World");
MyFoo -= OutputToFile;
MyFoo("Hello my darling!");

Moesz sprbowa zmodyfikowa program w ten sposb i sprawdzi jego dziaanie.

Metody anonimowe
Deklaracja delegatu to nie wszystko. Musimy rwnie przypisa odpowiedni metod, ktra
ma by wywoywana po uyciu delegatu. Uycie metod anonimowych pozwala na
przyspieszenie procesu projektowania poprzez pominicie nazwy metody.
Posumy si poprzednim przykadem. Spjrz na poniszy kod:

Foo MyFoo = delegate(string M)


{
Console.WriteLine(M);
};
MyFoo("Hello World");

Do zmiennej MyFoo (typu Foo) przypisujemy kod, ktry bdzie wykonywany w momencie
napotkania instrukcji MyFoo. Tworzenie anonimowych metod przypomina tworzenie
zwykych. Rnic jest to, e tutaj pomijana jest nazwa metody, a do jej tworzenia uywane
jest sowo kluczowe delegate.
Zwr uwag na konieczno uycia znaku rednika po klamrze zamykajcej kod metody
anonimowej.

Zdarzenia
O zdarzeniach wspominaem ju w poprzednim rozdziale. Myl, e po dotychczasowej
lekturze masz pojcie o znaczeniu i zastosowaniu zdarze. Powiniene rwnie umie
wykorzysta zdarzenia, uywajc rodowiska
Visual C# Express Edition. Chciabym na chwil zatrzyma si przy zdarzeniach i opisa, jak
wyglda ich dziaanie i wykorzystanie wewntrz kodu.
Jak wiesz, zdarzenia uywane s do oprogramowania czynnoci zachodzcych w trakcie
dziaania aplikacji. Takim zdarzeniem moe by ruch mysz, kliknicie w obrbie
komponentu, przesunicie kontrolki i wiele innych.
Utwrz nowy projekt aplikacji Windows Forms. Po zaadowaniu niezbdnych elementw
przez rodowisko Visual C# Express Edition na formularzu umie przykadowy komponent
np. Button. Kliknij na niego podwjnie, aby wygenerowa kod:
private void button1_Click(object sender, EventArgs e)
{
}

Nie zastanawia Ci, jak to si dzieje, e po naciniciu przycisku wykonywany jest kod
znajdujcy si w tej metodzie? Zdarzenia w duej mierze opieraj si na delegatach. W pliku
*.designer.cs moesz odnale instrukcje, ktre odpowiadaj za utworzenie przycisku Button
na formularzu oraz za przypisanie okrelonej metody do danego zdarzenia:
this.button1.Click += new
System.EventHandler(this.button1_Click);

Zdarzenie Click zadeklarowane jest w klasie Control (znajdujcej si w przestrzeni nazw


System.Windows.Forms) w sposb nastpujcy:
public event EventHandler Click;

Delegaty deklarujemy z uyciem sowa kluczowego delegate, a zdarzenia z uyciem


sowa event. Fraza EventHandler nie naley do sw kluczowych jzyka C#. Jest to nazwa
delegatu okrelonego w przestrzeni System:
public delegate void EventHandler(object sender, EventArgs
e);

Innymi sowy, procedury zdarzeniowe (metody) przypisywane do zdarzenia Click musz


mie sygnatur zgodn z delegatem EventHandler. Pierwszym parametrem sygnatury
delegatu jest parametr typu object (mwilimy o tym w rozdziale 5.), a drugim typu
EventArgs. Klasa EventArgs nie robi nic konkretnego, waciwie jest jedynie klas bazow
dla innych, ktre przechowuj informacje o zdarzeniu.
Skoro powiedzielimy, e zdarzenia w duej mierze opieraj si na delegatach, nic nie stoi na
przeszkodzie, aby przypisa do danego zdarzenia wicej ni jedn metod:
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Procedura zdarzeniowa #1");
}
private void myButton_Click(object sender, EventArgs e)
{
MessageBox.Show("Procedura zdarzeniowa #2");
}
private void Form1_Load(object sender, EventArgs e)
{
button1.Click += button1_Click;
button1.Click += myButton_Click;
}

Metoda Form1_Load() jest w rzeczywistoci procedur zdarzeniow dla zdarzenia Load(),


ktre jest wywoywane w momencie adowania formularza. W metodzie tej nakazujemy
przypisa do zdarzenia Click metody button1_Click() oraz myButton_Click().
Wicej informacji
na temat biblioteki WinForms oraz projektowania wizualnego znajdziesz w rozdziale 10.

Napiszmy prosty program obsugujcy zdarzenia. Oczywicie najczstszym zastosowaniem


zdarze jest ich uycie wraz z komponentami, ale ja zaprezentuj prosty przykad. Klasa,
ktra dokonuje banalnego dziaania: dzielenie dwch liczb wskazanych przez uytkownika:
class DoIt
{
public delegate void OddDelegate(float Value);
public event OddDelegate Odd;
public float Div(float X, float Y)
{
float Z = X / Y;
if (Z % 2 == 0)
{
Odd(Z);
}
return Z;
}
}

W klasie zadeklarowaem delegat OddDelegate oraz darzenie Odd. W metodzie Div()


nastpuje dzielenie wartoci wskazanych w parametrach X oraz Y. Nastpnie sprawdzamy,
czy liczba jest parzysta. Jeeli tak, wywoujemy zdarzenie Odd.
Na formularzu umieciem dwa komponenty TextBox oraz przycisk. Oprogramowaem
zdarzenie Click przycisku w nastpujcy sposb:
private void btnDoIt_Click(object sender, EventArgs e)
{
DoIt MyDoIt = new DoIt();
MyDoIt.Odd += new DoIt.OddDelegate(MyOdd);
float Result = MyDoIt.Div(float.Parse(edtX.Text),
float.Parse(edtY.Text));
lblResult.Text = "Suma: " + Convert.ToString(Result);
lblResult.Visible = true; // spraw, aby komponent by
widoczny
}

Na samym pocztku utworzyem nowy egzemplarz (obiekt) klasy DoIt. Nastpnie


przypisaem metod MyOdd() do zdarzenia Odd. Innymi sowy, jeeli liczba uzyskana w
wyniku dzielenia bdzie parzysta, zostanie wywoana metoda MyOdd(), ktrej budowa jest
do prosta:

private void MyOdd(float Value)


{
MessageBox.Show("Liczba parzysta " + Value);
}

Dziaanie takiego programu prezentuje rysunek 6.2, a cay kod rdowy zawarty jest na
listingu 6.4.

Rysunek 6.2. Przykad uycia zdarze


Listing 6.4. Przykad wykorzystania zdarze
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace EventsApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void MyOdd(float Value)
{
MessageBox.Show("Liczba parzysta " + Value);
}
private void btnDoIt_Click(object sender, EventArgs e)
{
DoIt MyDoIt = new DoIt();
MyDoIt.Odd += new DoIt.OddDelegate(MyOdd);
float Result = MyDoIt.Div(float.Parse(edtX.Text),
float.Parse(edtY.Text));

lblResult.Text = "Suma: " +


Convert.ToString(Result);
lblResult.Visible = true;
}
}
class DoIt
{
public delegate void OddDelegate(float Value);
public event OddDelegate Odd;
public float Div(float X, float Y)
{
float Z = X / Y;
if (Z % 2 == 0)
{
Odd(Z);
}
return Z;
}
}
}

Podsumowanie
Delegaty i zdarzenia nale do bardziej skomplikowanych mechanizmw jzyka C#, na
pocztku moesz nie dostrzega zalet ich wykorzystania. Z czasem jednak, kiedy bdziesz
projektowa bardziej zaawansowane aplikacje, delegaty mog okaza si bardzo wydajnym
mechanizmem zwikszajcym czytelno i wydajno kodu. Ze zdarzeniami natomiast
bdziesz mia kontakt cay czas, programujc z wykorzystaniem wizualnej biblioteki
Windows Forms.

Rozdzia 7
Tablice i kolekcje
Omwilimy ju spor cz tego, co oferuje jzyk C#. Powiedzielimy sobie o
najwaniejszym programowaniu obiektowym, ktre moe przysporzy najwicej kopotw
pocztkujcemu programicie. Nie zaprezentowaem do tej pory bardzo wanego elementu

wielu jzykw programowania, a mianowicie obsugi tablic.


Jest to bardzo wygodna funkcja jzyka programowania; przekonasz si o tym podczas pisania
przykadowej aplikacji podsumowujcej dotychczasow wiedz o jzyku C#. Bdzie to znana
i lubiana gra kko i krzyyk. O tym jednak pod koniec tego rozdziau. Nie przeduajc,
spiesz z wyjanieniem, czym s tablice

Czym s tablice
Wyobra sobie, e w swojej aplikacji musisz przechowa wiele zmiennych tego samego typu.
Dla przykadu, niech bd to dni tygodnia typu string. Proste? Owszem, wystarczy
zadeklarowa siedem zmiennych typu string:
string pon, wt, r, czw, pt, so, nd;

Teraz do tych zmiennych naley przypisa warto:


pon = "Poniedziaek";
wt = "Wtorek";
// itd

Teraz wyobra sobie sytuacj, w ktrej musisz zadeklarowa 12 zmiennych oznaczajcych


nazwy miesicy. Nieco uciliwe? Owszem. Do tego celu najlepiej uy tablic, ktre su do
grupowania wielu elementw tego samego typu. Osobicie z tablic korzystam bardzo czsto,
jest to znakomity, czytelny sposb na przechowywanie duej iloci danych.
Przejdmy jednak do rzeczy. W C# istnieje moliwo deklaracji zmiennej, ktra
przechowywa bdzie wiele danych. Tak w skrcie i uproszczeniu moemy powiedzie o
tablicach.

Deklarowanie tablic
Tablice deklaruje si podobnie jak zwyke zmienne. Jedyn rnic jest zastosowanie
nawiasw kwadratowych:
typ[] Nazwa;
W miejsce typ naley poda typ danych elementw tablicowych (np. int, string), a w
miejsce nazwa nazw zmiennej tablicowej. Przykadowo:

int[] Foo;

W tym miejscu zadeklarowalimy tablic Foo, ktra moe przechowywa elementy typu int.
Przed uyciem takiej tablicy naley zadeklarowa, z ilu elementw ma si ona skada. W
tym celu korzystamy ze znanego nam operatora new:
Foo = new int[5];

Taka konstrukcja oznacza zadeklarowanie w pamici komputera miejsca dla piciu


elementw tablicy Foo. Przypisywanie danych do poszczeglnych elementw odbywa si
rwnie przy pomocy symboli nawiasw kwadratowych:
int[] Foo;
Foo = new int[5];
Foo[0]
Foo[1]
Foo[2]
Foo[3]
Foo[4]

=
=
=
=
=

100;
1000;
10000;
100000;
1000000;

Console.WriteLine(Foo[4]);

Moliwy jest rwnie skrtowy zapis deklaracji tablic, podobny do tego znanego z tworzenia
obiektw:
int[] Foo = new int[5];

Indeks
Tablica skada si z elementw. Kademu z nich przypisany jest tzw. indeks, dziki ktremu
odwoujemy si do konkretnego elementu tablicy. w indeks ma posta liczby i wpisujemy
go w nawiasach kwadratowych, tak jak to zaprezentowano w poprzednim przykadzie. Spjrz
na kolejny przykad:
char[] Foo = new char[5];
Foo[0]
Foo[1]
Foo[2]
Foo[3]
Foo[4]

=
=
=
=
=

'H';
'e';
'l';
'l';
'o';

Indeksy numerowane s od zera do N 1, gdzie N to ilo elementw tablicy. Aby lepiej to


zrozumie, spjrz na tabel 7.1.
Tabela 7.1. Prezentacja zalenoci indeksw elementw
Indeks 0 1 2 3 4
Warto H e l l o

Uwaga! Naley uwaa, aby nie odwoa si do elementu, ktry nie istnieje! Jeeli
zadeklarowalimy tablic 5-elementow i odwoujemy si do szstego elementu (poprzez
indeks nr 5), kompilator C# nie zareaguje! Bd zostanie wywietlony dopiero po
uruchomieniu programu.

Inicjalizacja danych
Po utworzeniu tablicy kademu elementowi przypisywana jest domylna warto. Np. w
przypadku typu int jest to cyfra 0. Programista po zadeklarowaniu takiej tablicy ma
moliwo przypisania wartoci dla konkretnego elementu.
Istnieje moliwo przypisania wartoci dla konkretnego elementu ju przy deklarowaniu
tablicy. Naley wwczas wypisa wartoci w klamrach:
char[] Foo = new char[5] {'H', 'e', 'l', 'l', 'o'};
Console.WriteLine(Foo[4]);

Jzyk C# dopuszcza uproszczony zapis takiego kodu wystarczy pomin ilo elementw
tablicy:
char[] Foo = {'H', 'e', 'l', 'l', 'o'};

Kompilator oblicza rozmiar takiej tablicy po iloci elementw uporzdkowanych pomidzy


klamrami.

Tablice wielowymiarowe

C# umoliwia take deklarowanie tzw. tablic wielowymiarowych. Przykadowo, poniszy


kod tworzy tablic 7x2 (7 kolumn i 2 wiersze):
string[,] Foo = new string[7, 2];

Zasada deklarowania tablic wielowymiarowych jest prosta. W nawiasie kwadratowym


wpisujemy znak przecinka (,), natomiast podczas inicjalizacji musimy poda wymiar tablicy
(ilo elementw naley rwnie rozdzieli znakiem rednika). Podczas przypisywania
danych do elementw naley poda dokadny indeks:
Foo[0,
Foo[1,
Foo[2,
Foo[3,
Foo[4,
Foo[5,
Foo[6,

0]
0]
0]
0]
0]
0]
0]

=
=
=
=
=
=
=

"Pn";
"Wt";
"r";
"Czw";
"Pt";
"So";
"Nd";

Foo[0,
Foo[1,
Foo[2,
Foo[3,
Foo[4,
Foo[5,
Foo[6,

1]
1]
1]
1]
1]
1]
1]

=
=
=
=
=
=
=

"Mon";
"Tue";
"Wed";
"Thu";
"Fri";
"Sat";
"Sun";

Jzyk C# nie ogranicza nas w iloci wymiarw. Moemy wic wprowadzi do naszej tablicy
kolejny wymiar. Poniszy fragment prezentuje deklaracj tablicy 2x4x2:
string[, ,] Foo = new string[2, 4, 2];

Inicjalizacja danych tablicy wielowymiarowej jest analogiczna do standardowej tablicy:


int[,] Foo = new int[3, 3] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8,
9 } };

Zauwa jednak, e poszczeglne elementy zawarte w klamrach s rozdzielone znakiem


przecinka. Oczywicie istnieje moliwo skrtowego zapisu:
int[,] Foo = new int[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9
} };

lub:
int[,] Foo = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };

Ptla foreach
Podczas omawiania zagadnienia ptli nie wspomniaem o jednej wanej ptli sucej do
operowania na tablicach. Poniewa tematyka tablic w rozdziale 3. nie bya omawiania, pragn
wspomnie o tej ptli wanie tutaj.
Ptla ta, znana programistom PHP, Perl czy te Delphi, dla .NET jako parametru wymaga
tablicy. Spjrz na poniszy fragment kodu:
string[] Foo = new string[7];
Foo[0]
Foo[1]
Foo[2]
Foo[3]
Foo[4]
Foo[5]
Foo[6]

=
=
=
=
=
=
=

"Pn";
"Wt";
"r";
"Czw";
"Pt";
"So";
"Nd";

foreach (string Bar in Foo)


{
Console.WriteLine(Bar);
}

Uruchomienie takiej aplikacji spowoduje wywietlenie, jeden pod drugim, kolejnych


elementw tablicy. Po kadej iteracji kolejny element tablicy przypisywany jest do zmiennej
Bar. Tutaj wana uwaga. Zmienna Bar nie moe by zadeklarowana lub uyta we
wczeniejszych fragmentach kodu. Np. ponisza konstrukcja jest bdna:
string Bar = "Test";
foreach (string Bar in Foo)
{
Console.WriteLine(Bar);
}

Przy prbie kompilacji wywietlony zostanie bd: A local variable named 'Bar'

cannot be declared in this scope because it would give a different meaning


to 'Bar', which is already used in a 'parent or current' scope to denote
something else.

Identyczny rezultat jak ten pokazany przed chwil mona osign, stosujc ptl for:
for (int i = 0; i < Foo.Length; i++)
{
Console.WriteLine(Foo[i]);
}

Konstrukcja Foo.Length zwraca rozmiar tablicy.


Zasadniczo wygodniejszym i czytelniejszym sposobem jest uycie ptli foreach, ktra w
kocu zostaa stworzona po to, by operowa na tablicach. Jednake uycie ptli for ma jedn
przewag nad foreach mona w niej modyfikowa wartoci elementw. Spjrz na
poniszy przykad:
for (int i = 0; i < Foo.Length; i++)
{
Foo[i] = "Foo";
Console.WriteLine(Foo[i]);
}

Identycznego efektu nie uzyskamy, stosujc ptl foreach:


foreach (string Bar in Foo)
{
Bar = "Foo";
}

W tym momencie kompilator zasygnalizuje bd: Cannot assign to 'Bar' because it


is a 'foreach iteration variable'.

Ptla foreach a tablice wielowymiarowe


Ptla foreach z powodzeniem dziaa na tablicach wielowymiarowych. W takim wypadku
kolejno iteracji jest nastpujca: najpierw przekazana zostanie warto elementu [0, 1],
nastpnie [0, 2] itd. Krtki kod prezentujcy takie dziaanie:

string[,] Foo = new string[7, 2];


Foo[0,
Foo[1,
Foo[2,
Foo[3,
Foo[4,
Foo[5,
Foo[6,

0]
0]
0]
0]
0]
0]
0]

=
=
=
=
=
=
=

"Pn";
"Wt";
"r";
"Czw";
"Pt";
"So";
"Nd";

Foo[0,
Foo[1,
Foo[2,
Foo[3,
Foo[4,
Foo[5,
Foo[6,

1]
1]
1]
1]
1]
1]
1]

=
=
=
=
=
=
=

"Mon";
"Tue";
"Wed";
"Thu";
"Fri";
"Sat";
"Sun";

foreach (string Bar in Foo)


{
Console.WriteLine(Bar);
}

Kolejno wywietlania danych na konsoli bdzie nastpujca:


Pon
Mon
Wt
Tue
...

W przypadku tablic wielowymiarowych konstrukcja Tablica.Length zwraca liczb wszystkich


elementw w tablicy. W prezentowanym przykadzie bdzie to 7x2, czyli 14.
Dziaanie ptli for na tablicach wielowymiarowych jest nieco inne. Przykadowo, ponisza
ptla spowoduje wywietlenie jedynie polskich dni tygodnia:
for (int i = 0; i < Foo.Length / 2; i++)
{
Console.WriteLine(Foo[i, 0]);
}

Tablice tablic
Mechanizm tablic jest w jzyku C# bardzo rozbudowany. Umoliwia nawet tworzenie tablic,
ktre zawieraj kolejne tablice. Poniszy kod prezentuje deklaracj takiej tablicy:
int[][] Foo = new int[2][];

Ten zapis oznacza, i tablica Foo zawiera bdzie kolejne dwie tablice o nieokrelonej
jeszcze liczbie elementw. Te dwie kolejne tablice rwnie musz zosta utworzone:
Foo[0] = new int[50];
Foo[1] = new int[1000];

Przypisywanie danych do takich tablic wyglda podobnie jak w przypadku tablic


wielowymiarowych:
// przypisanie wartoci do elementu 26. tablicy nr 1
Foo[0][25] = 100;
// przypisanie wartoci do elementu 1000. tablicy drugiej
Foo[1][999] = 1;

Sprawa inicjalizacji tablic prezentuje si podobnie, jak to zostao zaprezentowane w trakcie


omawiania bardziej podstawowych elementw. Przypisanie wartoci do elementw w
tablicach tego typu charakteryzuje si do specyficzn skadni:
int[][] Foo = new
{
new int[] {1,
new int[] {1,
}; // zwr uwag

int[][]
2}, // zwr uwag na brak rednika!
2, 3}
na obecno rednika!

Console.WriteLine(Foo[0][1]);

Moim zdaniem przejrzystszy jest skrtowy zapis powyszego kodu, rwnie akceptowany
przez kompilator C#:
int[][] Foo =
{
new int[] {1, 2},
new int[] {1, 2, 3}
};

Tablice struktur
O strukturach i wyliczeniach powiedzielimy sobie w rozdziale 5. W niektrych przypadkach
przydatna okazuje si moliwo deklarowania tablic struktur lub typw wyliczeniowych. Jest
to sprawa do prosta, jeeli znasz ju podstawy uycia tablic, bowiem zamiast typu
dotychczas uywanego (czyli int, string, char itp.) naley uy wczeniej zadeklarowanej
struktury:
public struct Bar
{
public string Name;
public byte Age;
}
class Program
{
static void Main(string[] args)
{
Bar[] BarArr = new Bar[2];
BarArr[0].Name = "Janusz Kowalski";
BarArr[0].Age = 52;
BarArr[1].Name = "Piotr Nowak";
BarArr[1].Age = 18;
}
}

Parametr args w metodzie Main()


Gdy w rozdziale trzecim omawiaem podstawowe elementy programu C#, wspomniaem
oczywicie o metodzie Main(), lecz pominem znaczenie parametru args. Zrobiem to
celowo, aby nie wprowadza zamtu, gdy tematyka tablic czy nawet typw danych nie bya
wwczas poruszana. Parametr args typu tablicowego zawiera ewentualne parametry
przekazane do naszej aplikacji z linii polece. Czyli uruchamiajc program z poziomu linii
komend, mog napisa:
MojaAplikacja.exe Parametr1 Parametr2

Zarwno Parametr1, jak i Parametr2 zostan przekazane do aplikacji, kady zostanie


przypisany do odrbnego elementu tablicy. Napiszmy dla treningu prosty program. Jego
zadanie bdzie banalne: sortowanie argumentw przekazanych do programu.
Waciwie najtrudniejsz rzecz w programie jest sama konwersja danych z acucha string
na warto cakowit int. Samo sortowanie tablicy realizuje metoda Sort() klasy Array.
Cao programu prezentuje listing 7.1.
Listing 7.1. Pobieranie i sortowanie argumentw programu
using System;
namespace FooConsole
{
class Program
{
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("Brak argumentw
programu!");
return;
}
int[] ArrInt = new int[args.Length];
int count = 0;
foreach (string element in args)
{
ArrInt[count] = Int32.Parse(element);
++count;
}
Array.Sort(ArrInt);
for (int i = 0; i < ArrInt.Length; i++)
{
Console.WriteLine("{0} ", ArrInt[i]);
}
}
}
}

Argumenty przekazane do aplikacji konwertujemy, a nastpnie zapisujemy w nowo


utworzonej tablicy ArrInt. Zwr uwag, e do konwersji danych ze string na int uyem
metody Parse() z klasy Int32. To rwnie jest dopuszczalny sposb, rwnie dobrze mogem
take uy klasy Convert.

Posortowan tablic prezentuj na konsoli, wywietlajc w ptli kolejne jej elementy.


Element {0} wykorzystany w acuchu w metodzie WriteLine() zostanie zastpiony
wartoci zmiennej przekazanej w tej samej metodzie. Jest to czytelny i prosty sposb
formatowania acuchw np.:
string sName = "Adam";
string sLocation = "Wrocawiu";
Console.WriteLine("Mam na imi {0} i mieszkam we {1}", sName,
sLocation);

Klasa System.Array
Chyba oswoie si ju z myl, e cae rodowisko .NET oparte jest na klasach, strukturach i
wyliczeniach? Nie inaczej jest w przypadku tablic. Kada tablica w jzyku C# dziedziczy po
klasie System.Array, ktra dostarcza podstawowych mechanizmw do manipulacji na
elementach tablicy. To dziki metodom tej klasy moemy pobra ilo elementw w tablicy,
posortowa j czy przeszuka. Kilka najbliszych stron zostanie przeznaczonych na opisanie
podstawowych elementw tej klasy.
Jeeli chodzi o waciwoci klasy, to najwaniejsz jest Length, ktra zwraca aktualn liczb
elementw tablicy. Ta sama klasa udostpnia rwnie waciwo LongLength, ktra zwraca
64-bitow warto okrelajc rozmiar wszystkich elementw w przypadku tablic
wielowymiarowych.
Warto rwnie wspomnie o waciwoci Rank, ktra zwraca liczb wymiarw danej tablicy:
int[,] Foo = new int[3, 2] { { 1, 2 }, { 1, 2 }, { 1, 2 } };
Console.WriteLine(Foo.Rank); // tablica dwuwymiarowa
(wywietli 2)

Metody klasy
W trakcie omawiania klasy System.Array naley wspomnie o paru metodach, ktre mog
Ci si przyda przy okazji operowania na tablicach.

BinarySearch()
Uywajc algorytmu przeszukiwania binarnego, przegldam elementy tablicy, aby znale
dan warto. Pierwszym parametrem tej metody musi by nazwa tablicy, na ktrej bdzie
ona operowa. Drugim parametrem szukany element. Oto przykad uycia tej metody:
string[] Foo = new string[] { "Pn", "Wt", "r", "Czw", "Pt" };
Console.WriteLine(Array.BinarySearch(Foo, "r"));

Metoda zwraca numer indeksu, pod jakim znajduje si szukany element, lub 1, jeeli nic nie
zostao znalezione. W zaprezentowanym przykadzie metoda zwrci warto 2.

Clear()
Metoda umoliwia wyczyszczenie tablicy. W rzeczywistoci ustawia kademu elementowi
warto 0 lub null w zalenoci od jego typu. Metoda przyjmuje trzy parametry. Pierwszym
jest nazwa tablicy, drugim numer indeksu, od ktrego ma rozpocz czyszczenie, a trzecim
zasig tego procesu. Dziki metodzie Clear() mona bowiem wyczyci okrelone
elementy z tablicy. Poniszy przykad prezentuje czyszczenie caej zawartoci:
Array.Clear(Foo, 0, Foo.Length);

Metoda Clear() nie zmienia rozmiaru czyszczonej tablicy. Jeeli czycimy tablic, ktra ma
powiedzmy 5 elementw, to po przeprowadzeniu tej operacji nadal bdzie ich miaa tyle
samo.
Do elementw wyczyszczonej tablicy ponownie moemy przypisywa jakie wartoci.
Sowo kluczowe null w jzyku C# oznacza warto pust.

Clone()
Metoda Clone() zwraca kopi tablicy, z ktrej zostaa wywoana np.:
Bar = Foo.Clone();

Od tej pory Bar bdzie posiadaa takie same elementy co Foo. Poniewa metoda Clone()
zwraca dane w postaci typu object, naley dokona rzutowania na waciwy typ. Tzn. jeeli

mamy tablic typu string, naley na niego dokona odpowiedniego rzutowania, co


prezentuje poniszy przykad:
string[] Foo = new string[] { "Pn", "Wt", "r", "Czw", "Pt" };
// tworzenie kopii
string[] Bar = (string[])Foo.Clone();
foreach (string element in Bar)
{
Console.WriteLine(element);
}

Copy()
By moe lepszym sposobem na utworzenie kopii tablicy bdzie zastosowanie metody
Copy(). Umoliwia ona dodatkowo okrelenie rozmiarw kopiowania, tj. ile elementw
zostanie skopiowanych. Oto przykad:
string[] Foo = new string[] { "Pn", "Wt", "r", "Czw", "Pt" };
string[] Bar = new string[Foo.Length];
// tworzenie kopii
Array.Copy(Foo, Bar, Foo.Length);

Pierwszym parametrem tej metody musi by tablica rdowa (kopiowana), drugim


tablica, do ktrej skopiowane zostan elementy. Trzeci parametr to oczywicie ilo
kopiowanych elementw.

Find()
Metoda umoliwia przeszukanie caej tablicy w celu znalezienia danego elementu.
Kwalifikacja danego elementu jako znaleziony lub te nie odbywa si przy pomocy
zewntrznej metody. Oto przykadowy program:
{
Point[]
new
new
new
};

points = {
Point(10, 20),
Point(100, 200),
Point(400, 500)

Point first = Array.Find(points, pointFind);

Console.WriteLine("Found: {0}, {1}", first.X, first.Y);


Console.Read();
}
private static bool pointFind(Point point)
{
if (point.X % 2 == 0)
{
return true;
}
else
{
return false;
}
}

Na samym pocztku zadeklarowaem tablic struktur Point, ktre bd przeszukiwane.


Program wyszukuje elementw tablicy, w ktrej element X struktury Point jest liczb
parzyst. Po znalezieniu pierwszego metoda pointFind() zwraca true i koczy swe
dziaanie.
Struktura Point zadeklarowana jest w przestrzeni nazw System.Drawing. Nie zapomnij
zadeklarowa jej uycia przy pomocy sowa using oraz doczy odpowiedniego podzespou
(System.Drawing.dll).

FindAll()
Jak sama nazwa wskazuje, metoda FindAll() wyszukuje wszystkie elementy, ktre speniaj
dane kryteria poszukiwa. Oto jak powinien wyglda program z poprzedniego listingu, jeli
ma wyszukiwa wszystkie elementy:
static void
{
Point[]
new
new
new
};

Main(string[] args)
points = {
Point(10, 20),
Point(100, 200),
Point(400, 500)

Point[] find = Array.FindAll(points, pointFind);


foreach (Point element in find)
{
Console.WriteLine("Found: {0}, {1}", element.X,
element.Y);

}
Console.Read();
}
private static bool pointFind(Point point)
{
if (point.X % 2 == 0)
{
return true;
}
else
{
return false;
}
}

FindLast()
Metoda ta dziaa podobnie jak Find(). Jedyna rnica jest taka, e FindLast() szuka
ostatniego wystpienia danego elementu; nie koczy pracy, gdy znajdzie pierwszy element
pasujcy do kryteriw.

GetLength()
Zwraca ilo elementw w tablicy. Umoliwia dziaanie na tablicach wielowymiarowych. W
parametrze tej metody naley poda numer wymiaru, ktrego ilo elementw ma by
pobrana. Jeeli mamy do czynienia z tablic jednowymiarow, w parametrze wypisujemy
cyfr 0.
Klasa System.Array udostpnia rwnie metod GetLongLength(), ktra dziaa
analogicznie do GetLength(), z t rnic, i zwraca dane w postaci liczby typu long.

GetLowerBund(), GetUpperBund()
Metoda GetLowerBund() zwraca numer najmniejszego indeksu w tablicy. W przewaajcej
czci przypadkw bdzie to po prostu cyfra 0. Metoda GetUpperBund() zwraca natomiast
najwikszy indeks danej tablicy. Obie metody mog dziaa na tablicach wielowymiarowych;
wwczas naley w parametrze poda indeks wymiaru. Przykadowe uycie:

int[] Foo = { 1, 2, 3, 4, 5, 6 };
Console.WriteLine("Najmniejszy indeks: {0}, najwikszy: {1}",
Foo.GetLowerBound(0), Foo.GetUpperBound(0));

GetValue()
Prawdopodobnie nie bdziesz zmuszony do czstego korzystania z tej metody, zwraca ona
bowiem warto danego elementu tablicy. W parametrze tej metody musisz poda indeks
elementu, tak wic jej dziaanie jest rwnoznaczne z konstrukcj:
Tablica[1]; // zwraca element znajdujcy si pod indeksem 1

Chcc wykorzysta t metod, kod moemy zapisa nastpujco:


int[] Foo = { 1, 2, 3, 4, 5, 6 };
Console.WriteLine(Foo.GetValue(1));

Initialize()
We wczeniejszych fragmentach tego rozdziau pisaem o inicjalizacji tablicy. Metoda
Initialize() moe to uatwi. Jej uycie powoduje przypisanie kademu elementowi pustej
wartoci (czyli moe to by cyfra 0 lub np. warto null). Jej uycie jest bardzo proste, nie
wymaga podawania adnych argumentw:
Foo.Initialize();

IndexOf()
Przydatna metoda. Zwraca numer indeksu na podstawie podanej wartoci elementu. Przykad
uycia:

string[] Foo = { "Pn", "Wt", "r", "Czw", "Pt", "So", "Nd" };


Console.WriteLine(Array.IndexOf(Foo, "Pt"));

W powyszym przykadzie na konsoli zostanie wywietlona cyfra 4, gdy pod tym numerem
kryje si element Pt.

Resize()
Metoda Resize() przydaje si wwczas, gdy musimy zmieni rozmiar danej tablicy. W
pierwszym jej parametrze musimy poda nazw tablicy poprzedzon sowem kluczowym ref
oznaczajcym referencj. Drugim parametrem musi by nowy rozmiar tablicy:
string[] Foo = { "Pn", "Wt", "r", "Czw", "Pt", "So", "Nd" };
Array.Resize(ref Foo, Foo.Length + 5);

SetValue()
Metoda SetValue() umoliwia nadanie wartoci dla danego elementu tablicy.
Prawdopodobnie nie bdziesz korzysta z niej zbyt czsto, gdy to samo dziaanie mona
zrealizowa przy pomocy operatora przypisania. Gdyby jednak mia wtpliwoci, co do jej
uycia, poniej prezentuj przykad:
Foo.SetValue("Weekend", 9);

Taki zapis oznacza przypisanie wartoci Weekend pod indeks nr 9.

Sowo kluczowe params


Mechanizm tablic jzyka C# nie umoliwia tworzenia tablic dynamicznych, tj. o zmiennym
rozmiarze. Zmiana rozmiaru tablic (iloci elementw) jest nieco problematyczna, podobnie
jak usuwanie elementw. Warto jednak wspomnie o sowie kluczowym params, uywanym
w poczeniu z tablicami. Konkretnie z tablicowymi parametrami metod:

static void Foo(params string[] args)


{
}

Sowo params, ktre poprzedza waciw deklaracj parametru, mwi o tym, i liczba
elementw przekazywanych do metody bdzie zmienna. Oto przykad takiego programu:
using System;
namespace FooApp
{
class Program
{
static void Foo(params string[] args)
{
for (int i = 0; i < args.Length; i++)
{
Console.Write(args[i] + " ");
}
Console.WriteLine();
}
static void Main(string[] args)
{
Foo("Adam", "Paulina");
Foo("Adam", "Paulina", "Marta");
Console.Read();
}
}
}

Jak widzisz, moliwe jest przekazanie dowolnej liczby parametrw do metody Foo(). Kady
parametr bdzie kolejnym elementem tablicy i jest to przydatna cecha jzyka C#.
Moliwe jest przekazywanie parametrw rnego typu. Nagwek metody musi wyglda
wwczas tak:
static void Foo(params object[] args)

Tak metod mona wywoa np. tak:


Foo("Adam", 10, 12.2);

Przykad gra kko i krzyyk


Wydaje mi si, e ju do powiedziaem o klasach, obiektach i tablicach, nie prezentujc
adnego przykadu wymagajcego napisania wicej ni 100 linii kodu. Powimy wic
troch czasu na napisanie aplikacji, ktra bdzie wykorzystywaa tablice. Niech to bdzie
popularna gra kko i krzyyk. Na samym pocztku napiszemy silnik aplikacji, ktry
bdzie zawarty w osobnym module, w klasie nazwijmy j GomokuEngine (niekiedy gra
kko i krzyyk jest wanie tak nazywana). Aby lepiej zaprezentowa pewn cech klas,
najpierw napiszemy interfejs konsolowy, ktry bdzie wykorzystywa nasz silnik, a dopiero
pniej, w dalszej czci ksiki, skorzystamy z WinForms.

Zasady gry
Wydaje mi si, e wikszo Czytelnikw zna zasady gry kko i krzyyk, lecz na wszelki
wypadek je przypomn. W najpopularniejszym wydaniu gra odbywa si na planszy 3x3.
Gracze na przemian umieszczaj w polach swoje znaki, dc do zajcia trzech pl w jednej
linii. Gracz ma przyporzdkowany znak krzyyka (X) lub kka (O). Jedno pole moe by
zajte przez jednego gracza i nie zmienia si przez cay przebieg gry (rysunek 7.1).

Rysunek 7.1. Prezentacja gry kko i krzyyk

Specyfikacja klasy
Zacznijmy od utworzenia nowego moduu w naszym projekcie. Na pasku narzdziowym
znajd przycisk Add New Item (Ctrl+Shift+A) i wybierz pozycj Code File.
Przede wszystkim naley zadeklarowa nowy typ wyliczeniowy identyfikujcy znak
umieszczony na planszy do gry:
public enum FieldType {ftCircle = 1, ftCross = 10};

Nie przypadkowo wartociom typu wyliczeniowego nadaem indeksy 1 oraz 10. Te liczby
zostan wykorzystane w algorytmie obliczania, ktry z graczy wygra (lub czy w ogle kto

wygra). Jeeli mamy ju typ wyliczeniowy, naley zadeklarowa tablic, ktra bdzie
kluczowa w caej grze. Bdzie bowiem przechowywa stan gry i zawarto planszy
jednoczenie:
private FieldType[,] FField = new FieldType[3, 3];

Do tego musimy skorzysta z tablicy wielowymiarowej 3x3.


Jeeli wic uytkownik bdzie chcia wykona jaki ruch, naley okreli wsprzdne pola,
na ktrym zostanie postawiony znak:
FField[1, 1] = FieldType.ftCircle; // np...

Teraz warto zastanowi si, jakie zapisy powinny w tej klasie znale si w sekcji publicznej,
aby gotowa gra speniaa oczekiwania uytkownikw. A jakie s oczekiwania?
Funkcjonalno
gry nie musi by dua, wystarczy funkcja
, ktra na podstawie wsprzdnych X i Y umieci w tablicy odpowiednie pole (krzyyk lub
kko). Trzeba oczywicie zapewni dostp
do tej tablicy, czyli klas t naley zadeklarowa w sekcji public. Dodatkowo przydayby si
waciwoci, dziki ktrym bdzie mona okreli imiona graczy. Co poza tym? Na pewno
bdziemy chcieli wiedzie, czy gra zostaa zakoczona i kto wygra. Potrzebne bd jeszcze
dwie metody Start() oraz NewGame(). Pierwsza rozpoczyna gr, druga resetuje
ustawienia (liczb zwycistw poszczeglnych graczy).

Ustawienia gracza
Gracze bd identyfikowani za pomoc nazw. Na samym pocztku gry uytkownicy bd
mogli wpisa swoje imiona. Proponuj wic pj dalej i utworzy now struktur Player,
ktra bdzie zawiera informacj
o graczu:
// struktura opisujca gracza
public struct Player
{
// nazwa gracza
public string Name;
// ilo zwycistw
public int Winnings;
// reprezentujcy go symbol
public FieldType Type;
}

W strukturze znajduje si nazwa uytkownika, symbol, jakim si on posuguje (pole Type),


czyli kko (ftCircle) lub krzyyk (ftCross), oraz liczba zwycistw (Winnings). Skoro
graczy bdzie dwch, to mona w sekcji private klasy utworzy tablic dwuelementow:
private Player[] FPlayer = new Player[2];

Zarys klasy
Powiedzielimy ju o informacji o graczach oraz planszy do gry. Wane jest take, aby
zawrze w klasie waciwo zwracajc aktywnego uytkownika, tj. takiego, ktry w danej
chwili ma wykona ruch. Naley rwnie zadeklarowa waciwo Winner typu bool, ktra
bdzie przypisywa warto true, jeeli ktry z graczy wygra. Bdzie to informacja, aby
zakoczy gr. W swojej klasie skorzystaem z waciwoci, z ktrych przewaajca cze
jest tylko do odczytu.
Na podstawie tego, co powiedziaem, list pl oraz waciwoci mona zapisa tak, jak to
zostao przedstawione na listingu 7.2.
Listing 7.2. Zarys klasy GomokuEngine
using System;
// typ pola (kko circle lub krzyyk (cross))
public enum FieldType {ftCircle = 1, ftCross = 10};
// struktura opisujca gracza
public struct Player
{
// nazwa gracza
public string Name;
// ilo zwycistw
public int Winnings;
// reprezentujcy go symbol
public FieldType Type;
}
class GomokuEngine
{
// tablica 3x3 reprezentujca pole gry
private FieldType[,] FField = new FieldType[3, 3];
// zmienna oznaczajca zwycistwo ktrego gracza (true)
private bool FWinner;
// ID gracza, ktry teraz wykonuje ruch
private int FActive;
// tablica graczy (tylko dwch graczy)

private Player[] FPlayer = new Player[2];


/* METODY PRYWATNE */
private string GetPlayer1()
{
return FPlayer[0].Name;
}
private void SetPlayer1(string Name)
{
FPlayer[0].Name = Name;
}
private string GetPlayer2()
{
return FPlayer[1].Name;
}
private void SetPlayer2(string Name)
{
FPlayer[1].Name = Name;
}
private Player GetActive()
{
return FPlayer[this.FActive];
}
// waciwo Winner
public bool Winner
{
get
{
return FWinner;
}
}
// waciwo zwraca aktywnego gracza
public Player Active
{
get
{
return GetActive();
}
}
// zwraca informacje o graczu nr 1
public string Player1
{
get
{
return GetPlayer1();
}

set
{
SetPlayer1(value);
}
}
// waciwo zwraca informacje o graczu nr 2
public string Player2
{
get
{
return GetPlayer2();
}
set
{
SetPlayer2(value);
}
}
// waciwo tylko do odczytu, zwraca informacje o polu
bitwy ;-)
public FieldType[,] Field
{
get
{
return FField;
}
}
}

W klasie nie ma pl typu public zadeklarowaem same waciwoci oraz metody.


Dodatkowo wikszo waciwoci jest tylko do odczytu wartoci mona przypisa
jedynie dwm waciwociom, Player1 oraz Player2 (imiona graczy).
Zacznijmy od rzeczy najwaniejszej, czyli od pola FPlayer. Jest to dwuelementowa tablica
typu Player przechowujca informacje dotyczce graczy. Pole FActive typu int przechowuje
informacj, ktry uytkownik aktualnie wykonuje ruch.
Dla uytkownika naszej klasy zapewne wana bdzie waciwo Active, ktra reprezentuje
danego gracza. Owa waciwo typu Player jest tylko do odczytu, zwracane przez ni dane
s odczytywane za pomoc metody GetActive():
private Player GetActive()
{
return FPlayer[FActive];
}

Informacje o uytkowniku s zwracane na podstawie pola FActive.

Ustawienie symbolu na planszy


Jedn z waniejszych funkcji klasy jest m.in. metoda Set(), ktra na podstawie
wsprzdnych X i Y umieszcza odpowiedni symbol w tablicy FField. Druga wana metoda
klasy to CheckWinner(), ktra po kadym ruchu uytkownika sprawdza, czy gr mona
zakoczy. W tym celu naley opracowa odpowiedni algorytm, ktry jest najtrudniejsz
czci programu, ale tym zajmiemy si pniej.
Najpierw przyjrzyjmy si metodzie Set():
// Metoda suy do ustawiania symbolu na danym polu
public bool Set(int X, int Y)
{
// poniewa indeks tablic rozpoczyna si od zera, naley
zmniejszy
// wartoci wsprzdnych, bo user podaje wsprzdne
numerowane od 1
--X;
--Y;
// sprawdzenie, czy pole nie jest zajte
if (FField[X, Y] > 0)
{
Console.WriteLine("To pole nie jest puste!");
return false;
}
// sprawdzamy, czy uytkownik poda prawidowe wsprzdne
if ((X > 3) || (Y > 3))
{
Console.WriteLine("Nieprawidowe wartoci X lub/i Y");
return false;
}
// ustawienie znaku na danym polu
FField[X, Y] = GetActive().Type;
// sprawdzenie, czy naley zakoczy gr
CheckWinner();
// jeeli nikt nie wygra zamiana graczy
if (!Winner)
{
FActive = (FActive == 0 ? 1 : 0);
}
return true;
}

Naley wzi pod uwag fakt, i uytkownik podajc wsprzdne, bdzie podawa wartoci z
przedziau 1 3. Poniewa elementy w tablicy s indeksowane od 0, naley podane wartoci
pomniejszy o 1. Nastpnie przeprowadzamy proces tzw. walidacji danych, czyli sprawdzenia

ich poprawnoci. Uytkownik nie moe postawi znaku na ju zajtym polu. Nie moe
rwnie poda wsprzdnych wikszych ni 3.
Jeeli jednak wszystko pjdzie dobrze, w tablicy FField, w odpowiednim miejscu stawiamy
odpowiedni znak. w znak odczytujemy przy pomocy metody GetActive(). Ostatnim
krokiem jest sprawdzenie, czy nie naley zakoczy gry (metoda CheckWinner()). Jeeli nie
trzeba jej zakoczy, zamieniamy aktywnego gracza.
W powyszym kodzie brakuje sprawdzania, czy uytkownik nie poda wsprzdnych
mniejszych od cyfry 1. To jest zadanie dla Ciebie. Mam nadziej, e poradzisz sobie z tym
problemem.
Jeeli w metodzie Set() proces walidacji zostanie przeprowadzony bez problemw, zwraca
ona warto true. W przeciwnym wypadku false.

Sprawdzenie wygranej
Metoda CheckWinner() ma sprawdza, czy ktry z graczy wygra, tj. czy zapeni trzy pola
w jednej linii. Zastanwmy si, ile moe by kombinacji wygranych w grze kko i krzyyk.
Skoro pl jest dziewi, mona naliczy osiem wygranych kombinacji (trzy w poziomie, trzy
w pionie i dwie na ukos). Spjrzmy na rysunek 7.2.

Rysunek 7.2. Plansza do gry kko i krzyyk


Wyobramy sobie, e plansza na rysunku 7.2 odwzorowuje tablic FFields w klasie
GomokuEngine. Warto zauway, e pole FFields wskazuje na tablic FieldType, a elementy
tablicy s w rzeczywistoci liczbami. Kko odpowiada cyfrze 1, a krzyyk liczbie 10, tak jak
to zostao przedstawione na rysunku 7.2. Jak wic sprawdzi, czy gracz zakoczy gr
zwycistwem? Wystarczy zsumowa wartoci pl w jednej linii. Przykadowo: na planszy
znalazy si trzy znaki krzyyka ustawione obok siebie w linii poziomej (tak jak to
zaprezentowano na rysunku 7.2). Po zsumowaniu wartoci tych elementw tablicy
otrzymamy liczb 30. Gdyby zamiast krzyykw umieci kka, otrzymamy cyfr 3. Oto
dwie metody, ktre odpowiadaj za sprawdzenie zwyciscy gry:
// metoda sprawdza, czy gracz nr 1 lub 2 wygra gr
private void Sum(int Value)
{

if (Value == 3 || Value == 30)


{
FPlayer[FActive].Winnings++;
FWinner = true;
}
}
// algorytm sprawdza, czy ktry z graczy wygra gr
private void CheckWinner()
{
for (int i = 0; i < 3; i++)
{
Sum((int)FField[i, 0] + (int)FField[i, 1] +
(int)FField[i, 2]);
Sum((int)FField[0, i] + (int)FField[1, i] +
(int)FField[2, i]);
}
Sum((int)FField[0, 0] + (int)FField[1, 1] + (int)FField[2,
2]);
Sum((int)FField[0, 2] + (int)FField[1, 0] + (int)FField[2,
0]);
}

Metoda CheckWinner() sprawdza wszystkie kombinacje w taki sposb, e sumuje po trzy


pola z tablicy i przekazuje zsumowan warto do metody Sum(). Metoda Sum() sprawdza,
czy przekazana warto (parametr Value) rwna si liczbie 3 lub 30. Jeeli tak jest, mona
zakoczy gr i ogosi zwycizc (warto pola FWinner zmieniamy na true).
Peny kod rdowy moduu, w ktrym znajduje si klasa GomokuEngine, prezentuje listing
7.3.
Listing 7.3. Kod rdowy moduu
/************************************************************
*
Gomoku (Kko i krzyyk)
*
*
Copyright (c) Adam Boduch 2006
*
*
E-mail: adam@boduch.net
*
*
http://4programmers.net
*
*
*
* *********************************************************/
using System;
// typ pola (kko circle lub krzyyk (cross))
public enum FieldType {ftCircle = 1, ftCross = 10};
// struktura opisujca gracza
public struct Player
{
// nazwa gracza

public string Name;


// ilo zwycistw
public int Winnings;
// reprezentujcy go symbol
public FieldType Type;
}
class GomokuEngine
{
// tablica 3x3 reprezentujca pole gry
private FieldType[,] FField = new FieldType[3, 3];
// zmienna oznaczajca zwycistwo ktrego gracza (true)
private bool FWinner;
// ID gracza, ktry teraz wykonuje ruch
private int FActive;
// tablica graczy (tylko dwch graczy)
private Player[] FPlayer = new Player[2];
/* METODY PRYWATNE */
private string GetPlayer1()
{
return FPlayer[0].Name;
}
private void SetPlayer1(string Name)
{
FPlayer[0].Name = Name;
}
private string GetPlayer2()
{
return FPlayer[1].Name;
}
private void SetPlayer2(string Name)
{
FPlayer[1].Name = Name;
}
private Player GetActive()
{
return FPlayer[FActive];
}
// waciwo Winner
public bool Winner
{
get
{
return FWinner;
}

}
// waciwo zwraca aktywnego gracza
public Player Active
{
get
{
return GetActive();
}
}
// zwraca informacje o graczu nr 1
public string Player1
{
get
{
return GetPlayer1();
}
set
{
SetPlayer1(value);
}
}
// waciwo zwraca informacje o graczu nr 2
public string Player2
{
get
{
return GetPlayer2();
}
set
{
SetPlayer2(value);
}
}
// waciwo tylko do odczytu, zwraca informacje o polu
bitwy ;-)
public FieldType[,] Field
{
get
{
return FField;
}
}
// metoda sprawdza, czy gracz nr 1 lub 2 wygra gr
private void Sum(int Value)
{
if (Value == 3 || Value == 30)
{
FPlayer[FActive].Winnings++;
FWinner = true;
}

}
// algorytm sprawdza, czy ktry z graczy wygra gr
private void CheckWinner()
{
for (int i = 0; i < 3; i++)
{
Sum((int)FField[i, 0] + (int)FField[i, 1] +
(int)FField[i, 2]);
Sum((int)FField[0, i] + (int)FField[1, i] +
(int)FField[2, i]);
}
Sum((int)FField[0, 0] + (int)FField[1, 1] +
(int)FField[2, 2]);
Sum((int)FField[0, 2] + (int)FField[1, 0] +
(int)FField[2, 0]);
}
/* rozpoczyna waciw gr */
public void Start()
{
// przyporzdkowanie symbolu danemu graczowi
FPlayer[0].Type = FieldType.ftCircle;
FPlayer[1].Type = FieldType.ftCross;
FWinner = false;
// czyszczenie tablicy
System.Array.Clear(FField, 0, FField.Length);
}
// nowa gra ilo zwycistw zostaje wyzerowana
public void NewGame()
{
FPlayer[0].Winnings = 0;
FPlayer[1].Winnings = 0;
}
// Metoda suy do ustawiania symbolu na danym polu
public bool Set(int X, int Y)
{
// poniewa indeks tablic rozpoczyna si od zera,
naley zmniejszy
// wartoci wsprzdnych, bo user podaje wsprzdne
numerowane od 1
--X;
--Y;
// sprawdzenie, czy pole nie jest zajte
if (FField[X, Y] > 0)
{

Console.WriteLine("To pole nie jest puste!");


return false;
}
// sprawdzamy, czy uytkownik poda prawidowe
wsprzdne
if ((X > 3) || (Y > 3))
{
Console.WriteLine("Nieprawidowe wartoci X lub/i
Y");
return false;
}
// ustawienie znaku na danym polu
FField[X, Y] = GetActive().Type;
// sprawdzenie, czy naley zakoczy gr
CheckWinner();
// jeeli nikt nie wygra zamiana graczy
if (!Winner)
{
FActive = (FActive == 0 ? 1 : 0);
}
return true;
}
}

Interfejs aplikacji
Na samym pocztku wykorzystamy utworzon klas w aplikacji konsolowej. Aby uytkownik
mg zapeni okrelone pole, musi poda jego wsprzdn X i Y. Nastpnie aplikacja
wywietla aktualny stan gry (rysunek 7.3).

Rysunek 7.3. Gra kkoi krzyyk w trybie konsoli


Kod rdowy aplikacji korzystajcej z wczeniej stworzonego moduu jest do prosty.
Podstaw gry jest menu, dziki ktremu uytkownik steruje prac programu.

Menu do gry
Menu jest do proste. Zawiera kilka opcji, ktre mona wybra przy pomocy klawiatury.
Operacje rysowania menu zawarem w osobnej metodzie Menu():
static void Menu()
{
Console.Clear();
Console.WriteLine("------- Kko i krzyyk ------");
Console.WriteLine("------------------------------");
Console.WriteLine(" 1 -- Start gry
");
Console.WriteLine(" 2 -- Opcje
");
Console.WriteLine(" 3 -- O programie
");
Console.WriteLine(" Esc -- Zakoczenie
");
Console.WriteLine("------------------------------");
}

Wyjanienia wymaga waciwie tylko pierwsza linia z ciaa tej metody: Console.Clear().
Poniewa ta metoda bdzie wywoywana wiele razy w naszym programie, naley wyczyci
zawarto okna konsoli.

Sterowanie menu
Uytkownik bdzie mg sterowa menu za pomoc klawiszy klawiatury. Trzeba wic pobra
informacje na temat wcinitego klawisza. Umoliwia to metoda ReadKey() z klasy Console,
zwracajca informacje w postaci typu ConsoleKeyInfo:
ConsoleKeyInfo Key;
Key = Console.ReadKey(true);

W zalenoci od wcinitego klawisza wykonujemy odpowiednie metody naszego programu:


switch (Key.KeyChar)
{
case '1':
Start();
break;
case '2':
Option();
break;
case '3':
About();
break;
case '4':
break;
}

Warunkiem zakoczenia programu jest nacinicie klawisza Esc, wic wywietlanie menu
naley kontynuowa w ptli. Oto cay kod metody Main():
static void Main(string[] args)
{
// utworzenie nowej instancji klasy
GomokuObj = new GomokuEngine();
ConsoleKeyInfo Key;
do
{
Menu();
// pobranie wcinitego klawisza

Key = Console.ReadKey(true);
// w zalenoci od wcinitego klawisza, wykonujemy
okrelon metod
switch (Key.KeyChar)
{
case '1':
Start();
break;
case '2':
Option();
break;
case '3':
About();
break;
case '4':
break;
}
}
while (Key.Key != ConsoleKey.Escape);
}

Kod rdowy moduu gwnego


Najwaniejsza w naszym programie jest metoda Start(). To ona zawiera gwny kod
aplikacji, ktry odpowiada za pobieranie wsprzdnych i wywoywanie metody Set() z
klasy GomokuEngine. Ona odpowiada rwnie za rysowanie planszy do gry. Peny kod
rdowy aplikacji przedstawiony jest na listingu 7.4.
Listing 7.4. Kod aplikacji Gomoku
/************************************************************
*
Gomoku (Kko i krzyyk)
*
*
Copyright (c) Adam Boduch 2006
*
*
E-mail: adam@boduch.net
*
*
http://4programmers.net
*
*
*
* *********************************************************/
using System;
class Program

{
// instancja klasy
static GomokuEngine GomokuObj;
// metoda wywietla informacje o autorze
static void About()
{
Console.WriteLine(
"
Kko i Krzyyk v. 1.0
\n" +
"
Copyrigh (c) Adam Boduch 2006
\n" +
"
E-mail: adam@boduch.net
\n");
Console.ReadLine();
}
// metoda wywietla menu gry
static void Menu()
{
Console.Clear();
Console.WriteLine("------- Kko i krzyyk ------");
Console.WriteLine("------------------------------");
Console.WriteLine(" 1 -- Start gry
");
Console.WriteLine(" 2 -- Opcje
");
Console.WriteLine(" 3 -- O programie
");
Console.WriteLine(" Esc -- Zakoczenie
");
Console.WriteLine("------------------------------");
}
// gwna metoda rozpoczcie gry
static void Start()
{
// jeeli uytkownik nie poda imion, przekierowujemy
go do metody Option
if (GomokuObj.Player1 == null || GomokuObj.Player2 ==
null)
{
Option();
}
// inicjalizacja gry
GomokuObj.Start();
// licznik
tur
int Counter = 1;
Console.WriteLine();
int X, Y;
char C;
// gra bdzie kontynuowana, dopki kto nie wygra
// jednym z warunkw zakoczenia jest rwnie remis
while (GomokuObj.Winner == false)

{
Console.WriteLine("Tura nr. " +
Convert.ToString(Counter) + ", Gracz: " +
GomokuObj.Active.Name);
// pobranie wsprzdnych pola
Console.Write("Podaj wsprzdn Y: ");
X = Convert.ToInt32(Console.ReadLine());
Console.Write("Podaj wsprzdn X: ");
Y = Convert.ToInt32(Console.ReadLine());
// jeeli nie mona umieci znaku na polu, nie
wykonujemy dalszego kodu
// przechodzimy do kolejnej iteracji
if (!GomokuObj.Set(X, Y))
{
continue;
}
Counter++;
// jeeli jest to 9 ruch, widocznie jest remis
przerywamy ptl
if (Counter == 9)
{
break;
}
// ponisze ptle maj za zadanie rysowanie
planszy do gry
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
switch (GomokuObj.Field[i, j])
{
case FieldType.ftCross:
C = 'X';
break;
case FieldType.ftCircle:
C = 'O';
break;
default:
C = '_';
break;
}
Console.Write(" {0} |", C);

}
Console.WriteLine();
}
}
if (GomokuObj.Winner)
{
Console.WriteLine("Gratulacje, wygra gracz " +
GomokuObj.Active.Name);
}
else
{
Console.WriteLine("Remis!");
}
Console.WriteLine("Nacinij Enter, aby powrci do
menu");
Console.ReadLine();
}
// wywietlanie opcji do gry, czyli moliwoci wpisania
imion
static void Option()
{
Console.Write("Podaj imi pierwszego gracza: ");
GomokuObj.Player1 = Console.ReadLine();
Console.Write("Podaj imi drugiego gracza: ");
GomokuObj.Player2 = Console.ReadLine();
}
static void Main(string[] args)
{
// utworzenie nowej instancji klasy
GomokuObj = new GomokuEngine();
ConsoleKeyInfo Key;
do
{
Menu();
// pobranie wcinitego klawisza
Key = Console.ReadKey(true);
// w zalenoci od wcinitego klawisza,
wykonujemy okrelon metod
switch (Key.KeyChar)
{
case '1':
Start();

break;
case '2':
Option();
break;
case '3':
About();
break;
case '4':
break;
}
}
while (Key.Key != ConsoleKey.Escape);
}
}

wiczenie dodatkowe
W klasie GomokuEngine zaimplementowaem metod NewGame(), lecz nigdzie w programie
nie ma moliwoci rozpoczcia gry od nowa, czyli wyzerowania licznika wygranych. Ba,
licznik wygranych nie jest nawet nigdzie w programie prezentowany. To zadanie dla Ciebie!

Mechanizm indeksowania
Skoro powiedzielimy sobie o tablicach, grzechem byoby nie wspomnie o tzw. mechanizmie
indeksowania, funkcji jzyka C#, ktra pozwala odwoywa si do obiektu tak jak do
zwykych tablic (przy pomocy symboli [ ]). Jest to ciekawy element jzyka C#, nieco bardziej
zaawansowany, lecz myl, e warto o nim wspomnie.
O indeksatorach moesz myle jak o tablicach poczonych z moliwoci deklarowania
waciwoci klas. Wyobra sobie, e przy pomocy symboli [ oraz ] moesz odwoywa si do
pewnych elementw klasy:
TranslateList MyList = new TranslateList();
MyList[0] = "Foo";

Dodatkowo moesz kontrolowa proces pobierania oraz przypisywania takich elementw.

Indeksatory w klasie deklaruje si podobnie jak waciwoci, z t rnic, e zamiast nazwy


waciwoci stosujemy sowo kluczowe this:
class TranslateList
{
public string this[string index]
{
get
{
}
set
{
}
}
}

Jak widzisz, skadnia jest do charakterystyczna:

Indeksatory deklarujemy z uyciem sowa kluczowego this.


Indeksator musi posiada typ zwrotny.
Indeksator musi posiada akcesor get i/lub set.
W jednej klasie moe by wiele indeksatorw, pod warunkiem e bd miay rne
parametry.

Zaprezentuj teraz prosty przykad wykorzystania indeksatorw. Spjrz na ponisz klas:


class TranslateList
{
private string[,] DayList;
private int LangId;
public TranslateList(string lang)
{
DayList = new string[,]
{
{"Pn", "Wt", "r", "Czw", "Pt", "So", "Nd"},
{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
};
LangId = lang == "pl" ? 0 : 1;
}
public string this[int index]
{
get
{
return DayList[LangId, index];

}
}
}

W konstruktorze klasy wypeniamy danymi dwuwymiarow tablic, w ktrej znajduj si


oznaczenia dni tygodnia w jzyku polskim oraz angielskim. W indeksatorze istnieje
moliwo odczytania nazwy danego dnia w zalenoci od wartoci pola LangId. Teraz przy
pomocy nawiasw kwadratowych moemy odwoa si do poszczeglnych elementw:
TranslateList MyList = new TranslateList("en");
for (int i = 0; i < 7; i++)
{
Console.Write("{0} ", MyList[i]);
}

W zaprezentowanym przeze mnie przykadzie nie ma moliwoci przypisania danych do


indeksatora. Kompilator zaprotestuje, wywietlajc komunikat o bdzie: Property or
indexer cannot be assigned to -- it is read only.

Indeksy acuchowe
Tablice asocjacyjne, znane zapewne wielu programistom PHP, nie s niestety dostpne w
jzyku C#. Zamiast tego moemy jednak skorzysta z indeksatorw, ktre dopuszczaj uycie
acuchw w nazwach indeksu:
Console.WriteLine(MyList["Pn"]);
MyList["Pn"] = "Monday";
Console.WriteLine(MyList["Pn"]);

Oczywicie musimy jeszcze odpowiednio oprogramowa nasz indeksator:


class TranslateList
{
private string[] ShortDay;
private string[] LongDay;
public TranslateList()
{
ShortDay = new string[] {"Pn", "Wt", "r", "Czw",
"Pt", "So", "Nd"};
LongDay = new string[] {"Poniedziaek", "Wtorek",
"roda", "Czwartek", "Pitek", "Sobota", "Niedziela"};

}
public string this[string index]
{
get
{
int IndexOf = System.Array.IndexOf(ShortDay,
index);
return LongDay[IndexOf];
}
set
{
int IndexOf = System.Array.IndexOf(ShortDay,
index);
LongDay[IndexOf] = value;
}
}
}

Takie rozwizanie jest jednak nieco problematyczne. Lepiej w takich przypadkach uywa
list i sownikw, o ktrych bdzie mowa w tym rozdziale.
Podsumowujc: mechanizm indeksowania mona traktowa jako rozbudowany system tablic,
poniewa daj one wiksz kontrol nad przypisywaniem i odczytywaniem danych.
Indeksatory nie mog by opatrzone modyfikatorem dostpu static.

Kolekcje
Pisaem ju o tablicach, zgbiem temat mechanizmu indeksowania, lecz omawiajc tablice
jzyka C#, nie sposb nie wspomnie jeszcze o mechanizmie kolekcji. Tablice jako takie
obecne s w wikszoci jzykw wysokiego poziomu. Niekiedy mechanizm korzystania z
tablic jest naprawd rozbudowany (jak np. w jzyku PHP), a niekiedy moe okaza si
niewystarczajcy (jak w jzyku C#). Tablice w jzyku C# maj wiele ogranicze, ktre
moemy atwo omin, stosujc mechanizm kolekcji. Mwic o ograniczeniach, mam na
myli brak moliwoci nadawania okrelonych indeksw dla elementw tablicy (jeeli nie
chcemy, aby elementy byy indeksowane od 0) czy trudnoci w usuwaniu lub dodawaniu
kolejnych elementw tablicy. Deklarujc tablic w C#, musimy poda z gry jej rozmiar lub
nada jej elementy.
Kolekcje stanowi bardziej zaawansowany mechanizm przechowywania zbioru rnych
obiektw. Odpowiednie klasy dostarczaj bardziej zaawansowane metody suce do operacji
na zbiorze elementw kolekcji. W przestrzeni nazw System.Collections znajduje si wiele
klas sucych do operowania na zbiorach danych. W dalszej czci tego rozdziau omwimy
kilka z nich.

Interfejsy System.Collections
W przestrzeni nazw System.Collections zadeklarowanych jest wiele klas sucych do
operowania na zbiorach danych, a ich zaleno jest do dua, co moe spowodowa pewn
dezorientacj. Powrmy jeszcze na chwil do klasy System.Array. Mimo i naley ona do
przestrzeni nazw System, implementuje metody interfejsw ICloneable, IList,
ICollection oraz IEnumerable! Moemy wic o niej powiedzie, i jest prost klas
wykorzystujc mechanizm kolekcji!
Mona wyrni 8 interfejsw zdefiniowanych wewntrz przestrzeni System.Collections:
IEnumerable, ICollection, IList, IDictionary, IEnumerator, IComparer,
IDictionaryEnumerator, IHashCodeProvider. W tabeli 7.2 znajduje si krtki opis kadej
z nich.
Tabela 7.2. Opis interfejsw z przestrzeni System.Collections
Interfejs
Opis
Udostpnia interfejs, ktry umoliwia przegldanie elementw
IEnumerable
kolekcji w jednym kierunku.
ICollection
Dziedziczy metody IEnumerable. Definiuje rozmiar kolekcji.
Definiuje kolekcj, ktrej elementy s dostpne za porednictwem
IList
indeksw.
Definiuje kolekcj, w ktrej elementy s dostpne za porednictwem
IDictionary
kluczy.
Definiuje metody umoliwiajce przegldanie kolejnych elementw
IEnumerator
kolekcji.
Definiuje waciwoci umoliwiajce pobieranie kluczy i wartoci
IDictionaryEnumerator
sownikw (obiektw klas Dictionary).
IComparer
Definiuje metod suc do porwnywania dwch obiektw.
IHashCodeProvider
Definiuje metod zwracajc unikalny kod danego obiektu.

aden z tych interfejsw nie moe oczywicie dziaa samodzielnie. One jedynie definiuj
metody, ktre s nastpnie implementowane w klasach reprezentujcych. Tym zajmiemy si
w dalszej czci rozdziau. Teraz omwi pokrtce kilka najwaniejszych interfejsw.

IEnumerable
Bazowy interfejs. Definiuje waciwie tylko jedn metod GetEnumerator(). Zwraca ona
egzemplarz klasy, ktry implementuje interfejs IEnumerable. I to waciwie wszystko, co
znajduje si w tym interfejsie i wymaga omwienia.

ICollection
Interfejs, ktry dziedziczy po IEnumerable. Definiuje przydatn waciwo, z ktrej
bdziesz korzysta nie raz. Jest to waciwo Count, ktra zwraca ilo elementw w
kolekcji. Definiuje rwnie metod CopyTo(), ktra umoliwia skopiowanie obiektu
zawartego w danej kolekcji do tablicy (System.Array). Metoda ta posiada dwa parametry.
Pierwszy z nich to nazwa tablicy, do ktrej zostan skopiowane dane. Drugi to indeks
(liczony od zera), od ktrego rozpocznie si kopiowanie.

IList
Interfejs IList przejmuje waciwoci oraz metody po interfejsach ICollection oraz
IEnumerable. Jest to bazowy interfejs dla tzw. list. Definiuje metody oraz waciwoci
umoliwiajce dodawanie i kasowanie elementw listy. Na razie przyjrzyj si metodom i
waciwociom zdefiniowanym w interfejsie IList (tabela 7.3.). Przykady wykorzystania
klas implementujcych ten interfejs zaprezentuj w dalszej czci ksiki.
Tabela 7.3. Metody oraz waciwoci zdefiniowane w interfejsie IList
Waciwo/Metoda
Opis
IsFixedSize
Waciwo okrela, czy lista ma stay rozmiar.
Waciwo okrela, czy dana lista zwraca swoje wartoci jedynie do
IsReadOnly
odczytu.
Waciwo umoliwia pobranie lub ustawienie elementw pod danym
Item
indeksem.
Add()
Metoda umoliwia dodanie elementw do listy.
Clear()
Usuwa elementy z danej listy.
Contains()
Sprawdza, czy lista zawiera dan warto.
IndexOf()
Zwraca indeks danej wartoci.
Insert()
Umoliwia wstawienie elementu pod wskazan pozycj.
Remove()
Usuwa pierwsze wystpienie danego elementu.
RemoveAt()
Usuwa element okrelony danym indeksem.

IDictionary
Interfejs definiujcy metody oraz waciwoci umoliwiajce korzystanie z tzw. sownikw.
Sowniki umoliwiaj przechowywanie danych, do ktrych dostp uzyskuje si, podajc

odpowiedni klucz nie indeks, jak to jest w tablicach oraz listach. Tym zagadnieniem
zajmiemy si nieco pniej.
Interfejs, podobnie jak IList, definiuje waciwoci IsFixedSize, IsReadOnly i Items oraz
metody Add(), Clear(), Contains(), Remove(). Dodatkowo posiada dwie waciwoci:

Keys waciwo zwraca kolekcj skadajc si z kluczy danego sownika.


Values waciwo zwraca kolekcj skadajc si z wartoci danego sownika.

IEnumerator
Interfejs IEnumerator definiuje waciwo Current zwracajc biecy element kolekcji.
Dodatkowo definiuje metody MoveNext() oraz Reset(). Pierwsza z nich suy do
przechodzenia do kolejnego elementu kolekcji, a druga do przejcia do pierwszego
elementu.

Stosy
Po tym przydugim wstpie czas przej do rzeczy bardziej praktycznych, czyli obsugi
kolejek. Na pocztek chciabym wspomnie o klasie Stack (ang. stos), ktra suy do
przechowywania danych zgodnie z zasad FILO (ang. First-in Last-out), co oznacza:
pierwszy wchodzi, ostatni wychodzi.
Wyobra sobie stos talerzy jeden ley na drugim. Aby wycign ten na samym dole,
musisz najpierw cign te lece na nim, prawda? Identycznie dziaa klasa Stack. Moesz
swobodnie dodawa kolejne elementy do kolekcji, lecz nie ma sposobu na usunicie tego
dodanego na samym pocztku.
Spjrz na listing 7.5, ktry prezentuje przykad uycia klasy Stack oraz wywietlania
zawartoci danego stosu.
Listing 7.5. Przykad wykorzystania klasy Stack
using System;
using System.Collections;
namespace FooConsole
{
class Program
{
static void Main(string[] args)

{
Stack MyStack = new Stack();
for (int i = 0; i < 20; i++)
{
MyStack.Push("Pozycja nr " + i);
}
Console.WriteLine("Ostatni element: " +
MyStack.Peek());
Console.WriteLine("Usunity element: " +
MyStack.Pop());
Console.WriteLine();
IEnumerator MyEnum = MyStack.GetEnumerator();
while (MyEnum.MoveNext())
{
Console.WriteLine(MyEnum.Current.ToString());
}
Console.Read();
}
}
}

Przed uyciem klasy Stack naley wywoa konstruktor klasy. Mona w nim okreli
przewidywaln ilo elementw lub wywoa konstruktor bez parametrw, tak jak ja to
zrobiem. Wwczas zostanie nadana domylna ilo elementw, czyli 10. W razie potrzeby ta
warto zostanie automatycznie zwikszona, wic nie musisz si ba, e wystpi bd (takie
ryzyko istniao przy okazji korzystania z tablic).
Korzystajc z metody Push(), umieszczam na stosie kolejne elementy. Metoda Peek() suy
do pobrania ostatniego elementu stosu, natomiast Pop() zwraca ostatni element, po czym go
usuwa.
Zwr uwag na sposb wywietlania elementw z kolekcji. W tym celu zadeklarowaem
zmienn wskazujc na interfejs IEnumerator. Uyem rwnie metody GetEnumerator(),
ktra zwraca implementowany obiekt. Dziki temu, posugujc si odpowiednimi metodami,
jestem w stanie pobra kolejne elementy ze stosu i je wywietli.
Klasa Stack implementuje interfejsy ICollection, IEnumerable, ICloneable. Pamitaj
wic o tym, i zawiera metody oraz waciwoci zdefiniowane w tych interfejsach.
Ze wzgldu na to, e klasa Stack nie umoliwia usuwania dowolnego elementu stosu, pewnie
nie bdziesz z niej czsto korzysta. Waciwie zaley to tylko od Twoich potrzeb. Jeeli
istnieje konieczno umieszczenia elementw w formie stosu, wydaje mi si, e zastosowanie
klasy Stack bdzie dobrym rozwizaniem. Jeeli musisz mie moliwo usuwania
dowolnego elementu, zapewne bdziesz zmuszony skorzysta z list.

Kolejki
Klasa Queue dziaa podobnie jak Stack. Rny jest jednak sposb ich dziaania. Klasa Queue
dziaa zgodnie z zasad FIFO (ang. First-in First-out), co mona przetumaczy jako:
pierwszy wchodzi, pierwszy wychodzi. Aby lepiej zrozumie zasad dziaania tej klasy,
wyobra sobie zwyk kolejk sklepow. Ten, kto stoi w niej ostatni, obsugiwany jest jako
ostatni, prawda? Pierwsza osoba w kolejce wychodzi ze sklepu jako pierwsza. Klasa Queue
rwnie nie posiada metody umoliwiajcej usunicie dowolnego elementu. Zamiast tego
istnieje moliwo umieszczenia danego elementu jako ostatniego w kolejce, co realizuje
metoda Enqueue(). Jeeli mwimy o usuwaniu, to realizuje to metoda Dequeue(), ktra
usuwa pierwszy element w kolejce. Podsumowujc: jeeli chcemy usun ostatni element
kolejki, najpierw musimy usun wszystkie pozostae utworzone wczeniej. Na listingu 7.6
znajduje si kod rdowy zmodyfikowanego programu z listingu 7.5.
Listing 7.6. Program prezentujcy uycie klasy Queue
using System;
using System.Collections;
namespace FooConsole
{
class Program
{
static void Main(string[] args)
{
Queue MyQueue = new Queue();
for (int i = 0; i < 20; i++)
{
MyQueue.Enqueue("Pozycja nr " + i);
}
Console.WriteLine("Pierwszy element: " +
MyQueue.Peek());
Console.WriteLine("Usunity element: " +
MyQueue.Dequeue());
Console.WriteLine();
IEnumerator MyEnum = MyQueue.GetEnumerator();
while (MyEnum.MoveNext())
{
Console.WriteLine(MyEnum.Current.ToString());
}
Console.Read();
}
}
}

Rysunek 7.4 prezentuje program w trakcie dziaania.

Rysunek 7.4. Program prezentujcy dziaanie klasy Queue


Klasa Queue implementuje te same interfejsy, co klasa Stack.

Klasa ArrayList
ArrayList stanowi prawdopodobnie najlepsz alternatyw pomidzy klas Stack oraz
Queue. Posiada ona rwnie metody obecne w klasie Array, wic operowanie ni jest
podobne do obsugi zwykych tablic. Implementuje interfejsy IList, ICollection,
IEnumerable, ICloneable, wic miej na uwadze to, i zawiera waciwoci i metody, o

ktrych wspominaem kilka stron wczeniej, przy okazji omawiania tych interfejsw.

Listy
Opisaem ju, czym charakteryzuj si kolejki oraz stosy. Chciabym teraz pj nieco dalej i
zaj si tematyk list oraz typami generycznymi. Zostawmy na razie moliwoci oferowane
przez przestrze nazw System.Collections i pjdmy dalej. Zajmijmy si moliwociami
oferowanymi przez .NET 2.0, z ktrego zapewne teraz korzystasz, a konkretnie klasami
znajdujcymi si w przestrzeni System.Collections.Generic.

Typy generyczne
Podczas omawiania klas nie wspomniaem o jednej waciwoci klas szeroko
wykorzystywanej przy kolekcjach. Jest to nowo w rodowisku .NET Framework 2.0
(poprzednia wersja 1.1 nie posiadaa moliwoci wykorzystania typw generycznych),
wzorowana na technologii templates z jzyka C++.
Spjrz na poniszy kod prezentujcy, w jaki sposb moemy doda elementy do kolekcji typu
ArrayList i wywietli je:
ArrayList Foo = new ArrayList();
Foo.Add("Adam");
Foo.Add("Marta");
Foo.Add(100);
Foo.Add(2.34);
for (int i = 0; i < Foo.Count; i++)
{
Console.WriteLine("Indeks: {0} warto: {1}", i, Foo[i]);
}

Przy pomocy Add() dodajemy do listy kolejne elementy, raz typu string, pniej liczb
staoprzecinkow i wreszcie liczb rzeczywist. Jest to absolutnie dopuszczalne, gdy
parametr metody Add() jest typu object, a jak wiadomo wszystkie typy .NET
Framework dziedzicz po tej klasie.
Przy kadym wywoaniu metody Add() program musi wykona pakowanie (ang. boxing)
typw, a przy wywietlaniu odpakowywanie. Przy duej iloci danych trwa to do dugo.
O technice
pakowania oraz odpakowywania pisaem w rozdziale 5.
Dlatego jeeli zamierzasz umieci w kolekcji dane tego samego typu, lepiej wykorzysta
klasy oferowane przez przestrze nazw System.Collection.Generic. Znajduj si tam
klasy, odpowiedniki ju wspomnianych Stack oraz Queue, ktre dziaaj szybciej na danych
tego samego typu. Nie ma w przestrzeni nazw System.Collection.Generic klasy
ArrayList. Zamiast tego moemy jednak wykorzysta klas List, ktra jest waciwie
generycznym odpowiednikiem ArrayList. Listing 7.7 prezentuje prosty przykad uycia
klasy List:
Listing 7.7. Przykad uycia klasy List
using System;
using System.Collections.Generic;
namespace FooConsole
{
class Program

{
static void Main(string[] args)
{
List<int> Foo = new List<int>();
Foo.Add(10);
Foo.Add(100);
Foo.Add(10000);
for (int i = 0; i < Foo.Count; i++)
{
Console.WriteLine("Indeks: {0} warto: {1}",
i, Foo[i]);
}
Console.Read();
}
}
}

Przy deklarowaniu i utworzeniu obiektu klasy List musiaem poda typ danych (int), na
jakim chcemy operowa. Oczywicie istnieje moliwo operowania na dowolnym typie
danych wwczas w miejsce T naley poda jego nazw.

Tworzenie typw generycznych


Istnieje moliwo tworzenia wasnych klas generycznych. Jedyne, co musimy zrobi, to na
kocu nazwy doda fraz <T>:
class Generic<T>
{
}

Teraz przy tworzeniu egzemplarza klasy kompilator bdzie wymaga, aby poda rwnie typ
danych, na ktrych ma ona operowa np.:
Generic<string> MyGeneric = new Generic<string>();

Przyjo si, e przy deklarowaniu typw generycznych stosujemy fraz <T>. Kompilator nie
wymusza jednak takiego nazewnictwa, wic rwnie dobrze moemy napisa: class
Generic<Type> {}.

Po zadeklarowaniu takiej klasy w jej obrbie typ T bdzie oznacza typ danych podany
podczas jej tworzenia. Mona go dowolnie wykorzystywa. Np.:
class Generic<T>
{
public void Add(T X)
{
Console.WriteLine("{0}", X);
}
}

Obsuga takiej klasy wie si z odpowiednim utworzeniem obiektu:


Generic<string> MyGeneric = new Generic<string>();
MyGeneric.Add("Hello World!");

Metody generyczne
Istnieje rwnie moliwo deklarowania metod generycznych. Ich tworzenie wyglda bardzo
podobnie:
static void Foo<T>(T Bar)
{
Console.WriteLine("{0}", Bar);
}

Wywoujc tak metod, moesz, aczkolwiek nie musisz, podawa typu danych np.:
Foo("Adam"); // dobrze
Foo<int>(12); // dobrze

Kompilator domyli si typu na podstawie przekazanych parametrw.

Korzystanie z list
Klasa List posiada spore moliwoci implementujc interfejsy IList, ICollection,
IEnumerable, IList, ICollection, IEnumerable, umoliwia dodawanie, usuwanie

dowolnych pozycji list. Nie bd prezentowa spisu wszystkich metod oraz waciwoci, gdy
to zostao waciwie powiedziane ju wczeniej, przy okazji omawiania tablic oraz
interfejsw. Zaprezentuj za to prost aplikacj przedstawiajc uycie list.
Aplikacja bdzie do prosta. Po uruchomieniu uytkownik bdzie mg doda do listy nazw
wojewdztwa wraz z jego stolic (rysunek 7.5).

Rysunek 7.5. Aplikacja w trakcie dziaania


Uytkownik dodatkowo bdzie mg mie moliwo usunicia zaznaczonej pozycji oraz
wyczyszczenia caej listy. W licie bd przechowywane dane odnonie do wojewdztw oraz
ich stolic. W tym celu zadeklarowaem odpowiedni struktur:
public struct Location
{
public string Province;
public string Capital;
}

Listing 7.8 zawiera kod rdowy gwnego formularza programu.


Listing 7.8. Kod rdowy formularza
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace ListApp

{
// struktura przechowywana w licie
public struct Location
{
public string Province;
public string Capital;
}
public partial class MainForm : Form
{
private List<Location> MyList;
public MainForm()
{
InitializeComponent();
}
// metoda uaktualniajca stan komponentu ListBox
zgodnie ze stanem faktycznym
private void UpdateList()
{
lbLocation.Items.Clear();
for (int i = 0; i < MyList.Count; i++)
{
lbLocation.Items.Add(
String.Format("{0,-10} {1,10}",
MyList[i].Province, MyList[i].Capital
));
}
}
private void MainForm_Load(object sender, EventArgs e)
{
// utworzenie instancji klasy w momencie
zaadowania formularza
MyList = new List<Location>();
lbLocation.Items.Clear();
}
private void btnClear_Click(object sender, EventArgs
e)
{
MyList.Clear();
UpdateList();
}
private void btnDelete_Click(object sender, EventArgs
e)
{
// usunicie zaznaczonej pozycji

MyList.RemoveAt(lbLocation.SelectedIndex);
UpdateList();
}
private void btnAdd_Click(object sender, EventArgs e)
{
Location MyLocation = new Location();
MyLocation.Province = textProvince.Text;
MyLocation.Capital = textCapitol.Text;
textProvince.Text = textCapitol.Text = "";
MyList.Add(MyLocation);
UpdateList();
}
}
}

Myl, e aplikacja jest do prosta. Za wywietlanie wszystkich pozycji w komponencie typu


ListBox odpowiada metoda UpdateList(). W ptli dodaje ona kolejne pozycje do
komponentu ListBox po uprzednim jego wyczyszczeniu. Odpowiednie procedury
zdarzeniowe komponentw Button su do dodawania nowej pozycji, usuwania zaznaczonej
oraz czyszczenia wszystkich dodanych rekordw.
Po naciniciu przycisku sucego do dodawania nowych wpisw tworzona jest instancja
struktury, do ktrej przypisujemy dane wpisane w kontrolkach typu TextBox. Nastpnie przy
pomocy metody Add() dodajemy nowy wpis do kolejki.

Sowniki
Programistom PHP zapewne znany jest mechanizm tablic, w ktrym zamiast indeksu
uywany jest tzw. klucz. Wiesz ju, e indeks jest unikaln wartoci identyfikujc dany
element w tablicy/licie. Omawiajc mechanizm indeksowania, pokazaem, w jaki sposb
zrobi indeks, ktry mgby przyjmowa wartoci acuchowe. W przypadku sownikw
zamiast indeksu jest klucz (warto typu string), ktry rwnie musi posiada unikaln
warto w obrbie caej kolekcji. Waciwie to, czy bdzie on typu string, czy
jakiegokolwiek innego, zaley ju tylko od nas. To samo dotyczy wartoci elementw
sownika. Najczciej sowniki su mimo wszystko do przechowywania wartoci
tekstowych, gdzie klucz i warto s typu string.
Klasa Dictionary zadeklarowana jest w przestrzeni nazw System.Collections.Generic w
sposb nastpujcy:

public class Dictionary<TKey, TValue> : IDictionary<TKey,


TValue>, ICollection<KeyValuePair<TKey, TValue>>,
IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary,
ICollection, IEnumerable, ISerializable,
IDeserializationCallback { }

Jak wic widzisz, ilo interfejsw, ktre s w klasie implementowane, jest imponujca.
Deklarujc instancj klasy, naley poda zarwno typ klucza, jak i wartoci np.:
Dictionary<string, string> MyDictionary;

Korzystanie ze sownikw jest bardzo podobne do uywania zwykych list. Rnica jest taka,
i sowniki daj nam moliwo okrelenia typu klucza. Tak wic dla powyszej deklaracji,
dane do sownika moemy przypisywa w ten sposb:
MyDictionary["Klucz"] = "Warto";

Jeeli chcemy korzysta ze sownikw tak jak z list, naley utworzy nastpujc zmienn
wskazujc na klas:
Dictionary<int, string> MyDictionary;

Wwczas kluczem takiego sownika jest liczba staoprzecinkowa, a wartoci cig znakw
typu string.

Przykadowy program
Aby zaprezentowa moliwoci dziaania sownikw, napisaem prosty program
umoliwiajcy tumaczenie tekstu na podstawie swek wpisanych do sownika. Dziaanie jest
proste: przy pomocy metody Replace() program zamienia okrelony tekst znajdujcy si w
komponencie RichTextBox na wyrazy pobierane ze sownika.
Program napisaem na podstawie biblioteki wizualnej Windows Forms. Aplikacja zostaa
podzielona na dwie zakadki przy uyciu komponentu TabControl (rysunki 7.6 oraz 7.7).

Rysunek 7.6. Zakadka umoliwiajca dodanie danych do sownika

Rysunek 7.7. Zakadka umoliwiajca zamian tekstu


Na pierwszej zakadce, w komponencie ListView wywietlana jest zawarto sownika.
Lewa kolumna zawiera klucze sownika, a prawa ich wartoci. Program umoliwia
modyfikowanie, usuwanie oraz czyszczenie sownika.

Na prawej zakadce istnieje moliwo wprowadzenia tekstu. Po naciniciu przycisku


program odszukuje wyrazy okrelone w kluczach sownika i zastpuje je wartociami tych
kluczy.
Jeeli chodzi o zastpowanie tekstu, to realizuje to metoda Replace() klasy System.String.
Omwienie metod sucych do operowania na tekcie, znajdziesz w rozdziale 9. W ptli
musimy pobiera dane ze sownika, i nie tylko wartoci elementw, ale rwnie wartoci
kluczy. Realizuje to poniszy kod:
// utworzenie instancji klasy
Dictionary<string, string>.Enumerator Enumerator =
MyDictionary.GetEnumerator();
while (Enumerator.MoveNext())
{
// pobierz kolejny klucz i warto
KeyValuePair<string, string> Value = Enumerator.Current;
// zastp tekst
richTextBox.Text = richTextBox.Text.Replace(Value.Key,
Value.Value);
}

Aby zrealizowa to zadanie, musimy zadeklarowa zmienn wskazujc na struktur


Dictionary.Enumerator, do ktrej przypiszemy warto zwracan przez metod
GetEnumerator(). Pobranie kolejnych kluczy i wartoci nastpuje w ptli przy pomocy
struktury KeyValuePair.
Dodanie kolejnego elementu do sownika realizuje metoda Add(), ktra posiada dwa
parametry nazw klucza oraz warto. Chcc usun dany element, naley skorzysta z
metody Remove() poprzez podanie nazwy klucza.

Podsumowanie
By moe na tym etapie nauki jzyka nie dostrzegasz zalet stosowania tablic czy kolekcji.
By moe nie widzisz rwnie potrzeby stosowania tego typu rozwiza we wasnych
aplikacjach. Moesz mi wierzy lub nie, ale z czasem, gdy Twoje programy bd coraz
bardziej zaawansowane, zaistnieje konieczno wykorzystania tablic. Jako e tablice w C# nie
daj takich korzyci jak w innych jzykach (m.in. z powodu trudnoci w deklarowaniu
rozmiaru w trakcie dziaania programu), by moe bdziesz zmuszony do wykorzystania
kolekcji. Wwczas moesz sign po ten rozdzia i przeanalizowa prezentowane tutaj
przykady

Rozdzia 8
Obsuga wyjtkw
Trzeba sobie uwiadomi, e bdy w aplikacjach s niczym nieg na biegunie
elementem nieodcznym. Wielu programistw, czsto z popiechu, lecz take z braku
wystarczajcych umiejtnoci, marginalizuje problem bdw w aplikacjach. Wiele firm,
mimo ogromnego zaangaowania setek programistw oraz duych nakadw finansowych,
wci nie jest w stanie pozby si wszystkich bdw (np. Microsoft) i bez przerwy publikuje
nowe poprawki do swoich produktw. Tylko program zawierajcy trzy linie kodu rdowego
moe by pozbawiony jakichkolwiek bdw, lecz w przypadku skomplikowanych,
rozbudowanych aplikacji uniknicie niedoskonaoci jest niemoliwe. Dzieje si tak dlatego,
e programista jest tylko czowiekiem i po prostu si myli. Pozornie aplikacja moe
zachowywa si normalnie, a bd moe tkwi gdzie indziej. Nie mwi tutaj bowiem o
bdach wykrywanych w czasie kompilacji, najatwiejszych do usunicia, ktre sprowadza si
najczciej do drobnych poprawek, czsto bardzo miesznych na przykad dopisanie
rednika na kocu wyraenia. Najtrudniejsze do wykrycia s bdy zagniedone w kodzie,
kiedy program pozornie dziaa prawidowo, lecz nie do koca wykonuje operacje, ktrych
oczekuje uytkownik. Warto, aby w tym momencie dobrze zapamita stwierdzenie, i
program zawsze dziaa prawidowo, a jeli nie dziaa zgodnie z naszymi oczekiwaniami
jest to zwyczajnie wina projektanta.
Pomijam tutaj bdy kompilatora, bo te rwnie s tworzone przez ludzi i rwnie mog
zawiera bdy. Kompilator jest jednak produktem podwyszonego ryzyka: nie mona
pozwoli, aby zawiera choby najdrobniejsze bdy.
Bd w aplikacji czsto jest okrelany mianem bug (z ang. robak, pluskwa). Termin ten wzi
si z czasw, gdy komputery zajmoway due pomieszczenia i pobieray tyle energii co mae
osiedle, a ich obsug zajmowa si sztab ludzi. Robaki, ktre zalgy si gdzie w
zakamarkach ogromnej maszyny, czasami powodoway zwarcie instalacji elektrycznej. Od
tamtej pory bdy nazywa si bugami, a proces ich wykrywania debugowaniem (z ang.
debugging).
Czst przyczyn uwidocznienia bdu jest czynnik ludzki. Zamy na przykad, e piszesz
skomplikowan aplikacj biurow, z ktrej bdzie korzystao wielu ludzi. Nie kady z nich
jest informatykiem, nie kady posiada odpowiedni wiedz, aby wystarczajco dobrze
obsuy Twj program. Ty oczywicie powiniene dostarczy wraz z aplikacj podrcznik
uytkownika oraz projektowa interfejsy w sposb przejrzysty, lecz nie jeste w stanie
unikn sytuacji, w ktrej uytkownik obsuy j nieprawidowo na przykad w danym
polu tekstowym wpisze tekst zamiast liczby. Taki z pozoru bahy bd moe spowodowa
niespodziewane efekty i dziwne zachowania Twojego programu, dlatego nowoczesne jzyki
programowania dostarczaj odpowiednie mechanizmy, dziki ktrym jestemy w stanie
odpowiednio zareagowa na takie przypadki.

Czym s wyjtki

Wyjtkiem nazywamy mechanizm kontroli przepywu wystpujcy w jzykach


programowania i sucy do obsugi zdarze wyjtkowych. Zdarzenia wyjtkowe to w
szczeglnoci bdy, jak np. dzielenie przez zero.
Dzielenie liczby przez zero w programowaniu nie jest dopuszczalne. Nowoczesne
kompilatory staraj si wykry takie sytuacje i nie dopuci do kompilacji. Przykad:
int X;
X = 10 / 0;

Taki kod nie zostanie skompilowany, poniewa kompilator wykryje prb dzielenia przez
zero. Mona go jednak atwo oszuka, podstawiajc wartoci pod zmienne:
int
X =
Y =
Z =

X, Y, Z;
10;
0;
X / Y;

Powyszy kod zostanie skompilowany, lecz ju w trakcie dziaania taki program si


wysypie, wywietlajc bd. Mechanizm wyjtkw pozwala nam przechwyci takie
niedopuszczalne i wyjtkowe sytuacje i odpowiednio zareagowa, np. wywietlajc
komunikat: Panie, nie wiesz, e nie mona dzieli przez zero?.
Kompilator jest jednak tylko gupim programem i musimy go odpowiednio uwiadomi, jakie
miejsce kodu jest naraone na wystpienie bdu.

Obsuga wyjtkw
W jzyku C#, jak i w wielu innych popularnych jzykach, kod naraony na wystpienie
nieprzewidzianych sytuacji musimy oznaczy sowem kluczowym try. Kod wystpujcy w
bloku try bdzie obserwowany i w razie wystpienia nieprzewidzianych sytuacji bdzie
mona odpowiednio zareagowa:
try
{
// tutaj kod
}

Sam blok try nie wystarczy, musimy gdzie zawrze kod, ktry bdzie wykonywany w razie
wystpienia bdu. W tym celu uywamy sowa kluczowego catch:

try
{
// kod
}
catch
{
// kod w razie wystpienia wyjtku
}

Przykadowo, aby odpowiednio zareagowa na prb dzielenia, moemy zastosowa


nastpujce instrukcje:
int X, Y, Z;
X = 10;
Y = 0;
try
{
Z = X / Y;
}
catch
{
Console.WriteLine("Prosimy nie dzieli przez zero!");
}

Sprbuj uruchomi taki kod. Wskutek jego dziaania na konsoli wywietlony zostanie tekst
Prosimy nie dzieli przez zero!.
Uruchamiajc projekt z poziomu rodowiska Visual C# Express Edition, moesz nie
zaobserwowa dziaania wyjtkw. Wszystko dlatego, e to rodowisko
odpowiada w takim przypadku za obsug bdw. Aby lepiej zobrazowa dziaanie wyjtkw,
uruchamiaj swoje programy bez uycia debuggera. W Visual C# Express Edition odpowiada
za to skrt klawiaturowy Ctrl+F5.

Blok finally
Blok catch jest opcjonalny. Rwnie dobrze w miejsce sowa kluczowego catch moemy
wpisa finally. Rnica jest spora: kod zawarty w bloku finally zostanie wykonany
zawsze, bez wzgldu na to, czy wyjtek wystpi, czy te nie. Jest to dobre miejsce na
wykonanie instrukcji, ktre musz by wykonane przed zamkniciem programu. Dobrym
przykadem s operacje na plikach
. Po otwarciu pliku dokonujemy odczytu danych i skomplikowanych operacji. Dodajmy do
tego, e plik jest otwarty na wyczno naszego programu. Chcemy, aby w razie wystpienia

bdu plik zosta zamknity.


Dobr praktyk jest czenie bloku try-catch-finally dziki temu moemy odpowiednio
zareagowa na wystpienie wyjtku oraz wykona kod niezbdny przed zamkniciem
aplikacji. Oto przykad:
try
{
Z = X / Y;
}
catch
{
Console.WriteLine("Prosimy nie dzieli przez zero!");
}
finally
{
Console.WriteLine("Kod z bloku finally");
}

Po uruchomieniu takiej aplikacji oba komunikaty zostan wywietlone tylko wtedy, jeeli
dzielenie spowoduje bd (w najlepszym
przypadku zostanie wywietlony jeden z nich, z bloku finally).

Zagniedanie wyjtkw
Bloki wyjtkw mona w miar potrzeb dowolnie zagnieda:
try
{
// kod
try
{
// kod
try
{
}
finally
{
// finally
}
}
catch
{

// jeszcze inny bd
}
}
catch
{
// bd
}
finally
{
// blok finally
}

Wystpienie wyjtku w zagniedonym bloku nie oznacza, e wykonany zostanie rwnie


kod z zewntrznych blokw catch.

Klasa System.Exception
Nieobsuone wyjtki, czyli takie, ktrych nie obsuguje nasza aplikacja, s obsugiwane
przez system. Owocuje to najczciej wystpieniem komunikatu o bdzie, czsto niejasnym,
informujcym np. o nieprawidowym odwoaniu do pamici. To ju jednak s ekstremalne
sytuacje.
W kadym razie jeli wystpi wyjtek, system dostarcza programicie informacji na jego
temat, ktre moe on obsuy wedle wasnego uznania. Informacje oczywicie dostarczone
s w formie obiektu klasy, ktrej klas bazow jest System.Exception. W rzeczywistoci
rodowisko .NET
Framework posiada cakiem pokan kolekcj wyjtkw dziedziczonych po tej wanie
klasie.
Poniszy kod prezentuje obsug wyjtku polegajcego na wywietlaniu dostarczonego
komunikatu o bdzie:
// kod
try
{
Z = X / Y;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}

Jak widzisz, parametr bloku catch jest opcjonalny, aczkolwiek dopuszczalny. Taki zapis
oznacza deklaracj zmiennej e typu Exception (czyli w rzeczywistoci System.Exception) i

przypisanie do niej informacji odnonie do bdu. Tabela 8.1 zawiera spis najwaniejszych
waciwoci klasy System.Exception.
Tabela 8.1. Najwaniejsze waciwoci klasy System.Exception
Waciwo
Opis
Data
Dodatkowe informacje na temat rda wystpienia wyjtku.
Umoliwia odczytanie lub ustawienie linka (np. do pomocy) zwizanego z
HelpLink
bdem.
Message
Komunikat bdu.
Umoliwia odczytanie lub przypisanie nazwy aplikacji lub obiektu, w ktrym
Source
wystpi bd.
TargetSite Umoliwia odczytanie metody, w ktrej wystpi bd.

Selektywna obsuga wyjtkw


Czasami moe zaistnie sytuacja, w ktrej bdziemy chcieli odpowiednio zareagowa w
zalenoci od rodzaju wyjtku, jaki wystpi. Jzyk C# umoliwia w takich sytuacjach
dodanie kolejnego bloku catch:
string[] Foo = new string[5];
try
{
Foo[10] = "Bar";
}
catch (IndexOutOfRangeException e)
{
Console.WriteLine("Indeks jest zbyt duy!");
}
catch
{
Console.WriteLine("Inny bd");
}

Jak widzisz, w tym programie popeniem ewidentny bd prbuj przypisa warto do


indeksu tablicy nr 10. Wskutek takiego dziaania zostanie wykonany wyjtek
IndexOutOfRangeException, ktry obsuy pierwszy blok catch. Wszelkie inne
nieobsuone jeszcze wyjtki bd obsugiwane przez domylny blok catch.

Wywoywanie wyjtkw

W wielu jzykach programowania istnieje moliwo wywoywania danego wyjtku w


dowolnym miejscu kodu. Jest to zasygnalizowanie aplikacji, i w tym miejscu dochodzi do
nieprzewidzianej sytuacji.
Konstrukcja jest do prosta, suy do tego sowo kluczowe throw. Poniewa naley uy tego
sowa w poczeniu
z obiektem dziedziczonym po klasie System.Exception, bdziemy musieli dodatkowo uy
sowa kluczowego new:
try
{
throw new IndexOutOfRangeException();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}

throw jest najczciej uywane wewntrz bloku try, aczkolwiek dopuszczalne jest jego

uycie poza nim. W takiej sytuacji aplikacja moe uruchomi domyln obsug wyjtkw, co
najczciej koczy si komunikatem o bdzie.

Wasne klasy wyjtkw


Na potrzeby naszego programu moemy zadeklarowa w nim wasne klasy obsugi wyjtkw.
Przykadowo, w grze w kko i krzyyk, w metodzie Set() przeprowadzalimy walidacj
danych, naleao sprawdzi, czy uytkownik poda prawidowe wsprzdne. Dobrym
rozwizaniem byoby zadeklarowanie wwczas wasnej klasy wyjtkw, ktra byaby
wykonywana w razie podania nieprawidowych danych:
try
{
FField[X, Y] = GetActive().Type;
}
catch
{
throw new BadPointException();
}

Jeli utworzylibymy now klas BadPointException, moglibymy odpowiednio


zareagowa na t sytuacj, nie tylko poprzez wywietlenie odpowiedniego komunikatu, ale
rwnie poprzez podanie odnonika (URL) np. do opisanych zasad gry. No dobrze, by
moe troch zbytnio zobrazowaemsytuacj, rwnie dobrze mona to rozwiza w ten sposb:

try
{
FField[X, Y] = GetActive().Type;
}
catch
{
Console.WriteLine("Nieprawidowe pole! Naucz si gra!");
}

Ale czy koniecznie chcemy, aby komunikat bdu by wywietlany w oknie konsoli? Moe
lepszym rozwizaniem byoby, gdybymy pozwolili decydowa klasie wyjtku, co ma zrobi
z danym komunikatem?
BadPointException("Nieprawidowe pole! Naucz si gra!");

Niech klasa BadPointException decyduje, co program powinien w takiej chwili zrobi.


Moe wywietli komunikat na konsoli albo w nowym okienku Windows? Jeeli bdziemy
dostosowywali nasz program, aby dziaa nie jak dotychczas w oknie konsoli, ale z
wykorzystaniem biblioteki WinForms, wymagane poprawki bd kosmetyczne (albo w ogle
ich nie bdzie).

Deklarowanie wasnej klasy


Najlepszym rozwizaniem jest skorzystanie z tego, co ju jest. Po co wywarza otwarte
drzwi? Najlepiej wic bdzie, gdy nasza nowa klasa bdzie dziedziczya po
System.Exception. Po lekturze poprzednich rozdziaw nie powinno by z tym problemu:
public class MediumException : System.Exception
{
public MediumException(string Message)
: base(Message)
{
this.Source = "FooException";
this.HelpLink = "http://4programmers.net/C_sharp";
}
}

Dobr praktyk jest, aby klasy obsugi wyjtkw posiaday w nazwie swko Exception.
W konstruktorze klasy do odpowiednich waciwoci przypisywane s dane, ktre mog
pomc w ewentualnym odszukaniu i naprawie usterki. Zwr rwnie uwag, e konstruktor
klasy MediumException dziedziczy po takim samym konstruktorze z System.Array. W
bardziej rozbudowanych aplikacjach zalecane jest deklarowanie 3 konstruktorw dla klas
wyjtkw, kady z innymi parametrami:

public MediumException()
{
}
public MediumException(string Message)
: base(Message)
{
}
public MediumException(string Message, Exception inner)
: base(Message, inner)
{
}

Przykadowa aplikacja
Aby usystematyzowa wiedz na temat wyjtkw, proponuj napisanie prostej aplikacji z
wykorzystaniem biblioteki WinForms. W zalenoci od wybranej opcji bdzie ona generowa
dany wyjtek, a nastpnie odpowiednio go obsugiwa. Program w trakcie dziaania
zaprezentowany zosta na rysunku 8.1.

Rysunek 8.1. Program podczas dziaania

Do napisania takiej aplikacji uyem komponentw Button, RadioButton oraz


RichTextBox. W zalenoci od wybranej opcji po naciniciu przycisku generowany zostanie
dany wyjtek.
Kod procedury zdarzeniowej wyglda nastpujco:
private void RunBtn_Click(object sender, EventArgs e)
{
try
{
if (lowExceptionRadio.Checked)
{
throw new LowException("Niegrony bd");
}
if (mediumExceptionRadio.Checked)
{
throw new MediumException("redni bd");
}
if (HighExceptionRadio.Checked)
{
throw new HighException();
}
}
catch (Exception ex)
{
RichBox.Clear();
RichBox.Text += String.Format(
"Komunikat: {0}\n" +
"Podzesp: {1}\n" +
"Metoda: {2}\n" +
"Podzesp: {3}", ex.Message,
ex.Source, ex.TargetSite, ex.HelpLink);
}
}

W bloku catch nastpuje przechwycenie wyjtku i wywietlenie informacji na jego temat.


Peny kod rdowy programu znajduje si na listingu 8.1.
Waciwo Checked (typu bool) komponentu RadioButton informuje, czy kontrolka jest
zaznaczona, czy te nie.
Listing 8.1. Obsuga wyjtkw w C#
using System;
using System.ComponentModel;
using System.Data;
using System.Drawing;

using System.Text;
using System.Windows.Forms;
namespace ExceptionApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void RunBtn_Click(object sender, EventArgs e)
{
try
{
if (lowExceptionRadio.Checked)
{
throw new LowException("Niegrony bd");
}
if (mediumExceptionRadio.Checked)
{
throw new MediumException("redni bd");
}
if (HighExceptionRadio.Checked)
{
throw new HighException();
}
}
catch (Exception ex)
{
RichBox.Clear();
RichBox.Text += String.Format(
"Komunikat: {0}\n" +
"Podzesp: {1}\n" +
"Metoda: {2}\n" +
"Podzesp: {3}", ex.Message,
ex.Source, ex.TargetSite, ex.HelpLink);
}
}
}
public class LowException : System.Exception
{
public LowException(string Message) : base(Message)
{
this.Source = "FooException";
this.HelpLink = "http://4programmers.net";
}

}
public class MediumException : System.Exception
{
public MediumException(string Message)
: base(Message)
{
this.Source = "FooException";
this.HelpLink = "http://4programmers.net/C_sharp";
}
}
public class HighException : System.Exception

Pamitaj, aby wszelkie klasy swojego programu umieszcza w kodzie niej ni klasa obsugi
formularza (w moim wypadku Form1). Inaczej rodowisko Visual C# Express Edition ma
problem z prawidowym dziaaniem w trybie projektowania.
Waciwie wszystko w tym kodzie powinno by dla Ciebie zrozumiae. W konstruktorze
przypisujemy wartoci waciwociom dziedziczonym po klasie System.Exception. Owe
waciwoci s pniej odczytywane w bloku catch.

Przepenienia zmiennych
Na pocztku tej ksiki wprowadziem pojcie typu danych. Tam rwnie wspomniaem o
tym, i kady typ danych posiada jaki maksymalny zakres, tj. maksymaln warto, jak
mona przypisa do zmiennej tego typu. Np. maksymalna warto, jak mona przypisa do
zmiennej typu byte, to 255. Prba przypisania wikszej wartoci zakoczy si bdem:
byte b = 256;

OK, kompilator wykrywa takie prby, lecz nie jest w stanie wykry prby przypisania
wartoci wikszej ni 255 w trakcie dziaania programu:
byte X = 250;
byte Y = 50;
byte Z = (byte) (X + Y);

W tym przykadzie zmienna Z bdzie posiada warto 44. Dlaczego? Po przekroczeniu


zakresu wartoci bd ponownie numerowane od zera. Tak wic: 255+50 = 300255 = 45.

Poniewa wartoci numerowane s od zera, zmienna Z bdzie miaa warto 44, a nie 45, jak
mogoby wynika z tego rachunku matematycznego.
Uycie sowa kluczowego checked spowoduje, i przy przepenieniu zmiennej zgaszany
bdzie wyjtek OverflowException:
using System;
namespace FooApp
{
class Program
{
static void Main(string[] args)
{
byte X, Y, Z;
X = 250;
Y = 50;
Z = 0;
try
{
Z = checked((byte)(X + Y));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.Write(Z);
Console.Read();
}
}

W wyniku zaistnienia takiego kodu na ekranie konsoli wywietlona zostanie tre komunikatu
o bdzie. Sowa kluczowego checked mona uy jako operatora
(tak jak to przedstawiono w przykadzie powyej) lub w formie konstrukcji:
checked
{
// kod naraony na przepenienie zmiennej
}

Jzyk C# posiada rwnie sowo kluczowe unchecked, ktre powoduje, i w przypadku


przepenienia zmiennej nie jest zgaszany wyjtek. Jest to jednak domylne zachowanie
aplikacji pisanej w C# wic nie ma potrzeby jawnego uycia tego sowa.

Podsumowanie
Obsuga wyjtkw w C# nie jest ani trudna, ani te konieczna, aczkolwiek warto si nad tym
zastanowi podczas pisania aplikacji. Jeeli piszesz kod, ktry moe by naraony na ryzyko
wystpienia bdu, stosuj wywoanie try-catch-finally. Pozwoli Ci ono odpowiednio
zareagowa na ewentualne bdy oraz zwolni wszystkie zasoby zadeklarowane w trakcie
dziaania aplikacji.

Rozdzia 9
acuchy w C#
acuchy w informatyce oznaczaj cig znakw. Do tej pory pojcie cig kojarzyo Ci si
zapewne z typem string. Powiedzmy sobie szczerze, e jest to jeden z najczciej
wykorzystywanych typw danych w C#. W tym rozdziale mam zamiar wyoy tematyk
wykorzystania acuchw w C#, rwnie przy uyciu klasy System.StringBuilder.
Omwione zostan podstawowe metody suce do operowania na acuchach w rodowisku
.NET Framework.

Typ System.String
Podstawowym typem danych w rodowisku .NET Framework, ktry umoliwia operacje na
acuchach, jest System.String. W C# odpowiednikiem tego typu jest string i taki zapis
stosuje si najczciej ze wzgldu na skrcon form. Kady jzyk obsugujcy platform
.NET posiada wasny odpowiednik typu System.String, np. w Delphi jest to po prostu
String.
W zamierzchych czasach Turbo Pascala, ktry rwnie posiada obsug acuchw, dugo
jednego acucha moga wynosi maksymalnie 255 znakw. Nie jest to zbyt praktyczne, gdy
musimy przechowa w zmiennej naprawd du ilo tekstu. W nowoczesnych
jzykach programowania dugo tekstu, jaki moemy przypisa do acucha, jest niemal
nieograniczona, a praktycznie ogranicza j tylko ilo pamici, jak posiadamy w
komputerze. Ilo pamici, jaka jest rezerwowana na potrzeby danego acucha, zaley od
jego dugoci. Wszystko odbywa si automatycznie, nie musimy si martwi o przydzielanie
czy zwalnianie pamici.
acuch w .NET jest waciwie sekwencj (tablic) znakw typu String.Char poczon w

jedn cao. Do kadego elementu acucha moemy odwoywa si jak do elementu tablicy
przy pomocy symboli []:
System.String S = "Hello World";
Console.WriteLine(S[0]);

Wspomniana tablica jest indeksowana od zera, co oznacza, e pierwsza litera w acuchu


posiada indeks 0, druga 1 itd. Czyli pod konstrukcj S[0] kryje si litera H.
W rzeczywistoci tak moliwo zapewnia indekser zadeklarowany w klasie System.String.
W dalszej czci rozdziau, mwic o typie System.String, bd si posugiwa skrcon
nazw string.

Unicode w acuchach
Platforma .NET w caoci wspiera system kodowania znakw Unicode. Standard ten w
zamierzeniu obejmuje wszystkie jzyki wiata i eliminuje problem tzw. krzaczkw. Oznacza
to, e acuchy, m.in. jzyka C#, s kodowane w systemie Unicode, co sprawia, e kady
znak zajmuje 2 bajty pamici. Co wicej kodowanie Unicode moemy rwnie stosowa
w kodzie rdowym, uywajc np. polskich znakw w nazwie zmiennych:
string gegka;

Jest to bardzo dobre rozwizanie majce uatwi pisanie aplikacji wielojzykowych. Dziki
temu unikamy rwnie problemu kodowania i ewentualnej konwersji systemu znakw.

Niezmienno acuchw
Kad warto raz przypisan acuchowi moesz zmieni to fakt. Co wic oznacza, e
acuchy s niezmienne? Oznacza to, e jeeli raz stworzylimy egzemplarz typu string, to
nie bdziemy mogli zmieni jego wartoci. Nie oznacza to jednak, e poniszy kod
spowoduje bd:
System.String S = "Hello World";
S = "Witaj wiecie";

Jest on jak najbardziej prawidowy, jednak jego realizacja wymaga zastosowania dwch
egzemplarzy (obiektw) w pamici komputera. Co prawda kod wyglda tak, jakbymy t

warto modyfikowali, ale w rzeczywistoci w pamici tworzony jest nowy obiekt z now
wartoci, ktra jest zwracana. Stara warto znajduje si w pamici i czeka na usunicie
przez mechanizm platformy .NET garbage collection. Nie naley si wic tym zbytnio
przejmowa.
Aby si przekona, e modyfikowana warto w rzeczywistoci wcale nie ulega modyfikacji,
moesz wykona mae wiczenie. Oto prosty fragment kodu:
System.String S = "Hello World";
Console.WriteLine(S.ToUpper());
Console.WriteLine(S);

Metoda ToUpper() powoduje zamian wszystkich znakw w acuchu na wielkie i zwrcenie


nowej, zmodyfikowanej wartoci. Pod zmienn S kryje si jednak oryginalna, pierwotnie
zadeklarowana warto. Aby zastpi dotychczasow warto, naley uy oczywicie
operatora
przypisania:
S = S.ToUpper();

Ze wzgldu na to, i acuchy s niezmienne, nie zaleca si przeprowadzania na nich wielu


operacji dodawania tekstu, zamiany czy usuwania fragmentw acucha. Taki kod bdzie po
prostu dziaa niezwykle wolno. Zamiast tego zaleca si uycie klasy StringBuilder.
Klasa System.String jest zaplombowana. Oznacza to, e adna klasa nie moe po niej
dziedziczy.
Przeanalizujmy teraz tak sytuacj:
System.String S = "Adam";
S = S.Insert(4, " Boduch").ToUpper().Replace("CH", "SZEK");

Mimo i ten kod moe wydawa si nieco dziwny, jest jak najbardziej prawidowy. W
pierwszej kolejnoci pod indeksem nr 4 wstawiany jest nowy tekst. To owocuje powstaniem
w pamici nowego, zamienionego obiektu. Nastpnie wszystkie znaki w takim acuchu s
zamieniane na wielkie, co znowu tworzy nowy obiekt. Na kocu nastpuje zamiana znakw i
utworzenie nowego obiektu, ktry zostaje przypisany do zmiennej S. Pozostae obiekty bd
dostpne w pamici do czasu ich usunicia przez odmiecacz pamici (ang. garbage
collection).
Aby bardziej zobrazowa ca sytuacj, proponuj wklei do projektu nastpujcy kod:
System.String S = "Adam";
Console.WriteLine(S.Insert(4, " Boduch"));

Console.WriteLine(S.ToUpper());
Console.WriteLine(S.Replace("CH", "SZEK"));
// warto oryginalna
Console.WriteLine(S);

Wszystkie operacje zawarte w tym kodzie bd przeprowadzane na acuchu oryginalnym


znajdujcym si w zmiennej S.

Konstruktory klasy
Aby korzysta z waciwoci klasy string, naley jedynie zadeklarowa zmienn wskazujc
na ten typ danych. Nie ma potrzeby jawnego wywoywania konstruktora tej klasy. Jeeli
jednak zajdzie taka potrzeba, klasa System.String posiada kilka przecionych
konstruktorw, ktre mog przyj rne wartoci. Oto przykad wywoania konstruktora,
ktry jako argument przyjmuje tablic znakw char:
char[] charArr = new char[] { 'H', 'e', 'l', 'l', 'o' };
System.String S = new System.String(charArr);

Klasa udostpnia wiele konstruktorw. Ciekawych odsyam do dokumentacji jzyka C#.

Operacje na acuchach
Klasa System.String udostpnia kilka ciekawych metod sucych do operowania na
acuchach. Zapewne z wielu z nich bdziesz nie raz korzysta w trakcie programowania w
C#, wic postanowiem tutaj opisa kilka najciekawszych.

Porwnywanie acuchw
Zacznijmy od rzeczy najprostszej, czyli od porwnywania wartoci acuchw. Najprociej
uy przecionych operatorw != oraz ==, ktre zadeklarowane zostay w klasie
System.String. Czyli porwnywanie wartoci acuchw wyglda tak jak np.
porwnywanie wartoci typw liczbowych:
string s1, s2;

s1 = "Hello";
s2 = "hello";
if (s1 != s2)
{
Console.WriteLine("Wartoci s rne");
}

Taka instrukcja warunkowa zwrci warto true, poniewa operator != rozrnia wielko
liter, ktra jest rna w zmiennych s1 i s2. Jeeli chcemy, aby aplikacja ignorowaa wielko
znakw, naley skorzysta z metody Compare():
s1 = "Hello";
s2 = "hello";
if (String.Compare(s1, s2, true) == 0)
{
Console.WriteLine("Wartoci s rwne");
}

Pierwsze dwa parametry metody Compare() to oczywicie nazwy zmiennych. Trzeci


parametr okrela, czy rnice w wielkoci znakw maj by ignorowane (true), czy te
uwzgldniane (false). Metoda Compare() zwraca warto liczbow. Jeeli jest ona mniejsza
od zera, oznacza to, e acuch s1 jest mniejszy od s2. Warto wiksza od zera oznacza, e
s1 jest wikszy od s2, a zero oznacza, i s one rwne.
Trzeci parametr metody Compare() jest opcjonalny. Jeeli go nie okrelimy, metoda
domylnie bdzie rozrnia wielko znakw.
Porwnywanie acuchw przy pomocy operatorw == oraz != zapewnia w rzeczywistoci
mechanizm przeadowania operatorw, ktry zosta uyty w klasie System.String.
Istnieje rwnie metoda CompareOrdinal(), ktrej z pewnoci bdziesz rzadziej uywa.
Porwnuje ona cigi zgodnie z kodami ASCII przypisanymi do danego znaku; nie
odzwierciedla porzdku alfabetycznego:
s1 = "hello";
s2 = "HELLO";
Console.WriteLine(String.Compare(s1, s2, true));
Console.WriteLine(String.CompareOrdinal(s1, s2));

Po uruchomieniu takiego kodu na ekranie konsoli zostanie wywietlone:


0
32

Wartoci zwracane przez metod CompareOrdinal() oznaczaj to samo, co te zwracane przez


Compare() (tzn. warto 0 oznacza, i cigi s takie same).
Skoro jestemy przy temacie porwnywania wartoci acuchw, warto wspomnie o
moliwoci porwnywania z uwzgldnieniem rnych kultur. Suy do tego przeciona
metoda Compare():
s1 = "info";
s2 = "INFO";
Console.WriteLine(String.Compare(s1, s2, true, new
CultureInfo("pl-PL")));
Console.WriteLine(String.Compare(s1, s2, true, new
CultureInfo("tr-TR")));

Umoliwia ona porwnywanie wartoci z uwzgldnieniem regionu (kultury). W pierwszym


wypadku porwnywanie odbyo si przy zastosowaniu kultury pl-PL (Polska), a drugie tr-TR
(Turcja). Okazuje si, e w Turcji maa i dua litera i ma zupenie inne znaczenie i takie
porwnywanie zakoczy si wywietleniem na konsoli:
0
1
Typ CultureInfo() znajduje si w przestrzeni nazw System.Globalization.

Modyfikacja acuchw
W tabeli 9.1 zawarem najwaniejsze metody klasy System.String suce do modyfikacji
tekstu. Tabela zawiera skrtowy opis ich przeznaczenia; w dalszej czci rozdziau zawarem
krtkie przykady prezentujce dziaanie kadej z nich.
Tabela 9.1. Gwne metody klasy System.String
Funkcja/Procedura
Opis
Concat()
czy ze sob dwie lub wicej instancji klasy string.
Contains()
Sprawdza, czy cz acucha nie znajduje si w podanym acuchu.
Length()
Zwraca ilo znakw znajdujcych si w cigu znakowym.
Copy()
Tworzy now instancj cigu znakowego (kopiuje jego zawarto).
Insert()
Wstawia tekst w okrelone miejsce cigu znakowego.
Join()
Funkcja czy kilka elementw tekstowych w jeden.
Remove()
Funkcja umoliwia usunicie kawaka cigu znakw.
Replace()
Zamienia cz cigu znakowego.

Split()
SubString()
ToLower()
ToUpper()
Trim()
TrimEnd()
TrimStart()

Umoliwia rozdzielenie cigu znakw na mniejsze fragmenty na


podstawie podanego znaku.
Wycina cz cigu znakw.
Zwraca cig znakw zapisany maymi literami.
Zwraca cig znakw zapisany wielkimi literami.
Usuwa wszystkie spacje z pocztku i z koca cigu znakw.
Usuwa wszystkie spacje z koca cigu znakw.
Usuwa wszystkie spacje z pocztku cigu znakw.

Concat()
Metoda Concat() suy do czenia ze sob dwch lub wikszej iloci acuchw. Jej uycie
jest proste, bowiem ogranicza si do podania na licie parametrw acuchw, ktre maj
zosta poczone:
s1 = "Hello";
s2 = " World";
Console.WriteLine(String.Concat(s1, s2));

Metoda zwraca warto poczonych ze sob acuchw.


Moim zdaniem atwiejszym i przejrzystszym sposobem jest wykorzystanie do tego celu
operatora + (dodawanie), ktry z rwnie dobrym skutkiem poczy
dane cigi:
Console.WriteLine(s1 + s2);

Contains()
Metoda Contains() moe by wykorzystana do wyszukiwania danej frazy w acuchu.
Zwraca true, jeeli dana fraza zostaa odnaleziona, lub false jeeli nie albo acuch jest
pusty. Naley zwraca uwag na wielko znakw, gdy metoda Contains() je rozrnia.
Oto prosty przykad jej wykorzystania:
string s1 = "Ja i Magosia wietn par byli!";
if (s1.Contains("Magosia"))

{
Console.WriteLine("String zawiera sowo \"Magosia\"");
}

Jeeli chcemy w acuchu tekstowym zawrze znak cudzysowu ("), naley poprzedzi go
backslashem (\). Znak \ rwnie musi by poprzedzony backslashem, jeeli ma zosta
wywietlony:
s1 = "C:\\Windows\\System";

Length
Podobnie jak w przypadku tablic waciwo Length() podaje rozmiar, tak w kontekcie
uycia z acuchami zwraca ilo znajdujcych si w nich znakw:
s1 = "Ja i Magosia wietn par byli!";
Console.WriteLine(s1.Length);

Waciwo Length jest jedynie do odczytu, nie mona przypisywa do niej adnych wartoci.

Copy()
Metoda Copy() tworzy now instancj klasy z identyczn zawartoci:
s1 = "Ja i Magosia wietn par byli!";
s2 = String.Copy(s1);

Insert()
Metoda Insert() umoliwia wstawienie tekstu w dane miejsce acucha. Pierwszym
parametrem musi by pozycja (indeks), w jakiej zostanie umieszczony nowy tekst. Drugim
parametrem musi by sam tekst do wstawienia. Oto przykad:
s1 = "Ja i Magosia wietn par byli!";
s1 = s1.Insert(s1.Length, " I co im po tym?");
Console.WriteLine(s1);

Join()
Czsto w trakcie zmagania si z jakim problemem programistycznym moesz si natkn na
konieczno poczenia elementw tablicy w jeden cig. Metoda Join() czy elementy
tablicy w jedn cao na podstawie podanego cznika (dowolny cig znakw). Prosty
przykad:
string[] Foo = { "Ala", "ma", "kota" };
string s1;
s1 = String.Join(" ", Foo);
Console.WriteLine(s1);

Zmienna s1 po wykonaniu takiego kodu bdzie miaa warto Ala ma kota.

Split()
Metoda Split(), znana zapewne programistom Perla czy PHP, wykonuje czynno odwrotn
ni Join(). Rozbija mianowicie acuch znakw na podstawie podanego separatora i zapisuje
do tablicy kolejne elementy cigu.
Parametrem metody Split() jest lista separatorw w postaci tablicy typu char. Metoda
zwraca tablic typu string, w ktrej znajduj si odseparowane elementy:
string s1 = "Ala ma kota";
string[] Foo = s1.Split(new char[] { ' ' });
foreach (string Bar in Foo)
{
Console.WriteLine(Bar);
}
Console.Read();

Replace()

Metoda Replace() suy do podmieniania pewnych elementw w acuchu na nowe. Jej


uycie zaprezentowaem na pocztku tego rozdziau. Pierwszym parametrem musi by tekst,
ktry ma zosta zastpiony, a drugim nowa fraza:
string s1 = "Ala ma kota";
Console.WriteLine(s1.Replace("Ala", "Bartek"));

W wyniku wykonania takiego kodu na ekranie konsoli zostanie wywietlony napis Bartek
ma kota.

Remove()
Czasem moe zaistnie potrzeba usunicia danej frazy acucha. Wwczas z pomoc
przychodzi metoda Remove(). Pierwszym parametrem musi by indeks, od ktrego metoda
rozpocznie usuwanie tekstu. To jest waciwie jedyny wymagany przez ni parametr. Wtedy
usunie ona wszystko, co znajduje si za podanym indeksem. Istnieje jednak moliwo
okrelenia iloci tekstu, jaka ma zosta skasowana:
string s1 = "Ala ma kota";
Console.WriteLine(s1.Remove(0, 4));

Taki kod wywietli na konsoli napis ma kota.

SubString()
Metoda SubString pozwala na skopiowanie czci cigu, poczwszy od podanego znaku, i
zwrcenie skopiowanej wartoci. Jej budowa i zastosowanie s proste. Pierwszym
parametrem musi by pozycja, od ktrej rozpocznie si kopiowanie cigu znakw, a drugim
jest liczba znakw do skopiowania:
string s1 = "Ala ma kota";
/* wywietli napis: Ala */
Console.WriteLine(s1.Substring(0, 4));

ToUpper(), ToLower()
Zastosowanie tych metod jest proste. Pierwsza z nich (ToUpper()) zamienia wszystkie znaki
w acuchu na wielkie, a druga (ToLower()) na mae.

Trim(), TrimStart(), TrimEnd()


Jeeli na pocztku lub/i na kocu acucha znajduj si tzw. biae znaki (spacje, znaki nowej
linii), moemy je usun przy pomocy metody Trim(). Oto przykad:
string s1 = "
Ala ma kota\t\n";
Console.WriteLine(s1.Trim());

Po wykonaniu metody Trim() wartoci acucha bdzie Ala ma kota. Podobnie


TrimStart() usuwa biae znaki na pocztku acucha, a TrimEnd() na jego kocu.
\n w acuchu powoduje wstawienie w danym miejscu znaku nowej linii, natomiast \t to znak
tabulacji.

acuchy w WinForms
Co prawda biblioteka WinForms nie jest tematem tego rozdziau, ale chciabym tutaj
wspomnie o kilku jej tekstowych kontrolkach, ktre wykorzystuj mechanizmy acuchw.
Musisz sobie uwiadomi, e biblioteka WinForms, tak jak caa biblioteka klas .NET
Framework, opiera si na klasach. Komponenty (ktre rwnie s klasami) posiadaj
waciwoci oraz metody, z ktrych dua cz jest wanie typu System.String.
Podstawowe dwa komponenty suce do edycji tekstu to Textbox oraz RichTextBox.
Pierwsza z nich jest kontrolk jednoliniow suc do wpisywania prostych i krtkich notek.
Drugi komponent jest rozbudowan kontrolk tekstow miniedytorem tekstu. Moe
przechowywa dugie teksty, wywietla zawarto plikw
tekstowych.
Maksymaln dugo tekstu, jak moe przechowywa komponent RichTextBox, definiuje jego
waciwo MaxLength. Domylnie jest to 2147483647 znakw.
Komponent TextBox moe suy jako kontrolka wieloliniowa, lecz jest to moliwo rzadko
uywana. Jeeli mimo wszystko chcesz, aby komponent mg przechowywa wiele linii tekstu,
zmie waciwo Multiline na true.

Za wywietlanie tekstu w komponencie TextBox odpowiada waciwo Text. Moesz


przypisa tekst wywietlany w kontrolce za porednictwem okna Properties lub bezporednio
w kodzie:
TextBox1.Text = "Witam";

Waciwo Text jest typu System.String, wic automatycznie na jej wartoci moemy
operowa tak jak na zwykych zmiennych typu string.
W komponencie RichTextBox sprawa jest nieco bardziej skomplikowana, bo i on sam jest
bardziej skomplikowany. Zasadniczo mamy moliwo korzystania z waciwoci Lines,
ktra w rzeczywistoci jest tablic typu string. Zasadniczo lepszym rozwizaniem bdzie
korzystanie z waciwoci Text typu System.String. Naley pamita, i w takim wypadku
znak \n jest odpowiedzialny za przejcie do nowej linii.
Listing 9.1 prezentuje prosty program WinForms sucy do wczytywania zawartoci pliku
tekstowego. Oprcz wczytania zawartoci do komponentu RichTextBox program wywietla
ciek do wybranego pliku w komponencie TextBox (rysunek 9.1).
Rysunek 9.1. Program wywietlajcy zawarto plikw tekstowych

Listing 9.1. Program sucy do wczytywania zawartoci pliku tekstowego


using System;
using System.ComponentModel;

using System.Data;
using System.Windows.Forms;
using System.IO;
namespace WinForms
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// wywietlenie okna Otwrz
openFileDialog1.ShowDialog();
// przypisanie cieki do pliku
textBox1.Text = openFileDialog1.FileName;
// odczytanie zawartoci pliku
FileStream FileStr =
(FileStream)openFileDialog1.OpenFile();
StreamReader Reader = new StreamReader(FileStr);
richTextBox1.Clear();
// przypisanie tekstu
richTextBox1.Text = Reader.ReadToEnd();
}
}
}

Do zbudowania interfejsu aplikacji skorzystaem z kontrolki OpenFileDialog, Button,


TextBox, Panel oraz oczywicie RichTextBox.

Klasa StringBuilder
Klasa StringBuilder (w odrnieniu od System.String) reprezentuje zmienny acuch.
Stosowanie klasy StringBuilder jest szczeglnie zalecane w sytuacjach, w ktrych musisz
czsto modyfikowa acuchy w swoim programie (np. w ptli). Klasa StringBuilder
zostaa zdefiniowana w przestrzeni nazw System.Text.
Do utworzenia obiektu tej klasy mona skorzysta z jednego z kilku przecionych
konstruktorw. W zalenoci od rodzaju konstruktora bdziesz musia poda wartoci dla
nastpujcych parametrw:

capacity pocztkowy rozmiar tablicy znakw. Domylnie jest to 16 znakw. Jeeli


okae si, e rozmiar tablicy jest zbyt may, by zrealizowa dane zadanie, klasa
StringBuilder podwaja t liczb. Ze wzgldw wydajnociowych warto jest nada
ju w konstruktorze rozmiar, ktry bez zwikszania wystarczy do wykonania danej
operacji.
length dugo acucha przekazanego w konstruktorze (wyraona w znakach).
value pocztkowa warto (acuch znakw) przekazana do klasy.

Metody klasy StringBuilder


W tabeli 9.2 znajduj si najwaniejsze metody klasy StringBuilder.
Tabela 9.2. Najwaniejsze metody klasy StringBuilder
Metoda
Opis
Metoda suy do dodawania tekstu na kocu acucha utrzymywanego
Append()
przez klas StringBulider.
Metoda o podobnym dziaaniu do Append(). Jej dodatkowym atutem jest
AppendFormat()
moliwo przekazania tzw. specyfikatorw formatu.
AppendLine() Umoliwia wstawienie znaku koca linii.
Umoliwia wstawienie acuchowej reprezentacji danego obiektu we
Insert()
wskazanym miejscu.
Remove()
Usuwa wskazan cz acucha.
Replace()
Umoliwia podmian czci acucha.

Zastosowanie klasy StringBuilder


Wspominaem wczeniej o tym, i klasa StringBuilder (w przeciwiestwie do String)
reprezentuje zmienny acuch. W sytuacjach gdy musimy czsto modyfikowa warto
danego acucha, ze wzgldw wydajnociowych zalecane jest uywanie klasy
StringBuilder (listing 9.2).
Listing 9.2. Dodawanie wartoci do cigu tekstowego
using System;
using System.Diagnostics;
using System.Text;
namespace FooConsole
{

class Program
{
static void Main(string[] args)
{
Stopwatch Timer = new Stopwatch();
string Bar = "";
// rozpoczynamy odliczanie
Timer.Start();
for (int i = 0; i < 10000; i++)
{
Bar += "Ala ma kota ";
}
Timer.Stop();
Console.WriteLine("Czas wykonywania: {0} ms",
Timer.ElapsedMilliseconds);
Console.ReadLine();
}
}
}

Ten prosty program dodaje w ptli warto do zmiennej Bar. Czas potrzebny na wykonanie
10 000 tys. powtrze ptli to ok. 9000 milisekund (w przypadku mojego procesora), czyli 9
sekund.
Aby przyspieszy dziaanie aplikacji, mona skorzysta z klasy StringBuilder, ktra
znajduje si w przestrzeni System.Text. Uycie tej klasy spowoduje przyspieszenie dziaania
aplikacji a do 3 milisekund! Rnic w wydajnoci wida wic bardzo wyranie.
Aby skorzysta z klasy StringBuilder, naley utworzy jej instancj i oczywicie
doda przestrze nazw System.Text. Przerobiony program z listingu 9.2 zaprezentowany
zosta na listingu 9.3.
Listing 9.3. Program napisany z uyciem klasy StringBuilder
using System;
using System.Diagnostics;
using System.Text;
namespace FooConsole
{
class Program
{
static void Main(string[] args)
{
Stopwatch Timer = new Stopwatch();
StringBuilder Bar = new StringBuilder();

// rozpoczynamy odliczanie
Timer.Start();
for (int i = 0; i < 10000; i++)
{
Bar.Append("Ala ma kota ");
}
Timer.Stop();
Console.WriteLine("Czas wykonywania: {0} ms",
Timer.ElapsedMilliseconds);
Console.ReadLine();
}
}
}

Do sprawdzenia czasu wykonywania danego kodu uyem klasy Stopwatch, ktra znajduje si
w przestrzeni nazw System.Diagnostics. Podstawowe dwie metody tej klasy to Start(),
ktra rozpoczyna odliczanie, oraz Stop() oznaczajca zakoczenie pomiaru czasu. Czas
potrzebny na wykonanie danego kodu moemy odczyta z waciwoci ElapsedMilliseconds
(czas w milisekundach).

Formatowanie acuchw
Nieraz podczas czytania tej ksiki moge zauway, i stosowaem formatowanie
acuchw np.:
Console.WriteLine("Warto: {0}", Bar);

Chciabym na chwil zatrzyma si przy tym zagadnieniu i powici nieco wicej uwagi
moliwociom i korzyciom wynikajcym z uycia formatowania acuchw. Korzyci jest
przede wszystkim przejrzystszy kod. Ale nie tylko, bo jak si za chwil przekonasz, pojcie
formatowania acuchw to co wicej ni tylko podstawianie wartoci w okrelonym
miejscu cigu.
Owszem jest to podstawowe zadanie formatowania. W miejsce symboli zastpczych,
znajdujcych si pomidzy klamrami { oraz } w procesie wykonywania kodu zostan
podstawione wartoci odpowiadajce danemu indeksowi. Oto przykad:
Console.WriteLine("{0}, lat {1}, {2} cm wzrostu", "Jan
Kowalski", 42, 170);

Kady kolejny parametr metody WriteLine() jest numerowany od zera. Czyli parametr Jan
Kowalski ma indeks 0, parametr 42 indeks 1 itd. Taka instrukcja spowoduje wywietlenie
na ekranie konsoli tekstu Jan Kowalski, lat 42, 170 cm wzrostu.
Pomidzy klamrami naley umieci numer indeksu, nic nie stoi na przeszkodzie, aby takie
dane wywietli w dowolnej kolejnoci:
Console.WriteLine("{2} cm wzrostu, {1} lat, {0}, ", "Jan
Kowalski", 42, 170);

Jeeli chcemy wywietli w konsoli znak { lub }, musimy te symbole zapisa podwjnie:
Console.WriteLine("{{Ten tekst bdzie ujty w klamry}}");
Przeanalizujmy kolejny przykad. Symbol zastpczy moe zawiera parametr okrelajcy
rozmiar wyrwnania. Jest to liczba cakowita okrelajca szeroko danego pola oraz
wyrwnanie do prawej lub lewej strony. Spjrz na listing 9.4.
Listing 9.4. Symbole zastpcze z parametrem wyrwnania
using System;
namespace FooConsole
{
class Program
{
public struct User
{
public string Name;
public byte Age;
}
static void Main(string[] args)
{
User[] Bar = new User[2];
Bar[0].Name = "Jan Kowalski";
Bar[0].Age = 32;
Bar[1].Name = "Piotr Nowak";
Bar[1].Age = 56;
for (int i = 0; i < Bar.Length; i++)
{
Console.WriteLine("{0,-15} | {1,5}",
Bar[i].Name, Bar[i].Age
);
}
Console.ReadLine();
}

}
}

Program jest prosty. Zadeklarowaem w nim dwuelementow tablic struktur, a nastpnie w


ptli wywietliem jej zawarto. W symbolach zastpczych, po przecinku okreliem
wartoci wyrwnania. Warto -15 oznacza, i pole bdzie posiada rozmiar w wielkoci 15
znakw, a tekst bdzie wyrwnany do lewej. Warto dodatnia oznacza wyrwnanie do
prawej (zobacz rysunek 9.2).

Rysunek 9.2. Program prezentujcy waciwoci wyrwnania


Do tej pory prezentowaem zastosowanie formatowania na przykadzie metody WriteLine().
Chciabym zaznaczy, e taka moliwo jest dostpna dla kadego acucha typu string
dziki statycznej metodzie Format():
string Bar = String.Format("{0}, {1}", "Jan Kowalski", 42);

Specyfikatory formatw
Symbole zastpcze maj o wiele wiksze zastosowanie ni wspomniana moliwo
wyrwnywania wartoci. rodowisko .NET Framework definiuje zestaw symboli nazwanych
specyfikatorami formatw. Umoliwiaj one formatowanie wartoci liczbowych, daty i czasu
oraz wyliczeniowych, wedug wasnego upodobania.
Spjrz na poniszy kod:

int X = 110003242;
Console.WriteLine("{0:X}", X);

Oznacza on wywietlenie liczby, ktra jest zapisana w zmiennej X, w postaci szesnastkowej.


Symbol X jest specyfikatorem nakazujcym prezentacj danych w postaci szesnastkowej
(heksadecymalnej). Kolejny przykad prezentuje wywietlenie danej wartoci w postaci
walutowej:
int X = 10;
Console.WriteLine("{0:C}", X);

Po uruchomieniu takiego kodu na konsoli zostanie wywietlona warto walutowa 10,00 z.


W tabeli 9.3 znajduj si specyfikatory dla wartoci liczbowych.
Symbol waluty (z) oczywicie zaley od lokalizacji kraju, w jakim si znajdujemy. W USA taki
kod spowodowaby wywietlenie nastpujcej wartoci: $10.00.

Tabela 9.3. Specyfikatory formatw dla wartoci liczbowych


Symbol
Opis
C lub c Walutowy
D lub d Dziesitny
E lub e Naukowy (wykadniczy)
F lub f Staoprzecinkowy
G lub g Oglny
N lub n Liczbowy
P lub p Procentowy
R lub r Zaokrglony
X lub x Szesnastkowy

Przykad programu uywajcego specyfikatorw liczbowych znajduje si na listingu 9.5.


Listing 9.5. Przykad zastosowania specyfikatorw
using System;
namespace FooConsole
{
class Program
{
static void Main(string[] args)
{
System.Int32 Integer = 10;
System.Double Decimal = 10.25;
System.Double Percent = 0.50;

Console.WriteLine("Walutowy: {0:C}", Integer);


Console.WriteLine("Dziesitny: {0:D5}", Integer);
Console.WriteLine("Naukowy: {0:E}", Integer);
Console.WriteLine("Staoprzecinkowy: {0:F2}",
Integer);
Console.WriteLine("Oglny: {0:G2}", Integer);
Console.WriteLine("Liczbowy: {0:N2}", Integer);
Console.WriteLine("Procentowy: {0:P0}", Percent);
Console.WriteLine("Zaokrglony: {0:R1}", Decimal);
Console.WriteLine("Heksadecymalny: {0:X}",
Integer);
Console.ReadLine();
}
}
}

W programie zadeklarowaem 3 przykadowe zmienne, ktrym nadaem odpowiednie


wartoci. Przy pomocy odpowiednich specyfikatorw mog spreparowa wywietlane dane
wedug wasnych upodoba. Rezultat dziaania takiego programu prezentuje rysunek 9.3.

Rysunek 9.3. Przykad zastosowania specyfikatorw liczbowych


Zwr uwag, i symbol specyfikatora okrela si po znaku dwukropka, zaraz po numerze
indeksu symbolu zastpczego:
{:}
Moe zauwaye, i w kilku przypadkach po symbolu specyfikatora znajduje si cyfra. Ta
cyfra okrela precyzj wywietlanych danych. Np. poniszy kod oznacza, i liczba bdzie
wywietlana z precyzj dwch miejsc po przecinku:

Console.WriteLine("Staoprzecinkowy: {0:F2}", Integer);

Wasne specyfikatory formatowania


Kiedy okae si, e standardowe specyfikatory formatw nie speniaj naszych oczekiwa w
zakresie formatowania wartoci liczbowych, moemy stworzy i zastosowa wasne acuchy
formatowania. Przykadowo, przechowujemy w zmiennej liczb 100230786 i dla zwikszenia
jej czytelnoci chcemy j wywietli w postaci 100 230 786. Umiejtne wykorzystanie
niestandardowych specyfikatorw nie powinno sprawi problemu w realizacji tego zadania.
Oto kod:
System.Int32 Integer = 100230786;
Console.WriteLine("{0:### ### ###}", Integer);

Symbol # oznacza dowoln liczb. Innymi sowy, symbol ten zostanie zastpiony kolejn
cyfr z liczby przekazanej jako parametr. Kolejne przykady:
System.Double Integer = 123.345;
// 123.3
Console.WriteLine("{0:#.#}", Integer);
// 123.3450
Console.WriteLine("{0:#.###0}", Integer);

W tabeli 9.4 przedstawiono list niestandardowych specyfikatorw formatowania.


Tabela 9.4. Niestandardowe specyfikatory formatowania
Symbol
Opis
0
Symbol zastpczy dla zera
#
Symbol zastpczy dla cyfry
.
Kropka dziesitna
,
Separator tysica
%
Symbol zastpczy dla procentw
E0, E+0, E-0, e0, e+0, e-0 Notacja naukowa
'AAA', "AAA"
Stay acuch
;
Separator sekcji

Pozostae znaki

Wszystkie inne znaki wykorzystywane w acuchach formatowania

Tematyka niestandardowych specyfikatorw formatowania wykracza poza ramy tej ksiki.


Po wicej informacji
na ten temat odsyam do dokumentacji firmy Microsoft na temat platformy .NET.

Specyfikatory typw wyliczeniowych


Dla typw wyliczeniowych rodowisko .NET Framework udostpnia cztery specyfikatory
przedstawione w tabeli 9.5.
Tabela 9.5. Specyfikatory typw wyliczeniowych
Symbol
Opis
Wywietla wyliczenie w postaci acucha. Jeli nie jest to moliwe, wywietla
G lub g
warto cakowitoliczbow.
Wywietla wyliczenie w postaci acucha. Jeli nie jest to moliwe, wywietla
F lub f warto liczbow. Wywietla sum tych wartoci jeli jest to moliwe, acuchy
s konkatenowane i oddzielane przecinkami.
D lub d Wywietla zmienn typu wyliczeniowego w postaci wartoci liczbowej.
X lub x Wywietla zmienn typu wyliczeniowego w postaci wartoci szesnastkowej.

Na listingu 9.6 znajduje si przykadowy program prezentujcy w praktyce zastosowanie


specyfikatorw zaprezentowanych w tabeli 9.4.
Listing 9.6. Prezentacja specyfikatorw formatowania dla typw wyliczeniowych
using System;
namespace FooConsole
{
class Program
{
static void Main(string[] args)
{
DayOfWeek MyDays = DayOfWeek.Friday;
Console.WriteLine(MyDays.ToString("G"));
// Wywietli Friday.
Console.WriteLine(MyDays.ToString("F"));
// Wywietli Friday.
Console.WriteLine(MyDays.ToString("D"));
// Wywietli 5.
Console.WriteLine(MyDays.ToString("X"));
// Wywietli 00000005.

Console.ReadLine();
}
}
}

Przykad z listingu 9.6 operuje na typie wyliczeniowym DayOfWeek, a konkretnie na jego


elemencie Friday. Zwr rwnie uwag, w jaki sposb zastosowaem funkcj formatowania
acuchw. Naley wspomnie, i jest to moliwe rwnie w metodzie ToStrnig(). Zauwa
take, i w takim przypadku nie jest konieczne wpisywanie numeru indeksu. Jeeli metod
ToString() wywoam ze zmiennej MyDays, to formatowan wartoci jest ta znajdujca si
w tej zmiennej.
DayOfWeek jest typem wyliczeniowym znajdujcym si w przestrzeni System,
reprezentujcym dni tygodnia. Pierwszym elementem tego typu jest Sunday. Typ wyliczeniowy
jest numerowany od zera.

Typ System.Char
Typ char jzyka C#, bdcy odpowiednikiem klasy System.Char z CTS, suy do
przechowywania pojedynczych znakw typu Unicode. Warto zmiennej typu char musi by
zawarta w apostrofach:
char C = 'A';

Zmienna typu char nie moe zawiera wicej znakw ni jeden, gdy kompilator wskae bd
Too many characters in character literal.
Warto przypisywana do zmiennej typu char moe by zapisana w formie heksadecymalnej:
char
char
char
char

C1
C2
C3
C4

=
=
=
=

'\x0058';
(char)65;
'A';
'\t'; // znak tabulacji

W tabeli 9.6 zebrano kilka ciekawych metod zawartych w klasie System.Char.


Tabela 9.6. Metody klasy System.Char
Metoda
Opis
IsControl()
Zwraca true, jeeli znak zawarty w zmiennej jest znakiem kontrolnym (np.

\t czy \n).
IsDigit()
Zwraca true, jeeli znak zawarty w zmiennej jest liczb.
IsLetter()
Zwraca true, jeeli znak zawarty w zmiennej jest liter.
IsLower()
Zwraca true, jeeli znak zawarty w zmiennej jest ma liter.
IsSymbol()
Zwraca true, jeeli znak zawarty w zmiennej jest symbolem.
Zwraca true, jeeli znak zawarty w zmiennej mona zaliczy do tzw.
IsWhiteSpace()
biaych znakw (spacje, znaki nowej linii itp.).

Podsumowanie
Tematyka operowania na acuchach wbrew pozorom jest cakiem rozlega. Ja w tym
rozdziale opisaem podstawowe mechanizmy operowania na cigach znakw, w tym metody
suce do modyfikacji acuchw. Wspomniaem rwnie o klasie StringBuilder oraz o
formatowaniu acuchw, co z pewnoci pomoe Ci efektywnie wykorzysta moliwoci
platformy .NET w tym zakresie. Bardziej zaawansowanym operowaniem na acuchach jest
mechanizm wyrae regularnych (ang. regular expression), lecz ta tematyka wykracza poza
ramy niniejszej publikacji. Po wicej informacji na ten temat odsyam do dokumentacji
rodowiska .NET Framework.

Rozdzia 10
Biblioteka Windows Forms
Z dowiadczenia wiem, e pocztkujcym programistom najwicej frajdy sprawia wizualne
projektowanie aplikacji. Umieszczanie w programie wszelkiego rodzaju przyciskw,
kontrolek, czenie caoci w dziaajc aplikacj przynosi najwicej satysfakcji. W
poprzednich rozdziaach wikszo przykadw prezentowana bya na aplikacjach
konsolowych. Byo to celowe dziaanie, gdy uycie biblioteki wizualnej wymaga
zastosowania wikszej iloci kodu rdowego, konstrukcji, ktrych wczeniej nie znae.
Teraz, gdy nieobce jest Ci programowanie obiektowe, wiesz, czym s klasy i tablice, moemy
przej do programowania wizualnego. Odstawimy na bok aplikacje konsolowe, z ktrych
mao kto dzisiaj korzysta. Zajmiemy si budow programw na podstawie komponentw,
ktre s w dzisiejszych czasach podstaw do budowania aplikacji okienkowych, dziaajcych
w systemach Windows.
W gruncie rzeczy do tej pory powiedzielimy ju sobie cakiem sporo o aplikacjach
wizualnych. Potrafisz ju tworzy nowe projekty WinForms przy pomocy rodowiska Visual
C# Express Edition. W rozdziale 5. omwiem podstawowy kod aplikacji wizualnej, a w
rozdziale 6. wyjaniem, czym s zdarzenia. Wiesz ju, czym s waciwoci, w jaki sposb je

modyfikowa oraz jak obsugiwa zdarzenia. Brakuje Ci jedynie wiedzy praktycznej,


umoliwiajcej wykorzystanie podstawowych komponentw biblioteki Windows Forms.

Podzesp System.Windows.Forms
Przestrze nazw System.Windows.Forms znajduje si w podzespole o tej samej nazwie. Jest
to podstawowa biblioteka suca do projektowania wizualnego. Kompilujc dany projekt z
uyciem kompilatora csc.exe, w linii komend musisz zaznaczy, i ma on by kompilowany
wraz z podzespoem System.Windows.Forms.dll. Jest to bardzo niewygodne, dlatego zalecam
korzystanie z wizualnych rodowisk typu Visual C# Express Edition. Podczas tworzenia
nowego projektu WinForms odpowiednie podzespoy umoliwiajce jego prawidow
kompilacj s wczane automatycznie. Moesz to sprawdzi, rozwijajc ga References w
oknie Solution Explorer (rysunek 10.1).

Rysunek 10.1. Okno Solution Explorer


Gdy klikniemy dan ga oznaczajc podzesp i wybierzemy pozycj Properties z menu
podrcznego, wywietli si lista waciwoci podzespou. Wywietlone zostan m.in.
informacje o wersji oraz nazwie pliku danego podzespou.
O podzespoach .NET
opowiem w rozdziale 11.

Okno Object Browser


aden programista nie jest w stanie zapamita setek typw, jakie oferuje biblioteka klas
platformy .NET Framework. Podejrzewam, e sami twrcy czasami musz sign do
dokumentacji, aby przypomnie sobie nazw danej klasy. Dlatego rodowiska takie jak Visual
C# Express zapewniaj wsparcie, aby programowanie byo jeszcze prostsze i szybsze. Skoro
jestemy przy temacie podzespow, to warto wspomnie o oknie Object Browser. Umoliwia

nam ono przegldanie zawartoci danych podzespow, przestrzeni nazw i typw. Cao jest
adnie przedstawiona w sposb hierarchiczny (rysunek 10.2).

Rysunek 10.2. Okno Object Browser


Okno Object Browser moemy wywietli, jeli klikniemy dwukrotnie nazw podzespou w
gazi References lub przejdziemy do menu View/Other Windows/Object Browser.

Przestrze System.Windows.Forms
Najwaniejsz przestrzeni nazw w podzespole System.Windows.Forms.dll jest ta o takiej
samej nazwie (czyli System.Windows.Forms), wczana do kadego projektu typu
WinForms. Zawiera ona podstawowe klasy obsugi aplikacji, ale rwnie wszystkie klasy
komponentw. Musisz zda sobie spraw, e kady komponent jest jednoczenie klas, tak
sam, jak tworzylimy w rozdziale 5. Taka klasa zawiera elementy, o ktrych wspominaem
ju niejednokrotnie w tej ksice, czyli waciwoci, metody, zdarzenia, indeksery, typy
wyliczeniowe czy struktury. Oglnie, klasy biblioteki WinForms mona zakwalifikowa do
kilku kategorii, ktre omwiem w tabeli 10.1.
Tabela 10.1. Kategorie klasy przestrzeni System.Windows.Forms
Grupa
Opis

Komponenty

Kontrolki

Elementy
pozycjonowania

Menu i paski
narzdziowe

Okna dialogowe

O komponentach potocznie mwi si jak o kontrolkach wizualnych. W


rzeczywistoci mona powiedzie, e s to niewidoczne elementy
programu, klocki dziaajce w tle, ktre mog mie duy wpyw na
dziaanie aplikacji, lecz nie s czci jej interfejsu.
Kontrolki to inaczej komponenty wizualne. Dziki nim moesz tworzy
interfejs swojego programu. Do tej kategorii mona zaliczy przyciski,
listy rozwijane, pola edycyjne itp.
Biblioteka WinForms posiada kilka komponentw, ktre umoliwiaj
zarzdzanie elementami interfejsu. Jest to np. komponent umoliwiajcy
tworzenie zakadek czy komponent Panel, ktry suy tylko do tego, aby
grupowa w sobie inne kontrolki.
W programach wizualnych czsto zachodzi potrzeba utworzenia menu czy
paskw narzdziowych. W bibliotece WinForms mamy do dyspozycji
kilka komponentw, dziki ktrym jestemy w stanie zrealizowa to
zadanie.
S to ukryte komponenty, ktre odpowiadaj za wywietlanie okien
dialogowych obecnych w wielu aplikacjach biurowych (np. Otwrz,
Zapisz jako, Drukuj itp.).

Podstawowe klasy
Oczywicie caa biblioteka WinForms zbudowana jest na mechanizmie dziedziczenia oraz
polimorfizmu (jak mogoby by inaczej?), tote istnieje pewien zestaw klas bazowych dla
wszystkich komponentw, o ktrych powiniene wiedzie. Nie jest to wiedza niezbdna w
pracy nad komponentami, ale by moe ciekawostka dla osb, ktre chc wiedzie, jak to
wszystko jest zbudowane.

System.ComponentModel.Component
Klasa o dziwo nie znajduje si w przestrzeni System.Windows.Forms, lecz w
System.ComponentModel. Jest to podstawowa klasa dla wszystkich komponentw z
biblioteki WinForms, implementuje interfejs IComponent.
Waciwie nigdy nie bdziesz mia potrzeby uywania tej klasy samodzielnie, suy ona
jedynie jako klasa bazowa. Zawiera kilka elementw, m.in. metod Events() zwracajc list
zdarze przypisan do danego komponentu.

System.Windows.Forms.Control

W przestrzeni nazw System.Windows.Forms znajduje si klasa Control, ktra jest bazowa


dla kadej wizualnej kontrolki biblioteki WinForms. Co oznacza, e kontrolka jest wizualna?
Przede wszystkim kontrolki tego typu tworz interfejs aplikacji, s widoczne podczas jej
dziaania. Dodatkowo maj one moliwo reagowania na zdarzenia uytkownika
, takie jak naciskanie klawiszy klawiatury czy myszy.

Waciwoci klasy
Klasa Control zawiera cale mnstwo waciwoci publicznych oraz chronionych, ktre
dziedziczone s w klasach potomnych. Waciwoci te su do okrelania podstawowych
cech komponentw, takich jak pooenie, rozmiar, kolor itp. W tabeli 10.2 zawarem
podstawow list waciwoci, ktre z pewnoci bd przez Ciebie wykorzystywane
najczciej.
Tabela 10.2. Podstawowa lista waciwoci klasy Control
Waciwo
Opis
Waciwo typu bool okrela, czy dany komponent moe by rodzicem
AllowDrop
dla innych, tzn. czy za pomoc techniki przecignij i upu (ang. drag &
drop) mona w nim umieszcza inne kontrolki.
Okrela, w jaki sposb komponent ma zmienia swoje rozmiary, jeeli
Anchor
kontrolka znajdujca si w nim zmienia swj rozmiar.
Waciwo typu bool okrela, czy komponent ma mie rozmiar zgodny z
AutoSize
wymiarami treci, ktra si w nim znajduje.
BackColor
Umoliwia okrelenie koloru ta dla komponentu.
BackgroundImage Okrela obrazek, ktry bdzie uywany jako to komponentu.
Okrela kursor myszy, jaki ma by wywietlany, jeeli jej wskanik
Cursor
zostanie naprowadzony nad dan kontrolk.
Waciwo typu bool okrela, czy komponent bdzie wyczony
Enabled
(nieaktywna kontrolka jest specjalnie oznaczana, nie reaguje na
zdarzenia).
Umoliwia ustawienie czcionki, jaka bdzie wywietlana, jeeli kontrolka
Font
umoliwia wywietlanie tekstu.
ForeColor
Okrela kolor tekstu dla komponentu.
Okrela, czy w komponencie znajduj si inne kontrolki (tzw. kontrolki
HasChildren
potomne).
Height
Umoliwia odczytanie lub przypisanie szerokoci kontrolki.
Umoliwia okrelenie (w pikselach) odlegoci lewej krawdzi od obszaru
Left
formularza lub innego komponentu.
Location
Waciwo typu Point umoliwia okrelenie pooenia dla komponentu.
Name
Bardzo wana waciwo. Okrela nazw komponentu.
Umoliwia okrelenie lub odczytanie odstpw (minimalnej odlegoci)
Padding
wewntrz danej kontrolki.

Parent
Right
Size
TabIndex
Tag
Top
Visible
Width

Umoliwia odczytanie lub przypisanie kontrolki macierzystej dla danego


komponentu.
Okrela odstp (w pikselach) od prawej krawdzi obszaru formularza lub
innego komponentu.
Waciwo typu Point umoliwia okrelenie rozmiaru kontrolki.
Umoliwia przypisanie lub odczytanie kolejnoci przechodzenia do
danego komponentu przy pomocy klawisza Tab.
Waciwo moe by wykorzystana do dowolnych celw. Nie robi nic
konkretnego, moe przechowywa dowolne wartoci.
Okrela odlego (w pikselach) od grnej krawdzi formularza lub
komponentu.
Waciwo typu bool umoliwia okrelenie, czy komponent bdzie
widoczny, czy te nie.
Umoliwia odczytanie lub nadanie szerokoci dla komponentu.

Oczywicie chciabym zaznaczy, e to tylko niektre z waciwoci. Staraem si zamieci


w tabeli komponenty, ktre s w wikszoci wsplne dla wszystkich kontrolek wizualnych.
Nie wszystkie waciwoci prezentowane w tabeli 10.2 musz by widoczne na licie okna
Properties. Niektre mog by dostpne jedynie z poziomu kodu programu.

System.Windows.Forms.Application
Klasa Application ma due znaczenie dla naszego programu. Zawiera bowiem bardzo
wan metod odpowiedzialn za wywietlenie gwnego formularza projektu.
Wykonajmy mae wiczenie pozwalajce napisa najprostszy program, ktry bdzie
wykorzystywa bibliotek WinForms. Nasza aplikacja bdzie wywietlaa jedynie puste okno
formularza.
Formularzem nazywamy okno systemu Windows.
1. Z menu File wybierz pozycj New Project.
2. Zaznacz pozycj Empty project i nacinij przycisk OK.
3. W oknie Solution Explorer zaznacz, a nastpnie kliknij prawym przyciskiem pozycj
References.
4. Kliknij Add Reference.
5. W oknie, w zakadce .NET odszukaj pozycj System, a nastpnie kliknij przycisk OK.
6. W ten sam sposb dodaj pozycj System.Windows.Forms.
7. Z menu Project wybierz pozycj Add New Item.
8. Zaznacz pozycj Empty file i kliknij OK.

Utworzylimy w ten sposb nowy projekt, rcznie dodalimy odwoania do odpowiednich


podzespow (rysunek 10.3). Mamy przed sob pust zawarto pliku
rdowego.

Rysunek 10.3. Okno Solution Explorer z gazi prezentujc odwoania


Kod odpowiedzialny za utworzenie nowego formularza prezentuje listing 10.1.
Listing 10.1. Kod odpowiedzialny za wywietlenie nowego formularza
using System;
using System.Windows.Forms;
public class WinForms : Form
{
static void Main()
{
Application.Run(new WinForms());
}
}

Jak widzisz, kod jest prosty i krtki. Wywoujemy metod Run(), ktra jako parametru
wymaga instancji klasy Form. Klasa Form jest podstawow klas obsugi formularzy, dlatego
WinForms musi po niej dziedziczy.
Jeeli kod z listingu 10.1 oprcz okna formularza wywietla rwnie okno konsoli, musisz
sprecyzowa typ projektu. Z menu Project wybierz Properties. W zakadce Application
znajduje si lista rozwijana Output type. Wybierz z niej pozycj Windows Application.
Klasa Application ma wiele metod oraz waciwoci odpowiadajcych za obsug tzw.
komunikatw oraz wtkw. Ze wzgldu na ograniczenia objtoci tej ksiki, te tematy nie
zostan zaprezentowane. Klasa zawiera jednak dwie metody, ktre mog Ci si przyda w
dalszej pracy z jzykiem C#. Mam tu na myli metod Exit(), umoliwiajc zakoczenie
pracy oraz Restart(), ktra ponownie uruchamia aplikacj. Rozbudujmy nasz program z
listingu 10.1, dodajc do niego przyciski umoliwiajce zamknicie oraz ponowne
uruchomienie aplikacji.
Aby zrealizowa to zadanie, musimy umieci w programie kod tworzcy dwa komponenty

Button oraz przypisa im odpowiednie zdarzenia. Oczywicie moglibymy to prosto zrobi w

trybie projektowania w rodowisku Visual C#. Aby jednak utrudni sobie to zadanie,
wpiszmy odpowiedni kod rcznie.
Utworzenie nowego komponentu na formularzu polega zwyczajnie na utworzeniu nowej
instancji (np. komponentu Button) oraz ustawieniu odpowiednich waciwoci:
RestartBtn = new Button();
RestartBtn.Parent = this;
RestartBtn.Top = 10;
RestartBtn.Left = 10;
RestartBtn.Text = "Restartuj";
RestartBtn.Click += new System.EventHandler(OnRestareClick);

Kluczowe w tym kodzie jest przypisanie odpowiedniej wartoci dla waciwoci Parent.
Musimy okreli komponent macierzysty dla naszej kontrolki (bdzie to formularz), co
prezentuje listing 10.2.
Listing 10.2. Dynamiczne
tworzenie komponentw na formularzu
using System;
using System.Windows.Forms;
public class MyForm : Form
{
private Button RestartBtn;
private Button ExitBtn;
private void OnRestareClick(object sender, EventArgs e)
{
Application.Restart();
}
private void OnExitClick(object sender, EventArgs e)
{
Application.Exit();
}
public MyForm()
{
this.Text = Application.StartupPath;
RestartBtn = new Button();
RestartBtn.Parent = this;
RestartBtn.Top = 10;
RestartBtn.Left = 10;
RestartBtn.Text = "Restartuj";
RestartBtn.Click += new
System.EventHandler(OnRestareClick);

ExitBtn = new Button();


ExitBtn.Parent = this;
ExitBtn.Top = 10;
ExitBtn.Left = 120;
ExitBtn.Text = "Zamknij";
ExitBtn.Click += new
System.EventHandler(OnExitClick);
}
}
public class WinForms
{
static void Main()
{
Application.Run(new MyForm());
}
}

Rysunek 10.4 prezentuje dziaanie takiego programu. Zwr uwag, e pasek tytuowy
formularza zawiera ciek do uruchomionego programu. Odpowiada za to linia:
this.Text = Application.StartupPath;

Waciwo StartupPath zwraca pen ciek do programu.


W klasie Application znajduje si podobna waciwo ExecutablePath ktra rwnie
wywietla ciek do programu, wczajc w to jego nazw.

Rysunek 10.4. Dziaanie aplikacji WinForms

Przykad dziaania
Zostawmy na chwil such teori, a zajmijmy si tworzeniem graficznego interfejsu
uytkownika (GUI ang. Graphical User Interface). Pamitasz, jak w rozdziale 6. pisalimy
gr Kko i krzyyk? Wwczas caa gra odbywaa si w trybie konsolowym. Poniewa cae
engine gry jest ju gotowe, wystarczy zaprojektowa interfejs, ktry obsugiwaby jej klas.
Do roboty!

Przygotowanie klasy
Utwrz nowy projekt aplikacji Windows Forms. Zapisz go na dysku, po czym skopiuj modu,
w ktrym znajdowaa si klasa GomokuEngine, do katalogu z projektem. Naley doda ten
modu do projektu:
1. Prawym przyciskiem kliknij w obrbie okna Solution Explorer.
2. Wybierz pozycj Add/Existing Item.
3. Wska plik, w ktrym znajduje si kod gry, i nacinij OK.

Projektowanie interfejsu
Zacznijmy od tytuu okna gwnego. Domylny napis Form1 nie odpowiada nazwie naszej
gry. W oknie Properties moesz zmieni warto waciwoci Text, wpisujc wasn nazw
okna np. Gomoku Win.
Jeeli okno Properties nie wywietla prawidowo waciwoci formularza, by moe nie jest
on zaznaczony. Kliknij wic okno formularza, aby wywietli list jego waciwoci.
Kolejna rzecz, jakiej potrzebujemy, to plansza do gry. W celu jej utworzenia proponuj
umieci na formularzu 9 komponentw typu Button. Na razie umie jeden komponent,
gdy musimy ustali waciwoci jego wygldu. Zacznijmy od rozmiaru. W oknie Properties
odnajd waciwo Size i rozwi jej ga. Warto Width zmie na 50, a Height na 30. Teraz
przyszed czas na zmian czcionki. Rozwi ga Font i zmie nastpujce pozycje:

Name (nazwa czcionki) Arial Black;


Size (rozmiar czcionki) 14.

Teraz uywajc kombinacji klawiszy Ctrl+C (kopiuj) oraz Ctrl+V (wklej) moesz

dziewi razy wklei skopiowany wczeniej komponent Button. Oszczdzi Ci to czasu na


modyfikacj waciwoci kolejnych przyciskw.
Potrzebujemy jeszcze dwch kontrolek tekstowych, w ktrych uytkownicy bd mogli
wpisa swe imiona. Moja wersja interfejsu uytkownika zaprezentowana zostaa na rysunku
10.5.

Rysunek 10.5. Interfejs gry


Ramk otaczajc podstawowe elementy uzyskaem, umieszczajc na formularzu komponent
GroupBox. Dodatkowo ustawiem waciwo Text tego komponentu na warto pust.
Etykiety, w ktrych znajduj si napisy Gracz #1 oraz Gracz #2, to komponenty Label
ich jedynym zadaniem jest przechowywanie krtkich tekstw.

Rozwizania programistyczne
W kadym projekcie tworzenie interfejsu aplikacji to dopiero pocztek. Naszym zadaniem
jest odpowiednia obsuga klasy Gomoku, gdy cay silnik gry mamy ju gotowy. Zacznijmy
od rzeczy najprostszej, czyli oprogramowania zdarzenia Click komponentu btnStart (start
gry):
private void btnStart_Click(object sender, EventArgs e)
{
if (textPlayer1.Text.Length > 0 && textPlayer2.Text.Length
> 0)
{
GomokuObj.Player1 = textPlayer1.Text;
GomokuObj.Player2 = textPlayer2.Text;
GomokuObj.Start();
Ready = true;
}
}

W instrukcji warunkowej sprawdzamy, czy uytkownik wpisa imiona graczy. Nastpnie


imiona przypisujemy do odpowiednich waciwoci klasy, po czym wywoujemy metod
Start(). Na samym kocu znajduje si instrukcja zmieniajca warto pola Ready na true.
To pole jest uywane na wewntrzny potrzeby naszego programu (wykorzystywane jest w
dalszej czci kodu).
Zakadam, e nie masz problemu z generowaniem i obsug zdarze. Gdyby jednak mia
odsyam do wczeniejszych fragmentw ksiki.
Teraz pomylmy chwil nad plansz do gry. Mamy 9 przyciskw, ktrych zdarzenia trzeba
oprogramowa. Ale eby nie generowa 9 procedur zdarzeniowych, wygenerujemy jedn i
przypiszemy j do pozostaych przyciskw.
Skoro jedna metoda bdzie obsugiwa 9 przyciskw, to skd mamy wiedzie, ktry przycisk
zosta przez uytkownika wcinity. Po lekturze poprzednich rozdziaw powiniene si
domyli, e wykorzystamy do tego parametr sender. Pamitaj jednak, e do metody Set()
klasy Gomoku musimy przekaza wsprzdne pola (przycisku), ktre wybra uytkownik. Do
tego wykorzystamy waciwo Tag, ktra obecna jest we wszystkich komponentach.
Waciwo Tag typu System.Object moe przechowywa dowolne dane na uytek
programu. W naszym przypadku bdzie przechowywa wsprzdne danego przycisku.
Otwrz plik przechowujcy ustawienia formularza (u mnie jest to Form1.Designer.cs) i
odnajd kod, ktry odpowiedzialny jest za utworzenie przyciskw:
this.button1.Font = new System.Drawing.Font("Arial Black",
14F, System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.button1.Location = new System.Drawing.Point(12, 12);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(50, 30);
this.button1.TabIndex = 0;
this.button1.TextAlign =
System.Drawing.ContentAlignment.TopCenter;
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new
System.EventHandler(this.button1_Click);
this.button1.Tag = new System.Drawing.Point(1, 1);

Pogrubiona linia oznacza przypisanie wartoci do waciwoci Tag. Wpisaem j rcznie.


Zrb tak z kadym przyciskiem, zmieniajc jednak wsprzdne, czyli nastpnie bdzie to: (1,
2), (1, 3), (2, 1), (2, 2) itd.
Oto kod metody zdarzeniowej dla komponentw typu Button:
private void button1_Click(object sender, EventArgs e)
{
if (!Ready)

{
MessageBox.Show("Podaj imiona graczy!");
return;
}
Point p = new Point();
p = (Point)(sender as Button).Tag;
(sender as Button).Text = GomokuObj.Active.Type ==
FieldType.ftCircle ? "O" : "X";
GomokuObj.Set(p.X, p.Y);
if (GomokuObj.Winner)
{
MessageBox.Show(String.Format("Brawo dla {0}",
GomokuObj.Active.Name), "Wygrana!");
}
}

Pierwsza instrukcja sprawdza, czy pole Ready ma warto false. Jeeli tak, wywietla
ostrzeenie, i nie podano imion graczy. Nastpnie tworzymy now struktur Point i
przypisujemy do niej informacje
z waciwoci Tag nacinitego przycisku. Kolejna instrukcja umieszcza na przycisku symbol
odpowiadajcy danemu graczowi, po czym wywouje metod Set() z odpowiednimi
parametrami. Na samym kocu instrukcja sprawdza, czy nie zakoczy gry, jeeli ktry z
uytkownikw wygra.
Omwienia wymaga rwnie metoda zdarzeniowa przycisku btnNewGame:
private void btnNewGame_Click(object sender, EventArgs e)
{
for (int i = 0; i < Controls.Count; i++)
{
if (Controls[i] is Button && (Controls[i] as
Button).Tag != null)
{
(Controls[i] as Button).Text = "";
}
}
GomokuObj.NewGame();
}

Jej zadaniem jest wywoanie metody NewGame(), lecz wczeniej czyci wartoci Text
przyciskw planszy. Waciwo Controls zwraca informacje na temat kontrolek
umieszczonych w tym przypadku na formularzu. W ptli sprawdzamy kad kontrolk i
jeeli jest typu Button oraz jej waciwo Tag nie jest pusta, czycimy zawarto jej
waciwoci Text. Kod rdowy gry (gwnego moduu aplikacji) zawarty jest na listingu
10.3.

Rysunek 10.6 prezentuje gr w trakcie dziaania.


Listing 10.3. Kod rdowy gry Gomoku
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace Gomoku
{
public partial class Form1 : Form
{
private GomokuEngine GomokuObj;
private bool Ready = false;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
GomokuObj = new GomokuEngine();
}
private void btnStart_Click(object sender, EventArgs
e)
{
if (textPlayer1.Text.Length > 0 &&
textPlayer2.Text.Length > 0)
{
GomokuObj.Player1 = textPlayer1.Text;
GomokuObj.Player2 = textPlayer2.Text;
GomokuObj.Start();
Ready = true;
}
}
private void btnNewGame_Click(object sender, EventArgs
e)
{
for (int i = 0; i < Controls.Count; i++)
{
if (Controls[i] is Button && (Controls[i] as

Button).Tag != null)
{
(Controls[i] as Button).Text = "";
}
}
GomokuObj.NewGame();
}
private void button1_Click(object sender, EventArgs e)
{
if (!Ready)
{
MessageBox.Show("Podaj imiona graczy!");
return;
}
Point p = new Point();
p = (Point)(sender as Button).Tag;
(sender as Button).Text = GomokuObj.Active.Type ==
FieldType.ftCircle ? "O" : "X";
GomokuObj.Set(p.X, p.Y);
if (GomokuObj.Winner)
{
MessageBox.Show(String.Format("Brawo dla {0}",
GomokuObj.Active.Name), "Wygrana!");
}
}
}
}

Rysunek 10.6. Gra Gomoku w trakcie dziaania

Technika przecignij i upu

Technika przecignij i upu (ang. Drag & Drop) z powodzeniem stosowana jest w wielu
programach komputerowych. Sam z pewnoci nieraz z niej skorzystae. Przykadowo:
przenoszenie plikw za pomoc kliknicia i przesuwania kursora myszy jest ju
wykorzystaniem techniki przecignij i upu! Dziki temu obsuga aplikacji jest
przyjemniejsza i bardziej intuicyjna. Poniewa obsuga tej techniki w rodowisku .NET
Framework nie naley do najprostszych, postanowiem zaprezentowa przykady jej
wykorzystania.
W naszej przykadowej aplikacji uytkownik bdzie mia moliwo przenoszenia elementw
pomidzy dwoma komponentami ListBox. Kontrolka ListBox suy do przechowywania
kolejnych linii tekstu (pozycji). Rysunek 10.7 prezentuje dziaanie takiej aplikacji.

Rysunek 10.7. Aplikacja prezentujca dziaanie techniki przecignij i upu


Pierwsze, co musimy zrobi, to zmieni warto waciwoci AllowDrop na true w obu
kontrolkach ListBox. Waciwo ta okrela, czy dany komponent moe obsugiwa dane
przychodzce przy pomocy techniki drag & drop.
Poniewa zaley nam na tym, aby mona byo dowolnie przemieszcza dane, procedury
zdarzeniowe bd takie same dla obydwu kontrolek. Zacznijmy od oprogramowania
zdarzenia MouseDown, ktre wystpuje w momencie kliknicia klawiszem myszy w obrbie
kontrolki:
private void lb_MouseDown(object sender, MouseEventArgs e)
{
DragDropEffects effects =
(sender as ListBox).DoDragDrop((sender as
ListBox).SelectedItem, DragDropEffects.Move);

if (effects != DragDropEffects.None)
{
(sender as ListBox).Items.RemoveAt((sender as
ListBox).SelectedIndex);

}
}

Najwaniejsza w tym kodzie jest metoda DoDragDrop(), ktra uruchamia proces przecigania
danych. Pierwszym parametrem s dane, ktre bd przecigane, czyli w naszym przypadku
zaznaczony element w ListBox. Drugim parametrem jest tryb przenoszenia. Metoda
zwraca obiekt klasy DragDropEffects.
Sami musimy zadba o usuwanie elementu z komponentu rdowego. Realizuje to metoda
RemoveAt() z waciwoci Items. W parametrze tej metody musimy poda numer pozycji do
usunicia. Usuwamy zaznaczon pozycj, wic skorzystamy z waciwoci SelectedIndex,
ktra dostarcza nam informacji o zaznaczonej pozycji.
Waciwo Items komponentu typu ListBox zwraca list pozycji (elementw) komponentw
w formie kolekcji.
Kolejnym krokiem bdzie obsuenie zdarzenia DragEnter, ktre wykonywane jest w
momencie, gdy kursor myszy z przeciganym elementem wejdzie w obszar kontrolki:
private void lb_DragEnter(object sender, DragEventArgs e)
{
StatusBar.Text = "Przesyam dane...";
if (e.Data.GetDataPresent(typeof(System.String)))
{
e.Effect = DragDropEffects.Move;
}
}

Parametr e tej procedury zdarzeniowej zawiera informacje o przesyanych danych.


Akceptujemy jedynie dane typu System.String, wic musimy upewni si, czy mamy do
czynienia z prawidowym typem.
Operator typeof zwraca informacje o typie danych w postaci obiektu System.Type.
Dodatkowo umieszczamy na pasku statusu tekstow informacj o wykonywanych
czynnociach. Skorzystaem z komponentu StatusStrip, do ktrego dodaem etykiet
(rysunek 10.8) i nazwaem j StatusBar.

Rysunek 10.8. Dodawanie etykiety w komponencie StatusStrip


Ostatnie zdarzenie, ktre musimy obsuy, to DragDrop, wykonywane w momencie
zwolnienia klawisza myszy. Procedura zdarzeniowa musi zawiera kod informujcy aplikacj
o tym, co zrobi z dostarczonymi danymi:
private void lb_DragDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(System.String)))
{
Object item =
(object)e.Data.GetData(typeof(System.String));
(sender as ListBox).Items.Add(item);
StatusBar.Text = "";
}
}

Pierwsza instrukcja suy do sprawdzenia poprawnoci danych, podobnie jak w procedurze


zdarzeniowej lb_DragEnter(). Nastpnie odczytujemy dane i przypisujemy je do obiektu
item, ktrego zawarto wstawiania jest do listy w komponencie typu ListBox. Listing 10.4
zawiera kod rdowy aplikacji.
Listing 10.4. Przykadowy program korzystajcy z techniki Drag & Drop
using System;
using System.Collections.Generic;

using
using
using
using
using

System.ComponentModel;
System.Data;
System.Drawing;
System.Text;
System.Windows.Forms;

namespace DockApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnAdd_Click(object sender, EventArgs e)
{
int selectedItem = lbSource.SelectedIndex;
object item = lbSource.SelectedItem;
if (selectedItem > 0)
{
lbSource.Items.RemoveAt(selectedItem);
lbDest.Items.Add(item);
}
}
private void btnRemove_Click(object sender, EventArgs
e)
{
int selectedItem = lbDest.SelectedIndex;
object item = lbDest.SelectedItem;
if (selectedItem > 0)
{
lbDest.Items.RemoveAt(selectedItem);
lbSource.Items.Add(item);
}
}
private void lb_DragEnter(object sender, DragEventArgs
e)
{
StatusBar.Text = "Przesyam dane...";
if (e.Data.GetDataPresent(typeof(System.String)))
{
e.Effect = DragDropEffects.Move;
}
}

private void lb_DragLeave(object sender, EventArgs e)


{
StatusBar.Text = "";
}
private void lb_MouseDown(object sender,
MouseEventArgs e)
{
DragDropEffects effects =
(sender as ListBox).DoDragDrop((sender as
ListBox).SelectedItem, DragDropEffects.Move);

if (effects != DragDropEffects.None)
{
(sender as ListBox).Items.RemoveAt((sender as
ListBox).SelectedIndex);
}
}
private void lb_DragDrop(object sender, DragEventArgs
e)
{
if (e.Data.GetDataPresent(typeof(System.String)))
{
Object item =
(object)e.Data.GetData(typeof(System.String));
(sender as ListBox).Items.Add(item);
StatusBar.Text = "";
}
}
}
}

Tworzenie menu
W wielu aplikacjach, nie tylko tych przeznaczonych dla systemu Windows, obecne jest menu
gwne. Nie musz Ci chyba tumaczy, czym jest menu, ale warto wspomnie o tym, jak si
je tworzy w rodowisku Visual C# Express Edition. Wbrew pozorom, w rodowisku
wizualnym projektowanie menu jest bardzo proste i przyjemne.
Za wywietlanie i obsug menu odpowiada komponent MenuStrip. Umie ten komponent
na formularzu. Zostanie on umieszczony na pasku podrcznym (rysunek 10.9), gdy naley
do komponentw niewizualnych.

Rysunek 10.9. Komponent MenuStrip umieszczony na pasku podrcznym


Po zaznaczeniu tego komponentu u gry formularza wywietlona zostanie pozycja z napisem
Type here, suca do wpisania etykiety pierwszego menu (rysunek 10.10).

Rysunek 10.10. Projektowanie menu


Po klikniciu tej pozycji kursor zostanie zmieniony, co da nam moliwo wpisania etykiety.
Po naciniciu klawisza Enter rodowisko da nam moliwo dodania kolejnego elementu
menu.
Rysunek 10.11 prezentuje moj propozycj menu, ktre zawiera podstawowe elementy kadej
aplikacji biurowej (menu Plik, Edycja, Pomoc).

Rysunek 10.11. Menu w trakcie edycji


Separator w menu mona utworzy, wpisujc znak -.

Waciwoci menu
Menu moesz rwnie edytowa, jeli klikniesz prawym przyciskiem ikon komponentu i
wybierzesz pozycj Edit Items. Okno Items Collection Editor daje moliwo okrelenia
szczegowych waciwoci dla menu, z ktrych najwaniejsze opisaem w tabeli 10.3.
Tabela 10.3. Waciwoci pozycji menu
Waciwo
Opis
BackColor
Umoliwia okrelenie koloru ta pozycji menu.
BackgroundImage
Umoliwia okrelenie obrazka ta.
BackgroundImageLayout Okrela pozycj i zachowanie obrazka ta.
Zmiana tej waciwoci na true spowoduje pojawienie si przy
Checked
pozycji zaznaczenia (charakterystycznego ptaszka).
Okrela sposb wywietlania pozycji (tekst i obrazek, tekst lub
DisplayStyle
obrazek).
Image
Umoliwia okrelenie ikony, ktra bdzie wywietlana w pozycji.
ImageAlign
Okrela pooenie obrazka.
ImageTransparent
Umoliwia okrelenie koloru ta obrazka (jeeli jest przezroczysty).
RightToLeft
Okrela, czy pozycje w menu bd wyrwnane do prawej.
TextAlign
Umoliwia szczegowe okrelenie pozycji tekstu w menu.
TextDirection
Okrela kierunek wywietlania tekstu (domylnie poziomy).
TextImageRelation
Umoliwia okrelenie pooenia tekstu wzgldem obrazka.
AutoSize
Dopasowanie rozmiaru elementu do jego zawartoci.

AutoToolTip
CheckOnClick
ToolTipText
DropDownItems
ShortcutKeys
ShowShortcutKeys

Automatyczne wywietlanie dymkw podpowiedzi po


naprowadzeniu kursora nad pozycj.
Kliknita pozycja zostanie zaznaczona, jeeli warto tej
waciwoci to true.
Zawarto dymka podpowiedzi.
Umoliwia edycj elementw podmenu.
Umoliwia okrelenie skrtu klawiaturowego dla elementu.
Okrela, czy obok pozycji menu wywietla skrt klawiaturowy
(jeeli zosta okrelony).

Ikony dla menu


Aby nasze menu byo bardziej intuicyjne i adne, dodamy do niego ikony, ktre bd zdobi
jego pozycje. Obrazek moe by zapisany w formacie *.bmp, *.gif, *.jpeg lub *.png.
Nowy obrazek moemy doda bardzo prosto, korzystajc z waciwoci Image. Po jej
zaznaczeniu wywietlona zostanie maa ikona gdy j naciniemy otwarte zostanie okno
Select Resource (rysunek 10.12).

Rysunek 10.12. Okno Select Resource


Zaznacz pozycj Project resource file, a nastpnie kliknij przycisk Import. Zostaniesz
poproszony o wskazanie pliku z obrazem, ktry zostanie dodany do listy. Po naciniciu OK
dany obrazek zostanie przypisany do pozycji menu.
Prostszym rozwizaniem bdzie skorzystanie z opcji podrcznego menu. Po klikniciu danej

pozycji menu w trakcie projektowania wybierz pozycj Set Image, ktra rwnie wywietli
okno Select Resource (rysunek 10.13).

Rysunek 10.13. Opcje podrcznego menu w trakcie projektowania


Obrazki przypisane do menu bd zapisane w tzw. zasobach programu. Zasoby (takie jak np.
obrazy, teksty, dwiki) s doczane do pliku wykonywalnego w trakcie kompilacji. Nie ma
wic potrzeby doczania do aplikacji dodatkowych plikw graficznych czy dwikowych.
Informacje o zasobach s przechowywane w formacie XML, w pliku Resources.resx, ktry
znajduje si w podkatalogu Properties Twojego projektu.
Jeeli zajdzie taka potrzeba, moesz odczyta informacje zwizane z obrazkami przypisanymi
do menu. Oczywicie przy pomocy waciwoci, ktre mog zosta wywietlone w oknie
Properties. W trakcie projektowania wybierz dla przykadu menu Plik/Nowy (tak jak to
pokazaem na rysunku 10.13), kliknij go prawym przyciskiem myszy i wybierz pozycj
Properties.
W oknie Properties, znajduje si waciwo Image (to ju wiesz), ktrej pozycje mog
zosta rozwinite. Uzyskasz wwczas informacje o ikonie przypisanej do danego menu
(rysunek 10.14).

Rysunek 10.14. Informacje o ikonie przypisanej do pozycji menu


Pozycje w oknie Properties oznaczone kolorem szarym przeznaczone s jedynie do odczytu,
nie mona ich modyfikowa.

Skrty klawiaturowe
Aby nada naszemu menu bardziej profesjonalny charakter, naley niektrym pozycjom
przypisa skrty klawiaturowe. Umoliwi to szybkie wybranie danej pozycji przy pomocy
klawiatury, co znacznie uatwi prac z aplikacj. Do ustawiania skrtu klawiaturowego suy
waciwo ShortcutKeys. Dziki niej moemy wybra dowoln kombinacj klawiaturow,
ktra spowoduje wybranie danej pozycji (rysunek 10.15).

Rysunek 10.15. Ustawianie skrtu klawiaturowego


Waciwo ShowShortcutKeys okrela, czy skrt bdzie wywietlony obok etykiety pozycji
w menu. Zalecane jest pozostawienie wartoci domylnej (czyli true).
Inn zalecan technik jest umieszczenie w etykiecie znaku &. Przykadowo, zaznacz menu
Plik i przejd do waciwoci Text. Zmie jej warto na &Plik. Spowoduje to dodanie pod
liter P charakterystycznego podkrelenia. Dziki temu uytkownik bdzie mg przej do tej
pozycji menu, uywajc skrtu Lewy Alt+P.

Menu podrczne

Opcje menu podrcznego wywietlane s w momencie, gdy uytkownik kliknie prawym


przyciskiem myszy w obrbie danego elementu. Do tworzenia menu podrcznego suy
komponent ContextMenuStrip. Pozycje tego menu tworzone s na takiej samej zasadzie jak
we wspomnianym komponencie MenuStrip.
Jedyne, o czym warto wspomnie, to przypisywanie menu podrcznego do danego elementu.
Wikszo kontrolek wizualnych posiada rozwijan waciwo ContextMenuStrip, ktra
suy do kojarzenia danego elementu z menu podrcznym. Jeeli wic chcesz, aby menu
podrczne byo wywietlane po klikniciu prawym przyciskiem myszy w obrbie formularza,
zaznacz formularz i odszukaj waciwo ContextMenuStrip. Z listy wybierz menu, ktre ma
obsugiwa formularz.

Paski narzdziowe
Paski narzdziowe (ang. tool bar) to rwnie czsto spotykany element interfejsu aplikacji.
Zawiera przyciski z najczstszymi opcjami programu ozdobione ikon. Podczas
projektowania aplikacji w menu powiniene umieszcza wszystkie opcje, jakie oferuje
program, a na pasku jedynie te najczciej uywane.
W bibliotece WinForms do tworzenia paskw narzdziowych moemy wykorzysta
komponent ToolStrip. Umieszczony na formularzu, automatycznie dopasowuje si do jego
grnej krawdzi. Zwr uwag na ma ikon wywietlan na pasku w trybie projektowania
(rysunek 10.16).

Rysunek 10.16. Pasek narzdziowy w trybie projektowania


Rozwinicie jego pozycji wywietla spis komponentw, ktre moemy umieci na pasku.
Wybr jest na tyle spory, e powinien zaspokoi Twoje wymagania. Najczciej bowiem na
pasku umieszczamy przyciski (Button) oraz separatory (Separator).

Przypisywanie obrazkw do przyciskw wyglda tak samo jak w przypadku menu. Po


klikniciu przycisku prawym klawiszem myszy naley wybra pozycj Set Image.
Projektowanie paskw narzdziowych i menu jest podobne, a to za spraw waciwoci, ktre
s bardzo zblione w obu przypadkach. Wierz, e poradzisz sobie z zaprojektowaniem menu
pasujcego do aplikacji, wic postanowiem pomin ponowne opisywanie waciwoci
komponentu ToolStrip.

Pasek statusu
Pasek statusu rwnie czsto goci we wszelkiego rodzaju aplikacjach okienkowych. Jest to
pasek, ktry automatycznie dopasowuje si do dolnej krawdzi okna i suy do wywietlania
podstawowych informacji w trakcie dziaania programu.
W Windows Forms za wywietlanie paska statusu odpowiada komponent StatusStrip. Po
jego wywietleniu, podobnie jak w przypadku paskw narzdziowych, moemy umieci na
nim wiele elementw, w tym etykiet czy pasek postpu (rysunek 10.17).

Rysunek 10.17. Pasek statusu w trakcie projektowania


Oczywicie najczstszym zastosowaniem paska jest wywietlanie informacji tekstowych, std
pewnie pozycja StatusLabel bdzie t, ktr bdziesz wybiera najczciej.

Zakadki
Czsto gdy mamy rozbudowany interfejs, zachodzi potrzeba podzielenia go na kilka czci.
wietnie nadaje si do tego mechanizm zakadek, ktre moemy tworzy dziki kontrolce
TabControl.
TabControl (rysunek 10.18) jest komponentem-rodzicem. Oznacza to, e w jego wntrzu

moemy umieszcza inne komponenty.

Rysunek 10.18. Komponent TabControl


TabControl domylnie posiada dwie zakadki, ktre moemy dodawa lub usuwa.

Wystarczy je klikn i wybra z menu podrcznego pozycj Add lub Remove. Jeeli chcesz
edytowa waciwoci danej zakadki, musisz wybra waciwo TabPages z okna
waciwoci Properties. Waciwo TabPages oferuje wiele opcji, dziki ktrym bdziesz
mg ustawi cechy kadej zakadki z osobna. Kilka interesujcych waciwoci komponentu
TabControl prezentuje tabela 10.4.
Tabela 10.4. Najwaniejsze waciwoci komponentu TabControl
Waciwo
Opis
Okrela pooenie zakadek. Domylnie s one pooone u gry (Top), ale
Alingment moliwe s rwnie wartoci: Bottom (d), Left (lewa strona), Right (prawa
strona).
Okrela sposb prezentacji zakadek. Mona wybra warto Button (przyciski)
Appearance
lub Flat Buttons (paskie przyciski).
Jeeli waciwo ma warto true, zakadka zostanie podwietlona, gdy
HotTrack
naprowadzimy na ni kursor.
Okrela, czy tytu zakadki moe mieci si w wicej ni jednej linii (domylnie
Multiline
false nie moe).
SizeMode Tryb rozmiaru zakadek. Domylnie szeroko zakadki dopasowywana jest do

treci. Mona ustawi wartoci: FillToRight (wyrwnanie do prawej) lub Fixed


(kada zakadka bdzie miaa tak sam szeroko).
Kontrolka TabControl jest najczciej dopasowywana do szerokoci formularza albo
komponentu-rodzica. Jeeli chcesz, aby rozmiar komponentu by dopasowany do obiektu
macierzystego, wystarczy zmieni waciwo Dock na Fill.

Kontrolki tekstowe
Osobicie dziel kontrolki tekstowe na jednowierszowe i wielowierszowe. Wielowierszow
kontrolk edycyjn jest komponent RichTextBox. Jednowierszow kontrolk edycyjn jest
np. TextBox. Do czsto uywanych kontrolek mona zaliczy rwnie:

ComboBox,
ListBox.

Ta pierwsza prezentuje kolejne linie tekstu w formie listy rozwijanej. Komponent ListBox
dziaa bardzo podobnie, tyle e kolejne linie prezentowane s w formie listy nierozwijanej.
Na nowej zakadce komponentu TabControl umieciem list rozwijan (ComboBox) oraz
komponent WebBrowser (rysunek 10.19). Ten drugi suy do wywietlania stron
internetowych.

Rysunek 10.19. Komponent ComboBox umieszczony na zakadce


Komponent ComboBox bdzie umoliwia wpisanie adresu strony, ktra ma zosta
wywietlona w komponencie typu WebBrowser. Wpisane adresy stron WWW bd
zapamitywane i umieszczane na licie rozwijanej.
Oprogramujmy wic zdarzenie KeyDown komponentu ComboBox. Musimy wychwyci moment
nacinicia klawisza Enter. Jeeli to nastpi, adujemy stron o okrelonym URL, a sam adres

dodajemy do listy rozwijanej (rysunek 10.20).

Rysunek 10.20. Lista adresw WWW w komponencie ComboBox


Procedura zdarzeniowa zdarzenia KeyDown wyglda nastpujco:
private void comboBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyValue == (int)Keys.Enter)
{

IE.Navigate(comboBox1.Text);
comboBox1.Items.Add(comboBox1.Text);
}
}

Jeeli warunek zostanie speniony, wywoana bdzie metoda Navigate(), ktra nakazuje
przej do okrelonego URL wpisanego w komponencie ComboBox. Kolejna instrukcja dodaje
wpisany adres do listy. Musimy si w niej odwoa do waciwoci Items, ktra reprezentuje
kolekcj. Za dodanie nowej pozycji odpowiada metoda Add().
Kilka interesujcych waciwoci komponentu ComboBox zostao zaprezentowanych w tabeli
10.5.
Tabela 10.5. Waciwoci komponentu ComboBox
Waciwo
Opis
Reprezentuje styl listy rozwijanej. Warto Simple oznacza usunicie
DropDownStyle
ikony sucej do rozwijania listy. Warto DropDownList
uniemoliwia nadanie wartoci waciwoci Text.
DropDownHeight Reprezentuje wysoko listy (w pikselach).
DropDownWidth
Reprezentuje szeroko listy (w pikselach).
Oznacza maksymaln liczb pozycji, jaka moe znale si na licie
MaxDropDownItems
rozwijanej.
Sorted
Warto true oznacza, e pozycje na licie bd sortowane.

Waciwoci oraz uycie komponentu ListBox s bardzo podobne, wic pozostawi to


zagadnienie bez opisu.

Komponent RichTextBox
Mona powiedzie, e komponent RichTextBox jest miniedytorem tekstu. Zawiera metody
suce do kopiowania, wycinania czy wklejania tekstu, jak rwnie takie, ktre umoliwi
zmian koloru zaznaczonego tekstu czy iloci wci.
Komponent RichTextBox idealnie nadaje si do reprezentowania duych porcji tekstu np.
zawartoci plikw tekstowych. Skoro pokazaem ju, w jaki sposb tworzy menu, paski
narzdziowe czy zakadki, moe warto by byo przeobrazi nasz program w co wicej ni
tylko pusty interfejs.
Oprogramujmy zdarzenia Click poszczeglnych pozycji menu.
Zacznijmy od pozycji Wytnij, Kopiuj, Wklej oraz Zaznacz wszystko. Wygeneruj zdarzenia
Click dla tych pozycji.

Kod procedur zdarzeniowych powinien wyglda tak:


private void mmCut_Click(object sender, EventArgs e)
{
MyRichBox.Cut();
}
private void mmCopy_Click(object sender, EventArgs e)
{
MyRichBox.Copy();
}
private void mmPaste_Click(object sender, EventArgs e)
{
MyRichBox.Paste();
}
private void mmSelectAll_Click(object sender, EventArgs e)
{
MyRichBox.SelectAll();
}

Jak widzisz, proces kopiowania czy wycinania tekstu jest bardzo prosty. Wystarczy wywoa
metod Cut(), aby wyci zaznaczony tekst, Copy(), aby go skopiowa, i Paste(), aby
wklei tekst ze schowka. Za zaznaczenie caego tekstu w komponencie RichTextBox
odpowiada metoda SelectAll().
Poniewa na pasku narzdziowym rwnie mamy ikony odpowiadajce za kopiowanie,
wycinanie i wklejanie, naley przypisa im odpowiednie procedury zdarzeniowe.
Inne ciekawe metody komponentu RichTextBox wymieniem w tabeli 10.6.
Tabela 10.6. Interesujce metody komponentu RichTextBox
Waciwo
Opis
AppendText()
Umoliwia dodanie tekstu na kocu aktualnej linii.
Zwraca warto true, jeeli w schowku s dane,
CanPaste()
ktre uytkownik moe wklei.
Clear()
Czyci zawarto komponentu RichTextBox.
Czyci informacje na temat ostatnio cofanych
ClearUndo()
operacji.
DeselectAll()
Usuwa ewentualne zaznaczenie tekstu.
Zwraca znak znajdujcy si w okrelonej pozycji
GetCharFromPosition()
tekstu.
GetFirstCharIndexFromLine()
Zwraca indeks pierwszego znaku z podanej linii.
GetFirstCharIndexOfCurrentLine() Zwraca indeks pierwszego znaku aktualnej linii.
LoadFile()
Umoliwia wczytanie zawartoci pliku.

Redo()
SaveFile()
Select()
Undo()

Powtarza operacj, ktra zostaa uprzednio cofnita.


Zapisuje tekst wpisany w kontrolce do okrelonego
pliku.
Zaznacz okrelony tekst znajdujcy si w kontrolce.
Cofnij ostatnio wykonan operacj.

Okna dialogowe
Zapomnijmy na chwil o komponencie RichTextBox. Nasza przykadowa aplikacja ma
pozycje w menu suce do otwierania, zapisywania pliku. Aby zrealizowa to zadanie i
nada naszej aplikacji wicej profesjonalizmu, moemy skorzysta z okien dialogowych
Otwrz oraz Zapisz S to standardowe okna systemu Windows, ktre zapewne nieraz
widziae. Umoliwiaj one wskazanie pliku, ktry zostanie otwarty lub zapisany.
Komponenty umoliwiajce wywietlanie okien dialogowych znajduj si w kategorii
Dialogs okna Toolbox. Utwrz na formularzu komponenty OpenFileDialog oraz
SaveFileDialog. S to komponenty niewizualne, wic reprezentujce je ikony zostan
umieszczone w panelu podrcznym zakadki projektowania.
Wygeneruj zdarzenie Click pozycji Otwrz z menu gwnego. Kod metody zdarzeniowej
moe wyglda tak:
private void mmOpen_Click(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == DialogResult.OK
&& openFileDialog1.FileName.Length > 0)
{
MyRichBox.LoadFile(openFileDialog1.FileName);
Text = AppName + " [" + openFileDialog1.FileName +
"]";
}
}

Jak widzisz, sposb otwarcia nowego dokumentu jest prosty. Przede wszystkim naley
wywietli okno dialogowe (metoda ShowDialog()). Jeeli uytkownik wskae plik i
nacinie przycisk OK, zmieniony zostanie tytu okna dialogowego, ale przede wszystkim
zawarto pliku zostanie zaadowana do komponentu (metoda LoadFile()).
Waciwo FileName komponentu OpenFileDialog zwraca ciek do pliku, ktry wybra
uytkownik.
Oprogramujmy teraz zdarzenie Click pozycji odpowiadajcej za zapisanie dokumentu
(pozycja Zapisz oraz Zapisz jako):

private void mmSave_Click(object sender, EventArgs e)


{
if (FileName == null)
{
mmSaveAs_Click(sender, e);
}
else
{
MyRichBox.SaveFile(FileName);
Modified = false;
}
}
private void mmSaveAs_Click(object sender, EventArgs e)
{
if (saveFileDialog1.ShowDialog() == DialogResult.OK
&& saveFileDialog1.FileName.Length > 0)
{
MyRichBox.SaveFile(saveFileDialog1.FileName);
Text = AppName + " [" + saveFileDialog1.FileName +
"]";
FileName = saveFileDialog1.FileName;
Modified = false;
}
}

W programie zadeklarowaem pole prywatne FileName typu string. Jeeli warto tego pola
jest rwna null, naley wywoa okno dialogowe, w ktrym uytkownik musi poda ciek
oraz nazw pliku. Zapis zawartoci komponentu RichTextBox realizuje metoda SaveFile(),
w ktrej naley poda ciek do zapisu.
Metoda SaveFile() zapisuje plik w formacie RTF (ang. Rich Text Format), ktry akceptuje
potem take przy odczycie.

Waciwoci okien dialogowych


Zarwno komponent OpenFileDialog, jak i SaveFileDialog posiada kilka interesujcych
waciwoci, o ktrych warto wspomnie. Kilka z nich omwiem w tabeli 10.7.
Tabela 10.7. Waciwoci okien dialogowych
Waciwo
Opis
Title
Tytu okna dialogowego.
Waciwo okrela, czy automatycznie dodawa rozszerzenie, jeeli
AddExtension
uytkownik je pomin.

Okrela, czy generowa ostrzeenie (true), jeeli plik wskazany przez


uytkownika nie istnieje.
Okrela, czy generowa ostrzeenie (true), jeeli wskazana cieka nie
CheckPathExists
istnieje.
DefaultExt
Domylnie rozszerzenie (np. txt, rtf).
Filtry plikw, jakie wywietlone s w licie rozwijanej okna dialogowego
Filter
(np. Pliki RTF|*.rtf).
ShowHelp
Okrela, czy wywietla przycisk pomocy (domylnie false).
Domylna nazwa dla pliku (wywietlana w kontrolce tekstowej w oknie
FileName
dialogowym).
Domylny katalog, ktrego zawarto zostanie wywietlona zaraz po
InitialDirectory
otwarciu okna dialogowego.
CheckFileExists

Aplikacja edytor tekstw


Na podstawie tego, co zaprezentowaem w tym rozdziale, stworzylimy prosty edytor
tekstw. Zaprezentowaem rwnie procedury zdarzeniowe dla niektrych zdarze Click.
Czas zaprezentowa kod pozostaych procedur zdarzeniowych, ktrych zreszt nie ma zbyt
wiele. Zacznijmy od pozycji Nowy w naszym menu:
private void mmNew_Click(object sender, EventArgs e)
{
FileName = null;
Text = AppName;
MyRichBox.Clear();
Modified = true;
}

Pierwsza instrukcja nadaje warto null dla pola FileName (okrelajcego ciek aktualnie
edytowanego pliku). Kolejna instrukcja przypisuje do waciwoci Text warto pola
AppName, ktre okrela nazw programu:
private const string AppName = "Notepad Extra";

Ostatnia instrukcja czyci zawarto komponentu typu RichTextBox.


Idc dalej, naley rwnie oprogramowa zdarzenie Click pozycji Zakocz:
private void mmExit_Click(object sender, EventArgs e)
{

if (Modified)
{
DialogResult dr = MessageBox.Show("Plik nie zosta
zapisany. Zapisa?",
"Plik niezapisany",
MessageBoxButtons.YesNoCancel);
if (dr == DialogResult.Cancel)
{
return;
}
else if (dr == DialogResult.Yes)
{
mmSave_Click(sender, e);
}
Application.Exit();
}
}

Po wybraniu tej pozycji aplikacja zostanie zamknita. Uprzednio jednak program zapyta
uytkownika, czy zapisa zmiany w pliku, jeeli zosta on zmodyfikowany.
Metoda Show() z klasy MessageBox (ktra notabene bya czsto uywana przeze mnie w tej
ksice) zwraca informacje o nacinitym klawiszu w formie typu wyliczeniowego
DialogResult. W naszym przykadzie zawarto pliku zostanie zapisana tylko wtedy, gdy
uytkownik wybierze opcj Yes.

Tworzenie nowego formularza


Powrmy raz jeszcze do naszego przykadowego programu edytora tekstu.
Zapomnielimy oprogramowa jeszcze jednej opcji z menu, a mianowicie O programie.
Chcielibymy, aby po wybraniu tej opcji wywietlony zosta nowy formularz informacji o
autorze aplikacji i adres e-mail autora.
Pracujc w rodowisku Visual C# Express Edition, z menu Project wybierz Add Windows
Form. W oknie Add New Item wska pozycj Windows Form. Na kolejnej zakadce zostanie
wywietlony formularz. Moesz umieci na nim etykiety informujce o autorze programu
oraz dane kontaktowe (rysunek 10.21).

Rysunek 10.21. Okno O programie


Aby uniemoliwi zmiany rozmiarw okna formularza, zmieniem waciwo
FormBorderStyle na FixedToolWindow. Zwr uwag, e formularz zaprezentowany na
rysunku 10.21 nie ma ikon minimalizacji oraz maksymalizacji okna.
Aby wywietli formularz O programie, naley zwyczajnie utworzy nowy egzemplarz jego
klasy, a nastpnie wywoa metod Show():
private void mmAbout_Click(object sender, EventArgs e)
{
AboutForm About = new AboutForm();
About.Show();
}

Podsumowanie
Opisanie caej biblioteki WinForms wymaga odrbnej publikacji. Ja w tym rozdziale
opisaem jedynie kilka podstawowych komponentw. Mam nadziej, e na tej podstawie
bdziesz w stanie wykorzysta uyteczno pozostaych komponentw, jakie udostpnia
biblioteka Windows Forms. Uwierz mi, e w poznaniu tej biblioteki due znaczenie odgrywa
dowiadczenie. Im czciej bdziesz wykorzystywa w swoich aplikacjach bibliotek
WinForms, tym lepiej j zgbisz i wkrtce nie bdzie ona krya przed Tob adnych
tajemnic.

Rozdzia 11
Podzespoy .NET

Pojcie podzesp .NET zostao wprowadzone w rozdziale 2. Podzespoy s fizycznymi


plikami zapisanymi w formacie PE. Mog by plikami wykonywalnymi (z rozszerzeniem
.exe) albo wspuytkowanymi bibliotekami .dll. Tak wic kady program tworzony w
rodowisku .NET jest podzespoem.
Czasami mona si spotka z okreleniem komponenty .NET.
Nim rozpoczn omawianie podzespow, pragn przybliy Ci tematyk modeli COM, ktre
rwnie stanowiy komponenty przenone w rodowisku Win32.
W dalszej czci tego rozdziau nauczysz si pisa wasne podzespoy oraz przeprowadza
komunikacj pomidzy poszczeglnymi aplikacjami.

Czym jest COM


Pierwowzorem idei komponentw .NET jest COM (ang. Component Object Model), COM+,
DCOM. COM zapewnia niezaleno jzyka programowania, lecz .NET wprowadza
dodatkowo jego integracj oraz niezaleno od platformy.
Rozwiniciem angielskiego skrtu COM jest Component Object Model (obiektowy model
komponentw). Jest to specyfikacja firmy Microsoft, ktra w zaoeniu dotyczy tworzenia
obiektw wielokrotnego uytku, niezalenych od jzyka programowania.
Aby zrozumie ActiveX, trzeba pozna COM postaram si zwile wytumaczy to
pojcie. Ot firma Microsoft stworzya model obiektw, ktre mog by wykorzystywane w
kadym rodowisku programistycznym Windows. Wynikiem powstania obiektu COM jest
kontrolka plik binarny z rozszerzeniem .ocx. Kontrolka taka moe by wykorzystywana
zarwno w Delphi, jak i w jzykach Visual C++, C++ Builder czy Visual Basic.
Na razie obiekty COM dziaaj jedynie w rnych rodowiskach Windows wykorzystanie
ich poza tym systemem jest niemoliwe.

Kontrolka w rozumieniu COM


Na tym etapie bd uywa sowa kontrolka w znaczeniu obiektu COM. Mona powiedzie,
e obiekty COM s takim uniwersalnym komponentem podobnym do biblioteki DLL. Raz
utworzona kontrolka moe by wykorzystywana wiele razy, przez wielu programistw oraz w
rnych rodowiskach programowania. Jeeli kto ju napisa kontrolk speniajc dan
funkcj, to powtrne tworzenie takiego samego elementu nie ma sensu. Przykadem moe by
przegldarka WWW. Napisanie programu analizujcego kod HTML jest niezwykle
czasochonnym i mudnym zadaniem. Niekiedy jednak w tworzonym programie konieczne
staje si wywietlenie jakiego dokumentu w formie strony WWW. Dziki technologii COM i
ActiveX (o ActiveX opowiem nieco pniej) moemy zaimportowa udostpnione przez

twrcw przegldarki obiekty COM i wykorzysta je w Delphi w bardzo prosty sposb.

Odrobin historii
COM jest technologi stosunkowo now, bo powsta kilka lat temu. Wprowadzenie jej miao
na celu zapewnienie jednolitego standardu komunikowania si, tak aby np. (by jeszcze raz
posuy si wczeniejszym przykadem) programici mogli korzysta z moliwoci
przegldania stron WWW w swoich aplikacjach. Firma Microsoft wysza naprzeciw tym
potrzebom i utworzya modu obiektw (COM), ktry umoliwia udostpnianie innym
aplikacjom swoich metod.

ActiveX
ActiveX jest technologi opart na COM. Pozwala na tworzenie kontrolek .ocx lub .dll. W
rzeczywistoci ActiveX to obiekt COM, tyle e posiadajcy wasny interfejs (okna, kontrolki
itp.). Tak wic moglimy tworzy kontrolki ActiveX, wykorzystujc np. Delphi oraz jego
zalety projektowania wizualnego. Mona byo korzysta ze wszystkich komponentw i,
oglnie rzecz biorc, projektowanie byo atwiejsze ni w przypadku zwykych obiektw
COM.
Dodatkowo ActiveX pozwala na wygenerowanie kodu umoliwiajcego umieszczenie
aplikacji na stronie WWW.
Platforma .NET jest nastpczyni COM, ktra zakada integralno pomidzy programami.
Do tej pory programici mogli budowa
osobne kontrolki, ktre pniej dawao si wykorzystywa w innych aplikacjach. Wizao si
to z rejestracj tej kontrolki i dodawaniem odpowiednich wpisw w rejestrze Windows. W
.NET komunikacja midzy aplikacjami bdzie uatwiona dany program bdzie mg
dziedziczy po klasie z innego, obsugiwa jego wyjtki itp.

DCOM
DCOM jest akronimem sw Distributed Component Object Model. Technologia ta, rwnie
opracowana przez firm Microsoft, zakada moliwo komunikowania si pomidzy
poszczeglnymi kontrolkami COM za porednictwem internetu. Ta technologia rwnie
zostaa uznana za przestarza w stosunku do platformy .NET.

Podstawowy podzesp
Jak ju wiesz, podzespoy zawieraj przestrzenie nazw, a te z kolei kolejne klasy, struktury
czy typy wyliczeniowe. Wszystkie aplikacje .NET wykorzystuj podzesp mscorlib.dll.
Podzesp ten zawiera nie tylko wszystkie podstawowe typy wykorzystywane w tej
platformie, ale take klas bazow Exception oraz wiele innych wanych elementw.
Plik mscorelib.dll znajduje si w katalogu, w ktrym zainstalowane jest rodowisko .NET
Framework. Na moim komputerze jest to
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorlib.dll.

Deasembler .NET
Deasembler jest programem dziaajcym odwrotnie ni asembler przeksztaca form
binarnej aplikacji do kodu poredniego IL. Wraz z pakietem .NET Framework SDK jest
dostarczany deasembler, ktry umoliwia analiz kodu binarnego PE i dokonanie konwersji
do kodu IL.
SDK to skrt od Software Developer Kit. Pakiet .NET Framework SDK zawiera narzdzia,
dokumentacj oraz wiele przydatnych rzeczy, ktre mog przyda si kademu programicie
.NET. Pakiet ten nie jest wymagany do prawidowego funkcjonowania aplikacji .NET. Moesz
go cign za darmo ze strony
http://msdn.microsoft.com/netframework/downloads/updates/default.aspx.
Deasembler, o ktrym mowa, znajduje si w katalogu C:\Program
Files\Microsoft.NET\SDK\v2.0\Bin (w zalenoci od konfiguracji) pod nazw Ildasm.exe
(rysunek 11.1).

Rysunek 11.1. Deasembler w trakcie dziaania


Program jest do prosty w uyciu. Wystarczy z menu File wybra Open i wskaza plik, ktry
nastpnie zostanie otwarty przez deasembler. Rysunek 11.1 przedstawia zawarto
podzespou mscorlib.dll.
Aby obejrze kod IL danego fragmentu aplikacji, wystarczy klikn odpowiedni pozycj.
Przykadowy fragment takiego kodu IL wywietlonego przez deasembler pokazano na
rysunku 11.2.

Rysunek 11.2. Podgld kodu IL


Pewnie chciaby teraz zapyta, w jaki sposb mona zabezpieczy si przed deasemblacj
programu. Prawda jest taka, e nie mona. Nikt nie bdzie jednak w stanie przeksztaci kodu
IL do rzeczywistego kodu rdowego C#, wic nie ma powodw do zmartwie. Jedyne
ryzyko jest takie, e kady moe dowiedzie si, z jakich moduw korzysta projektant.
Moe take pozna struktur klas aplikacji.
W menu File znajduje si ciekawa pozycja Dump, ktra umoliwia zapisanie zrzutu (kodu IL i
metadanych) do pliku tekstowego.

Komponenty .NET
Gwn zalet .NET Framework jest niezaleno od platformy oraz od jzyka. W rozdziale
2. wspominaem o technologii Common Type System (wsplny system typw). Pisaem
wwczas, i dziki CTS moliwe jest komunikowanie
si pomidzy poszczeglnymi podzespoami.
W rodowisku Win32 moliwe jest podzielenie aplikacji na mniejsze jednostki. W tym celu
stosuje si biblioteki DLL. Biblioteki DLL mog eksportowa funkcje i procedury, ktre z
kolei mog by wykorzystywane w aplikacji EXE. Innym rozwizaniem jest zastosowanie
kontrolek COM. Uycie takiej kontrolki w programie take nie jest atwe, gdy przed
wykorzystaniem obiekt COM musi zosta zarejestrowany.

W .NET sytuacja wyglda zupenie inaczej: poszczeglne podzespoy mog wykorzystywa


si wzajemnie. Podzesp A moe odwoywa si do metody z klasy, ktra znajduje si w
podzespole B. W przeciwiestwie do bibliotek DLL, mona korzysta z caych klas
umieszczonych w danym podzespole (a nie tylko z procedur i funkcji).
W tej czci rozdziau zaprezentuj, w jaki sposb aplikacja C# moe wykorzystywa
komponent napisany w Delphi 1.
Delphi jest obiektowym, profesjonalnym jzykiem programowania, stworzonym przez firm
Borland. Umoliwia projektowanie aplikacji zarwno na platform Win32, jak i .NET. Zdaj
sobie spraw, e tematyka programowania w Delphi moe Ci nie interesowa i moesz nie
zna tego jzyka. Dlatego prezentowane tutaj przykady kodw rdowych umieszczone s w
formie rdowej oraz skompilowanej na pycie CD doczonej do ksiki.

Przygotowanie komponentu w Delphi


Nasz przykadowy program moe by bardzo prosty, nie musi wykonywa adnych
wyspecjalizowanych dziaa. Odgrywa jedynie rol demonstracyjn.
Nie jest w tej chwili istotne, czy nasza aplikacja bdzie podzespoem .exe, czy .dll jest
skompilowana do kodu IL, wic mona j wykorzysta w ten sam sposb.
Na listingu 11.1 znajduje si przykadowy program zawierajcy klas Vehicle. Program jest
prosty, zawiera kilka metod, ktre wywietlaj okrelony tekst na konsoli. Jak ju
wspominaem, nie jest to program zbyt uyteczny, bowiem jedynie demonstruje
wspdziaanie komponentw .NET.
Listing 11.1. Przykadowy podzesp napisany w Delphi
library Assembly;
{$APPTYPE CONSOLE}
{ Copyright (c) 2004 by Adam Boduch }
type
Vehicle = class
private
procedure TurnLeft;
procedure TurnRight;
procedure Breaks;
public
procedure SendMsg(const Message : String);
end;

{ Vehicle }
procedure Vehicle.Breaks;
begin
Console.WriteLine('Wczam hamulce.');
end;
procedure Vehicle.SendMsg(const Message: String);
begin
{ sprawdzenie, jaki parametr zosta przekazany do procedury }
if Message = 'Left' then TurnLeft
else if Message = 'Right' then TurnRight
else if Message = 'Breaks' then Breaks
else Console.WriteLine('Nieprawidowa komenda.');
end;
procedure Vehicle.TurnLeft;
begin
Console.WriteLine('Skrcam w lewo.');
end;
procedure Vehicle.TurnRight;
begin
Console.WriteLine('Skrcam w prawo.');
end;
begin
{ empty }
end.

Jeeli masz na swoim komputerze zainstalowane rodowisko Delphi 8 lub nowsze, moesz
skompilowa w nim poniszy kod. Dla wszystkich tych, ktrzy nie maj zainstalowanego
rodowiska Delphi, na pycie CD doczonej do ksiki zamieciem wersj skompilowan.
Parametr przekazany do metody SendMsg() decyduje o tym, jaka procedura zostanie
wywoana z klasy Vehicle.

Przygotowanie komponentu C#
Przede wszystkim uruchom rodowisko Visual C# Express Edition i utwrz nowy projekt
aplikacji konsolowej. W oknie Solution odnajd ga References, kliknij j prawym
przyciskiem myszy i wybierz Add Reference. Dziki oknu Add Reference moemy doda do
programu odwoanie do innego komponentu .NET lub kontrolki COM. Nas interesuje
zakadka Browse pozwala ona wskaza skompilowany podzesp, do ktrego zostanie
utworzone odwoanie.

Odszukaj i wska skompilowany podzesp Assembly.dll, ktry moesz znale na pycie CD


doczonej do ksiki. Po dodaniu nowa pozycja zostanie wywietlona w oknie Solution.
Teraz czas na wykorzystanie klasy Vehicle z naszego podzespou. Rozwizanie znajduje si
na listingu 11.2.
Listing 11.2. Program korzystajcy z podzespou
using System;
using Assembly;
/* Copyright (c) 2004 by Adam Boduch */
class MainClass
{
public static void Main()
{
try
{
Vehicle Car = new Vehicle(); // utworzenie
obiektu
String S; // deklaracja acucha
Console.WriteLine("Przykadowa aplikacja
korzystajca z podzespou Assembly");
Console.WriteLine("Podaj komend do wysania:");
Console.WriteLine("
-- Left");
Console.WriteLine("
-- Right");
Console.WriteLine("
-- Breaks");
Console.WriteLine();
Console.Write("Komenda: ");
S = Console.ReadLine(); // pobranie komendy
Car.SendMsg(S); // przesanie do podzespou
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Console.Read();
}
}

Zwr uwag, i przy pomocy instrukcji using musimy wczy do programu odpowiedni
przestrze nazw. Dalsza cz kodu powinna by dla Ciebie jasna. Rysunek 11.3 prezentuje
dziaanie takiego programu.

Rysunek 11.3. Prezentacja moliwoci wykorzystania podzespou

Kompilacja z poziomu linii polece


Oczywicie do kompilacji takiej aplikacji mona rwnie uy kompilatora csc.exe. Ja
skorzystaem ze rodowiska Visual C# Express Edition jedynie ze wzgldu na wygod
.
Aby kompilacja takiego programu przy pomocy kompilatora csc.exe przebiega bez adnych
przeszkd, naley kompilowa w ten sposb:
csc /r:C:\katalog\Assembly.dll /out:C:\demo.exe C:\katalog\demo.cs

Powysze polecenie odniesie oczekiwany efekt, pod warunkiem e plik Assembly.dll oraz
demo.cs znajduj si w katalogu C:\katalog. Przecznik /r: okrela podzesp, z jakim
zostanie skompilowany nasz program. Dziki temu kompilator rozpoznaje typ Vehicle w
kodzie C#. Z kolei przecznik /out: okrela ciek, w ktrej zostanie umieszczona wersja
wynikowa naszego programu.
Jeeli kompilator nie wywietli adnego bdu, mona sprbowa uruchomi aplikacj
demo.exe.

Zalety stosowania podzespow


W poprzednich przykadach zaprezentowaem sposb na wykorzystanie moliwoci danego

podzespou z poziomu drugiego programu. Taki przykad powinien zobrazowa Ci pewn


zalet platformy .NET, a mianowicie niezaleno. Jak widzisz, jeden program (podzesp)
zosta napisany w jzyku Delphi, a drugi w C#. Niezalenie od tego bez adnych
problemw moglimy wykorzysta klas Vehicle. W ten sposb skonstruowana jest caa
biblioteka klas .NET Framework. Plik mscorelib.dll jest skompilowanym podzespoem
zawierajcym setki klas. Programujc na platformie .NET, moemy ten plik wczy do
swojej aplikacji i wykorzystywa znajdujce si w nim klasy.
Bdc programistom, moesz pisa swoje aplikacje w jzyku C#, a nastpnie sprzedawa.
Wyobra sobie, e napisae program zawierajcy klas suc do szyfrowania tekstu. Jest
ona tak rewolucyjna, e bez problemw znajdujesz kupcw na swj program. Nie chcesz
jednak udostpnia swoich kodw rdowych, sprzedajesz wic wersj skompilowan.
Firma, ktra kupia od Ciebie w program, moe wykorzystywa klasy zawarte w podzespole,
nie znajc przy tym zawartoci kodu! Takiej firmie wystarczy jedynie dokumentacja klasy.
Przy pomocy mechanizmu dziedziczenia oraz polimorfizmu firma, ktra kupia od Ciebie
podzesp, moe napisa now klas, dziedziczc po Twojej, znajdujcej si w osobnym
podzespole!
Inn zalet dzielenia aplikacji na kilka podzespow jest podzia funkcjonalnoci. Moesz
podzieli aplikacj na kilka mniejszych moduw wedug ich funkcjonalnoci. Przypuszczam,
e ju niedugo bdziesz (jeeli jeszcze nie jeste) zawodowym programist. Moesz pisa
aplikacje i udostpnia je na zasadach licencji shareware. W kadej aplikacji zamieszczasz
formularz sucy do rejestracji programu i uzyskiwania klucza produktu. Po co ten sam kod
kopiowa do wielu programw? Nie lepiej przenie go do osobnego podzespou, ktry
bdziemy adowa, kiedy bdzie potrzebny? Dzielenie aplikacji na kilka moduw
(podzespow) ma te inn zalet. Jeeli w jednym z moduw wykryjesz bd, moesz
udostpni swoim klientom
poprawk w postaci jednego podzespou, a nie caej aplikacji.
Moesz uniemoliwi innym podzespoom dziedziczenie lub wrcz wykorzystywanie Twoich
klas lub ich elementw. Naley wwczas deklarowa elementy z uyciem modyfikatora
dostpu internal.

Budowa podzespou
Powiedzielimy sobie, e podzesp to nie tylko poredni kod IL. W rzeczywistoci jego
zawarto mona podzieli na cztery elementy:

kod poredni IL,


zasoby
aplikacji,

manifest,
metadane.

Te wszystkie elementy s umieszczane w jednym pliku wykonywalnym (.exe lub .dll).


Kod poredni jest zapisany w jzyku IL, ktry przypomina nieco Asemblera. Wspominaem o
nim w rozdziale 4. i wydaje mi si, e to pojcie nie wymaga dalszego wyjanienia.
O zasobach wspominaem w poprzednim rozdziale. Zasoby to pliki dwikowe, graficzne,
tekstowe oraz inne pliki wykonywalne. Zasoby mog by umieszczone w zewntrznym pliku,
ale najczciej s umieszczane w aplikacji wykonywalnej.
Metadane to informacje o kodzie rdowym. S one generowane w trakcie kompilacji i
opisuj klasy, metody czy inne typy wykorzystywane w kodzie. Dziki metadanym jestemy
w stanie stwierdzi, jaka jest zawarto danego podzespou, lepiej wykorzysta jego
funkcjonalno. Wykonaj mae dowiadczenie. W kodzie rdowym kliknij prawym
przyciskiem nazw jakiej klasy biblioteki FCL. Z menu podrcznego wybierz pozycj Go to
definition. Na podstawie metadanych zawartych w podzespole rodowisko Visual C# Express
Edition jest w stanie wywietli informacj o zawartoci danej klasy czy przestrzeni nazw.
Manifest to informacje o metadanych podzespou. Manifest zawiera informacje odnonie do
podzespou, jego relacji z innymi komponentami .NET. W szczeglnoci jest to:

Nazwa atrybutu cig znakw okrelajcy nazw podzespou.


Wersja podzespou numer wersji podzespou.
Lokalizacja informacje o lokalizacji lub o jzyku interfejsu podzespou.
cisa kontrola nazw publiczny klucz podzespou. O tym opowiem w dalszej czci
rozdziau.
Lista plikw informacje o plikach wykorzystywanych w projekcie.

Atrybuty podzespou
W podzespoach mona zadeklarowa okrelone atrybuty opisujce dany podzesp. Mog
one by pniej odczytywane przez inne programy w celu weryfikacji numeru wersji aplikacji
oraz nazwy producenta. Innymi sowy, atrybuty s narzdziem programisty, ktry moe
decydowa o zawartoci manifestu.
Jeeli masz otwarty projekt aplikacji C#, niewane, czy Windows Forms, czy konsolowy,
przejd do okna Solution w gazi Properties, wybierz pozycj AssemblyInfo.cs i kliknij j:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through
the following
// set of attributes. Change these attribute values to modify

the information
// associated with an assembly.
[assembly: AssemblyTitle("AssemblyApp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AssemblyApp")]
[assembly: AssemblyCopyright("Copyright 2006")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this
assembly not visible
// to COM components. If you need to access a type in this
assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this
project is exposed to COM
[assembly: Guid("a0bf0a73-54d8-4b7b-9472-3a2c0980654f")]
// Version information for an assembly consists of the
following four values:
//
//
Major Version
//
Minor Version
//
Build Number
//
Revision
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Zawarto tego moduu jest rwnie kompilowana wraz z pozostaym kodem. Modu ten
zawiera informacje o naszej aplikacji w formie atrybutw. Te informacje zostan
skompilowane do kodu poredniego IL.
Jak widzisz, umieszczanie atrybutw w kodzie programu jest do specyficzne. Atrybuty
umieszczane s w nawiasach kwadratowych, z uyciem sowa okrelajcego przeznaczenie
atrybutu (assembly):
[assembly: nazwa atrybutu]

Atrybuty s zwykymi klasami, ktre dziedzicz po Attribute (ktra to zadeklarowana jest


w przestrzeni System.Reflection). Przykadowo, deklaracja klasy
AssemblyFileVersionAttribute w przestrzeni System.Reflection wyglda nastpujco:
public sealed class AssemblyFileVersionAttribute : Attribute
{

public AssemblyFileVersionAttribute(string version);


public string Version { get; }
}

W przypadku tego atrybutu do klasy naley przekaza jeden parametr w postaci typu string.
Jeeli chcesz, moesz zmieni zawarto atrybutw, m.in. podajc prawidow nazw
aplikacji oraz wersj czy autora.

Mechanizm refleksji
Powiedziaem ju, e dziki metadanym mamy moliwo odczytania informacji na temat
elementw umieszczonych w podzespole. Pokazywaem, jak to zrobi, korzystajc z opcji Go
to definition w menu podrcznym rodowiska Visual C# Express Edition.
Mechanizm refleksji umoliwia odczytanie informacji odnonie do metadanych. Z tego
mechanizmu korzysta m.in. rodowisko Visual C# Express Edition przy odczytywaniu
informacji z podzespou. Mamy moliwo przegldania zawartoci biblioteki klas FCL lub
jakiegokolwiek innego podzespou przy pomocy okna Object Browser (rysunek 11.4).

Rysunek 11.4. Okno Object Browser


Okno Object Browser mona wywoa, wybierajc z menu View pozycj Other
Windows/Object Browser.
Korzystajc z przycisku ... znajdujcego si obok listy rozwijanej Browse, mamy moliwo
wyboru podzespou, z ktrego dane zostan wywietlone w oknie. Rysunek 11.4 przedstawia
informacje o metadanych podzespou Assembly.dll (napisanego w Delphi).
Po zaznaczeniu wybranej pozycji w prawym oknie wywietlona zostanie lista elementw
danej klasy wraz z ewentualnymi parametrami metod.
Okno Object Browser umoliwia odczytywanie informacji z wybranego podzespou i
rwnoczenie stanowi prosty i szybki sposb na poznanie budowy
programu. Istnieje moliwo zastosowania mechanizmu refleksji w naszej aplikacji dziki
przestrzeni nazw System.Reflection, ktra jest udostpniana przez pakiet .NET

Framework.
Mechanizm refleksji pozwala na odczyt dowolnego podzespou .NET. Nie jest wic istotne, czy
program zosta napisany w C#, Delphi czy w C++. Po prostu program napisany w Delphi
bdzie zawiera wicej informacji, gdy kompilator Delphi dodatkowo wcza w aplikacj
wykonywaln zawarto przestrzeni Borland.Delphi.System.

Funkcja GetType
Kada klasa .NET posiada metod GetType(), ktra jest dziedziczona po klasie gwnej
System.Object. Owa metoda zwraca rezultat w postaci klasy System.Type. Dziki niej
mona odczytywa takie informacje jak nazwa podzespou, jego wersja, a take przestrze
adresowa, w jakiej si on znajduje. Tym zajmiemy si nieco pniej teraz pozostaniemy
jedynie przy odczytaniu nazwy danego typu:
using System;
namespace FooApp
{
class Program
{
static void Main(string[] args)
{
int I = 10;
double D = 10.5;
Console.WriteLine("Zmienna I jest typu: " +
I.GetType().ToString());
Console.WriteLine("Zmienna D jest typu: " +
D.GetType().ToString());
Console.Read();
}
}
}

Typy int czy double w C# s odpowiednikami typw .NET, rwnie s klasami, zatem
posiadaj metod GetType(), ktra zwraca informacje o obiekcie. W powyszym przykadzie
uyem metody ToString() do znakowego przedstawiania typu zmiennej. Dziaanie takiego
programu spowoduje wywietlenie na ekranie tekstu:
Zmienna I jest typu System.Int32
Zmienna D jest typu System.Double

Klasa System.Type
Jak powiedziaem wczeniej, metoda GetType() zwraca informacje w postaci obiektu
System.Type, z ktrego mona wyczyta wicej informacji na temat konkretnego obiektu
.NET.
Na formularzu WinForms umiemy teraz komponent ListBox oraz Button. Nazwijmy je,
odpowiednio, lbInfo oraz btnGetInfo (nazwy komponentw nie odgrywaj wikszej roli).
Zdarzenie Click przycisku powinno wyglda tak jak na listingu 11.3.
Listing 11.3. Wykorzystanie klasy System.Type
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WinForms
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void btnGetInfo_Click(object sender, EventArgs
e)
{
System.Type MyType;
MyType = sender.GetType();
lbInfo.Items.Add("Nazwa klasy: " +
MyType.FullName);
lbInfo.Items.Add("Przestrze nazw: " +
MyType.Namespace);
lbInfo.Items.Add("Nazwa podzespou: " +
MyType.Assembly.FullName);
lbInfo.Items.Add("Wersja podzespou: " +
MyType.Assembly.GetName().Version.ToString());
lbInfo.Items.Add("Nazwa pliku: " +
MyType.Module.Name);
}
}
}

Po naciniciu przycisku aplikacja pobiera informacj na temat komponentu Button.


Wywietla m.in. pen nazw klasy (System.Windows.Forms.Button) oraz podzespou.
Naley zapamita, e nazwa, jak nadaje si komponentowi, nie ma wikszego znaczenia dla
dziaania programu. Nie mona jednak przepisywa bezmylnie kodu z tej ksiki, gdy w
poszczeglnych przypadkach nazwa komponentu moe si rni od tej, ktr ja nadaem. W
takiej sytuacji program nie zostanie skompilowany.
Na samym pocztku do zadeklarowanej zmiennej MyType jest przypisywany rezultat
dziaania metody GetType(). Od tego momentu zmienna MyType zawiera informacj na
temat przycisku. Nastpnie informacje te s umieszczane w komponencie typu ListBox.

adowanie podzespou
Program przedstawiony w poprzednim przykadzie pobiera informacje o komponencie typu
Button. Jest to do proste, lecz niezbyt przydatne. Okno Object Browser umoliwia
odczytanie informacji na temat dowolnego podzespou. Aby to wykona, mona wykorzysta
klas System.Reflection.Assembly.
Dla przypomnienia: dla kompilatora nie ma znaczenia, czy zadeklarowana zmienna bdzie
typu Assembly czy System.Reflection.Assembly.
Zaadowanie podzespou wie si zaledwie z jedn lini kodu wywoaniem metody
LoadFrom() z klasy System.Reflection.Assembly (w skrcie bd nazywa t klas po
prostu Assembly):
System.Reflection.Assembly AFile;
AFile = System.Reflection.Assembly.LoadFrom("Assembly.exe");

Taki zapis umoliwia nastpnie odczytanie klas znajdujcych si w podzespole oraz ich
waciwoci, zdarze itd.

Przykad dziaania program Reflection


Rysunek 11.5 prezentuje przykadowy program Reflection, ktry analizuje zawarto
podzespou. Zadaniem aplikacji jest wywietlanie listy typw zawartych w podzespole wraz z
ich waciwociami, polami, metodami oraz zdarzeniami.

Rysunek 11.5. Przykadowy program Reflection


Na rysunku 11.5 program odczytuje zawarto biblioteki Assembly.dll, ktr opisywaem w
trakcie prezentowania moliwoci wsplnego modelu programowania.
Interfejs aplikacji skada si z komponentw TreeView (komponent nazwaem tvAssembly),
Button oraz niewidoczny OpenFileDialog (przypomn suy on do wywietlania
standardowego okna Windows Otwrz plik). Komponent TreeView zosta uyty z uwagi na
moliwo tworzenia gazi, co daje wraenie hierarchicznej struktury.
W programie wykorzystaem rwnie komponent ProgressBar. Pokazuje on postp w trakcie
analizowania i wczytywania podzespou.
Cay kod rdowy zosta przedstawiony na listingu 11.4. Najpierw mu si przyjrzyj, pniej
przeczytasz opis jego budowy.
Listing 11.4. Kod rdowy programu Reflection
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace Reflection

{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void btnLoad_Click(object sender, EventArgs e)
{
System.Reflection.Assembly AFile;
TreeNode ClassTree, PropTree, MethodTree,
FieldTree, EventTree;
// usunicie wszystkich gazi (jeeli s)
tvAssembly.Nodes.Clear();
// wywietlenie okna dialogowego
openFileDialog1.ShowDialog();
try
{
// pobranie nazwy wybranego podzespou oraz
zaadowanie go
AFile =
System.Reflection.Assembly.LoadFrom(openFileDialog1.FileName);
Text = "Reflection [" +
openFileDialog1.FileName + "]";
}
catch
{
MessageBox.Show("Nie mona zaadowa
podzespou!");
return;
}
// zaadowanie informacji o klasach znajdujcych
si w podzespole
System.Type[] MyType = AFile.GetTypes();

progressLoad.Maximum = MyType.Length;
for (int i = 0; i < MyType.Length; i++)
{
// utworzenie nowego wza o nazwie klasy z
podzespou
ClassTree =
tvAssembly.Nodes.Add(MyType[i].FullName);
// utworzenie nowej gazi o nazwie
Waciwoci

PropTree = ClassTree.Nodes.Add("Waciwoci");
for (int subI = 0; subI <
MyType[i].GetProperties().Length; subI++)
{
PropTree.Nodes.Add(MyType[i].GetProperties().GetValue(subI).To
String());
}
// utworzenie nowej gazi o nazwie Metody
MethodTree = ClassTree.Nodes.Add("Metody");
for (int subI = 0; subI <
MyType[i].GetMethods().Length; subI++)
{
MethodTree.Nodes.Add(MyType[i].GetMethods().GetValue(subI).ToS
tring());
}
// utworzenie nowej gazi o nazwie Pola
FieldTree = ClassTree.Nodes.Add("Pola");
for (int subI = 0; subI <
MyType[i].GetFields().Length; subI++)
{
FieldTree.Nodes.Add(MyType[i].GetFields().GetValue(subI).ToStr
ing());
}
// utworzenie nowej gazi o nazwie
Zdarzenia
EventTree = ClassTree.Nodes.Add("Zdarzenia");
for (int subI = 0; subI <
MyType[i].GetEvents().Length; subI++)
{
MethodTree.Nodes.Add(MyType[i].GetEvents().GetValue(subI).ToSt
ring());
}
progressLoad.Value = i;
}
progressLoad.Value = 0;
}
}
}

Po zaadowaniu wybranego przez uytkownika podzespou nastpuje odczytanie


znajdujcych si w nim typw (metoda GetTypes()) do tablicy MyTypes. Nastpnie ptla for

analizuje po kolei wszystkie elementy, dodajc za kadym razem now pozycj w


komponencie TreeView:
ClassTree = tvAssembly.Nodes.Add(MyType[i].FullName);

Tworzenie nowej pozycji w komponencie TreeView przypomina nieco tworzenie nowej linii
w kontrolce ListBox. Zmienna ClassTree stanowi niejako uchwyt, ktry bdzie
wykorzystywany do tworzenia kolejnych odgazie.
Kolejnym krokiem w dziaaniu programu jest odczytanie waciwoci, metod, zdarze, pl
oraz metod kadej z klas. W kadym przypadku proces ten jest niemal identyczny (rne s
jedynie nazwy sucych do tego funkcji):
PropTree = ClassTree.Nodes.Add("Waciwoci");
for (int subI = 0; subI < MyType[i].GetProperties().Length;
subI++)
{
PropTree.Nodes.Add(MyType[i].GetProperties().GetValue(subI).T
oString());
}

Na samym pocztku tworzone jest odgazienie Waciwoci, do ktrego bd doklejane


kolejne gazie zawierajce nazwy waciwoci. W tym celu kolejna ptla for pobiera nazwy
kolejnych waciwoci klasy.
Jak zapewne zdye si zorientowa, do pobierania listy waciwoci danego typu suy
metoda GetProperties(), ktra rwnie zwraca list w postaci tablicy.

Odczyt atrybutw z podzespou


W naszym programie Reflection odczytywalimy jedynie drzewo klas i metod danego
podzespou. Teraz zajmiemy si odczytem niestandardowych atrybutw podzespou, takich
jak jego opis, tytu itp.
W tym celu .NET Framework posiada klasy umoliwiajce odczyt np. tytuu podzespou
(klasa AssemblyTitleAttribute). Naszym zadaniem jest zaadowanie wybranego
podzespou (to ju opisywaem), a nastpnie wywoanie metody GetCustomAttributes(),
ktra pobierze nazwy wszystkich atrybutw znajdujcych si w podzespole i zapisze je do
tablicy.
Rozbudujmy wic nasz aplikacj Reflection, dodajc do niej now funkcjonalno. Do
formularza dodaem kilka komponentw typu TextBox, ktre bd przechowywa nazw
podzespou, jego opis, opis praw autorskich oraz wersj. Odpowiada za to prywatna metoda:

private void loadAttributes(System.Reflection.Assembly AFile)


{
System.Object[] Attrs = AFile.GetCustomAttributes(true);
for (int i = 0; i < Attrs.Length; i++)
{
/*
W tych instrukcjach nastpuje sprawdzenie, czy
dany obiekt naley do szukanej przez nas klasy.
Pozwala to unikn bdw w trakcie dziaania
programu.
Inaczej mwic, instrukcja if sprawdza, czy w
podzespole
znajduje si dany atrybut (przykadowo:
AssemblyTitleAttribute
*/
if (Attrs[i] is AssemblyTitleAttribute)
{
edtTitle.Text = (Attrs[i] as
AssemblyTitleAttribute).Title;
}
if (Attrs[i] is AssemblyDescriptionAttribute)
{
edtDescription.Text = (Attrs[i] as
AssemblyDescriptionAttribute).Description;
}
if (Attrs[i] is AssemblyCopyrightAttribute)
{
edtCopyright.Text = (Attrs[i] as
AssemblyCopyrightAttribute).Copyright;
}
if (Attrs[i] is ComCompatibleVersionAttribute)
{
edtVersion.Text = Convert.ToString(
(Attrs[i] as
ComCompatibleVersionAttribute).MajorVersion) + '.' +
Convert.ToString((Attrs[i] as
ComCompatibleVersionAttribute).MinorVersion);
}
}
}

Przed prb kompilacji programu nie mona zapomnie o wczeniu do programu przestrzeni
nazw System.Reflection i System.Runtime.InteropServices.

W powyszym przykadzie posuyem si mechanizmem rzutowania za pomoc operatorw


is i as:
if (Attrs[i] is AssemblyTitleAttribute)
{
edtTitle.Text = (Attrs[i] as
AssemblyTitleAttribute).Title;
}

Najpierw program sprawdza, czy element tablicy okrelony zmienn i jest typu
AssemblyTitleAttribute. Jeeli tak, nastpuje rzutowanie tego elementu tablicy oraz
wycignicie wartoci waciwoci Title. Kontrolka o nazwie edtText w rzeczywistoci jest
komponentem TextBox umieszczonym na formularzu.
Zastosowanie takiego mechanizmu byo konieczne ze wzgldu na to, i funkcja
GetCustomAttributes zwraca elementy w postaci tablicy typu System.Object. Naleao
wic ustali, czy rzutowanie na dan klas (np. AssemblyTitleAttribute) powiedzie si
std konieczno uycia operatorw is i as.

Wasne atrybuty
Powiedziaem, e atrybuty s danymi opisujcymi dane. Oprcz standardowych atrybutw
okrelajcych np. prawa autorskie do podzespou mona zadeklarowa wasne, ktre tak
samo bd odczytywane przez programy typu Reflection.
Do czego mog si przyda wasne atrybuty? Moliwoci s nieograniczone. Przykadowo:
umieszczanie w swoich programach takich informacji jak odnonik do strony WWW
zawierajcej kod XML danego programu. Gdy wszystkie Twoje aplikacje bd zawieray
odpowiedni atrybut z odnonikiem do pliku XML, to bd mogy zacieni wspprac w
pliku XML moe znajdowa si dodatkowa informacja dotyczca programu.
Inny przykad: notowanie bdw aplikacji. Mona oczywicie umieszcza komentarz przy
klasie, w ktrej poprawiono kod. Mona take uy atrybutu, np. w taki sposb:
[BugFixedAttribute("Adam", "19-05-2006", "Dodaem metod")]
[BugFixedAttribute("Janek", "21-06-2006", "Poprawka")]
class Program
{
static void Main(string[] args)
{
}
}

Wasny atrybut BugFixAttribute opisuje dane dotyczce bdu nazwisko programisty,


ktry go naprawi, dat naprawy oraz komentarz. Takie informacje moe zawiera take ID
bdu. Nastpnie odpowiedni modu aplikacji odczyta wszystkie wartoci atrybutu
TBugFixAttribute, w tym ID bdu, oraz pozwoli na poczenie z baz danych, ktra na
podstawie ID ujawni wicej informacji na temat poprawki.

Deklaracja wasnego atrybutu


Aby dany atrybut by rozpoznawany przez kompilator, trzeba w aplikacji utworzy klas o
nazwie odpowiadajcej atrybutowi:
class BugFixedAttribute : Attribute
{
public BugFixedAttribute(string Programmer, string Date,
string Comment)
{
}
}

Taka klasa musi dziedziczy po Attribute. Jak widzisz, klasa BugFixedAttribute posiada
konstruktor z trzema parametrami. Jest to wany element programu, gdy kiedy uyjemy
atrybutu, jego wartoci zostan przypisane do parametrw konstruktora:
[BugFixedAttribute("Adam", "19-05-2006", "Dodaem metod")]

Uycie atrybutu w kodzie programu przypomina wywoanie metody klasy. Mamy rwnie
moliwo przekazania do niego okrelonych parametrw oddzielonych znakiem przecinka.
Istnieje moliwo jawnego przypisania parametrw atrybutu do danych waciwoci np.:
[BugFixedAttribute(Programmer = "Janek", Date = "21-06-2006",
Comment = "Poprawka")]

Wwczas w klasie BugFixedAttribute naley zadeklarowa waciwoci Programmer, Date


oraz Comment:
class BugFixedAttribute : Attribute
{
private string FProgrammer;
private string FDate;
private string FComment;
public string Programmer

{
get
{
return FProgrammer;
}
set
{
FProgrammer = value;
}
}
public string Date
{
get
{
return FDate;
}
set
{
FDate = value;
}
}
public string Comment
{
get
{
return FComment;
}
set
{
FComment = value;
}
}

public BugFixedAttribute(string AProgrammer, string ADate,


string AComment)
{
FProgrammer = AProgrammer;
FDate = ADate;
FComment = AComment;
}
public BugFixedAttribute()
{
}
}

Wydaje mi si, e zapis z konstruktorem jest szybszy i przejrzystszy, aczkolwiek nie jest zbyt
czytelny dla programisty nieznajcego parametrw atrybutu.

Odczyt wartoci atrybutu


W poprzednim przykadzie pokazywaem, w jaki sposb mona odczyta atrybuty znajdujce
si w innym podzespole. Aby odczyta atrybuty znajdujce si we wasnym programie, nie
trzeba korzysta z typu Assembly kod stanie si bardziej przejrzysty po uyciu
nastpujcej instrukcji:
System.Object[] Attrs;
Attrs =
typeof(Foo).GetCustomAttributes(typeof(BugFixedAttribute),
true);

Operator typeof jest tutaj wany, gdy zwraca typ danych (w tym przypadku klasy) w postaci
zmiennej typu System.Type. Po uzyskaniu typu mona wywoa metod
GetCustomAttributes(), pobierajc atrybuty programu.
Pierwszy parametr funkcji, GetCustomAttributes(), informuje o tym, e atrybuty maj by
jedynie typu BugFixAttributes. Jeli mamy tablic danych typu Object (Attrs), odczyt
atrybutw jest taki sam jak w poprzednio prezentowanym przykadzie.
Listing 11.5 zawiera peny kod rdowy programu odczytujcego atrybuty uyte w
programie.
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace AttrsApp
{
[AttributeUsage(AttributeTargets.All, AllowMultiple=true)]
class BugFixedAttribute : Attribute
{
private string FProgrammer;
private string FDate;
private string FComment;
public string Programmer
{
get
{
return FProgrammer;
}
set
{
FProgrammer = value;
}

}
public string Date
{
get
{
return FDate;
}
set
{
FDate = value;
}
}
public string Comment
{
get
{
return FComment;
}
set
{
FComment = value;
}
}

public BugFixedAttribute(string AProgrammer, string


ADate, string AComment)
{
FProgrammer = AProgrammer;
FDate = ADate;
FComment = AComment;
}
public BugFixedAttribute()
{
}
}
[BugFixedAttribute("Adam", "19-05-2006", "Dodaem
metod")]
[BugFixedAttribute(Programmer = "Janek", Date = "21-062006", Comment = "Poprawka")]
class Foo
{
static void Bar()
{
}
}
class Program
{
static void Main(string[] args)

{
System.Object[] Attrs;
Attrs =
typeof(Foo).GetCustomAttributes(typeof(BugFixedAttribute),
true);
for (int i = 0; i < Attrs.Length; i++)
{
Console.WriteLine("-------");
Console.WriteLine("Programista: " + (Attrs[i]
as BugFixedAttribute).Programmer);
Console.WriteLine("Data: " + (Attrs[i] as
BugFixedAttribute).Date);
Console.WriteLine("Komentarz: " + (Attrs[i] as
BugFixedAttribute).Comment);
}
Console.Read();
}
}
}

Rysunek 11.6 prezentuje program w trakcie dziaania.

Rysunek 11.6. Odczyt atrybutw uytych w programie

Uycie atrybutu

Przypatrz si dokadnie listingowi 11.5. Klasa BugFixedAttribute zostaa opatrzona


atrybutem AttributeUsage:
[AttributeUsage(AttributeTargets.All, AllowMultiple=true)]

Takie uycie oznacza, e nasza klasa BugFixedAttribute bdzie moga by uyta w


poczeniu z dowolnym elementem programu. Parametr AllowMultiple informuje, e nasz
atrybut BugFixedAttribute moe by uyty w wielu miejscach aplikacji. Warto false
spowodowaaby bd kompilacji: Duplicate 'BugFixedAttribute' attribute w przypadku
wielokrotnego uycia tego samego atrybutu.

Aplikacje .NET Framework SDK


Pakiet .NET Framework Software Developer Kit zawiera aplikacje przydatne kademu
programicie .NET. Pakiet ten nie jest wymagany do prawidowego dziaania aplikacji, lecz
moe si okaza przydatny, gdybymy chcieli np. wykorzysta deasemblera .NET czy inne
programy zwizane z t platform. Chciabym w tym miejscu wspomnie o kilku uytecznych
aplikacjach dostpnych w pakiecie SDK.
Wicej informacji na temat omawianych tu programw (np. dodatkowe parametry czy opcje)
mona znale w dokumentacji .NET Framework.
Spod adresu http://msdn.microsoft.com/netframework/downloads/updates/default.aspx moesz
cign najnowszy pakiet .NET Framework SDK.

Global Assembly Cache Tool


Obiekty COM naleao zarejestrowa przed uyciem, co powodowao tworzenie
odpowiednich wpisw w rejestrze. Dopiero pniej mona byo uy takiej kontrolki.
Jeeli chodzi o komponenty .NET, zabiegi takie nie s konieczne stosowny przykad
zaprezentowaem wczeniej w tym rozdziale, gdzie klasa z aplikacji napisanej w Delphi bya
wykorzystana w programie C#. W tamtym przykadzie skorzystaem z tzw. komponentw
prywatnych (ang. private components).
Oznaczao to, e tylko jedna nasza aplikacja moga korzysta z owego podzespou, a przy tym
naleao uwaa na cieki (katalogi), w ktrych si ona znajdowaa. Przykadowo, w
opisywanym przykadzie podzesp, z ktrego korzystamy, zawsze bdzie musia znajdowa
si w tym samym katalogu co korzystajcy z niego program.
.NET zakada moliwo tworzenia podzespow wspuytkowanych (ang. shared

components), czyli takich, ktre raz zarejestrowane zostan umieszczone w jednym gwnym
katalogu. Z takiego katalogu bdzie moga korzysta kada aplikacja, ktra zechce uy
naszego podzespou. Przestrze, w ktrej s rejestrowane takie podzespoy, nazywa si
Global Assembly Cache (w skrcie GAC) i znajduje si w katalogu C:\WINDOWS\assembly
(w moim przypadku).
Kady podzesp powinien posiada opis, nazw, informacj o prawach autorskich oraz co
bardzo wane numer wersji. Na jednej maszynie moe istnie kilka takich samych
podzespow o rnych numerach wersji. Program Global Assembly Cache Tool, kryjcy si
pod nazw gacutil.exe, umoliwia rejestracj danego podzespou jako globalnego,
wspuytkowanego.
Program gacutil jest wywoywany z poziomu wiersza polece. Aby zainstalowa dany
podzesp, naley wywoa program z opcj /i, tak jak to zrobiem poniej:
C:\Program Files\Microsoft.NET\SDK\v1.1\Bin>gacutil /i
c:\csharpexample\Assembly.dll
Microsoft (R) .NET Global Assembly Cache Utility. Version
1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights
reserved.
Assembly successfully added to the cache

Przed zainstalowaniem podzespou jako GAC zaleca si wygenerowanie specjalnego klucza,


ktry stanowi o unikalnoci jego oraz numeru jego wersji. Do tego celu naley uy narzdzia
Strong Name Tool (sn.exe), ktre wygeneruje odpowiedni plik bdcy kluczem. Plik ten z
kolei naley doczy jako atrybut do programu:
C:\Program Files\Microsoft.NET\SDK\v1.1\Bin>sn -k Assembly.snk
Microsoft (R) .NET Framework Strong Name Utility Version
1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights
reserved.
Key pair written to Assembly.snk

Program sn.exe take jest aplikacj wywoywan z poziomu wiersza polece. W celu
wygenerowania klucza naley uruchomi j z parametrem k oraz poda nazw pliku, do
ktrego klucz zostanie zapisany (tak jak to przedstawiem powyej).
Koncepcja strong names jest podobna do 128-bitowego klucza GUID (ang. Globally Unique
Identifier) w COM. GUID zapewnia unikalno klucza. Tymczasem wersja oraz nazwa
podzespou nie gwarantuj unikalnoci tak jak klucz. Dlatego te uywamy narzdzia sn.exe.
Wczenie klucza do podzespou moe nastpi za spraw uycia programu al.exe. Mona
take wczy klucz bezporednio w kodzie programu, uywajc atrybutu
AssemblyKeyFileAttribute.

Wygenerowany tym sposobem plik mona skopiowa do katalogu z kodem rdowym


programu, a nastpnie wczy go, korzystajc z atrybutu AssemblyKeyFileAttribute.
[assembly: AssemblyKeyFileAttribute("Assembly.snk")]

Rysunek 11.7 przedstawia podzesp zainstalowany w przestrzeni GAC.

Rysunek 11.7. Podzesp zainstalowany w przestrzeni GAC


Zalecane jest odpowiednie nazewnictwo podzespow w postaci
NazwaFirmy.NazwaPodzespou, np. Boduch.Foo itd. atwiej jest si wtedy zorientowa,
czyjego autorstwa jest podzesp, oraz wprowadza si pewn zasad nadawania nazw, ktra
powinna by respektowana przez wikszo programistw.

WinCV
Nazwa programu jest skrtem od sw Windows Class Viewer (ang. podgld klas). Jest to
przydatny i prosty program sucy do odnajdywania i podgldu zawartoci klas .NET
(rysunek 11.8).
{{Image:csharp11.8.jpg}}
Rysunek 11.8. Program WinCV w trakcie dziaania
Gwnym elementem programu jest pole, w ktrym naley wpisa sowo kluczowe do
wyszukania. Na licie po lewej stronie zostan wywietlone klasy zawierajce to sowo
kluczowe.
Po zaznaczeniu danej klasy w gwnym oknie zostanie wywietlona lista znajdujcych si w
niej waciwoci, pl, zdarze oraz metod wraz z nazwami oraz typem parametrw.

Narzdzie konfiguracji .NET Framework


Narzdzie konfiguracji .NET Framework kryje si pod nazw mscorcfg.msc. Jest to program,
dziki ktremu mona zarzdza GAC poprzez dodawanie lub usuwanie wybranych
podzespow. Istnieje moliwo ustawienia wielu opcji uprawnie aplikacji, wersji, jaka ma
by adowana itp.

PEVerify narzdzie weryfikacji


Narzdzie PEVerify.exe suy do weryfikacji, czy kod MSIL i metadane speniaj warunki
bezpiecznego kodu. Aby upewni si, e dana aplikacja jest bezpieczna, mona wywoa
PEVerify.exe z poziomu wiersza polece:
C:\Program Files\Microsoft.NET\SDK\v1.1\Bin>PEVerify C:\P6_1.exe
Microsoft (R) .NET Framework PE Verifier Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
All Classes and Methods in C:\P6_1.exe Verified
Jeeli po wywoaniu aplikacji zostanie wywietlony komunikat All Classes and Methods in
C:\P6_1.exe Verified, mona by pewnym, e jest ona bezpieczna i zweryfikowana.

.NET a COM
Pokazaem, w jaki sposb poszczeglne podzespoy mog komunikowa si ze sob oraz
wykorzystywa klasy z innych podzespow. Jednak cay ten proces odbywa si w obrbie
kodu zarzdzanego (managed code). W tym podrozdziale zaprezentuj sposb stosowania
kodu niezarzdzanego (kontrolki COM) w jzyku C# na .NET.
Import obiektu COM do aplikacji .NET jest dosy prosty. Obiekty Win32 COM mog zosta
zaimportowane do .NET za pomoc narzdzia o nazwie Tlbimp.exe, doczonego do .NET
Framework SDK.
Zasady importowania obiektw COM do .NET przedstawi na przykadzie kontrolki SAPI
(Microsoft Speech API). Najpierw odszukajmy na dysku plik sapi.dll. Potem z poziomu
wiersza polece trzeba uruchomi program Tlbimp.exe, ktry zaimportuje kontrolk COM do
.NET:
tlbimp "C:\Scieka do pliku\sapi.dll" /verbose /out:C:\Interop.SAPI.dll

Takie uycie programu spowoduje utworzenie podzespou Interop.SAPI.dll, ktry mona w


peni wykorzysta w rodowisku .NET. Normalne jest, e podczas konwersji na konsoli jest
wywietlana masa komunikatw ostrzegajcych o moliwej niekompatybilnoci owej
kontrolki z .NET.
Gdy utworzymy podzesp, trzeba skopiowa go do katalogu, w ktrym nastpnie naley
umieci nowy projekt WinForms. W oknie Solution naley doda odwoanie do pliku
Interop.SAPI.dll. Interfejs aplikacji skada si z komponentu typu RichTextBox oraz Button
(rysunek 11.9). Po klikniciu przycisku cig znakowy z pola tekstowego zostanie przekazany
do biblioteki, co spowoduje wywoanie lektora, ktry przeczyta tekst.

Rysunek 11.9. Przykad uycia kontrolki COM w aplikacji .NET


Uycie kodu z biblioteki COM wie si z dodaniem odpowiedniej przestrzeni nazw
Interop.SAPI. Kod rdowy aplikacji wykorzystujcej t bibliotek COM wyglda tak:
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Text;
System.Windows.Forms;
Interop.SAPI;

namespace SAPI
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void btnTalk_Click(object sender, EventArgs e)
{

SpVoice Voice;
Voice = new SpVoice();
Voice.Speak(richTextBox1.Text,
SpeechVoiceSpeakFlags.SVSFDefault);
}
}
}

PInvoke
.NET jest now platform programistyczn. Minie jeszcze sporo czasu, zanim programici
przystosuj swoje aplikacje do nowej platformy oraz obdarz j zaufaniem. Mimo i .NET
udostpnia dziesitki klas umoliwiajcych atwiejsze programowanie, w niektrych
przypadkach nadal konieczne moe okaza si wykorzystanie funkcji udostpnionych przez
WinAPI.
W wielu przypadkach uycie funkcji Win32 stanie si wrcz niezastpione, tak wic w tej
czci rozdziau zajmiemy si wykorzystaniem bibliotek Win32 DLL w aplikacjach .NET.
W tym celu bdziemy korzysta z mechanizmu zwanego Platform Invocation Service, czyli
Platform Invoke, zwanego w skrcie PInvoke (lub P/Invoke). Mechanizm w pozwala na
importowanie funkcji z bibliotek Win32 DLL za pomoc atrybutu [DllImport].
Dla przypomnienia powiem, i system Windows udostpnia interfejs programistyczny zwany
WinAPI (pisaem o tym w rozdziale 2.), ktry zawiera setki funkcji moliwych do
wykorzystania w naszych aplikacjach. Funkcje te zawarte s w tzw. bibliotekach DLL.
Biblioteki DLL to pliki z rozszerzeniem .dll, zawierajce skompilowany kod. Nie s to jednak
aplikacje wykonywalne (takie jak .exe).

Uycie funkcji Win32 API


Aby skorzysta z funkcji Win32 API w kodzie zarzdzanym, naley przede wszystkim
utworzy prototyp funkcji w kodzie zarzdzanym. Dla przykadu poka sposb uycia
funkcji GetUserName(), ktra znajduje si w bibliotece Advapi32.dll. Zwraca ona nazw
uytkownika zalogowanego w systemie.
Aby utworzy prototyp funkcji, naley wiedzie, jak wyglda ich budowa w bibliotekach
DLL. Windows by pisany w jzyku C, std te przykady w dokumentacji WinAPI s
zapisane w tym jzyku. Wiele funkcji zwracao rezultaty do parametru referencyjnego

warto tu wspomnie chociaby o funkcjach GetUserName czy GetComputerName. Ich


deklaracja w C wygldaa tak:
BOOL GetComputerName(
LPTSTR lpBuffer,
LPDWORD nSize
);
BOOL GetUserName(
LPTSTR lpBuffer,
LPDWORD nSize
);

// address of name buffer


// address of size of name buffer

// address of name buffer


// address of size of name buffer

Dla przykadu poka, w jaki sposb pobra nazw uytkownika (funkcja GetUserName())
oraz ciek do systemu Windows (funkcja GetWindowsDirectory()). Ich prototyp w kodzie
zarzdzanym wyglda tak:
[DllImport("kernel32.dll")]
static extern bool GetWindowsDirectory(StringBuilder lpBuffer,
ref int nSize);
[DllImport("Advapi32.dll")]
static extern bool GetUserName(StringBuilder lpBuffer, ref int
nSize);

Obie metody zostay opatrzone sowem kluczowym extern. Informuje ono kompilator, e
metody bd adowane z bibliotek zewntrznych. Metody zostay opatrzone atrybutem
DLLImport, okrelajcym nazw biblioteki, z ktrej adowane bd funkcje. Listing 11.6
zawiera program, ktry wykorzystuje funkcje Win32API.
Listing 11.6. Przykad uycia funkcji Win32 API
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace PInvoke
{
class Program
{
[DllImport("kernel32.dll")]
static extern bool GetWindowsDirectory(StringBuilder
lpBuffer, ref int nSize);
[DllImport("Advapi32.dll")]
static extern bool GetUserName(StringBuilder lpBuffer,
ref int nSize);
static void Main(string[] args)
{
StringBuilder Buffer = new StringBuilder(64);

int nSize = 64;


GetWindowsDirectory(Buffer, ref nSize);
Console.WriteLine("cieka do katalogu Windowsa:
{0}", Buffer.ToString());
GetUserName(Buffer, ref nSize);
Console.WriteLine("Nazwa uytkownika: {0}",
Buffer.ToString());
}
}
}

Interesujce nas dane (cieka do systemu Windows oraz nazwa uytkownika) zostan
przypisane do zmiennej Buffer typu StringBuilder.
Aby wykorzysta funkcje Win32 API, naley zna ich budow (parametry oraz typy). Tego
moesz dowiedzie si z dokumentacji firmy Microsoft, znajdujcej si pod adresem
http://msdn.microsoft.com.

Uycie atrybutu DLLImport


Przed skorzystaniem z atrybutu DllImport trzeba doczy do programu przestrze nazw
System.Runtime.InteropServices. Uycie tego atrybutu w najprostszym wydaniu
prezentuje poprzedni przykad. Jego budowa jest do prosta, gdy w takim przypadku naley
poda jedynie nazw biblioteki DLL:
[DllImport("Nazwa biblioteki DLL")];

Taki atrybut nie posiada adnych dodatkowych parametrw. Normalnie jest moliwe
okrelenie konwencji wywoania parametrw (cdecl, stdcall itp.), nazwy adowanej
procedury bd funkcji oraz kodowania cigw znakowych (Unicode, ANSI String).
Parametry atrybutu DllImport mona okrela tak:
[DllImport("SimpleDLL.dll", CallingConvention =
CallingConvention.Stdcall, EntryPoint="About")]

W powyszym przykadzie sprecyzowaem sposb wywoania parametrw funkcji (parametr


CallingConvention) oraz okreliem dokadnie nazw importowanej funkcji (EntryPoint).
Istnieje jeszcze jeden parametr uywany tylko w przypadku, gdy w parametrze funkcji lub

procedury znajduje si cig znakw (String). Tym parametrem jest CharSet, ktry okrela
kodowanie:

Ansi cig znakowy ANSI,


Unicode cigi znakowe Unikodu,
None oznacza to samo co parametr Ansi.

W przypadku Win32 API wiele funkcji miao dwie odmiany jedn z parametrem typu
PAnsiChar, a drug z PWideChar. Dla przypomnienia: wszystkie cigi znakowe w .NET s
zapisane w Unicode, take typ String.
Wspomniaem tutaj o konwencji wywoania. Jest to zaawansowane zagadnienie zwizane z
przekazywaniem wartoci do parametrw funkcji, a konkretnie ich umieszczaniem w pamici.
Wyjanienie tych poj wykracza poza ramy niniejszej publikacji.
Zasadniczo technika PInvoke ma o wiele wiksze zastosowanie. Ja pokazaem jedynie prosty
przykad wywoania funkcji Win32 API, lecz ze wzgldu na rnice pomidzy systemem Win32
a .NET niekiedy uycie funkcji API moe okaza si o wiele trudniejsze. Jeeli jeste
zainteresowany technologi PInvoke, odsyam do dokumentacji platformy .NET.

Podsumowanie
Moliwo integracji poszczeglnych podzespow to wielka zaleta platformy .NET, dajca
ogromne moliwoci. Naley uwiadomi sobie, e rodowisko .NET Framework to nie tylko
biblioteka klas czy biblioteka WinForms, ale rwnie CLS, czyli wsplny jzyk
programowania. Dziki temu niewane jest, w jakim jzyku piszemy swoje aplikacje,
poniewa wygenerowany kod poredni zawsze bdzie taki sam, niezalenie od jzyka. Mam
nadziej, e po przeczytaniu niniejszego rozdziau masz pewn wiadomo moliwoci
pyncych ze wspdzielenia podzespow.
[1] W tej czci rozdziau terminem komponent .NET bd okrela podzesp (ang.
assembly), czyli zwyk aplikacj skompilowan do kodu IL i posiadajc rozszerzenie .exe
lub .dll.

Rozdzia 12
Pliki i obsuga strumieni
Czym jest plik

? Tego chyba nie trzeba wyjania adnemu uytkownikowi komputera. Istnieje kilka
rodzajw plikw: tekstowe, binarne, typowane itp. Pliki s wykorzystywane przez programy
do pobierania lub przechowywania informacji. Mog te zawiera binarne fragmenty
programu. Ten rozdzia bdzie powicony plikom i ich obsudze w C#. Zaczniemy od rzeczy
najprostszych, przechodzc do coraz bardziej zaawansowanych aspektw.
Biblioteka klas rodowiska .NET
Framework zawiera szereg klas umoliwiajcych obsug plikw oraz katalogw. Klasy te
znajduj si w przestrzeni nazw System.IO.
IO to potoczne okrelenie operacji wejcia-wyjcia (ang. input-output).

Czym s strumienie
Strumienie s specjaln form wymiany i transportu danych, obsugiwan przez klasy
przestrzeni System.IO. To okrelenie moe nie jest zbyt precyzyjne, ale zaraz postaram si
wyjani to szczegowo.
Dziki strumieniom mona w prosty sposb operowa na danych znajdujcych si w pamici
komputera, w plikach itp. Przykadowo, strumie moe by plikiem, pamici operacyjn lub
wspdzielonym zasobem
sieciowym.

Klasy przestrzeni System.IO


Do operowania na plikach i katalogach moemy wykorzysta klasy opisane w tabeli 12.1.
Niektre klasy zawieraj metody statyczne, wic nie jest konieczne tworzenie ich instancji.
Tabela 12.1. Podstawowe klasy operowania na plikach i katalogach
Klasa
Opis
Udostpnia metody suce do operowania na katalogach (przenoszenie,
Directory
kopiowanie).
Klasa udostpnia podstawowe mechanizmy pozwalajce na tworzenie,
File
usuwanie oraz przenoszenie plikw.
Klasa suy do przetwarzania informacji o ciekach katalogw oraz
Path
plikw.
Ma dziaanie podobne do klasy Directory. Jeeli dokonujemy wielu dziaa
DirectoryInfo na katalogach, jest to optymalna klasa, gdy jej metody nie wykonuj tzw.
testw bezpieczestwa.
Ma dziaanie podobne do klasy File. Jeeli dokonujemy wielu dziaa na
FileInfo
plikach, jest to optymalna klasa, gdy jej metody nie wykonuj testw
bezpieczestwa.
FileSystemInfo Klasa bazowa dla klas DirectoryInfo oraz FileInfo.

Operacje na katalogach
Do operowania na katalogach wykorzystujemy klas Directory lub DirectoryInfo.
Directory moe by wygodnym sposobem prostego operowania na katalogach, gdy nie
wymaga tworzenia egzemplarza klasy. Jeeli jednak musimy wykona wiele operacji na
katalogach, wydajniejszym sposobem bdzie skorzystanie z klasy DirectoryInfo.

Tworzenie i usuwanie katalogw


Jeeli chcemy utworzy nowy katalog, naley skorzysta z metody CreateDirectory() z
klasy Directory:
if (!Directory.Exists("C:\\Foo"))
{
Directory.CreateDirectory("C:\\Foo");
}

Zwr uwag, e przed utworzeniem katalogu nastpuje sprawdzenie, czy przypadkiem on


ju nie istnieje (metoda Exists()). Nie jest to jednak konieczne, gdy w przypadku gdy
tworzony katalog istnieje na dysku, aden wyjtek nie zostanie wygenerowany.
Przy pomocy metody CreateDirectory() moemy utworzy katalog, nawet wwczas gdy
nie istnieje katalog macierzysty. Np.:
Directory.CreateDirectory("C:\\Foo\\Bar");

cieka C:\Foo\Bar zostanie utworzona, nawet gdy na dysku nie ma katalogu Foo.
Pamitaj o tym, aby w trakcie podawania cieek w acuchu korzysta z podwjnych
backslashw (\\). Podobnie jak w jzykach C/C++, kompilator po znaku \ oczekuje symbolu
specjalnego, takiego jak np. \n, ktry oznacza now lini.
Alternatywny kod korzystajcy z klasy DirectoryInfo, rwnie tworzcy nowy katalog,
wyglda nastpujco:
DirectoryInfo dInfo = new DirectoryInfo("C:\\Foo");

if (!dInfo.Exists)
{
dInfo.Create();
}

Jak widzisz, w konstruktorze klasy DirectoryInfo musimy poda ciek do katalogu, na


jakim bdziemy operowali. Za tworzenie nowego folderu odpowiada metoda Create(),
natomiast waciwo Exists zwraca warto true, jeeli cieka przekazana w konstruktorze
istnieje.
Jeeli chcemy usun dany katalog, moemy skorzysta z metody Delete() klasy
DirectoryInfo:
if (dInfo.Exists)
{
dInfo.Delete();
}

Klasa DirectoryInfo udostpnia przecion metod Delete(). Jej druga wersja moe
posiada parametr typu bool, ktry okrela, czy katalog bdzie usuwany nawet wwczas, gdy
posiada inne pliki i foldery (warto true).

Kopiowanie i przenoszenie
Podczas tworzenia aplikacji
przetwarzajcych z katalogami moe zaistnie konieczno skopiowania i (lub) przeniesienia
pewnych folderw. Okazuje si, e klasy Directory i DirectoryInfo udostpniaj tylko
metody do przenoszenia katalogw. Zaprezentuj rwnie technik umoliwiajc
kopiowanie caych katalogw, gdy nie wiedzie czemu takiej moliwoci brakuje w
bibliotece klas FCL.
Przenoszenie katalogw jest proste. Klasa Directory udostpnia metod Move(), ktra
przyjmuje dwa parametry katalog rdowy oraz docelowy:
Directory.Move("C:\\Foo", "C:\\Bar");

Przenoszenie jest proste. Naley jednak zastanowi si nad metod kopiowania katalogw,
ktr trzeba napisa samodzielnie. Metoda, ktr za chwil zaprezentuj, wykorzystuje
elementy nieomwione do tej pory omwi je w dalszej czci ksiki.
Metoda, ktra realizuje kopiowanie katalogu, przedstawiona zostaa poniej:

private void CopyDir(string SourceDir, string TargetDir)


{
string[] Files;
FileAttributes Attr;
string srcFile;
// sprawdzenie, czy na kocu cieki znajduje si
separator \
if
(!SourceDir.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
SourceDir += Path.DirectorySeparatorChar;
}
if
(!TargetDir.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
TargetDir += Path.DirectorySeparatorChar;
}
// pobranie listy plikw z katalogu
Files = Directory.GetFileSystemEntries(SourceDir);
// utworzenie katalogu docelowego
Directory.CreateDirectory(TargetDir);
for (int i = 0; i < Files.Length; i++)
{
// pobranie atrybutu pliku
Attr = File.GetAttributes(Files[i]);
// pobranie nazwy pliku
srcFile = Path.GetFileName(Files[i]);
// warunek sprawdza, czy plik jest katalogiem
if (FileAttributes.Directory == Attr)
{
if (!Directory.Exists(TargetDir + srcFile))
{
Directory.CreateDirectory(TargetDir +
srcFile);
}
// wywoanie rekurencyjne
CopyDir(Files[i], TargetDir + srcFile);
}
else
{
// skopiowanie pliku z katalogu
File.Copy(Files[i], TargetDir + srcFile);
}
}
}

Prywatna metoda ma dwa parametry ciek katalogu rdowego oraz docelowego.


Pierwsze dwie instrukcje warunkowe sprawdzaj, czy na kocu cieki znajduje si znak
ukonika (\ w systemie Windows lub / w Linuksie). W kolejnych wierszach kodu pobieramy
list katalogw i plikw znajdujcych si w danym
folderze. Dalej w ptli s pobierane atrybuty pliku, gdy trzeba sprawdzi, czy kopiowany
plik nie jest katalogiem. Jeeli jest, stosujemy rekurencj i kopiujemy zawarto podkatalogu.
Jeeli nie po prostu kopiujemy plik z katalogu rdowego do docelowego.
Metoda rekurencyjna to taka, ktra wywouje sam siebie.
Wykorzystanie takiej metody jest bardzo proste. Moemy pobra od uytkownika
cieki katalogu rdowego oraz docelowego i wywoa metod:
CopyDir(edtSrc.Text, edtDst.Text);

Odczytywanie informacji o katalogu


Klasa DirectoryInfo posiada kilka uytecznych metod sucych do odczytu informacji o
katalogach. Opis najwaniejszych waciwoci znajduje si w tabeli 12.2.
Tabela 12.2. Waciwoci klasy DirectoryInfo
Waciwo
Opis
Attributes
Atrybuty katalogu
CreationTime
Czas utworzenia katalogu
Czas utworzenia katalogu w formacie UTC (ang. Coordinal Universal
CreationTimeUtc
Time UTC)
FullName
Pena cieka do katalogu
LastAccessTime
Czas ostatniego dostpu do katalogu
LastAccessTimeUtc Czas ostatniego dostpu do katalogu w formacie UTC
LastWriteTime
Czas ostatniego zapisu do katalogu
LastWriteTimeUtc Czas ostatniego zapisu do katalogu
Name
Zwraca nazw katalogu
FullName
Zwraca pen ciek do katalogu
Parent
Zwraca macierzysty katalog

Listing 12.1 prezentuje przykad odczytu informacji o katalogu, w ktrym znajduje si


uruchamiany program (rysunek 12.1).
Listing 12.1. Przykad odczytu informacji o katalogu

using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Text;
System.Windows.Forms;
System.IO;

namespace WindowsApplication1
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void btnLoad_Click(object sender, EventArgs e)
{
DirectoryInfo dirInfo = new
DirectoryInfo(Application.StartupPath);
lbDirectory.Items.Add(
"Pena cieka: " + dirInfo.FullName);
lbDirectory.Items.Add(
"Katalog macierzysty: " + dirInfo.Parent);
lbDirectory.Items.Add(
"Data utworzenia: " +
dirInfo.CreationTime.ToShortDateString());
lbDirectory.Items.Add(
"Data ostatniego dostpu: " +
dirInfo.LastAccessTime.ToShortDateString());
lbDirectory.Items.Add(
"Data zapisu: " +
dirInfo.LastWriteTime.ToShortDateString());
lbDirectory.Items.Add(
"Atrybuty: " + dirInfo.Attributes.ToString());
}
}
}

Rysunek 12.1. Przykad odczytu informacji o katalogu


Metody zwracajce czas ostatniej modyfikacji katalogu (ewentualnie dostpu) zwracaj
informacje o dacie i czasie, w formacie DateTime. Ta klasa posiada metod
ToShortDateString(), ktra suy do acuchowego prezentowania daty w krtkim
formacie.

Obsuga plikw
Tak jak omwione w poprzednim podrozdziale klasy Directory i DirectoryInfo su do
obsugi katalogw, tak File oraz FileInfo su do obsugi plikw. Z klasy File
skorzystaem ju wczeniej, podczas prezentowania sposobu na skopiowanie katalogu. Teraz
chciabym skupi si na podstawowych operacjach, jakich dokonujemy na plikach.

Tworzenie i usuwanie plikw


Chyba najprostszym sposobem na utworzenie nowego pliku jest uycie metody
CreateText() z klasy File:
if (!File.Exists("C:\\foo.txt"))
{
File.CreateText("C:\\foo.txt");
}

Tworzy ona nowy plik gotowy do zapisu tekstu z kodowaniem UTF-8. Oczywicie

parametrem musi by cieka do tworzonego pliku.


Metoda Exists() zwraca informacj, czy plik, ktrego cieka przekazana jest w parametrze,
istnieje (wwczas zwraca warto true).
Alternatywnym rozwizaniem jest uycie klasy FileInfo:
FileInfo F = new FileInfo("C:\\foo.txt");
if (!F.Exists)
{
F.CreateText();
}

Oczywicie utworzony w ten sposb plik bdzie pusty. Ani klasa File, ani FileInfo nie
oferuje metod sucych do zapisu lub odczytu danych z pliku. W tym celu mona skorzysta
z klasy StreamWriter, ktrej obiekt jest zwracany przez metod CreateText():
if (!File.Exists("C:\\foo.txt"))
{
StreamWriter sw = File.CreateText("C:\\foo.txt");
sw.WriteLine("Hello World!");
sw.Close();
}

W tym przykadzie metoda WriteLine() zapisuje now lini do pliku tekstowego. Taki plik
naley koniecznie zamkn, korzystajc z metody Close().
Usuwanie plikw jest rwnie proste jak usuwanie katalogw. Suy do tego metoda
Delete():
File.Delete("C:\\foo.txt");

Wersja klasy FileInfo:


FileInfo F = new FileInfo("C:\\foo.txt");
F.Delete();

Kopiowanie i przenoszenie plikw

Poprzednio w rozdziale uywaem ju metody Copy() z klasy File. Oczywicie, jak si


domylasz, realizuje ona kopiowanie pliku. W parametrach metody naley poda ciek
rdow oraz docelow:
string srcPath = "C:\\foo.txt";
string dstPath = "C:\\bar.txt";
if (!File.Exists(dstPath))
{
File.Copy(srcPath, dstPath);
}

Przenoszenie realizuje metoda Move():


string srcPath = "C:\\foo.txt";
string dstPath = "C:\\bar.txt";
if (!File.Exists(dstPath))
{
File.Move(srcPath, dstPath);
}

Dobr praktyk jest sprawdzanie (przy pomocy metody


<kbd>Exists()</kbd>), czy pod ciek docelow nie znajduje
si jaki plik.

Odczytywanie informacji o pliku


Podobnie jak klasa DirectoryInfo udostpnia informacje o katalogu, do odczytania
informacji o plikach moemy uy klasy FileInfo.
Kilka z nich omwiem w tabeli 12.3.
Tabela 12.3. Waciwoci klasy FileInfo
Waciwo
Opis
Name
Nazwa pliku
Length
Rozmiar pliku (ilo bajtw)
DirectoryName Nazwa katalogu, w ktrym znajduje si plik
Extension
Rozszerzenie pliku
Attributes
Atrybuty pliku

CreationTime Czas utworzenia pliku


LastAccessTime Czas ostatniego dostpu do pliku
LastWriteTime Czas zapisu do pliku

Rysunek 12.2 prezentuje aplikacj, ktra odczytuje informacje o pliku i dodaje je kolejno do
komponentu typu ListBox.

Rysunek 12.2. Odczytywanie informacji o pliku


Listing 12.2 zawiera kod rdowy programu zaprezentowanego na rysunku 12.2.
Listing 12.2. Analizowanie informacji o pliku
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace WinForms
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (!File.Exists("C:\\Foo.txt"))

{
File.CreateText("C:\\Foo.txt");
}
FileInfo F = new FileInfo("C:\\Foo.txt");
lbFile.Items.Add(
"Nazwa pliku: " + F.Name);
lbFile.Items.Add(
"Rozmiar pliku: " + F.Length);
lbFile.Items.Add(
"Nazwa katalogu: " + F.DirectoryName);
lbFile.Items.Add(
"Rozszerzenie: " + F.Extension);
lbFile.Items.Add(
"Atrybuty: " + F.Attributes.ToString());
lbFile.Items.Add(
"Czas utworzenia pliku: " +
F.CreationTime.ToShortDateString());
lbFile.Items.Add(
"Czas dostpu: " +
F.LastAccessTime.ToShortDateString());
lbFile.Items.Add(
"Czas ostatniej modyfikacji: " +
F.LastWriteTime.ToShortDateString());
File.Delete("C:\\Foo.txt");
}
}
}

Strumienie
O strumieniach moesz myle jako o cigach danych. W rodowisku .NET Framework
dostpnych jest wiele klas obsugujcych strumienie, w zalenoci od medium
przechowujcego te dane. I tak do obsugi strumieni plikw wykorzystujemy klas
FileStream, natomiast klasa MemoryStream reprezentuje strumienie znajdujce si w
pamici operacyjnej. Pomimo tych rnic wykorzystywanie tych klas jest niemale
identyczne.
Wspomniaem o klasach reprezentujcych dane. Jednake do odczytywania i zapisywania
danych do strumieni uywamy odrbnych klas StreamReader oraz StreamWriter. W
przypadku danych binarnych s to odpowiednio klasy BinaryWriter i BinaryReader.
Moliwoci stosowania tych klas w praktyce zostan zilustrowane przykadami w kolejnych
punktach.

Obsuga plikw tekstowych


Podstawowe operacje na plikach tekstowych, jakie wykonujemy, programujc, to:
zapisywanie, odczytywanie oraz ewentualnie przemieszczanie si w pliku tekstowym.
Zacznijmy od utworzenia egzemplarza klasy FileStream. Klasa posiada wiele przecionych
konstruktorw. Jeden z nich wymaga podania trzech parametrw:

cieki do pliku,
trybu otwarcia pliku,
trybu dostpu do pliku.

Typowe utworzenie nowego egzemplarza moe wyglda tak:


FileStream fs = new FileStream("C:\\Foo.txt",
FileMode.OpenOrCreate, FileAccess.ReadWrite);

W drugim i trzecim parametrze okrelony zosta tryb otwarcia oraz dostpu do pliku. S to
typy wyliczeniowe, ktrych wartoci opisaem w tabeli 12.4 oraz 12.5.
Tabela 12.4. Wartoci typu wyliczeniowego FileMode
Warto
Opis
Tworzy lub otwiera plik i przechodzi na jego koniec. Wymaga utworzenia
Append
obiektu z parametrem FileAccess.Write.
Tworzy plik, a w razie gdy on ju istnieje, zastpuje jego dotychczasow
Create
zawarto.
CreateNew
Tworzy plik, a w razie gdy on ju istnieje, generuje odpowiedni wyjtek.
Otwiera nowy plik do odczytu. Jeeli plik nie istnieje, generowany jest
Open
wyjtek.
OpenOrCreate Otwiera plik, a jeeli ten nie istnieje tworzy nowy.
Truncate
Otwiera plik i czyci jego zawarto.

Tabela 12.5. Wartoci typu wyliczeniowego FileAccess


Warto
Opis
Read
Dane mog by jedynie odczytywane.
Write
Dane mog by tylko zapisywane.
ReadWrite Dane mog by zarwno zapisywane, jak i odczytywane.

Powyszy kod tworzcy obiekt klasy FileStream prbuje otworzy plik. Jeeli ten nie
istnieje tworzy nowy. Plik jest otwierany zarwno do odczytu, jak i do zapisu.
Aby zapisa warto w pliku tekstowym, naley utworzy rwnie egzemplarz klasy
StreamWriter. W parametrze konstruktora tej klasy naley przekaza obiekt klasy
FileStream (patrz listing 12.3).
Listing 12.3. Przykad tworzenia oraz zapisania wartoci do pliku
using System;
using System.IO;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
FileStream fs = new FileStream("C:\\Foo.txt",
FileMode.OpenOrCreate, FileAccess.ReadWrite);
try
{
StreamWriter sw = new StreamWriter(fs);
sw.WriteLine("Hello World!");
sw.WriteLine("Bye!");
sw.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}

Podobnie jak metoda WriteLine() z klasy Console wywietla now lini w oknie konsoli,
tak metoda WriteLine() z klasy StreamWriter zapisuje now lini w pliku tekstowym.
Naley wspomnie o tym, e wywoanie metody WriteLine() nie rwna si bezporedniemu
zapisaniu do pliku, lecz do bufora pamici. Naley pamita o wywoaniu metody Close()
lub Flush(), ktra czyci bufor.
Aby odczyta zawarto pliku tekstowego, moemy skorzysta z klasy StreamReader. Jej
uycie jest podobne jak w przypadku wczeniej wspominanej klasy StreamWriter. Listing
12.4 prezentuje przykad zaadowania do komponentu typu RichtextBox zawartoci pliku
tekstowego wskazanego przez uytkownika.
Listing 12.4. Przykad zaadowania zawartoci pliku

using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Text;
System.Windows.Forms;
System.IO;

namespace WinForms
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
openDialog.ShowDialog();
FileStream fs = new
FileStream(openDialog.FileName,
FileMode.Open, FileAccess.Read);
try
{
StreamReader sr = new StreamReader(fs);
richText.Text = sr.ReadToEnd();
sr.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
}

Do operacji odczytu zawartoci pliku suy metoda ReadToEnd(). Odczytuje ona zawarto
caego pliku, zwracajc j w formie acucha string. Tak wic przy wikszych plikach uycie
tej metody moe wpyn na zawarto pamici, jaka jest uywana przez nasz aplikacj. W
przypadku wikszych plikw mona skorzysta z metody ReadLine(), ktra odczytuje
kolejne linie pliku tekstowego. T metod naley odtwarza w ptli:
while (!sr.EndOfStream)
{

richText.Text += sr.ReadLine();
}

Waciwo EndOfStream zwraca true, jeeli napotkano koniec pliku. W przypadku


strumieni istnieje pojcie biecej pozycji wewntrz reprezentowanego bloku danych, z ktrej
dane s odczytywane lub na ktrej s zapisywane. Moe to by albo pocztek strumienia, albo
koniec, albo wybrany punkt pomidzy jego pocztkiem a kocem.
Do przesuwania wewntrznej pozycji strumienia suy metoda Seek(), w ktrej moemy
poda ilo bajtw, o jak zostanie przesunity wskanik:
sr.BaseStream.Seek(10, SeekOrigin.Begin);

Pozycja odczytu-zapisu w strumieniu jest reprezentowana za pomoc wartoci typu


wyliczeniowego SeekOrigin (patrz tabela 12.6).
Tabela 12.6. Wartoci typu wyliczeniowego SeekOrigin
Warto
Opis
Begin
Reprezentuje pocztek strumienia.
Current Reprezentuje biec pozycj w strumieniu.
End
Reprezentuje koniec strumienia.

Czyli np. wywoanie:


se.BaseStream.Seek(0, SeekOrigin.End);

oznacza ustawienie wskanika na kocu pliku.


Rozwizaniem alternatywnym do zaprezentowanej przed chwil waciwoci EndOfStream
jest metoda Peek(). Zwraca ona numer kolejnego znaku (typ int), ale nie zmienia
dotychczasowego pooenia wskanika. Jeeli warto zwrcona przez t metod jest rwna 1, oznacza to, e mamy do czynienia z kocem pliku:
while (sr.Peek() != -1)
{
// kod
}

Klasa StreamReader udostpnia rwnie metod ReadBlock(), ktra umoliwia odczytanie


konkretnego fragmentu pliku. Pierwszy parametr musi wskazywa na zmienn tablicow
(typu char), do ktrej zostanie przypisana odczytana warto. Kolejny parametr oznacza
warto pocztkow, od ktrej rozpocznie si kopiowanie znakw, a ostatni ilo znakw
do skopiowania. Np.:

char[] buff = new char[100];


sr.ReadBlock(buff, 0, 100);

Operacje na danych binarnych


Praca z danymi binarnymi jest bardzo podobna do pracy ze zwykymi plikami tekstowymi.
Rnica jest taka, i w tych drugich do odczytu oraz zapisu stosujemy klasy BinaryRead oraz
BinaryWrite.
Przy pomocy klasy BinaryWrite moemy zapisa do pliku dane wielu rodzajw, poczwszy
od acuchw tekstowych, na liczbach i tablicach typu char skoczywszy. Wszystko dziki
przecionej metodzie Write().
Oto przykad:
char[] MyChar = { 'H', 'e', 'l', 'l', 'o' };
FileStream fs = new FileStream("C:\\Data.dat",
FileMode.OpenOrCreate, FileAccess.ReadWrite);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write("Hello World!");
bw.Write(12.23);
bw.Write(true);
bw.Write(MyChar);
bw.Close();

Konstruktor klasy BinaryWriter jako parametru rwnie wymaga obiektu klasy


FileStream.
Do odczytu danych z pliku binarnego musimy uy odpowiednich metod, ktrych nazwa
tworzona jest ze sw Read oraz typu danych. Np. za odczyt danych typu string odpowiada
metoda ReadString(). Poniej zaprezentowaem kod odpowiadajcy za zaadowanie i
wywietlenie danych binarnych:
FileStream fs = new FileStream("C:\\Data.dat",
FileMode.OpenOrCreate, FileAccess.ReadWrite);
BinaryReader bw = new BinaryReader(fs);
Console.WriteLine("String: {0}", bw.ReadString());
Console.WriteLine("Double: {0}", bw.ReadDouble());

Console.WriteLine("Boolean: {0}", bw.ReadBoolean());


Console.WriteLine("Char array: {0}", bw.ReadChars(5));
Console.Read();

Metoda ReadChars(), w odrnieniu od pozostaych, jako parametru wymaga iloci znakw,


ktre zostan odczytane.

Serializacja
Serializacja jest procesem umoliwiajcym zapisywanie informacji o obiektach klas w
plikach tekstowych lub binarnych. cilej mwic, jest procesem przeksztacania klas w
strumie bajtw, ktry moe by utrwalany na noniku danych, przesyany do innego procesu
lub nawet na inny komputer. Procesem odwrotnym jest deserializacja (ang. deserialization),
ktra polega na odczytaniu strumienia bajtw z pliku lub zdalnego komputera i
zrekonstruowaniu zawartej w nim klasy wcznie z jej zapisanym stanem.
Dziki serializacji moemy w kadej chwili zapisa stan, w jakim znajduje si obiekt, wraz z
wartociami jego elementw. Kiedy obiekt jest serializowany, rodowisko uruchomieniowe
CLR wewntrznie buduje graf obiektw. Umoliwia on temu rodowisku obsug
utrwalanych obiektw wraz ze wszystkimi obiektami, ktre s z nimi powizane.
Seralizowana klasa musi by opatrzona atrybutem [Serializable]. Serializowane s
wszystkie elementy klasy, pod warunkiem e nie zostay opatrzone atrybutem
[NonSerialized]:
[Serializable]
class Foo
{
int i;
string s;
[NonSerialized]
double d;
}

Formaty zapisu danych


Platforma .NET Framework udostpnia dwa formaty zapisu serializowanych danych
binarny i SOAP. Naley pamita, e platforma .NET Framework nie ogranicza liczby tych
formatw w razie potrzeby moemy wic tworzy wasne formaty. Aby zapisa klasy w
formacie binarnym, musimy uy klasy BinaryFormatter, nalecej do przestrzeni nazw

System.Runtime.Serialization.Formatters.Binary. Uycie formatu SOAP wymaga


wykorzystania klasy SoapFormatter, nalecej do przestrzeni nazw
System.Runtime.Serialization.Formatters.Soap.

SOAP ang. Simple Object Access Protocol jest jzykiem opartym na XML,
wykorzystywanym przez usugi sieciowe do wywoywania procedur. Protok SOAP okrela
format przesyanych danych, nazwy parametrw itp. Jzyk XML zostanie omwiony w
kolejnym rozdziale ksiki.
Klasa SoapFormatter nie jest zalecana jako format przechowywanych danych. W wersji 2.0
biblioteki klas FCL ta klasa moe by nieobecna.

Przykad serializacji
Listing 12.5 zawiera przykadowy kod rdowy, ktry dokonuje procesu serializacji, a
nastpnie deserializacji danych. Serializowane dane s zapisywane na dysku C: pod nazw
data.dat.
Listing 12.5. Przykad serializacji
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleApp
{
public enum GenreEnum { Fish, Mammal };
[Serializable]
public struct Animal
{
public string Name;
public int Age;
public GenreEnum Genre;
}
class Program
{
static void Main(string[] args)
{
// deklaracja tablicy struktur
Animal[] MyPet = new Animal[2];
// wypenienie tablicy struktur
MyPet[0].Name = "Pies";
MyPet[0].Age = 10;
MyPet[0].Genre = GenreEnum. Mammal;

MyPet[1].Name = "Ryba";
MyPet[1].Age = 1;
MyPet[1].Genre = GenreEnum.Fish;
FileStream MyStream;
// utworzenie pliku, ktry bdzie zawiera
strumienie danych
MyStream = new FileStream("C:\\data.dat",
FileMode.Create);

BinaryFormatter MyFormatter = new


BinaryFormatter();
// prba serializacji
MyFormatter.Serialize(MyStream, MyPet);
// zamknicie strumienia
MyStream.Close();
// ponowne otwarcie strumienia
MyStream = new FileStream("C:\\data.dat",
FileMode.Open);
// deserializacja
MyPet =
(Animal[])MyFormatter.Deserialize(MyStream);
foreach (Animal Pet in MyPet)
{
Console.WriteLine("{0} {1} {2}",
Pet.Name, Pet.Age, Pet.Genre
);
}
Console.Read();
}
}
}

Serializowanymi danymi jest struktura Animal. Struktura suy do przechowywania


informacji o zwierztach. W programie zadeklarowaem dwuelementow tablic typu Animal,
ktr wypeniam danymi.
Nastpnie tworzona jest instancja klasy FileStream oraz BinaryFormatter. Serializowania
dokonuje metoda Serialize(). W tym samym programie dokonujemy rwnie deserializacji
przy pomocy metody Deserialize(). Poniewa metoda ta zwraca dane typu object,
konieczne jest rzutowanie typw na tablic Animal, ktr wywietlamy w oknie konsoli.

Podsumowanie
Obsuga plikw we wasnej aplikacji wci, z uwagi na wiksz popularno systemw
bazodanowych, jest rzadziej wykorzystywana. Do przechowywania danych dotyczcych
aplikacji uywa si baz danych czy plikw XML (bdzie o nich mowa w kolejnym rozdziale).
Strumienie s jednak wykorzystywane dosy czsto w bibliotece klas FCL w wielu klasach,
dlatego warto zna choby ich podstawow obsug. Niekiedy moe si rwnie nadarzy
okazja do uycia plikw w formie bazy danych do przechowywania informacji. Wwczas
moesz powrci do tego rozdziau i przypomnie sobie sposoby obsugi rnych typw
strumieni.

Rozdzia 13
Obsuga formatu XML
XML jest stosunkowo now technologi zaproponowan przez najwiksze firmy
informatyczne (Microsoft i Borland) jako nowy standard przechowywania i wymiany
informacji.
XML jest skrtem od sw eXtensible Markup Language, co oznacza rozszerzalny jzyk
znacznikw. Zosta opracowany przez W3C (ang. World Wide Web
Consorcium).
HTML (ang. HyperText Markup Language) jest jzykiem znacznikw opisujcym struktur
dokumentu WWW. Projektant, aby utworzy stron WWW, musi zapisa pewne z gry
okrelone polecenia, tak aby byy rozpoznawane przez przegldark oraz odpowiednio
interpretowane. Dziki temu, korzystajc z przegldarek, moemy obserwowa dokumenty
pene odnonikw, obrazkw oraz animacji.
XML jest nieco inny tutaj to programista ustala nazwy znacznikw oraz ca struktur
dokumentw. Dziki temu tworzy unikalny format dokumentw, ktry moe by
odczytywany za pomoc dowolnego edytora tekstu.
XML jest czsto mylony z baz danych. Naley jednak podkreli, e XML chocia dziaa
podobnie nie jest baz danych. W przypadku baz danych kada platforma (MySQL,
PostgreSQL, Oracle) posiada wasn struktur danych i wasne formaty plikw, w ktrych s
gromadzone informacje. Co prawda uniwersalny jest tutaj jzyk zapyta SQL, za pomoc
ktrego komunikujemy si z baz danych, ale sam zapis i odczyt informacji z plikw
zapewnia aplikacja
np. MySQL. Plik XML jest natomiast zwykym plikiem tekstowym, dziki czemu kady
moe modyfikowa jego zawarto.
Obecnie istnieje wiele tzw. parserw, ktre pozwalaj na prost modyfikacj plikw XML.
Su one do odczytywania struktury takiego pliku, dziki czemu odciaj programist od

mozolnego procesu edycji kodu XML.


XML nie zosta stworzony przez adn firm, lecz przez niezalene konsorcjum (W3C
rwnie utworzyo standard HTML), co gwarantuje mu niezaleno oraz brak praw
patentowych. Popularno XML cigle ronie, a dowodem tego s nowe standardy, ktre go
wykorzystuj (np. SOAP).

Niezaleno XML
XML jest niezaleny od platformy, jzyka programowania i sprztu (PC, Palmtop), jest wic
doskonaym i atwym formatem do przechowywania danych. Wemy jako przykad
notowania
spek giedowych przedstawiane na rnych portalach. Notowania te s zapisane na stronie
w formie tabel HTML, tak wic jedynym problemem w tym momencie jest odpowiednia
edycja kodu HTML, tak aby pozyska z niego potrzebne dane. Wystarczy jednak maa
zmiana w tym kodzie, aby wykorzystane narzdzie przestao poprawnie funkcjonowa. Jeeli
takie dane byyby przedstawiane jedynie w formie XML, znika problem niekompatybilnoci.
Wynika to z tego, e XML prezentuje jedynie tre, a nie form jak ma to miejsce w
HTML.

XHTML
XHTML jest nowym standardem, opracowanym take przez W3C, ktry w zamierzeniach ma
zastpi HTML i stanowi pewien porednik pomidzy jzykiem HTML a XML. Rozwj
HTML zosta wstrzymany i teraz do projektowania stron WWW zaleca si uywanie
XHTML.
XHTML jest bardziej uporzdkowany ni HTML. Usunito std wiele znacznikw HTML,
stawiajc na rozwj arkuszy stylw CSS. Innymi sowy, projektant powinien zrezygnowa z
wielu znacznikw HTML na rzecz CSS.

Budowa dokumentu
W tym podrozdziale mam zamiar opisa, jak wyglda budowa
plikw XML i jakie s ich wymagane elementy. Jako edytora mona tutaj uy dowolnego
programu pozwalajcego na zapisywanie tekstu, ja bd korzysta z edytora Visual C#
Express Edition.

Jeeli masz otwarty nowy projekt, z menu Project wybierz Add New Item. W oknie wybierz
pozycj XML File i nacinij Add. Do projektu dodany zostanie nowy plik, ktrego zawarto
wyglda tak:
<?xml version="1.0" encoding="UTF-8" ?>

Taki dokument mona zapisa pod dowoln nazw z rozszerzeniem *.xml. Pliki XML
otwiera si w przegldarce np. Internet Explorer aby sprawdzi poprawno
dokumentu.
Jeli dokument jest poprawny, Internet Explorer powinien wywietli jego tre, w
przeciwnym razie wskae bd w dokumencie.
Naley w tym momencie zaznaczy, e XML nie jest tak elastyczny jak HTML. Przegldarki
obecnie s w stanie zaakceptowa nieprawidowy zapis kodu HTML i mimo bdu poprawnie
zinterpretowa dany znacznik. Najmniejszy bd w XML spowoduje, e przegldarka
wywietli bdy w dokumencie.

Prolog
Specyficzna instrukcja dodana automatycznie przez rodowisko Visual C# Express Edition do
dokumentu to tzw. prolog. Jest to opcjonalna informacja dla parsera, informujca o typie
dokumentu oraz kodowaniu. Powinno si przykada du wag do owego nagwka i zawsze
stosowa go w swoich dokumentach.
Standardowa budowa prologu jest nastpujca:
<?xml version="1.0" encoding="UTF-8" ?>

Podstawowym warunkiem jest umieszczenie go midzy znacznikami <?xml oraz ?>. Opis
poszczeglnych parametrw prologu znajduje si w tabeli 13.1.
Tabela 13.1. Elementy skadowe prologu
<?xml ?>
Podstawowe instrukcje uywane przy deklaracji nagwka XML.
Wersja specyfikacji XML. Obecnie jedyn wersj owej specyfikacji jest
version="1.0"
1.0.
Wany nagwek, mwicy o kodowaniu dokumentu. Zalecam uywanie
kodowania Unicode (UTF-8), gdy zapewnia on poprawne wywietlanie
encoding="UTF-8"
wikszoci znakw. Godnym polecenia jest rwnie standard ISO-88592.
Dodatkowa warto okrelajca, czy dokument zawiera jakie odwoania
standalone="yes"
do innych plikw (yes).

Znaczniki
Budowa dokumentw HTML, XHTML oraz XML opiera si na znacznikach, zwanych take
tagami. W przypadku HTML oraz XHTML znaczniki musz mie z gry okrelon nazw. W
przypadku XML nie ma takich restrykcji, tak wic nazwa danego znacznika zaley ju od
programisty. Listing 13.1 przedstawia przykadow zawarto pliku XML.
Listing 13.1. Przykadowa zawarto pliku XML
<?xml version="1.0" encoding="ISO-8859-2" standalone="no" ?>
<jedzenie>
<pizza>
<nazwa>Hawajska</nazwa>
<cena>14,50</cena>
</pizza>
<pizza>
<nazwa>Peperoni</nazwa>
<cena>15,50</cena>
</pizza>
</jedzenie>

Tak zawarto moesz wklei do swojego edytora i zapisa. Po otwarciu takiego pliku w
przegldarce Internet Explorer powinien zosta wywietlony obraz taki jak na rysunku 13.1.

Rysunek 13.1. Zawarto dokumentu XML wywietlona przez Internet Explorer


Jeli wpisany kod bdzie niepoprawny, Internet Explorer wywietli informacj o tym, w
ktrej linii wystpi bd.

Budowa znacznikowa
Znaczniki s elementami o okrelonej nazwie, umieszczonymi pomidzy nawiasami (< oraz
>). Wyrniamy dwa rodzaje znacznikw otwierajcy oraz zamykajcy. W takim
przypadku w znaczniku zamykajcym musi si znale znak /:
<nazwa_znacznika> </nazwa_znacznika>

Pomidzy znacznikami mona umieci okrelon warto, pewne dane.


Istnieje kilka wanych regu, jak np. nazewnictwo. Nazwy znacznika otwierajcego oraz
zamykajcego musz by identyczne. Rozrniana jest take wielko znakw. Tak wic
poniszy zapis spowoduje zgoszenie bdu:
<nazwa_znacznika> dane </Nazwa_Znacznika>

Podczas edycji znacznikw XML naley take zwrci uwag na ich nazwy nie mog si
zaczyna od cyfr oraz nie mog zawiera znakw $#%&*(/. Przykadowo, ponisza nazwa
znacznika nie jest poprawna:
<a12$%3> dane </a12$%3>

Znacznik otwierajcy i zamykajcy oraz informacja umieszczona midzy nimi to wze.

Elementy HTML
Format XML jest stworzony jedynie do przechowywania informacji, tak wic upikszajce
tekst znaczniki HTML nie maj dla XML znaczenia. Przykadowo, znacznik <b> suy w
HTML do pogrubiania tekstu. W XML taki znacznik bdzie zwykym tagiem,
niewpywajcym na sposb wywietlania tekstu:
<nazwa>Pizza <b>Peperoni</b> jest dobra</nazwa>

Znaczniki zagniedone
W zwizku z powyszym przykadem chciabym wspomnie o znacznikach zagniedonych.
Element <nazwa> bdzie gwnym znacznikiem zawierajcym tekst Pizza jest dobra.
Natomiast w powyszym przypadku znacznikiem zagniedonym bdzie <b>, zawierajcy
tekst Peperoni.

Znaczniki zagniedone s podstawowym element projektowania dokumentw XML ten


mechanizm wykorzystaem rwnie na listingu 13.1. W tamtym przypadku gwnym
znacznikiem jest <jedzenie>. Znacznik ten zawiera z kolei dwa elementy <pizza>. W
elementach <pizza> znalazy si znaczniki okrelajce nazw pizzy oraz cen.

Znaki specjalne
Podczas wpisywania informacji pomidzy znacznikami nie mona uywa znakw < oraz >.
Aby umieci te znaki pomidzy tagami, naley zastosowa pewien trik, polegajcy na
zastpieniu ich innymi wartociami (tabela 13.2).
Tabela 13.2. Znaki specjalne XML
Znak specjalny Naley zastpi
<
>
&

&lt;
&gt;
&amp;
&quot;
&apos;

Po maych zabiegach taki kod XML bdzie prawidowy:


<nazwa>Pizza &lt;Peperoni&gt; jest dobra</nazwa>

I co najwaniejsze znaki < oraz > zostan poprawnie wywietlone.

Atrybuty
Kady element moe dodatkowo wykorzystywa atrybuty przybierajce okrelone wartoci:
<nazwa id="101">Peperoni</nazwa>

W tym przykadzie pizza Peperoni posiada atrybut id o wartoci 101. Atrybuty naley
wpisywa midzy apostrofami lub w cudzysowie. W danym elemencie mona umieci
dowoln liczb parametrw.
Tak samo jak w przypadku nazw elementw, nazwy atrybutw nie mog zawiera
niedozwolonych znakw (!@#^&*), nawiasw itp.

Znaczniki puste
Istnieje moliwo deklarowania elementw w sposb specyficzny uwzgldniajc jedynie
atrybuty, bez zamykania danego znacznika. W takim przypadku na kocu nazwy elementu
musi znale si znak /.
<nazwa id="101" nazwa_pizzy="Peperoni" />

Powyszy element posiada atrybuty id oraz nazwa_pizzy i jest znacznikiem pustym.

Podstawowa terminologia
Czasami wyrazy tag, znacznik, element czy wze s uywane zamiennie, gdy czowiek jest
w stanie domyli si ich znaczenia. Pragn jednak sprecyzowa pewne pojcia.
Znacznik inaczej moemy nazywa tagiem jest to specjalnie sformatowany cig znakw,
np.:
<table>
<p>

Elementy skadaj si z otwartych oraz zamknitych znacznikw:


<p> </p>
<table> </table>

Elementy mog by take puste tak moliwo przewiduje specyfikacja XML lub
XHTML:
<br />

Elementy mog z kolei mie atrybuty, ktre mog przybiera wartoci:


<element atrybut="warto">

Wzem nazywamy element, ktry posiada warto:


<element>warto</element>

Wzy z kolei mog posiada zagniedone elementy. Mam nadziej, e ta podstawowa


terminologia pozwoli Ci zrozumie tre niniejszego rozdziau.

Wze gwny
Analizujc listing 13.1, mona zauway, e cay pozostay kod XML znalaz si w wle
gwnym . Jest to podstawowa zasada: wszystkie pozostae wzy musz zosta
umieszczony w jednym gwnym korzeniu (ang. root). Trzeba o tym pamita podczas
tworzenia wasnych dokumentw XML.

Komentarze
Jak w kadym jzyku takim jak HTML czy XHTML, take i w XML istniej komentarze.
Komentarze nie s uwzgldniane przez parser i musz by zawarte pomidzy znakami <!-oraz -->, tak samo jak to ma miejsce w dokumentach HTML:
<!--komentarz-->

Oczywicie taki komentarz nie musi ogranicza si tylko do jednej linii:


<!-Lista TODO:
1) Benek: wyrwnaj elementy.
2) Kaziu: a gdzie reszta?
-->

Przestrzenie nazw
Konstrukcja przestrzeni nazw (ang. namespace) pozwala na deklaracj wicej ni jednego
zbioru znacznikw.
Najlepiej przedstawi to na przykadzie. W pewnym projekcie istnieje dokument XML, do

ktrego dostp ma kilku projektantw. Dokument jest do rozbudowany i wci


powikszany. Jeden z programistw zadeklarowa w dokumencie tag , oznaczajcy autora
ksiki. Drugi z programistw, nie wiedzc o tym, rwnie zadeklarowa tag , tyle e wedug
niego mia on okrela autora w sensie wykonawcy np. albumu muzycznego. Dochodzi w
tym momencie do konfliktu.
Dokumenty XSD korzystaj z przestrzeni nazw (o tym w dalszej czci rozdziau), wic
konieczne staje si wprowadzenie Ci w t tematyk. Poniej prezentuj przykad utworzenia
znacznikw wraz z przestrzeniami nazw:
<ns1:pizza xmlns:ns1="http://4programmers.net">
<nazwa>Hawajska</nazwa>
<cena>14,50</cena>
</ns1:pizza>
<ns2:pizza xmlns:ns2="http://programowanie.org">
<nazwa>Peperoni</nazwa>
<cena>15,50</cena>
</ns2:pizza>

W tym przykadzie oba znaczniki rni si od siebie identyfikatorem przestrzeni nazw w


pierwszym przypadku jest to http://4programmers.net, a w drugim http://programowanie.org.

Skadnia przestrzeni nazw


Naley rozrnia pewne pojcia, takie jak identyfikator przestrzeni nazw oraz alias
przestrzeni. Spjrzmy na ponisz lini:
<ns2:pizza xmlns:ns2="http://programowanie.org">

W tym przykadzie identyfikatorem jest fraza http://programowanie.org, a aliasem skrtowym


ns2. Identyfikator musi by adresem URL, niekoniecznie zwizanym z dokumentem XML
nie musi nawet istnie. Moe to by fikcyjny adres w sieci. Czsto w tym miejscu jest
umieszczany odnonik do schematu zawierajcego definicj dokumentu XML (tym na razie
nie naley si przejmowa opisz to w dalszej czci rozdziau).
Konstrukcja przestrzeni nazw jest prosta, deklaruje si j za pomoc frazy xmlns:ns2, gdzie
ns2 to alias przestrzeni nazw. Owego aliasu uywamy podczas deklarowania znacznikw w
poniszy sposb:
<alias:nazwa_znacznika>

Przestrzenie nazw i atrybuty


Moliwe jest take przydzielenie danej przestrzeni nazw do atrybutw:
<ns2:pizza xmlns:ns2="http://4programmers.net"
ns2:lang="PL">

Co prawda nie jest to czsto uywana konstrukcja, ale warto o niej wiedzie. Wspominam o
tym, poniewa chciabym w tym momencie zwrci uwag na moliwo deklarowania
przestrzeni nazw http://www.w3.org/XML/1998/namespace o aliasie xml. Przestrze ta
zawiera atrybut lang, ktry moe by stosowany dla kadego elementu dokumentu bez
koniecznoci jawnego okrelania przestrzeni xml. Dziki temu mona np. w dokumencie
XML stosowa wzy wielojzyczne, co uatwia proces tworzenia wielojzycznego
dokumentu:
<ns2:pizza xmlns:ns2="http://4programmers.net">
<ns2:nazwa>Peperoni</ns2:nazwa>
<ns2:cena xml:lang="PL">15,50</ns2:cena>
<ns2:cena xml:lang="EU">4</ns2:cena>
</ns2:pizza>

W powyszym przykadzie jeden element ma atrybut xml:lang o wartoci PL, a drugi EU.
Dziki temu moemy np. ustali cen pizzy w zotwkach oraz w euro.

DTD
Mona mwi o dwch rodzajach dokumentu poprawnie sformatowanym oraz
poprawnym. Dokument poprawnie sformatowany to taki, w ktrym wszystkie znaczniki s
zamknite, a elementy prawidowo zagniedone.
Dokument poprawny musi z kolei zawiera dodatkowo deklaracj DTD, czyli Document Type
Declaration. Polecenia DTD mog si znale bezporednio w dokumencie XML lub w
osobnym pliku. Instrukcje DTD, najprociej mwic, su do opisu tego, z czego skada si
dokument XML. Instrukcje DTD s unikalne i proste listing 13.2 przedstawia dokument
XML wraz z opisem struktury.
Listing 13.2. Dokument XML wraz z deklaracj DTD
<?xml version="1.0" encoding="ISO-8859-2" standalone="no" ?>
<!DOCTYPE jedzenie [
<!ELEMENT nazwa (#PCDATA)>

<!ELEMENT pizza (nazwa)>


]>
<jedzenie>
<pizza>
<nazwa>Hawajska</nazwa>
<cena>14,50</cena>
</pizza>
<pizza>
<nazwa>Peperoni</nazwa>
<cena>15,50</cena>
</pizza>
</jedzenie>

Pocztek instrukcji DTD musi rozpoczyna si fraz <!DOCTYPE, a koczy ]>. Pomidzy
tymi instrukcjami znajduj si opisy elementw i oglnie struktury pliku XML.

Deklaracja elementu
Kluczow czci DTD jest deklaracja typu elementu znajdujcego si w kodzie XML. Owa
deklaracja jest wykonywana przy uyciu jednej linii, ktra rozpoczyna si fraz <!ELEMENT:
<!ELEMENT nazwa (#PCDATA)>

Nastpnie naley poda nazw elementu oraz zawarto w tym przypadku #PCDATA.
Tabela 13.3 przedstawia frazy, ktre mog posuy w opisie DTD do okrelania danego
elementu.
Tabela 13.3. Frazy DTD opisujce zawarto elementu
Fraza
Opis
Przykad
DTD: <!ELEMENT nazwa EMPTY> XML: <nazwa
EMPTY
Pusty element
/>

Pomidzy znacznikami
(#PCDATA) znajduje si zwarto
(zwyky tekst)
Dany element posiada
(child)
inny zagniedony
element o nazwie child
Dany element posiada
(Seq1,Seq2) zagniedone elementy
Seq1 oraz Seq2

DTD: <!ELEMENT nazwa (#PCDATA)> XML:


<nazwa>zawarto</nazwa>
DTD: <!ELEMENT parent (child)> <!ELEMENT
child (#PCDATA)> XML: <parent>
<child>zawarto</child> </parent>

DTD: <!ELEMENT SEQ (N1, N2)> <!ELEMENT N1


(#PCDATA)> <!ELEMENT N2 (#PCDATA)> XML:
<SEQ> <N1>zawarto</N2>
<N2>zawarto</N2> </SEQ>

(child*)
(child+)
(child?)

Znaki *, ? oraz +
okrelaj, ile razy element
child moe wystpi: *
zero lub wicej razy, +
jeden raz lub wicej, ?
zero lub jeden raz

(child1 |
child2)

Znak | (pipe) oznacza


moliwo wyboru. Uyty
moe zosta element
child1 lub child2

DTD: <!ELEMENT parent (child*)> <!ELEMENT


child (#PCDATA)> XML: <parent>
<child>zawarto</child>
<child>zawarto</child> </parent>

DTD: <!ELEMENT parent (child1 | child2)>


<!ELEMENT child1 (#PCDATA)> <!ELEMENT
child2 (EMPTY)> XML: <parent>
<child1>zawarto</child1> </parent>
<parent> <child2 /> </parent>

Deklaracja atrybutu
DTD jest rwnie uywane do deklarowania atrybutw, jakie s wykorzystywane przy
elementach. Ich budowa jest nastpujca:
<!ATTLIST Element Atrybut Typ Domylna_warto>

W przypadku gdy atrybutw jest wiele, mona okreli ich domylne wartoci w nastpujcy
sposb:
<!ATTLIST Element Atrybut Typ Domylna_warto>
Typ Domylna_warto>

Gdzie:

Element nazwa elementu (znacznika),


Atrybut nazwa atrybutu w znaczniku,
Typ typ atrybutu: tekstowy, znakowy, wyliczeniowy.

Typ tekstowy
Typ tekstowy jest deklarowany z uyciem sowa kluczowego CDATA:
<!ATTLIST nazwa id CDATA 'acuch'>

W takim przypadku wiadomo, e atrybut id znajduje si w elemencie nazwa i jest typu


tekstowego. Domyln wartoci atrybutu (jeeli nie zosta zadeklarowany w kodzie XML)
bdzie cig znakowy.
Jeeli atrybut ma zawiera liczby, mona okreli typ DTD take jako CDATA i po prostu
otoczy liczb znakami apostrofu.

Typ wyliczeniowy
Opisanie atrybutu DTD w sposb wyliczeniowy daje wybr, pewien zakres wartoci, jakie
moe on przyjmowa. Przyjrzyjmy si poniszemu fragmentowi kodu:
<?xml version="1.0" encoding="ISO-8859-2" standalone="no" ?>
<!DOCTYPE
<!ELEMENT
<!ELEMENT
<!ATTLIST
]>

jedzenie [
nazwa (#PCDATA)>
pizza (nazwa)>
nazwa id (101|102|103) '103'>

<jedzenie>
<pizza>
<nazwa>Hawajska</nazwa>
<cena>14,50</cena>
</pizza>
<pizza>
<nazwa id="101">Peperoni</nazwa>
<cena>15,50</cena>
</pizza>
</jedzenie>

Waciwoci atrybutu id w elemencie nazwa okrela ponisza linia:


<!ATTLIST nazwa id (101|102|103) '103'>

W takim przypadku atrybut id bdzie mg przyjmowa warto 101, 102 lub 103, lecz
wartoci domyln bdzie 103. Naley take zwrci uwag, e pierwszy element <nazwa>
nie ma zadeklarowanego atrybutu id. W zwizku z tym, gdy wywietlimy kod pliku XML w
przegldarce Internet Explorer, nada ona automatycznie atrybut id brakujcym elementom.

DTD w osobnym pliku


Czsto stosowan technik jest wyodrbnianie czci DTD z kodu XML i zapisywanie jej w
osobnym pliku. Uatwia to pniejsz modyfikacj kodu oraz daje moliwo wczenia
deklaracji DTD w kilku plikach.
Plik z deklaracj DTD powinien mie rozszerzenie .dtd. Teraz mona wic skopiowa tekst
znajdujcy si pomidzy <!DOCTYPE a ]> do osobnego pliku u mnie wyglda on tak:
<!ELEMENT nazwa (#PCDATA)>
<!ELEMENT pizza (nazwa)>
<!ATTLIST nazwa id (101|102|103) '103'>
<!ENTITY Special "Hawajska">
<!ENTITY Price "14,50">

Teraz w gwnym pliku XML naley wczy deklaracj DTD w nastpujcy sposb:
<!DOCTYPE jedzenie SYSTEM "jedzenie.dtd">

Encje tekstowe
Encje tekstowe musz zosta zadeklarowane w sekcji DTD. Peni one w dokumencie XML
funkcj szablonu. Dziki encjom programista unika cigego wpisywania tej samej frazy w
tych samych miejscach kodu XML. Dziaanie encji mona porwna do staych jedna staa
moe by pniej uyta w dalszej czci programu, tak wic uatwia to ewentualn zmian
danych wystarczy wwczas tylko zmiana jednej staej. Listing 13.3 prezentuje sposb
wykorzystania encji tekstowych.
Listing 13.3. Prezentacja wykorzystania encji tekstowych
<?xml version="1.0" encoding="ISO-8859-2" standalone="no" ?>
<!DOCTYPE
<!ELEMENT
<!ELEMENT
<!ATTLIST

jedzenie [
nazwa (#PCDATA)>
pizza (nazwa)>
nazwa id (101|102|103) '103'>

<!ENTITY Special "Hawajska">


<!ENTITY Price "14,50">
]>
<jedzenie>
<pizza>

<nazwa>&Special;</nazwa>
<cena>&Price;</cena>
</pizza>
<pizza>
<nazwa id="101">Peperoni</nazwa>
<cena>15,50</cena>
</pizza>
</jedzenie>

Dziki encjom zostay utworzone nowe znaczniki &Special; oraz &Price;. Encj deklaruje
si w nastpujcy sposb:
<!ENTITY Special "Hawajska">

Budowa tego elementu jest prosta: encj rozpoczyna fraza <!ENTITY, po ktrej wystpuje
nazwa oraz warto midzy znakami apostrofw. W tym momencie wstawienie znacznika
&amp;Special; w kodzie XML spowoduje zamienienie go na warto owej encji.
Naley zwrci uwag na znak rednika na kocu (&Special;). Jak ju wspominaem, w XML
liczy si precyzja i takich szczegw nie mona pomin.
W kodzie XML mona uywa zarwno cudzysowu ("), jak i apostrofw (').

XSD
Do weryfikacji poprawnoci dokumentu XML stosowane jest DTD lub XSD (ang. XML
Schema Definition) standard opisu dokumentu zalecany przez W3C. Konsorcjum W3C
preferuje wykorzystanie XSD zamiast DTD, poniewa schemat ten korzysta z jzyka XML.
XSD definiuje elementy, atrybuty oraz typy danych zawartych w dokumencie XML. XSD jest
bardziej uyteczny oraz rozbudowany ni DTD, a przede wszystkim opracowany i zalecany
przez W3C.
Idea utworzenia XSD zostaa zapocztkowana przez firm Microsoft, ale w kocu w roku
2001 patronat nad powstaniem i utworzeniem standardu obja organizacja W3C.
Przypominam, e XML opracowano w roku 1998.
Utwrzmy nowy plik tekstowy i nazwijmy go jedzenie.xsd. Ten plik tekstowy naley
umieci w tym samym katalogu co przykadowy dokument XML. Bdzie on zawiera
instrukcje XSD. Przyjmujemy wic, e deklaracje XSD zostan umieszczone w osobnym
pliku. Na listingu 13.4 znajduje si przykadowy kod z instrukcjami XSD. Przypominam, e
deklaracje (instrukcje) XSD s napisane w jzyku XML i to jest podstawowa rnica
pomidzy nim a DTD.

Listing 13.4. Przykadowe instrukcje XSD


<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.programowanie.org"
elementFormDefault="qualified">
<xs:element name="jedzenie">
<xs:element name="pizza">
<xs:element name="nazwa" type="xs:string" />
<xs:element name="cena" type="xs:string" />
</xs:element>
</xs:element>
</xs:schema>

Nagwek XSD
Na samym pocztku naley zaznaczy, e XSD korzysta z przestrzeni nazw XML, ktr
trzeba okreli na samym pocztku. Wszystkie schematy XSD musz mie gwny wze
<schema>, taki jak ten poniej:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.programowanie.org"
elementFormDefault="qualified">

Moe nie by zupenie identyczny, ale wany jest tutaj element o nazwie <schema>, ktry jest
obowizkowy w dokumencie XSD. Jak wida, uywamy tutaj przestrzeni nazw, deklarujc
http://www.w3.org/2001/XMLSchema o aliasie xs.

Elementy XSD
XSD w rzeczywistoci jest charakterystycznym kodem XML, np.:
<xs:element name="nazwa" type="xs:string" />

Taka konstrukcja suy do opisania nastpujcego kodu XML:

<nazwa>Peperoni</nazwa>

Budowa XSD jest do prosta, charakteryzuje si uyciem sowa kluczowego element:


<xs:element name="nazwa_elementu" type="xs:typ_elementu" />

Wartoci atrybutu name musi by nazwa elementu w kodzie XML, a atrybut type okrela
rodzaj (typ) danych umieszczonych pomidzy tagami. Opisany element naley do elementw
prostych, czyli takich, ktre zwieraj jakie dane (tekst, liczby, wartoci typu boolean), nie
mog jednak zawiera innych znacznikw oraz atrybutw.
Oto przykad schematu XSD opisujcego kod XML, ktry zawiera zagniedone znaczniki:
<xs:element name="pizza">
<xs:element name="nazwa" type="xs:string" />
<xs:element name="cena" type="xs:float" />
</xs:element>

Powyszy przykad opisuje nastpujcy kod XML:


<pizza>
<nazwa>Hawajska</nazwa>
<cena>14,50</cena>
</pizza>
<pizza>
<nazwa>Peperoni</nazwa>
<cena>15,50</cena>
</pizza>

Typy danych
Ju si dowiedziae, e schematy XSD wymagaj podania typu danych elementw XML,
ktre opisuj. W poprzednich przykadach uyem typu string oraz float. Tabela 13.4
prezentuje kompletn list typw danych.
Tabela 13.4. Typy danych
Typ
Opis
string Cig tekstowy, czyli cig znakw
float 32-bitowe liczby rzeczywiste

double 64-bitowe liczby rzeczywiste (wiksza precyzja)


datime Data i czas w formacie YYYY-MM-DD hh:mm:ss
integer Liczby rzeczywiste
boolean Wartoci true lub false

Typy proste
Czym jest typ prosty? Jest to element, ktry zawiera jedynie tekst dane proste. Moe nie
jest to do koca poprawne sformuowanie, bo element prosty moe take zawiera wartoci
numeryczne typu boolean itp.
Deklarowalimy ju schematy opisujce proste elementy XML:
<xs:element name="nazwa" type="xs:string" />

Schemat dokumentu XML moe uwzgldnia pewne restrykcje dotyczce danych


mieszczcych si w konkretnym elemencie XML. Nim jednak wspomn o ograniczeniach,
zaprezentuj nadawanie wartoci domylnych dla elementw.
Oto przykad:
<xs:element name="nazwa" type="xs:string" default="Hawajska"
/>

Taki fragment schematu opisuje nastpujce dane XML:

Opisuje element XML o nazwie nazwa.


Okrela, e typem elementu nazwa jest cig znakw (string).
Deklaruje, e domylna warto elementu nazwa to Hawajska.

W miejsce atrybutu default mona uy fixed. Podana w tym atrybucie warto nie bdzie
jedynie domylna nie bdzie jej mona pniej zmieni!

Atrybuty
Na pocztku moesz by nieco sceptyczny, bowiem atrybuty elementu XML w schemacie

XSD s opisywane jako element. Wane jest zrozumienie tego zdania. Przypumy, e mamy
fragment kodu XML o takiej postaci:
<pizza name="Hawajska" />

Jest to prosty, zwyky znacznik zawierajcy atrybut name. Teraz schemat opisujcy zarwno
ten element, jak i atrybut wyglda nastpujco:
<xs:element="pizza" type="xs:string" />
<xs:attribute="name" type="xs:string" />
</xs:element>

Schematy opisujce atrybuty, tak samo jak te opisujce elementy, mog zawiera wartoci
domylne (czyli parametry default oraz fixed).
Wszystkie atrybuty elementu XML domylnie s opcjonalne. Nie jest konieczne ich
deklarowanie w trakcie tworzenia dokumentu XML. Mona to zmieni, deklarujc schemat
opisujcy element:
<xs:element="pizza" type="xs:string" />
<xs:attribute="name" type="xs:string" use="optional" />
</xs:element>

Powysza deklaracja okrela, i atrybut name elementu pizza bdzie opcjonalny tak samo
jak w poprzednim przykadzie. Taki zapis nie jest konieczny, gdy jak ju powiedziaem
atrybuty XML domylnie s opcjonalne.
Mona natomiast okreli, i dany atrybut ma by wymagany! W miejsce sowa optional
naley wpisa required:
<xs:element="pizza" type="xs:string" />
<xs:attribute="name" type="xs:string" use="required" />
</xs:element>

Restrykcje
Restrykcje s elementem XSD, ktry pozwala na przypisanie szczegowych informacji
odnonie danego elementu XML np. maksymalnej wartoci, jaka moe by zawarta w
danym elemencie. Oto przykad nadania restrykcji dla elementu XML price:

<xs:element="price">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="10" />
<xs:maxInclusive value="50" />
<xs:restriction>
</xs:simpleType>
</xs:element>

Restrykcje tego typu prostego mwi, i warto elementu price musi by wiksza od 10, ale
jednoczenie mniejsza od 50. Opis pozostaych restrykcji znajduje si w tabeli 13.5.
Tabela 13.5. Restrykcje moliwe do uycia w schematach XSD
Restrykcja
Opis
length
Opisuje dugo danego elementu.
minLength
Minimalna dugo, jak moe przybra dany element.
maxLength
Maksymalna dugo, jak moe przybra dany element.
pattern
Maska, zaawansowane opcje zwizane z wyraeniami regularnymi.
enumeration Warto musi by jedn z podanych.
minInclusive Warto elementu musi by wiksza lub rwna liczbie podanej w schemacie.
minExclusive Warto elementu musi by wiksza od liczby podanej w schemacie.
maxInclusive Warto elementu musi by mniejsza lub rwna liczbie podanej w schemacie.
maxExclusive Warto elementu musi by mniejsza od liczby podanej w schemacie.

Restrykcje typu Enumeration


Elementy XML mog mie warto jedn spord wczeniej okrelonych. W takim
przypadku w elemencie XSD restriction naley zagniedzi kolejny element
enumeration:
<xs:element="pizza">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="Peperoni" />
<xs:enumeration value="Hawajska" />
<xs:restriction>
</xs:simpleType>
</xs:element>

Powyszy schemat mwi, i wartoci elementu pizza moe by albo Peperoni, albo
Hawajska.

Typy zoone
Typy zoone maj inne zagniedone znaczniki. By moe ju domylasz si, jak wyglda
schemat typw zoonych. Oto przykadowy znacznik XML:
<pizza>
<nazwa>Hawajska</nazwa>
<cena>14,50</cena>
</pizza>

Schemat XSD opisujcy takie znaczniki wyglda nastpujco:


<xs:element name="pizza">
<xs:complexType>
<xs:element name="nazwa" type="xs:string" />
<xs:element name="cena" type="xs:float" />
</xs:complexType>
</xs:element>

XML a bazy danych


Czstym nieporozumieniem dotyczcym XML jest stwierdzenie, jakoby mia on zastpowa
bazy danych. Dane przechowywane w XML s zasadniczo inne ni w bazach danych. Przede
wszystkim XML jest zwykym plikiem tekstowym, atwym do odczytania dla ludzi oraz przez
maszyny. Bazy danych gromadz informacje w plikach binarnych, co uniemoliwia ich
odczytanie bez znajomoci budowy takiego pliku.
Dane w bazach danych s dodatkowo przechowywane w tabelach, co zwiksza zdolno do
prezentowania wielu dynamicznych perspektyw tych samych zbiorw.
Podsumujmy wic rnice pomidzy XML a bazami danych:

XML dobrze spisuje si przy prezentowaniu zarwno prostych, jak i zoonych


struktur danych. Bazy danych su do przechowywania liniowych struktur danych,
ktre s reprezentowane w formie tabeli.
Dokumenty XML mona bardzo atwo przenosi do innych systemw i na inne
platformy jest to bardzo elastyczny i uniwersalny jzyk. Bazy danych natomiast
trudno przenie do innych systemw.
Format XML bardzo atwo mona poddawa edycji za pomoc prostego edytora
tekstw lub korzystajc ze specjalnych programw. W przypadku baz danych edycja
jest moliwa tylko za pomoc specjalnego programu.

Systemy baz danych s bardziej efektywne i skomplikowane. Korzystajc z jzyka


SQL, mona osign lepsze efekty, natomiast dziaanie parserw XML jest bardziej
czasochonne i bardziej obcia zasoby systemowe.

Oczywicie nic nie stoi na przeszkodzie, aby poczy zalety baz danych oraz XML,
przechowujc kod XML w bazie danych w polu typu BLOB lub TEXT.

XSL
Kolejnym terminem, o ktrym warto wspomnie, jest XSL (ang. eXtensible Stylesheet
Language). Jest to jzyk, ktry opisuje przeksztacenia dokumentw XSL. Jest to waciwie
rodzina jzykw, bo skadaj si na ni:

XSLT XSL Transformation, czyli jzyk dla przeksztace dokumentw XML,


XPath XML Path Language, czyli jzyk opisujcy dostp lub odwoanie do
fragmentw XML,
XSL-FO sownik opisujcy formatowanie.

Jak wida, rodzina technologii


XML jest bardzo dua, a jej cakowite opanowanie nie jest takie proste. XSLT suy do
przeksztacania dokumentu XML na dokument wizualny, ktry moe zosta wydrukowany.
W XSLT mona stosowa HTML i CSS, a nastpnie dynamicznie tworzy stron WWW,
ktra bdzie pobiera informacje z pliku XML. Takie informacje bd np. formowane w
postaci tabeli HTML.

DOM
DOM, czyli Document Object Model, jest standardem opracowanym przez W3C,
definiujcym dostp do dokumentw XML oraz HTML. w standard zosta przeniesiony na
rzeczywisty zbir interfejsw (API) i funkcji w wielu parserach czy np. przegldarkach
internetowych. DOM definiuje struktur dokumentu oraz sposb manipulowania nim. Od
czasu opublikowania specyfikacji DOM
powstao wiele jej implementacji. W rodowisku Win32 moglimy korzysta z kontrolki
COM, udostpniajcej interfejsy obsugujce XML.
W .NET bdziemy korzystali z przestrzeni nazw System.XML i znajdujcych si w niej klas.
W DOM dokumenty posiadaj logiczn struktur podobn do drzewa, ktre zawiera wzy.

Np. poniszy fragment kodu XML w DOM bdzie mia struktur podobn do tej z rysunku
13.2:
<jedzenie>
<pizza>
<nazwa>Hawajska</nazwa>
<cena>14,50</cena>
</pizza>
<pizza>
<nazwa>Peperoni</nazwa>
<cena>15,50</cena>
</pizza>
</jedzenie>

Rysunek 13.2. Struktura dokumentu XML


Podsumowujc: interfejs API o nazwie DOM wczytuje do pamici zawarto pliku XML w
postaci drzewa, a nastpnie umoliwia przemieszczanie si po kolejnych wzach oraz
modyfikacj drzewa.

SAX
SAX (ang. Simple API for XML) jest podobnie jak DOM interfejsem programistycznym
definiujcym dostp do plikw XML. O interfejsie SAX mwi si, e jest sterowany
zdarzeniami. Oznacza to, e SAX parsujc zawarto dokumentu, napotyka na znaczniki i
inicjalizuje rozmaite czynnoci, ktrych implementacja naley do programisty. Programista
musi wic zainicjalizowa procedury obsugi (ang. handlers) konkretnych znacznikw.

Podstawow rnic pomidzy DOM a SAX jest sposb odczytywania dokumentw. SAX
czyta dokumenty stosunkowo maymi porcjami, parsuje je i w razie koniecznoci
wywouje procedury obsugi. SAX nadaje si wic do analizy bardzo duych dokumentw
XML.
DOM jednorazowo wczytuje cay dokument do pamici, w ktrej tworzy drzewo gazi.
Oznacza to due obcienie dla pamici w przypadku wikszych dokumentw. DOM nadaje
si wic do obsugi maych oraz rednich plikw, a SAX jest wrcz stworzony do odczytu
duych dokumentw. SAX ma jednak du wad nie umoliwia zapisywania dokumentw
XML, lecz suy jedynie do ich odczytu.

Korzystanie z System.XML
W prezentowanych tutaj przykadach bd posugiwa si podzespoem System.XML.dll, w
ktrym znajduje si przestrze nazw System.XML dodajmy j wic do naszego programu.
Gwn klas, ktr bdziemy stosowa do adowania i zapisywania dokumentu, jest
System.XML.XMLDocument, jednak przestrze System.XML definiuje o wiele wicej klas
okrelajcych np. wze (ang. node) XML.

adowanie pliku XML


Aby zaadowa do pamici plik XML, naley najpierw utworzy nowy obiekt klasy
System.XML.XmlDocument, a pniej skorzysta z metody Load, podajc nazw oraz ciek
do pliku XML:
using System;
using System.Xml;
namespace XmlApp
{
class Program
{
static void Main(string[] args)
{
XmlDocument XmlDoc = new XmlDocument();
try
{
XmlDoc.Load("C:\\demo.xml");
Console.WriteLine("Dokument zaadowany!");
}

catch (XmlException e)
{
Console.WriteLine(e.Message);
}
Console.Read();
}
}
}

Jak wida, konstrukcja jest prosta. Metoda Load() aduje plik XML do pamici. Warto take
wiedzie o metodzie Save(), ktra zapisuje do podanego pliku zawarto edytowanego
dokumentu XML.
Warto te zwrci uwag na obsug wyjtku XMLException. Klasa XMLDocument ma
wbudowany mechanizm walidacji, ktry sprawdza poprawno dokumentu XML. Dziki
temu w razie jakich nieprawidowoci w kodzie XML zostanie wywietlony komunikat
bdu.
Do odczytania zawartoci pliku XML mona take uy metody LoadXML.

Odczyt dowolnego elementu


Odczytanie wartoci danego elementu jest zwizane z wywoaniem metody
GetElementsByTagName(). To w niej podajemy nazw znacznika, ktrego warto zostanie
odczytana. Na listingu 13.5 znajduje si fragment kodu odpowiadajcego za zaadowanie
pliku XML do komponentu TreeView.
Listing 13.5. Metoda odczytujca zawarto pliku XML
private void btnLoad_Click(object sender, EventArgs e)
{
TreeNode NewXmlNode;
XmlDocument XmlDoc = new XmlDocument();
tvXML.Nodes.Clear();
try
{
XmlDoc.Load("C:\\demo.xml");
int Count =
XmlDoc.GetElementsByTagName("pizza").Count;
for (int i = 0; i < Count; i++)
{
NewXmlNode =
tvXML.Nodes.Add(XmlDoc.GetElementsByTagName("nazwa").Item(i).I

nnerText);
NewXmlNode.Nodes.Add("Skadniki: " +
XmlDoc.GetElementsByTagName("skladnik").Item(i).InnerText);
NewXmlNode.Nodes.Add("Cena: " +
XmlDoc.GetElementsByTagName("cena").Item(i).InnerText);
}
}
catch (XmlException ex)
{
MessageBox.Show(ex.Message);
}
}

Na samym pocztku dziaania programu naleao okreli, ile elementw pizza zawiera nasz
plik XML. Skorzystaem przy tym z waciwoci Count klasy XMLNodeList:
XmlDoc.GetElementsByTagName("pizza").Count

Metoda GetElementsByTagName() zwraca dane wanie w postaci klasy XMLNodeList.


Po okreleniu liczby wystpie elementu pizza moemy zaprogramowa ptl, ktra odczyta
po kolei interesujce nas dane. Pobranie konkretnej wartoci danego elementu wyglda
nastpujco:
XmlDoc.GetElementsByTagName("skladnik").Item(i).InnerText

Wywoujc metod GetElementsByTagName(), podajemy nazw znacznika, ktrego warto


zostanie odczytana. Jak wiadomo, w pojedynczym pliku XML moe istnie wiele znacznikw
o tej samej nazwie, naley wic okreli jego numer (waciwo Item). Pniej pozostaje ju
tylko odczytanie wartoci (InnerText). Rezultat dziaania programu zosta przedstawiony na
rysunku 13.3, a sam kod XML prezentuje si nastpujco:
<?xml version="1.0" encoding="ISO-8859-2" standalone="no"?>
<jedzenie xmlns="http://www.programowanie.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.programowanie.org
jedzenie.xsd">
<pizza>
<nazwa>Hawajska</nazwa>
<cena>14,50</cena>
<skladnik>ser, szynka, ananasy</skladnik>

</pizza>
<pizza>
<nazwa>Peperoni</nazwa>
<cena>20,00</cena>
<skladnik>peperoni, cebula</skladnik>
</pizza>
</jedzenie>

Rysunek 13.3. Rezultat dziaania programu

Odczyt wartoci atrybutw


W poprzednim przykadzie przedstawiem sposb odczytania wartoci danego elementu
(wza). Nie naley zapomina o atrybutach danego wza, ktre take odgrywaj znaczc
rol podczas tworzenia plikw XML. Na przykad w ostatnio prezentowanym pliku XML
kadej pizzy przypisano cen, mieszczc si w znaczniku <cena>. Nie naley zapomina, e
w wikszoci pizzerii ceny pizz zale od ich wielkoci. W takich sytuacjach idealne wydaje
si zastosowanie atrybutw:
<?xml version="1.0" encoding="ISO-8859-2" standalone="no"?>
<jedzenie xmlns="http://www.programowanie.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.programowanie.org
jedzenie.xsd">
<pizza>
<nazwa>Hawajska</nazwa>
<cena rozmiar="maa">14,50</cena>

<cena rozmiar="rednia">20,00</cena>
<cena rozmiar="dua">27,00</cena>
<skladnik>ser, szynka, ananasy</skladnik>
</pizza>
<pizza>
<nazwa>Peperoni</nazwa>
<cena rozmiar="maa">15,50</cena>
<cena rozmiar="rednia">19,50</cena>
<cena rozmiar="dua">26,50</cena>
<skladnik>peperoni, cebula</skladnik>
</pizza>
<pizza>
<nazwa>Barbacue</nazwa>
<cena rozmiar="maa">19,10</cena>
<cena rozmiar="rednia">26,00</cena>
<cena rozmiar="dua">31,50</cena>
<skladnik>bekon, kawaki kurczaka, cebula, czerwona
papryka, ser</skladnik>
</pizza>

</jedzenie>

W powyszym dokumencie XML zadeklarowaem kilka znacznikw <cena>, z ktrych kady


ma inny atrybut rozmiar (maa, rednia, dua) i w zalenoci od niego cena pizzy jest
rna. Listing 13.6 przedstawia fragment kodu aplikacji przystosowanej do wywietlania
informacji o trzech rodzajach pizz.
Listing 13.6. Odczyt atrybutw wzw
private void btnLoad_Click(object sender, EventArgs e)
{
TreeNode NewXmlNode, PriceNode;
XmlNode Node;
XmlDocument XmlDoc = new XmlDocument();
tvXML.Nodes.Clear();
try
{
XmlDoc.Load("C:\\demo.xml");
int Count =
XmlDoc.GetElementsByTagName("pizza").Count;
for (int i = 0; i < Count; i++)
{
NewXmlNode =
tvXML.Nodes.Add(XmlDoc.GetElementsByTagName("nazwa").Item(i).I

nnerText);
NewXmlNode.Nodes.Add("Skadniki: " +
XmlDoc.GetElementsByTagName("skladnik").Item(i).InnerText);
PriceNode = NewXmlNode.Nodes.Add("Ceny");
for (int j = i * 3; j <= (i * 3) + 2; j++)
{
Node =
XmlDoc.GetElementsByTagName("cena").Item(j);
PriceNode.Nodes.Add(
Node.Attributes.GetNamedItem("rozmiar").InnerText + ": " +
Node.InnerText
);
}
}
}
catch (XmlException ex)
{
MessageBox.Show(ex.Message);
}
}

Pocztek tej metody jest identyczny jak tej z listingu 13.5. Problemy zaczynaj si pniej.
Zdefiniowano 3 rne ceny dla kadej pizzy, kolejna ptla for musi przetworzy trzy kolejne
wzy, odczytujc ich atrybuty. Skoro liczba wzw <cena> w wle <pizza> jest znana,
wyliczenie liczby iteracji opiera si na rachunku matematycznym:
for (int j = i * 3; j <= (i * 3) + 2; j++)

W celu skrcenia zapisu do zmiennej Node jest przypisywany rezultat dziaania funkcji
GetElementsByTagName("cena").Item(j). Nastpnie w celu pobrania wartoci atrybutu
naley skorzysta z metody GetNamedItem():
PriceNode.Nodes.Add(Node.Attributes.GetNamedItem("rozmiar").In
nerText + ": " + Node.InnerText);

Prosty odczyt pliku XML

Nim zajmiemy si zapisywaniem dokumentw XML, przyjrzyjmy si klasie XmlTextReader,


ktra zapewnia szybki i prosty mechanizm dostpu do dokumentw XML. Podczas tworzenia
klasy naley poda w konstruktorze ciek do dokumentu XML:
XmlTextReader Reader = new XmlTextReader("C:\\demo.xml");

Klasa Reader posiada metod Read() (ktra zwraca warto typu Boolean), dziki ktrej
ptla moe po kolei odczyta wszystkie elementy:
using System;
using System.Xml;
namespace XmlApp
{
class Program
{
static void Main(string[] args)
{
XmlTextReader Reader = new
XmlTextReader("C:\\demo.xml");
while (Reader.Read())
{
Console.WriteLine(Reader.Name + ' ' +
Reader.Value);
}
Reader.Close();
Console.Read();
}
}
}

Waciwo Name zwraca nazw elementu, a Value jego warto. Takie rozwizanie ma
pewne wady, poniewa klasa XmlTextReader nie rozpoznaje typw znacznikw, czego
rezultatem moe by odczytanie przez ni wszystkich znacznikw rwnie tych
zamykajcych. Aby temu zapobiec, omawiana klasa wykorzystuje waciwo NodeType,
dziki ktrej mona odczyta jedynie interesujce nas elementy. Waciwo NodeType moe
przybra nastpujce wartoci:

All wszystkie wzy,


Attribute atrybut,
Comment komentarz XML,
Document element gwny drzewa danych XML,
Element poszczeglny element XML,
EndTag znacznik zamykajcy elementu,

None aden wze nie jest analizowany,


XMLDeclaration wze zawierajcy deklaracj XML.

Tworzenie pliku
Do tej pory zajmowalimy si jedynie odczytem wzw i atrybutw. Nie wspomniaem w
ogle o tworzeniu plikw XML, a taka moliwo w modelu DOM istnieje. Do tworzenia
plikw XML, w tym wzw oraz atrybutw, suy klasa XMLTextWriter.
Podczas tworzenia klasy XMLTextWriter w parametrze konstruktora naley poda ciek do
pliku, ktry ma by utworzony w wyniku dziaania programu. Utworzenie i zwolnienie klasy
w najprostszym przypadku wyglda nastpujco:
XmlTextWriter Writer = new XmlTextWriter("C:\\output.xml",
null);

Drugi parametr konstruktora okrela kodowanie pliku zalecam pozostawienie w tym


miejscu wartoci pustej (null), co oznacza kodowanie domylne, czyli Unicode (UTF-8).
Jednak sam konstruktor nie wystarczy do stworzenia przykadowego, prostego dokumentu.
Bdziemy musieli dodatkowo uy prostych metod WriteStartDocument(),
WriteEndDocument():
using System;
using System.Xml;
namespace XmlApp
{
class Program
{
static void Main(string[] args)
{
XmlTextWriter Writer = new
XmlTextWriter("C:\\output.xml", null);
Writer.WriteStartDocument();
Writer.WriteStartElement("root");
Writer.WriteEndElement();
Writer.WriteEndDocument();
Writer.Close();
}
}
}

Powyszy program utworzy prosty dokument XML, majcy jeden wze gwny root:
<?xml version="1.0"?><root />

Przy tworzeniu dokumentw XML naley przestrzega pewnych zasad:


1.
2.
3.
4.

Tworzenie dokumentu rozpoczynamy od metody WriteStartDocument().


Dokument koczymy wywoaniem metody WriteEndDocument().
Zamknicie dokumentu nastpuje w momencie wywoania metody Close().
Dokument musi mie przynajmniej jeden gwny wze tworzymy go metod
WriteStartElement(), podajc w nawiasie jego nazw.
5. Wze zamykamy metod WriteEndElement().
6. Kady wze utworzony metod WriteStartElement() musi zosta zakoczony
metod WriteEndElement().

Waciwoci klasy XMLTextWriter


Klasa XMLTextWriter ma par ciekawych waciwoci majcych wpyw na wygld
dokumentu. Najwaniejsze z nich zgromadziem w tabeli 13.6.
Tabela 13.6. Najwaniejsze waciwoci klasy XMLTextWriter
Waciwo
Opis
Formatowanie kodu (Formatting.Indented ze wciciami lub
Formatting
Formatting.None bez wci).
Indentation Liczba znakw, ktre bd stanowiy wcicia.
IdentChar Znak, ktry bdzie stanowi wcicie.
Namespaces Warto True spowoduje obsug przestrzeni nazw.
QuoteChar Znak cytowania (apostrof lub cudzysw).
WriteState Okrela, jakie dane bd wpisywane (apostrof, element).
XmlLang
Okrela atrybut xml:lang.

atwo zauway, e wikszo z tych waciwoci jest zwizana z formatowaniem kodu


przez klas XMLTextWriter. Mona wic okreli wartoci, ktre pozwol na tworzenie
czytelniejszego kodu:
Writer.Formatting = Formatting.Indented;
Writer.Indentation = 4;

Metody klasy XMLTextWriter


Metody klasy XMLTextWriter s zwizane z dodawaniem rnych informacji do pliku XML
np. encji, atrybutw oraz elementw. Najpierw spjrzmy na tabel 13.7, prezentujc
najwaniejsze z metod, a pniej bliej omwi kilka z nich.
Tabela 13.7. Najwaniejsze metody klasy XMLTextWriter
Metoda
Opis
LookupPrefix()
Zwraca prefiks najbliszej przestrzeni nazw.
WriteAttributes()
Dodaje atrybut do otwartego wza.
WriteBase64()
Dodaje tekst zakodowany algorytmem base64.
WriteBinHex()
Zapisuje dane w postaci heksadecymalnej.
WriteCData()
Zapisuje blok tekstu w postaci <![CDATA[...]]>.
WriteChars()
Zapisuje dane w formacie tekstowym.
WriteComment()
Dodaje komentarz w aktualnej pozycji.
WriteDocType()
Dodaje informacje DOCTYPE.
WriteEndAttribute() Koczy wpisywanie atrybutu.
WriteEndDocument()
Zamyka otwarty dokument.
WriteEndElement()
Zamyka otwarty element.
WriteStartAttribute() Otwiera nowy atrybut.
WriteStartDocument() Dodaje deklaracj dokumentu <?xml ?>.
WriteStartElement() Tworzy nowy element.
WriteWhitespace()
Dodaje pust przestrze.

Dodawanie prostych wzw


Klasy XML s tak skonstruowane, e dodawanie prostych i krtkich wzw i tak moe zaj
troch czasu ze wzgldu na du liczb wymaganych instrukcji. Inna sprawa, e samo
wpisywanie nazw metody moe troch potrwa.
Spjrz na poniszy fragment prostego kodu XML:
<?xml version="1.0"?>
<pizze>
<pizza>Hawajska</pizza>
</pizze>

Sprawa jest prosta: wze gwny pizze zawiera jeden element pizza. Do wygenerowania
takiego tekstu jest wymagany nastpujcy fragment kodu C#:
using System;
using System.Xml;
namespace XmlApp
{
class Program
{
static void Main(string[] args)
{
XmlTextWriter Writer = new
XmlTextWriter("C:\\output.xml", null);
Writer.Formatting = Formatting.Indented;
Writer.Indentation = 4;
Writer.WriteStartDocument();
Writer.WriteStartElement("pizze");
Writer.WriteStartElement("pizza");
Writer.WriteString("Hawajska");
Writer.WriteEndElement();
Writer.WriteEndElement();
Writer.WriteEndDocument();
Writer.Close();
}
}
}

Jak wida, rezultat dziaania takiego kodu zgadza si z tym, co zostao wygenerowane:
wcicia w kodzie maj wielko czterech spacji, a wze gwny nosi nazw pizze zgodnie
z tym, co zaprogramowalimy.
Dane, ktre maj znale si w znaczniku pizza, s okrelone jako parametr metody
WriteString().

Dynamiczne generowanie wzw


Najczciej stosowanym modelem jest dodawanie kolejnych wzw, ktre wczytuje program
np. z bazy danych lub z tablicy. Tworzenie takich dynamicznych wzw polega na
poczeniu ptli z odpowiednimi instrukcjami XML. Spjrzmy na listing 13.7. W programie
jest zadeklarowana dwuwymiarowa tablica PizzaArray, ktra zawiera informacje o nazwie
pizzy oraz jej cenie. Program odczytuje liczb elementw tablicy i na tej podstawie dodaje
informacje do pliku.
Listing 13.7. Dynamiczne tworzenie wzw

using System;
using System.Xml;
namespace XmlApp
{
class Program
{
static void Main(string[] args)
{
String[,] PizzaArray =
{
{ "Peperoni", "14,50" },
{ "Hawajska", "15,00" }
};
XmlTextWriter Writer = new
XmlTextWriter("C:\\output.xml", null);
Writer.Formatting = Formatting.Indented;
Writer.Indentation = 4;
Writer.WriteStartDocument();
Writer.WriteStartElement("pizze");
for (int i = 0; i < PizzaArray.GetLength(1); i++)
{
Writer.WriteStartElement("pizza");
Writer.WriteStartElement("nazwa");
Writer.WriteString(PizzaArray[i, 0]);
Writer.WriteEndElement();
Writer.WriteStartElement("cena");
Writer.WriteString(PizzaArray[i, 1]);
Writer.WriteEndElement();
Writer.WriteEndElement();
}
Writer.WriteEndElement();
Writer.WriteEndDocument();
Writer.Close();
}
}
}

Efektem dziaania takiego kodu bdzie utworzenie pliku o nastpujcej budowie:


<?xml version="1.0"?>
<pizze>
<pizza>
<nazwa>Peperoni</nazwa>

<cena>14</cena>
</pizza>
<pizza>
<nazwa>Hawajska</nazwa>
<cena>15,50</cena>
</pizza>
</pizze>

Czyli wszystko zadziaao zgodnie z naszymi oczekiwaniami.

Dodawanie atrybutw
Zmodyfikujmy aplikacj, tak aby wraz z elementem cena by dodawany atrybut o nazwie
rozmiar. Oto zmodyfikowany fragment poprzedniego programu:
Writer.WriteStartElement("cena");
Writer.WriteAttributeString("rozmiar", "maa");
Writer.WriteString(PizzaArray[i, 1]);
Writer.WriteEndElement();
Writer.WriteEndElement();

Dziki takiemu zabiegowi do wza cena zostanie doklejony atrybut rozmiar o wartoci
maa:
<cena rozmiar="maa">15,50</cena>

Zarwno klasa XmlTextWriter, jak i XmlTextReader nie korzystaj z modelu DOM. Zostay
one zaprojektowane w taki sposb, aby dostarcza mechanizmy odczytu oraz zapisu przy jak
najmniejszym nakadzie zasobw.

Dokumentacja XML
XML jest bardzo uniwersalnym formatem. rodowisko Visual C# Express Edition i jzyk C#
umoliwiaj tworzenie komentarzy XML. Su one do opisywania kodu, klas, metod, pl
czy waciwoci. Na podstawie takich komentarzy kompilator C# umoliwia wygenerowanie
dokumentacji w formacie XML. Oto przykad uycia komentarzy XML:
/// <summary>
/// Klasa MyFoo (przykad)
/// </summary>
class MyFoo

{
/// <summary>
/// Pomocnicze pole
/// </summary>
static string MyBar;
/// <summary>
/// Przykadowa metoda Foo
/// </summary>
/// <param name="Bar">Wany parametr</param>
static void Foo(string Bar)
{
}
/// <summary>
/// Waciwo Bar
/// </summary>
static string Bar
{
get
{
return MyBar;
}
}
}

Jak widzisz, element <summary> okrela opis danego elementu, aczkolwiek moliwe jest
rwnie opisywanie parametrw metod (element <param>).
Tworzenie komentarzy XML w rodowisku Visual C# Express Edition jest bardzo proste i
przyjemne. Wystarczy nad danym elementem trzykrotnie wcisn przycisk /, aby rodowisko
wygenerowao ca otoczk dla komentarza (czyli znaczniki XML).
Z menu Project wybierz pozycj Properties. Wybierz zakadk Build i zaznacz pozycj XML
documentation file. W polu tekstowym moesz okreli ciek, pod ktr kompilator bdzie
zapisywa dokumentacj XML. Kompilujc w ten sposb projekt, spowodujemy utworzenie
dokumentacji kodu, np.:
<?xml version="1.0"?>
<doc>
<assembly>
<name>FooApp</name>
</assembly>
<members>
<member name="T:FooApp.MyFoo">
<summary>
Klasa MyFoo (przykad)
</summary>
</member>

<member name="F:FooApp.MyFoo.MyBar">
<summary>
Pomocnicze pole
</summary>
</member>
<member name="M:FooApp.MyFoo.Foo(System.String)">
<summary>
Przykadowa metoda Foo
</summary>
<param name="Bar">Wany parametr</param>
</member>
<member name="P:FooApp.MyFoo.Bar">
<summary>
Waciwo Bar
</summary>
</member>
</members>
</doc>

Pokazaem uycie zaledwie dwch znacznikw XML <summary> oraz <param>. W


rzeczywistoci kompilator C# podczas tworzenia dokumentacji XML rozpoznaje o wiele
wicej znacznikw. Po wicej informacji na ten temat odsyam do dokumentacji rodowiska
.NET Framework.

Podsumowanie
XML jest bez wtpienia technologi rewolucyjn. Jej du zalet jest niezaleno i
moliwo atwego przeksztacenia dokumentw do innego formatu danych. Dziki licznym
parserom w do prosty sposb mona odczyta zawarto dokumentu XML, a nawet go
zmodyfikowa. Zreszt modyfikowanie pliku XML jest moliwe nawet za pomoc zwykego
edytora tekstu.
Rozdzia ten stanowi jedynie wprowadzenie do technologii XML, jest to bowiem bardzo
obszerny temat. Platforma .NET udostpnia ponadto wiele klas, o ktrych tu nie
wspominaem, a ktre take pomagaj w tworzeniu i obrbce dokumentw XML. Jeeli jeste
zainteresowany transformacj dokumentw XML do formatu HTML, powiniene si
zainteresowa specyfikacj XSL.

You might also like