Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00365 008367 11063071 na godz. na dobę w sumie
Visual C# 2005. Zapiski programisty - książka
Visual C# 2005. Zapiski programisty - książka
Autor: Liczba stron: 280
Wydawca: Helion Język publikacji: polski
ISBN: 83-246-0249-6 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> .net - programowanie
Porównaj ceny (książka, ebook, audiobook).

Odkryj nowe możliwości platformy .NET 2005

Visual C# 2005 to najnowsza wersja języka programowania uważanego przez wielu programistów za najlepszy język służący do tworzenia aplikacji dla platformy .NET. W połączeniu z nową biblioteką klas .NET i nowymi możliwościami środowiska Visual Studio 2005 druga edycja języka C# stała się jeszcze doskonalsza. Pisanie programów wymaga znacznie mniejszych nakładów pracy, a nowe elementy umożliwiają realizację większej ilości zadań programistycznych.

Aby poznać nowe możliwości języka C#, sięgnij po książkę 'Visual C# 2005. Zapiski programisty'. W tej wzorowanej na zeszytach laboratoryjnych publikacji znajdziesz notatki programistów, którzy jako pierwsi zetknęli się z tą technologią. Nie ma w niej teoretycznych wywodów, diagramów i niepotrzebnych informacji. Wykonując 50 ćwiczeń demonstrujących poszczególne aspekty tworzenia aplikacji, poznasz prostotę stosowania nowych elementów i mechanizmów i przekonasz się, jak wiele udogodnień wnosi do pracy programisty Visual C# 2005.

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 Visual C# 2005. Zapiski programisty Autor: Jesse Liberty T³umaczenie: Przemys³aw Szeremiota ISBN: 83-246-0249-6 Tytu³ orygina³u: Visual C# 2005: A Developers Notebook Format: B5, stron: 280 Przyk³ady na ftp: 1162 kB DODAJ DO KOSZYKA DODAJ DO KOSZYKA Odkryj nowe mo¿liwoœci platformy .NET 2005 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 Visual C# 2005 to najnowsza wersja jêzyka programowania uwa¿anego przez wielu programistów za najlepszy jêzyk s³u¿¹cy do tworzenia aplikacji dla platformy .NET. W po³¹czeniu z now¹ bibliotek¹ klas .NET i nowymi mo¿liwoœciami œrodowiska Visual Studio 2005 druga edycja jêzyka C# sta³a siê jeszcze doskonalsza. Pisanie programów wymaga znacznie mniejszych nak³adów pracy, a nowe elementy umo¿liwiaj¹ realizacjê wiêkszej iloœci zadañ programistycznych. Aby poznaæ nowe mo¿liwoœci jêzyka C#, siêgnij po ksi¹¿kê „Visual C# 2005. Zapiski programisty”. W tej wzorowanej na zeszytach laboratoryjnych publikacji znajdziesz notatki programistów, którzy jako pierwsi zetknêli siê z t¹ technologi¹. Nie ma w niej teoretycznych wywodów, diagramów i niepotrzebnych informacji. Wykonuj¹c 50 æwiczeñ demonstruj¹cych poszczególne aspekty tworzenia aplikacji, poznasz prostotê stosowania nowych elementów i mechanizmów i przekonasz siê, jak wiele udogodnieñ wnosi do pracy programisty Visual C# 2005. (cid:129) Stosowanie klas generycznych (cid:129) Korzystanie z metod anonimowych (cid:129) Refaktoryzacja kodu Ÿród³owego (cid:129) Tworzenie interfejsów u¿ytkownika i formularzy (cid:129) Mechanizmy szybkiej instalacji aplikacji (cid:129) Zabezpieczanie aplikacji WWW (cid:129) Personalizacja stron WWW z u¿yciem motywów i szablonów (cid:129) Po³¹czenia z baz¹ danych Wydawnictwo Helion ul. Chopina 6 44-100 Gliwice tel. (32)230-98-63 e-mail: helion@helion.pl Spis treści Seria „Zapiski programisty” ..................................................................... 7 Wprowadzenie ..................................................................................... 13 Rozdział 1. C# 2.0 ............................................................................... 21 Tworzenie typowanych list za pomocą kolekcji generycznych ............22 Własna kolekcja generyczna ..............................................................28 Implementacja interfejsów kolekcji .....................................................33 Stosowanie iteratorów generycznych ..................................................41 Implementacja GetEnumerator dla złożonych struktur danych ...........45 Upraszczanie kodu — metody anonimowe .........................................52 Ukrywanie kodu — typy częściowe ....................................................55 Tworzenie klas statycznych ................................................................58 Wyrażanie wartości pustych typami nullable .....................................60 Odwołania do obiektów z globalnej przestrzeni nazw ..........................65 Ograniczanie dostępu do właściwości .................................................68 Elastyczność delegacji z kowariancją i kontrawariancją .....................70 Rozdział 2. Visual Studio 2005 ............................................................ 75 Konfigurowanie i utrwalanie konfiguracji środowiska programistycznego ........................................................76 Konfigurowanie aplikacji .....................................................................81 Przysposabianie edytora kodu .............................................................84 3 Refaktoryzacja kodu ............................................................................90 Gotowce ...............................................................................................97 Inspekcja obiektów podczas debugowania ...........................................99 Wizualizacja danych XML ................................................................100 Diagnozowanie wyjątków ..................................................................103 Rozdział 3. Aplikacje okienkowe .......................................................107 Stosowanie pasków narzędzi .............................................................108 Narzucanie formatu danych wejściowych .........................................113 Pola tekstowe z automatycznym uzupełnianiem ...............................118 Odtwarzanie dźwięków ......................................................................121 Okna dzielone ....................................................................................123 Dynamiczne tworzenie formularzy ....................................................126 Tworzenie zadań asynchronicznych .................................................130 Okno na świat ....................................................................................137 Instalacja jednym kliknięciem — ClickOnce .....................................141 Rozdział 4. Aplikacje WWW ................................................................147 Tworzenie aplikacji WWW bez IIS ....................................................148 Zabezpieczenie aplikacji WWW bez jednego wiersza kodu ...............154 Role w ASP.NET ...............................................................................167 Personalizacja stron WWW ..............................................................183 Personalizacja z użyciem typów złożonych .......................................192 Personalizacja dla użytkowników anonimowych ...............................197 Personalizacja z użyciem motywów ..................................................203 Ujednolicanie wyglądu aplikacji — szablony stron ...........................214 Rozdział 5. Praca z danymi ................................................................223 Wiązanie aplikacji z danymi .............................................................224 Formularze ........................................................................................237 Widok typu ogół-szczegół ..................................................................244 Pozyskiwanie statystyk bazy danych ................................................248 Aktualizacje wsadowe a wydajność ..................................................251 4 Spis treści Źródło danych w dokumencie XML ...................................................255 Manipulowanie dokumentami XML — XPathDocument ..................260 Zawężanie elementów dokumentu XML przy użyciu interfejsu XPath .............................................................................................265 Skorowidz ......................................................................................... 269 Spis treści 5 ROZDZIAŁ 1. C# 2.0 W tym rozdziale: (cid:22) Tworzenie typowanych list za pomocą kolekcji generycznych (cid:22) Własne kolekcje generyczne (cid:22) Implementacja interfejsów kolekcji (cid:22) Stosowanie iteratorów generycznych (cid:22) Implementacja GetEnumerator dla złożonych struktur danych (cid:22) Upraszczanie kodu — metody anonimowe (cid:22) Ukrywanie kodu — typy częściowe (cid:22) Tworzenie klas statycznych (cid:22) Wyrażanie wartości pustych typami nullable (cid:22) Odwołania do obiektów z globalnej przestrzeni nazw (cid:22) Ograniczanie dostępu do właściwości (cid:22) Elastyczność delegacji z kowariancją i kontrawariancją Pierwszy rozdział będzie prezentował nowe cechy języka C# w wersji 2.0 — mowa będzie o typach generycznych, iteratorach, metodach ano- nimowych, typach częściowych, klasach statycznych, typach wyróżniają- cych wartości puste („nullable”) oraz ograniczaniu dostępu do właściwo- ści; omówienie obejmie też kowariancję i kontrawariancję delegacji. 21 Najbardziej chyba wyczekiwaną cechą języka C# w wersji 2.0 są jed- nak typy generyczne, dające możliwość szybkiego tworzenia kolekcji. I od nich właśnie zaczniemy. Tworzenie typowanych list za pomocą kolekcji generycznych Bezpieczeństwo typowania to klucz do tworzenia kodu prostego w kon- serwacji. Język ze ścisłą kontrolą typów potrafi (skutecznie) wyszukiwać błędy typowania w czasie kompilacji, a nie w czasie wykonania (kiedy program zostanie już oddany do użytku klientom!). Największą słabością C# 1.x była nieobecność typów generycznych, które pozwalałyby na de- klarowanie kolekcji uogólnionych (choćby stosów bądź list) mogących prze- chowywać elementy dowolnego typu przy zachowaniu możliwości kontro- lowania typu w czasie kompilacji. W wersji 1.x niemal wszystkie kolekcje były deklarowane jako kolekcje przechowujące obiekty typu System.Object; ponieważ zaś wszelkie klasy wywodzą się z System.Object, kolekcje takie mogły przechowywać ele- menty dosłownie dowolnych typów. Elastyczność ta osiągana była kosztem bezpieczeństwa typowania. Załóżmy, że w C# 1.x zajdzie potrzeba utworzenia listy obiektów Employee. W tym celu można by wykorzystać klasę ArrayList pozwalającą na prze- chowywanie obiektów typu System.Object. Dodanie nowych obiektów Em- ployee do takiej kolekcji nie stanowi żadnego problemu — przecież typ Employee jest pochodną System.Object. Problem pojawia się dopiero przy próbie wyłuskania obiektu Employee z kolekcji ArrayList — operacja taka zwraca referencję do typu Object, którą trzeba dopiero rzutować na typ Employee: Employee theEmployee = (Employee) myArrayList[1]; Sęk nie w tym, że trzeba dokonywać rzutowania, ale w tym, że w kolekcji ArrayList można równie łatwo umieścić obiekt dowolnego innego typu, na przykład ciąg znaków. I wszystko będzie w porządku dopóty, dopóki program będzie się do niego odwoływał jak do ciągu. Ale jeśli kolekcja cią- gów zostanie omyłkowo przekazana do metody oczekującej przekazania 22 Rozdział 1: C# 2.0 kolekcji obiektów Employee, próba rzutowania obiektu String na typ Em- ployee sprowokuje wyjątek. Wreszcie kolekcje platformy .Net 1.x utrudniały przechowywanie warto- ści typów prostych (ang. value type). Wartości typów prostych musiały być przed wstawieniem do kolekcji pakowane do obiektów, a po wyłuska- niu z kolekcji — rozpakowywane z obiektów. W .NET 2.0 wszystkie te problemy zostały wyeliminowane przez udo- stępnienie nowej biblioteki kolekcji generycznych, zagnieżdżonej w prze- strzeni nazw System.Collections.Generic. Otóż kolekcja generyczna to po prostu taka kolekcja, która pozwala na ujęcie w deklaracji typów ele- mentów przechowywanych. Dzięki temu kompilator znający deklarację będzie zezwalał na wstawianie do kolekcji jedynie obiektów właściwego typu. Kolekcje generyczne definiuje się z użyciem specjalnej składni; w na- wiasach ostrych wymienia się nazwę typu, który musi zostać zdefinio- wany przy deklarowaniu egzemplarza kolekcji. Nie trzeba już rzutować obiektów wyłuskiwanych z kolekcji generycznej, a sam kod korzystający z takich kolekcji jest bezpieczniejszy, bo podlega statycznemu (realizowanemu w czasie kompilacji) typowaniu; łatwiej pod- daje się konserwacji i prościej się go stosuje. Jak to zrobić? Aby oswoić się z nowymi typami generycznymi w .NET 2.0, najlepiej spró- bować samemu utworzyć typowaną klasę kolekcji (List) przechowującą elementy typu Employee („pracownik”). Ćwiczenie należy rozpocząć od uru- chomienia środowiska Visual Studio 2005, utworzenia nowej aplikacji kon- solowej języka C# i opatrzenia jej nazwą CreateATypeSafeList. Kod ge- nerowany w ramach szkieletu aplikacji przez kreator Visual Studio 2005 należy zastąpić kodem z listingu 1.1. WSKAZÓWKA Korzystanie z typów generycznych wymaga wciągnięcia do programu przestrzeni nazw System.Collections.Generic. W Visual Studio 2005 jest to domyślne zachowanie dla wszystkich tworzonych projektów. Kolekcje generyczne czynią kod bezpieczniejszym, upraszczają jego konserwację i stosowanie. Tworzenie typowanych list za pomocą kolekcji generycznych 23 Listing 1.1. Tworzenie typowanej listy using System; using System.Collections.Generic; namespace CreateATypeSafeList { // klasa obiektów przechowywanych na liście public class Employee { private int empID; // konstruktor public Employee(int empID) { this.empID = empID; } // przesłonięcie metody ToString, tak // aby wyświetlała identyfikator obiektu public override string ToString() { return empID.ToString(); } } // koniec klasy // Klasa testująca public class Program { // punkt wejściowy static void Main() { // Deklaracja listy typowanej (obiektów typu Employee) List Employee empList = new List Employee (); // Deklaracja drugiej listy typowanej (wartości całkowitych) List int intList = new List int (); // wypełnienie list for (int i = 0; i 5; i++) { empList.Add(new Employee(I + 100)); intList.Add(i * 5); // empList.Add(i * 5); // patrz punkt A co… } // wypisanie elementów listy wartości całkowitych foreach (int i in intList) { Console.Write( {0} , i.ToString()); } Console.Write( ); 24 Rozdział 1: C# 2.0 // wypisanie identyfikatorów obiektów Employee foreach (Employee employee in empList) { Console.Write( {0} , employee.ToString()); } Console.Write( ); } } } Wynik: 0 5 10 15 20 100 101 102 103 104 WSKAZÓWKA Kod źródłowy poszczególnych ćwiczeń można pobrać z serwerów wy- dawnictwa Helion, spod adresu ftp://ftp.helion.pl/przyklady/vc25za.zip. Kod publikowany jest w postaci spakowanego archiwum. Rozpakowanie archiwum zaowocuje utworzeniem szeregu katalogów odpowiadających poszczególnym rozdziałom, a w nich podkatalogów o nazwach zgod- nych z nazwami poszczególnych projektów. Na przykład kodu z listin- gu 1.1 należy szukać w katalogu r1CreateATypeSafeList. Jak to działa? Kod z listingu 1.1 utworzył dwie klasy: klasę Employee (pracownik) — kla- sę obiektów przechowywanych w kolekcji, oraz klasę Program tworzoną przez kreator Visual Studio 2005. Do tego w programie wykorzystana zo- stała klasa List z biblioteki klas .NET Framework Class Library. Klasa Employee zawiera pojedyncze prywatne pole (empID), konstruktor i metodę przesłaniającą metodę ToString i wyświetlającą ciąg zawiera- jący wartość pola empID. W klasie Program tworzony jest egzemplarz klasy List mający przecho- wywać obiekty klasy Employee. Typem empList jest więc „kolekcja List obiektów Employee”. Stąd deklaracja kolekcji: List Employee empList W definicji List T element T jest symbolem reprezentującym przyszły, właściwy typ listy. Tworzenie typowanych list za pomocą kolekcji generycznych 25 Deklaracja empList tworzy (jak zwykle) referencję obiektu tworzonego na stercie słowem kluczowym new. Słowo kluczowe new należy uzupełnić wy- wołaniem konstruktora, co wygląda następująco: new List Employee () Takie wywołanie tworzy na stercie egzemplarz „kolekcji List obiektów Employee”; cała instrukcja oznacza zaś utworzenie empList i przypisa- nie do niego referencji nowego obiektu sterty: List Employee empList = new List Employee (); WSKAZÓWKA Całość działa dokładnie tak, jak w instrukcji: Dog milo = new Dog(); tworzącej egzemplarz klasy Dog na stercie i przypisującej go do re- ferencji do typu Dog o nazwie milo. W kolejnej instrukcji następuje utworzenie drugiej kolekcji; tym razem jest to „kolekcja List wartości całkowitych”: List int intList = new List int (); Od tego momentu można rozpocząć wypełnianie listy wartości całkowi- tych wartościami całkowitymi, a listy elementów typu Employee obiektami klasy Employee. Po wypełnieniu list można w pętlach foreach przejrzeć listy, wyłuskać poszczególne elementy i wypisać ich wartości na konsoli: foreach (Employee employee in empList) { Console.Write( {0} , employee.ToString()); } A co… … kiedy do listy obiektów Employee dodana zostanie wartość całkowita? Cóż, trzeba spróbować. Wystarczy usunąć znacznik komentarza z pre- zentowanego poniżej wiersza z listingu 1.1 i spróbować ponownie skom- pilować program: empList.Add(i * 5); Kompilator powinien zgłosić parę błędów: 26 Rozdział 1: C# 2.0 Error 1 The best overloaded method match for System.Collections. Generic.List ListCollection.Employee .Add(ListCollection.Employee) has some invalid arguments Error 2 Argument 1 : cannot convert from int to ListCollection.Employee Komunikaty opisujące te dwa błędy pozwalają stwierdzić, że nie można dodawać elementu typu int do kolekcji obiektów typu Employee, bo pomię- dzy tymi typami nie da się przeprowadzić niejawnej konwersji, nie za- chodzi też relacja zawierania się jednego typu w drugim. Co ważniejsze, kolizję typów wykrywa się już na etapie kompilacji, a nie dopiero w czasie wykonania — a wiadomo, że błędy czasu wykonania mają tendencję do ujawniania się nie na stanowiskach testowych, a na biurkach klientów! … z innymi kolekcjami generycznymi; są jeszcze jakieś? Owszem, w platformie .NET 2.0 dostępne są też inne kolekcje typowane, jak choćby Stack (stos) czy Queue (kolejka); do tego dochodzi interfejs ICollection. Kolekcje te stosuje się tak samo, jak List T . Aby na przykład utworzyć stos obiektów typu Employee, należy w definicji klasy Stack zastąpić T (Stack T ) nazwą typu Employee: Stack Employee employeeStack = new Stack Employee (); Więcej informacji Kompletu informacji o klasach generycznych w .NET 2.0 należy szukać w pomocy MSDN, pod hasłem „Commonly Used Collection Types”; warto też zapoznać się z artykułem publikowanym w witrynie ONDotnet.com (O’Reilly) pod adresem http://www.ondotnet.com/pub/a/dotnet/2004/05/17/ liberty.html. WSKAZÓWKA Artykuły i publikacje, na które powołuję się w kolejnych ćwiczeniach, są wymienione na stronie WWW książki pod adresem http://www.Li- bertyAssociates.com (odnośnik Books, a następnie pozycja C# 2005: A Developer’s Notebook). W kolekcjach typowanych można umieszczać elementy typów pochodnych wobec typu deklarowanego. Kolekcja elementów typu Employee może więc przechowywać również elementy typu Manager, o ile Manager jest typem pochodnym Employee. Tworzenie typowanych list za pomocą kolekcji generycznych 27 Od czasu do czasu przyjdzie Ci zdefiniować własną klasę kolekcji generycznych. Następne ćwiczenie będzie ilustrowało sposób tworzenia własnej typo- wanej kolekcji, uzupełniającej zbiór kolekcji udostępnianych w bibliotece klas platformy .NET. Własna kolekcja generyczna Platforma .NET 2.0 udostępnia programistom szereg klas kolekcji gene- rycznych implementujących typowane listy, stosy, kolejki, słowniki i tym podobne. Tak bogaty zestaw zaspokaja zdecydowaną większość potrzeb programistów w tym zakresie. Załóżmy jednak, że stoimy w obliczu ko- nieczności utworzenia własnej klasy kolekcji generycznej, uwzględniającej wiedzę z dziedziny danego problemu czy przejawiającej specjalne cechy (na przykład optymalizującej rozmieszczenie elementów celem przyspiesze- nia odwołań). Na szczęście i sam język, i platforma udostępniają narzędzia i mechanizmy tworzenia własnych klas kolekcji generycznych. Jak to zrobić? Najprostszym sposobem powołania do życia własnej klasy kolekcji gene- rycznej jest utworzenie danej kolekcji (np. kolekcji wartości całkowitych) i potem zastąpienie nazwy typu int uogólnionym symbolem typu, na przykład T. Mianowicie: private int data; ma przyjąć postać: private T data; // T to generyczny parametr typowy Generyczny parametr typowy (tutaj T) jest ściśle definiowany dopiero w momencie tworzenia klasy kolekcji przez parametr typu występujący między nawiasami kątowymi ( ): public class Node T Tak definiuje się nowy typ — „węzeł Node typu T”; jeśli w definicji obiektu miejsce T zajmie int, dla środowiska wykonawczego obiekt ten będzie typu „węzeł Node typu int” (analogicznie tworzy się węzły dowolnego innego typu). 28 Rozdział 1: C# 2.0 WSKAZÓWKA Wielu programistów stosuje wygodny w zapisie symbol typu T, ale Microsoft zaleca stosowanie dłuższych, bardziej opisowych symboli (na przykład Node DocumentType ). Listing 1.2 prezentuje przykładowy kod tworzący listę węzłów typu T i na- stępnie powołujący do życia dwa egzemplarze takich list dla różnych wła- ściwych obiektów węzłów. Listing 1.2. Własna kolekcja generyczna using System; namespace GenericLinkedList { public class Pilgrim { private string name; public Pilgrim(string name) { this.name = name; } public override string ToString() { return this.name; } } public class Node T { // pola składowych private T data; private Node T next = null; // konstruktor public Node(T data) { this.data = data; } // właściwości public T Data { get { return this.data; } } public Node T Next { get { return this.next; } } // metody public void Append(Node T newNode) Własna kolekcja generyczna 29 { if (this.next == null) { this.next = newNode; } else { next.Append(newNode); } } public override string ToString() { string output = data.ToString(); if (next != null) { output += , + next.ToString(); } return output; } } // koniec klasy public class LinkedList T { // pola składowe private Node T headNode = null; // właściwości // indekser public T this[int index] { get { int ctr = 0; Node T node = headNode; while (node != null ctr = index) { if (ctr == index) { return node.Data; } else { node = node.Next; } ++ctr; } // koniec while throw new ArgumentOutOfRangeException(); 30 Rozdział 1: C# 2.0 } // koniec get } // koniec indeksera // konstruktor public LinkedList() { } // metody public void Add(T data) { if (headNode == null) { headNode = new Node T (data); } else { headNode.Append(new Node T (data)); } } public override string ToString() { if (this.headNode != null) { return this.headNode.ToString(); } else { return string.Empty; } } } class Program { static void Main(string[] args) { LinkedList int myLinkedList = new LinkedList int (); for (int i = 0; i 10; i++) { myLinkedList.Add(i); } Console.WriteLine( Liczby: + myLinkedList); LinkedList Pilgrim pilgrims = new LinkedList Pilgrim (); pilgrims.Add(new Pilgrim( Rycerz )); pilgrims.Add(new Pilgrim( Młynarz )); pilgrims.Add(new Pilgrim( Szeryf )); pilgrims.Add(new Pilgrim( Kucharz )); Console.WriteLine( Pielgrzymi: + pilgrims); Console.WriteLine( Czwarta liczba to + myLinkedList[3]); Własna kolekcja generyczna 31 Pilgrim d = pilgrims[1]; Console.WriteLine( Drugi pielgrzym to + d); } } } Wynik: Liczby: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 Pielgrzymi: Rycerz, Młynarz, Szeryf, Kucharz Czwarta liczba to 3 Drugi pielgrzym to Młynarz Jak to działa? Otóż właśnie powstała generyczna kolekcja — lista. Kolekcja podlegająca typowaniu — można deklarować jej egzemplarze dla dowolnych typów. Najłatwiej skonstruować listę tego rodzaju, bazując na implementacji zwy- kłej (nie generycznej) listy dla konkretnego typu danych. Ten konkretny przykład przy definiowaniu listy definiuje listę generyczną, której czoło (węzeł czołowy) jest inicjalizowany wartością pustą: public class LinkedList T { private Node T headNode = null; ... } Tworzenie kolekcji z typami generycznymi jest doprawdy bardzo proste. Bo cóż prostszego, niż zaimplementować kolekcję dla dowolnie wybranego typu, a następnie zastąpić nazwę typu parametrem typu generycznego T . Kiedy do listy dodawane są elementy, tworzony jest obiekt nowego wę- zła; w obliczu braku węzła czołowego ów nowy węzeł pełni rolę czoła listy; jeśli czoło listy jest już zajęte, nowy węzeł jest dołączany do listy metodą Append węzła czołowego. Przy dodawaniu nowego węzła do listy następuje przegląd węzłów skła- dających się na listę; szukany jest węzeł ostatni, czyli taki, który we wskaź- niku następnego węzła (pole next) przechowuje wartość pustą. Po jego odnalezieniu do pola next przypisywana jest referencja nowego węzła. Lista LinkedList została celowo zadeklarowana z identycznym parame- trem typu jak Node. Obie stosują w roli symbolu generycznego parametru typu tę samą literę (T), dzięki czemu kompilator „wie”, że typ podstawiany w miejsce T w definicji LinkedList ma również zastępować T w definicji Node. To w zasadzie oczywiste: przecież węzłami listy wartości całkowi- tych powinny być węzły wartości całkowitych. 32 Rozdział 1: C# 2.0 A co… … ze stosowaniem typów generycznych w innych strukturach? Czy to możliwe? Owszem, typy generyczne można stosować w strukturach, interfejsach, delegacjach, a nawet w metodach. Więcej informacji Szczegółowych informacji o tworzeniu własnych klas z udziałem typów generycznych należy szukać w pomocy MSDN pod hasłem „Topic: Gene- rics”, ewentualnie we wspomnianym już artykule z witryny ONDotnet.com (O’Reilly), http://www.ondotnet.com/pub/a/dotnet/2004/05/17/liberty.html. Warto też zapoznać się z projektem mającym na celu gromadzenie i udo- stępnianie bibliotek klas dla platformy .NET, o którym więcej na stronie Wintellect (http://www.wintellect.com/powercollections/). Implementacja interfejsów kolekcji W platformie .NET 2.0 poza generycznymi klasami kolekcji znajduje się również zestaw generycznych interfejsów pozwalających na tworzenie ty- powanych kolekcji cechujących się pełnym zakresem funkcji wcześniej- szych, niegenerycznych klas kolekcji z biblioteki platformy .NET 1.x. Inter- fejsy te zostały zebrane w przestrzeni nazw System.Collections.Generic, do której trafiło również sporo interfejsów pomocniczych, w tym ICompa- rable T służący do porównywania dwóch obiektów typu T (niekoniecznie przechowywanych w kolekcji). Wszystko to pozwala na proste utworzenie kolekcji w postaci uporządko- wanej listy elementów — wystarczy dla każdego typu T danych przecho- wywanego w elementach listy zaimplementować interfejs IComparable T i uczynić obiekt Node odpowiedzialnym za wstawianie nowych węzłów (obiektów Node) w odpowiednie miejsca listy — tak aby zachować upo- rządkowanie węzłów. Implementacja interfejsów kolekcji 33 Zakres typów właściwych dla typu generycznego można ograniczać. Jak to zrobić? Interfejs IComparable jest już zaimplementowany dla typu Integer; w pro- sty sposób można taką implementację utworzyć również dla klasy Pil- grim. Trzeba jedynie zmodyfikować definicję klasy Pilgrim, tak aby sy- gnalizowała implementację interfejsu IComparable T : public class Pilgrim: IComparable Pilgrim Rzecz jasna trzeba też koniecznie zaimplementować wymagane metody interfejsu: CompareTo i Equals. Interfejs jest typowany, więc obiekty prze- kazywane do tych metod będą typu Pilgrim: public int CompareTo(Pilgrim rhs) public bool Equals(Pilgrim rhs) Teraz wystarczy już zmienić logikę kodu odpowiedzialnego za dodawa- nie nowych węzłów do listy. Tym razem zamiast szukać ostatniego wę- zła listy, należałoby szukać odpowiedniego miejsca pomiędzy istniejącymi węzłami — takiego, żeby po wstawieniu nowego węzła zachowane zo- stało uporządkowanie listy; miejsce to wyszukuje się na bazie implemen- tacji CompareTo. Przede wszystkim typ przechowywany w węźle musi implementować in- terfejs IComparable. Osiąga się to przy użyciu słowa kluczowego where: public class Node T : IComparable Node T where T:IComparable T Powyższy wiersz kodu deklaruje klasę węzła Node typu T implementującą interfejs IComparable (dla węzłów Node typu T) i ograniczoną do przecho- wywania w węzłach takich typów danych T, które implementują interfejs IComparable. Jeśli w węźle spróbujemy umieścić obiekt innego typu niż implementujący IComparable, próba kompilacji kodu sprowokuje błąd. Przy samym wstawianiu węzła do listy trzeba rozważyć i obsłużyć przy- padek szczególny, kiedy to nowy węzeł będzie „mniejszy” od węzła sta- nowiącego czoło listy. Widać to na listingu 1.3 (różnice w stosunku do po- przedniej implementacji kolekcji zostały wyróżnione pogrubieniem). Listing 1.3. Implementacja interfejsów generycznych using System; using System.Collections.Generic; 34 Rozdział 1: C# 2.0 namespace ImplementingGenericInterfaces { public class Pilgrim : IComparable Pilgrim { private string name; public Pilgrim(string name) { this.name = name; } public override string ToString() { return this.name; } // implementacja interfejsu public int CompareTo(Pilgrim rhs) { return this.name.CompareTo(rhs.name); } public bool Equals(Pilgrim rhs) { return this.name == rhs.name; } } // węzeł musi implementować interfejs IComparable dla węzłów Node typu T // węzeł może przechowywać jedynie te typy T, które implementują IComparable // (warunek określany słowem kluczowym where). public class Node T : IComparable Node T where T:IComparable T { // pola składowe private T data; private Node T next = null; private Node T prev = null; // konstruktor public Node(T data) { this.data = data; } // właściwości public T Data { get { return this.data; } } public Node T Next { get { return this.next; } } public int CompareTo(Node T rhs) { Implementacja interfejsów kolekcji 35 // działa z racji ograniczenia (where T:IComparable T ) return data.CompareTo(rhs.data); } public bool Equals(Node T rhs) { return this.data.Equals(rhs.data); } // metody public Node T Add(Node T newNode) { if (this.CompareTo(newNode) 0) // wstawienie przed węzeł // bieżący { newNode.next = this; // wskaźnik next ustawiany na węzeł // bieżący // jeśli istnieje węzeł poprzedni, powinien od tego momentu // wskazywać polem next nowy węzeł if (this.prev != null) { this.prev.next = newNode; newNode.prev = this.prev; } // wskaźnik poprzednika węzła bieżącego ma wskazywać nowy // węzeł this.prev = newNode; // zwrócenie referencji nowego węzła, jeśli stał się nowym // czołem listy return newNode; } else // wstawienie za węzeł bieżący { // jeśli bieżący nie jest ostatnim, całą operację przejmuje // następny if (this.next != null) { this.next.Add(newNode); } // brak następnego węzła — nowy węzeł trzeba skojarzyć // z polem next bieżącego; // a w polu prev nowego wstawić referencję do bieżącego else { this.next = newNode; newNode.prev = this; } return this; 36 Rozdział 1: C# 2.0 } } public override string ToString() { string output = data.ToString(); if (next != null) { output += , + next.ToString(); } return output; } } // koniec klasy public class SortedLinkedList T where T : IComparable T { // pola składowych private Node T headNode = null; // właściwości // indekser public T this[int index] { get { int ctr = 0; Node T node = headNode; while (node != null ctr = index) { if (ctr == index) { return node.Data; } else { node = node.Next; } ++ctr; } // koniec while throw new ArgumentOutOfRangeException(); } // koniec get } // koniec indeksera // konstruktor public SortedLinkedList() Implementacja interfejsów kolekcji 37 { } // metody public void Add(T data) { if (headNode == null) { headNode = new Node T (data); } else { headNode = headNode.Add(new Node T (data)); } } public override string ToString() { if (this.headNode != null) { return this.headNode.ToString(); } else { return string.Empty; } } } class Program { // punkt wejścia static void Main(string[] args) { SortedLinkedList int mySortedLinkedList = new SortedLinkedList int (); Random rand = new Random(); Console.Write( Wypełnianie: ); for (int i = 0; i 10; i++) { int nextInt = rand.Next(10); Console.Write( {0} , nextInt); mySortedLinkedList.Add(nextInt); } SortedLinkedList Pilgrim pilgrims = new SortedLinkedList Pilgrim (); pilgrims.Add(new Pilgrim( Rycerz )); pilgrims.Add(new Pilgrim( Młynarz )); pilgrims.Add(new Pilgrim( Szeryf )); pilgrims.Add(new Pilgrim( Kucharz )); pilgrims.Add(new Pilgrim( Adwokat )); 38 Rozdział 1: C# 2.0 Console.WriteLine( Pobieranie kolekcji... ); DisplayList int ( Liczby , mySortedLinkedList); DisplayList Pilgrim ( Pielgrzymi , pilgrims); //Console.WriteLine( Liczby: + mySortedLinkedList); //Console.WriteLine( Pielgrzymi: + pilgrims); Console.WriteLine( Czwarta liczba to + mySortedLinkedList[3]); Pilgrim d = pilgrims[2]; Console.WriteLine( Trzeci pielgrzym to + d); // foreach (Pilgrim p in pilgrims) // { // Console.WriteLine( Zawód pielgrzyma to + p.ToString()); // } } // koniec metody Main private static void DisplayList T (string intro, SortedLinkedList T theList) where T : IComparable T { Console.WriteLine(intro + : + theList); } } // koniec klasy } // koniec przestrzeni nazw Wynik: Wypełnianie: 2 8 2 5 1 7 2 8 5 5 Pobieranie kolekcji... Liczby: 1, 2, 2, 2, 5, 5, 5, 7, 8, 8 Pielgrzymi: Adwokat, Kucharz, Młynarz, Rycerz, Szeryf Czwarta liczba to 2 Trzeci pielgrzym to Młynarz Jak to działa? Klasa Pilgrim została uzupełniona o implementację interfejsu generycz- nego IComparable. Sama lista nie zmieniła się ani na jotę, ale już klasa węzłów listy (Node) przeszła poważną metamorfozę, dzięki której wsta- wianie węzłów do listy odbywa się z zachowaniem ich wzajemnego upo- rządkowania. Po pierwsze, klasa Node została oznaczona jako klasa implementująca in- terfejs IComparable i ograniczona do przechowywania obiektów takich ty- pów, które również implementują ów interfejs: public class Node T : IComparable Node T where T:IComparable T Implementacja interfejsów kolekcji 39 Po drugie, w węźle obok pola z referencją do następnego węzła pojawiło się pole referencji do węzła poprzedniego (czyniąc listę listą dwukierunkową): private Node T next = null; private Node T prev = null; Klasa Node musi teraz implementować metody CompareTo i Equals. Są to proste metody, bo ich działanie sprowadza się do oddelegowania porów- nania do analogicznych metod obiektu przechowywanego — a wiadomo, że obiekty te również implementują interfejs IComparable: public int CompareTo(Node T rhs) { // działa z racji ograniczenia (where T:IComparable T ) return data.CompareTo(rhs.data); } A co… … z wymaganiem implementacji interfejsu IComparable? Dlaczego mu- siała go implementować klasa Pilgrim i Node, a już sama klasa kolekcji (tu SortedLinkedList) nie? Aby to wyjaśnić, trzeba przypomnieć, że i Pilgrim, i Node to obiekty da- nych podlegające operacjom porównania; sama lista jako ogólniejsza struk- tura nie jest zaś nigdy porównywana z innymi listami. Uporządkowanie węzłów listy odbywa się przez ustalanie ich kolejności na bazie porów- nań; nigdzie nie zachodzi zaś porównanie dwóch list i sprawdzanie, która z nich jest „większa”. … z przekazywaniem typów generycznych do metod? Czy to możliwe? Tak, przekazywanie typów generycznych do metod jest dozwolone, pod warunkiem że chodzi o metody generyczne. Przykładem może być po- niższy kod zaczerpnięty z listingu 1.3, wyświetlający zawartość listy liczb i listy pielgrzymów: Console.WriteLine( Liczby: + myLinkedList); Console.WriteLine( Pielgrzymi: + pilgrims); Nic nie stoi na przeszkodzie, aby utworzyć metodę przyjmującą za pośred- nictwem argumentu taką listę i wyświetlającą jej elementy (albo w dowolny inny sposób manipulującą tą listą): 40 Rozdział 1: C# 2.0 private static void DisplayList T (string intro, LinkedList T theList) where T : IComparable T { Console.WriteLine(intro + : + theList); } W wywołaniu takiej metody należy uściślić typy: DisplayList int ( Liczby , myLinkedList); DisplayList Pilgrim ( Pielgrzymi , pilgrims); WSKAZÓWKA Kompilator ma możliwość wnioskowania o typie metody na podstawie typów argumentów, więc poprzednie dwa wywołania można zapisać również tak: DisplayList( Liczby , myLinkedList); DisplayList( Pielgrzymi , pilgrims); Więcej informacji Zawartość przestrzeni nazw Generic jest wyczerpująco omawiana w do- kumentacji MSDN — wystarczy ją przeszukać pod kątem hasła „Sys- tems.Collections.Generic”. Polecam też artykuł traktujący o typach gene- rycznych, publikowany w serwisie ONDotnet.com (O’Reilly) (http://www. ondotnet.com/pub/a/dotnet/2004/05/17/liberty.html). Stosowanie iteratorów generycznych W poprzednio prezentowanych przykładach nie dało się przeglądać listy Pilgrims w pętli foreach. Gdyby w programie z listingu 1.3 umieścić po- niższy kod: foreach (Pilgrim p in pilgrims) { Console.WriteLine( Zawód pielgrzyma to + p.ToString()); } próba kompilacji programu doprowadziłaby do zgłoszenia następującego błędu: Error 1 foreach statement cannot operate on variables of type ImplementingGenericInterfaces.LinkedList ImplementingGenericInterfaces. Pilgrim because ImplementingGenericInterfaces.LinkedList Dodawanie iteratorów pozwala klientom przeglądać kolekcje w pętlach foreach. Stosowanie iteratorów generycznych 41 ImplementingGenericInterfaces.Pilgrim does not contain a public definition for GetEnumerator W poprzednich wersjach C# implementowanie metody GetEnumerator było dość uciążliwe i skomplikowane; w C# 2.0 cały zabieg został znacz- nie uproszczony. Jak to zrobić? Aby uprościć sobie tworzenie iteratorów, należałoby przede wszystkim uprościć klasy Pilgrim i LinkedList. Klasa kolekcji LinkedList powinna też zaniechać stosowania węzłów i przechowywać elementy listy w ta- blicy o stałym rozmiarze (taka tablica to najprostszy możliwy typowany kontener-kolekcja). Kolekcja zostanie więc listą jedynie z nazwy! To po- zwoli się jednak skupić na implementacji interfejsu IEnumerable, prezen- towanej na listingu 1.4. Listing 1.4. Implementacja interfejsu IEnumerable (wersja uproszczona) #region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace SimplifiedEnumerator { // uproszczona wersja klasy Pilgrim public class Pilgrim { private string name; public Pilgrim(string name) { this.name = name; } public override string ToString() { return this.name; } } // uproszczona wersja klasy listy class NotReallyALinkedList T : IEnumerable T { 42 Rozdział 1: C# 2.0 // wszystkie elementy listy są przechowywane w tablicy // o stałym rozmiarze T[] myArray; // konstruktor przyjmuje tablicę i umieszcza jej elementy we własnej // tablicy public NotReallyALinkedList(T[] members) { myArray = members; } // implementacja głównej metody interfejsu IEnumerable IEnumerator T IEnumerable T .GetEnumerator() { foreach (T t in this.myArray) { yield return t; } } // wymagana implementacja również dla wersji niegenerycznej System.Collections.IEnumerator System.Collections.IEnumerable. GetEnumerator() { throw new NotImplementedException(); } } class Program { static void Main(string[] args) { // ręczne tworzenie tablicy obiektów klasy Pilgrim Pilgrim[] pilgrims = new Pilgrim[5]; pilgrims[0] = new Pilgrim( Rycerz ); pilgrims[1] = new Pilgrim( Młynarz ); pilgrims[2] = new Pilgrim( Szeryf ); pilgrims[3] = new Pilgrim( Kucharz ); pilgrims[4] = new Pilgrim( Adwokat ); // utworzenie listy, przekazanie tablicy elementów NotReallyALinkedList Pilgrim pilgrimCollection = new NotReallyALinkedList Pilgrim (pilgrims); // przeglądanie elementów listy foreach (Pilgrim p in pilgrimCollection) { Console.WriteLine(p); } } } } Stosowanie iteratorów generycznych 43 Wynik: Rycerz Młynarz Szeryf Kucharz Adwokat Jak to działa? W tym przykładzie lista została znacznie uproszczona — elementy listy zamiast w przydzielanych dynamicznie węzłach lądują w statycznej ta- blicy (co w zasadzie dyskwalifikuje tę implementację jako listę). Ponieważ owa pseudo-lista daje się jednak przeglądać z użyciem iteratorów, prze- chowywana w niej kolekcja obiektów Pilgrim daje się przeglądać w pętli foreach. W obliczu zapisu: foreach (Pilgrim p in pilgrimCollection) kompilator języka C# wywołuje na rzecz obiektu kolekcji metodę GetEnume- rator. Rozwinięcie takiej pętli jest wewnętrznie implementowane mniej więcej tak: Enumerator e = pilgrimCollection.GetEnumerator(); while (e.MoveNext()) { Pilgrim p = e.Current; } Jak już powiedziano, w C# 2.0 nie trzeba się zajmować implementacją metody MoveNext() czy właściwością Current. Trzeba jedynie zastosować nowe słowo kluczowe C# — yield: WSKAZÓWKA Słowo kluczowe yield można stosować jedynie w blokach iteracji. Słowo to albo odnosi się do wartości obiektu enumeratora, albo sygna- lizuje koniec iteracji: yield return wyrażenie; yield break; Gdyby wejść do wnętrza pętli foreach za pomocą debugera, okazałoby się, że za każdym razem następuje tam wywołanie na rzecz obiektu kolekcji Każde zastosowanie pętli foreach jest przez kompilator tłumaczone na wywołanie metody GetEnumerator. 44 Rozdział 1: C# 2.0 metody GetEnumerator; we wnętrzu tej metody następuje zaś wielokrotne zwracanie wartości do pętli foreach za pośrednictwem kolejnych wystą- pień słowa kluczowego yield1. A co… … z implementacją metody GetEnumerator dla bardziej złożonych struktur danych, na przykład dla prawdziwej listy? To właśnie będzie przedmiotem następnego ćwiczenia. Więcej informacji Poruszone zagadnienie jest wyczerpująco omawiane w obszernym arty- kule z biblioteki MSDN, zatytułowanym „Iterators (C#)”. Implementacja GetEnumerator dla złożonych struktur danych Aby dać pierwotnej liście LinkedList możliwość przeglądania elemen- tów za pośrednictwem iteratora, trzeba zaimplementować interfejs IEnume- rable T dla samej klasy listy (LinkedList) i klasy węzłów listy (Node). public class LinkedList T : IEnumerable T public class Node T : IComparable Node T , IEnumerable Node T Jak to zrobić? Przy okazji poprzedniego ćwiczenia okazało się, że interfejs IEnumerable wymaga zasadniczo implementacji tylko jednej metody — GetEnumera- tor. Implementację tę dla bardziej złożonej struktury danych, jaką jest choćby lista, prezentuje listing 1.5 (zmiany względem kodu z listingu 1.3 zostały wyróżnione pogrubieniem). 1 Słowo yield działa jak instrukcja return uzupełniona o pamięć stanu — służy do iteracyjnego zwracania kolejnych elementów sekwencji — przyp. tłum. Implementacja GetEnumerator dla złożonych struktur danych 45 Listing 1.5. Implementacja iteratora dla klasy listy using System; using System.Collections.Generic; namespace GenericEnumeration { public class Pilgrim : IComparable Pilgrim { private string name; public Pilgrim(string name) { this.name = name; } public override string ToString() { return this.name; } // implementacja interfejsu IComparable public int CompareTo(Pilgrim rhs) { return this.name.CompareTo(rhs.name); } public bool Equals(Pilgrim rhs) { return this.name == rhs.name; } } // węzeł musi być implementować interfejs IComparable dla węzłów Node // typu T teraz dodatkowo implementuje interfejs IEnumerable do użytku // w pętlach foreach public class Node T : IComparable Node T , IEnumerable Node T where T:IComparable T { // pola składowe private T data; private Node T next = null; private Node T prev = null; // konstruktor public Node(T data) { this.data = data; } // właściwości public T Data { get { return this.data; } } public Node T Next { get { return this.next; } 46 Rozdział 1: C# 2.0 } public int CompareTo(Node T rhs) { return data.CompareTo(rhs.data); } public bool Equals(Node T rhs) { return this.data.Equals(rhs.data); } // metody public Node T Add(Node T newNode) { if (this.CompareTo(newNode) 0) // wstawienie przed węzeł // bieżący { newNode.next = this; // wskaźnik next ustawiany na węzeł // bieżący jeśli istnieje węzeł poprzedni, powinien od tego // momentu wskazywać polem next nowy węzeł if (this.prev != null) { this.prev.next = newNode; newNode.prev = this.prev; } // wskaźnik poprzednika węzła bieżącego ma wskazywać nowy // węzeł this.prev = newNode; // zwrócenie referencji nowego węzła, jeśli stał się nowym // czołem listy return newNode; } else // wstawienie za węzeł bieżący { // jeśli bieżący nie jest ostatnim, całą operację przejmuje // następny if (this.next != null) { this.next.Add(newNode); } // brak następnego węzła — nowy węzeł trzeba skojarzyć // z polem next bieżącego; // a w polu prev nowego wstawić referencję do bieżącego else { this.next = newNode; newNode.prev = this; } Implementacja GetEnumerator dla złożonych struktur danych 47 return this; } } public override string ToString() { string output = data.ToString(); if (next != null) { output += , + next.ToString(); } return output; } // Metody wymagane przez IEnumerable IEnumerator Node T IEnumerable Node T .GetEnumerator() { Node T nextNode = this; // przeglądanie wszystkich węzłów listy, // zwracanie (yield) kolejnych węzłów do { Node T returnNode = nextNode; nextNode = nextNode.next; yield return returnNode; } while (nextNode != null); } System.Collections.IEnumerator System.Collections.IEnumerable. GetEnumerator() { throw new NotImplementedException(); } } // koniec klasy // implementacja IEnumerable pozwalająca na stosowanie // klasy LinkedList w pętlach foreach public class LinkedList T : IEnumerable T where T : IComparable T { // pola składowych private Node T headNode = null; // właściwości // indekser 48 Rozdział 1: C# 2.0 public T this[int index] { get { int ctr = 0; Node T node = headNode; while (node != null ctr = index) { if (ctr == index) { return node.Data; } else { node = node.Next; } ++ctr; } // koniec while throw new ArgumentOutOfRangeException(); } // koniec get } // koniec indeksera // konstruktor public LinkedList() { } // metody public void Add(T data) { if (headNode == null) { headNode = new Node T (data); } else { headNode = headNode.Add(new Node T (data)); } } public override string ToString() { if (this.headNode != null) { return this.headNode.ToString(); } else { return string.Empty; } } Implementacja GetEnumerator dla złożonych struktur danych 49 // Implementacja wymaganej metody IEnumerable // przeglądająca węzły (również implementujące ten interfejs) // i zwracająca (yield) dane zwrócone z węzła IEnumerator T IEnumerable T .GetEnumerator() { foreach (Node T node in this.headNode) { yield return node.Data; } } System.Collections.IEnumerator System.Collections.IEnumerable. GetEnumerator() { throw new NotImplementedException(); } } class Program { private static void DisplayList T (string intro, LinkedList T theList) where T : IComparable T { Console.WriteLine(intro + : + theList); } // punkt wejścia static void Main(string[] args) { LinkedList Pilgrim pilgrims = new LinkedList Pilgrim (); pilgrims.Add(new Pilgrim( Rycerz )); pilgrims.Add(new Pilgrim( Młynarz )); pilgrims.Add(new Pilgrim( Szeryf )); pilgrims.Add(new Pilgrim( Kucharz )); pilgrims.Add(new Pilgrim( Adwokat )); DisplayList Pilgrim ( Pilgrims , pilgrims); Console.WriteLine( Przeglądanie listy pielgrzymów... ); // teraz lista daje się przeglądać, więc można ją // zastosować w pętli foreach foreach (Pilgrim p in pilgrims) { Console.WriteLine( Zawód pielgrzyma to + p.ToString()); } } } } 50 Rozdział 1: C# 2.0 Wynik: Pielgrzymi: Adwokat, Kucharz, Młynarz, Rycerz, Szeryf Przeglądanie listy pielgrzymów... Zawód pielgrzyma to Adwokat Zawód pielgrzyma to Kucharz Zawód pielgrzyma to Młynarz Zawód pielgrzyma to Rycerz Zawód pielgrzyma to Szeryf Jak to działa? Lista implementuje teraz enumerator; implementacja polega na zainicjo- waniu pętli foreach dla czołowego węzła listy (klasa węzła również im- plementuje interfejs IEnumerable). Implementacja zwraca obiekt danych zwrócony przez węzeł: IEnumerator T IEnumerable T .GetEnumerator() { foreach (Node T node in this.headNode) { yield return node.Data; } } Odpowiedzialność za realizację iteracji spada tym samym na klasę Node, która we własnej implementacji metody GetEnumerator również posługuje się słowem kluczowym yield. IEnumerator Node T IEnumerable Node T .GetEnumerator() { Node T nextNode = this; do { Node T returnNode = nextNode; nextNode = nextNode.next; yield return returnNode; } while (nextNode != null); } Obiekt nextNode jest inicjalizowany referencją do węzła bieżącego, po czym następuje rozpoczęcie pętli do...while. Pętla taka zostanie wykonana przy- najmniej jednokrotnie. W pętli następuje przepisanie wartości nextNode do returnNode i próba odwołania się do następnego węzła listy (wskazywa- nego polem next). Następna instrukcja pętli zwraca do wywołującego (za pomocą słowa yield) węzeł zapamiętany przed chwilą w returnNode. Kiedy w następnym kroku iteracji nastąpi powrót do pętli, zostanie ona W miejsce instrukcji yield kompilator automatycznie generuje zagnieżdżoną implementację IEnumerator. Zapamiętuje tam stan iteracji; programista musi jedynie wskazać wartości do zwrócenia w kolejnych krokach iteracji. Implementacja GetEnumerator dla złożonych struktur danych 51 wznowiona od tego miejsca; całość będzie powtarzana dopóty, dopóki pole next któregoś z kolejnych węzłów nie okaże się puste, a tym samym węzeł będzie ostatnim węzłem listy. A co… … znaczy występujące w implementacji LinkedList żądanie przejrzenia (foreach) elementów Node T w headNode? Przecież headNode to nie lista, a jeden z jej węzłów (konkretnie węzeł czołowy)? Otóż headNode to faktycznie czołowy węzeł listy. Ponieważ jednak klasa Node implementuje interfejs IEnumerable, dla potrzeb iteracji węzeł zacho- wuje się jak kolekcja. Choć brzmi to niedorzecznie, jest całkiem uzasad- nione, bo węzeł w istocie przejawia pewne cechy kolekcji, w tym przy- najmniej sensie, że potrafi wskazać następny element kolekcji (następny węzeł listy). Całość można by przeprojektować tak, żeby węzły nie były tak „sprytne”, za to sama lista była „sprytniejsza” — wtedy zadanie realizacji iteracji spoczywałoby w całości na liście i ta nie delegowałaby zadania do węzłów. Więcej informacji O interfejsie IEnumerable T można się sporo dowiedzieć z plików pomocy MSDN dla hasła „Topic: IEnumerable T ”. Upraszczanie kodu — metody anonimowe Metody anonimowe pozwalają na definiowanie nienazwanych bloków kodu rozwijanych w miejscu wywołania. Z metod anonimowych można korzy- stać wszędzie tam, gdzie dozwolone są delegacje. Za ich pośrednictwem można na przykład znakomicie uprościć rejestrowanie procedur obsługi zdarzeń. Jak to zrobić? Zastosowania metod anonimowych najlepiej zilustrować przykładem: 1. Utworzyć w Visual Studio .NET 2005 nową aplikację okienkową i nadać jej nazwę AnonymousMethods. 52 Rozdział 1: C# 2.0 Metody anonimowe pozwalają na stosowanie bloków kodu w roli parametrów. 2. Przeciągnąć na domyślny formularz okna dwie kontrolki: etykietę oraz przycisk (nie warto zajmować się przydzielaniem im specjalnych nazw). 3. Dwukrotnie kliknąć przycisk lewym klawiszem myszy. Wyświetlone zostanie okno edytora, w którym należy umieścić poniższy kod: private void button1_Click(object sender, EventArgs e) { label1.Text = Do widzenia! ; } 4. Uruchomić aplikację. Kliknięcie przycisku powinno zmieniać treść ety- kiety na Do widzenia!. Wszystko świetnie. Ale jest tu pewien ukrywany przed programistą narzut. Otóż powyższy kod wymaga rejestrowania delegacji (wyręcza nas w tym stosowny kreator Visual Studio 2005), a obsługa kliknięcia przycisku wy- maga zdefiniowania nowej metody. Całość można zaś uprościć stosując metody anonimowe. Aby sprawdzić, jak faktycznie rejestrowana jest metoda obsługi zdarzenia kliknięcia przycisku, należy kliknąć w IDE przycisk Show All Files (pre- zentowany na rysunku 1.1). Rysunek 1.1. Przycisk Show All Files Teraz należałoby otworzyć plik Form1.Designer.cs i odszukać w nim delegację button1.Click: this.button1.Click += new System.EventHandler(this.button1_Click); Nie powinno się ręcznie modyfikować tego kodu, ale można wyeliminować ten wiersz inaczej — wracając do formularza i klikając w oknie właści- wości (Properties) ikonę błyskawicy, wywołującą procedury obsługi zda- rzeń. Tam można usunąć procedurę obsługi zarejestrowaną dla zdarze- nia Click. Upraszczanie kodu — metody anonimowe 53 Po powrocie do kodu Form1.Designer.cs okaże się, że procedura obsługi zdarzenia button1.Click nie jest w ogóle zarejestrowana! Teraz należy otworzyć do edycji plik Form1.cs i dodać do konstruktora (za wywołaniem InitializeComponent()) poniższy wiersz: this.button1.Click += delegate { label1.Text = Do widzenia! }; Dzięki temu można już pozbyć się dodatkowej metody procedury obsługi zdarzenia — można ją usunąć albo oznaczyć jako komentarz: // private void button1_Click(object sender, EventArgs e) // { // label1.Text = Do widzenia! ; // } Działanie metody anonimowej można sprawdzić, ponownie uruchamiając aplikację. Powinna zachowywać się dokładnie tak, jak poprzednio. Jak widać, zamiast rejestrować delegację wywołującą metodę obsługi zda- rzenia, można wskazać metodę anonimową — nienazwany, rozwijany w miejscu wywołania blok kodu. A co… … z innymi zastosowaniami metod anonimowych? Czy można je sto- sować we własnym kodzie? Żaden problem. Metody anonimowe można stosować nie tylko przy inicja- lizowaniu delegacji, ale i wszędzie tam, gdzie dozwolone jest użycie de- legacji — we wszystkich tych miejscach można przekazać nienazwany blok kodu. … jeśli w takim bloku kodu nastąpi odwołanie do zmiennej lokalnej? Dobre pytanie. To dość myląca sytuacja i łatwo tu o pomyłkę, zwłaszcza kiedy nie jest się w pełni świadomym konsekwencji takich odwołań. Otóż C# pozwala na wciąganie zmiennych lokalnych do zasięgu anonimowego bloku kodu; odwołania do nich są wykonywane w momencie wykonania owego bloku kodu. Może to prowokować rozmaite efekty uboczne — choćby podtrzymywanie przy życiu obiektów, którymi inaczej już dawno zaopie- kowałby się mechanizm zbierania nieużytków. 54 Rozdział 1: C# 2.0 … z usuwaniem procedury obsługi dla zdarzenia, dodanej za pomocą delegacji anonimowej; da się to zrobić? Jeśli procedura obsługi zdarzenia została określona delegacją anonimo- wą, nie można jej usunąć; dlatego delegacje anonimowe powinno się stoso- wać jedynie dla tych procedur obsługi, które mają być trwale skojarzone z danymi zdarzeniami. Ponadto można stosować delegacje anonimowe również w innych dziedzi- nach, choćby przy implementowaniu metody List.Find przyjmującej dele- gację opisującą kryteria wyszukiwania. Więcej informacji W zasobach MSDN można znaleźć świetny artykuł traktujący o metodach anonimowych. Mowa o artykule „Create Elegant Code with Anonymous Methods, Iterators and Partial Classes” autorstwa Juvala Lowy’ego. Warto też zapoznać się z artykułem z serwisu ONDotnet.com (O’Reilly), publiko- wanym pod adresem http://www.ondotnet.com/pub/a/dotnet/2004/04/05/ csharpwhidbeypt1.html. Ukrywanie kodu — typy częściowe W poprzednich wersjach C# całość definicji klasy musiała być umieszcza- na w pojedynczym pliku. Teraz dzięki słowu kluczowemu partial można dzielić klasę na części przechowywane w większej liczbie plików. Moż- liwość ta jest cenna z dwóch względów: • W zespole programistycznym można przeprowadzić podział polegający na przypisaniu różnych programistów do prac nad różnymi częściami klasy. • Visual Studio 2005 może w ten sposób oddzielać kod generowany au- tomatycznie od kodu własnego programisty. Słowo kluczowe partial pozwala na podział definicji klasy na wiele plików. Ukrywanie kodu — typy częściowe 55 Jak to zrobić? Praktyczne zastosowanie typów częściowych można zilustrować na bazie poprzedniego przykładu (AnonymousMethods). Spójrzmy na deklarację klasy w pliku Form1.cs: partial class Form1 : Form { public Form1() { InitializeComponent(); this.button1.Click += delegate { label1.Text = Do widzenia! ; }; } // private void button1_Click(object sender, EventArgs e) // { // label1.Text = Do widzenia! ; // } } Słowo kluczowe partial sygnalizuje, że kod zamieszczony w tym pliku niekoniecznie reprezentuje całość definicji klasy. Co zresztą zgadza się z naszą wiedzą, bo przecież w poprzednim podrozdziale zaglądaliśmy do drugiego pliku Form1.Designer.cs zawierającego resztę definicji klasy: namespace AnonymousMethods { partial class Form1 { /// summary /// Required designer variable. /// /summary private System.ComponentModel.IContainer components = null; /// summary /// Clean up any resources being used. /// /summary protected override void Dispose(bool disposing) { if (disposing (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code ... #endregion 56 Rozdział 1: C# 2.0 private System.Windows.Forms.Label label1; private System.Windows.Forms.Button button1; } } Kompletną definicję klasy Form1 dają dopiero te dwa pliki wzięte razem; podział klasy pozwala na wyodrębnienie kodu tworzonego przez programi- stę i kodu generowanego automatycznie przez różne mechanizmy środo- wiska programistycznego. Czyni to projekt przejrzystszym i prostszym. Stosując klasy częściowe, trzeba mieć świadomość kilku aspektów: • Wszystkie częściowe definicje typów muszą zawierać słowo kluczowe partial i muszą należeć do tej samej przestrzeni nazw oraz tego same- go modułu i podzespołu. • Modyfikator partial może występować jedynie przed słowami kluczo- wymi class, inerface i struct. • We wszystkich definicjach częściowych należy uzgodnić modyfika- tory dostępu do składowych (public, private itd.). A co… … ze stosowaniem klas częściowych we własnych projektach? Microsoft sugeruje, że klasy częściowe mogą przydać się programistom pracującym w zespołach — mogą wtedy podzielić się pracą nad klasami. Wciąż jednak za wcześnie, aby stwierdzić, czy taka praktyka się przyj- mie; osobiście uważam, że każda klasa tak rozbudowana, aby jej roz- miar uzasadniał podział pracy, powinna po
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Visual C# 2005. Zapiski programisty
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ą: