You are on page 1of 58

IDZ DO

PRZYKADOWY ROZDZIA
SPIS TRECI

KATALOG KSIEK
KATALOG ONLINE
ZAMW DRUKOWANY KATALOG

Visual C# 2005.
Zapiski programisty
Autor: Jesse Liberty
Tumaczenie: Przemysaw Szeremiota
ISBN: 83-246-0249-6
Tytu oryginau: Visual C# 2005: A Developers Notebook
Format: B5, stron: 280
Przykady na ftp: 1162 kB

TWJ KOSZYK
DODAJ DO KOSZYKA

CENNIK I INFORMACJE
ZAMW INFORMACJE
O NOWOCIACH
ZAMW CENNIK

CZYTELNIA
FRAGMENTY KSIEK ONLINE

Odkryj nowe moliwoci platformy .NET 2005


Visual C# 2005 to najnowsza wersja jzyka programowania uwaanego przez wielu
programistw za najlepszy jzyk sucy do tworzenia aplikacji dla platformy .NET.
W poczeniu z now bibliotek klas .NET i nowymi moliwociami rodowiska Visual
Studio 2005 druga edycja jzyka C# staa si jeszcze doskonalsza. Pisanie programw
wymaga znacznie mniejszych nakadw pracy, a nowe elementy umoliwiaj realizacj
wikszej iloci zada programistycznych.
Aby pozna nowe moliwoci jzyka C#, signij po ksik Visual C# 2005. Zapiski
programisty. W tej wzorowanej na zeszytach laboratoryjnych publikacji znajdziesz
notatki programistw, ktrzy jako pierwsi zetknli si z t technologi. Nie ma w niej
teoretycznych wywodw, diagramw i niepotrzebnych informacji. Wykonujc 50
wicze demonstrujcych poszczeglne aspekty tworzenia aplikacji, poznasz prostot
stosowania nowych elementw i mechanizmw i przekonasz si, jak wiele udogodnie
wnosi do pracy programisty Visual C# 2005.
Stosowanie klas generycznych
Korzystanie z metod anonimowych
Refaktoryzacja kodu rdowego
Tworzenie interfejsw uytkownika i formularzy
Mechanizmy szybkiej instalacji aplikacji
Zabezpieczanie aplikacji WWW
Personalizacja stron WWW z uyciem motyww i szablonw
Poczenia z baz danych

Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63
e-mail: helion@helion.pl

Spis treci

Seria Zapiski programisty ..................................................................... 7


Wprowadzenie ..................................................................................... 13
Rozdzia 1. C# 2.0 ............................................................................... 21
Tworzenie typowanych list za pomoc kolekcji generycznych ............ 22
Wasna kolekcja generyczna .............................................................. 28
Implementacja interfejsw kolekcji ..................................................... 33
Stosowanie iteratorw generycznych .................................................. 41
Implementacja GetEnumerator dla zoonych struktur danych ........... 45
Upraszczanie kodu metody anonimowe ......................................... 52
Ukrywanie kodu typy czciowe .................................................... 55
Tworzenie klas statycznych ................................................................ 58
Wyraanie wartoci pustych typami nullable ..................................... 60
Odwoania do obiektw z globalnej przestrzeni nazw .......................... 65
Ograniczanie dostpu do waciwoci ................................................. 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 obiektw podczas debugowania ...........................................99
Wizualizacja danych XML ................................................................100
Diagnozowanie wyjtkw ..................................................................103
Rozdzia 3. Aplikacje okienkowe ....................................................... 107
Stosowanie paskw narzdzi .............................................................108
Narzucanie formatu danych wejciowych .........................................113
Pola tekstowe z automatycznym uzupenianiem ...............................118
Odtwarzanie dwikw ......................................................................121
Okna dzielone ....................................................................................123
Dynamiczne tworzenie formularzy ....................................................126
Tworzenie zada asynchronicznych .................................................130
Okno na wiat ....................................................................................137
Instalacja jednym klikniciem 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 uyciem typw zoonych .......................................192
Personalizacja dla uytkownikw anonimowych ...............................197
Personalizacja z uyciem motyww ..................................................203
Ujednolicanie wygldu aplikacji szablony stron ...........................214
Rozdzia 5. Praca z danymi ................................................................ 223
Wizanie aplikacji z danymi .............................................................224
Formularze ........................................................................................237
Widok typu og-szczeg ..................................................................244
Pozyskiwanie statystyk bazy danych ................................................248
Aktualizacje wsadowe a wydajno ..................................................251

Spis treci

rdo danych w dokumencie XML ................................................... 255


Manipulowanie dokumentami XML XPathDocument .................. 260
Zawanie elementw dokumentu XML przy uyciu interfejsu
XPath ............................................................................................. 265
Skorowidz ......................................................................................... 269

Spis treci

ROZDZIA 1.

C# 2.0

W tym rozdziale:
 Tworzenie typowanych list za pomoc kolekcji generycznych
 Wasne kolekcje generyczne
 Implementacja interfejsw kolekcji
 Stosowanie iteratorw generycznych
 Implementacja GetEnumerator dla zoonych struktur danych
 Upraszczanie kodu metody anonimowe
 Ukrywanie kodu typy czciowe
 Tworzenie klas statycznych
 Wyraanie wartoci pustych typami nullable
 Odwoania do obiektw z globalnej przestrzeni nazw
 Ograniczanie dostpu do waciwoci
 Elastyczno delegacji z kowariancj i kontrawariancj
Pierwszy rozdzia bdzie prezentowa nowe cechy jzyka C# w wersji
2.0 mowa bdzie o typach generycznych, iteratorach, metodach anonimowych, typach czciowych, klasach statycznych, typach wyrniajcych wartoci puste (nullable) oraz ograniczaniu dostpu do waciwoci; omwienie obejmie te kowariancj i kontrawariancj delegacji.

21

Najbardziej chyba wyczekiwan cech jzyka C# w wersji 2.0 s jednak typy generyczne, dajce moliwo szybkiego tworzenia kolekcji. I od
nich wanie zaczniemy.

Tworzenie typowanych list


za pomoc kolekcji generycznych
Bezpieczestwo typowania to klucz do tworzenia kodu prostego w konserwacji. Jzyk ze cis kontrol typw potrafi (skutecznie) wyszukiwa
bdy typowania w czasie kompilacji, a nie w czasie wykonania (kiedy
program zostanie ju oddany do uytku klientom!). Najwiksz saboci
C# 1.x bya nieobecno typw generycznych, ktre pozwalayby na deklarowanie kolekcji uoglnionych (choby stosw bd list) mogcych przechowywa elementy dowolnego typu przy zachowaniu moliwoci kontrolowania typu w czasie kompilacji.
W wersji 1.x niemal wszystkie kolekcje byy deklarowane jako kolekcje
przechowujce obiekty typu System.Object; poniewa za wszelkie klasy
wywodz si z System.Object, kolekcje takie mogy przechowywa elementy dosownie dowolnych typw. Elastyczno ta osigana bya kosztem
bezpieczestwa typowania.
Zamy, e w C# 1.x zajdzie potrzeba utworzenia listy obiektw Employee.
W tym celu mona by wykorzysta klas ArrayList pozwalajc na przechowywanie obiektw typu System.Object. Dodanie nowych obiektw Employee do takiej kolekcji nie stanowi adnego problemu przecie typ
Employee jest pochodn System.Object. Problem pojawia si dopiero przy
prbie wyuskania obiektu Employee z kolekcji ArrayList operacja
taka zwraca referencj do typu Object, ktr trzeba dopiero rzutowa na
typ Employee:
Employee theEmployee = (Employee) myArrayList[1];

Sk nie w tym, e trzeba dokonywa rzutowania, ale w tym, e w kolekcji


ArrayList mona rwnie atwo umieci obiekt dowolnego innego typu,
na przykad cig znakw. I wszystko bdzie w porzdku dopty, dopki
program bdzie si do niego odwoywa jak do cigu. Ale jeli kolekcja cigw zostanie omykowo przekazana do metody oczekujcej przekazania
22

Rozdzia 1: C# 2.0

kolekcji obiektw Employee, prba rzutowania obiektu String na typ Employee sprowokuje wyjtek.
Wreszcie kolekcje platformy .Net 1.x utrudniay przechowywanie wartoci typw prostych (ang. value type). Wartoci typw prostych musiay
by przed wstawieniem do kolekcji pakowane do obiektw, a po wyuskaniu z kolekcji rozpakowywane z obiektw.
W .NET 2.0 wszystkie te problemy zostay wyeliminowane przez udostpnienie nowej biblioteki kolekcji generycznych, zagniedonej w przestrzeni nazw System.Collections.Generic. Ot kolekcja generyczna to
po prostu taka kolekcja, ktra pozwala na ujcie w deklaracji typw elementw przechowywanych. Dziki temu kompilator znajcy deklaracj
bdzie zezwala na wstawianie do kolekcji jedynie obiektw waciwego typu.
Kolekcje generyczne definiuje si z uyciem specjalnej skadni; w nawiasach ostrych wymienia si nazw typu, ktry musi zosta zdefiniowany przy deklarowaniu egzemplarza kolekcji.
Nie trzeba ju rzutowa obiektw wyuskiwanych z kolekcji generycznej,
a sam kod korzystajcy z takich kolekcji jest bezpieczniejszy, bo podlega
statycznemu (realizowanemu w czasie kompilacji) typowaniu; atwiej poddaje si konserwacji i prociej si go stosuje.

Jak to zrobi?
Aby oswoi si z nowymi typami generycznymi w .NET 2.0, najlepiej sprbowa samemu utworzy typowan klas kolekcji (List) przechowujc
elementy typu Employee (pracownik). wiczenie naley rozpocz od uruchomienia rodowiska Visual Studio 2005, utworzenia nowej aplikacji konsolowej jzyka C# i opatrzenia jej nazw CreateATypeSafeList. Kod generowany w ramach szkieletu aplikacji przez kreator Visual Studio 2005
naley zastpi kodem z listingu 1.1.

