Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00448 010353 11037632 na godz. na dobę w sumie
Zrozumieć platformę .NET. Wydanie II - książka
Zrozumieć platformę .NET. Wydanie II - książka
Autor: Liczba stron: 312
Wydawca: Helion Język publikacji: polski
ISBN: 83-246-0755-2 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> .net - programowanie
Porównaj ceny (książka, ebook, audiobook).

Poznaj platformę .NET

Wprowadzenie platformy programistycznej .NET okazało się przełomem w programowaniu aplikacji dla systemu Windows. Obsługiwane przez nią technologie, takie jak ADO.NET czy ASP.NET, pozwalają szybko i łatwo tworzyć różnorodne programy dla tego systemu, a także witryny oraz usługi internetowe. Zestaw elementów .NET składa się na jedną z najpotężniejszych obecnie platform programistycznych, a podstawowym narzędziem umożliwiającym korzystanie z możliwości jej najnowszej, drugiej, wersji jest Visual Studio 2005.

'Zrozumieć platformę .NET. Wydanie II' to krótkie wprowadzenie w niezwykle bogaty świat platformy .NET. Z książki tej dowiesz się, jak działa wspólne środowisko uruchomieniowe (CLR) oraz biblioteka klas .NET Framework. Poznasz możliwości Visual Studio 2005 oraz podstawowe języki platformy, takie jak C#, Visual Basic i C++. Nauczysz się tworzyć różne rodzaje programów przy użyciu podstawowych technologii platformy .NET, między innymi aplikacje webowe za pomocą ASP.NET czy bazodanowe w ADO.NET. Książka ta pozwoli Ci rozpocząć korzystanie z olbrzymich możliwości platformy .NET.

Dzięki tej książce szybko wkroczysz w świat platformy .NET.

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