Kolekcje generyczne
czyni kod
bezpieczniejszym,
upraszczaj jego
konserwacj
i stosowanie.

WSK A ZWK A
Korzystanie z typw generycznych wymaga wcignicia do programu
przestrzeni nazw System.Collections.Generic. W Visual Studio 2005
jest to domylne zachowanie dla wszystkich tworzonych projektw.

Tworzenie typowanych list za pomoc kolekcji generycznych

23

Listing 1.1. Tworzenie typowanej listy


using System;
using System.Collections.Generic;
namespace CreateATypeSafeList
{
// klasa obiektw przechowywanych na licie
public class Employee
{
private int empID;
// konstruktor
public Employee(int empID)
{
this.empID = empID;
}

// przesonicie metody ToString, tak


// aby wywietlaa identyfikator obiektu
public override string ToString()
{
return empID.ToString();
}
// koniec klasy

// Klasa testujca
public class Program
{
// punkt wejciowy
static void Main()
{
// Deklaracja listy typowanej (obiektw typu Employee)
List<Employee> empList = new List<Employee>();
// Deklaracja drugiej listy typowanej (wartoci cakowitych)
List<int> intList = new List<int>();
// wypenienie 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 elementw listy wartoci cakowitych
foreach (int i in intList)
{
Console.Write("{0} ", i.ToString());
}
Console.Write("\n");

24

Rozdzia 1: C# 2.0

// wypisanie identyfikatorw obiektw Employee


foreach (Employee employee in empList)
{
Console.Write("{0} ", employee.ToString());
}
Console.Write("\n");
}
}
}

Wynik:
0 5 10 15 20
100 101 102 103 104

WSK A ZWK A
Kod rdowy poszczeglnych wicze mona pobra z serwerw wydawnictwa Helion, spod adresu ftp://ftp.helion.pl/przyklady/vc25za.zip.
Kod publikowany jest w postaci spakowanego archiwum. Rozpakowanie
archiwum zaowocuje utworzeniem szeregu katalogw odpowiadajcych
poszczeglnym rozdziaom, a w nich podkatalogw o nazwach zgodnych z nazwami poszczeglnych projektw. Na przykad kodu z listingu 1.1 naley szuka w katalogu r1\CreateATypeSafeList.

Jak to dziaa?
Kod z listingu 1.1 utworzy dwie klasy: klas Employee (pracownik) klas obiektw przechowywanych w kolekcji, oraz klas Program tworzon
przez kreator Visual Studio 2005. Do tego w programie wykorzystana zostaa klasa List z biblioteki klas .NET Framework Class Library.
Klasa Employee zawiera pojedyncze prywatne pole (empID), konstruktor
i metod przesaniajc metod ToString i wywietlajc cig zawierajcy warto pola empID.
W klasie Program tworzony jest egzemplarz klasy List majcy przechowywa obiekty klasy Employee. Typem empList jest wic kolekcja List
obiektw Employee. Std deklaracja kolekcji:
List<Employee> empList

W definicji List<T> element T jest symbolem reprezentujcym przyszy,


waciwy typ listy.
Tworzenie typowanych list za pomoc kolekcji generycznych

25

Deklaracja empList tworzy (jak zwykle) referencj obiektu tworzonego na


stercie sowem kluczowym new. Sowo kluczowe new naley uzupeni wywoaniem konstruktora, co wyglda nastpujco:
new List<Employee>()

Takie wywoanie tworzy na stercie egzemplarz kolekcji List obiektw


Employee; caa instrukcja oznacza za utworzenie empList i przypisanie do niego referencji nowego obiektu sterty:
List<Employee> empList = new List<Employee>();

WSK A ZWK A
Cao dziaa dokadnie tak, jak w instrukcji:
Dog milo = new Dog();

tworzcej egzemplarz klasy Dog na stercie i przypisujcej go do referencji do typu Dog o nazwie milo.

W kolejnej instrukcji nastpuje utworzenie drugiej kolekcji; tym razem jest


to kolekcja List wartoci cakowitych:
List<int> intList = new List<int>();

Od tego momentu mona rozpocz wypenianie listy wartoci cakowitych wartociami cakowitymi, a listy elementw typu Employee obiektami
klasy Employee. Po wypenieniu list mona w ptlach foreach przejrze listy,
wyuska poszczeglne elementy i wypisa ich wartoci na konsoli:
foreach (Employee employee in empList)
{
Console.Write("{0} ", employee.ToString());
}

A co
kiedy do listy obiektw Employee dodana zostanie warto cakowita?
C, trzeba sprbowa. Wystarczy usun znacznik komentarza z prezentowanego poniej wiersza z listingu 1.1 i sprbowa ponownie skompilowa program:
empList.Add(i * 5);

Kompilator powinien zgosi par bdw:


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 opisujce te dwa bdy pozwalaj stwierdzi, e nie mona


dodawa elementu typu int do kolekcji obiektw typu Employee, bo pomidzy tymi typami nie da si przeprowadzi niejawnej konwersji, nie zachodzi te relacja zawierania si jednego typu w drugim.
Co waniejsze, kolizj typw wykrywa si ju na etapie kompilacji, a nie
dopiero w czasie wykonania a wiadomo, e bdy czasu wykonania
maj tendencj do ujawniania si nie na stanowiskach testowych, a na
biurkach klientw!
z innymi kolekcjami generycznymi; s jeszcze jakie?
Owszem, w platformie .NET 2.0 dostpne s te inne kolekcje typowane,
jak choby Stack (stos) czy Queue (kolejka); do tego dochodzi interfejs
ICollection.
Kolekcje te stosuje si tak samo, jak List<T>. Aby na przykad utworzy
stos obiektw typu Employee, naley w definicji klasy Stack zastpi
T (Stack<T>) nazw typu Employee:

W kolekcjach
typowanych mona
umieszcza elementy
typw pochodnych
wobec typu
deklarowanego.
Kolekcja elementw
typu Employee moe
wic przechowywa
rwnie elementy
typu Manager, o ile
Manager jest typem
pochodnym
Employee.

Stack<Employee> employeeStack = new Stack<Employee>();

Wicej informacji
Kompletu informacji o klasach generycznych w .NET 2.0 naley szuka
w pomocy MSDN, pod hasem Commonly Used Collection Types; warto
te zapozna si z artykuem publikowanym w witrynie ONDotnet.com
(OReilly) pod adresem http://www.ondotnet.com/pub/a/dotnet/2004/05/17/
liberty.html.
WSK A ZWK A
Artykuy i publikacje, na ktre powouj si w kolejnych wiczeniach,
s wymienione na stronie WWW ksiki pod adresem http://www.LibertyAssociates.com (odnonik Books, a nastpnie pozycja C# 2005:
A Developers Notebook).

Tworzenie typowanych list za pomoc kolekcji generycznych

27

Nastpne wiczenie bdzie ilustrowao sposb tworzenia wasnej typowanej kolekcji, uzupeniajcej zbir kolekcji udostpnianych w bibliotece
klas platformy .NET.

Wasna kolekcja generyczna

Od czasu do czasu
przyjdzie Ci
zdefiniowa
wasn klas kolekcji
generycznych.

Platforma .NET 2.0 udostpnia programistom szereg klas kolekcji generycznych implementujcych typowane listy, stosy, kolejki, sowniki i tym
podobne. Tak bogaty zestaw zaspokaja zdecydowan wikszo potrzeb
programistw w tym zakresie. Zamy jednak, e stoimy w obliczu koniecznoci utworzenia wasnej klasy kolekcji generycznej, uwzgldniajcej
wiedz z dziedziny danego problemu czy przejawiajcej specjalne cechy
(na przykad optymalizujcej rozmieszczenie elementw celem przyspieszenia odwoa). Na szczcie i sam jzyk, i platforma udostpniaj narzdzia
i mechanizmy tworzenia wasnych klas kolekcji generycznych.

Jak to zrobi?
Najprostszym sposobem powoania do ycia wasnej klasy kolekcji generycznej jest utworzenie danej kolekcji (np. kolekcji wartoci cakowitych)
i potem zastpienie nazwy typu int uoglnionym symbolem typu, na
przykad T.
Mianowicie:
private int data;

ma przyj posta:
private T data;

// T to generyczny parametr typowy

Generyczny parametr typowy (tutaj T) jest cile definiowany dopiero


w momencie tworzenia klasy kolekcji przez parametr typu wystpujcy
midzy nawiasami ktowymi (< >):
public class Node<T>

Tak definiuje si nowy typ wze Node typu T; jeli w definicji obiektu
miejsce T zajmie int, dla rodowiska wykonawczego obiekt ten bdzie typu
wze Node typu int (analogicznie tworzy si wzy dowolnego innego
typu).
28

Rozdzia 1: C# 2.0

WSK A ZWK A
Wielu programistw stosuje wygodny w zapisie symbol typu T, ale
Microsoft zaleca stosowanie duszych, bardziej opisowych symboli
(na przykad Node<DocumentType>).

Listing 1.2 prezentuje przykadowy kod tworzcy list wzw typu T i nastpnie powoujcy do ycia dwa egzemplarze takich list dla rnych waciwych obiektw wzw.
Listing 1.2. Wasna 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 skadowych
private T data;
private Node<T> next = null;
// konstruktor
public Node(T data)
{
this.data = data;
}
// waciwoci
public T Data { get { return this.data; } }
public Node<T> Next
{
get { return this.next; }
}
// metody
public void Append(Node<T> newNode)

Wasna 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 skadowe
private Node<T> headNode = null;
// waciwoci
// indekser
public T this[int index]
{
get
{
int ctr = 0;
Node<T> node = headNode;
while (node !=
{
if (ctr ==
{
return
}
else
{
node =
}

null && ctr <= index)


index)
node.Data;

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("Mynarz"));
pilgrims.Add(new Pilgrim("Szeryf"));
pilgrims.Add(new Pilgrim("Kucharz"));
Console.WriteLine("Pielgrzymi: " + pilgrims);
Console.WriteLine("Czwarta liczba to " + myLinkedList[3]);

Wasna 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, Mynarz, Szeryf, Kucharz
Czwarta liczba to 3
Drugi pielgrzym to Mynarz

Jak to dziaa?
Ot wanie powstaa generyczna kolekcja lista. Kolekcja podlegajca
typowaniu mona deklarowa jej egzemplarze dla dowolnych typw.
Najatwiej skonstruowa list tego rodzaju, bazujc na implementacji zwykej (nie generycznej) listy dla konkretnego typu danych. Ten konkretny
przykad przy definiowaniu listy definiuje list generyczn, ktrej czoo
(wze czoowy) jest inicjalizowany wartoci 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 nastpnie zastpi
nazw typu
parametrem typu
generycznego <T>.

32

Kiedy do listy dodawane s elementy, tworzony jest obiekt nowego wza; w obliczu braku wza czoowego w nowy wze peni rol czoa listy;
jeli czoo listy jest ju zajte, nowy wze jest doczany do listy metod
Append wza czoowego.
Przy dodawaniu nowego wza do listy nastpuje przegld wzw skadajcych si na list; szukany jest wze ostatni, czyli taki, ktry we wskaniku nastpnego wza (pole next) przechowuje warto pust. Po jego
odnalezieniu do pola next przypisywana jest referencja nowego wza.
Lista LinkedList zostaa celowo zadeklarowana z identycznym parametrem typu jak Node. Obie stosuj w roli symbolu generycznego parametru
typu t sam liter (T), dziki czemu kompilator wie, e typ podstawiany
w miejsce T w definicji LinkedList ma rwnie zastpowa T w definicji
Node. To w zasadzie oczywiste: przecie wzami listy wartoci cakowitych powinny by wzy wartoci cakowitych.

Rozdzia 1: C# 2.0

A co
ze stosowaniem typw generycznych w innych strukturach? Czy to
moliwe?
Owszem, typy generyczne mona stosowa w strukturach, interfejsach,
delegacjach, a nawet w metodach.

Wicej informacji
Szczegowych informacji o tworzeniu wasnych klas z udziaem typw
generycznych naley szuka w pomocy MSDN pod hasem Topic: Generics, ewentualnie we wspomnianym ju artykule z witryny ONDotnet.com
(OReilly), http://www.ondotnet.com/pub/a/dotnet/2004/05/17/liberty.html.
Warto te zapozna si z projektem majcym na celu gromadzenie i udostpnianie bibliotek klas dla platformy .NET, o ktrym wicej na stronie
Wintellect (http://www.wintellect.com/powercollections/).

Implementacja interfejsw kolekcji


W platformie .NET 2.0 poza generycznymi klasami kolekcji znajduje si
rwnie zestaw generycznych interfejsw pozwalajcych na tworzenie typowanych kolekcji cechujcych si penym zakresem funkcji wczeniejszych, niegenerycznych klas kolekcji z biblioteki platformy .NET 1.x. Interfejsy te zostay zebrane w przestrzeni nazw System.Collections.Generic,
do ktrej trafio rwnie sporo interfejsw pomocniczych, w tym IComparable<T> sucy do porwnywania dwch obiektw typu T (niekoniecznie
przechowywanych w kolekcji).
Wszystko to pozwala na proste utworzenie kolekcji w postaci uporzdkowanej listy elementw wystarczy dla kadego typu T danych przechowywanego w elementach listy zaimplementowa interfejs IComparable<T>
i uczyni obiekt Node odpowiedzialnym za wstawianie nowych wzw
(obiektw Node) w odpowiednie miejsca listy tak aby zachowa uporzdkowanie wzw.

Implementacja interfejsw kolekcji

33

Jak to zrobi?
Interfejs IComparable jest ju zaimplementowany dla typu Integer; w prosty sposb mona tak implementacj utworzy rwnie dla klasy Pilgrim. Trzeba jedynie zmodyfikowa definicj klasy Pilgrim, tak aby sygnalizowaa implementacj interfejsu IComparable<T>:
public class Pilgrim: IComparable<Pilgrim>

Zakres typw
waciwych dla typu
generycznego mona
ogranicza.

Rzecz jasna trzeba te koniecznie zaimplementowa wymagane metody


interfejsu: CompareTo i Equals. Interfejs jest typowany, wic obiekty przekazywane do tych metod bd typu Pilgrim:
public int CompareTo(Pilgrim rhs)
public bool Equals(Pilgrim rhs)

Teraz wystarczy ju zmieni logik kodu odpowiedzialnego za dodawanie nowych wzw do listy. Tym razem zamiast szuka ostatniego wza listy, naleaoby szuka odpowiedniego miejsca pomidzy istniejcymi
wzami takiego, eby po wstawieniu nowego wza zachowane zostao uporzdkowanie listy; miejsce to wyszukuje si na bazie implementacji CompareTo.
Przede wszystkim typ przechowywany w wle musi implementowa interfejs IComparable. Osiga si to przy uyciu sowa kluczowego where:
public class Node<T> : IComparable<Node<T>> where T:IComparable<T>

Powyszy wiersz kodu deklaruje klas wza Node typu T implementujc


interfejs IComparable (dla wzw Node typu T) i ograniczon do przechowywania w wzach takich typw danych T, ktre implementuj interfejs
IComparable. Jeli w wle sprbujemy umieci obiekt innego typu ni
implementujcy IComparable, prba kompilacji kodu sprowokuje bd.
Przy samym wstawianiu wza do listy trzeba rozway i obsuy przypadek szczeglny, kiedy to nowy wze bdzie mniejszy od wza stanowicego czoo listy. Wida to na listingu 1.3 (rnice w stosunku do poprzedniej implementacji kolekcji zostay wyrnione pogrubieniem).
Listing 1.3. Implementacja interfejsw 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;
}
}

// wze musi implementowa interfejs IComparable dla wzw Node typu T


// wze moe przechowywa jedynie te typy T, ktre implementuj
IComparable
// (warunek okrelany sowem kluczowym where).
public class Node<T> : IComparable<Node<T>> where T:IComparable<T>
{
// pola skadowe
private T data;
private Node<T> next = null;
private Node<T> prev = null;
// konstruktor
public Node(T data)
{
this.data = data;
}
// waciwoci
public T Data { get { return this.data; } }
public Node<T> Next
{
get { return this.next; }
}
public int CompareTo(Node<T> rhs)
{

Implementacja interfejsw kolekcji

35

// dziaa 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 wze
// biecy
{
newNode.next = this; // wskanik next ustawiany na wze
// biecy
// jeli istnieje wze poprzedni, powinien od tego momentu
// wskazywa polem next nowy wze
if (this.prev != null)
{
this.prev.next = newNode;
newNode.prev = this.prev;
}
// wskanik poprzednika wza biecego ma wskazywa nowy
// wze
this.prev = newNode;
// zwrcenie referencji nowego wza, jeli sta si nowym
// czoem listy
return newNode;
}
else
{

// wstawienie za wze biecy


// jeli biecy nie jest ostatnim, ca operacj przejmuje
// nastpny
if (this.next != null)
{
this.next.Add(newNode);
}
// brak nastpnego wza nowy wze trzeba skojarzy
// z polem next biecego;
// a w polu prev nowego wstawi referencj do biecego
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 skadowych
private Node<T> headNode = null;

// waciwoci
// indekser
public T this[int index]
{
get
{
int ctr = 0;
Node<T> node = headNode;
while (node !=
{
if (ctr ==
{
return
}
else
{
node =
}

null && ctr <= index)


index)
node.Data;

node.Next;

++ctr;
} // koniec while
throw new ArgumentOutOfRangeException();
} // koniec get
// koniec indeksera

// konstruktor
public SortedLinkedList()

Implementacja interfejsw 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 wejcia
static void Main(string[] args)
{
SortedLinkedList<int> mySortedLinkedList = new
SortedLinkedList<int>();
Random rand = new Random();
Console.Write("Wypenianie: ");
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("Mynarz"));
pilgrims.Add(new Pilgrim("Szeryf"));
pilgrims.Add(new Pilgrim("Kucharz"));
pilgrims.Add(new Pilgrim("Adwokat"));

38

Rozdzia 1: C# 2.0

Console.WriteLine("\nPobieranie 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("Zawd 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:
Wypenianie: 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, Mynarz, Rycerz, Szeryf
Czwarta liczba to 2
Trzeci pielgrzym to Mynarz

Jak to dziaa?
Klasa Pilgrim zostaa uzupeniona o implementacj interfejsu generycznego IComparable. Sama lista nie zmienia si ani na jot, ale ju klasa
wzw listy (Node) przesza powan metamorfoz, dziki ktrej wstawianie wzw do listy odbywa si z zachowaniem ich wzajemnego uporzdkowania.
Po pierwsze, klasa Node zostaa oznaczona jako klasa implementujca interfejs IComparable i ograniczona do przechowywania obiektw takich typw, ktre rwnie implementuj w interfejs:
public class Node<T> : IComparable<Node<T>> where T:IComparable<T>

Implementacja interfejsw kolekcji

39

Po drugie, w wle obok pola z referencj do nastpnego wza pojawio si


pole referencji do wza poprzedniego (czynic 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 dziaanie sprowadza si do oddelegowania porwnania do analogicznych metod obiektu przechowywanego a wiadomo,
e obiekty te rwnie implementuj interfejs IComparable:
public int CompareTo(Node<T> rhs)
{
// dziaa z racji ograniczenia (where T:IComparable<T>)
return data.CompareTo(rhs.data);
}

A co
z wymaganiem implementacji interfejsu IComparable? Dlaczego musiaa go implementowa klasa Pilgrim i Node, a ju sama klasa kolekcji
(tu SortedLinkedList) nie?
Aby to wyjani, trzeba przypomnie, e i Pilgrim, i Node to obiekty danych podlegajce operacjom porwnania; sama lista jako oglniejsza struktura nie jest za nigdy porwnywana z innymi listami. Uporzdkowanie
wzw listy odbywa si przez ustalanie ich kolejnoci na bazie porwna; nigdzie nie zachodzi za porwnanie dwch list i sprawdzanie, ktra
z nich jest wiksza.
z przekazywaniem typw generycznych do metod? Czy to moliwe?
Tak, przekazywanie typw generycznych do metod jest dozwolone, pod
warunkiem e chodzi o metody generyczne. Przykadem moe by poniszy kod zaczerpnity z listingu 1.3, wywietlajcy zawarto listy liczb
i listy pielgrzymw:
Console.WriteLine("Liczby: " + myLinkedList);
Console.WriteLine("Pielgrzymi: " + pilgrims);

Nic nie stoi na przeszkodzie, aby utworzy metod przyjmujc za porednictwem argumentu tak list i wywietlajc jej elementy (albo w dowolny
inny sposb manipulujc 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 wywoaniu takiej metody naley ucili typy:


DisplayList<int>("Liczby", myLinkedList);
DisplayList<Pilgrim>("Pielgrzymi", pilgrims);

WSK A ZWK A
Kompilator ma moliwo wnioskowania o typie metody na podstawie
typw argumentw, wic poprzednie dwa wywoania mona zapisa
rwnie tak:
DisplayList("Liczby", myLinkedList);
DisplayList("Pielgrzymi", pilgrims);

Wicej informacji
Zawarto przestrzeni nazw Generic jest wyczerpujco omawiana w dokumentacji MSDN wystarczy j przeszuka pod ktem hasa Systems.Collections.Generic. Polecam te artyku traktujcy o typach generycznych, publikowany w serwisie ONDotnet.com (OReilly) (http://www.
ondotnet.com/pub/a/dotnet/2004/05/17/liberty.html).

Stosowanie iteratorw generycznych


W poprzednio prezentowanych przykadach nie dao si przeglda listy
Pilgrims w ptli foreach. Gdyby w programie z listingu 1.3 umieci poniszy kod:
foreach (Pilgrim p in pilgrims)
{
Console.WriteLine("Zawd pielgrzyma to " + p.ToString());
}

prba kompilacji programu doprowadziaby do zgoszenia nastpujcego


bdu:
Error
1
foreach statement cannot operate on variables of type
'ImplementingGenericInterfaces.LinkedList <ImplementingGenericInterfaces.
Pilgrim>' because 'ImplementingGenericInterfaces.LinkedList

Stosowanie iteratorw generycznych

Dodawanie
iteratorw pozwala
klientom przeglda
kolekcje w ptlach
foreach.

41

<ImplementingGenericInterfaces.Pilgrim>' does not contain a public


definition for 'GetEnumerator'

W poprzednich wersjach C# implementowanie metody GetEnumerator


byo do uciliwe i skomplikowane; w C# 2.0 cay zabieg zosta znacznie uproszczony.

Jak to zrobi?
Aby uproci sobie tworzenie iteratorw, naleaoby przede wszystkim
uproci klasy Pilgrim i LinkedList. Klasa kolekcji LinkedList powinna
te zaniecha stosowania wzw i przechowywa elementy listy w tablicy o staym rozmiarze (taka tablica to najprostszy moliwy typowany
kontener-kolekcja). Kolekcja zostanie wic list jedynie z nazwy! To pozwoli si jednak skupi na implementacji interfejsu IEnumerable, prezentowanej 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 staym rozmiarze
T[] myArray;
// konstruktor przyjmuje tablic i umieszcza jej elementy we wasnej
// tablicy
public NotReallyALinkedList(T[] members)
{
myArray = members;
}
// implementacja gwnej metody interfejsu IEnumerable
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
foreach (T t in this.myArray)
{
yield return t;
}
}
// wymagana implementacja rwnie dla wersji niegenerycznej
System.Collections.IEnumerator System.Collections.IEnumerable.
GetEnumerator()
{
throw new NotImplementedException();
}
}

class Program
{
static void Main(string[] args)
{
// rczne tworzenie tablicy obiektw klasy Pilgrim
Pilgrim[] pilgrims = new Pilgrim[5];
pilgrims[0] = new Pilgrim("Rycerz");
pilgrims[1] = new Pilgrim("Mynarz");
pilgrims[2] = new Pilgrim("Szeryf");
pilgrims[3] = new Pilgrim("Kucharz");
pilgrims[4] = new Pilgrim("Adwokat");
// utworzenie listy, przekazanie tablicy elementw
NotReallyALinkedList<Pilgrim> pilgrimCollection =
new NotReallyALinkedList<Pilgrim>(pilgrims);
// przegldanie elementw listy
foreach (Pilgrim p in pilgrimCollection)
{
Console.WriteLine(p);
}
}
}
}

Stosowanie iteratorw generycznych

43

Wynik:
Rycerz
Mynarz
Szeryf
Kucharz
Adwokat

Jak to dziaa?
W tym przykadzie lista zostaa znacznie uproszczona elementy listy
zamiast w przydzielanych dynamicznie wzach lduj w statycznej tablicy (co w zasadzie dyskwalifikuje t implementacj jako list). Poniewa
owa pseudo-lista daje si jednak przeglda z uyciem iteratorw, przechowywana w niej kolekcja obiektw Pilgrim daje si przeglda w ptli
foreach.
W obliczu zapisu:
foreach (Pilgrim p in pilgrimCollection)

Kade zastosowanie
ptli foreach
jest przez
kompilator
tumaczone
na wywoanie
metody
GetEnumerator.

kompilator jzyka C# wywouje na rzecz obiektu kolekcji metod GetEnumerator. Rozwinicie takiej ptli jest wewntrznie implementowane mniej
wicej 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 waciwoci Current. Trzeba jedynie zastosowa
nowe sowo kluczowe C# yield:
WSK A ZWK A
Sowo kluczowe yield mona stosowa jedynie w blokach iteracji.
Sowo to albo odnosi si do wartoci obiektu enumeratora, albo sygnalizuje koniec iteracji:
yield return wyraenie;
yield break;

Gdyby wej do wntrza ptli foreach za pomoc debugera, okazaoby si,


e za kadym razem nastpuje tam wywoanie na rzecz obiektu kolekcji
44

Rozdzia 1: C# 2.0

metody GetEnumerator; we wntrzu tej metody nastpuje za wielokrotne


zwracanie wartoci do ptli foreach za porednictwem kolejnych wystpie sowa kluczowego yield1.

A co
z implementacj metody GetEnumerator dla bardziej zoonych struktur
danych, na przykad dla prawdziwej listy?
To wanie bdzie przedmiotem nastpnego wiczenia.

Wicej informacji
Poruszone zagadnienie jest wyczerpujco omawiane w obszernym artykule z biblioteki MSDN, zatytuowanym Iterators (C#).

Implementacja GetEnumerator
dla zoonych struktur danych
Aby da pierwotnej licie LinkedList moliwo przegldania elementw za porednictwem iteratora, trzeba zaimplementowa interfejs IEnumerable<T> dla samej klasy listy (LinkedList) i klasy wzw 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 okazao si, e interfejs IEnumerable
wymaga zasadniczo implementacji tylko jednej metody GetEnumerator. Implementacj t dla bardziej zoonej struktury danych, jak jest
choby lista, prezentuje listing 1.5 (zmiany wzgldem kodu z listingu 1.3
zostay wyrnione pogrubieniem).

Sowo yield dziaa jak instrukcja return uzupeniona o pami stanu suy do iteracyjnego
zwracania kolejnych elementw sekwencji przyp. tum.
Implementacja GetEnumerator dla zoonych 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;
}
}

// wze musi by implementowa interfejs IComparable dla wzw Node


// typu T teraz dodatkowo implementuje interfejs IEnumerable do uytku
// w ptlach foreach
public class Node<T> : IComparable<Node<T>>, IEnumerable<Node<T>> where
T:IComparable<T>
{
// pola skadowe
private T data;
private Node<T> next = null;
private Node<T> prev = null;
// konstruktor
public Node(T data)
{
this.data = data;
}
// waciwoci
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 wze
// biecy
{
newNode.next = this; // wskanik next ustawiany na wze
// biecy jeli istnieje wze poprzedni, powinien od tego
// momentu wskazywa polem next nowy wze
if (this.prev != null)
{
this.prev.next = newNode;
newNode.prev = this.prev;
}
// wskanik poprzednika wza biecego ma wskazywa nowy
// wze
this.prev = newNode;
// zwrcenie referencji nowego wza, jeli sta si nowym
// czoem listy
return newNode;
}
else
{

// wstawienie za wze biecy


// jeli biecy nie jest ostatnim, ca operacj przejmuje
// nastpny
if (this.next != null)
{
this.next.Add(newNode);
}
// brak nastpnego wza nowy wze trzeba skojarzy
// z polem next biecego;
// a w polu prev nowego wstawi referencj do biecego
else
{
this.next = newNode;
newNode.prev = this;
}

Implementacja GetEnumerator dla zoonych 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;
// przegldanie wszystkich wzw listy,
// zwracanie (yield) kolejnych wzw
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 pozwalajca na stosowanie


// klasy LinkedList w ptlach foreach
public class LinkedList<T> : IEnumerable<T> where T : IComparable<T>
{
// pola skadowych
private Node<T> headNode = null;

// waciwoci
// indekser

48

Rozdzia 1: C# 2.0

public T this[int index]


{
get
{
int ctr = 0;
Node<T> node = headNode;
while (node !=
{
if (ctr ==
{
return
}
else
{
node =
}

null && ctr <= index)


index)
node.Data;

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 zoonych struktur danych

49

// Implementacja wymaganej metody IEnumerable


// przegldajca wzy (rwnie implementujce ten interfejs)
// i zwracajca (yield) dane zwrcone z wza
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 wejcia
static void Main(string[] args)
{
LinkedList<Pilgrim> pilgrims = new LinkedList<Pilgrim>();
pilgrims.Add(new Pilgrim("Rycerz"));
pilgrims.Add(new Pilgrim("Mynarz"));
pilgrims.Add(new Pilgrim("Szeryf"));
pilgrims.Add(new Pilgrim("Kucharz"));
pilgrims.Add(new Pilgrim("Adwokat"));
DisplayList<Pilgrim>("Pilgrims", pilgrims);
Console.WriteLine("Przegldanie listy pielgrzymw...");
// teraz lista daje si przeglda, wic mona j
// zastosowa w ptli foreach
foreach (Pilgrim p in pilgrims)
{
Console.WriteLine("Zawd pielgrzyma to " + p.ToString());
}
}
}
}

50

Rozdzia 1: C# 2.0

Wynik:
Pielgrzymi: Adwokat, Kucharz, Mynarz, Rycerz, Szeryf
Przegldanie listy pielgrzymw...
Zawd pielgrzyma to Adwokat
Zawd pielgrzyma to Kucharz
Zawd pielgrzyma to Mynarz
Zawd pielgrzyma to Rycerz
Zawd pielgrzyma to Szeryf

Jak to dziaa?
Lista implementuje teraz enumerator; implementacja polega na zainicjowaniu ptli foreach dla czoowego wza listy (klasa wza rwnie implementuje interfejs IEnumerable). Implementacja zwraca obiekt danych
zwrcony przez wze:
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,


ktra we wasnej implementacji metody GetEnumerator rwnie posuguje
si sowem 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 wza biecego, po czym


nastpuje rozpoczcie ptli do...while. Ptla taka zostanie wykonana przynajmniej jednokrotnie. W ptli nastpuje przepisanie wartoci nextNode do
returnNode i prba odwoania si do nastpnego wza listy (wskazywanego polem next). Nastpna instrukcja ptli zwraca do wywoujcego (za
pomoc sowa yield) wze zapamitany przed chwil w returnNode.
Kiedy w nastpnym kroku iteracji nastpi powrt do ptli, zostanie ona
Implementacja GetEnumerator dla zoonych struktur danych

W miejsce instrukcji
yield kompilator
automatycznie
generuje
zagniedon
implementacj
IEnumerator.
Zapamituje tam
stan iteracji;
programista musi
jedynie wskaza
wartoci
do zwrcenia
w kolejnych krokach
iteracji.

51

wznowiona od tego miejsca; cao bdzie powtarzana dopty, dopki pole


next ktrego z kolejnych wzw nie okae si puste, a tym samym
wze bdzie ostatnim wzem listy.

A co
znaczy wystpujce w implementacji LinkedList danie przejrzenia
(foreach) elementw Node<T> w headNode? Przecie headNode to nie lista,
a jeden z jej wzw (konkretnie wze czoowy)?
Ot headNode to faktycznie czoowy wze listy. Poniewa jednak klasa
Node implementuje interfejs IEnumerable, dla potrzeb iteracji wze zachowuje si jak kolekcja. Cho brzmi to niedorzecznie, jest cakiem uzasadnione, bo wze w istocie przejawia pewne cechy kolekcji, w tym przynajmniej sensie, e potrafi wskaza nastpny element kolekcji (nastpny
wze listy). Cao mona by przeprojektowa tak, eby wzy nie byy
tak sprytne, za to sama lista bya sprytniejsza wtedy zadanie
realizacji iteracji spoczywaoby w caoci na licie i ta nie delegowaaby
zadania do wzw.

Wicej informacji
O interfejsie IEnumerable<T> mona si sporo dowiedzie z plikw pomocy
MSDN dla hasa Topic: IEnumerable<T>.

Upraszczanie kodu metody anonimowe


Metody anonimowe pozwalaj na definiowanie nienazwanych blokw kodu
rozwijanych w miejscu wywoania. Z metod anonimowych mona korzysta wszdzie tam, gdzie dozwolone s delegacje. Za ich porednictwem
mona na przykad znakomicie uproci rejestrowanie procedur obsugi
zdarze.

Jak to zrobi?
Zastosowania metod anonimowych najlepiej zilustrowa przykadem:
1.

52

Utworzy w Visual Studio .NET 2005 now aplikacj okienkow i nada


jej nazw AnonymousMethods.

Rozdzia 1: C# 2.0

2.

Przecign na domylny formularz okna dwie kontrolki: etykiet oraz


przycisk (nie warto zajmowa si przydzielaniem im specjalnych nazw).

3.

Dwukrotnie klikn przycisk lewym klawiszem myszy. Wywietlone


zostanie okno edytora, w ktrym naley umieci poniszy kod:
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "Do widzenia!";
}

4.

Metody anonimowe
pozwalaj na
stosowanie blokw
kodu w roli
parametrw.

Uruchomi aplikacj. Kliknicie przycisku powinno zmienia tre etykiety na Do widzenia!.

Wszystko wietnie. Ale jest tu pewien ukrywany przed programist narzut.


Ot powyszy kod wymaga rejestrowania delegacji (wyrcza nas w tym
stosowny kreator Visual Studio 2005), a obsuga kliknicia przycisku wymaga zdefiniowania nowej metody. Cao mona za uproci stosujc
metody anonimowe.
Aby sprawdzi, jak faktycznie rejestrowana jest metoda obsugi zdarzenia
kliknicia przycisku, naley klikn w IDE przycisk Show All Files (prezentowany na rysunku 1.1).

Rysunek 1.1. Przycisk Show All Files

Teraz naleaoby otworzy plik Form1.Designer.cs i odszuka w nim


delegacj button1.Click:
this.button1.Click += new System.EventHandler(this.button1_Click);

Nie powinno si rcznie modyfikowa tego kodu, ale mona wyeliminowa


ten wiersz inaczej wracajc do formularza i klikajc w oknie waciwoci (Properties) ikon byskawicy, wywoujc procedury obsugi zdarze. Tam mona usun procedur obsugi zarejestrowan dla zdarzenia Click.

Upraszczanie kodu metody anonimowe

53

Po powrocie do kodu Form1.Designer.cs okae si, e procedura obsugi


zdarzenia button1.Click nie jest w ogle zarejestrowana!
Teraz naley otworzy do edycji plik Form1.cs i doda do konstruktora
(za wywoaniem InitializeComponent()) poniszy wiersz:
this.button1.Click += delegate { label1.Text = "Do widzenia!" };

Dziki temu mona ju pozby si dodatkowej metody procedury obsugi


zdarzenia mona j usun albo oznaczy jako komentarz:
// private void button1_Click(object sender, EventArgs e)
// {
//
label1.Text = "Do widzenia!";
// }

Dziaanie metody anonimowej mona sprawdzi, ponownie uruchamiajc


aplikacj. Powinna zachowywa si dokadnie tak, jak poprzednio.
Jak wida, zamiast rejestrowa delegacj wywoujc metod obsugi zdarzenia, mona wskaza metod anonimow nienazwany, rozwijany
w miejscu wywoania blok kodu.

A co
z innymi zastosowaniami metod anonimowych? Czy mona je stosowa we wasnym kodzie?
aden problem. Metody anonimowe mona stosowa nie tylko przy inicjalizowaniu delegacji, ale i wszdzie tam, gdzie dozwolone jest uycie delegacji we wszystkich tych miejscach mona przekaza nienazwany
blok kodu.
jeli w takim bloku kodu nastpi odwoanie do zmiennej lokalnej?
Dobre pytanie. To do mylca sytuacja i atwo tu o pomyk, zwaszcza
kiedy nie jest si w peni wiadomym konsekwencji takich odwoa. Ot
C# pozwala na wciganie zmiennych lokalnych do zasigu anonimowego
bloku kodu; odwoania do nich s wykonywane w momencie wykonania
owego bloku kodu. Moe to prowokowa rozmaite efekty uboczne choby
podtrzymywanie przy yciu obiektw, ktrymi inaczej ju dawno zaopiekowaby si mechanizm zbierania nieuytkw.

54

Rozdzia 1: C# 2.0

z usuwaniem procedury obsugi dla zdarzenia, dodanej za pomoc


delegacji anonimowej; da si to zrobi?
Jeli procedura obsugi zdarzenia zostaa okrelona delegacj anonimow, nie mona jej usun; dlatego delegacje anonimowe powinno si stosowa jedynie dla tych procedur obsugi, ktre maj by trwale skojarzone
z danymi zdarzeniami.
Ponadto mona stosowa delegacje anonimowe rwnie w innych dziedzinach, choby przy implementowaniu metody List.Find przyjmujcej delegacj opisujc kryteria wyszukiwania.

Wicej informacji
W zasobach MSDN mona znale wietny artyku traktujcy o metodach
anonimowych. Mowa o artykule Create Elegant Code with Anonymous
Methods, Iterators and Partial Classes autorstwa Juvala Lowyego. Warto
te zapozna si z artykuem z serwisu ONDotnet.com (OReilly), publikowanym pod adresem http://www.ondotnet.com/pub/a/dotnet/2004/04/05/
csharpwhidbeypt1.html.

Ukrywanie kodu typy czciowe


W poprzednich wersjach C# cao definicji klasy musiaa by umieszczana w pojedynczym pliku. Teraz dziki sowu kluczowemu partial mona
dzieli klas na czci przechowywane w wikszej liczbie plikw. Moliwo ta jest cenna z dwch wzgldw:

Sowo kluczowe
partial pozwala
na podzia definicji
klasy na wiele plikw.

W zespole programistycznym mona przeprowadzi podzia polegajcy


na przypisaniu rnych programistw do prac nad rnymi czciami
klasy.
Visual Studio 2005 moe w ten sposb oddziela kod generowany automatycznie od kodu wasnego programisty.

Ukrywanie kodu typy czciowe

55

Jak to zrobi?
Praktyczne zastosowanie typw czciowych mona zilustrowa na bazie
poprzedniego przykadu (AnonymousMethods). Spjrzmy 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!";
}

Sowo kluczowe partial sygnalizuje, e kod zamieszczony w tym pliku


niekoniecznie reprezentuje cao definicji klasy. Co zreszt zgadza si
z nasz wiedz, bo przecie w poprzednim podrozdziale zagldalimy do
drugiego pliku Form1.Designer.cs zawierajcego 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 wzite razem;
podzia klasy pozwala na wyodrbnienie kodu tworzonego przez programist i kodu generowanego automatycznie przez rne mechanizmy rodowiska programistycznego. Czyni to projekt przejrzystszym i prostszym.
Stosujc klasy czciowe, trzeba mie wiadomo kilku aspektw:
Wszystkie czciowe definicje typw musz zawiera sowo kluczowe
partial i musz nalee do tej samej przestrzeni nazw oraz tego samego moduu i podzespou.
Modyfikator partial moe wystpowa jedynie przed sowami kluczowymi class, inerface i struct.
We wszystkich definicjach czciowych naley uzgodni modyfikatory dostpu do skadowych (public, private itd.).

A co
ze stosowaniem klas czciowych we wasnych projektach?
Microsoft sugeruje, e klasy czciowe mog przyda si programistom
pracujcym w zespoach mog wtedy podzieli si prac nad klasami.
Wci jednak za wczenie, aby stwierdzi, czy taka praktyka si przyjmie; osobicie uwaam, e kada klasa tak rozbudowana, aby jej rozmiar uzasadnia podzia pracy, powinna po prostu zosta podzielona na
mniejsze klasy. Na razie wic gwnym zastosowaniem typw czciowych jest upychanie po ktach rozmaitych tworw generowanych przez
kreatory rodowiska programistycznego Visual Studio 2005.

Wicej informacji
Dobry artyku o typach czciowych mona znale w archiwach witryny
Developer.com. Publikowany jest pod adresem http://www.developer.com/
net/net/article.php/2232061.

Ukrywanie kodu typy czciowe

57

Tworzenie klas statycznych


W C# 2.0
klas mona
zadeklarowa
jako statyczn,
sygnalizujc, e ma
ona suy jedynie
w roli zasobnika
zestawu statycznych
metod
narzdziowych.

W nowej wersji jzyka C# jako statyczne mona deklarowa nie tylko


metody, ale rwnie cae klasy.
Zadaniem klasy statycznej jest udostpnianie zestawu statycznych metod
pomocniczych ujtych w zasigu nazwy klasy jak w klasie Convert
z biblioteki Framework Class Library.

Jak to zrobi?
Utworzenie klasy statycznej polega na poprzedzeniu nazwy klasy sowem
kluczowym static i upewnieniu si, e sama definicja klasy spenia podane
wyej kryterium co do metod. Trzeba te pamita o dodatkowych ograniczeniach nakadanych na klasy statyczne:
Klasy statyczne mog zawiera wycznie statyczne skadowe.
Nie wolno tworzy egzemplarza klasy statycznej.
Wszystkie klasy statyczne s klasami finalnymi (bezpodnymi) nie
mona wyprowadza z nich klas pochodnych.
Oprcz tego klasa statyczna nie moe zawiera konstruktora. Waciwe
zastosowanie klasy statycznej ilustruje kod z listingu 1.6.
Listing 1.6. Stosowanie klas statycznych
#region Using directives
using System;
#endregion
namespace StaticClass
{
public static class CupConversions
{
public static int CupToOz(int cups)
{
return cups * 8; // szklanka to 8 uncji pynu
}
public static double CupToPint(double cups)
{
return cups * 0.5; // szklanka to p pinty

58

Rozdzia 1: C# 2.0

}
public static double CupToMil(double cups)
{
return cups * 237; // 237 mililitrw to jedna szklanka
}
public static double CupToPeck(double cups)
{
return cups / 32; // 8 kwart to 1 peck
}
public static double CupToBushel(double cups)
{
return cups / 128; // 4 pecki to 1 buszel
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Nie kady wie, e " +
"szklanka pynu da si przeliczy na: ");
Console.WriteLine(CupConversions.CupToOz(1) + " uncji");
Console.WriteLine(CupConversions.CupToPint(1) + " pint");
Console.WriteLine(CupConversions.CupToMil(1) + " mililitrw");
Console.WriteLine(CupConversions.CupToPeck(1) + " peckw");
Console.WriteLine(CupConversions.CupToBushel(1) + " buszli");
}
}
}

Wynik:
Nie kady wie, e szklanka pynu da si przeliczy na:
8 uncji
0.5 pint
237 mililitrw
0.03125 peckw
0.0078125 buszli

Gwna metoda klasy Program wywouje statyczne metody klasy CupConversions. Poniewa klasa ta istnieje tylko jako zasobnik metod narzdziowych
(pomocniczych), a obiekt klasy nie jest wcale potrzebny, klasa CupConversion moga zosta zdefiniowana jako statyczna.

A co
z polami i waciwociami? Czy klasa statyczna moe mie takie
skadowe?
Tworzenie klas statycznych

59

Owszem, moe, ale wszelkie skadowe (metody, pola i waciwoci) powinny by rwnie statyczne.

Wicej informacji
Klasy statyczne zostay omwione midzy innymi w znakomitym artykule
Erica Gunnersona. Artyku jest dostpny w zasobach MSDN pod adresem
http://blogs.msdn.com/ericgu/archive/2004/04/13/112274.aspx.

Wyraanie wartoci
pustych typami nullable
Typy nullable
pozwalaj na
wyrnienie wartoci
pustych rwnie
w takich typach
prostych, jak typy
cakowitoliczbowe
czy typy logiczne.

Nowe typy, tzw. typy nullable, to takie typy proste, ktrym mona przypisywa wartoci puste i wartoci te da si potem odrni od wartoci
z waciwej dziedziny typu. Moliwo ta okazuje si niezwykle uyteczna,
zwaszcza przy pracy z bazami danych, kiedy to zwracana warto pola
moe by wartoci pust; bez moliwoci przepisania takiego pola do
typu nullable nie mona by stwierdzi, czy pole reprezentuje warto pust,
czy moe zero (a to rnica!); nie mona by te wyrazi wartoci logicznej, ktra nie reprezentuje jeszcze ani prawdy, ani faszu.

Jak to zrobi?
Typ mogcy przyjmowa wartoci puste deklaruje si nastpujco:
System.Nullable<T> zmienna

A w obrbie zasigu typu czy metody generycznej mona stosowa zapis:


T? zmienna

Dwie zmienne przechowujce wartoci cakowite z wyrnion wartoci


pust mona wic zadeklarowa tak:
System.Nullable<int> myNullableInt;
int? myOtherNullableInt;

Warto pust zmiennej typu nullable wykrywa si dwojako. Mona zastosowa konstrukcj tak:
if (myNullableInt.HasValue)

60

Rozdzia 1: C# 2.0

albo tak:
if (myNullableInt != null)

Obie zwrc true, jeli zmienna myNullableInt bdzie zawieraa jak


warto, bd false, kiedy zmienna bdzie pusta. Zastosowanie typw
nullable ilustruje listing 1.7.
Listing 1.7. Typy nullable
using System;
namespace NullableTypes
{
public class Dog
{
private int age;
public Dog(int age)
{
this.age = age;
}
}
class Program
{
static void Main(string[] args)
{
int? myNullableInt = 25;
double? myNullableDouble = 3.14159;
bool? myNullableBool = null; // ani tak, ani nie
// string? myNullableString = "Ahoj"; // niedozwolone
// Dog? myNullableDog = new Dog(3); // niedozwolone
if (myNullableInt.HasValue)
{
Console.WriteLine("myNullableInt to " +
myNullableInt.Value);
}
else
{
Console.WriteLine("myNullableInt ma warto pust!");
}
if (myNullableDouble != null)
{
Console.WriteLine("myNullableDouble: " + myNullableDouble);
}
else
{
Console.WriteLine("myNullableDouble ma warto pust!");
}

Wyraanie wartoci pustych typami nullable

61

if ( myNullableBool != null )
{
Console.WriteLine("myNullableBool: " + myNullableBool);
}
else
{
Console.WriteLine("myNullableBool ma warto pust!");
}

myNullableInt = null;

// przypisanie wartoci pustej


// do zmiennej cakowitej
// int a = myNullableInt; // bd kompilacji
int b;
try
{
b = (int)myNullableInt;

// sprowokuje wyjtek,
// kiedy x bdzie puste
Console.WriteLine("b: " + b);

}
catch (System.Exception e)
{
Console.WriteLine("Wyjtek! " + e.Message);
}
int c = myNullableInt ?? -1; // przypisze 1, kiedy x bdzie
// puste
Console.WriteLine("c: {0}", c);
// ostronie z zaoeniami jeli ktrykolwiek z operandw
// bdzie pusty, wszelkie porwnania dadz wynik false!
if (myNullableInt >= c)
{
Console.WriteLine("myNullableInt jest wiksze (rwne) od c");
}
else
{
Console.WriteLine("Czy myNullableInt jest mniejsze od c?");
}
}
}
}

Wynik:
myNullableInt to 25
myNullableDouble: 3.14159
myNullableBool ma warto pust!

62

Rozdzia 1: C# 2.0

Wyjtek! Nullable object must have a value.


C: -1
Czy myNullableInt jest mniejsze od c?

Jak to dziaa?
Skupmy si na metodzie Main. Nastpuje tu utworzenie piciu zmiennych
typw prostych, z wyrnieniem wartoci pustych:
int? myNullableInt = 25;
double? myNullableDouble = 3.14159;
bool? myNullableBool = null; // ani tak, ani nie
// string? myNullableString = "Ahoj";
// Dog? myNullableDog = new Dog(3);

Pierwsze trzy deklaracje s jak najbardziej poprawne, nie da si jednak


utworzy cigu majcego cech nullable ani nada tej cechy klasie (typowi
definiowanemu przez uytkownika); aby kod da si skompilowa, trzeba
byo oznaczy go jako komentarz.
Dalej nastpuje sprawdzenie wartoci kadej ze zmiennych, a konkretnie sprawdzenie, czy zmiennym tym zostay nadane waciwe, niepuste
wartoci (co powoduje ustawienie waciwoci HasValue na true). Jeli tak,
mona te wartoci wypisa (albo wprost, albo odwoujc si do waciwoci Value).

Wyjtkiem s
struktury, ktre
cho s typami
definiowanymi przez
uytkownika
mog by
wykorzystywane
jako typy nullable.

Potem do zmiennej myNullableInt przypisywana jest warto pusta (null):


myNullableInt = null;

W nastpnym wierszu miaa nastpi prba zadeklarowania zmiennej


typu int i przypisanie do niej wartoci myNullableInt, ale okazuje si to
niemoliwe. Takie przypisanie nie jest dozwolone, bo nie istnieje moliwo
niejawnej konwersji typu int nullable do zwykego typu int. Trzeba dokona jawnego rzutowania:
b = (int)myNullableInt;

Takie przypisanie da si ju skompilowa, ale w czasie wykonania naley


spodziewa si wyjtku, kiedy myNullableInt bdzie miao warto pust
(std te ujcie tej instrukcji w bloku chronionym try).

Wyraanie wartoci pustych typami nullable

Jeli ktrykolwiek
z operandw
ma warto pust,
operatory relacji
dadz w wyniku
false!

63

Drugi sposb przypisania wartoci typu nullable int do zmiennej zwykego


typu int to udostpnienie wartoci domylnej, wykorzystywanej w przypadku, kiedy to warto nullable int bdzie akurat pusta:
int c = myNullableInt ?? -1;

Powyszy wiersz mwi: zainicjalizuj c wartoci myNullableInt; chyba


e myNullableInt ma warto pust wtedy zainicjalizuj c wartoci -1.
Trzeba te pamita, e operatory porwna (>, <, <= i tak dalej) zwracaj warto false, jeli ktrykolwiek z operandw ma warto pust.
Wynikowi porwnania mona wic zaufa tylko wtedy, kiedy da ono true:
if (myNullableInt >= c)
{
Console.WriteLine("myNullableInt jest wiksze od (rwne) c");
}

O S TR Z E ENIE
Wyjtkiem jest operator ==, ktry da warto
gdy oba operandy bd puste.

true

rwnie wtedy,

Jeli na wyjciu wypisany zostanie komunikat myNullableInt jest wiksze


od (rwne) c, wiadomo na pewno, e ani c, ani myNullableInt nie miao
wartoci pustej oraz dodatkowo warto myNullableInt jest wiksza od
wartoci c (albo s one rwne). Jeli jednak porwnanie daje wynik false,
nie mona jednoznacznie stwierdzi relacji pomidzy wartociami:
else
{
Console.WriteLine("Czy myNullableInt jest mniejsze od c?");
}

Klauzula else moe zosta uruchomiona, kiedy okae si, e albo myNullableInt ma warto mniejsz od c, albo myNullableInt bd c jest
puste.

A co
z pustymi wartociami logicznymi? Jak wypadaj ich porwnania i jak
je odnie do trjwartociowych typw logicznych charakterystycznych
dla SQL?

64

Rozdzia 1: C# 2.0

Jzyk C# udostpnia dwa nowe operatory:


bool? operator &(bool? x, bool? y)
bool? operator |(bool? x, bool? y)

Dziaanie tych operatorw definiuje tabela prawdy z tabeli 1.1.


Tabela 1.1. Tabela prawdy operatorw logicznych dla typw logicznych z wyrnion
wartoci pust
x

x&y

x|y

prawda

prawda

prawda

prawda

prawda

fasz

fasz

prawda

prawda

pusta

pusta

prawda

fasz

prawda

fasz

prawda

fasz

fasz

fasz

fasz

fasz

pusta

fasz

pusta

pusta

prawda

pusta

prawda

pusta

fasz

fasz

pusta

pusta

pusta

pusta

pusta

Wicej informacji
wietny artyku o typach nullable mona znale w czeluciach Visual
C# Developer Center (http://msdn.microsoft.com/vcsharp/2005/overview/
language/nullabletypes/).

Wszelkie obiekty, ktrych nie definiuje si jawnie w ktrej z przestrzeni


nazw, lduj w globalnej przestrzeni nazw. Obiekty globalnej przestrzeni

Kwalifikator
globalnej przestrzeni
nazw pozwala
na odwoywanie si
do identyfikatora
z (domylnie)
globalnej przestrzeni
nazw; normalnie
odwoania
s ograniczane
do zestawu
identyfikatorw
z lokalnej przestrzeni
nazw,
a identyfikatory
z tej przestrzeni
przesaniaj nazwy
definiowane
globalnie.

Odwoania do obiektw z globalnej przestrzeni nazw

65

Odwoania do obiektw
z globalnej przestrzeni nazw
Tak jak w poprzednim wydaniu jzyka C# do deklarowania zasigu widocznoci nazw (tzw. przestrzeni nazw) suy sowo kluczowe namespace.
Stosowanie przestrzeni nazw pozwala lepiej organizowa kod i zapobiega
ewentualnym kolizjom nazw (na przykad prbie zdefiniowania dwch klas
o identycznej nazwie) przydatno podziau przestrzeni nazw ujawnia
si zwaszcza przy korzystaniu z komponentw zewntrznych, tworzonych
przez osoby trzecie.

nazw s dostpne dla obiektw wszystkich pozostaych (wszych) przestrzeni nazw. W przypadku kolizji nazw potrzebny jest jednak sposb sygnalizowania, e dane odwoanie dotyczy nie lokalnej, a wanie globalnej przestrzeni nazw.

Jak to zrobi?
Odwoanie do obiektu z globalnej przestrzeni nazw naley zasygnalizowa
kwalifikatorem zasigu global::, jak na listingu 1.8.
Listing 1.8. Stosowanie globalnej przestrzeni nazw
using System;
namespace GlobalNameSpace
{
class Program
{
// utworzenie zagniedonej klasy System udostpniajcej zestaw
// narzdzi interakcji z obsugiwanym przez program systemem;
// nazwa System koliduje z nazw przestrzeni nazw System
public class System
{
}
static void Main(string[] args)
{
// znacznik sygnalizujcy uruchomienie aplikacji konsoli;
// koliduje z nazw Console z przestrzeni nazw System
bool Console = true;
int x = 5;
// Console.WriteLine(x); // odmowa kompilacji kolizja
// z lokalnym Console
// System.Console.WriteLine(x); // kolizja z lokalnym System
global::System.Console.WriteLine(x); // dziaa
global::System.Console.WriteLine(Console);
}
}
}

Wynik:
5
True

66

Rozdzia 1: C# 2.0

Jak to dziaa?
Tworzenie zagniedonej w klasie Program klasy o nazwie System i deklarowanie w metodzie Main zmiennej lokalnej o nazwie Console to przykad cokolwiek sztuczny. Tym niemniej takie deklaracje blokuj w lokalnej
przestrzeni nazw dostp do globalnych identyfikatorw System i Console,
co uniemoliwia kompilacj wywoa:
Console.WriteLine(x);
System.Console.WriteLine(x);

Aby zasygnalizowa, e chodzi o odwoania do identyfikatora System


w globalnej przestrzeni nazw, naley zastosowa kwalifikator globalnej
przestrzeni nazw:
global::System.Console.WriteLine(x);

Warto te zauway, e w ostatnim wierszu kodu w odwoaniu do globalnych identyfikatorw System i Console stosowany jest kwalifikator globalnej przestrzeni nazw, a niekwalifikowane odwoanie do Console dotyczy lokalnej zmiennej metody:
global::System.Console.WriteLine(Console);

A co
z innymi zastosowaniami operatora zasigu (::)?
Operator :: suy jako kwalifikator aliasu przestrzeni nazw. Wystpuje
zawsze pomidzy dwoma identyfikatorami:
identyfikator1::identyfikator2

Jeli identyfikator1 reprezentuje globaln przestrze nazw, operator zasigu suy do wycignicia identyfikatora identyfikator2 z teje przestrzeni globalnej. Ale jeli identyfikator1 bdzie dowoln przestrzeni
nazw inn od przestrzeni globalnej, operator zawzi poszukiwania identyfikator2 do zasigu identyfikator1.

Wicej informacji
Kwalifikator globalnej przestrzeni nazw wspominany jest w artykule Create Elegant Code with Anonymous Methods, Iterators and Partial Classes

Odwoania do obiektw z globalnej przestrzeni nazw

67

autorstwa Juvala Lowyego, publikowanym pod adresem http://msdn.microsoft.com/msdnmag/issues/04/05/c20/ (MSDN).

Ograniczanie dostpu do waciwoci


Wreszcie mona
ogranicza
dostpno
akcesorw
waciwoci.

Nowa specyfikacja jzyka C# pozwala na ograniczanie poziomu dostpnoci metod-akcesorw ustawiajcych i odczytujcych waciwoci. Su
do tego stosowne modyfikatory dostpu. Zwykle dostp ogranicza si jedynie do akcesorw ustawiajcych waciwoci (set); akcesory odczytujce
s zazwyczaj udostpniane publicznie.

Jak to zrobi?
Ograniczenie dostpu do akcesora waciwoci polega na opatrzeniu deklaracji tego akcesora stosownym modyfikatorem dostpu, jak na listingu 1.9.
Listing 1.9. Ograniczanie dostpu do akcesora waciwoci
#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
#endregion
namespace LimitPropertyAccess
{
public class Employee
{
private string name;
public Employee(string name)
{
this.name = name;
}
public string Name
{
get { return name; }
protected set { name = value; }
}
public virtual void ChangeName(string name)
{
// tu operacje aktualizujce rekordy
Name = name; // odwoanie do prywatnego akcesora
}
}
class Program

68

Rozdzia 1: C# 2.0

{
static void Main(string[] args)
{
Employee joe = new Employee("Joe");
// inne operacje
string whatName = joe.Name; // dziaa
// joe.Name = "Bob"; // odmowa kompilacji
joe.ChangeName("Bob"); // dziaa
Console.WriteLine("imi joe'a: {0}", joe.Name);
}
}
}

Wynik:
imi joe'a: Bob

Jak to dziaa?
Projekt klasy Employee (pracownik) sygnalizuje, e cig imienia pracownika ma by prywatny. Ale programista przewidzia, e kiedy przyjdzie
mu wstawia dane o pracownikach do bazy danych, wic udostpni imi
za porednictwem waciwoci Name.
Pozostae klasy programu powinny mie moliwo odwoywania si do
Name, ale jedynie w odwoaniach niemodyfikujcych. Zmiana wartoci pola
moe si odbywa jedynie za porednictwem jawnego wywoania metody
ChangeName. Metoda zostaa oznaczona jako wirtualna w przyszych
klasach pochodnych zmiana imienia pracownika bdzie si pewnie wizaa z dodatkowymi operacjami.
Zachodzi tu potrzeba udostpnienia akcesora set, ale tylko metodom klasy
Employee i metodom jej klas pochodnych. Ograniczenie takie mona wy-

egzekwowa modyfikatorem dostpu dla akcesora set:


protected set { name = value; }

A co
z ograniczeniami odnonie stosowania modyfikatorw dostpu?
Ot modyfikatorw tych nie mona stosowa wobec interfejsw i jawnych
implementacji skadowych interfejsw. Modyfikatory dostpu mona stosowa jedynie wtedy, kiedy waciwo obejmuje oba akcesory (get i set);
mona nimi przy tym opatrywa tylko jeden z akcesorw.
Ograniczanie dostpu do waciwoci

69

Poza tym modyfikator musi ogranicza, a nie rozszerza dostpno. Nie


mona wic zadeklarowa waciwoci ze sowem protected, a potem za
pomoc modyfikatora oznaczy akcesor get jako dostpny publicznie.

Wicej informacji
O waciwociach i modyfikatorach dostpu do waciwoci traktuje artyku
MSDN publikowany pod adresem http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vclrfPropertiesPG.asp.

Elastyczno delegacji z kowariancj


i kontrawariancj
Kowariancja
pozwala
na stosowanie
z delegacjami metod
zwracajcych
wartoci typu
bdcego pochodn
(bezporedni
lub poredni)
typu zwracanego
okrelonego
w definicji delegacji.

Nowa specyfikacja jzyka C# zezwala na okrelanie metod delegacji z typem wartoci zwracanej, bdcej pochodn (bezporedni bd poredni)
typu zwracanego okrelonego w definicji delegacji; operacja taka nosi miano kowariancji. Chodzi o to, e jeli definicja delegacji zakada zwracanie
wartoci typu Mammal (ssak), to delegacj t mona zastosowa do metody
zwracajcej warto typu Dog (pies), o ile Dog jest pochodn Mammal, a take
do metody zwracajcej warto typu Retriever, o ile Retriever to pochodna
typu Dog, a ten jest pochodn Mammal.
Analogicznie dozwolone jest przekazywanie sygnatury metody delegacji,
w ktrej typy parametrw s pochodnymi typw parametrw zdefiniowanych w delegacji. To z kolei okrela si mianem kontrawariancji. Chodzi
o to, e jeli definicja delegacji wymaga podania metody przyjmujcej parametr typu Dog, to mona j uy z metod przyjmujc parametr typu
Mammal (znw pod warunkiem e Dog to pochodna Mammal).

Kontrawariancja
pozwala
na stosowanie
z delegacjami metod
przyjmujcych
parametry typu
bdcego typem
bazowym
(bezporednim
lub porednim)
typu parametru
okrelonego
w definicji delegacji.

Kowariancja i kontrawariancja zwikszaj elastyczno w zakresie doboru


metod dla delegacji. Zastosowanie kowariancji i kontrawariancji ilustruje
listing 1.10.

70

Rozdzia 1: C# 2.0

Jak to zrobi?

Listing 1.10. Stosowanie kowariancji i kontrawariancji


#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
#endregion
namespace CoAndContraVariance
{
class Mammal
{
public virtual Mammal ReturnsMammal()
{
Console.WriteLine("Ssak");
return this;
}
}
class Dog : Mammal
{
public Dog ReturnsDog()
{
Console.WriteLine("Pies");
return this;
}
}
class Program
{
public delegate Mammal theCoVariantDelegate();
public delegate void theContraVariantDelegate(Dog theDog);

private static void MyMethodThatTakesAMammal(Mammal theMammal)


{
Console.WriteLine("W metodzie akceptujcej ssaki");
}
private static void MyMethodThatTakesADog(Dog theDog)
{
Console.WriteLine("W metodzie akceptujcej psy");
}

static void Main(string[] args)


{
Mammal m = new Mammal();

Elastyczno delegacji z kowariancj i kontrawariancj

71

Dog d = new Dog();


theCoVariantDelegate myCoVariantDelegate =
new theCoVariantDelegate(m.ReturnsMammal);
myCoVariantDelegate();
myCoVariantDelegate =
new theCoVariantDelegate(d.ReturnsDog);
myCoVariantDelegate();
theContraVariantDelegate myContraVariantDelegate =
new theContraVariantDelegate(MyMethodThatTakesADog);
myContraVariantDelegate(d);
myContraVariantDelegate =
new theContraVariantDelegate(MyMethodThatTakesAMammal);
myContraVariantDelegate(d);
}
}
}

Wynik:
Ssak
Pies
W metodzie akceptujcej psy
W metodzie akceptujcej ssaki

Jak to dziaa?
Klasa Program z listingu 1.10 deklaruje dwie delegacje. Pierwsza z nich
obejmuje metody bezparametrowe i zwracajce wartoci typu Mammal:
Z wczeniejszych
definicji z listingu 1.10
wynika, e Dog
to pochodna
Mammal.

public delegate Mammal theCoVariantDelegate();

W metodzie run deklarowane s egzemplarze klas Mammal i Dog (po jednym z kadej klasy):
Mammal m = new Mammal();
Dog d = new Dog();

Potem mona ju przystpi do utworzenia pierwszego egzemplarza theCoVariantDelegate:


theCoVariantDelegate myCoVariantDelegate =
new theCoVariantDelegate(m.ReturnsMammal);

Wszystko pasuje do sygnatury delegacji (m.ReturnsMammal() to metoda


nieprzyjmujca adnych argumentw i zwracajca warto typu Mammal),
mona wic przystpi do wywoania metody za porednictwem delegacji:
myCoVariantDelegate();

72

Rozdzia 1: C# 2.0

Stosujc kowariancj, mona obj t sam delegacj rwnie inn metod:


myCoVariantDelegate =
new theCoVariantDelegate(d.ReturnsDog);

Tym razem metoda realizujca delegacj to metoda zwracajca warto


typu Dog (d.ReturnsDog()), a nie Mammal; tak si jednak skada, e typ Dog
jest pochodn typu Mammal:
public Dog ReturnsDog()
{
Console.WriteLine("Pies");
return this;
}

Tak dziaa kowariancja. Kontrawariancj ilustruje druga z delegacji, ktrej


sygnatura zakada podawanie metod niezwracajcych adnych wartoci,
za to przyjmujcych za porednictwem argumentu wywoania warto
typu Dog:
public delegate void theContraVariantDelegate(Dog theDog);

Pierwszy egzemplarz delegacji jest konstruowany na bazie metody pasujcej do sygnatury deklaracji: metoda zwraca typ void i deklaruje parametr typu Dog:
theContraVariantDelegate myContraVariantDelegate =
new theContraVariantDelegate(MyMethodThatTakesADog);

Metod t mona oczywicie swobodnie wywoywa przez delegacj.


W drugim zastosowaniu delegacji zostaa jednak wykorzystana metoda,
ktra co prawda przyjmuje jeden parametr, ale nie typu Dog, a typu Mammal:
myContraVariantDelegate =
new theContraVariantDelegate(MyMethodThatTakesAMammal);

Sygnatura metody MyMethodThatTakesAMammal zakada przyjmowanie


za porednictwem jedynego parametru wywoania obiektw typu Mammal
(ssaki), nie typu Dog:

Zauwa,
e kontrawariancja
pozwala
na przekazywanie
obiektu klasy
bazowej tam,
gdzie oczekiwany
jest obiekt klasy
pochodnej.

private static void MyMethodThatTakesAMammal(Mammal theMammal)


{
Console.WriteLine("W metodzie akceptujcej ssaki");
}

Cao dziaa, bo Dog jest pochodn Mammal, a kontrawariancja pozwala


na podstawianie nadtypu (typu bazowego) w miejsce typu waciwego.
Elastyczno delegacji z kowariancj i kontrawariancj

73

A co
z t kontrawariancj? Wiadomo, e dziki kowariancji da si zwrci
obiekt typu Dog (bo Mammal zawiera si w Dog), ale skd moliwo i jaki
sens podstawienia odwrotnego? Czy tam, gdzie oczekiwany jest pewien
typ, nie powinno si przekazywa tego wanie typu, ewentualnie typu
pochodnego?
Dr Jonathan Bruce
Postel (1913 1998),
wsptwrca
standardw
internetowych.

Ot kontrawariancja jest spjna z regu Postela, mwic, eby. Klient


musi si upewni, czy to, co przekaza do metody, bdzie z t metod
dziaao, ale twrca implementacji metody powinien podchodzi do tego
liberalnie i akceptowa wartoci typu Dog niezalenie od formy, w jakiej
s przekazywane nawet jeli wystpuj w postaci referencji do obiektu
typu bazowego.
gdyby odwrci zastosowanie kowariancji i zwraca typ bazowy tam,
gdzie oczekiwany jest typ pochodny? Czy to dozwolone?
Nie, kowariancja dziaa tylko w jedn stron. Mona zwrci typ pochodny
tam, gdzie oczekiwany jest typ bazowy.
gdyby odwrci kontrawariancj i przekaza typ pochodny tam, gdzie
oczekiwany jest typ bazowy?
To te nie jest dozwolone. Kontrawariancja te jest jednokierunkowa. Mona
jedynie przekazywa typ bazowy tam, gdzie oczekiwany jest typ pochodny.

Wicej informacji
Dodatkowych informacji naleaoby szuka w archiwach licznych grup
dyskusyjnych, gdzie toczyy si zaarte spory o zalety i wady jzykw
obiektowych obsugujcych kowariancj i kontrawariancj. Szczegowego
omwienie stosowania kowariancji i kontrawariancji w programach pisanych w jzyku C# naley szuka w dokumentacji MSDN.

74

Rozdzia 1: C# 2.0

You might also like