IDZ DO IDZ DO PRZYK£ADOWY ROZDZIA£ PRZYK£ADOWY ROZDZIA£ SPIS TREœCI SPIS TREœCI KATALOG KSI¥¯EK KATALOG KSI¥¯EK KATALOG ONLINE KATALOG ONLINE ZAMÓW DRUKOWANY KATALOG ZAMÓW DRUKOWANY KATALOG TWÓJ KOSZYK TWÓJ KOSZYK DODAJ DO KOSZYKA DODAJ DO KOSZYKA CENNIK I INFORMACJE CENNIK I INFORMACJE ZAMÓW INFORMACJE ZAMÓW INFORMACJE O NOWOœCIACH O NOWOœCIACH ZAMÓW CENNIK ZAMÓW CENNIK CZYTELNIA CZYTELNIA FRAGMENTY KSI¥¯EK ONLINE FRAGMENTY KSI¥¯EK ONLINE Wydawnictwo Helion ul. Koœciuszki 1c 44-100 Gliwice tel. 032 230 98 63 e-mail: helion@helion.pl Zrozumieæ platformê .NET. Wydanie II Autor: David Chappell T³umaczenie: Anna Trojan ISBN: 83-246-0755-2 Tytu³ orygina³u: Understanding .NET (2nd Edition) Format: B5, stron: 312 Poznaj platformê .NET (cid:129) Dowiedz siê, jak funkcjonuje platforma .NET (cid:129) Naucz siê tworzyæ za jej pomoc¹ ró¿ne rodzaje aplikacji (cid:129) Zapoznaj siê z jej jêzykami Wprowadzenie platformy programistycznej .NET okaza³o siê prze³omem w programowaniu aplikacji dla systemu Windows. Obs³ugiwane przez ni¹ technologie, takie jak ADO.NET czy ASP.NET, pozwalaj¹ szybko i ³atwo tworzyæ ró¿norodne programy dla tego systemu, a tak¿e witryny oraz us³ugi internetowe. Zestaw elementów .NET sk³ada siê na jedn¹ z najpotê¿niejszych obecnie platform programistycznych, a podstawowym narzêdziem umo¿liwiaj¹cym korzystanie z mo¿liwoœci jej najnowszej, drugiej, wersji jest Visual Studio 2005. „Zrozumieæ platformê .NET. Wydanie II” to krótkie wprowadzenie w niezwykle bogaty œwiat platformy .NET. Z ksi¹¿ki tej dowiesz siê, jak dzia³a wspólne œrodowisko uruchomieniowe (CLR) oraz biblioteka klas .NET Framework. Poznasz mo¿liwoœci Visual Studio 2005 oraz podstawowe jêzyki platformy, takie jak C#, Visual Basic i C++. Nauczysz siê tworzyæ ró¿ne rodzaje programów przy u¿yciu podstawowych technologii platformy .NET, miêdzy innymi aplikacje webowe za pomoc¹ ASP.NET czy bazodanowe w ADO.NET. Ksi¹¿ka ta pozwoli Ci rozpocz¹æ korzystanie z olbrzymich mo¿liwoœci platformy .NET. (cid:129) Biblioteka klas .NET Framework (cid:129) Wspólne œrodowisko uruchomieniowe (CLR) (cid:129) Przegl¹d jêzyków .NET (cid:129) Visual Studio 2005 (cid:129) Tworzenie aplikacji webowych za pomoc¹ ASP.NET (cid:129) U¿ywanie ADO.NET do obs³ugi danych (cid:129) Programowanie rozproszone Dziêki tej ksi¹¿ce szybko wkroczysz w œwiat platformy .NET Spis treści Przedmowa 1 WPROWADZENIE DO .NET Platforma .NET Framework Wspólne środowisko uruchomieniowe (CLR) Biblioteka klas .NET Framework Visual Studio 2005 Języki ogólnego przeznaczenia Języki domenowe Praca w grupach — Visual Studio Team System Wnioski 2 WSPÓLNE ŚRODOWISKO URUCHOMIENIOWE (CLR) Tworzenie kodu zarządzanego — wspólny system typów CTS Wprowadzenie do CTS Bliższe spojrzenie na typy CTS Konwersja typów bezpośrednich na typy referencyjne — pakowanie Specyfikacja CLS Kompilowanie kodu zarządzanego Język MSIL Metadane Organizowanie kodu zarządzanego — pakiety Metadane dla pakietów — manifesty Kategoryzacja pakietów 9 13 14 20 23 32 36 40 43 45 47 48 49 51 55 56 57 58 61 63 63 65 6 Spis treści Wykonywanie kodu zarządzanego Ładowanie pakietów Kompilowanie kodu w MSIL Tworzenie macierzystego obrazu — NGEN Zabezpieczanie pakietów Czyszczenie pamięci Domeny aplikacji Wnioski 3 JĘZYKI .NET C# Przykład C# Typy w C# Struktury sterujące w C# Inne cechy C# Visual Basic Przykład Visual Basic Typy w Visual Basic Struktury sterujące w Visual Basic Inne cechy Visual Basic C++ C++/CLI Managed C++ Wniosek 67 67 68 72 72 77 80 82 85 87 87 90 104 105 113 114 117 129 130 134 136 140 144 4 PRZEGLĄD BIBLIOTEKI KLAS .NET FRAMEWORK Przegląd biblioteki Podstawowe przestrzenie nazw 145 145 Przestrzeń nazw System 146 Przegląd przestrzeni nazw podporządkowanych System 147 157 157 160 164 167 175 179 183 193 Wejście i wyjście — System.IO Serializacja — System.Runtime.Serialization Introspekcja — System.Reflection XML — System.Xml Transakcje — System.Transactions Współdziałanie — System.Runtime.InteropServices GUI Windows — System.Windows.Forms Wniosek Spis treści 7 5 BUDOWANIE APLIKACJI WEBOWYCH — ASP.NET Aplikacje ASP.NET — podstawy Tworzenie plików .aspx Używanie kontrolek webowych Oddzielanie interfejsu użytkownika od kodu — schowanie kodu (code-behind) Definiowanie aplikacji Wykorzystywanie informacji o kontekście Aplikacje ASP.NET — zagadnienia zaawansowane Zarządzanie stanem Przechowywanie danych w pamięci podręcznej Uwierzytelnianie i autoryzacja Zarządzanie użytkownikami — przynależność Praca z danymi — wiązanie danych Dostosowanie interfejsów użytkownika do własnych potrzeb — Web Parts Wniosek 6 DOSTĘP DO DANYCH — ADO.NET Wykorzystywanie dostawców danych .NET Framework Wykorzystywanie obiektów Connection i Command Dostęp do danych za pomocą DataReader Dostęp do danych za pomocą DataSet Tworzenie i wykorzystywanie DataSet Dostęp do zawartości DataSet i jego modyfikacja Wykorzystywanie DataSet z danymi zdefiniowanymi w XML Wniosek 7 BUDOWANIE APLIKACJI ROZPROSZONYCH Usługi sieciowe ASP.NET — System.Web.Services Podstawy usług sieciowych Aplikacje usług sieciowych ASP.NET — podstawy Aplikacje usług sieciowych ASP.NET — zagadnienia zaawansowane .NET Remoting — System.Runtime.Remoting Przegląd procesu .NET Remoting Przekazywanie informacji do zdalnych obiektów 195 196 197 201 206 208 210 212 212 217 218 220 221 224 226 227 228 233 235 239 240 246 248 255 257 257 258 260 264 268 270 271 8 Spis treści Wybór kanału Tworzenie i niszczenie zdalnych obiektów Enterprise Services — System.EnterpriseServices Co udostępniają Enterprise Services Enterprise Services i COM+ Podsumowanie O autorze Skorowidz 273 276 282 283 286 289 291 293 Zrozumienie języka opartego na CLR rozpoczyna się od zrozumienia CLR 3 Języki .NET Wspólne środowisko uruchomieniowe (CLR) zostało zaprojektowane specjalnie w celu wspierania wielu języków. Jednak ogólnie rzecz bio- rąc, języki zbudowane na bazie CLR zazwyczaj mają ze sobą wiele wspólnego. Definiując duży zbiór podstawowej semantyki, CLR jed- nocześnie definiuje znaczną część typowego języka programowania, który je wykorzystuje. Podstawową sprawą przy opanowaniu dowol- nego języka opartego na CLR jest na przykład zrozumienie, w jaki spo- sób standardowe typy zdefiniowane przez CLR są odwzorowywane na ten język. Oczywiście, konieczne jest także nauczenie się składni języka, w tym struktur sterujących, które ten język zapewnia. Jednak wiedząc już, co oferuje CLR, łatwiej jest zrozumieć dowolny język zbudowany na bazie wspólnego środowiska uruchomieniowego. Niniejszy rozdział opisuje C# i Visual Basic — dwa najważniejsze języki oparte na CLR. Omawia także krótko C++/CLI, czyli rozszerzenia, które pozwalają programistom C++ na pisanie kodu opartego na CLR. Celem nie jest dostarczenie wyczerpującego opisu każdej cechy tych języków — do tego potrzeba by następnych trzech książek — ale raczej naszkicowanie, jak wyglądają te języki i w jaki sposób wyrażają podstawową funkcjonalność zapewnianą przez CLR. W całym rozdziale opisano wersje tych języków dostępne w Visual Studio 2005. 86 Języki .NET (cid:132) Perspektywa: co z Javą dla .NET Framework? Od samego początku Microsoft dostarczał J#, wersję języka programowania Java dla .NET Framework. Język ten implementuje składnię i zachowanie Javy na bazie CLR. Dobrze mieć taką możliwość, ale kiedy używanie J# ma jakikolwiek sens? Istnieją dwie sytuacje, w których J# będzie dobrym wyborem. Pierwsza ma miejsce, gdy istniejący kod w Javie musi zostać przeniesiony na .NET Framework. Kod ten mógł zostać napisany za pomocą Visual J++, narzędzia sprzed ery .NET produkcji Microsoftu, bądź też za pomocą standardowych narzędzi do języka Java, takich jak Eclipse. W obu przypadkach istnienie J# ułatwia przeniesienie tego kodu do świata .NET. Należy jednak zwrócić uwagę na fakt, iż .NET Framework nie implementuje popularnych tech- nologii Javy, takich jak Java Server Pages (JSP) czy Enterprise JavaBeans, zatem nie każdy kod w tym języku można łatwo przenieść. Mimo to typowa logika biznesowa Javy może być uruchomiona na .NET Framework bez większego nakładu pracy. Istnieje nawet narzędzie konwersji binarnej, które pozwala przekonwertować kod bajtowy Javy na MSIL, przydatne wtedy, gdy nie mamy już dostępu do kodu źródłowego aplikacji. Druga okazja, w której wybranie J# jest dobrym rozwiązaniem, to sytuacja mająca miejsce, gdy programista Javy przenosi się na .NET Framework. Ludzie często bardzo się przywiązują do konkretnej składni, dlatego praca w znajomym języku może ułatwić przejście. Nadal jednak trudno jest powiedzieć, że tworzenie kodu .NET Framework w Javie jest dobrym pomysłem. Microsoft wyraźnie skupia się na C# oraz VB jako głównych językach do tworzenia nowych aplikacji .NET, dlatego wybranie innego języka jest zawsze nieco ryzykowne. Ponadto brak standardowych pakietów Javy w .NET ozna- cza, że programista przechodzący z Javy nadal nie będzie czuł się w .NET jak w domu — ciągle jeszcze będzie musiał się dużo nauczyć. Jednak biorąc pod uwagę podobieństwa pomiędzy Javą i C#, nawet najbardziej zagorzały fan Javy nie powinien być zmartwiony przesiadką na C#. Obsługa Javy przez Microsoft ma wyraźnie zachęcać do przenoszenia kodu na plat- formę .NET Framework i kusić programistów tego języka, a nie pomagać programistom w tworzeniu nowego oprogramowania w Javie. Linie frontu są jasne: to .NET przeciwko światowi Javy. Bez wątpienia ma to jednak pozytywne strony. Dwa obozy z potężnymi technologiami, każdy z silną pozycją — to idealna sytuacja. Każdy z nich dostarcza in- nowacji, które od razu podchwytuje druga strona, dzięki czemu w efekcie końcowym na konkurencji zyskują wszyscy. C# 87 C# jest zorientowanym obiektowo językiem programowania ze składnią podobną do języka C Visual Studio 2005 implementuje wersję 2.0 języka C# Microsoft dostarcza najpopularniejsze kompilatory C#, jednak nie jedyne C# Jak sugeruje sama nazwa, C# jest członkiem rodziny języków progra- mowania C. W przeciwieństwie do C, C# jest jawnie zorientowany obiektowo. Z kolei przeciwieństwie do C++, który był najpopular- niejszym językiem zorientowanym obiektowo z tej rodziny, C# nie jest tak bardzo skomplikowany. Zamiast tego C# został zaprojekto- wany jako język przystępny dla każdego, kto ma jakieś doświadczenie z C++ czy Javą. Zaprojektowany przez Microsoft C# pierwszy raz pojawił się wraz z wydaniem Visual Studio .NET w 2002 roku. W Visual Studio 2005 zaimplementowano C# 2.0, zbudowany na podstawie tej początkowej wersji sprzed trzech lat. Jak w przypadku wszystkich zagadnień omó- wionych w niniejszej książce, opisana tutaj wersja języka jest wersją z wydania z 2005 roku. Najpopularniejszym narzędziem, za pomocą którego tworzy się dzisiaj kod w C#, jest Microsoft Visual Studio. Nie jest to jednak jedyny możliwy wybór. W ramach .NET Framework Microsoft dostarcza także kompi- lator wiersza poleceń o nazwie csc.exe; istnieje także open source’owy kompilator C#. Jednak biorąc pod uwagę rozbudowane wsparcie dla tworzenia aplikacji napisanych w C# i opartych na CLR w Visual Studio, trudno sobie wyobrazić, że alternatywne rozwiązania przyciągną wielu programistów. Przykład C# Jak większość języków programowania, C# definiuje typy danych, struktury sterujące i tak dalej. W przeciwieństwie do starszych języków, C# robi to wszystko, budując w oparciu o CLR. By zilustrować to twier- dzenie, poniżej znajduje się prosty przykład C#: // Przykład C# interface IMath { int Factorial(int f); double SquareRoot(double s); } class Compute : IMath { public int Factorial(int f) 88 Języki .NET { int i; int result = 1; for (i=2; i =f; i++) result = result * i; return result; } public double SquareRoot(double s) { return System.Math.Sqrt(s); } } class DisplayValues { static void Main() { Compute c = new Compute(); int v; v = 5; System.Console.WriteLine( {0} silnia: {1} , v, c.Factorial(v)); System.Console.WriteLine( Pierwiastek kwadratowy z {0}: {1:f4} , v, c.SquareRoot(v)); } } Każdy program w C# zbudowany jest z jednego lub większej liczby typów Program rozpoczyna się od komentarza, oznaczonego przez dwa prawe ukośniki, który zawiera krótki opis celów programu. Ciało programu składa się z trzech typów: interfejsu o nazwie IMath oraz dwóch klas — Compute i DisplayValues. Wszystkie programy w C# składają się z pewnej liczby typów, z których najbardziej zewnętrzne to klasy, in- terfejsy, struktury, typy wyliczeniowe lub delegaty (mogą się tu również pojawić, omówione później, przestrzenie nazw). Wszystkie metody, pola i inne składniki typów muszą przynależeć do jednego z wymie- nionych typów, co oznacza, że C# nie pozwala ani na globalne zmienne, ani na globalne metody. Interfejs C# jest wyrażeniem interfejsu CTS Interfejs IMath, będący w C# inkarnacją typu „interfejs” ze wspólnego systemu typów CTS, opisanego w rozdziale 2., definiuje metody Facto- rial oraz SquareRoot. Każda z tych metod przyjmuje jeden parametr C# 89 Klasa C# jest wyrażeniem klasy CTS Wykonanie programu w C# rozpoczyna się od metody Main i zwraca wynik liczbowy. Parametry te przekazywane są za pomocą wartości, co jest domyślnym zachowaniem w C#. Oznacza to, że zmiany wartości parametru wewnątrz metody nie będą widziane przez program wywołujący po zakończeniu metody. Umieszczenie słowa kluczowego ref przed parametrem sprawia, że parametr jest przeka- zywany przez referencję, co oznacza, że zmiany wewnątrz metody zo- staną odzwierciedlone w programie wywołującym. Każda klasa w powyższym przykładzie jest także inkarnacją typu CTS w C#. Klasy języka C# mogą implementować jeden lub więcej inter- fejsów, dziedziczyć po co najwyżej jednej klasie i wykonywać inne działania zdefiniowane dla klasy CTS. Pierwsza zaprezentowana po- wyżej klasa, Compute, implementuje interfejs IMath, na co wskazuje dwukropek pomiędzy Compute a IMath. Zgodnie z tym klasa musi za- wierać implementacje obu metod interfejsu. Ciało metody Factorial deklaruje parę zmiennych liczbowych (nazywanych w żargonie CTS polami), inicjalizuje drugą z nich jako 1, a następnie wykonuje prostą pętlę for w celu obliczenia silni jej parametru (i nie przejmuje się sprawdzaniem przepełnienia, co jest bez wątpienia złą praktyką pro- gramistyczną). Druga metoda Compute, SquareRoot, jest jeszcze prostsza. Bazuje ona na bibliotece klas .NET Framework, wywołując funkcję Sqrt dostarczaną przez klasę Math z przestrzeni nazw System. Ostatni typ w powyższym przykładzie, klasa DisplayValues, zawiera pojedynczą metodę — Main. Podobnie jak C i C++, program w C# rozpoczyna wykonywanie od tej metody w dowolnym typie, w którym ona się znajduje. Main musi być zadeklarowana jako metoda statyczna i — choć nie jest to tutaj pokazane — może przyjmować argumenty przekazane w momencie uruchomienia programu. W powyższym przykładzie Main zwraca void, co jest stosowanym w C# sposobem powiedzenia, że metoda nie zwraca wartości. Typ void nie może być jednak wykorzystywany w parametrach w taki sposób, jak w C czy C++. Zamiast tego jego jedynym celem jest wskazanie, że metoda nie zwraca wartości. W powyższym przykładzie Main tworzy obiekt klasy Compute, używając operatora new z C#. Kiedy program zostanie uruchomiony, new zosta- nie przetłumaczone na instrukcję języka MSIL newobj, opisaną w roz- dziale 2. Następnie Main deklaruje zmienną int i ustawia jej wartość na 5. Wartość ta przekazywana jest jako parametr do wywołań metod 90 Języki .NET Metoda WriteLine klasy Console wypisuje sformatowane dane wyjściowe do konsoli Typy w C# są zbudowane na bazie typów CTS Factorial i SquareRoot, dostarczonych przez interfejs Compute. Metoda Factorial oczekuje int, czyli dokładnie tego, co przekazano jej w tym wywołaniu, jednak SquareRoot oczekuje typu double. Typ int zosta- nie automatycznie przekonwertowany na double, ponieważ konwersja ta może zostać dokonana bez straty informacji. C# nazywa to kon- wersją niejawną (ang. implicit conversion), odróżniając ją w ten spo- sób od konwersji typów, które są jawnie oznaczone w kodzie. Wyniki są wypisywane za pomocą metody WriteLine klasy Console, innej standardowej części przestrzeni nazw System w .NET Framework. Metoda ta wykorzystuje opakowane w nawiasy klamrowe liczby, od- powiadające zmiennym, które mają być zwrócone. Warto zwrócić uwagę na fakt, iż w drugim wywołaniu WriteLine po liczbie w nawiasach występuje :f4. Ta dyrektywa formatująca oznacza, że wartość powin- na być zapisana jako liczba stałoprzecinkowa z czterema miejscami po prawej stronie przecinka. Zgodnie z tym dane wyjściowe powyższego prostego programu będą następujące: 5 silnia: 120 Pierwiastek kwadratowy z 5: 2.2361 Powyższy przykład jest nierealistycznie prosty, jednak jego celem jest pokazanie ogólnej struktury i stylu C#. W języku tym istnieje oczywi- ście o wiele więcej możliwości, co zaprezentowano w dalszej części rozdziału. Typy w C# Każdy typ w C# jest zbudowany na bazie analogicznego typu CTS, dostarczonego przez CLR. Tabela 3.1 prezentuje większość typów CTS wraz z ich odpowiednikami w C#. Jak wspomniano wcześniej w książce, wszystkie typy danych są zdefiniowane w przestrzeni nazw System. Odpowiedniki w C# pokazane w tabeli są w rzeczywistości skróco- nymi synonimami alternatywnych definicji. W omówionym powyżej przykładzie linia: int i; mogłaby zostać zastąpiona przez: System.Int32 i; Obie możliwości działają i obie zwrócą dokładnie te same wyniki. Tabela 3.1. Niektóre typy CTS i ich odpowiedniki w C# C# 91 CTS Byte Char Int16 Int32 Int64 UInt16 UInt32 UInt64 Single Double Decimal Boolean Class Interface Delegate C# byte char short int long ushort uint ulong float double decimal bool class interface delegate Warto zauważyć, że w C# wielkość liter ma znaczenie. Zadeklarowanie zmiennej jako Double zamiast double skończy się błędem kompilatora. Dla osób przyzwyczajonych do języków wywodzących się z C wydaje się to normalne. Pozostałe osoby będą jednak musiały poświęcić nieco czasu na przyzwyczajenie się do tego. Klasy Klasy C# pokazują zachowanie klas CTS, używając składni wywodzą- cej się z języka C. Na przykład klasy CTS mogą implementować jeden lub więcej interfejsów, jednak dziedziczą bezpośrednio po co najwy- żej jednej klasie. Klasa C# Calculator, która implementuje interfejsy IAlgebra oraz ITrig i dziedziczy po klasie MathBasics byłaby zade- klarowana w następujący sposób: class Calculator : MathBasics, IAlgebra, ITrig { ... } Warto zauważyć, że klasa bazowa, o ile taka występuje, musi pojawić się na tej liście jako pierwsza, a dopiero po niej następują nazwy in- terfejsów. Klasy C# mogą być także oznaczone jako zapieczętowane Tak jak klasa CTS, klasa C# może bezpośrednio dziedziczyć jedynie po jednej klasie 92 Języki .NET Klasa C# może zawierać pola, metody oraz właściwości Właściwość zmusza wszystkie próby dostępu do wartości do używania metod get oraz set lub abstrakcyjne, jak omówiono to w rozdziale 2., oraz mogą mieć przypisaną widoczność publiczną lub wewnętrzną (ang. internal), która jest wartością domyślną. Przekładają się one odpowiednio na zdefi- niowane w CTS widoczności public oraz assembly. Po skompilowaniu wszystkie te informacje są przechowywane w metadanych klasy. Klasa C# może zawierać pola, metody oraz właściwości — wszystkie one są zdefiniowane dla dowolnej klasy CTS. Każde z nich posiada dostępność, oznaczoną w C# przez odpowiedni modyfikator dostępu, taki jak public czy private. Zarówno pola, jak i metody były zilu- strowane na powyższym prostym programie, jednak właściwości są na tyle ważne, że zasługują na poświęcenie im osobnego przykładu. Do każdego pola oznaczonego jako public można uzyskać bezpo- średni dostęp z kodu innej klasy. Przypuśćmy jednak, że klasa, w któ- rej pole to jest zdefiniowane, potrzebuje kontrolować sposób, w jaki się to odbywa. Być może powinno się na przykład sprawdzać każde przypisanie do tego pola w odniesieniu do zdefiniowanych wcześniej limitów lub też każda próba odczytania tego pola powinna być w jakiś sposób weryfikowana. Jednym ze sposobów wykonania tego zadania byłoby oznaczenie pola jako private, a następnie utworzenie metod, za pomocą których pole będzie mogło być modyfikowane i odczy- tywane. Ponieważ jednak ten wzorzec jest tak częsty, C# zapewnia właściwości, będące łatwiejszą możliwością osiągnięcia tego samego. Poniżej znajduje się prosty przykład: class PriorityValue { private int pValue; public int Priority { get { return pValue; } set { if (value 0 value 11) pValue = value; } } } class PropertyExample C# 93 Dostęp do właściwości odbywa się podobnie jak do pól Klasy dostarczają konstruktory, nadpisują metody swojego rodzica i redefiniują operatory { static void Main() { PriorityValue p = new PriorityValue(); p.Priority = 8; System.Console.WriteLine( Priorytet: {0} , p.Priority); } } Klasa PriorityValue deklaruje prywatne pole pValue oraz właściwość o nazwie Priority. Łatwo stwierdzić, że Priority jest właściwością, ponieważ zawiera dwie metody dostępowe (ang. accessors): get oraz set. Dostęp do właściwości odbywa się poprzez kod zawarty w tych metodach dostępowych. Tutaj na przykład próba odczytania właści- wości Priority wykonuje kod get, który zwraca zawartość pValue. Próba zmodyfikowania Priority wywołuje kod set, który uaktualnia tę właściwość tylko wtedy, gdy nowa wartość mieści się w przedziale od 1 do 10. Słowo kluczowe value oznacza cokolwiek, co kod wy- wołujący próbuje przypisać do tej właściwości. Dlaczego właściwości są lepsze od pisania własnych metod do otrzy- mania (get) i ustawienia (set) wartości pola? Ponieważ zamiast wyma- gania jawnych wywołań metod set i get, kod wykorzystujący właści- wości widzi te właściwości tak, jakby były one polami — składnia jest taka sama. Pozwala to na uproszczenie kodu wywołującego i jedno- cześnie na kontrolowanie sposobu, w jaki właściwość jest odczytywa- na i modyfikowana. W rzeczywistości istnieją silne przesłanki za tym, by nigdy nie używać publicznych pól. Właściwości są zawsze lepszym wyborem. Klasa może implementować jeden lub więcej konstruktorów, które są metodami wywoływanymi wtedy, gdy tworzony jest obiekt klasy. Każda klasa może także dostarczać co najwyżej jeden destruktor, który w C# jest w rzeczywistości nazwą finalizatora — koncepcji przedstawionej w rozdziale 2. Jeśli klasa dziedziczy po innej klasie, potencjalnie może nadpisywać jeden lub więcej składników typów rodzica, takich jak metoda. By to uczynić, składnik musi być zadeklarowany u rodzica ze słowem kluczowym virtual, natomiast klasa dziecka musi oznaczyć nowy składnik słowem kluczowym override. Klasa może także defi- niować przeciążone operatory. Przeciążony operator to taki, który został zredefiniowany w celu otrzymania specjalnego znaczenia w momencie 94 Języki .NET Interfejs C# może dziedziczyć bezpośrednio po jednym lub wielu innych interfejsach Struktury w C# są nieco uproszczonymi klasami C# użycia go z obiektami danej klasy. Na przykład klasa reprezentująca grupy robocze w organizacji może redefiniować operator + w taki spo- sób, by oznaczał on łączenie dwóch grup roboczych w jedną. Interfejsy Interfejsy są relatywnie prostymi podmiotami, natomiast podstawowa składnia w C# definiująca interfejs została zaprezentowana we wcze- śniejszym przykładzie. Nie pokazano tam jednak, w jaki sposób C# wyraża dziedziczenie po wielu interfejsach, czyli sytuację, w której jeden interfejs dziedziczy po więcej niż jednym rodzicu. Gdyby na przykład interfejs ITrig dziedziczył po trzech interfejsach (ISine, ICosine oraz ITangent), mógłby być zadeklarowany w następujący sposób: Interface ITrig: ISine, ICosine, ITangent { ... } ITrig będzie zawierał wszystkie metody, właściwości i inne składniki typów zdefiniowane w jego trzech rodzicach, a także wszystko to, co sam zdefiniuje. Struktury CTS nie definiuje w jawny sposób typu danych o nazwie „struktura”. Zamiast tego struktury w C# są oparte na klasach, zatem tak jak klasy mogą implementować interfejsy, zawierać metody, pola, właściwości i inne. W przeciwieństwie do klas, struktury są jednak typami bezpo- średnimi (dziedziczą po System.ValueType), a nie referencyjnymi, co oznacza, że są alokowane na stosie. Warto przypomnieć, że typy bez- pośrednie nie mogą uczestniczyć w dziedziczeniu, zatem — w prze- ciwieństwie do klas — struktury nie mogą dziedziczyć po innym typie. Nie można także zdefiniować typu, który dziedziczy po strukturze. Poniżej znajduje się prosty przykład struktury C#: struct employee { string name; int age; } W powyższym przykładzie struktura zawiera jedynie pola, podobnie jak tradycyjne struktury w stylu języka C. Struktura może jednak być bardziej złożona. Na przykład przedstawiona wcześniej klasa Compute C# 95 Tak jak tablice CTS, tablice C# są typami referencyjnymi mogłaby być przekonwertowana na strukturę, ze wszystkimi metodami i tak dalej, jedynie za pomocą zmiany słowa kluczowego class w jej definicji na słowo struct. Gdyby doszło do takiej zmiany, wykonanie programu wyglądałoby nieco inaczej, ale jego wynik byłby taki sam. Tablice Jak w innych językach programowania, tablice C# są uporządkowa- nymi grupami elementów tego samego typu. Jednak w przeciwień- stwie do wielu innych języków, tablice C# są obiektami. W rzeczywi- stości, jak opisano to w rozdziale 2., są one typami referencyjnymi, co oznacza, że są alokowane na stercie. Poniżej znajduje się przykład definiujący jednowymiarową tablicę liczb całkowitych: int[] ages; Ponieważ ages jest obiektem, nie istnieje żaden jego obiekt, dopóki się go jawnie nie utworzy. Można to zrobić w poniższy sposób: ages = new int[10]; co alokuje na stercie miejsce dla dziesięciu liczb całkowitych. Jak po- kazuje powyższy przykład, tablica C# nie ma określonego rozmiaru, dopóki nie utworzy się obiektu tego typu tablicy. Możliwe jest także zarówno zadeklarowanie, jak i utworzenie obiektu tablicy w pojedyn- czym poleceniu, takim jak: int[] ages = new int[10]; Możliwe jest deklarowanie tablic dowolnego typu, jednak w jaki do- kładnie sposób tablica zostanie zaalokowana, zależy od tego, czy bę- dzie to tablica typów bezpośrednich, czy referencyjnych. Powyższy przykład alokuje miejsce dla dziesięciu liczb całkowitych na stercie, podczas gdy: string[] names = new string[10]; alokuje miejsce dla dziesięciu referencji do łańcuchów znaków na stercie. Tablica typów bezpośrednich, takich jak liczby całkowite, w rze- czywistości zawiera ich wartości, podczas gdy tablica typów referen- cyjnych, takich jak łańcuchy znaków w ostatnim przykładzie, zawiera jedynie referencje do wartości. 96 Języki .NET Tablice C# mogą być wielowymiarowe Dostęp do standardo- wych metod i właściwości możliwy jest ze wszystkich tablic C# Przekazanie referencji do metody jako parametr jest często przydatne Tablice mogą także mieć wiele wymiarów. Na przykład instrukcja: int[,] points = new int[10,20]; tworzy dwuwymiarową tablicę liczb całkowitych. Pierwszy wymiar ma dziesięć elementów, natomiast drugi ma ich dwadzieścia. Bez względu na liczbę wymiarów w tablicy, dolną granicą każdego z nich jest zawsze zero. Typ tablic w C# jest zbudowany na bazie obsługi tablic zapewnionej przez CLR. Jak wspomniano w poprzednim rozdziale, wszystkie oparte na CLR tablice, włącznie z tablicami C#, dziedziczą po System.Array. Ten typ bazowy dostarcza różne metody i właściwości, do których do- stęp można uzyskać z dowolnego obiektu typu „tablica”. Na przykład metoda GetLength może być wykorzystana do ustalenia długości ele- mentów w danym wymiarze tablicy, podczas gdy metoda CopyTo może zostać użyta do skopiowania wszystkich elementów jednowymiarowej tablicy do innej jednowymiarowej tablicy. Delegaty i zdarzenia Przekazanie referencji do metody jest stosunkowo często spotykanym działaniem. Przypuśćmy na przykład, że musimy przekazać jakiejś czę- ści kodu informację o tym, która metoda powinna być wywołana, kiedy nastąpi określone zdarzenie. Potrzebny jest jakiś sposób przekazania tożsamości tej funkcji wywołania zwrotnego w czasie uruchomienia programu. W C++ odbywa się to dzięki przekazaniu adresu metody, czyli wskaźnika do kodu, który należy wywołać. Jednak w bezpiecznym świecie .NET Framework przekazywanie surowych adresów nie jest dozwolone. Problem ten jednak nie znika. Bezpieczny sposób przeka- zywania referencji do metody jest nadal przydatny. Delegat C# zapewnia bezpieczny sposób przekazywania referencji do metody Jak opisano to pokrótce w rozdziale 2., CTS definiuje dla tych celów typ referencyjny o nazwie delegat. Delegat jest obiektem, który za- wiera referencję do metody o pewnej konkretnej sygnaturze. Kiedy zostanie utworzony i zainicjalizowany, może być przekazany jako pa- rametr do jakiejś metody i wywołany. Poniżej znajduje się prosty przy- kład utworzenia i wykorzystania delegata w C#: C# 97 delegate void SDelegate(string s); class DelegateExample { public static void Main() { SDelegate del = new SDelegate(WriteString); CallDelegate(del); } public static void CallDelegate(SDelegate Write) { System.Console.WriteLine( W CallDelegate ); Write( Witaj w delegacie ); } public static void WriteString(string s) { System.Console.WriteLine( W WriteString: {0} , s); } } Przykład rozpoczyna się od zdefiniowania SDelegate jako typu „delegat”. Definicja ta określa, że obiekty SDelegate mogą zawierać referencje tylko do metod, które przyjmują pojedynczy parametr, będący łańcuchem znaków. W metodzie Main z przykładu zmienna del typu SDelegate zostaje zadeklarowana, a następnie zainicjalizowana w taki sposób, by zawierała referencję do metody WriteString. Metoda ta jest później definiowana w klasie i — zgodnie z wymaganiami — posiada poje- dynczy parametr typu string. Metoda Main wywołuje następnie me- todę CallDelegate, przekazując jej del jako parametr. Metoda Call- Delegate jest zdefiniowana w taki sposób, by przyjmowała SDelegate jako swój parametr. Innymi słowy, to, co przekazywane jest do metody, to obiekt-delegat zawierający adres jakiejś metody. Ponieważ jest to SDelegate, metoda ta musi posiadać pojedynczy parametr typu string. Wewnątrz SDelegate metoda identyfikowana przez przekazany pa- rametr jest określana jako Write, a po wyświetleniu prostego komuni- katu CallDelegate wywołuje metodę Write. Ponieważ jednak Write jest w rzeczywistości delegatem, tak naprawdę wywołana jest metoda, do której delegat zawiera referencję, a więc WriteString. Wynikiem tego prostego przykładu jest: W CallDelegate W WriteString: Witaj w delegacie Warto zwrócić uwagę na fakt, że jako pierwsza wykonywana jest me- toda CallDelegate, a dopiero po niej następuje WriteString. 98 Języki .NET Delegat może być łączony z innymi delegatami .NET Framework oraz C# zapewniają obsługę zdarzeń opartą na delegatach Delegaty mogą być znacznie bardziej skomplikowane, niż wskazuje na to powyższy przykład. Mogą być na przykład łączone tak, by wywoła- nie pojedynczego delegata wiązało się z wywołaniem dwóch lub więk- szej liczby delegatów, które zawiera pierwszy z nich. Jednak nawet proste delegaty mogą być przydatne. Dzięki zapewnieniu bezpieczne- go sposobu przekazania referencji do metody ułatwiają wykonanie tej czynności w sposób o wiele mnie ryzykowny niż w przypadku wcze- śniejszych języków. Jedno z bardziej popularnych zastosowań delegatów obejmuje obsługę zdarzeń. Na przykład w GUI kliknięcia myszką użytkownika, naciśnięcia klawiszy czy inne formy danych wejściowych mogą być przyjmowane jako zdarzenia; zdarzenia są także użyteczne w innych kontekstach. Ponieważ zdarzenia są tak popularne, C# oraz .NET Framework za- pewniają specjalną pomoc przy wykorzystywaniu delegatów w obsłudze zdarzeń w spójny sposób. Delegat, którego używa zdarzenie, nazywany jest programem obsługi zdarzeń (ang. event handler), choć w rzeczy- wistości jest to normalny delegat. Platforma .NET Framework definiuje jednak dwie konwencje dla takich programów obsługi zdarzeń: (cid:132) Program obsługi zdarzeń nie zwraca wartości, co oznacza, że typem zwracanej wartości jest void. (cid:132) Program obsługi zdarzeń zawsze przyjmuje dwa argumenty. Pierwszy argument, identyfikujący źródło zdarzenia, jest zgodnie z konwencją nazywany sender i jest typu System.Object (w C# jest po prostu typem object, który jest aliasem System.Object). Dzięki temu odbiorca zdarzenia może łatwo odpowiedzieć do dowolnego obiektu, który zdarzenie spowodował, na przykład wywołując metodę w tym obiekcie. Drugi argument, zawierający dane, które źródło przekazuje, kiedy wywołuje program obsługi zdarzeń, jest tradycyjnie zwany e i jest typu System.EventArgs bądź też typu dziedziczącego po System.EventArgs. Delegaty wykorzystywane w zdarzeniach przestrzegają pewnych konwencji Poniżej znajduje się przykładowa deklaracja programu obsługi zdarzeń: public delegate void MyEventHandler(object sender, MyEventArgs e); W powyższym przykładzie typ MyEventArgs musi pochodzić od System.EventArgs i musi rozszerzać ten typ bazowy w taki sposób, by mógł on służyć do przekazywania danych zdarzenia. Dla zdarzeń, które nie generują żadnych specyficznych informacji, typem służącym C# 99 do danych przekazywanych do programu obsługi zdarzeń może być po prostu System.EventArgs (nawet jeśli żadne dane nie są przeka- zywane, konwencja dotycząca zdarzeń wymaga, by ten parametr nadal pojawiał się w wywołaniu). Ponieważ zdarzenia często nie mają żadnych specyficznych dla nich danych, przestrzeń nazw System obej- muje także wbudowany typ — EventHandler. Typ ten jest po prostu de- legatem z dwoma argumentami: obiektem wraz z System.EventArgs. Kiedy odpowiedni program obsługi zdarzeń (to znaczy delegat zgodny z opisanymi powyżej konwencjami) zostanie zadeklarowany, możliwe jest definiowanie zdarzenia z użyciem tego delegata. Poniżej znajduje się przykład takiej sytuacji: C# dostarcza słowo kluczowe event, które służy do deklaracji zdarzeń public event MyEventHandler MyEvent; Jak pokazuje powyższy przykład, deklaracja musi składać się ze słowa kluczowego event, natomiast typ musi być typem delegata. Znając już podstawy, najłatwiej będzie zrozumieć sposób działania zdarzeń dzięki przykładowi. Zaprezentowany poniżej listing zawiera trzy klasy: EventSource, definiującą zdarzenie, EventSink, otrzymującą i odpowiadającą na zdarzenie, oraz EventMain, tworzącą obiekty dwóch pierwszych klas, a następnie generującą zdarzenie. Poniżej znajduje się odpowiedni kod: public class EventSource { public event System.EventHandler EventX; public void RaiseEventX() { if (EventX != null) EventX(this, System.EventArgs.Empty); } } public class EventSink { public EventSink(EventSource es) { es.EventX += new System.EventHandler(ReceiveEvent); } public void ReceiveEvent(object sender, System.EventArgs e) 100 Języki .NET Zdarzenia są inicjalizowane jako null EventSink może zarejestrować się do zdarzenia za pomocą operatora += C# { System.Console.WriteLine( EventX wywołane ); } } public class EventMain { public static void Main() { EventSource source = new EventSource(); EventSink sink = new EventSink(source); source.RaiseEventX(); } } Zdarzenie wykorzystane w powyższym przykładzie, EventX, jest de- klarowane na początku klasy EventSource. Ponieważ zdarzenie to nie posiada żadnych powiązanych danych, deklaracja wykorzystuje stan- dardową klasę .NET Framework System.EventHandler w miejsce de- klarowania własnego programu obsługi zdarzeń. Po deklaracji nastę- puje metoda RaiseEventX. Zdarzenie, które nie ma zarejestrowanych żadnych programów obsługi zdarzeń, będzie miało wartość null, zatem po upewnieniu się, że EventX nie jest null — to znaczy, że w rzeczywistości jest co wywoływać — metoda ta wywołuje zdarzenie. (System.EventArgs.Empty wskazuje, że żadne dane nie są przekazy- wane wraz ze zdarzeniem). Ponieważ zdarzenie jest w rzeczywistości delegatem, naprawdę wywoływana jest dowolna metoda, na którą wskazuje ten delegat. I choć powyższy przykład tego nie pokazuje, delegat może wskazywać na wiele metod, zatem przywołanie zdarze- nia sprawi, że wykonane zostaną wszystkie metody zarejestrowane w delegacie. Druga klasa, EventSink, ilustruje jedno z podejść do rejestrowania się i przetwarzania zdarzenia. Konstruktor klasy, który jak wszystkie kon- struktory ma taką samą nazwę jak sama klasa i działa zawsze, gdy two- rzony jest obiekt klasy, oczekuje, że zostanie mu przekazany obiekt EventSource. Następnie rejestruje program obsługi zdarzeń dla EventX za pomocą operatora +=. W tym prostym przykładzie konstruktor EventSink rejestruje metodę ReceiveEvent. Metoda ReceiveEvent posiada standardowe argumenty wykorzystywane dla zdarzeń i kiedy jest wywołana, wypisuje prosty komunikat do konsoli. Choć powyższy przykład tego nie pokazuje, programy obsługi zdarzeń mogą również być wyrejestrowane za pomocą operatora -=. C# 101 Ostatnia klasa, EventMain, zawiera metodę Main z przykładu. Metoda ta najpierw tworzy obiekt EventSource, a następnie obiekt EventSink, prze- kazując mu właśnie utworzony obiekt EventSource. To zmusza kon- struktor EventSink do działania i zarejestrowania metody ReceiveEvent z EventX w EventSource. Wynikiem jest wywołanie ReceiveEvent i program wypisuje: EventX wywołane W interesie prostoty powyższy przykład nie przestrzega wszystkich konwencji związanych ze zdarzeniami. Nadal jednak przykład ten ilu- struje podstawy tego, w jaki sposób w C# i niektórych konwencjach .NET Framework ulepszono delegaty, dzięki czemu możliwa jest bez- pośrednia obsługa zdarzeń. Bezpośrednia obsługa zdarzeń sprawia, że łatwiej jest wykorzystywać ten paradygmat Typy generyczne Wyobraźmy sobie, że chcielibyśmy utworzyć klasę, która może działać z różnymi typami danych. Być może aplikacja powinna działać z in- formacjami w parach, przetwarzając dwie dane tego samego typu. Jednym podejściem do wykonania tego byłoby zdefiniowanie różnych klas dla każdego rodzaju pary: jednej klasy dla pary liczb całkowitych, kolejnej dla pary łańcuchów znaków i tak dalej. Bardziej ogólne po- dejście opierałoby się na utworzeniu klasy Pair, która przechowuje dwie wartości typu System.Object. Ponieważ każdy typ .NET dziedziczy po System.Object, obiekt tej klasy mógłby przechowywać liczby cał- kowite, łańcuchy znaków czy cokolwiek innego. Jednak System.Object może być wszystkim, zatem nic nie zapobiegłoby sytuacji, w której obiekt klasy Pair przechowywałby jedną liczbę całkowitą i jeden łań- cuch znaków zamiast pary wartości tego samego typu. Z tego i innych powodów bezpośrednia praca z typami System.Object nie jest szcze- gólnie atrakcyjnym rozwiązaniem. To, czego naprawdę nam potrzeba, to sposób utworzenia obiektów typu Pair, który pozwoliłby na określenie w momencie tworzenia, jakie dokładnie informacje będą zawarte w Pair, a następnie wymu- szałby tę specyfikację. By odpowiedzieć na to wyzwanie, C# w wersji 2.0 z Visual Studio 2005 posiada obsługę typów generycznych (ang. generic types), popularnie zwanych generics. Kiedy definiowany jest typ generyczny, jeden lub więcej typów, które wykorzystuje, zostaje nieokreślonych. Prawdziwe typy, które powinny być użyte, są określane dopiero wtedy, gdy tworzony jest obiekt typu generycznego. Typy te mogą być różne dla różnych obiektów tego samego typu generycznego. 102 Języki .NET Na przykład poniżej znajduje się prosta ilustracja definiowania i uży- wania klasy Pair: class Pair T { T element1, element2; public void SetPair(T first, T second) { element1 = first; element2 = second; } public T GetFirst() { return element1; } public T GetSecond() { return element2; } } class GenericsExample { static void Main() { Pair int i = new Pair int (); i.SetPair(42,48); System.Console.WriteLine( Para liczb całkowitych: {0} {1} , i.GetFirst(), i.GetSecond()); Pair string s = new Pair string (); s.SetPair( Carpe , Diem ); System.Console.WriteLine( Para łańcuchów znaków: {0} {1} , s.GetFirst(), s.GetSecond()); } } Definicja klasy Pair wykorzystuje T, który w pierwszym wystąpieniu został opakowany w nawiasy ostre, co reprezentuje typ informacji, jakie obiekt tego typu będzie zawierał. Pola i metody klasy działają z T tak samo, jakby był to dowolny inny typ, używając go w parametrach oraz zwracanych wartościach. Czym jednak tak naprawdę jest T — liczbą całkowitą, łańcuchem znaków czy jeszcze czymś innym — ustala się dopiero w momencie, gdy deklarowany jest obiekt Pair. C# 103 Co nowego w C# 2.0? Typy generyczne to prawdopodobnie najważniejszy dodatek w wersji 2.0, jednak warto także wspomnieć o kilku innych nowych aspektach tego języka. Wśród nowości znaj- dują się poniższe cechy: (cid:132) Typy częściowe (ang. partial types) — dzięki wykorzystaniu nowego terminu partial definicja klasy, interfejsu czy struktury może teraz rozciągać się na dwa lub więcej plików źródłowych. Często spotykanym przykładem jest sytuacja, w której narzędzie takie jak Visual Studio 2005 generuje kod, do którego programista dodaje ciąg dalszy. Posiadając typy częściowe, programista nie musi bezpośrednio modyfikować kodu utworzonego przez narzędzie. Zamiast tego narzędzie oraz programista mogą utworzyć typy częściowe, które zostaną ze sobą połączone w końcową definicję. Ważnym przykładem zastosowania klas częściowych jest ASP.NET, opisany w rozdziale 5. (cid:132) Typy dopuszczające wartość null (ang. nullable types) — czasami przydatna jest możliwość ustawienia wartości na stan niezdefiniowany, często określany jako null. Najczęściej spotyka się tę sytuację w pracy z relacyjnymi bazami danych, gdzie null bywa poprawną wartością. W celu zaimplementowania tego pomysłu C# w wersji 2.0 pozwala na zadeklarowanie obiektu dowolnego typu bezpośredniego ze znakiem zapytania następującym po nazwie, jak w poniższym przykładzie: int? x; Zmienna zadeklarowana w ten sposób może przyjmować dowolną ze zwykłych wartości. Jednak w przeciwieństwie do zwykłej zmiennej typu int, może także zostać ustawiona na null w sposób jawny. (cid:132) Metody anonimowe (ang. anonymous methods) — jednym ze sposobów przekazania kodu jako parametru jest jawne zadeklarowanie tego kodu wewnątrz delegata. W C# 2.0 możliwe jest także przekazanie kodu bezpośrednio jako parametru — nie istnieje wymaganie, że kod musi być opakowany w osobno zadeklarowanym delegacie. W rezultacie przekazany kod zachowuje się jak metoda, jednak ponieważ nie ma nazwy, określany jest mianem metody anonimowej. Jak pokazuje przykład metody Main, tworzenie obiektu typu generycz- nego wymaga dokładnego określenia, jaki typ powinien być użyty dla T. Tutaj pierwsza para Pair będzie zawierała dwie liczby całkowite, zatem przy jej tworzeniu dostarczany jest typ int. Ten obiekt Pair jest następ- nie ustawiany tak, by zawierał dwie liczby całkowite, a jego zawartość 104 Języki .NET Struktury sterujące w C# są typowe dla współczesnego języka wysokiego poziomu jest wypisywana. Jednak drugi obiekt Pair będzie zawierał dwa łańcu- chy znaków, a zatem przy jego tworzeniu dostarczany jest typ string. Tym razem obiekt Pair jest ustawiony tak, by zawierał dwa łańcuchy znaków, które są również wypisywane. Wynikiem wykonania powyż- szego przykładowego kodu jest: Para liczb całkowitych: 42 48 Para łańcuchów znaków: Carpe Diem Typy generyczne mogą być używane z klasami, strukturami, interfej- sami, delegatami (i tym samym — zdarzeniami) oraz metodami, choć najczęściej pojawiają się w przypadku klas. Nie są odpowiednie dla każdej aplikacji, jednak przy niektórych rodzajach problemów typy generyczne mogą pomóc w stworzeniu właściwego rozwiązania. Struktury sterujące w C# C# dostarcza tradycyjny zbiór struktur sterujących dla współczesnego języka. Wśród najczęściej używanych struktur sterujących znajduje się instrukcja if, która wygląda następująco: if (x y) p = true; else p = false; Warto zwrócić uwagę na fakt, iż warunek dla if musi być wartością typu bool. W przeciwieństwie do języków C oraz C++, warunek nie może być liczbą całkowitą. C# posiada również instrukcję switch. Poniżej znajduje się przykład jej wykorzystania: switch (x) { case 1: y = 100; break; case 2: y = 200; break; default: y = 300; break; } C# 105 C# zawiera pętle while, do/while, for oraz foreach W zależności od wartości x, y będzie ustawiony na 100, 200 lub 300. Instrukcja break sprawia, że sterowanie przechodzi do instrukcji na- stępującej po kodzie switch. W przeciwieństwie do C i C++, takie (lub podobne) instrukcje są w C# obowiązkowe, nawet dla przypadku domyślnego. Pominięcie ich spowoduje błąd kompilatora. C# zawiera także różne rodzaje pętli. W pętli while warunek musi być obliczany dla bool, a nie liczby całkowitej — jest to kolejna cecha odróżniająca C# od języków C i C++. Istnieje także kombinacja do/while, która umieszcza test na końcu, a nie na początku, oraz pętla for, zilustrowana wcześniej przykładem. Wreszcie, C# zawiera także instrukcję foreach, która pozwala na iterację przez wszystkie elementy w danej wartości typu zbiorowego (ang. collection type). Istnieją różne sposoby kwalifikowania typów do typów zbiorowych, z czego najprost- szym z nich jest implementowanie standardowego interfejsu System. IEnumerable. Popularnym przykładem typu zbiorowego jest tablica, stąd jednym z zastosowań pętli foreach jest badanie lub przetwarzanie każdego elementu tablicy. C# zawiera także instrukcję goto, powodującą przejście do określo- nego i oznaczonego punktu w programie, oraz instrukcję continue, rozpoczynającą nową iterację poprzez natychmiastowy powrót do góry dowolnej pętli, w której jest umieszczona. Ogólnie rzecz biorąc, struktury sterujące w tym relatywnie nowym języku nie są niczym nowym i większość z nich będzie wyglądała znajomo dla każdego, kto zna inny język wysokiego poziomu. Inne cechy C# Podstawy języka programowania leżą w jego typach i strukturach ste- rujących. Jednak w C# istnieje o wiele więcej interesujących możli- wości — zbyt wiele, by przedstawić je szczegółowo w tak krótkim opisie. Niniejszy podrozdział prezentuje krótki przegląd niektórych bardziej interesujących dodatkowych aspektów tego języka. Praca z przestrzeniami nazw Ponieważ bazowe biblioteki klas są tak podstawowe, przestrzenie nazw są kluczową częścią programowania w .NET Framework. Jednym ze sposobów wywołania metody z bibliotek klas jest podanie jej pełnej nazwy kwalifikowanej. W zaprezentowanym wcześniej przykładzie metoda WriteLine została wywołana za pomocą następującego kodu: Instrukcja using w C# ułatwia odniesienia do zawartości przestrzeni nazw System.Console.WriteLine(...); 106 Języki .NET Wyjątki pozwalają na spójny sposób radzenia sobie z błędami we wszystkich językach opartych na CLR By uniknąć konieczności wprowadzania powtarzających się fragmentów kodu, C# dostarcza dyrektywę using. Pozwala to na odnoszenie się do zawartości przestrzeni nazw za pomocą krótszych nazw. Na przykład często rozpoczyna się każdy program w C# następującą linią: using System; Gdyby w przywołanym wyżej przykładzie zastosowano powyższy zapis, metoda WriteLine mogłaby zostać wywołana za pomocą skróconego zapisu: Console.WriteLine(...); Program może także zawierać kilka dyrektyw using, o ile jest to koniecz- ne; przykłady takich sytuacji zostaną pokazane w dalszej części książki. Dzięki użyciu słowa kluczowego namespace możliwe jest także zdefi- niowanie własnych przestrzeni nazw bezpośrednio w C#. Każda prze- strzeń nazw zawiera jeden lub więcej typów bądź też nawet inne przestrzenie nazw. Do typów z takiej przestrzeni nazw można odnieść się albo za pomocą pełnych, kwalifikowanych nazw, albo poprzez od- powiednie dyrektywy using, w taki sam sposób, w jaki odbywa się to w przypadku zewnętrznie zdefiniowanych przestrzeni nazw. Obsługa wyjątków Błędy są nieodłączną częścią życia programisty. W .NET Framework błędy pojawiające się w czasie uruchomienia dzięki wyjątkom obsługi- wane są w spójny sposób. Tak jak w innych przypadkach, C# dostarcza składni do pracy z wyjątkami, jednak podstawowe mechanizmy są osadzone w samym CLR. To pozwala nie tylko zapewnić spójne po- dejście do obsługi błędów dla wszystkich programistów C#, ale oznacza także, że wszystkie języki oparte na CLR będą traktowały ten potencjal- nie zawiły obszar w ten sam sposób. Błędy mogą nawet być przeka- zywane przez granice między językami, o ile języki te zbudowane są na bazie CLR. Wyjątek może być zgłoszony, kiedy wystąpi błąd Wyjątek jest obiektem, który reprezentuje jakieś niezwykłe zdarzenie, na przykład błąd. Platforma .NET Framework definiuje duży zbiór wy- jątków; możliwe jest również tworzenie własnych. Wyjątek jest zgłaszany automatycznie w czasie uruchomienia, gdy pojawia się błąd. Co się będzie na przykład działo w poniższym fragmencie kodu: x = y/z; C# 107 Wyjątki mogą być obsługiwane za pomocą bloków try/catch jeśli z jest równe zero? CLR zgłosi wówczas wyjątek System.DivideBy- ZeroException. Jeśli nie używa się żadnej obsługi wyjątków, program zostanie zakończony. C# umożliwia jednak przechwytywanie wyjątków za pomocą bloków try/catch. Powyższy kod można zmienić tak, by wyglądał następująco: try { x = y/z; } catch { System.Console.WriteLine( Wyjątek przechwycony ); } Kod wewnątrz nawiasów klamrowych instrukcji try będzie teraz mo- nitorowany na okoliczność wystąpienia wyjątków. Jeśli żaden wyjątek nie wystąpi, instrukcja catch zostanie pominięta, a program będzie kontynuowany. Jeśli zostanie zgłoszony wyjątek, wykonany zostanie kod z instrukcji catch, co w tym przypadku oznacza wypisanie ostrze- żenia, natomiast wykonanie będzie kontynuowane wraz z kolejną instrukcją, następującą po catch. Możliwe jest także posiadanie różnych instrukcji catch dla różnych wyjątków i dokładne sprawdzenie, który wyjątek wystąpił. Poniżej znaj- duje się kolejny przykład: Różne wyjątki mogą być obsługiwane w różny sposób try { x = y/z; } catch (System.DivideByZeroException) { System.Console.WriteLine( z jest zerem ); } catch (System.Exception e) { System.Console.WriteLine( Wyjątek: {0} , e.Message); } W powyższym przypadku, gdy nie wystąpi żaden wyjątek, do x zosta- nie przypisana wartość y podzielonego przez z, a kod znajdujący się w obu instrukcjach catch zostanie pominięty. Jeśli jednak z będzie zerem, wykonana zostanie pierwsza instrukcja catch, która wydrukuje 108 Języki .NET Możliwe jest definiowanie własnych wyjątków Program w C# może zawierać atrybuty odpowiedni komunikat. Wykonanie pominie wtedy kolejną instrukcję catch i przejdzie do kodu, który następuje po bloku try/catch. Jeśli wystąpi jakikolwiek inny wyjątek, wykonana zostanie druga instrukcja catch. Instrukcja ta deklaruje obiekt e typu System.Exception, a na- stępnie odwołuje się do właściwości Message tego obiektu w celu uzy- skania możliwego do wydrukowania łańcucha znaków, wskazującego, jaki wyjątek wystąpił. Skoro języki oparte na CLR, takie jak C#, w spójny sposób wykorzy- stują wyjątki do radzenia sobie z błędami, dlaczego nie zdefiniować własnych wyjątków do obsługi błędów? Można to uczynić dzięki zde- finiowaniu klasy dziedziczącej po System.Exception, a następnie wy- korzystaniu instrukcji throw w celu zgłoszenia własnego wyjątku. Takie wyjątki mogą być przechwytywane w blokach try/catch, tak samo jak wyjątki zdefiniowane przez system. Choć nie jest to tutaj pokazane, możliwe jest także zakończenie bloku try/catch instrukcją finally. Kod w tej instrukcji zostaje wykonany bez względu na wystąpienie wyjątku. Opcja ta jest przydatna, gdy potrzebny jest jakiś rodzaj końcowego uporządkowania bez względu na to, co się dzieje. Używanie atrybutów Po skompilowaniu każdy typ C# posiada powiązane z nim metadane, przechowywane w tym samym pliku. Większość metadanych opisuje sam typ. Jednak, jak pokazano to w poprzednim rozdziale, metadane mogą także zawierać atrybuty określane dla tego typu. Biorąc pod uwagę fakt, iż CLR zapewnia sposób przechowywania atrybutów, C# musi posiadać jakąś metodę definiowania atrybutów oraz ich wartości. Jak opisano to w dalszej części książki, atrybuty są szeroko wykorzy- stywane przez bibliotekę klas .NET Framework. Mogą być stosowane do klas, interfejsów, struktur, metod, pól, parametrów i innych. Możliwe jest nawet określenie atrybutów, które będą stosowane do całego pakietu. Załóżmy na przykład, że zaprezentowana wcześniej metoda Facto- rial została zadeklarowana wraz z odnoszącym się do niej atrybutem WebMethod. Zakładając, że zastosuje się odpowiednie dyrektywy using w celu zidentyfikowania właściwej przestrzeni nazw dla tego atrybutu, deklaracja w C# mogłaby wyglądać następująco: [WebMethod] public int Factorial(int f) {...} Atrybut ten jest wykorzystywany przez ASP.NET — część biblioteki klas .NET Framework — do wskazania, że metoda ta powinna być udostępniona jako usługa sieciowa możliwa do wywołania przez SOAP (więcej na temat wykorzystywania tego atrybutu znajduje się w roz- dziale 5.). Podobnie, załączenie atrybutu: [assembly:AssemblyCompanyAttribute( QwickBank )] w pliku C# ustawi wartość atrybutu używanego w całym pakiecie, przechowywanego w jego manifeście i zawierającego nazwę firmy, która utworzyła pakiet. Przykład ten pokazuje także sposób użycia parametrów w atrybutach, pozwalający użytkownikowi na określenie konkretnych wartości atrybutu. Programiści mogą także definiować swoje własne atrybuty. Być może przyda się na przykład zdefiniowanie atrybutu, który będzie mógł być wykorzystywany do identyfikacji daty, kiedy dany typ C# był modyfi- kowany. By to uczynić, można zdefiniować klasę, która dziedziczy po System.Attribute, a następnie zdefiniować informacje, które klasa ma zawierać, takie jak data. Nowy atrybut można następnie zastosować do typów w programie i tym samym automatycznie otrzymać odpo- wiednie informacje w metadanych tych typów. Po utworzeniu własnych atrybutów można je odczytywać za pomocą metody GetCustomAttri- butes, zdefiniowanej w klasie Attribute, części przestrzeni nazw System.Reflection w bibliotece klas .NET Framework. Jednak bez względu na to, czy atrybuty są standardowe, czy też własne, są one często wykorzystywaną cechą oprogramowania opartego na CLR. Pisanie niebezpiecznego kodu C# zazwyczaj polega na CLR w kwestii zarządzania pamięcią. Kiedy na przykład obiekt typu referencyjnego nie jest więcej wykorzystywany, czyszczenie pamięci z CLR zwolni pamięć zajmowaną przez ten obiekt. Jak opisano w rozdziale 2., proces czyszczenia pamięci ponownie usta- wia aktualnie używane elementy na zarządzanej stercie, zagęszczając je, by zyskać więcej wolnego miejsca. Co by się stało, gdyby wykorzystać w tym środowisku tradycyjne wskaźniki C/C++? Wskaźnik zawiera bezpośredni adres w pamięci, zatem wskaźnik do zarządzanej sterty musiałby się odnosić do kon- kretnej lokalizacji w pamięci sterty. Kiedy proces czyszczenia pamięci poprzestawia zawartość sterty, by zwolnić więcej miejsca, to, na co wskazywał wskaźnik, może się zmienić. Mieszanie wskaźników i czysz- czenia pamięci na ślepo jest najprostszą receptą na katastrofę. C# 109 Możliwe jest również zdefiniowanie własnych atrybutów Programiści C# zazwyczaj polegają na czyszczeniu pamięci przez CLR Wskaźniki i czyszczenie pamięci nie lubią się ze sobą 110 Języki .NET C# pozwala na tworzenie niebezpiecznego kodu, który wykorzystuje wskaźniki Jednak takie mieszanie jest czasem konieczne. Przypuśćmy na przykład, że należy wywołać istniejący kod, który nie jest oparty na CLR, taki jak na przykład kod systemu operacyjnego, a wywołanie zawiera strukturę z osadzonymi wskaźnikami. Lub być może jakaś część aplikacji jest tak istotna dla jej działania, że nie można polegać na tym, by to proces czysz- czenia pamięci zarządzał pamięcią. W takich sytuacjach C# umożliwia wykorzystywanie wskaźników w tak zwanym kodzie niebezpiecznym (ang. unsafe code). Kod niebezpieczny może wykorzystywać wskaźniki, ze wszystkimi za- letami i wadami tego rozwiązania. By taką „niebezpieczną” działalność jak najlepiej zabezpieczyć, C# wymaga jednak, by cały kod ją wyko- nujący został oznaczony słowem kluczowym unsafe. Wewnątrz nie- bezpiecznej metody można użyć instrukcji fixed w celu zablokowania jednej lub większej liczby wartości typu referencyjnego w odpowiednim miejscu na zarządzanej stercie (czasami jest to nazywane przypinaniem wartości — ang. pinning a value). Poniżej zamieszczono prosty przykład takiego działania: class Risky { unsafe public void PrintChars() { char[] charList = new char[2]; charList[0] = A ; charList[1] = B ; System.Console.WriteLine( {0} {1} , charList[0], charList[1]); fixed (char* f = charList) { charList[0] = *(f+1); } System.Console.WriteLine( {0} {1} , charList[0], charList[1]); } } class DisplayValues { static void Main() { Risky r = new Risky(); r.PrintChars(); } } C# 111 Kod niebezpieczny ma swoje ograniczenia Metoda PrintChars w klasie Risky jest oznaczona słowem kluczowym unsafe. Metoda ta deklaruje niewielką tablicę ze znakami o nazwie charList, a następnie ustawia dwa elementy tej tablicy jako odpo- wiednio A i B. Pierwsze wywołanie WriteLine zwraca: A B co jest dokładnie tym, czego należało oczekiwać. Instrukcja fixed de- klaruje następnie wskaźnik znaku f i inicjalizuje go w taki sposób, by zawierał on adres tablicy charList. Wewnątrz ciała instrukcji fixed do pierwszego elementu tablicy przypisuje się wartość z adresu f+1 (gwiazdka przed wyrażeniem oznacza „zwróć to, co znajduje się pod tym adresem”). Kiedy WriteLine jest ponownie wywołany, zwraca: B B Wartość znajdująca się o jedno miejsce dalej od początku tablicy, czyli znak B, został przypisany do pierwszej pozycji w tablicy. Powyższy przykład nie wykonuje oczywiście niczego przydatnego. Jego celem jest jedynie pokazanie, że C# pozwala na deklarowanie wskaźni- ków, wykonywanie arytmetyki wskaźników i inne, pod warunkiem że te instrukcje znajdują się wewnątrz obszaru wyraźnie oznaczonego jako unsafe. Twórcy języka naprawdę chcą, by autor niebezpiecznego ko- du wykonywał te czynności w sposób świadomy, dlatego kompilowanie niebezpiecznego kodu wymaga jawnego ustawienia opcji unsafe dla kompilatora C#. Kod niebezpieczny nie może też być weryfikowany pod kątem bezpieczeństwa typologicznego, co oznacza, że nie mogą być wykorzystywane wbudowane w CLR możliwości dotyczące bezpieczeń- stwa dostępu do kodu, opisane w rozdziale 2. Kod niebezpieczny może być uruchamiany jedynie w środowisku o pełnym zaufaniu, co czyni go generalnie niedostępnym dla oprogramowania pobieranego z Internetu. Nadal jednak występują sytuacje, w których kod niebezpieczny jest właściwym rozwiązaniem trudnego problemu. Dyrektywy preprocesora W przeciwieństwie do języków C i C++, C# nie posiada preprocesora. Zamiast tego kompilator ma wbudowaną obsługę najbardziej przydat- nych cech preprocesora. Na przykład dyrektywy preprocesora w C# obejmują #define — termin znany programistom C++. Dyrektywa ta nie może jednak być wykorzystywana do definiowania dowolnego za- stępującego łańcucha znaków dla słowa — nie można definiować makr. 112 Języki .NET (cid:132) Perspektywa: czy C# jest tylko kopią Javy? C# z pewnością jest bardzo podobny do języka Java. Biorąc pod uwagę dodatkowe podobieństwo pomiędzy CLR a wirtualną maszyną Javy, trudno uwierzyć, że Microsoft nie zainspirował się sukcesem Javy. Łącząc składnię w stylu języka C z obiektami w bardziej przystępny sposób niż w języku C++, twórcy Javy znaleźli złoty środek dla dużej grupy programistów. Przed nadejściem .NET widziałem wiele projektów, w których zdecydowano się na wybór środowiska Javy, ponieważ ani Visual Basic 6, ani C++ nie uznano za język nadający się dla tworzenia oprogramowania komercyjnego na wielką skalę. Nadejście C# i wersji VB opartej na .NET zdecydowanie polepszyło pozycję technologii Microsoftu przeciwko obozowi Javy. Jakość języka programowania nie jest już problemem. Jednak ponownie pojawia się pytania: czy C# nie jest jak Java? W wielu aspektach odpowiedź brzmi: tak. Podstawowa semantyka CLR jest bardzo podobna do Javy. Głębokie zorientowanie obiektowe, bezpośrednia obsługa interfejsów, pozwalanie na wielokrotne dziedziczenie interfejsów, ale pojedyncze dziedziczenie implementacji — wszystkie te cechy są podobne do Javy. C# posiada jednak także możliwości, których nie było w Javie. Na przykład wbudowana obsługa właściwości, oparta na obsłudze właściwości z CLR, odzwierciedla wpływ VB na twórców C#. Atrybuty, kolejna cecha oparta na CLR, zapewniają poziom elastyczności niedostępny w oryginalnej Javie, podobnie jak możliwość pisania niebezpiecznego kodu. C# jest wyrażeniem semantyki CLR w składni wywodzącej się z języka C. Ponieważ semantyka ta jest tak podobna do Javy, C# również jest bardzo podobny do Javy. Jednak nie jest to ten sam język. Czy C# jest lepszym językiem niż Java? Nie da się odpowiedzieć na to pytanie w spo- sób obiektywny, a nawet gdyby się dało — nie miałoby to znaczenia. Wybranie plat- formy programistycznej wyłącznie w oparciu o język programowania jest jak kupowa- nie samochodu, dlatego że podoba się nam jego radio. Można tak zrobić, jednak o wiele lepszym wyjściem będzie rozważenie całego pakietu. Gdyby Sun pozwolił Microsoftowi na niewielkie zmodyfikowanie Javy, C# mógłby dzisiaj nie istnieć. Ze zrozumiałych względów Sun oparł się zakusom Microsoftu, by dopasować
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Zrozumieć platformę .NET. Wydanie II
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: