You are on page 1of 134

IDZ DO

PRZYKADOWY ROZDZIA
SPIS TRECI

KATALOG KSIEK
KATALOG ONLINE

Delphi 2005
Autor: Elmar Warken
Tumaczenie: Wojciech Moch
ISBN: 83-7361-993-3
Tytu oryginau: Delphi 2005
Format: B5, stron: 810

ZAMW DRUKOWANY KATALOG

TWJ KOSZYK
DODAJ DO KOSZYKA

CENNIK I INFORMACJE
ZAMW INFORMACJE
O NOWOCIACH
ZAMW CENNIK

CZYTELNIA
FRAGMENTY KSIEK ONLINE

rodowisko programistyczne Delphi jest od dawna jednym z najpopularniejszych


narzdzi stosowanych przez twrcw aplikacji. Kada z jego wersji wnosia wiele
nowoci, jednak wersja oznaczona symbolem 2005 to prawdziwy przeom. Umoliwia
ona bowiem projektowanie aplikacji przeznaczonych dla platformy .NET, co otwiera
przez programistami tysice nowych moliwoci. Mog wykorzystywa bibliotek klas
FCL, tworzy aplikacje nie tylko w znanym z poprzednich wersji Delphi jzyku Object
Pascal, ale rwnie w zyskujcym coraz wiksz popularno jzyku C#, a take
stosowa w swoich programach klasy i obiekty napisane w dowolnym jzyku zgodnym
z platform .NET. Delphi 2005 to prawdziwa rewolucja.
Ksika Delphi 2005 wyczerpujco omawia najnowsz wersj tego rodowiska
programistycznego. Przedstawia jego moliwoci i ich praktyczne zastosowanie
praktyczne. Szczegowo opisuje zagadnienia podstawowe, takie jak praca
z interfejsem uytkownika i stosowanie komponentw oraz tematy zaawansowane
zwizane z tworzeniem aplikacji bazodanowych, korzystaniem z klas i obiektw
specyficznych dla platformy .NET oraz pisaniem wasnych komponentw.
Korzystanie z elementw interfejsu uytkownika
Zarzdzanie plikami projektu
Biblioteka klas .NET
Przetwarzanie plikw XML
Zasady programowania obiektowego w Object Pascal
Tworzenie aplikacji z wykorzystaniem biblioteki VCL.NET
Poczenia z baz danych za pomoc ADO.NET
Zasady tworzenia wasnych komponentw
Dziki tej ksice poznasz wszystkie moliwoci najnowszej wersji Delphi

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

Spis treci
Przedmowa ....................................................................................... 9
Rozdzia 1. Praca w IDE ................................................................................... 15
1.1. Konstrukcja komponentw ...................................................................................... 15
1.1.1. Elementy sterujce, narzdzia i komponenty ................................................ 16
1.1.2. Formularze i okna ......................................................................................... 18
1.2. Orientacja na zdarzenia ........................................................................................... 19
1.2.1. Zdarzenie na kad okazj ............................................................................ 19
1.2.2. Zdarzenia w Delphi ....................................................................................... 21
1.3. Cykl rozwoju aplikacji ............................................................................................ 23
1.3.1. Cykl rozwoju aplikacji w IDE Delphi ........................................................... 23
1.3.2. Program przykadowy ................................................................................... 24
1.4. IDE i narzdzia wizualne ......................................................................................... 25
1.4.1. Budowa IDE ................................................................................................. 25
1.4.2. Pomoc w IDE i opis jzyka ........................................................................... 29
1.4.3. Projektowanie formularzy ............................................................................. 32
1.4.4. Zarzdzanie plikami ...................................................................................... 36
1.4.5. Inspektor obiektw ........................................................................................ 37
1.5. czenie komponentw z kodem ............................................................................ 45
1.5.1. Wprowadzenie do obsugi zdarze ............................................................... 46
1.5.2. Podstawowe moliwoci procedur obsugi zdarze ...................................... 49
1.5.3. Przegld moduu formularza ......................................................................... 51
1.5.4. Zdarzenia w programie przykadowym ......................................................... 54
1.5.5. Pomoc w edytorze ......................................................................................... 64
1.5.6. czenie zdarze nawigacja, zmiany, usuwanie ...................................... 72
1.5.7. Spojrzenie za kulisy ...................................................................................... 74
1.6. Zarzdzanie projektem ............................................................................................ 78
1.6.1. Pliki projektu ................................................................................................. 78
1.6.2. Zarzdzanie projektem .................................................................................. 81
1.6.3. Przegldarka symboli w projektach i kompilatach .NET .............................. 84
1.6.4. Listy rzeczy do zrobienia ........................................................................... 89
1.7. Debuger ................................................................................................................... 91
1.7.1. Punkty wstrzymania ...................................................................................... 92
1.7.2. Kontrolowanie zmiennych ............................................................................ 95
1.7.3. Wykonywanie kodu ...................................................................................... 98
1.7.4. Oglne okna debugera .................................................................................. 99

Delphi 2005

Rozdzia 2. Biblioteka klas .NET ..................................................................... 103


2.1. Zaawansowane projektowanie formularzy ............................................................ 106
2.1.1. Hierarchia kontrolek i kolejno Z .............................................................. 108
2.1.2. Zmiany wielkoci formularzy i kontrolek ................................................... 111
2.1.3. Zwizki pomidzy formularzami i kontrolkami .......................................... 116
2.1.4. Dziedziczenie formularzy ........................................................................... 120
2.1.5. Efekty przezroczystoci i przenikania ......................................................... 121
2.2. Podstawy biblioteki Windows-Forms .................................................................... 124
2.2.1. Obsuga formularzy .................................................................................... 125
2.2.2. Formularze dialogw .................................................................................. 129
2.2.3. Przykadowy program WallpaperChanger .................................................. 131
2.2.4. Zarzdzanie kontrolkami w czasie dziaania programu ............................... 137
2.2.5. Kolekcje w bibliotece FCL ......................................................................... 144
2.2.6. Wymiana danych i mechanizm przecignij-i-upu .................................... 147
2.3. Stosowanie kontrolek ............................................................................................ 155
2.3.1. Podstawowe cechy wsplne wszystkich kontrolek ..................................... 155
2.3.2. Pola wprowadzania danych ......................................................................... 165
2.3.3. Pola tekstowe RTF i tabele waciwoci ..................................................... 167
2.3.4. Kontrolka LinkLabel ................................................................................... 172
2.3.5. Menu ........................................................................................................... 175
2.4. Kontrolki list i kontrolka TreeView ...................................................................... 177
2.4.1. ListBox ....................................................................................................... 177
2.4.2. ListView ..................................................................................................... 186
2.4.3. TreeView .................................................................................................... 193
2.5. Grafika ................................................................................................................... 203
2.6. Przechowywanie i zarzdzanie plikami ................................................................. 206
2.6.1. Serializacja .................................................................................................. 206
2.6.2. Pliki i katalogi ............................................................................................. 216
2.6.3. Odczytywanie i zapisywanie plikw ........................................................... 220
2.6.4. Zachowywanie ustawie uytkownika ........................................................ 226
2.7. XML ...................................................................................................................... 231
2.7.1. Podstawy XML ........................................................................................... 232
2.7.2. Program do graficznego podgldu plikw XML ......................................... 238
2.7.3. Zachowywanie ustawie uytkownika w formacie XML ........................... 242
2.7.4. Zapisywanie dokumentw programu w postaci plikw XML .................... 246
2.8. Wtki ..................................................................................................................... 251
2.8.1. Rwnolege wykonywanie fragmentw programw ................................... 251
2.8.2. Wtki w bibliotece FCL .............................................................................. 258
2.8.3. Wiele wtkw i ich synchronizacja ............................................................. 263

Rozdzia 3. Jzyk Delphi w rodowisku .NET .................................................... 273


3.1. Przestrzenie nazw i kompilaty ............................................................................... 275
3.1.1. Podstawowe pojcia rodowiska .NET ....................................................... 275
3.1.2. Przestrzenie nazw w Delphi ........................................................................ 278
3.1.3. Kompilaty w Delphi .................................................................................... 284
3.1.4. Moduy Delphi ............................................................................................ 293
3.1.5. Moduy Delphi dla nowicjuszy ................................................................... 296
3.2. Obiekty i klasy ...................................................................................................... 296
3.2.1. Deklaracja klasy .......................................................................................... 297
3.2.2. Atrybuty widocznoci ................................................................................. 299
3.2.3. Samowiadomo metody ........................................................................... 300
3.2.4. Waciwoci ................................................................................................ 302
3.2.5. Metody klas i zmienne klas ......................................................................... 306
3.2.6. Dziedziczenie .............................................................................................. 310

Spis treci

5
3.2.7. Uprzedzajce deklaracje klas ...................................................................... 312
3.2.8. Zagniedone deklaracje typw .................................................................. 313
3.3. Obiekty w czasie dziaania programu .................................................................... 314
3.3.1. Inicjalizacja obiektw: konstruktory ........................................................... 314
3.3.2. Zwalnianie zasobw i czyszczenie pamici ................................................ 316
3.3.3. Metody wirtualne ........................................................................................ 324
3.3.4. Konwersja typw i informacje o typach ...................................................... 329
3.3.5. Konstruktory wirtualne ............................................................................... 333
3.4. Typy interfejsw .................................................................................................... 335
3.4.1. Czym jest interfejs? ..................................................................................... 335
3.4.2. Implementowanie interfejsu ........................................................................ 339
3.5. Podstawy jzyka Object Pascal ............................................................................. 344
3.5.1. Elementy leksykalne ................................................................................... 345
3.5.2. Instrukcje kompilatora ................................................................................ 347
3.5.3. Typy i zmienne ........................................................................................... 350
3.5.4. Stae i zmienne inicjowane .......................................................................... 351
3.5.5. Obszary widocznoci i zmienne lokalne ..................................................... 353
3.5.6. Atrybuty ...................................................................................................... 355
3.6. Typy ...................................................................................................................... 356
3.6.1. Typy proste ................................................................................................. 356
3.6.2. Operatory i wyraenia ................................................................................. 364
3.6.3. Tablice ........................................................................................................ 367
3.6.4. Rne typy cigw znakw ........................................................................ 370
3.6.5. Typy strukturalne ........................................................................................ 375
3.6.6. Kategorie typw w CLR ............................................................................. 376
3.7. Instrukcje ............................................................................................................... 379
3.8. Procedury i funkcje ............................................................................................... 384
3.8.1. Typy parametrw ........................................................................................ 385
3.8.2. Przecianie metod i parametry standardowe ............................................. 389
3.8.3. Wskaniki metod ........................................................................................ 391
3.9. Wyjtki .................................................................................................................. 392
3.9.1. Wywoywanie wyjtkw ............................................................................. 392
3.9.2. Klasy wyjtkw .......................................................................................... 393
3.9.3. Zabezpieczanie kodu z wykorzystaniem sekcji finally ............................... 394
3.9.4. Obsuga wyjtkw ...................................................................................... 395
3.9.5. Asercja ........................................................................................................ 399

Rozdzia 4. Aplikacje VCL.NET ........................................................................ 401


4.1. Biblioteki VCL.NET i FCL ................................................................................... 401
4.1.1. Komponenty ................................................................................................ 403
4.2. Aplikacje VCL w IDE Delphi ............................................................................... 409
4.2.1. Nowy ukad IDE dla aplikacji VCL ............................................................ 409
4.2.2. Projekty VCL dla rodowisk .NET i Win32 ............................................... 411
4.2.3. Rnice w projektowaniu formularzy ......................................................... 413
4.2.4. Okno struktury w czasie projektowania formularza .................................... 415
4.2.5. Moduy formularzy VCL ............................................................................ 416
4.2.6. Pliki zasobw formularzy ........................................................................... 419
4.2.7. Instalowanie komponentw VCL ................................................................ 422
4.3. Programowanie z wykorzystaniem biblioteki VCL ............................................... 424
4.3.1. Dopasowanie biblioteki VCL do rodowiska .NET .................................... 425
4.3.2. Hierarchie kontrolek ................................................................................... 428
4.3.3. Najwaniejsze czci wsplne kontrolek .................................................... 430
4.3.4. Obsuga formularzy .................................................................................... 432
4.3.5. Kontrolki w czasie dziaania programu ....................................................... 438

Delphi 2005
4.3.6. Kontrolki TListBox, TListView i TTreeView ............................................ 438
4.3.7. Listy, kolekcje i strumienie ......................................................................... 441
4.3.8. Grafika ........................................................................................................ 445
4.3.9. Mechanizm przecignij-i-upu .................................................................. 452
4.3.10. Wtki ......................................................................................................... 456
4.4. Techniki ponownego wykorzystania formularzy ................................................... 459
4.4.1. Repozytorium obiektw .............................................................................. 460
4.4.2. Dziedziczenie formularzy ........................................................................... 463
4.4.3. Ramki .......................................................................................................... 467
4.5. Przykadowa aplikacja VCL .................................................................................. 471
4.5.1. O programie TreeDesigner .......................................................................... 472
4.5.2. Krtki opis i obsuga programu ................................................................... 474
4.6. Komponenty akcji ................................................................................................. 479
4.6.1. Listy polece z komponentu TActionList ................................................... 480
4.6.2. Akcje standardowe ...................................................................................... 483
4.6.3. Komponenty menedera akcji ..................................................................... 484
4.6.4. Komponent TControlBar ............................................................................ 490
4.6.5. Przykadowy interfejs uytkownika ............................................................ 493
4.7. Przenoszenie aplikacji VCL .................................................................................. 495
4.7.1. Przygotowania ............................................................................................. 496
4.7.2. Dopasowywanie aplikacji do rodowiska .NET .......................................... 499
4.7.3. Wywoania funkcji API i transpozycja danych ........................................... 503
4.7.4. Zmiany w interfejsie biblioteki VCL .......................................................... 509
4.7.5. Operacje na strumieniach ............................................................................ 511
4.8. Aplikacje VCL.NET i rodowisko Win32 ............................................................. 519
4.9. Biblioteki VCL.NET i FCL w ramach jednej aplikacji ......................................... 524
4.9.1. czenie bibliotek FCL i VCL na poziomie klas ........................................ 524
4.9.2. czenie bibliotek FCL i VCL na poziomie formularzy ............................. 528
4.9.3. czenie bibliotek FCL i VCL na poziomie komponentw ........................ 535

Rozdzia 5. Aplikacje bazodanowe ................................................................... 541

5.1. Biblioteka ADO.NET w Delphi ............................................................................ 542


5.1.1. Zbiory danych w pamici ............................................................................ 543
5.1.2. Komponenty udostpniajce dane (ang. Providers) .................................... 547
5.1.3. Komponenty Borland Data Providers ......................................................... 552
5.1.4. Eksplorator danych ..................................................................................... 555
5.2. Programowanie z wykorzystaniem biblioteki ADO.NET ..................................... 556
5.2.1. Wizanie danych ......................................................................................... 557
5.2.2. Kolumny i wiersze ...................................................................................... 566
5.2.3. Zbiory danych okrelonego typu ................................................................. 574
5.2.4. Relacje ........................................................................................................ 576
5.2.5. Wane operacje na bazach danych .............................................................. 581
5.3. Przykadowa aplikacja korzystajca z biblioteki ADO.NET ................................. 589
5.3.1. Tworzenie bazy danych .............................................................................. 589
5.3.2. Formularze aplikacji ................................................................................... 596
5.3.3. Zapytania SQL ............................................................................................ 599
5.3.4. Zapytania SQL z parametrami .................................................................... 602
5.3.5. Aktualizacje danych .................................................................................... 606
5.3.6. Aktualizacje w polach z automatyczn inkrementacj ................................ 610
5.3.7. Wygodny formularz wprowadzania danych ................................................ 614
5.3.8. Konflikty przy wielodostpie ...................................................................... 620
5.4. Aplikacje bazodanowe w bibliotece VCL.NET ..................................................... 630
5.4.1. Dostp do danych za pomoc dbExpress .................................................... 631
5.4.2. Formularze bazy danych i moduy danych .................................................. 636
5.4.3. Kontrolki operujce na danych z baz danych .............................................. 640

Spis treci

7
5.4.4. Podstawowe operacje na danych ................................................................. 642
5.4.5. Kolumny tabeli, czyli pola .......................................................................... 648
5.4.6. Pola trwae i edytor pl ............................................................................... 650
5.4.7. Dane z aktualnego wiersza .......................................................................... 652
5.4.8. Sortowanie, szukanie i filtrowanie .............................................................. 655
5.4.9. Przykadowa aplikacja terminarza .............................................................. 659

Rozdzia 6. Tworzenie komponentw .NET ....................................................... 679


6.1. Wprowadzenie ....................................................................................................... 680
6.1.1. Przegld przykadowych komponentw ..................................................... 680
6.1.2. Klasy komponentw ................................................................................... 682
6.1.3. Tworzenie komponentw w IDE Delphi ..................................................... 683
6.1.4. Kompilaty komponentw ............................................................................ 684
6.1.5. Pakiety komponentw ................................................................................. 684
6.1.6. Komponent minimalny ............................................................................... 688
6.1.7. Przykad przydatnego komponentu ............................................................. 690
6.2. Komponenty od rodka ...................................................................................... 693
6.2.1. Zdarzenia .................................................................................................... 694
6.2.2. Wywoywanie zdarze ................................................................................ 696
6.2.3. Zdarzenia typu multicast ............................................................................. 698
6.2.4. Zdarzenia w komponentach ........................................................................ 701
6.2.5. Waciwoci dla zaawansowanych .............................................................. 703
6.2.6. Interfejs rodowiska programistycznego ..................................................... 710
6.3. Rozbudowywanie istniejcych komponentw ....................................................... 713
6.3.1. Od komponentu ComboBox do FontComboBox ........................................ 714
6.3.2. Kontrolka ComboBox z automatyczn histori ........................................... 716
6.4. Kontrolki skadane z innych kontrolek .................................................................. 723
6.5. Nowe kontrolki ...................................................................................................... 727
6.5.1. Tworzenie rodowiska testowego ............................................................... 728
6.5.2. Interfejs nowej palety kolorw .................................................................... 729
6.5.3. Atrybuty waciwoci .................................................................................. 736
6.5.4. Implementacja komponentu ........................................................................ 738
6.5.5. Zdarzenia z moliwoci reakcji ................................................................. 743
6.6. Edytory w czasie projektowania ............................................................................ 745
6.6.1. Proste edytory waciwoci ......................................................................... 746
6.6.2. Menu czasu projektowania dla palety kolorw ........................................... 750
6.6.3. Edytowanie kolekcji obiektw .................................................................... 752
6.7. Pozostae komponenty przykadowe ..................................................................... 758
6.7.1. Komponent StateSaver ................................................................................ 758
6.7.2. Wyczanie wybranych okien z komunikatami ........................................... 762
6.7.3. Wywietlanie struktur katalogw i list plikw ............................................ 764

Skorowidz ................................................................................... 767

Rozdzia 3.

Jzyk Delphi
w rodowisku .NET
W tym rozdziale zajmowa si bdziemy jzykiem programowania Delphi znanym
te pod nazw Object Pascal i jego przeksztacaniem w klasy i kompilaty rodowiska
.NET. Rozdzia ten nie ma by dokumentacj tego jzyka (taka dokumentacja stanowi
cz systemu aktywnej pomocy Delphi; znale j mona w gazi Borland Help/
Delphi 2005 (Common)/Reference/Delphi Language Guide) i w zwizku z tym nie
bd tu opisywa wszystkich jego szczegw. W rozdziale tym bd si stara przedstawi jak najpeniejsze wprowadzenie do jzyka, skierowane do osb przesiadajcych
si z innych narzdzi programistycznych lub z wczeniejszych wersji Delphi. Poza tym
omawia bd powizania istniejce pomidzy Delphi i CLR (ang. Common Language
Runtime wsplne rodowisko uruchomieniowe dla wszystkich aplikacji .NET) oraz
wyjania wszystkie waciwoci jzyka, jakie bd wykorzystywane w ksice (rozdzia ten opisywa bdzie te ograniczenia jzyka w tym zakresie).
Na pocztku rozdziau nie bdziemy zajmowa si drobnymi elementami jzyka, takimi jak typy danych, zmienne i instrukcje, ale od razu przejdziemy do wikszych zagadnie, takich jak kompilaty (podrozdzia 3.1) i model obiektw (podrozdziay 3.2
do 3.4). Od podrozdziau 3.5 przejdziemy do szczegw jzyka, czyli zmiennych,
staych, typw, instrukcji, deklaracji metod i wyjtkw.

Wprowadzenie
Jzyk Object Pascal lub Delphi, jak ostatnio nazywa go firma Borland, jest bezporednim
nastpc jzyka Borland Pascal with Objects, ktry zosta wprowadzony w roku 1989
w pakiecie Turbo Pascal 5.5 (rodowisko programistyczne dla systemu DOS), a krtko
potem w pakiecie Turbo Pascal dla Windows, dziaajcym rwnie w systemie Windows. Od czasu powstania pierwszej wersji Delphi w roku 1995 do jzyka tego dodawano wiele poprawek i rozszerze, ale z kad nastpn wersj jzyka liczba dodatkw cay czas si zmniejszaa, co oznacza, e jzyk ustabilizowa si na wzgldnie
wysokim poziomie.

274

Delphi 2005

Wraz z przeniesieniem Delphi do rodowiska .NET, do jzyka Object Pascal znw


wprowadzono kilka wanych rozszerze. Wyglda jednak na to, e osoby przesiadajce
si z jzyka C++ na jzyk C# musz przyswaja sobie duo wicej zmian w jzyku,
ni osoby zmieniajce Delphi 7 na Delphi dla .NET. Czciowo mona to wytumaczy
wspomnianym wyej wysokim poziomem jzyka Object Pascal w Delphi 7, do ktrego
jzyki przygotowywane przez Microsoft musiay dopiero dotrze, ale czciowo wynika te z tego, e firmie Borland do dobrze udao si zamaskowa przed programistami rnice pomidzy jzykiem stosowanym w Delphi a wymaganiami rodowiska
.NET. Dziki temu zachowany zosta wysoki zakres wstecznej zgodnoci jzyka z poprzednimi wersjami i pozwolio to na znacznie atwiejsze przenoszenie programw na
inne platformy, w ktrych dziaa Delphi, takie jak Win32 lub Linux.

Nowoci w stosunku do Delphi 7


W tym rozdziale opisywa bd nastpujce unowoczenienia jzyka wprowadzone
do niego od czasu Delphi 7:
Dziaanie mechanizmu oczyszczania pamici (ang. Garbage Collector);

przenoszenie starych mechanizmw zwalniania pamici z jzyka Object


Pascal, takich jak destruktory i rczne zwalnianie obiektw metod Free
(punkt 3.3.2).
Zagniedone deklaracje typw (punkt 3.2.8).
Koncepcja typw wartoci i typw wskanikw (punkt 3.6.6).
Mechanizm Boxingu (punkt 3.6.6).
Rekordy jako klasy typw wartoci z metodami (3.6.5).
Zmienne klas, waciwoci klas, konstruktory klas (punkt 3.2.5).
Drobne rozszerzenia jzyka: strict private, strict protected (punkt 3.2.2)
i sealed (punkt 3.2.6).
Operatory przecione (tylko zastosowanie operatorw przecionych

punkt 3.6.2).

Delphi a jzyki C++ i C#


Jzyk Object Pascal ju we wczeniejszych wersjach Delphi charakteryzowa si
rozwizaniami, ktrych nie mona si byo doszuka w jzyku C++, a ktre w tej lub
innej formie obecne s dzisiaj w jzyku C#:
Wirtualne konstruktory, rozszerzajce koncepcj polimorfizmu rwnie

na mechanizmy konstrukcji obiektw (bdzie o tym mowa w punkcie 3.3.5).


W jzyku C# konstruktory wirtualne nie s co prawda dostpne, ale w jzykach
rodowiska .NET podobny efekt uzyska mona za pomoc mechanizmu
Reflection, wirtualnie wywoujc konstruktor poprzez metod InvokeMember
danego obiektu Type.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

275

Wskaniki metod, ktre s znacznie wydajniejsze i praktyczniejsze od podobnych

wskanikw z jzyka C++ (punkt 3.8.3). W jzyku C# wskaniki te nazywane


s delegacjami. Mwic dokadniej, typ wskanika metody obecny w Delphi
od pierwszej wersji w jzyku C# odpowiada egzemplarzowi (instancji)
typu Delegat.
Wyjtki odpowiadajce wyjtkom obsugiwanym w stylu jzyka C. Maj one

t przewag nad wyjtkami jzyka C++, e pozwalaj na stosowanie sekcji


finally (dostpna jest ona rwnie w jzyku C# podrozdzia 3.9).
Informacje o typach wykraczajce poza moliwoci oferowane przez mechanizm

RTTI z jzyka C++ (punkt 3.3.4). W rodowisku .NET informacje te dostpne


s w jeszcze szerszym zakresie ni Delphi 7 (mechanizm Reflection).
Konstruktory tablic otwartych pozwalajce na uzyskanie praktycznie dowolnej

listy parametrw (punkt 3.8.1).


Interfejsy, bardzo podobne do interfejsw znanych z jzyka Java, pozwalajce na

uzyskanie wielu operacji, ktre w jzyku C++ moliwe s tylko z wykorzystaniem


dziedziczenia wielobazowego, a dodatkowo wolne s od zagroe, jakie stwarza
ta waciwo jzyka C++ (podrozdzia 3.4).
Oprcz przedstawionych na pocztku jasnych stron jzyka Object Pascal, wymieni
mona tu jeszcze inne zalety, ktre znane byy ju we wczeniejszych wersjach Delphi: zbiory (punkt 3.6.5), otwarte tablice (punkt 3.8.1) i chyba najczciej wykorzystywana w tej ksice przewaga jzyka Pascal nad jzykiem C++ instrukcja with
(podrozdzia 3.7).

3.1. Przestrzenie nazw i kompilaty


Jak ju mwiem, forma Borland tworzc Delphi dla .NET chciaa uzyska jak najwikszy stopie zgodnoci z poprzednimi wersjami Delphi i przenonoci oprogramowania do systemw Windows i Linux. W wyniku tych de do rodowiska .NET
przeniesione zostay koncepcje znane z poprzednich wersji Delphi, takie jak moduy
i pakiety, ktre poczone zostay z koncepcjami funkcjonujcymi w rodowisku .NET,
takimi jak przestrzenie nazw i kompilaty. Zanim zajmiemy si samym Delphi, w tym
podrozdziale postaram si dokadniej opisa te podstawowe pojcia rodowiska .NET.

3.1.1. Podstawowe pojcia rodowiska .NET


W tym punkcie zajmiemy si najpierw pojciami kompilatw (ang. Assembly) i przestrzeni nazw (ang. Namespace) oraz przyjrzymy rodowisku zarzdzanemu przez CLR,
w ktrym wykonywane s wszystkie kompilaty. Opisywa bd rwnie takie mechanizmy jak oczyszczanie pamici (ang. Garbage Collector) i system wsplnych typw
(ang. Common Type System), ktre w kolejnych rozdziaach gra bd znaczc rol.

276

Delphi 2005

Kompilaty
Kompilaty (ang. Assemblies) s podstawowym budulcem aplikacji rodowiska .NET.
W wielu przypadkach kompilat jest po prostu bibliotek dynamiczn .dll albo plikiem
wykonywalnym .exe, ale koncepcja kompilatw jest o wiele szersza ni koncepcja
plikw. Jeden kompilat moe rozciga si na wiele plikw, ktre traktowane s jako
jedna cao. Pliki te, traktowane jako jeden kompilat, maj dokadnie te same numery
wersji oraz wspln stref prywatn w rodowisku .NET istnieje szczeglny
atrybut widocznoci o nazwie assembly, umoliwiajcy dostp do identyfikatora tylko
z wntrza danego kompilatu.
Co prawda podziay istniejce w kompilatach wpywaj przede wszystkim na sposb
wykonywania programu, jednak cz z nich dotyczy rwnie jego fizycznej struktury
(kompilaty adowane s jako cakowicie niezalene od siebie skadniki programu,
w zwizku z czym w czasie aktualizacji oprogramowania mog by aktualizowane
niezalenie od siebie). Istnieje te podzia wpywajcy na logiczn struktur programu,
ktry najwiksze znaczenie ma w czasie projektowania aplikacji.

Przestrzenie nazw
Przestrzenie nazw (ang. Namespaces) pozwalaj na dokonanie podziaw w cay czas
rosncych i przez to coraz trudniejszych do ogarnicia bibliotekach klas. Na przykad
klasy przeznaczone do generowania grafiki umieszczone zostay w przestrzeni nazw
System.Drawing, natomiast wszystkie kontrolki znane z aplikacji systemu Windows,
takie jak kontrolki ListBox lub Button, zapisane s w przestrzeni nazw System.Windows.
Forms. Dostpne s te inne kontrolki, przygotowane specjalnie do wykorzystania
w aplikacjach WWW, ktre rwnie nazywaj si ListBox i Button, chocia s to klasy
cakowicie niezalene od klas standardowych kontrolek. Rozrnienie pomidzy tymi
pierwszymi a tymi drugimi kontrolkami umoliwiaj wanie przestrzenie nazw: System.
Windows.Forms.ListBox to kontrolka listy wspdziaajca ze standardowymi aplikacjami
dziaajcymi bezporednio na komputerze uytkownika i na ekranie wywietlana jest
z wykorzystaniem interfejsu GDI+. Z kolei System.Web.UI.WebControls.ListBox to
kontrolka listy wsppracujca z aplikacjami WWW dziaajcymi na serwerach WWW,
a na komputer uytkownika przenoszona w postaci kodu HTML.
Rnorodno koncepcji przestrzeni nazw i kompilatw polega midzy innymi na tym,
e jedna przestrze nazw moe w sobie zawiera wiele kompilatw, a kady z kompilatw moe przechowywa w sobie wiele przestrzeni nazw. Mwic dokadniej,
kompilat nie moe w sobie tak naprawd zawiera przestrzeni nazw, poniewa moe
by ona rozbudowywana przez inne kompilaty. W zwizku z tym naleaoby powiedzie, e kompilat moe przechowywa nazwy pochodzce z wielu przestrzeni nazw,
ktre z kolei mog by rozsiane wrd wielu kompilatw.

Hierarchia przestrzeni nazw


Hierarchia przestrzeni nazw budowana jest za pomoc kropek umieszczanych pomidzy
kolejnymi nazwami. Na przykad przestrze nazw System.Drawing.Printing podporzdkowana jest hierarchicznie przestrzeni nazw System.Drawing, ktra z kolei podporzdkowana jest przestrzeni nazw System. Mona te powiedzie, e nadrzdne przestrzenie nazw zawieraj w sobie wszystkie przestrzenie podrzdne.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

277

Takie hierarchiczne zwizki zawierania maj jednak natur wycznie koncepcyjn i maj
suy przede wszystkim ludziom korzystajcym ze rodowiska .NET, umoliwiajc
lepsz orientacj w jego zawartoci. Istnieje na przykad konwencja mwica, e podrzdna
przestrze nazw powinna by zalena od przestrzeni nadrzdnej, ale nie odwrotnie1.
Dla rodowiska CLR zwizki zawierania przestrzeni nazw nie maj adnego znaczenia.
Zawierane przestrzenie nazw mog by zapisywane w osobnych kompilatach, niezalenych od nadrzdnej przestrzeni nazw, a takie kompilaty mog by instalowane, adowane i usuwane cakowicie niezalenie od kompilatw nadrzdnej przestrzeni nazw.
Najczciej jednak podrzdna przestrze nazw wykorzystuje elementy z przestrzeni
nadrzdnej, a odpowiednie kompilaty mog by zaadowane tylko wtedy, gdy dostpne
s te kompilaty nadrzdnej przestrzeni nazw. Ta zaleno nie jest jednak definiowana
przez nazwy przestrzeni nazw, ale przez bezporednie instrukcje nakazujce wczenie
danej przestrzeni nazw (w jzyku C# instrukcja ta nazywa si using, a w Delphi uses).

Kod zarzdzany i CLR


Kod tworzony przez Delphi dla .NET jest kodem porednim zapisanym w jzyku
MSIL (ang. Intermediate Language) przygotowanym przez Microsoft, ktry dopiero
w czasie pracy programu w CLR przetumaczony zostaje na kod maszynowy, dokadnie dopasowany do aktualnie uywanego w systemie procesora. Z tego powodu aplikacje .NET mog by uruchamiane tylko tym systemie, w ktrym zainstalowany jest
pakiet rodowiska .NET.
Kod, ktry wykonywany jest przez CLR, nazywany jest te kodem zarzdzanym (ang.
Managed Code). Na podobnej zasadzie, kod tworzony przez Delphi 7 okrelany jest
mianem kodu niezarzdzanego (ang. Non-managed Code), ktry tak naprawd jest do
pewnego stopnia zarzdzany, cho nie przez system operacyjny, ale przez procesor.
W przypadku kodu niezarzdzanego system operacyjny ogranicza si do zaadowania
kodu w okrelone miejsce w pamici i obsugi zgoszonych przez procesor narusze
zabezpiecze wystpujcych na przykad w sytuacji, gdy z powodu bdnie ustawionego wskanika program prbuje uzyska dostp do pamici zarezerwowanej dla systemu
operacyjnego.
Zupenie inaczej wyglda natomiast zarzdzanie kodem wykonywanym w ramach CLR:
rodowisko CLR moe odczyta wszystkie metadane zapisane w kompilatach i na ich
podstawie pozna typ kadej zmiennej stosowanej w programie oraz przeanalizowa
kod programu w najdrobniejszych szczegach. Nie ma tu moliwoci uzyskania dostpu
do obcych obszarw pamici, poniewa w zarzdzanym kodzie nie istniej wskaniki,
a CLR cay czas monitoruje wszystkie operacje na tablicach, nie pozwalajc na dostpy
wykraczajce poza zakres tablicy. Innymi sowy, kod zarzdzany tylko wtedy moe
spowodowa naruszenie zabezpiecze, kiedy nieprawidowo dziaa bdzie CLR.
Pozostae cechy rodowiska CLR, ktre mona zaliczy do kategorii zarzdzanie,
to automatyczne zwalnianie pamici (ang. Garbage Collector) i monitorowanie regu
bezpieczestwa zabraniajcych na przykad zapisywania plikw klasom pochodzcym
z internetu i wykonywanym w przegldarce internetowej.
1

Wicej na ten temat znale mona w dokumentacji rodowiska .NET, w dokumencie Namespace
Naming Guidelines, ms-help://borland.bds3/cpgenref/html/cpconnamespacenamingguidelines.htm.

278

Delphi 2005

System wsplnych typw


Ze wzgldu na bardzo cise reguy stosowane w czasie automatycznego monitorowania
aplikacji przez CLR, nie ma ju potrzeby przechowywania poszczeglnych aplikacji
w cakowicie odizolowanych od siebie przestrzeniach pamici, tak jak dzieje si to w nowoczesnych systemach operacyjnych. Wszystkie aplikacje mog by adowane do tego
samego obszaru pamici i dziaa w ramach jednego egzemplarza rodowiska CLR.
Bardzo wan zalet takiego rozwizania jest moliwo realizowania atwej komunikacji pomidzy aplikacjami. Aplikacje odizolowane od siebie nawzajem mog
wymienia si danymi tylko poprzez specjalne mechanizmy, podczas gdy w rodowisku
CLR aplikacje mog przekazywa sobie i wspuytkowa cae obiekty.
W tym wszystkim najbardziej rewolucyjne jest to, e nie ma tu znaczenia jzyk, w jakim
przygotowany zosta dany obiekt. Aplikacja napisana w jzyku C# moe korzysta
z obiektw przygotowanych w Delphi, a Delphi moe z kolei korzysta z obiektw
tworzonych w VB.NET. Co wicej, mona napisa klas w Delphi, bdc rozbudow
klasy przygotowanej oryginalnie w jzyku C#, a klasa ta moe by tworem zbudowanym
na podstawie klasy rodowiska .NET.
Kluczem do moliwoci wsplnego wykorzystywania obiektw jest jeden z elementw
rodowiska CLR: wsplny system typw (ang. Common Type System CTS). Definiuje on model obiektw kadej aplikacji rodowiska .NET, a take typy podstawowe,
z ktrymi pracowa musz wszystkie aplikacje i z ktrych mona budowa bardziej
zoone typy i klasy. Znany z Delphi typ SmallInt jest tylko inn nazw dla funkcjonujcego w rodowisku .NET typu Int16. Kada klasa w Delphi wrd swoich przodkw
ma przynajmniej klas Object pochodzc ze rodowiska .NET. Podobnie, wszystkie
pozostae jzyki programowania .NET w ten czy inny sposb wykorzystuj typ Int16
i klas Object.
Jak wida, CTS jest wsplnym systemem typw obowizujcych wszystkie jzyki dostpne w rodowisku .NET i w czasie dziaania tworzy wsplne implementacje wszystkich podstawowych typw, wykorzystywanych we wszystkich aplikacjach .NET. Jak
wiemy, wszystkie aplikacje dziaajce w rodowisku .NET skadaj si z klas rozwijajcych klasy CTS, wobec tego kad aplikacj mona traktowa jak cz CTS lub
jego rozszerzenie.
Wicej szczegw na temat zwizkw pomidzy typami CTS a typami Delphi podawa bd w podrozdziale 3.6.

3.1.2. Przestrzenie nazw w Delphi


Jako programici tworzcy w Delphi, zwizkami czcymi przestrzenie nazw i kompilaty martwi si musimy dopiero wtedy, gdy sytuacja zmusza nas do wykorzystania
obcych przestrzeni nazw i kompilatw. W programowaniu w Delphi przestrzenie
nazw i kompilaty wynikaj ze starszych poj funkcjonujcych w jzyku Object Pascal
moduu (ang. unit), programu (ang. program) i pakietu (ang. package):

Rozdzia 3. Jzyk Delphi w rodowisku .NET

279

Kady projekt w Delphi kompilowany jest do jednego kompilatu i w zwizku

z tym zawiera w sobie te przestrzenie nazw, ktre tworzone s w moduach


tego projektu.
Kady modu w Delphi automatycznie tworzy now przestrze nazw, o nazwie

zgodnej z nazw moduu. Modu MyUnit.pas powizany jest z przestrzeni


nazw MyUnit, a jeeli modu ten znajdowa si bdzie w projekcie MyProject.dpr,
to z plikiem projektu powizana zostanie kolejna przestrze nazw, o nazwie
MyProject. Jedyn metod pozwalajc na rczn zmian nazewnictwa przestrzeni
nazw jest zmiana nazewnictwa moduw.

Tworzenie przestrzeni nazw


W czasie nadawania nazw nowym moduom i programom tworzymy jednoczenie nazwy
przestrzeni nazw, dlatego twrcy jzyka Delphi w firmie Borland pozwolili na stosowanie kropek w nazwach programw i moduw. Dziki temu moemy teraz nazwa plik
projektu na przykad MojaFirma.MojProgram.dpr, a jednemu z moduw nada nazw
MojaFirma.MojProgram.UI.Dialogi.Login.pas. Na podstawie nazwy pliku zawierajcej kropki kompilator przygotuje nazw przestrze nazw, usuwajc z nazwy pliku
ostatni czon wraz z kropk, tak jak pokazano to na listingu 3.1.
Listing 3.1. Sposb tworzenia przestrzeni nazw na podstawie nazw programw i moduw
// Pierwszy wiersz w pliku .dpr:
program MojaFirma.MojProgram;
// -> Przestrze nazw nazywa si MojaFirma
// Pierwszy wiersz w pliku .pas:
unit MojaFirma.MojProgram.UI.Dialogi.Login;
// -> Przestrze nazw nazywa si MojaFirma.MojProgram.UI.Dialogi

Mimo kropek znajdujcych si w nazwach, kompilator Delphi rozpoznaje je tylko jako


cao. Jeeli w programie uyjemy osobnego identyfikatora, takiego jak MojaFirma lub
Login, to kompilator zgosi bd, informujc nas o znalezieniu nieznanego identyfikatora.
Informacja dla osb, ktre pracoway ju w Delphi 8: w Delphi 2005 zmieniona
zostaa stosowana w kompilatorze konwencja nazewnictwa przestrzeni nazw.
W Delphi 8 nie by usuwany ostatni czon nazwy, tak wic w podawanym wyej
przykadzie nazwa moduu w caoci wchodziaby do nazwy przestrzeni nazw,
wcznie z ostatnim czonem Login. Jeeli wemiemy pod uwag fakt, e przestrzenie
nazw s kontenerami dla typw, a w module Login z ca pewnoci zadeklarowany
bdzie typ formularza o nazwie LoginForm, to dojdziemy do wniosku, e mechanizm nazywania stosowany w Delphi 2005 jest lepiej zorganizowany. W Delphi 2005
peny identyfikator typu tego formularza otrzymaby nazw MojaFirma.MojProgram.
UI.Dialogi.LoginForm, podczas gdy w Delphi 8 ten sam identyfikator otrzymaby nieco
dusz i powtarzajc si na kocu nazw MojaFirma.MojProgram.UI.Dialogi.
Login.LoginForm.

280

Delphi 2005

Korzystajc z narzdzia Reflection, mona kontrolowa przestrzenie nazw, jakie zapisywane s w kompilatach tworzonych przez Delphi. Kada z nazw tych przestrzeni
nazw rozkadana jest zgodnie z umieszczonymi w niej kropkami na czci okrelajce
hierarchiczne poziomy, a kademu poziomowi przyporzdkowany jest osobny symbol
folderu. W kompilacie naprawd znajduj si tylko te przestrzenie nazw, przy ktrych
symbolach folderw znajduj si kolejne wzy podrzdne, a przynajmniej jeden symbol
o nazwie Unit.
Na rysunku 3.1 przedstawiono projekt, do ktrego w ramach porwnania doczono
trzy podobne nazwy moduw (prosz przyjrze si zawartoci okna menedera projektu znajdujcego si w prawej dolnej czci rysunku. Przedstawiony projekt zosta
skompilowany do pliku .exe i zaadowany do programu Reflection.
Rysunek 3.1.
Moduy Delphi
wywietlane
w menederze
projektu i powizane
z nimi przestrzenie
nazw wywietlane
w programie
Reflection

Wykorzystywanie przestrzeni nazw i kompilatw


Chcc w swoim programie wykorzystywa symbole pochodzce z innych przestrzeni
nazw, musimy wymieni je po sowie kluczowym uses we wszystkich moduach programu, w ktrych uywane s symbole danej przestrzeni nazw. W module MonitorForm
z przykadowego programu SystemLoadMonitor (punkt 1.5.4) wykorzystywany jest
cay szereg klas rodowiska .NET, w zwizku z czym do moduu musiao zosta doczonych kilka przestrzeni nazw, o czym mona przekona si, przegldajc listing 3.2.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

281

Listing 3.2. Przestrzenie nazw uywane w module MonitorForm programu SystemLoadMonitor


unit MonitorForm;
interface
uses
System.Drawing, System.Collections,
System.ComponentModel, System.Windows.Forms, System.Data,
System.Diagnostics, Microsoft.Win32,
System.Runtime.InteropServices, System.Globalization;

Oprcz tego, kompilator musi jeszcze wiedzie, w ktrych kompilatach bdzie mg


znale podane przestrzenie nazw (a dokadniej: symbole zapisane w podanych przestrzeniach nazw). W tym celu do projektu naley doda odpowiednie referencje, umieszczajc je w menederze projektu, w gazi References (prosz zobaczy rysunek 3.2).
Rysunek 3.2.
Kompilaty,
do ktrych odwouje
si program, musz
zosta wymienione
w menederze projektu

Kompilaty przedstawione na rysunku dodawane s do projektu automatycznie, zaraz


po utworzeniu nowego projektu. Przechowuj one w sobie bardzo wiele wanych klas
rodowiska .NET. Przy okazji dodawania do formularza nowego komponentu Delphi
rozbudowuje list kompilatw, uzupeniajc j w razie potrzeby o wszystkie te kompilaty,
ktrych wymaga nowo dodany komponent.
Jeeli w programie korzysta chcemy z klas, ktre nie s umieszczone w standardowych kompilatach, to musimy wasnorcznie rozbudowa list kompilatw, dodajc
do niej nowe pozycje. W tym celu naley wywoa z menu kontekstowego menedera
projektu pozycj Add Reference i wybra jeden lub kilka kompilatw z przedstawionej
listy (okno z t list zobaczy mona na rysunku 3.3). Po naciniciu przycisku Add
Reference wybrane kompilaty przenoszone s do dolnej listy New References, a po
naciniciu przycisku OK kompilaty znajdujce si na dolnej licie doczane s do
projektu. Jeeli potrzebny nam kompilat nie jest obecny na przedstawionej w oknie licie,
to korzystajc z przycisku Browse moemy wyszuka na dysku dowolny kompilat.

282

Delphi 2005

Rysunek 3.3.
Wybrane zostay
dwa kompilaty
rodowiska .NET,
ktre zajmuj si
funkcjonowaniem
komponentw
w czasie
projektowania
aplikacji

Kada referencja wymieniona w menederze projektu wpisywana jest te do pliku


projektu (zawarto tego pliku zobaczy mona, wybierajc z menu pozycj Project\
View Source), ktrego przykad mona zobaczy na listingu 3.3.
Listing 3.3. Referencje kompilatw zapisane w pliku projektu
program SystemLoadMonitor;
{%DelphiDotNetAssemblyCompiler
'$(SystemRoot)\microsoft.net\framework\v1.1.4322\System.dll'}
{%DelphiDotNetAssemblyCompiler
'$(SystemRoot)\microsoft.net\framework\v1.1.4322\System.Data.dll'}
...

Konsolidacja statyczna i dynamiczna


Do programw przygotowanych w Delphi kompilaty rodowiska .NET doczane s
dopiero w czasie ich dziaania, co oznacza, e doczone s one poprzez konsolidacj
dynamiczn. Czci samego programu, czyli jego moduy, wczane s do pliku .exe ju
w czasie kompilacji programu (konsolidacja statyczna). W przypadku moduw dostarczanych razem z Delphi nie jestemy ograniczeni do stosowania konsolidacji statycznej, ale wszystkie biblioteki czasu wykonania obecne w Delphi mona te docza
do programu dynamicznie.
W tym miejscu ponownie trzeba wykorzysta polecenie menedera projektu Add Reference.
Biblioteki dostarczane razem z Delphi zapisane s w szeregu plikw .dll, do ktrych
referencje moemy umieci w naszym programie. Moduy znajdujce si w tak doczonych do programu bibliotekach nie musz by ju fizycznie integrowane z plikiem .exe
aplikacji w procesie kompilacji.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

283

May przykad: W kadej aplikacji tworzonej w Delphi nieodzowna jest jedna z bibliotek
czasu wykonania dostarczanych razem z Delphi (modu System), ktra znajduje si
w pliku borland.delphi.dll. Jeeli bibliotek t dodamy do naszej aplikacji tak jak pokazano na rysunku 3.4, to w efekcie uzyskamy znacznie mniejszy plik wykonywalny
aplikacji. W minimalnej aplikacji Windows-Forms, skadajcej si z jednego pustego
formularza, wielko pliku .exe spada z 21 do 7 kB, a najprostsza aplikacja konsolowa
kompilowana z wykorzystaniem konsolidacji dynamicznej zamyka si w pliku o wielkoci
5 kB. W przypadku aplikacji konsolowych korzystajcych z moduu SysUtils konieczne
jest doczenie do programu referencji biblioteki borland.vcl, co ma zablokowa wczenie tej biblioteki do pliku wykonywalnego.
Rysunek 3.4.
Dynamicznie
konsolidowane
mog by te
biblioteki czasu
wykonania
dostarczane
razem z Delphi

Tworzc aplikacje konsolidowane dynamicznie musimy upewni si, e kompilaty, do


ktrych tworzymy referencje, rzeczywicie znajdowa si bd na komputerze, na ktrym
pracowa ma nasza aplikacja.
Jeeli chcielibymy si dowiedzie, czy w pliku .exe naszej aplikacji nadal znajduj
si niepotrzebnie obciajce go biblioteki, to wystarczy otworzy ten plik w IDE
Delphi, a wszystkie dane pliku wywietlone zostan w narzdziu Reflection. Jeeli
w wywietlanym drzewie nie ma gazi o nazwie Borland, to znaczy, e w pliku nie
zostay zapisane adne moduy przygotowane przez firm Borland.
Wymaganie powizania kadej aplikacji Delphi (w sposb statyczny lub dynamiczny)
z moduem System nie jest niczym wyjtkowym, poniewa takie samo wymaganie
istnieje we wszystkich innych jzykach rodowiska .NET z wyjtkiem jzyka C#.
Doczenie biblioteki dopasowujcej jzyk do podstawy tworzonej przez CLR wymagane jest take w jzykach VB.NET, i C++ dla .NET. Jedynie jzyk C# nie potrzebuje stosowania takiego dopasowania, poniewa zosta on od podstaw zaprojektowany do wsppracy ze rodowiskiem .NET.
Wszystkie trzy przykady tworzenia aplikacji (aplikacja WinForms konsolidowana
statycznie, aplikacja WinForms konsolidowana dynamicznie i aplikacja konsolowa
konsolidowana dynamicznie) znale mona na pycie CD doczonej do ksiki
w katalogu Rozdzial3\LinkSorts.

284

Delphi 2005

3.1.3. Kompilaty w Delphi


Jak ju mwiem w punkcie 3.1.2, kompilator Delphi przeksztaca projekt aplikacji
w kompilat rodowiska .NET. O typie tworzonego kompilatu (jak wiemy, istniej dwa
rodzaje plikw kompilatw) decyduje typ projektu przygotowywanego w Delphi.

Kompilaty EXE
Jeeli projekt Delphi zapisywany jest w pliku o rozszerzeniu .dpr, a tekst rdowy
projektu rozpoczyna si od sowa program, to taki projekt moe by nazywany rwnie aplikacj, a Delphi na jego podstawie przygotowywa bdzie plik (kompilat)
wykonywalny, ktrego rozszerzenie bdzie identyczne z rozszerzeniem aplikacji stosowanych w rodowisku Win32 .exe. To rozszerzenie informuje nas te o tym, e
plik ten moe by uruchamiany dokadnie tak samo jak kady inny plik o rozszerzeniu .exe, na przykad dwukrotnym klikniciem w Eksploratorze Windows albo wywoaniem z wiersza polece. Takie dziaanie jest moliwe dlatego, e kady z takich
plikw zawiera w sobie kod maszynowy, ktry moe by uruchomiony w systemach
Win32. Jedynym zadaniem tego kodu jest sprawdzenie obecnoci w systemie rodowiska .NET i CLR, i w przypadku ich znalezienia wywoanie tego rodowiska w celu
uruchomienia waciwej aplikacji.
W IDE Delphi dostpne s wstpnie przygotowane szablony projektw przeznaczone
do budowania rnych aplikacji, takich jak aplikacje konsolowe (ang. Console Application),
aplikacje Windows-Forms (ang. Windows Forms Application) lub aplikacje VCL (ang.
VCL Forms Application; s to pozycje menu File\New\Other).

Kompilaty DLL
Kompilaty DLL tworzone s wtedy, gdy kompilowany jest pakiet (ang. Package)
Delphi. Projekt pakietu zapisywany jest w pliku rdowym o rozszerzeniu .dpk, w ktrym
najwaniejszym sowem jest sowo kluczowe package. Nowy projekt pakietu rodowiska
.NET tworzony jest w IDE Delphi wywoaniem pozycji menu File\New\Other\Delphi
for .NET Projects\Package.
Pakiety rwnie mog mie wasne referencje na inne kompilaty, ale w menederze projektw nie s one nazywane referencjami (ang. References), ale wypisywane s w wle
Required (wymagane), co dobrze wida na rysunku 3.5.

Rysunek 3.5. Przykadowa biblioteka dynamiczna zawierajca jeden formularz, przedstawiona


w menederze projektu

Rozdzia 3. Jzyk Delphi w rodowisku .NET

285

Przykad przedstawiony na rysunku (na pycie CD znale mona go w katalogu


Rozdzial3\Packages\FormTestpackage.dpr) jest pakietem, ktry ma za zadanie udostpnia poprzez bibliotek dynamiczn prosty formularz testowy. Pakiet ten przygotowany zosta w trzech bardzo prostych krokach:
Utworzenie nowego pakietu wywoaniem z menu pozycji File\New\Other\
Delphi for .NET Projects\Package (na tym etapie w wle Requires znajduje
si tylko pozycja Borland.Delphi.dll).
Przygotowanie nowego formularza wywoaniem z menu pozycji File\New\

Other\Delphi for .NET Projects\New Files\Windows Form (w tym momencie


do menedera projektu dodawane s pozostae wpisy).
Uoenie na formularzu etykiety pozwalajcej na rozpoznanie go w czasie

testowych wywoa.

Jawne uycie moduw


Najwaniejsza regua tworzenia pakietw mwi, e wszystkie moduy musz by jawnie
dowizywane do pakietu, co oznacza, e:
Albo modu musi znajdowa si w bibliotece dynamicznej, ktra dowizywana

jest do pakietu poprzez referencj (w menederze projektu wypisana musi by


w gazi Requires jeeli do tego wza dodamy nowe pozycje korzystajc
z polecenia Add Reference, to operacj t przeprowadza bdziemy w oknie
przedstawionym na rysunku 3.3).
Albo modu musi znajdowa si w wle o nazwie Contains (zawiera).

W menu kontekstowym tego wza znajdziemy pozycj Add, ktra


otwiera okno dialogowe umoliwiajce wybranie potrzebnego nam moduu.
Wszystkie biblioteki DLL pochodzce z Delphi powinny by dynamicznie doczane
do tworzonych pakietw, poniewa tylko taka konfiguracja pozwoli na stosowanie
pakietu wewntrz aplikacji przygotowanej w Delphi, ktra rwnie wymaga zastosowania
tych samych bibliotek dynamicznych. Jeeli w tworzonym pakiecie usuniemy z wza
Requires zapisan w nim bibliotek Borland.Delphi.dll, to modu Borland.Delphi.
System zostanie doczony do pakietu statycznie. W takiej sytuacji uycie tego pakietu
w aplikacji przygotowanej w Delphi wizaoby si z dwukrotnym uruchomieniem biblioteki DLL jednym z aplikacji, a drugim z uywanego przez ni pakietu. Po wykryciu takiego przypadku kompilator przerwie kompilowanie aplikacji i wypisze nastpujcy komunikat:
[Fatal Error] Package 'FormTestPackage' already contains unit
'Borland.Delphi.System'.

Uywanie samodzielnie przygotowanych pakietw


W celu dowizania pakietu do wasnego projektu naley skorzysta z procedury przedstawionej w punkcie 3.1.2 i doda do projektu referencj pliku .dll pakietu. Jeeli tego nie
zrobimy, a jedynie umiecimy nazwy moduw zawartych w pakiecie w klauzuli uses, to
kompilator doczy te moduy bezporednio do pliku wykonywalnego naszego projektu.

286

Delphi 2005

Na rysunku 3.6 zobaczy mona, w jaki sposb przygotowana przed chwil biblioteka
dynamiczna o nazwie FormTestPackage.dll wykorzystywana jest w aplikacji tworzonej w Delphi. Przygotowany zosta nowy projekt aplikacji Windows-Forms, a biblioteka
DLL doczona zostaa do niego w menederze projektu za pomoc pozycji menu
Required/Add reference/Browse. Nastpnie modu z biblioteki zosta wprowadzony
do moduu formularza aplikacji poprzez dopisanie jego nazwy do klauzuli uses (w czasie
uzupeniania listy uses mona skorzysta z pomocy w programowaniu).

Rysunek 3.6. Przykadowa biblioteka dynamiczna z formularzem przedstawiana w menederze projektu

Kolejnym krokiem byo umieszczenie na formularzu aplikacji przycisku opisanego


Wywoaj formularz biblioteki, ktry opatrzymy zosta bardzo prost procedur obsugi
zdarzenia Click, przedstawion na listingu 3.4.
Listing 3.4. Procedura wywoujca formularz pobrany z biblioteki dynamicznej
// Na pycie CD: Rozdzial3\Packages\DLLUser.bdsproj (projekt Delphi)
procedure TWinForm2.Button1_Click(sender: System.Object;
e: System.EventArgs);
begin
FormForDll.TWinForm2.Create.ShowDialog;
end;

Nic wicej nie trzeba program i biblioteka DLL s od razu gotowe do uruchomienia.
Naley tu zauway, e rwnie atwo biblioteki napisane w Delphi mona wykorzystywa w programach tworzonych w jzyku C#. Kroki, jakie naley w tym celu wykona w pakiecie C#-Builder, s niemal identyczne z tymi znanymi z Delphi: utworzy

Rozdzia 3. Jzyk Delphi w rodowisku .NET

287

nowy projekt, przygotowan w Delphi bibliotek dopisa do referencji projektu, dowiza przestrze nazw (w C# zamiast sowa uses stosowane jest sowo using) i wywoa formularz. Ten ostatni krok wymaga uycia nieco zmienionej skadni, poniewa z punktu widzenia jzyka C# konstruktory nie tylko nie nazywaj si Create, ale
w ogle nie maj nazwy i wywoywane s przez operator new. Kod takiego wywoania
zapisanego w jzyku C# podaj na listingu 3.5, a wynik dziaania tego kodu zobaczy
mona na rysunku 3.7.
Listing 3.5. Procedura wywoujca formularz pobrany z biblioteki dynamicznej zapisana w jzyku C#
// Na pycie CD: Rozdzial3\Packages\CSharpDLLUser.bdsproj (projekt C#-Builder)
using FormForDll;
...
private void button1_Click(object sender, System.EventArgs e)
{
(new FormForDll.TWinForm2()).ShowDialog();
}

Rysunek 3.7. Formularz skompilowany w Delphi wywietlany jest przez aplikacj napisan w jzyku C#.
Tutaj jeszcze w starym, oddzielnym pakiecie C#-Builder, ktry teraz zintegrowany jest z Delphi 2005

W punkcie 6.1.4 dokadniej przyjrzymy si pakietowi przykadowych komponentw


przygotowanych na potrzeby tej ksiki, ktry oprcz tego, e zawiera kilka ciekawych komponentw, jest przykadem cakowicie normalnego pakietu.

Biblioteki z punktami wejcia Win32


Specjaln alternatyw tworzenia bibliotek dynamicznych rodowiska .NET jest projekt
typu library, ktry w IDE Delphi znale mona w pozycji menu File\New\Other\
Library. Po wybraniu tej pozycji menu otrzymamy nowy plik .dpr, ktry nie bdzie
rozpoczyna si sowem kluczowym program, ale sowem library. W czasie kompilowania takiego projektu Delphi rwnie przygotuje kompilat dynamicznie adowanej
biblioteki, ktra waciwie niczym nie bdzie rnia si od skompilowanego pakietu.

288

Delphi 2005

Rnica midzy pakietami a bibliotekami polega na tym, e pliki .dll typu library
mog by te wywoywane przez aplikacje Win32 pracujce w kodzie niezarzdzanym.
W tym celu kompilator musi generowa, niebezpieczne z punktu widzenia rodowiska
.NET, punkty wejcia uywane w rodowisku Win32, a zatem musimy jawnie zezwoli na stosowanie niebezpiecznego kodu. Procedury, ktre mog by wywoywane przez aplikacje Win32, musz by dodatkowo wypisane w klauzuli exports, tak
jak pokazano na listingu 3.6.
Listing 3.6. Szkielet kodu biblioteki zawierajcej procedur dostpn te w rodowisku Win32
{$UNSAFECODE ON}
library Library1;
...
procedure F1; begin end;
exports
F1; // procedura F1 moe by wywoywana przez aplikacje Win32
end.

Bez bardzo wanych powodw nie naley stosowa dyrektywy kompilatora $UnsafeCode, poniewa przygotowany z jej uyciem kompilat nie moe na przykad zosta
zweryfikowany jako kompilat typowy przez narzdzie wiersza polece PEVerify dostpne w pakiecie .NET SDK. Wynika z tego, e normalne biblioteki dynamiczne rodowiska .NET powinny by generowane w Delphi jako zwyczajne pakiety.

Manifest kompilatw
Kady kompilat rodowiska .NET zawiera w sobie tak zwany manifest, w ktrym zapisane
s midzy innymi nastpujce dane:
kompilaty, jakich oczekuje dany kompilat (s to kompilaty, ktre w menederze
projektu w Delphi wypisywane s w wle Requires lub References). Kady

z tych kompilatw opatrywany jest numerem wersji, a czasami rwnie


specjalnym kluczem uniemoliwiajcym zastosowanie przy kolejnych
uruchomieniach programu zmodyfikowanych lub faszywych kompilatw.
atrybuty samego kompilatu, takie jak nazwa produktu, nazwa producenta,

prawa wasnoci i numer wersji.


Pierwsza cz manifestu tworzona jest automatycznie przez Delphi na podstawie danych zapisanych w menederze projektu, natomiast na ksztat drugiej czci manifestu
wpywa mona poprzez rczne modyfikacje tekstu rdowego projektu, czyli edytowanie standardowo wstawianych do niego atrybutw. W tym celu naley wywietli
zawarto kodu rdowego projektu (pozycja menu Project\View source), rozwin
ukryty region tekstu o nazwie Program/Assembly information i zamiast pustych cigw
znakw wstawi odpowiednie dla danego projektu dane. Przykad takiego uzupeniania
danych manifestu przedstawiam na listingu 3.7.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

289

Listing 3.7. Cz danych manifestu kompilatu modyfikowa mona rcznie


{$REGION 'Program/Assembly Informations'}
...
[assembly: AssemblyDescription('Komponenty do ksiki o Delphi 8')]
[assembly: AssemblyConfiguration('')]
[assembly: AssemblyCompany('EWLAB')]
[assembly: AssemblyProduct('')]
[assembly: AssemblyCopyright('Copyright 2004 Elmar Warken')]
[assembly: AssemblyTrademark('')]
[assembly: AssemblyCulture('')]
[assembly: AssemblyTitle('Tytu mojego kompilatu')] // nadpisuje ustawienia
// pobrane z opcji Project\Options\Linker\Exe description
[assembly: AssemblyVersion('1.0.0.0')]
...
{$ENDREGION}

Jeeli chodzi o numer wersji kompilatu, to trzeba powiedzie, e w rodowisku .NET


skada si on z czterech czci. Pierwsze dwie czci okrelaj numer gwny i poboczny (w podanym wyej listingu by to numer 1.0), a za nimi znajduj si numer
kompilacji (ang. Build number) i numer poprawki (ang. Revision). Jak si przekonamy
za chwil w podpunkcie Instalacja kompilatw, rodowisko .NET moe przechowywa wiele wersji tej samej biblioteki, zapisujc je w globalnej skadnicy kompilatw
(ang. Global Assembly Cache GAC). W pliku konfiguracyjnym aplikacji zapisa
mona te, ktra wersja kompilatu zapisana w GAC ma by wykorzystywana w powizaniu z t aplikacj.
Standardowo nowy projekt w Delphi otrzymuje numer 1.0.*, gdzie znak gwiazdki (*)
zastpowany jest automatycznie przez kompilator odpowiednimi numerami kompilacji i poprawek. Numer kompilacji powikszany jest o jeden kadego dnia, natomiast
numer poprawki zmienia si na bieco w trakcie prac nad aplikacj. Wynika z tego,
e dwie nastpujce po sobie kompilacje rozrni mona ju po ich numerze wersji.
Jeeli gwiazdka zostanie zastpiona konkretnymi numerami wersji (tak jak w powyszym
listingu), to opisane przed chwil automatyczne mechanizmy generowania kolejnych
numerw wersji zostaj wyczone.

Podpisane kompilaty
Kompilat moe zosta zabezpieczony przed prbami dokonywania w nim pniejszych zmian i innych manipulacji. Zabezpieczenie takie polega na nadaniu mu mocnej
nazwy (ang. Strong name), skadajcej si z pary specjalnie w tym celu przygotowanych kluczy. Jeden z tych kluczy jest kluczem tajnym, przechowywanym na komputerze wytwrcy kompilatu, ktry w czasie kompilowania porednio wpywa na ksztat
danych tekstowych zapisywanych do kompilatu. Drugi klucz publiczny jest o wiele
krtszy i moe by jawnie zapisywany wewntrz kompilatu.
Za pomoc klucza publicznego CLR moe w czasie adowania kompilatu stwierdzi,
czy znajduje si on w oryginalnym stanie. Manipulacja dokonana w kompilacie bdzie
moga by zatajona przed CLR tylko wtedy, gdy podobnej manipulacji poddany zostanie
klucz tajny. Prby wprowadzenia takiej modyfikacji klucza s jednak z gry skazane
na niepowodzenie, poniewa kada aplikacja, ktra dowizuje do siebie kompilaty

290

Delphi 2005

za pomoc nazwy mocnej, zapisuje w sobie (a dokadniej wewntrz swojego manifestu)


znany klucz publiczny tych kompilatw (wzgldnie jego skrcon form, czyli tak
zwany ekstrakt klucza), przez co wszystkie manipulacje na tych kompilatach wykrywane s przez CLR w czasie ich adowania, co powoduje wygenerowanie wyjtku.
Dla przykadu, do przedstawionej wyej biblioteki dynamicznej FormTestPackage dodamy mocn nazw. Na pocztku musimy przygotowa klucz pakietu i zapisa go
w pliku FormTestPackage.snk. W tym celu skorzysta musimy z programu sn.exe bdcego czci pakietu .NET SDK, a dostpnego w katalogu Program Files\Microsoft.
NET\SDK\[NumerWersji]\Bin:
sn -k FormTestPackage.snk

Nastpnie plik z kluczem musi zosta dodany do kompilatu. W pliku rdowym


projektu FormTestPackage zmieni naley tylko jeden z trzech atrybutw powizanych
z podpisami kompilatw, tak jak pokazano to na listingu 3.8.
Listing 3.8. Wpisy w atrybutach opisujcych podpisy kompilatw
[assembly: AssemblyDelaySign(false)] // bez zmian
[assembly: AssemblyKeyFile('FormTestPackage.snk')]
[assembly: AssemblyKeyName('')] // bez zmian

Teraz biblioteka moe zosta ponownie skompilowana, a powstay plik .dll bdzie chroniony przed modyfikacjami przez wprowadzon do biblioteki mocn nazw. Ochron t
mona jeszcze skontrolowa, ponownie wywoujc program sn.exe (wywietlane przez
niego informacje przedstawiam na rysunku 3.8):
sn -Tp FormTestPackage.dll

Rysunek 3.8. Program sn.exe potwierdza, e klucz publiczny zosta przez Delphi dopisany
do biblioteki DLL. Wypisywany jest rwnie skrt klucza

Biblioteki chronione nazwami mocnymi s w Delphi traktowane dokadnie tak samo


jak normalne pliki .dll i mona je dodawa do projektw wywoujc w menederze
projektu polecenie Add reference oraz dopisujc nazwy moduw i przestrzeni nazw z tej
biblioteki do listy uses. Kompilator automatycznie zapisze skrt klucza takich bibliotek
do manifestu tworzonego kompilatu.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

291

W czasie przeprowadzanych przez mnie testw, z tak przygotowanej biblioteki FormTestPackage.dll skorzysta mona byo wycznie w programie tworzonym w C#Builder. Z niewiadomych powodw Delphi zapisywao do manifestu programu nieprawidowy skrt klucza, w zwizku z czym CLR odmawiao zaadowania biblioteki. Przygotowywany przez Delphi manifest mona przejrze za pomoc programu ILDasm.exe,
a wypisany przez niego skrt klucza musi by cakowicie zgodny ze skrtem podawanym przez program sn.exe (odpowiednie dane w tych programach zaznaczone zostay na rysunku 3.9).

Rysunek 3.9. W lewym grnym rogu widoczny jest skrt klucza publicznego, ktry wedug kompilatora C#
powinien znajdowa si w bibliotece. W prawym grnym rogu widoczny jest skrt klucza publicznego,
przy ktrym upiera si Delphi. Poniej wida skrt klucza publicznego rzeczywicie wpisany do biblioteki
Podpisan wersj biblioteki znale mona na pycie CD doczonej do ksiki,
w katalogu Rozdzial3\Signed Assemblies, obok projektw jzyka C# i Delphi korzystajcych z tej biblioteki. Do zainstalowania tego przykadu konieczne jest
rczne wywoanie programu sn -i przed kompilowaniem projektw.

Instalacja kompilatw
Do tej pory korzystalimy tylko z kompilatw dostarczanych przez firmy Microsoft
i Borland albo z wasnych kompilatw, ktre zapisane byy w tym samym katalogu co
plik wykonywalny naszej aplikacji. Oba te warianty przedstawiaj dwa podstawowe
sposoby instalowania bibliotek dynamicznych rodowiska .NET.
W przypadku kompilatw wykorzystywanych przez wiele rnych aplikacji waciwym miejscem instalowania jest globalna skadnica kompilatw GAC. Tutaj znajdziemy kompilaty dostarczane przez firmy Microsoft i Borland, ale take wszystkie te

292

Delphi 2005

kompilaty, ktre samodzielnie w niej zainstalujemy (w GAC instalowane mog by


tylko te kompilaty, ktre zabezpieczone zostay mocn nazw). Instalacja przedstawionej
wyej, podpisanej biblioteki realizowana jest poniszym poleceniem:
gacutil /i FormTestPackage.dll

W kadej chwili moemy te sprawdzi zawarto skadnicy, przegldajc w tym celu


katalog [KatalogSystemuWindows]\assembly. Dziki rozszerzeniom powoki instalowanym razem ze rodowiskiem .NET, Eksplorator Windows przedstawia ten katalog nie w swojej fizycznej, zagniedonej strukturze, ale wywietla wszystkie kompilaty w postaci przejrzystej listy, ktr zobaczy mona na rysunku 3.10.

Rysunek 3.10. Po zainstalowaniu Delphi 8 i C#-Builder wszystkie kompilaty przygotowane przez


firm Borland daj doskonay przykad tego, e w rodowisku .NET mona przechowywa obok siebie
kilka wersji tego samego kompilatu. W Delphi 2005 przedstawione na rysunku kompilaty maj numery
wersji zmienione na 2.0.0.0
Automatyczne kopiowanie doczanych kompilatw mona oczywicie wyczy.
Wystarczy w tym celu odnale referencj w menederze projektu i nada jego waciwoci Copy Local warto False.
Jeeli chodzi o umiejscowienie kompilatu, to rodowisko CLR jest bardzo elastyczne. Mona na przykad zdefiniowa inny standardowy katalog z dowizywanymi bibliotekami i zapisa go w pliku konfiguracyjnym projektu aplikacji (plik o rozszerzeniu .config znajdujcy si w katalogu aplikacji), wykorzystujc przy tym atrybut
codebase. Jeeli kompilat zostanie zainstalowany w GAC, to najprawdopodobniej
bdzie trzeba jeszcze dokonywa rozrnienia pomidzy jego rnymi wersjami.
Zagadnienie konfigurowania aplikacji wykracza niestety poza ramy tego rozdziau,
a na dodatek nie ma waciwie nic wsplnego z Delphi. O zoonoci dostpnych
w tym zakresie moliwoci przekona si mona przegldajc dokument z systemu
aktywnej pomocy dostpny w gazi: .NET Framework SDK\Configuring Applications.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

293

Instalowanie kompilatw w GAC nie jest jednak wymagane, poniewa CLR poszukuje najpierw kompilatw wykorzystywanych przez aplikacj w katalogu, w ktrym
bya ona uruchamiana. Jeeli w projekcie Delphi dowiemy pewien kompilat poleceniem Add Reference, a kompilat ten nie jest zapisany w skadnicy GAC i nie znajduje si te w katalogu projektu, to Delphi automatycznie skopiuje go do tego katalogu.
Proces kopiowania powtarzany bdzie przy kadej kompilacji projektu, chyba e oryginalny kompilat nie zmieni si od czasu ostatniej kompilacji.

3.1.4. Moduy Delphi


W punkcie 3.1.2 wskazywaem na podobiestwo przestrzeni nazw rodowiska .NET
i moduw Delphi. W tym miejscu zajmiemy si zawartoci i wewntrzn budow moduw Delphi, a przy okazji jeszcze raz sprawdzimy, jak bardzo moduy Delphi pokrywaj si z funkcjonujcym w rodowisku .NET pojciem przestrzeni nazw.

Zawarto przestrzeni nazw


Na poziomie rodowiska .NET przestrze nazw moe zawiera tylko jeden rodzaj elementw typy. Typ w rodowisku .NET zawsze jest typem obiektowym, czyli klas.
Poza tym definiowane mog by jeszcze nastpujce rodzaje typw:
typy wartoci (C#: struct, Delphi: record),
typy wyliczeniowe (C#: enum, Delphi: typ wyliczeniowy),
interfejsy (C# i Delphi: interface),
delegacje (C#: delegate, Delphi: typ metody, procedure of object),
klasy (C# i Delphi: class).

Wszystkie pi rodzajw typw mona definiowa rwnie w programach tworzonych w Delphi, wykorzystujc przy tym sowa kluczowe specyficzne dla Delphi (zostay one wymienione na powyszej licie). Kada z takich definicji typw musi by
w Delphi zapisana w sekcji pliku rdowego opisanej sowem type.

Zawarto moduw
Modu w Delphi skada si z nastpujcych elementw, ktre mog w nim wystpowa
w dowolnej kolejnoci, pod warunkiem, e nie przecz temu adne powizania czce
poszczeglne zadeklarowane elementy:
typy (sekcje pliku rdowego opisane sowem kluczowym type),
stae (sekcje const),
zmienne (sekcje var),
procedury i funkcje (nie ma opisu sekcji, ale kada procedura musi rozpoczyna
si od sowa kluczowego procedure, a kada funkcja od sowa kluczowego
function).

294

Delphi 2005

Wszystkie typy z jzyka Object Pascal mona atwo przeksztaci w odpowiadajce


im typy rodowiska .NET, ale nadal problemem pozostaj inne elementy jzyka, takie
jak stae, zmienne i metody, ktre w rodowisku .NET wystpuj wycznie jako elementy klasy, a w jzyku Object Pascal mog wystpowa cakowicie niezalenie od deklaracji klas, jako zmienne, stae i metody globalne.
Te reguy funkcjonowania jzyka Object Pascal nie powinny by zmieniane, dlatego
kompilator Delphi stosuje pewien wybieg pozwalajcy na uywanie globalnych staych,
zmiennych i metod, ktre opakowywane s w klas rodowiska .NET o nazwie Unit,
cakowicie niewidoczn dla programisty. Podczas gdy te globalne symbole mog by
normalnie zmieniane wewntrz programw tworzonych w Delphi, to zewntrzne kompilaty elementy te widzie bd jako cz klasy Unit.

Budowa moduu
Modu Delphi zbudowany jest tak, jak pokazano na listingu 3.9.
Listing 3.9. Oglna zawarto jednego moduu Delphi
unit ModuleName; {Nazwa moduu nie moe zosta pominita}
interface
[Klauzula uses]
[Deklaracje]
implementation
[Klauzula uses]
[Deklaracje i definicje]
Gwny program moduu zakoczony sowem "end."

Modu moe zawiera w sobie wszystkie elementy jzyka Object Pascal: procedury,
funkcje, zmienne, stae, typy i w szczeglnoci klasy, a zadaniem moduu jest czciowe
lub caociowe udostpnianie tych elementw innym moduom.
Wszystkie obiekty, ktre mog by uywane take przez inne moduy, musz by zadeklarowane w czci interface. W ten sposb wszystkie te identyfikatory uznawane
s za publiczne, czyli widoczne rwnie na zewntrz. W przypadku zmiennych, staych
i typw prostych wystarczajca jest tylko deklaracja, ale w przypadku funkcji i procedur, a take metod rnych klas konieczne jest te dopisanie ich definicji w sekcji
implementation. Wszystkie pozostae deklaracje umieszczone w sekcji implementacji
traktowane s jako prywatne.

Cykliczne czenie moduw


Powodem stosowania w moduach dwch osobnych instrukcji uses umieszczonych
w sekcjach interface i implementation jest umoliwienie cyklicznych zwizkw uytkowania, na przykad w sytuacji, gdy dwa moduy musz wykorzystywa si wzajemnie.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

295

W przykadzie przedstawionym na listingu 3.10, kompilator mgby wpa w nieskoczon ptl analizowania moduw, jeeli nie rozpoznaby prawidowo bdu w moduach
(instrukcja uses Unit2 nakazaaby mu wczyta dane z moduu Unit2, a znajdujca si
w nim instrukcja uses Unit1 ponownie nakazaaby wczytanie danych z moduu Unit1).
Listing 3.10. Nieprawidowy sposb tworzenia cyklicznych dowiza moduw
{pocztek pliku pierwszego moduu}
unit Unit1; interface uses Unit2;
{pocztek pliku drugiego moduu}
unit Unit2; interface uses Unit1;

W takim wypadku jedna z klauzul uses musi zosta przeniesiona do czci implementacji moduu, na przykad w sposb przedstawiony na listingu 3.11.
Listing 3.11. Waciwy sposb tworzenia cyklicznych dowiza moduw
{pocztek pliku pierwszego moduu}
unit Unit1; uses Unit2;
...
{pocztek pliku drugiego moduu}
unit Unit2;
interface
implementation
uses Unit1;

Jeeli kompilator zajmowa si bdzie analizowaniem moduu Unit1, to moe w ramach tego procesu wczyta interfejs moduu Unit2 i nie natrafi tam na instrukcj odsyajc go z powrotem do moduu Unit1. W trakcie analizowania moduu Unit1 kompilator zupenie nie interesuje si czci implementacji moduu Unit2. Co prawda
interfejs moduu Unit1 jest uzaleniony od moduu Unit2, ale interfejs moduu Unit2
jest cakowicie niezaleny od moduu Unit1. Przedstawione wyej rozwizanie powoduje oczywicie pewne komplikacje, poniewa uniemoliwia stosowanie identyfikatorw z moduu Unit1 w interfejsie moduu Unit2. Oznacza to, e dwa moduy wykorzystujce si wzajemnie musz zosta zaprojektowane tak, eby w interfejsie jednego
z nich nie byy uywane klasy ani typy deklarowane w drugim module.

Inicjalizacja i zamykanie moduw


W przedstawionej wyej konstrukcji moduw cz oznaczona jako gwny program
moduu wywoywana jest w momencie uruchamiania programu, co pozwala moduowi prawidowo si zainicjowa. Sekcj t mona rozpocz za pomoc tradycyjnego
sowa kluczowego begin lub wprowadzonego niedawno sowa initialization.
W kadym module zastosowa mona jeszcze sowo kluczowe finalization rozpoczynajce t cz kodu moduu, ktra zawsze wywoywana jest w momencie koczenia programu. Jak wida, zakoczenie moduu moe wyglda tak jak na listingu 3.12.

296

Delphi 2005

Listing 3.12. Kocowa cz moduu


initialization
{kod wykonywany przy inicjalizacji moduu}
finalization
{kod czyszczcy, wykonywany przy zamykaniu programu}
end.

3.1.5. Moduy Delphi dla nowicjuszy


Moduy jzyka Object Pascal wyrniaj si spord moduw innych jzykw programowania, takich jak C++, poprzez swoj wyjtkow niezaleno i zamknit
struktur. W jzyku C++ pliki mog by ze sob wizane na wiele rnych sposobw,
a jeden modu najczciej zapisywany jest w pliku implementacji i pliku nagwkowym (.cpp i .h), natomiast w jzyku Object Pascal modu jest pojedynczym zamknitym
plikiem tekstowym, zawierajcym w sobie zarwno cz interfejsu moduu (co odpowiada
plikowi nagwkowemu jzyka C++), jak i cz implementacji
Programici korzystajcy z jzykw Java lub C# przyzwyczajeni s do pracy z takimi
cakowicie kompletnymi plikami moduw. W Delphi bd musieli si dodatkowo
przyzwyczai do tego, e klasy rozbijane s na cz deklaracji i cz implementacji,
przy czym w czci deklaracji znajdowa si maj wycznie deklaracje klas publicznych,
a w czci implementacji zapisywane s implementacje wszystkich rodzajw klas.
Rozdzielenie na cz interfejsu i implementacji nie generuje a tak wielkiej iloci
dodatkowej pracy, jak mogoby si pocztkowo wydawa. W Delphi dostpna jest
funkcja automatycznego uzupeniania klas, ktra midzy innymi pozwala na automatyzacj synchronizacji czci interfejsu i implementacji moduu. Poza tym bardzo wygodne
jest to, e deklaracja klasy przechowuje wycznie nagwki metod, a nie ich peny kod.
Dziki temu mona atwo przejrze interfejs klasy nawet w najprostszym edytorze
nierealizujcym funkcji pomocniczych, takich jak zwijanie kodu lub okna struktury.

3.2. Obiekty i klasy


Gwn koncepcj w programowaniu zorientowanym obiektowo jest poczenie danych i kodu, ktre w programowaniu proceduralnym s od siebie cakowicie oddzielne,
wewntrz zamknitych struktur nazywanych obiektami. Obiekty, z ktrymi stykamy
si w projektancie formularzy, to przede wszystkim komponenty, kontrolki i formularze,
ale wrd waciwoci wywietlanych w oknie inspektora obiektw rwnie czsto spotyka si obiekty, takie jak obiekty Font definiujce czcionk stosowan w kontrolce lub
obiekty kolekcji przechowujce wpisy kontrolki ListBox, kolumny kontrolki ListView
lub zakadki kontrolki TabControl.
W rodowisku .NET nawet najprostsze wartoci, takie jak liczby lub cigi znakw,
mog by traktowane jako obiekty, dziki czemu liczb cakowit mona zamienia
w cig znakw stosujc stare proceduralne wywoanie:

Rozdzia 3. Jzyk Delphi w rodowisku .NET

297

str := IntToStr(liczba);
// Funkcja IntToStr moe by te wywoywana jako cz moduu SysUtils,
// w takim wypadku wywoanie naley zapisa tak:
str := SysUtils.IntToStr(liczba);

ale rwnie dobrze mona potraktowa liczb jak obiekt i skorzysta z jednej z jego
metod:
str := liczba.ToString;

Pojcie klasy
Jzyk Pascal zawsze by jzykiem bardzo restrykcyjnie pilnujcym typw, w ktrym
kada zmienna musiaa mie okrelony typ, dlatego obiekty w jzyku Object Pascal
(a take w rodowisku .NET) rwnie maj swoje okrelone typy. W przypadku obiektw typ nazywany jest klas obiektu, a kady obiekt, ktrego typ okrela pewna klasa,
jest egzemplarzem lub instancj tej klasy.
Przykadem uatwiajcym rozrnianie klas i ich egzemplarzy (instancji) s formularze przygotowywane w projektancie formularzy. Tworzone w nich klasy okien w czasie
dziaania programu zamieniane s w rzeczywiste okna wywietlane na ekranie.
W tej ksice stosowa bd wyraenie egzemplarz. Rwnie czsto wystpujce
okrelenie instancja powstao najprawdopodobniej w wyniku pierwszych i nie do
koca przemylanych tumacze angielskiego sowa instance. Sowo instancja
w jzyku polskim nie jest dokadnym tumaczeniem sowa angielskiego, ale oznacza rne stopnie w hierarchii instytucji.

3.2.1. Deklaracja klasy


Jako przykad deklaracji klasy przedstawiam na listingu 3.13 uproszczon wersj klasy
TimerEvent znanej nam ju z punktu 2.2.3.
Listing 3.13. Uproszczona deklaracja klasy TimerEvent
type
TimerEvent = class
public
// Zmienne:
ActivationTime: DateTime;
// Metody:
function ToString: String; override; // Zmienia dane zdarzenia w tekst
procedure Trigger; virtual;
// Wywouje zdarzenie
end;

Deklaracje klas zawsze znajduj si w czci pliku rdowego rozpoczynajcej si


od sowa kluczowego type. Najwaniejszym sowem kluczowym w takiej deklaracji
jest sowo class, odrniajce zapisan dalej struktur od rekordw, ktre deklarowane
s za pomoc sowa kluczowego record.

298

Delphi 2005

Za sowem kluczowym wypisywane s elementy klasy pogrupowane w sekcje opisane


sowami kluczowymi private i public, przy czym na podanym wyej przykadowym
listingu z deklaracj klasy znajdziemy wycznie sekcj public.
Wewntrz jednej z sekcji trzeba deklarowa najpierw zmienne, a dopiero za nimi metody
i waciwoci. Po pojawieniu si pierwszej deklaracji metody nastpne zmienne deklarowane mog by dopiero po pojawieniu si kolejnego opisu sekcji, na przykad public.
W poszczeglnych deklaracjach obowizuj nastpujce reguy:
Zmienne deklarowane s tak samo jak zwyczajne zmienne jzyka Object

Pascal. Szczegy na ten temat podaj w punkcie 3.5.3. Na pocztek wystarczy,


e bdziemy zna podstawow skadni nazwa: typ, za pomoc ktrej
deklarowana jest zmienna nazwa o typie typ.
Deklaracje metod rwnie stosuj si do skadni jzyka Object Pascal

obowizujcej w deklaracjach wszystkich funkcji i procedur. Jeeli metoda


zwraca w wyniku pewn warto, to deklarowana jest sowem kluczowym
function, a typ zwracanej wartoci zapisywany jest na kocu deklaracji
po dwukropku. Pozostae rodzaje deklaracji wykorzystuj sowo kluczowe
procedure. Wszystkie parametry przyjmowane przez metod zapisywane s
w nawiasach zaraz za jej nazw. Wicej informacji na temat oglnej skadni
stosowanej w deklaracjach znale mona w podrozdziale 3.8. W deklaracjach
metod stosowane s jeszcze pewne specjalne dyrektywy, takie jak widoczne
w powyszym listingu dyrektywy override i virtual, ktre opisywa bd za chwil.
Waciwoci to specjalne elementy wystpujce wycznie wewntrz klas.

Waciwociom powicony zosta punkt 3.2.4.

Uzupenianie klas
Po zapisaniu w deklaracji klasy samych nazw, parametrw i typw wartoci zwracanych
przez metody, w dalszej czci pliku rdowego przygotowane musz by implementacje tych metod (czyli ciaa metod z zapisanym wykonywanym w nich kodem),
cakowicie niezalenie od ich deklaracji. W jzyku Object Pascal nie jest moliwe
implementowanie metod bezporednio w deklaracjach klas, co umoliwiaj jzyki C#
i Java. Wszystkie implementacje metod musz by zapisane w czci implementacyjnej
moduu.
Najprostsz metod na uzyskanie szkieletu implementacji wszystkich metod jest wywoanie dostpnej w Delphi funkcji automatycznego uzupeniania klasy. Po umieszczeniu kursora edytora wewntrz przedstawionej wyej deklaracji klasy i naciniciu
kombinacji klawiszy Ctrl+Shift+C, Delphi przygotuje w czci implementacji moduu
szkielety metod przedstawione na listingu 3.14.
Listing 3.14. Implementacje metod przygotowane przez funkcj automatycznego uzupeniania klas
implementation
{ TimerEvent }
constructor TimerEvent.Create;
begin

Rozdzia 3. Jzyk Delphi w rodowisku .NET

299

end;
function TimerEvent.ToString: String;
begin
end;
procedure TimerEvent.Trigger;
begin
end;

Konwencje nazewnicze
Delphi stanowi ma pomost pomidzy rnymi wiatami programowania, dlatego nie
znajdziemy w nim adnych z gry narzuconych konwencji nazewniczych, ktre miayby obowizywa we wszystkich klasach zaprogramowanych w Delphi lub wykorzystywanych w tworzonych programach. W poprzednich wersjach Delphi sytuacja
bya jeszcze cakiem przejrzysta, poniewa programici stosowali ogln zasad przygotowan przez firm Borland, mwic, e nazwy klas zaczyna si maj od wielkiej
litery T (podobnie jak i wszystkie inne nazwy typw w jzyku Object Pascal), z ktrej
wynikaj wszystkie nazwy klas obecnych w bibliotece VCL: na przykad TColor,
TForm i TButton.
W rodowisku .NET nie ma takiej tradycji, a klasy nazywaj si tutaj po prostu Color,
Font lub Button. Ze wzgldu na ogromne iloci klas dostpnych w rodowisku .NET,
klasy Delphi z przedrostkiem T znajduj si w mniejszoci. Pozwala to jednak na do
atwe okrelenie pochodzenia danej klasy. Jeeli nie wykorzystujemy adnych dodatkowych klas pochodzcych od firm trzecich, to mona atwo stwierdzi, e klasy z przedrostkiem T przygotowane zostay przez firm Borland, a wszystkie pozostae pochodz
z firmy Microsoft.
W przykadowych programach prezentowanych w tej ksice te dwa schematy nazywania klas stosowane s zamiennie, w zalenoci od tego, czy programowi bliej jest
do biblioteki VCL, czy te do biblioteki klas rodowiska .NET. W ten sposb klasy
prezentowane w rozdziale 1. nie miay w nazwach dodatkowej litery T, ale w rozdziale 4. nazwy klas aplikacji tworzonych w bibliotece VCL.NET bd zaczynay si
od tej litery.

3.2.2. Atrybuty widocznoci


Dziki stosowaniu w deklaracjach klas opisw poszczeglnych sekcji, podobnych do
opisu public, jaki mielimy okazj zobaczy w deklaracji klasy TimerEvent, moemy
chroni pewne elementy klasy przed dostpem z zewntrz. Do wyboru mamy tutaj
nastpujce stopnie ochrony:
public Oznacza elementy publiczne, czyli niepodlegajce adnej ochronie.

Do elementw tych mona dowolnie odwoywa si z zewntrz klasy. W idealnie


przygotowanym programie obiektowym w klasach publiczne s wycznie
metody i waciwoci, ale nie zwyczajne dane.

300

Delphi 2005
strict protected Oznacza elementy chronione. Zabezpiecza elementy

przed dostpem z zewntrz, co oznacza na przykad, e zmienna zadeklarowana


w chronionej sekcji kontrolki Button nie bdzie dostpna wewntrz procedury
obsugi zdarzenia formularza, na ktrym uoona jest ta kontrolka. Do elementw
deklarowanych w tej sekcji mona si jednak odwoywa bez adnych ogranicze
w klasach wywiedzionych.
strict private Oznacza elementy prywatne. Nie pozwala na dostp do tak

zabezpieczonych elementw nawet z klas wywiedzionych, przez co klasa ta


ma niepodzieln kontrol nad tymi elementami.
protected i private Oba powysze atrybuty mog te wystpowa bez
przedrostka strict, co umoliwia dostp do tak zabezpieczanych elementw

z dowolnych metod zadeklarowanych w tym samym module, w ktrym znajduje si


deklaracja klasy. Dziki temu do tych elementw dostp bd miay te inne klasy
zadeklarowane w tym module. To rozszerzenie pozwolenia dostpu odpowiada
poziomowi widocznoci assembly, jaki istnieje w rodowisku CLR, z kolei
atrybutowi strict private w rodowisku CLR odpowiada poziom widocznoci
private, a atrybutowi strict protected poziom widocznoci family.
published Oznacza elementy opublikowane. Ten poziom zabezpiecze ma

znaczenie tylko dla komponentw, ktre maj by uywane w ramach projektanta


formularzy. W takich komponentach waciwoci, ktre maj by wywietlane
w inspektorze obiektw, musz by deklarowane jako opublikowane.
Standardowym ustawieniem zabezpiecze elementw klasy, czyli obowizujcym do
czasu pojawienia si pierwszego atrybutu zabezpiecze, jest brak jakichkolwiek zabezpiecze, czyli public, a w kontekcie biblioteki VCL.NET published.

3.2.3. Samowiadomo metody


Poczenie danych i kodu w ramach programowania zorientowanego obiektowo szczeglnie dobrze wida w sposobie, w jakim obiekt odwouje si do swoich wasnych
danych. Na listingu 3.15 przedstawiam przykad metody formularza, w ktrej zmieniany jest kolor ta okna.
Listing 3.15. Procedura formularza zmieniajca kolor okna
procedure TwinForm.ChangeBackgroundColor;
begin
BackColor := Color.Red;
end;

Formularz odwouje si do swojego elementu BackColor po prostu wymieniajc jego


nazw; na pierwszy rzut oka jest to jak najbardziej naturalne dziaanie. Formularz jest
jednak tylko klas, na podstawie ktrej tworzony jest faktyczny obiekt okna, a co
wicej na podstawie klasy formularza przygotowa mona kilka takich samych
okien. A gdy istnie ju bdzie kilka okien typu TWinForm, wtedy powstanie pytanie
kolor ta ktrego okna zmienia instrukcja przedstawiona na powyszym listingu?

Rozdzia 3. Jzyk Delphi w rodowisku .NET

301

Niejawny parametr self


Odpowied na to pytanie brzmi: do kadej metody automatycznie przekazywany jest
niejawny parametr self. Parametr ten jest obiektem, na rzecz ktrego wywoana zostaa dana metoda. Na potrzeby kadego obiektu, ktry stosowany jest wewntrz metody klasy, kompilator automatycznie tworzy ten specjalny parametr self. Wynika
z tego, e ciao metody mona przedstawi za pomoc kodu z listingu 3.16.
Listing 3.16. Rzeczywisty zapis wntrza jednej z metod formularza
procedure TWinForm.Methode(self: TForm1; ... tutaj zapisane s jawnie zadeklarowane
parametry);
begin
with self do begin
... tutaj pojawiaj si jawnie zapisane instrukcje
end;
end;

Dziki zastosowaniu instrukcji with self do wszystkie nazwy zapisane w tym bloku,
ktre s nazwami elementw obiektu self, bd dotyczyy wycznie tego obiektu.
To wszystko oznacza, e przedstawiona wyej instrukcja zmieniajca kolor ta formularza
wewntrz tej procedury wyglda tak:
self.BackColor := Color.Red;

Obiekt self w czasie dziaania programu


Dla lepszego zobrazowania dziaania tego mechanizmu na listingu 3.17 przedstawi
wycinek kodu, w ktrym stosowane s dwa obiekty tego samego formularza.
Listing 3.17. Kod, w ktrym wykorzystywane s dwa obiekty utworzone na podstawie tego samego formularza
var
Form1, Form2:TWinForm;
begin
...
Form2.ChangeBackgroundColor;

W tym przypadku parametr self wywoywanej metody wskazywa bdzie na obiekt


Form2, a w zwizku z tym w kodzie metody ChangeBackgroundColor (jego aktualn wersj przedstawiam na listingu 3.18) zmieniany bdzie kolor ta okna Form2.
Listing 3.18. Kod metody wykonywany w wyniku wywoania przedstawionego na listingu 3.17
procedure TwinForm.ChangeBackgroundColor(self = Form2);
begin
Form2.BackColor := Color.Red;
end;

Jak wida, rozrnianie wielu obiektw w czasie dziaania programu obywa si waciwie cakowicie automatycznie. W czasie programowania metod jednej klasy sytuacj t mona sobie wyobrazi tak, e w systemie nie istnieje wiele obiektw tej klasy,

302

Delphi 2005

ale tylko jeden, a my przygotowujemy nie metody klasy, ale metody tego jednego
obiektu. Obiekt, o ktrym tu mwi, nazywa si wanie self. Nie mona jednak nigdy
zapomnie o tym, e w czasie dziaania programu tworzone przez nas metody mog
by wywoywane na rzecz rnych wartoci obiektw zapisanych w parametrze self
(chyba e nasza klasa zostaa celowo tak ograniczona, e na jej podstawie moe by
utworzony tylko jeden obiekt).
Dopki sami nie zadeklarujemy osobnej zmiennej, ktra rwnie bdzie nazywaa si
self, moemy jawnie stosowa identyfikator self, aby w ten sposb pomin na przykad dziaanie innej instrukcji with. W kodzie przedstawionym na listingu 3.19, ze
wzgldu na tak wanie instrukcj with, kompilator uznaje, e identyfikator BackColor
nie jest powizany z obiektem self, ale z obiektem Button1. W takich warunkach,
chcc odwoa si do waciwoci BackColor formularza, musimy jawnie odwoa si
do obiektu self.
Listing 3.19. Instrukcja with moe wymusi jawne odwoanie si do obiektu self
with Button1 do
BackColor := self.BackColor;
{bez instrukcji with mona stosowa taki zapis}
Button1.Color := Color;

3.2.4. Waciwoci
Waciwoci obiektw znosz sprzeczno powstajc pomidzy filozofi programowania zorientowanego obiektowo a chci jak najatwiejszego tworzenia programw.
Jeeli bez stosowania waciwoci chcielibymy jak najprociej zmienia wartoci elementw danych obiektw, to zmuszeni bylibymy do stosowania takiego zapisu:
obiekt.ElementDanych := NowaWartosc;

Taki zapis zadziaaby tylko wtedy, gdyby ElementDanych by zadeklarowany jako publiczny element klasy, co byoby jednak zaprzeczeniem filozofii programowania obiektowego, ktra wymaga, aby takie przypisanie wartoci odbywao si poprzez nastpujce wywoanie metody:
obiekt.SetElementDanych(NowaWartosc);

Takie rozwizanie ma t zalet, e kod wywoujcy nie jest zaleny od wewntrznej


struktury danych klasy (jeeli ElementDanych zostaby pniej inaczej nazwany, usunity albo przeniesiony do zupenie innej struktury danych, to konieczna byaby tylko
przebudowa metody SetElementDanych, ale wszystkie jej wywoania mogyby pozosta bez zmian). Poza tym, metoda SetElementDanych mogaby przy okazji wykonywa
jeszcze inne operacje, ktre wizayby si ze zmianami wartoci zmiennej ElementDanych
(na przykad aktualizacja informacji na ekranie).
Jeeli teraz ElementDanych nie bdzie zmienn, ale waciwoci, to mona wygodnie
zapisa:
obiekt.ElementDanych := NowaWartosc;

a mimo to wywoana zostanie metoda Set.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

303

Deklarowanie wasnych waciwoci


Waciwoci umieszczane s wewntrz deklaracji klasy dokadnie w tym samym miejscu
co metody, czyli wewntrz jednej sekcji zabezpiecze (public, private, ) nie mona ich deklarowa przed znajdujcymi si w niej zmiennymi. Deklaracja przykadowej
waciwoci Width wyglda nastpujco:
property Width: Integer read GetWidth write SetWidth;

Dyrektywy read i write znajdujce si za deklaracj typu waciwoci (Integer) wskazuj metody stosowane w czasie odczytywania i zapisywania wartoci waciwoci.
Opuszczenie jednej z tych dyrektyw spowoduje, e deklarowana waciwo bdzie moga by tylko odczytywana lub tylko zapisywana.
Metoda odczytujca waciwo musi by funkcj bezparametrow, ktrej typ zwracanej
wartoci zgodny jest z typem waciwoci. Z drugiej strony, metoda zapisujca waciwo musi by procedur pobierajc jeden parametr typu zgodnego z typem waciwoci. Kody metod powizanych z deklarowan wyej waciwoci Width podaj
na listingu 3.20.
Listing 3.20. Kody metod odczytujcych i zapisujcych wartoci waciwoci Width
funktion TPewnaKlasa.GetWidth: Integer;
begin
Result := FWidth;
end;
procedure TPewnaKlasa.SetWidth(NewWidth: Integer);
begin
FWidth := NewWidth;
Invalidate;
end;

Tak jak w przedstawionych wyej metodach, z waciwociami powizane s te zmienne


o takim samym typie, a zadaniem metod waciwoci jest wykonywanie dodatkowych
operacji powizanych z operacjami odczytu i zapisu danych do waciwoci. Najczciej zmienna ta otrzymuje nazw skadajc si z pocztkowej litery F i nazwy samej
waciwoci (w naszym przykadzie zmienna nazywa si FWidth).

Zmienne przebrane za waciwoci


Pewn alternatyw w stosunku do stosowania metod podawanych w dyrektywach
read i write jest moliwo podawania w nich nazwy zmiennych, ktre mog by
bezporednio zapisywane lub odczytywane. W podanym wyej przykadzie metoda
GetWidth zajmuje si wycznie odczytaniem wartoci zmiennej FWidth, wobec czego
ten sam efekt mona uzyska deklarujc waciwo tak jak na listingu 3.21.
Listing 3.21. Inny sposb zadeklarowania waciwoci Width
{ Wszystkie deklaracje wymagane do zadeklarowania waciwoci }
Fwidth: Integer;
procedure SetWidth(NewWidth: Integer);
property Width: Integer; read FWidth write SetWidth;

304

Delphi 2005

Przykady waciwoci
W tej chwili wska tylko praktyczne przykady waciwoci, jakie mona znale
w innych miejscach tej ksiki:
W klasie RTFAttributes prezentowanej na stronie 171 zadeklarowanych jest

wiele waciwoci opisujcych atrybuty stosowane w kontrolce RichTextBox.


W klasie DesktopChangeEvent ze strony 216 stosowana jest waciwo
ColorARGB, umoliwiajca dopasowanie klasy do wymogw serializacji XML.

Ponadto w rozdziale 6. definiowane s komponenty, ktrych nieodczn czci s


waciwoci pozwalajce na edytowanie pewnych ustawie komponentu w inspektorze
obiektw.

Waciwoci tablicowe
Waciwoci tablicowe na pierwszy rzut oka dziaaj dokadnie tak samo jak zwyczajne
tablice, a dostp do ich elementw mona uzyska na przykad stosujc poniszy zapis:
Colors[0] := System.Drawing.Color.White;

Instrukcja ta przypisuje warto zerowemu elementowi waciwoci Colors. Deklarowanie takiej waciwoci wymaga zadeklarowania nazwy waciwoci i zamknicia
danych o wymiarach tablicy wewntrz nawiasw prostoktnych (na przykad [I: byte]).
Typ poszczeglnych elementw tablicy podawany jest po prostoktnym nawiasie zamykajcym, tak jak na listingu 3.22.
Listing 3.22. Deklarowanie waciwoci tablicowych
// Tablica obiektw typu TColor indeksowana wartociami typu integer
property Colors[i: Integer]: TColor read GetColor write SetColor;
// Trjwymiarowa tablica obiektw
// indeksowana trzema wartociami typu integer
property TrzyWymiary[x, y, z: Integer]: TObject; read Read3D write Set3D;

W deklaracjach metod odczytujcych i zapisujcych waciwoci tablicowe musz pojawi si zmienne indeksowe wymienione w deklaracji wymiarw samej waciwoci.
Dla przedstawionych wyej waciwoci deklaracje tych metod musz wyglda tak
jak na listingu 3.23.
Listing 3.23. Metody zapisujce i odczytujce wartoci waciwoci tablicowych
function GetColor(i: Integer): TColor;
procedure SetColor(i: Integer; val: TColor);
function Get3D(x, y, z: Integer): TObject;
procedure Set3D(x, y, z: Integer; val: TObject);

Rozdzia 3. Jzyk Delphi w rodowisku .NET

305

Waciwo tablicy standardowej


Jeeli obok deklaracji waciwoci tablicowej zapisane zostanie sowo kluczowe default
(tak deklaracj przedstawiam na listingu 3.24), to waciwo ta stanie si standardow
waciwoci tej klasy.
Listing 3.24. Deklarowanie standardowej waciwoci klasy
type
TList = class
property Items[Index: Integer]: TObject;
read GetItem write SetItem; default;

Dla obiektu klasy TList taka deklaracja oznacza na przykad, e cay obiekt tej klasy
mona obsugiwa dokadnie tak samo jak tablic, bez koniecznoci wymieniania konkretnej waciwoci, tak jak w kodzie przedstawionym na listingu 3.25.
Listing 3.25. Traktowanie obiektu listy jako tablicy
var
List: TList;
PierwszyObiekt: TObject;
begin
List := TList.Create(...);
PierwszyObiekt := List[0];

// Inicjowanie listy

Zapisane w powyszym listingu wyraenie List[0] ma dokadnie takie samo znaczenie jak zapis List.Items[0]. Jak mona si domyla, kada klasa moe mie najwyej jedn waciwo standardow tego rodzaju.

Jedna metoda dla wielu waciwoci


Waciwoci indeksowane s bardzo podobne do waciwoci tablicowych, ale w ich
przypadku dostp do poszczeglnych elementw uzyskiwany jest nie za pomoc indeksw, ale poprzez specjalne nazwy. W klasie TGraphicElement programu TreeDesigner
waciwoci Left, Right, Top i Bottom zadeklarowane s za pomoc kodu przedstawionego na listingu 3.26.
Listing 3.26. Deklarowanie waciwoci w klasie TGraphicElement
property
property
property
property

Left:
Right:
Top:
Bottom:

Integer
Integer
Integer
Integer

index
index
index
index

1
2
3
4

read
read
read
read

GetCoord;
GetCoord;
GetCoord;
GetCoord;

Wszystkie cztery waciwoci odczytywane s za pomoc tej samej metody, ktra rozrnia te waciwoci korzystajc z indeksu zapisanego w ich deklaracji. Kompilator
automatycznie dodaje waciw warto indeksu do wszystkich wywoa metody odczytujcej (kod tej metody podaj na listingu 3.27).

306

Delphi 2005

Listing 3.27. Odczytywanie indeksowanych waciwoci w klasie TGraphicElement


function TGraphicElement.GetCoord(Index : Integer) : Integer;
begin
with Points do
case Index of
1 : Result:=Left;
2 : Result:=Right;
3 : Result:=Top;
4 : Result:=Bottom;
end;
end;

Dokadnie tak samo dziaaj procedury zapisujce wartoci do takich waciwoci. One
rwnie wymagaj podania dwch parametrw: indeksu waciwoci i jej nowej wartoci.
W podanym wyej przykadzie nie byy definiowane adne metody zapisujce, poniewa
wartoci czterech przedstawionych waciwoci mona wycznie odczytywa.

3.2.5. Metody klas i zmienne klas


Delphi dla .NET pozwala na stosowanie w klasach metod statycznych, a take statycznych zmiennych i waciwoci. W Delphi 7 dozwolone byo stosowanie wycznie
metod statycznych, ktre od tego czasu nazywane byy te metodami klas, co wynikao ze specyficznej skadni stosowanej w czasie ich deklarowania (class function/
procedure). Takie metody s odpowiednikami metod statycznych funkcjonujcych na
poziomie CLR (maj w stosunku do tych metod statycznych pewne ograniczenie, ale
na ten temat mwi bd za chwil).
Okrelenie metoda klasy oznacza, e jest ona wywoywana dla klasy, a wic nie ma
zwizku z ktrymkolwiek obiektem utworzonym na podstawie tej klasy. Na listingu
3.28 przedstawiam przykad takiej metody.
Listing 3.28. Przykad fikcyjnej metody statycznej
type
TDemoClass = class
class function GetSpeedEstimate: Integer;
{ Ta funkcja okrela szacunkow prdko dziaania klasy.
Jeeli zwracana warto jest wiksza ni 100, to znaczy,
e klasa dziaa bardzo szybko}
// Funkcja klasy wywoywana jest nastpujco
if TDemoClass.GetSpeedEstimate < 100
then ShowMessage('Za wolno!')

Metody statyczne w stylu CLR


W przedstawionym wyej przykadzie metoda statyczna GetSpeedEstimate jest odpowiednikiem metod statycznych funkcjonujcych w rodowisku .NET i w zwizku z tym
jest w kompilatorze Delphi przeksztacana w tak wanie metod.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

307

W bibliotece klas rodowiska .NET metody statyczne stosowane s przede wszystkim


do tworzenia funkcji narzdziowych, ktre maj dziaa cakowicie niezalenie od jakichkolwiek obiektw. Wczeniej, w jzyku C++ (a take w Delphi) takie funkcje narzdziowe deklarowane byy jako funkcje globalne, jednak rodowisko CLR nie pozwala na stosowanie elementw globalnych. Statyczne metody traktowane s tutaj
jako wybieg, poniewa pozwalaj na definiowanie metod, ktre mona wykorzystywa
dokadnie tak samo jak wszechobecne wczeniej metody globalne. Do wywoania metody statycznej nie jest potrzebne tworzenie adnego obiektu, na rzecz ktrego mona
by j wywoa. W tym wypadku takim obiektem jest sama klasa, i to na jej potrzeby
wywoywana jest metoda.
Wiele klas rodowiska .NET udostpnia te metody statyczne, ale przykadem szczeglnie typowym dla przedstawionej wyej koncepcji zamiennikw dla funkcji globalnych s klasy udostpniajce wycznie metody statyczne, takie jak File, Directory
lub Path (mwilimy o nich w punkcie 2.6.2).
W konsekwencji takiego funkcjonowania metod statycznych w rodowisku .NET, nie
otrzymuj one niejawnego parametru self. I to wanie jest podstawowa rnica pomidzy metodami statycznymi rodowiska .NET a metodami statycznymi Delphi.

Metody statyczne w stylu Delphi


Metody statyczne w Delphi w parametrze self otrzymuj wskazania na klas, okrelajce klas, na rzecz ktrej wywoana zostaa metoda (w przypadku wywoania TDemoClass.
GetSpeedEstimate w parametrze tym znajdowaoby si wskazanie na klas TDemoClass).
W tekcie rdowym w Delphi parametr self jest parametrem niejawnym, ale wywoujc t metod z innego jzyka programowania albo przygldajc si jej w narzdziu
Reflection zauwaymy, e parametr ten jest pierwszym parametrem metody statycznej.
Parametr ten stosowany jest przede wszystkim w ramach zachowania zgodnoci z wczeniejszymi wersjami Delphi, poniewa w metodach statycznych, o ktrych mwilimy
do tej pory, by on cakowicie zbdny. W kadej metodzie statycznej doskonale wiadomo, z ktr klas jest ona zwizana.
W zakresie metod statycznych koncepcja jzyka stosowana w Delphi ma wiksze
moliwoci od tej zwizanej z jzykiem C#, w ktrym nie mona definiowa wirtualnych metod statycznych. Realizacja tej koncepcji w kompilatorze Delphi wskazuje, e mona by pokusi si o zasymulowanie jej rwnie w jzyku C#. Delphi
nie przeksztaca wirtualnych metod statycznych w statyczne metody funkcjonujce
w CLR, ale w wirtualne metody metaklasy, bdcej czci rodowiska CLR. Taka
metaklasa tworzona jest w Delphi dla kadej klasy zdefiniowanej w tekcie programu, co pozwala na implementowanie rwnie innych specjalnych rozwiza
dziaajcych w wiecie Delphi. Dla przykadowej klasy TDemoClass tworzona przez
Delphi metaklasa nazywaaby si @MetaTDemoClass, a w programie Reflection pojawiaby si jako podrzdny wze klasy TDemoClass (na rysunku 3.1 zobaczy
mona podobn metaklas @MetaTWinForm).

To wszystko zmienia si jednak diametralnie, gdy przyjrzymy si jeszcze jednej waciwoci Delphi. W jzyku Object Pascal metody statyczne mog by deklarowane jako
wirtualne (dyrektywa virtual), w zwizku z czym mog by pokrywane w klasach

308

Delphi 2005

wywiedzionych (na temat metod wirtualnych mwi bdziemy w punkcie 3.3.3).


Wynika z tego, e parametr self przekazywany metodzie statycznej moe wskazywa
inn klas ni ta, w ktrej zadeklarowana jest ta metoda.

Statyczne metody klas


Jak ju mwiem, jedynym technicznym szczegem rnicym statyczne metody
Delphi od podobnych metod rodowiska .NET jest dodatkowy parametr self przekazywany do statycznych metod w Delphi. Jeeli chcielibymy by w peni zgodni z zasadami obowizujcymi w rodowisku .NET, to statyczne metody moemy deklarowa
z wykorzystaniem dyrektywy static, ktra powoduje, e Delphi tworzy cakowicie
normaln metod statyczn rodowiska .NET, do ktrej nie przekazuje parametru self:
class function MyClassFunction: Integer; static;

Taka deklaracja nabiera wikszego znaczenia, gdy deklarujemy waciwoci klas, do


ktrych dostp powinny mie te inne jzyki programowania funkcjonujce w rodowisku .NET. Jzyki te oczekuj, e metody Get i Set obsugujce waciwo nie bd
pobieray parametru self.

Zmienne klas
Zmienne i waciwoci rwnie mona deklarowa tak jak metody statyczne, w wyniku czego pojawiaj si one tylko raz w ramach klasy, ale nie staj si czci kadego obiektu tworzonego na podstawie klasy. W przypadku zmiennych zadeklarowanie
takiego rodzaju zmiennej wymaga tylko dodania przed jej deklaracj sw kluczowych
class var.
Jako przykad przedstawi tutaj klas Color (jej deklaracj przedstawiam na listingu
3.29), wzorowan na klasie Color pochodzcej ze rodowiska .NET, w ktrej udostpniane s przygotowane wczeniej klasy kolorw zdefiniowane jako zmienne klasy.
Listing 3.29. Deklaracja klasy udostpniajcej statyczne zmienne obiektw kolorw
type
Color = class
class var White: Color;
class var Red: Color;
...
// Zastosowanie zmiennych klas:
Color.Red; // To wyraenie odczytuje zmienn Red tej klasy

Inicjowanie zmiennych klasy


Jeeli zmienne klasy nie maj pocztkowo otrzymywa standardowych wartoci stosowanych w rodowisku CLR (NULL lub nil), to musz zosta odpowiednio zainicjowane. Do statycznych zmiennych klasy moe mie dostp dowolny zewntrzny kod
i to jeszcze przed utworzeniem jakiegokolwiek obiektu tej klasy, dlatego powstaje
pytanie, kiedy najwczeniej mona prbowa inicjowa wartoci tych zmiennych.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

309

Odpowied na to pytanie znale mona w konstruktorach klas, ktre rwnie s nowoci w stosunku do Delphi 7. Konstruktory klas to metody klas, deklarowane za
pomoc sw kluczowych class constructor, a w rodowisku CLR wywoywane s
automatycznie jeszcze przed pierwszym wykorzystaniem danej klasy. W takim konstruktorze mona na przykad zainicjowa wartoci wszystkich obiektw opisujcych
kolory, tak jak na listingu 3.30.
Listing 3.30. Statyczny konstruktor klasy
type
Color = class
class var White: Color;
class var Red: Color;
...
class constructor Create;
...
class constructor Color.Create;
begin
White := Color.Create;
Red := Color.Create;
...

Waciwoci klas
W podobny sposb mona deklarowa te waciwoci, tworzc w wyniku waciwoci
statyczne lub waciwoci klas. W tym celu waciwoci musz by zapisane w czci
deklaracji klasy rozpoczynajcej si od sw kluczowych class var. Tych sw uylimy ju wczeniej do deklarowania zmiennych statycznych, i cho na listingu 3.30
zastosowane byy one przy kadej deklaracji zmiennej, to w rzeczywistoci wystarczyoby tylko raz zapisa je przed pierwsz zmienn. W przykadowym kodzie z listingu 3.31 deklarowane s dwie zmienne i dwie waciwoci tylko do odczytu, wszystkie
bdce statycznymi elementami klasy.
Listing 3.31. Deklarowanie statycznych zmiennych i waciwoci
type
Color = class
class function GetWhite: Color; static;
class var
FRed: Color;
FWhite: Color;
property Red read FRed;
// Dostp poprzez zmienn statyczn
property White read GetWhite; // Dostp poprzez metod statyczn
...
public // Koniec czci "class var" w deklaracji klasy
...
end;
// Koniec deklaracji klasy

W podanym kodzie cz deklaracji klasy zapocztkowana sowami kluczowymi class


var koczy si na sowie public. W podobny sposb zakoczy mona t cz deklaracji klasy stosujc inne atrybuty widocznoci, takie jak private lub protected; zakoczeniem sekcji elementw statycznych jest te pierwsza deklaracja metody.

310

Delphi 2005

Zapisane we waciwoci statycznej dyrektywy read i write musz wskazywa albo


na statyczne zmienne klasy, albo na statyczne metody klasy, czyli metody, ktre zadeklarowane zostay z wykorzystaniem sw kluczowych class function/procedure
i sowa static.

3.2.6. Dziedziczenie
Jedn z najbardziej podstawowych cech programowania obiektowego jest dziedziczenie,
w ktrym klasa bazowa przekazuje wszystkie swoje elementy do klasy wywiedzionej.
Klasa wywiedziona staje si dziki temu rozszerzeniem klasy bazowej. W rodowisku
.NET i w jzyku Object Pascal kada klasa moe mie tylko jedn klas bazow.
Obiekty klasy wywiedzionej zachowuj si pocztkowo dokadnie tak samo jak obiekty
klasy bazowej, ale dziki nowym deklaracjom metod i pokrywaniu implementacji istniejcych metod mona dodawa do wywiedzionej klasy nowe funkcje lub zmienia
w niej dziaanie funkcji odziedziczonych, przez co zmienia si te zachowanie obiektw
tej klasy.
W jzyku Object Pascal i w rodowisku .NET mechanizm dziedziczenia zaszyty jest
tak gboko, e nie da si w nich przygotowa nowej klasy bez dziedziczenia, nawet
jeeli w definicji nowej klasy nie podamy klasy bazowej. Taka klasa automatycznie
zostanie wywiedziona z klasy System.Object, ktra to w jzyku Object Pascal stosowana jest po nazw aliasu TObject.
Przykadem stosowania dziedziczenia jest kada klasa formularza przygotowywana
w projektancie formularzy:
type
TWinForm = class(System.Windows.Forms.Form)

Dziki zapisowi umieszczonemu w nawiasie za sowem kluczowym class, tworzona


w projektancie formularzy klasa TWinForm dziedziczy z klasy System.Windows.Forms.Form.
To wanie dziki temu dziedziczeniu w klasie formularza dostpne s wszystkie zdefiniowane w nim waciwoci, takie jak wykorzystywana ju w niejednym przykadzie
waciwo BackColor, ktrej mona uywa w metodach klasy TWinForm, tak jakby
zostaa ona zdefiniowana bezporednio w tej klasie.
Proces wywodzenia klas mona powtarza przez wiele poziomw dziedziczenia, dziki
czemu mona tworzy cae hierarchie klas, w ktrych istniej klasy oglne bdce
klasami bazowymi dla bardziej specjalizowanych klas wywiedzionych. Wszystkie
klasy wywiedzione musz realizowa tylko swj wasny zestaw funkcji, a w zakresie,
w ktrym istnieje zgodno z dziaaniami wykonywanymi w klasach bazowych, moe
wykorzysta funkcje odziedziczone z tych klas. Pierwszym przykadem takiego funkcjonowania hierarchii klas s klasy opisujce dziaanie kontrolek, zdefiniowane w bibliotece FCL i VCL.NET (prosz przyjrze si rysunkowi 2.1).

Rozdzia 3. Jzyk Delphi w rodowisku .NET

311

Dziedziczenie i zgodno przypisywania


Jedn z konsekwencji mechanizmu dziedziczenia jest fakt, e dany obiekt jest zgodny
nie tylko z obiektami tej samej klasy, ale moe by te wykorzystywany wszdzie tam,
gdzie oczekiwane jest podanie obiektu jednej z jego klas-przodkw, dokadnie tak, jak to
zaprezentowano na listingu 3.32.
Listing 3.32. Uywanie obiektw klasy wywiedzionej w miejsce obiektw klasy bazowej
var
Object: System.Object;
aComponent: Component;
aForm: Form;
aButton: Button;
...
aComponent := aForm;
aComponent := aButton;
Object := aComponent;

{
{
{
{

Object to klasa bazowa dla wszystkich innych klas }


Component jest klas wywiedzion z klasy Object }
Form jest klas porednio wywiedzion z klasy Component }
Klasa Button rwnie wywodzi si z klasy Component }

Formularze i przyciski dziedzicz wszystkie elementy klasy Component, dlatego mog


by uywane w miejscu obiektw typu Component (w powyszym kodzie zapisywane
s w zmiennej aComponent). Oprcz tego klasa Component jest klas wywiedzion
z klasy Object, co oznacza, e moe ona wykona wszystkie operacje, jakie normalnie wykonuje klasa Object, w zwizku z czym kompilator pozwoli rwnie na wykonanie ostatniego przypisania z powyszego listingu. Trzeba przy tym pamita, e dziaania w przeciwnym kierunku nie s dopuszczalne:
Form := Object;

{ bd niezgodnoci typw }

Od formularza oczekuje si, e bdzie wykonywa wiele szczegowych zada, z ktrych


jednym jest na przykad wywietlanie formularza na ekranie. Z klas Object moe
by powizany jednak dowolny obiekt, wobec czego nie mona zakada, e bdzie on
spenia wszystkie wymagania klasy Form. To wszystko oznacza, e do zmiennej typu
Form przypisa mona wycznie obiekty klasy Form lub klas z niej wywiedzionych.
Wynika z tego, e wywiedzenie jednej klasy z drugiej nie prowadzi wycznie do dziedziczenia wszystkich elementw klasy bazowej, ale tworzy te zwizki pokrewiestwa
odgrywajce niezwykle istotn rol w mechanizmie polimorfizmu, o czym za chwil
si przekonamy.
W zwizku z powyszymi wyjanieniami trzeba jeszcze powiedzie, e zmienne
obiektowe przechowuj wycznie wskazania na rzeczywiste obiekty. W przedstawianej wyej operacji przypisania kopiowany jest tylko wskanik, a sam obiekt nie
jest w aden sposb zmieniany.

Klasy zamknite
Delphi dla .NET obsuguje te koncepcj klas zamknitych funkcjonujc w rodowisku .NET i w zwizku z tym wprowadzone zostao specjalne sowo kluczowe sealed.
Klasa zadeklarowana z tym sowem kluczowym nie moe sta si ju klas bazow

312

Delphi 2005

dla innej klasy. Przykadem takiej zamknitej klasy moe by klasa Thread bdca
czci biblioteki klas rodowiska .NET. W jzyku Object Pascal deklaracja tej klasy
wyglda mogaby tak jak na listingu 3.33.
Listing 3.33. Hipotetyczna deklaracja klasy Thread w jzyku Object Pascal
type
Thread = class sealed
procedure Start;
...
end;
MyThread = class(Thread)
rozbudowywana"
end;

// Bd: "Zamknita klasa 'Thread' nie moe by ju

Deklarowanie klasy jako zamknitej przydaje si wtedy, gdy dana klasa wewntrz
pewnej biblioteki uznawana jest za niezmienn, co oznacza, e nie powinno si zmienia
jej zachowania poprzez pokrywanie metod w klasach potomnych. Oczywicie podobny
efekt uzyska mona, nie deklarujc w klasie metod wirtualnych. Deklarowanie klasy
jako zamknitej daje jednak uytkownikowi dodatkow informacj. Oznacza to, e rozbudowywanie tej klasy w mechanizmie dziedziczenia nie ma ju sensu, wobec czego
naley w tym zakresie korzysta z innych metod, na przykad z agregacji, takiej jak
pokazana na listingu 3.34.
Listing 3.34. Rozbudowywanie klasy Therad poprzez agregacj
type
MyThread = class
// Klasa MyThread jest rozszerzeniem klasy Thread; przechowuje ona
obiekt typu Thread
T: Thread;
// i ewentualnie przekazuje wywoania rnych metod do przechowywanego
obiektu
procedure Start; // wywouje metod T.Start
end;

Wyczerpujcy przykad tej metody rozbudowywania klasy Thread znale mona


w punkcie 2.8.2.

3.2.7. Uprzedzajce deklaracje klas


W jzyku Object Pascal identyfikatory mog by uywane dopiero wtedy, gdy zostan
one przedstawione kompilatorowi. Naley przy tym zakada, e kompilator przeglda
bdzie tekst rdowy tylko raz od pocztku do koca, w zwizku z czym na danym
etapie zna tylko te identyfikatory, ktre do tej pory znalaz w kodzie programu.
W wielu programach bardzo czsto zdarza si, e dwie klasy korzystaj z siebie nawzajem. Jak w takim razie pierwsza klasa ma by zadeklarowana przed drug, skoro
druga klasa powinna by te zadeklarowana przed pierwsz?

Rozdzia 3. Jzyk Delphi w rodowisku .NET

313

W takich sytuacjach skorzysta naley z uprzedzajcych deklaracji klas. Jeeli przykadowo chcielibymy poinformowa kompilator o istnieniu identyfikatora TDocument,
to wystarczy uy nastpujcej deklaracji:
type
TDocument = class;

Waciwa deklaracja klasy moe zosta zapisana w dalszej czci pliku rdowego.
Dziki takim deklaracjom wzajemne korzystanie z siebie dwch klas moe wyglda
tak jak na listingu 3.35.
Listing 3.35. Deklaracje dwch klas korzystajcych z siebie nawzajem
type
TDocument = class; { Klasa dokumentu }
TItem = class
{ Klasa jednego z elementw dokumentu }
{ Element musi wiedzie, w jakim dokumencie si znajduje }
ParentDocument: TDocument;
end;
TDocument = class;
{ Dokument musi zna przynajmniej swj pierwszy element: }
FirstItem: TItem;
end;

3.2.8. Zagniedone deklaracje typw


W bibliotece FCL bardzo atwo mona natkn si na typy zagniedone, na przykad
w opisywanych w punkcie 2.2.5 klasach kolekcji. Kompilator Delphi dla .NET rozpoznaje oczywicie typy zagniedone z biblioteki FCL, a oprcz tego pozwala te na
deklarowanie typw zagniedonych wewntrz programw tworzonych w jzyku
Object Pascal. Klasa ListViewItemCollection mogaby zosta w Delphi zapisana tak jak
na listingu 3.36.
Listing 3.36. Przykad klasy zagniedonej
type
ListView = class(System.Windows.Forms.Control)
// klasa zagniedona:
type ListViewItemCollection = class(IList, ICollection, IEnumerable)
... Elementy klasy ListViewItemCollection ...
end;
... Pozostae elementy klasy ListView ...
end;

Po takiej deklaracji klasy, kade uycie typu ListViewItemCollection w programie


wymaga jawnego wypisania te nazwy klasy zewntrznej:
var
WskaznikNaKolekcje: ListView.ListViewItemCollection;

314

Delphi 2005

3.3. Obiekty w czasie


dziaania programu
Zajmiemy si teraz procesami, ktre w czasie pracy programu mog zachodzi wewntrz
obiektw. Zaliczy do nich mona inicjalizacj wykonywan przez konstruktory i usuwanie obiektw wykonywane przez destruktory, a take najwaniejsze zagadnienie
programowania obiektowego: polimorfizm, dziki ktremu dopiero w czasie wykonywania programu podejmowana jest decyzja, ktra z metod zostanie wywoana (tak
zwane pne wizanie).

3.3.1. Inicjalizacja obiektw: konstruktory


Deklarujc zmienn typu jednej z klas uzyskujemy zmienn, ktra w czasie wykonywania programu moe wskazywa na pewien obiekt, ale pocztkowo inicjowana jest
przez CLR wartoci nil (co oznacza mniej wicej tyle co ta zmienna nie wskazuje
na aden obiekt):
var
MojPrzycisk: Button;

// CLR automatycznie inicjuje zmienn wartoci nil

Prbujc skorzysta z takiej zainicjowanej zmiennej spowodujemy wywoanie bdu


czasu wykonania. Stan ten mona oczywicie zmieni, przypisujc do zmiennej istniejcy ju obiekt typu Button:
MojPrzycisk := PewienFormularz.Button2;

Po wykonaniu tej operacji przypisania zmienne MojPrzycisk i PewienFormularz.Button2


bd wskazyway na ten sam obiekt typu Button.
Czsto konieczne jest jednak samodzielne przygotowanie nowego obiektu i do tego
wanie potrzebne s nam konstruktory. W czasie wywoywania konstruktora wykonywane jest przynajmniej rezerwowanie pamici dla obiektu, poniewa wszystkie
obiekty o typie deklarowanym za pomoc sowa kluczowego class s w rodowisku
CLR automatycznie tworzone na stercie. Poza tym, w konstruktorze klasa ma okazj
przypisa waciwe wartoci pocztkowe zmiennym tworzonego obiektu oraz zarezerwowa zasoby potrzebne w czasie pniejszej pracy obiektu. Typowo konstruktory
obiektw otrzymuj nazw Create.

Wywoywanie konstruktorw
Zadaniem konstruktorw jest nie tylko inicjowanie obiektu, ale i samo jego utworzenie
(czyli zarezerwowanie dla niego pamici). Na przykad, chcc utworzy obiekt typu
DynamicForm nie mona zastosowa wywoania przedstawionego na listingu 3.37.
Listing 3.37. Niewaciwa prba utworzenia nowego obiektu
var
DynamicForm: Form;
begin
DynamicForm.Create; { obiekt nie zostanie utworzony! }

Rozdzia 3. Jzyk Delphi w rodowisku .NET

315

Przedstawiona w powyszym listingu instrukcja wywouje konstruktor Create w poczeniu z obiektem DynamicForm, ktry jeszcze nie istnieje. Chcc utworzy obiekt,
musimy przypisa do zmiennej obiektu (tutaj DynamicForm) cakiem nowy obiekt. Nowy
obiekt tworzony jest przez wywoania konstruktora na rzecz klasy, a nie na rzecz
zmiennej obiektu. Wywoanie metody Form.Create zarezerwuje pami na nowy obiekt
dynamiczny, zainicjuje go i zwrci wskazanie na niego, dziki czemu bdzie mona
je przypisa do zmiennej obiektu:
DynamicForm := Form.Create;

Kontrolki i formularze definiowane w bibliotece Windows-Forms najczciej maj


konstruktory bezparametrowe. W bibliotece VCL.NET formularze i komponenty
oczekuj natomiast podania w parametrze konstruktora komponentu-waciciela.

Tworzenie wasnych konstruktorw


Wewntrz wasnych klas konstruktory deklarowa mona tak jak i inne metody, ale
wyrnia je naley sowem kluczowym constructor. Przykad deklaracji konstruktora
w klasie przedstawiam na listingu 3.38.
Listing 3.38. Przykadowa deklaracja konstruktora wewntrz deklaracji klasy
type
TGraphicElement = class(TPersistent)
constructor Create(InitRect: TRect);
{ Nazwa "Create" nie jest obowizkowa }

W powyszym przykadzie konstruktor klasy oczekiwa bdzie podania w parametrze


prostokta, ktrego wsprzdne wykorzystane zostan do inicjalizacji (nieprzedstawionych na listingu) danych obiektu.
W celu inicjalizowania formularzy w bibliotece Windows-Forms wystarczy dopisywa
instrukcje do ciaa konstruktora klasy formularza przygotowanego przez Delphi.
W przypadku biblioteki VCL.NET najczciej wystarcza obsuenie zdarzenia OnCreate.

Wewntrz konstruktora klasy bardzo czsto konieczne jest wywoanie konstruktora


klasy bazowej, ktry musi wykona swoj cz inicjalizacji klasy. Waciwy sposb
wywoania konstruktora klasy bazowej przedstawiam na listingu 3.39.
Listing 3.39. Wywoywanie konstruktora klasy bazowej
constructor TGraphicElement.Create(InitRect: TRect);
begin
inherited Create; // Do odziedziczonego konstruktora nie trzeba przekazywa adnych
parametrw
...

Jeeli konstruktor klasy bazowej pobiera jakie parametry, to dobrym rozwizaniem jest
przygotowanie w nowej klasie deklaracji konstruktora rwnie pobierajcego te same parametry. Dziki temu mona przekaza te parametry do konstruktora odziedziczonego.

316

Delphi 2005

Ostrzeenie dla osb znajcych Borland Pascal


W starszych wersjach jzyka Pascal przygotowywanych przez form Borland, do wywoywania
odziedziczonego konstruktora zamiast instrukcji inherited Create mona te stosowa instrukcj TPersistent.Create, nie tworzc jednoczenie nowego obiektu. W Delphi wywoanie
to spowodowaoby utworzenie nowego obiektu klasy TPersistent, ktry jednak nie mgby
by do niczego wykorzystany, poniewa wskazanie na utworzony obiekt nie zostaoby przypisane do adnej zmiennej. To wszystko oznacza, e w Delphi musimy korzysta w tym zakresie ze sowa kluczowego inherited.

3.3.2. Zwalnianie zasobw i czyszczenie pamici


We wszystkich programach przeciwiestwem konstruktorw s destruktory. Kady
obiekt, ktry tworzony jest w czasie dziaania programu i ktremu przydzielana jest
pami, musi w pewnym momencie zosta z tej pamici usunity.

Zasada dziaania mechanizmu oczyszczania pamici


W rodowisku .NET w tle cay czas dziaa proces oczyszczania pamici (ang. Garbage
Collector), automatycznie usuwajcy z pamici wszystkie obiekty, ktre nie s ju
uywane przez adn cz programu. Dziki temu w sytuacji idealnej moglibymy
tworzy dowolne obiekty i w ogle nie przejmowa si ich zwalnianiem. Tak wanie
w punkcie 2.2.4 przygotowany zosta dynamicznie formularz i wywietlony wywoaniem
metody ShowDialog (odpowiedni kod przedstawiam na listingu 3.40).
Listing 3.40. Dynamiczne tworzenie i wywietlanie okna dialogowego
procedure TWinForm.Button3_Click(sender: System.Object; e: System.EventArgs);
var
F: Form;
begin
F := Form.Create;
... Przygotowywanie waciwoci formularza ...
... Tworzenie i dodawanie elementw formularza ...
F.ShowDialog; // Wywietlanie formularza jako okna dialogowego
end;

rodowisko CLR rozpoznaje teraz zakoczenie metody i dziki temu wie, e po sowie kluczowym end wszystkie zmienne lokalne przestan istnie. W rodowisku CLR
z kadym utworzonym obiektem powizany jest licznik okrelajcy liczb istniejcych jeszcze wskaza na obiekt. W przedstawionym wyej kodzie licznik ten inicjowany jest wartoci 1, zaraz po zapisywaniu wskazania na ten obiekt do zmiennej F.
W momencie, gdy wykonywanie metody dojdzie do sowa kluczowego end, zmienna
ta przestanie istnie, a w zwizku z tym rodowisko CLR zmniejsza warto licznika
obiektu na zero. Zerowa warto licznika oznacza, e obiekt jest oznaczony do automatycznego usunicia z pamici.
Moemy te zaoy, e w trakcie dziaania procedury zawarto zmiennej F przekazywana jest te do innego obiektu, tak jak to pokazano na listingu 3.41.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

317

Listing 3.41. Przekazanie wskazania na obiekt do innej zmiennej


var
ZF: ZbiorFormularzy;
begin
...
ZbiorFormularzy.ZapiszFormularz(ZF);

Zamy, e metoda ZapiszFormularz zachowuje przekazany jej w parametrze formularz (na przykad wewntrz pewnej kolekcji), w wyniku czego CLR automatycznie
powiksza warto licznika obiektu tego formularza do wartoci 2. Jeeli teraz zmienna F
przestanie istnie, to warto licznika spadnie znowu do jedynki, ale tym razem nie
osigajc zera, przez co obiekt formularza nie zostanie oznaczony do usunicia z pamici.
W przypadku zwalniania obiektu (a mwic dokadniej, zmniejszania jego licznika
wskaza), ktry przechowuje w sobie inne obiekty, procedura zwalniania pamici jest
bardzo podobna do tej stosowanej przy zakoczeniu dziaania metody lokalnie rezerwujcej pami dla obiektw. Oznacza to, e w przypadku zwalniania formularza tak
jak w powyszym przykadzie, razem z formularzem usuwane s z pamici wszystkie
kontrolki tego formularza (oczywicie pod warunkiem, e nie s one wskazywane z innych
miejsc w kodzie programu).
rodowisko CLR ma pen kontrol nad wszystkimi wskazaniami wewntrz kodu zarzdzanego, dlatego mona z czystym sumieniem zakada, e zawsze bdzie wiedziao,
ktre obiekty mog by ju bezpiecznie usunite z pamici, a ktre nie2.

Po co w takim razie destruktory?


Co prawda mechanizm oczyszczania pamici jest bardzo skutecznym remedium na problemy ze znikaniem wolnej pamici spowodowane przez le napisane programy, ktre
nie zwalniaj nieuywanych ju obiektw (tak zwane wycieki pamici ang. Memory
leaks), ale nie speni wszystkich ycze programisty dotyczcych operacji jakie maj
by wykonywane w momencie zwalniania obiektw:
Po pierwsze, moe si zdarzy, e zasoby uywane przez obiekt musz by

zwolnione wczeniej, ni zrobiby to mechanizm oczyszczania pamici. Takim


przykadem mog by pliki, ktre powinny by zamykane w momencie, gdy
nie s ju uywane przez program. Co prawda zamknicie nieuywanego pliku
nastpi automatycznie w momencie usuwania z pamici obiektu FileStream,
ale bdzie to wymagao pewnego oczekiwania, zanim inna aplikacja bdzie
moga uzyska dostp do tego pliku. W takim wypadku konieczne jest rczne
zwolnienie zasobw, ktre w przypadku obiektw FileStream realizowane jest
przez wywoanie metody Close.
Po drugie, bywa te tak, e przy zwalnianiu obiektu nie tylko nastpi ma

zwolnienie zajmowanej przez niego pamici, ale wykonane maj by te inne


operacje, na przykad zapisanie do pliku statystyk zbieranych w czasie caego
2

Jeeli jednak w gr wchodzi te kod nieobsugiwany, ktry otrzymuje wskazanie na obiekt zarzdzany,
to nie mona zagwarantowa prawidowego dziaania mechanizmu oczyszczania pamici.

318

Delphi 2005

czasu ycia obiektu albo zachowanie w rejestrze lub pliku konfiguracyjnym


zmienionych ustawie dotyczcych konfiguracji programu. W takiej sytuacji
konieczna jest moliwo wykonania dodatkowych operacji w momencie,
gdy mechanizm oczyszczania pamici przystpuje do zwolnienia pamici
zajmowanej przez dany obiekt.
W rodowisku .NET mona wykonywa obie te operacje. Pierwszy z wymienionych
przypadkw rczna moliwo zwolnienia zasobw jest do oczywisty, zwaywszy fakt, e metod Close moemy samodzielnie zdefiniowa w kadej klasie.
Moliwo ta jest w rodowisku .NET uzupeniana przez interfejs IDisposable, za
pomoc ktrego takie zwolnienia zajtych zasobw mona przeprowadza w sposb
standaryzowany. Przy automatycznym zwalnianiu obiektu CLR wywouje metod
Finalize tego obiektu tu przed faktycznym zwolnieniem zajmowanej przez niego
pamici. Metoda Finalize pod wieloma wzgldami odpowiada destruktorom z wielu
jzykw programowania, cho na poziomie CLR pojcie destruktora nie zostao zdefiniowane.
Obie te cechy metoda Finalize i wzorzec IDispose dostpne s rwnie w Delphi,
a na dodatek firma Borland przeniosa do wiata .NET destruktory jako cech jzyka
programowania. Osoby znajce jzyk C# albo majce zamiar nauczy si go w przyszoci musz tutaj ostrzec przed niebezpieczestwem popenienia pomyki: destruktory z jzyka C# s dokadnym odpowiednikiem metody Finalize funkcjonujcej na poziomie CLR. Oznacza to, e destruktory jzyka C# w Delphi mona
realizowa wycznie poprzez jawne przygotowanie metody Finalize. Z kolei dziaanie faktycznego destruktora Delphi w jzyku C# odtworzy mona wycznie poprzez rczn implementacj wzorca IDispose. W tabeli 3.1 przedstawiam podsumowanie wszystkich tych rnic i jednoczenie zaznaczam, e w Delphi dostpne s dwa
warianty destruktorw, ktre opisz dokadnie w dalszej czci podrozdziau.
Tabela 3.1. Rodzaje destruktorw
Poziom CLR

w jzyku C#
odpowiada

wariant
... wariant
destruktora w Delphi rczny w Delphi

Wywoywanie
automatyczne

Finalize

Destruktor

Finalize

Finalize

Wywoywanie
rczne

Wzorzec

Wzorzec
IDispose

Free; Dispose;
Destruktor

Wzorzec IDispose

IDispose

Destruktory w Delphi
Tworzc samodzielnie destruktor, naley deklaracj metod rozpocz od sowa kluczowego destructor. W Delphi dla .NET wszystkie tworzone destruktory musz nazywa si Destroy, nie mog przyjmowa adnych parametrw, a w deklaracji klasy
musz by oznaczane dyrektyw override. W kadym wypadku, ostatni instrukcj
w kodzie destruktora powinno by wywoanie destruktora klasy bazowej, tak jak na
listingu 3.42.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

319

Listing 3.42. Kod przykadowego destruktora


// Deklaracja destruktora w klasie
destructor TGraphicElement.Destroy; override;
// Implementacja destruktora:
destructor TGraphicElement.Destroy;
begin
...
inherited Destroy;
end;

Firma Borland zezwolia na stosowanie tej skadni w Delphi dla .NET, poniewa istniejcy ju kod przygotowany w Delphi bardzo czsto wykorzystuje takie wanie destruktory. Kompilator Delphi automatycznie przekada ten wzorzec destruktorw na
wzorzec IDisposable stosowany w rodowisku .NET:
W skompilowanym kompilacie co prawda pojawia si metoda Destroy,

ktra jednak stosowana jest do implementowania wymaganej przez interfejs


IDisposable metody Dispose. Jeeli nasz obiekt zostanie przekazany obcemu
obiektowi, ktry waciwie posuguje si interfejsem IDispose, to obiekt ten
moe wywoa metod Dispose naszego obiektu, co doprowadzi do wywoania
przygotowanego przez nas destruktora.
Chcc spowodowa zwolnienie zasobw, nie naley bezporednio wywoywa
metody Destroy, ale skorzysta z automatycznie przygotowywanej przez
kompilator metody Free. Metoda ta sprawdza warto rwnie przygotowywanej
automatycznie przez kompilator zmiennej Disposed, przez co zapobiega
wielokrotnemu wywoywaniu metody Destroy.

Wykorzystanie tak przygotowanych obiektw wyglda powinno tak jak na listingu 3.43.
Listing 3.43. Sposb wykorzystania obiektu wyposaonego w destruktor
var
Obiekt : TGraphicElement;
begin
Obiekt := TGraphicElement.Create; // Konstruowanie obiektu
... uywanie obiektu ...
Obiekt.Free; // Zwalnianie zasobw obiektu
// Zwolnienie pamici realizowane przez mechanizm oczyszczania pamici
// nastpuje pniej automatycznie, ale moe by te wymuszone rcznie:
// GC.Collect;
// GC.WaitForPendingFinalizers;

Mimo e interfejs IDispose definiowany jest w samym rodowisku .NET, to jednak


metoda Dispose nie jest automatycznie wywoywana w momencie usuwania obiektu
z pamici.

320

Delphi 2005

Rczna implementacja interfejsu IDispose


Interfejs IDispose mona te implementowa rcznie, a kod, ktry normalnie umieszczony byby w destruktorze, przenie do metody Dispose. W takim rozwizaniu kompilator nie pozwoli ju na stosowanie w danym obiekcie destruktora.
W dokumentacji rodowiska .NET opisany zosta pewien wzorzec zastosowania interfejsu IDispose, w ktrym ten sam kod wykonywany jest zarwno przy rcznym zwalnianiu obiektu, jak rwnie przy jego automatycznej finalizacji. W tym celu naley
pokry metod Finalize jedyn metod, ktra wykonywana jest przy automatycznej
finalizacji obiektu w CLR i rcznie przekazywa w niej kontrol do metody Dispose.
Metoda Dispose musi by przygotowana na dwie ewentualnoci: wywoania rcznego
albo automatycznego wywoania w czasie finalizacji. W tym celu przygotowana musi by
specjalna wersja metody Dispose, w ktrej oba te przypadki rozrniane s za pomoc
parametru logicznego. Standardowa, wywoywana automatycznie metoda Dispose nie
ma adnego parametru, dlatego wywouje swoj przecion wersj przekazujc jej
w parametrze warto True, natomiast metoda Finalize wywouje t sam metod z parametrem False.
Listing 3.44. Ujednolicenie kodu dla rcznego i automatycznego zwalniania obiektu
// Deklaracja w czci interfejsu moduu:
TestClass = class (TInterfacedObject, IDisposable)
private
Disposed: Boolean;
public
procedure Dispose; overload;
procedure Dispose(ExplicitCall: Boolean); overload;
strict protected
procedure Finalize; override;
end;
// Implementacja w czci implementacji moduu:
procedure TestClass.Finalize;
begin
inherited;
Dispose(False);
end;
procedure TestClass.Dispose;
begin
inherited;
Dispose(true);
// Metoda Dispose zostaa wanie wywoana rcznie,
// dlatego metoda Finalize nie musi by ju wywoywana
// przy zwalnianiu obiektu
GC.SuppressFinalize(self);
end;
procedure TestClass.Dispose(ExplicitCall: Boolean);
begin

Rozdzia 3. Jzyk Delphi w rodowisku .NET

321

if not Disposed then begin


if ExplicitCall then begin
// Przy wywoaniu bezporednim wywoujcy chciaby,
// eby wszystkie zasoby obiektu zwolnione zostay jeszcze
// przed automatyczn finalizacj obiektu. Bdzie to moliwe
// tylko wtedy, gdy zwolnione zostan te wszystkie zasoby zarzdzane,
// uywane w innych obiektach powizanych z naszym obiektem.
// Na przykad:
// MojKomponent.Dispose;
end;
// W kadym wypadku (rwnie w czasie finalizacji) w tym miejscu
// mona zwalnia te wszystkie zasoby niezarzdzane.
// Przykad: PlikUytkownika.Close;
end;
Disposed := True; // zabezpieczenie przed podwjnym wywoaniem
// W klasie wywiedzionej z klasy TestClass zamiast instrukcji
// Disposed := True naley wywoa odziedziczon wersj
// metody, stosujc przy tym sowo kluczowe inherited.
end;

Zastosowanie tej klasy musi w takim razie wyglda tak jak na listingu 3.45.
Listing 3.45. Zastosowanie klasy TestClass
var
obiekt: TestClass;
begin
obiekt := TestClass.Create;
... Uytkowanie klasy ...
// Zwolnienie zasobw klasy (bez zwalniania pamici)
obiekt.Dispose; // Rczne zwolnienie

Zwolnienie pamici zajmowanej przez obiekt nastpi automatycznie w czasie nieokrelonym po wywoaniu metody Dispose, kiedy mechanizm oczyszczania pamici znajdzie czas na wykonanie tej operacji albo zostanie ona rcznie wymuszona wywoaniem
metody GC.Collect. Najwaniejsze jest tutaj to, e dziki rcznemu wywoaniu metody
Dispose nie ma ju potrzeby wykonywania automatycznej finalizacji obiektu (dziki
wywoaniu metody SuppressFinalize z wntrza metody TestClass.Dispose), co oszczdza
mechanizmowi oczyszczania pamici czci prac zwizanych z zarzdzaniem obiektem.
Przedstawiony wyej wycinek kodu, poczony z inn klas bazow realizujc t
sam koncepcj za pomoc stosowanych w Delphi destruktorw, mona znale
na pycie CD doczonej do ksiki, w projekcie GCDisposeVariants.
Jeeli w klasie przygotowujemy destruktora i jednoczenie pozwalamy kompilatorowi
automatycznie wygenerowa interfejs IDispose, to tracimy moliwo rcznego wywoania metody Dispose tak jak pokazano na powyszym listingu. Konieczne jest
tutaj wykonanie dodatkowej konwersji typw: (obiekt as IDisposable).Dispose.
(Maa uwaga teoretyczna: Jeeli przygotowywa bdziemy destruktor zgodny ze
star tradycj Delphi, to najprawdopodobniej bdziemy wywoywa go zgodnie z t
tradycj, czyli za pomoc metody Free).

322

Delphi 2005

Metoda Dispose dla formularzy


Przedstawiony wyej wzorzec czciowo implementowany jest rwnie w kodzie
formularzy Windows-Forms automatycznie generowanym przez Delphi. Kady formularz otrzymuje specjaln metod Dispose pobierajc parametr Disposing, bdcy
odpowiednikiem przedstawionego wyej parametru ExplicitCall. Kod takiej metody
przedstawiam na listingu 3.46.
Listing 3.46. Kod procedury Dispose formularza
procedure TWinForm1.Dispose(Disposing: Boolean);
begin
if Disposing then
begin
if Components <> nil then
Components.Dispose();
end;
inherited Dispose(Disposing);
end;

W przypadku, gdy parametr Disposing ma warto True, metoda Dispose wywouje


metody Dispose wszystkich komponentw znajdujcych si na formularzu. Takie
dziaanie ma znaczenie tylko w przypadku, gdy formularz ma by aktywny w czasie
wywietlania go w projektancie formularzy, poniewa w czasie dziaania programu
lista komponentw zapisana we waciwoci Components nie jest uywana i w zwizku
z tym jest cakiem pusta3.

Metoda Finalize w Delphi


Jak ju wspominaem, metoda Finalize jest jedyn metod, ktra jest standardowo
wywoywana przez mechanizm oczyszczania pamici. Przedstawiony na listingu 3.44
kod jest przykadem pokrywania w klasie metody Finalize i przedstawia wzorzec pozwalajcy na wykonanie tego samego kodu niezalenie do tego, czy automatycznie
zostaa wywoania metoda Finalize, czy te nastpio rczne wywoanie metody Free.
Jeeli nasza klasa nie potrzebuje stosowania metod rcznego zwalniania zasobw i chcemy
oprogramowa wycznie operacje zwalniania automatycznego wykonywane w czasie
finalizowania obiektu, to metody Finalize mona te uywa cakowicie niezalenie
od istniejcej implementacji interfejsu IDisposable lub przygotowanego destruktora.
Trzeba przy tym przestrzega tylko jednej zasady mwicej, e metoda Finalize nie
moe tworzy adnych nowych obiektw.

Wewntrzne dziaanie finalizacji obiektw


Metody Finalize powoduj opnienia w dziaaniu mechanizmu oczyszczania pamici.
Po pierwsze, zmuszaj go do dodatkowego przejrzenia wszystkich swoich wewntrznych list obiektw: jeeli obiekt posiadajcy metod Finalize nie jest ju nigdzie
3

Kiedy kod formularza jest aktywny ju w czasie projektowania? Tylko wtedy, gdy inny formularz
zostaje z niego wywiedziony, co mielimy okazj obserwowa w punkcie 2.1.4.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

323

uywany (czyli jego licznik wskaza ma warto zero), to pocztkowo dopisywany jest
do listy obiektw do finalizacji. W systemie od czasu do czasu wykonywane jest oczyszczanie pamici i wtedy dla kadego obiektu z tej listy wywoywana jest metoda Finalize,
a obiekty przekazywane s do listy obiektw przeznaczonych do zwolnienia. Wszystkie
obiekty usuwane s z pamici dopiero po przejrzeniu przez mechanizm oczyszczania
pamici tej drugiej listy.
Do tego wszystkiego dochodzi jeszcze taki problem, e mechanizm oczyszczania pamici
nie moe zwolni pamici tych obiektw, ktre wskazywane s przez obiekt wykonujcy metod Finalize, poniewa przez cay czas wykonywania tej metoda moe si
ona odwoywa do tych obiektw.
W metodzie finalizujcej jeden z obiektw mona odwoywa si te do innych
obiektw wykorzystywanych przez ten obiekt, poniewa ich zwolnienie nastpi
najwczeniej po zakoczeniu metody Finalize danego obiektu. Trzeba si jednak
liczy z tym, e dla tych istniejcych jeszcze obiektw wywoana moga by ju metoda Finalize, przez co nie bdziemy mieli penej moliwoci korzystania ze wszystkich funkcji tych obiektw. Kolejno wywoywania metod Finalize w grupie obiektw
nie zostaa nigdzie zdefiniowana i moe zmienia si przy poszczeglnych uruchomieniach programu, a take w zalenoci od wersji stosowanego rodowiska
CLR. Oznacza to, e w czasie tworzenia metod Finalize nie moemy zakada
konkretnej kolejnoci wywoa tych metod.

Zwalnianie zmiennych obiektu


Przedstawiane w dotychczasowych przykadach wywoania metod Free i Dispose zakaday, e zwalniane obiekty przechowywane s w lokalnych zmiennych metody. Zmienne
lokalne przestaj istnie wraz z zakoczeniem pracy metody, dlatego nie trzeba byo
si w tym przypadku martwi o ewentualne prby wielokrotnego zwolnienia tego samego obiektu.
W przypadku obiektw zapisanych w zmiennej danej klasy moe si w pewnych sytuacjach zdarzy, e chcielibymy zwolni zapisany w nich obiekt i jednoczenie zaznaczy, e w zmiennej nie ma ju adnego obiektu. W takim wypadku, po zwolnieniu
obiektu naley do przechowujcej go zmiennej przypisa warto nil (tak jak na listingu 3.47), ktra oznacza bdzie, e tu nie ma adnego obiektu.
Listing 3.47. Zwalnianie obiektu i oznaczanie zmiennej jako pustej
// Jeeli stosowana jest skadania destruktora:
DynamicObject.Free;
// A jeeli stosowany jest interfejs IDisposable:
// DynamicObject.Dispose;
DynamicObject := nil;

Po takim oznaczeniu obiektu moemy w kadej chwili sprawdzi, czy do zmiennej nadal
przypisany jest jaki obiekt, wywoujc w tym celu funkcj Assigned. Funkcja ta zwraca
warto False, jeeli podana zmienna przechowuje warto nil. Dziki temu mona
unikn pomykowego wywoywania metod zwolnionego ju obiektu:

324

Delphi 2005
if Assigned(DynamicObject)
then DynamicObject.ZrobCos;

Zwalnianie obiektw czciowo zainicjowanych


Jeeli w trakcie wykonywania konstruktora wystpi jaki wyjtek, to wykonanie tego
konstruktora jest automatycznie przerywane. Jeeli przechwycimy ten wyjtek i pozwolimy dalej pracowa programowi, to w ktrym momencie trzeba bdzie zwolni
pami zajmowan przez tak czciowo zainicjowany obiekt. Oznacza to, e metody
Finalize i Dispose, a take destruktory powinny by przygotowane na to, e konstruktor moe nie wykona do koca swojej pracy, w wyniku czego obiekt bdzie tylko
czciowo zainicjowany. W kodzie przedstawionym na listingu 3.48 zasada ta nie jest
przestrzegana, wic w pewnych sytuacjach w programie bd pojawia si bdy.
Listing 3.48. Metoda Finalize nieuwzgldniajca moliwoci wystpienia bdw w czasie dziaania
konstruktora
constructor DemoObject.Create;
begin
inherited Create;
File1 := OpenFile1; // Tutaj moe wystpi wyjtek,
File2 := OpenFile2; // a wtedy zmienna File2 nie zostanie zainicjowana
end;
procedure DemoObject.Finalize;
begin
File1.Close;
File2.Close; // Tutaj zmienna File2 moe mie warto nil!
inherited Destroy;
end;

Jeeli w metodzie Create w czasie dziaania metody OpenFile1 (albo wczeniej) wystpi jakikolwiek wyjtek, to w czasie dziaania destruktora zmienne File1 i File2 nie
bd zainicjowane. Wynika z tego, e kod finalizacji tego obiektu bdzie bezpieczny
tylko wtedy, gdy bdzie wyglda tak jak na listingu 3.49.
Listing 3.49. Prawidowa posta kodu destruktora obiektu DemoObject
if Assigned(File2) then
File2.Close;
if Assigned(File1) then
File1.Close;

3.3.3. Metody wirtualne


W czasie prostego programowania formularzy nie trzeba sobie zawraca gowy metodami wirtualnymi, ale pamita naley o tym, e metody wirtualne s jednym z najwaniejszych elementw programowania zorientowanego obiektowo, ktrego najczciej nie da si pomin tam, gdzie w gr wchodzi te mechanizm dziedziczenia klas.
Solidna wiedza o dziedziczeniu i metodach wirtualnych jest nieodzowna na przykad
w czasie przygotowywania kodu nowych kontrolek i komponentw.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

325

Przykad motywacyjny
Standardowo wszystkie metody s niewirtualne, a metodami wirtualnymi staj si dopiero po zastosowaniu wobec nich dyrektywy virtual lub override. W poniszym
przykadzie chc odpowiedzie na rodzce si tu pytanie: do czego w ogle potrzebne
s metody wirtualne? Zamy, e w programie definiujemy kilka klas, ktre wszystkie
maj w sobie metod Pracuj, ale w kadej klasie metoda ta wykonywa bdzie cakowicie inne operacje. Przykadowy kod takich klas przedstawiam na listingu 3.50.
Listing 3.50. Przykadowa deklaracja klas zawierajcych metod o takiej samej nazwie
type
KlasaAbstrakcyjna = class
// Konstruktor Create dziedziczony jest z klasy System.Object
procedure Pracuj;
end;
Klasa1 = class (KlasaAbstrakcyjna) procedure Pracuj; end;
Klasa2 = class (KlasaAbstrakcyjna) procedure Pracuj; end;
...
Klasa10 = class (KlasaAbstrakcyjna) procedure Pracuj; end;

Teraz moemy pozwoli uytkownikowi aplikacji zadecydowa, ktrej klasy bdzie


chcia uy. W zalenoci od wyboru dokonanego przez uytkownika, dynamicznie
tworzymy obiekt odpowiedniej klasy i przypisujemy go do zmiennej ObiektRoboczy,
tak jak na listingu 3.51.
Listing 3.51. Tworzenie obiektu na podstawie wyboru dokonanego przez uytkownika
var
ObiektRoboczy: KlasaAbstrakcyjna;
begin
case WyborUzytkownika of
{ "WyboremUzytkownika" moe by na przykad warto (Sender as TButton).Tag }
Button1: ObiektRoboczy := Klasa1.Create;
Button2: ObiektRoboczy := Klasa2.Create;
...
Button10: ObiektRoboczy := Klasa10.Create;

Zakadamy teraz, e operacj zwizan z wybran przez uytkownika klas wykona


chcemy w zupenie innym miejscu w programie:
ObiektRoboczy.Pracuj;

I jak teraz kompilator na podstawie takiego wywoania metody ma si dowiedzie, ktr


z metod Pracuj ma w danym momencie wywoa? Nie moe przecie przewidywa,
jakiej klasy bdzie obiekt przypisany do zmiennej ObiektRoboczy. W kodzie poinformowalimy go tylko o tym, e zmienna ObiektRoboczy zadeklarowana jest z jako zmienna
klasy KlasaAbstrakcyjna. W zwizku z powyszym kompilator na stae powie to
wywoanie z braku metod wirtualnych z metod KlasaAbstrakcyjna.Pracuj.
Oznacza to, e metody Pracuj zdefiniowane w klasach wywiedzionych nie bd nigdy
wywoywane.

326

Delphi 2005

Metody wirtualne
Przedstawione w powyszym przykadzie metody niewirtualne niszcz cay sens stosowania dziedziczenia klas, ktry to mechanizm ma przede wszystkim umoliwia
zmian w klasach wywiedzionych zachowania metod zdefiniowanych w klasach bazowych. Rozwizaniem tego problemu jest zrezygnowanie z trwaego zapisywania w kodzie
programu metody KlasaAbstrakcyjna.Pracuj (takie rozwizanie nazywa si wczesnym
dowizaniem), na rzecz okrelania wywoywanej implementacji metody dopiero w czasie
pracy programu (pne dowizanie).
Dokadnie tak zachowuj si metody wirtualne. W kodzie przedstawionego wyej
przykadu przeczenie stosowanej metody dowizywania z wczesnej na pn wymaga tylko dopisania za nagwkiem metody Pracuj w klasie bazowej sowa kluczowego virtual, a za nagwkami tej samej metody we wszystkich klasach wywiedzionych
sowa kluczowego override, tak jak to pokazano na listingu 3.52.
Listing 3.52. Poprawiona deklaracja klas wczajca metody wirtualne
type
KlasaAbstrakcyjna = class
{ Konstruktor Create dziedziczony jest z klasy System.Object }
procedure Pracuj; virtual;
end;
Klasa1 = class (KlasaAbstrakcyjna)
procedure Pracuj; override;
end;
...
... pozostay kod - jak wyej ...
...
ObiektRoboczy.Pracuj; { zawsze wywoywana jest waciwa metoda }

Teraz, w zalenoci od tego, ktry obiekt zostanie przypisany do zmiennej ObiektRoboczy


przez instrukcj case, wywoanie ObiektRoboczy.Pracuj spowoduje uruchomienie
implementacji metody Klasa1.Pracuj, Klasa2.Pracuj itd. W podanym kodzie w ogle
nie jest natomiast wykorzystywana implementacja KlasaAbstrakcyjna. Pracuj, dlatego
mogaby ona by zadeklarowana jako abstrakcyjna, tak jak zrobimy to z metod w jednym
z dalszych przykadw.
W tekcie rdowym biblioteki VCL od czasu do czasu mona znale deklaracje
metod wykorzystujce sowo kluczowe dynamic. W tych miejscach chodzi o specjalny wariant metody wirtualnej, ktr w kodzie rdowym programu wykorzystuje
si dokadnie tak samo jak wszystkie opisywane wyej zwyczajne metody wirtualne. Jedyna rnica pomidzy tymi dwoma rodzajami metod polega na ich nieco
innym wewntrznym dziaaniu w rodowisku Win32. W kompilatorze dla Win32
metody dynamiczne s optymalizowane pod wzgldem wielkoci kodu, a nie tak
jak zwyczajne metody wirtualne pod wzgldem prdkoci dziaania. Kompilator
dla .NET metody dynamiczne traktuje dokadnie tak samo jak zwyczajne metody
wirtualne, poniewa w rodowisku .NET w ogle nie funkcjonuje pojcie metody
dynamicznej.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

327

Override
Proces ponownego definiowania odziedziczonej metody wirtualnej nazywany jest pokrywaniem tej metody. W tym celu niezbdne jest zastosowanie w definicji metody
sowa kluczowego override. Jeeli dyrektywa ta zostaaby opuszczona, to kompilator
utworzy samodzieln metod, ktra nie ma nic wsplnego z metod odziedziczon
z poprzedniego przykadu (w ktrym zmienna ObiektRoboczy zadeklarowana jest z typem
klasy abstrakcyjnej) i w zwizku z tym nie zostanie wywoana. Z punktu widzenia
nowej klasy nowa metoda o takiej samej nazwie, niedeklarowana ze sowem kluczowym
override zasania cakowicie metod odziedziczon.
Wskazwka dla uytkownikw jzyka Borland Pascal
Trzeba tu te zaznaczy, e w jzyku Object Pascal nie istniaa jeszcze dyrektywa override,
a metody wirtualne w klasach wywiedzionych zawsze pokrywane byy metodami o takich samych nazwach. Z tego powodu Delphi standardowo generuje ostrzeenie, jeeli odziedziczona
metoda w klasie definiowana jest bez dyrektywy override, czyli jest zasaniana.

Nie naley te myli dyrektywy override z dyrektyw overload. Ta druga stosowana


jest przy definiowaniu kilku metod o takiej samej nazwie, ktre maj by uywane
alternatywnie, i nie ma nic wsplnego z dziedziczeniem klas.

Reintroduce
Oprcz tego w Delphi dostpna jest jeszcze dyrektywa reintroduce, umoliwiajca
zasonicie odziedziczonej metody podobnie do sytuacji opisanej przed chwil, ale
blokujca wypisywanie ostrzeenia przez kompilator. Wystarczy tylko za deklaracj
metody w klasie umieci sowo kluczowe reintroduce.
Wynika z tego, e jeeli w klasie wywiedzionej definiujemy te metod, ktrej nazwa
jest zgodna z nazw metody odziedziczonej to koniecznie musimy w jej deklaracji zastosowa jedno ze sw kluczowych override lub reintroduce, dziki czemu kompilator nie bdzie generowa komunikatw ostrzee, a i dla czowieka atwiejsze bdzie
czytanie kodu takiej klasy. Trzeba jednak pamita, e dyrektyw override stosowa
mona tylko wtedy, gdy lista parametrw i typ zwracanej wartoci nowej metody s
zgodne z tymi samymi danymi o metodzie odziedziczonej. Ograniczenie to nie dotyczy
dyrektywy reintroduce.

Implementowanie pokrywanych metod


Implementujc metod wirtualn we wasnej klasie i korzystajc przy tym z dyrektywy
override do pokrywania odziedziczonej metody, najczciej nie mona zapomina
o wywoaniu wewntrz niej metody odziedziczonej. Dziki temu nowa klasa wykorzystuje te funkcjonalno odziedziczon z klasy bazowej. Do takich wywoa stosowana jest instrukcja inherited.
W przykadowym programie WallpaperChanger z punktu 2.2.3 zdefiniowana zostaa
klasa TimerEvent, w ktrej metoda Trigger ustala warto zmiennej Done na True. Kod
tej metody przypominam na listingu 3.53.

328

Delphi 2005

Listing 3.53. Metoda Trigger klasy TimerEvent


procedure TimerEvent.Trigger;
wirtualna
begin
Done := True;
end;

// W deklaracji klasy metoda ta deklarowana jest jako

W przypadku klasy specjalnej AlarmEvent w ramach wykonywania metody Trigger


(jej kod podaj na listingu 3.54) oprcz ustawienia wartoci zmiennej Done wywietlane
jest te okno z komunikatem. Dziki wywoaniu metody Trigger odziedziczonej z klasy
TimerEvent zapewniamy, e logika odziedziczonej metody nie pjdzie w zapomnienie.
Listing 3.54. Metoda Trigger klasy AlarmEvent
procedure AlarmEvent.Trigger; // W deklaracji klasy metoda ta deklarowana jest
begin
// ze sowem kluczowym override
inherited;
MessageBox.Show(MessageText);
end;

Dyrektywy override, virtual i reintroduce mona stosowa wycznie wewntrz


deklaracji klas i w dalszej czci kodu, w miejscu implementowania metod nie
powinny by powtarzane. Jeeli szkielet metody tworzony jest za pomoc funkcji
automatycznego uzupeniania klas, to w przygotowanym szkielecie znajdowa si
ju bdzie wstpne wywoanie odziedziczonej metody wykorzystujce sowo kluczowe inherited.

Polimorfizm
Koncepcja programowania realizowana za pomoc dziedziczenia i pnych dowiza
nazywana jest polimorfizmem. Obiektem polimorficznym nazywana jest zmienna obiektowa, dla ktrej kompilator nie jest w stanie okreli klasy obiektu, z jakim bdzie ona
zwizana w czasie dziaania programu. Co wicej, w czasie dziaania programu klasa
tej zmiennej moe si wielokrotnie zmienia, poniewa do jednej zmiennej przypisane
mog by obiekty wielu rnych klas.
Obiekt dostpny poprzez zmienn polimorficzn w kodzie programu opisany jest pewn
konkretn klas, ale w czasie dziaania moe przyjmowa rne formy. Obiekty polimorficzne spotyka si w programach w wielu rnych miejscach, na przykad:
W metodach, ktre w parametrach przyjmowa mog dowolne obiekty, jako typ
tych parametrw podaj klas Object. Taki typ maj na przykad parametry
sender przekazywane do metod obsugujcych zdarzenia formularza, a take

poszczeglne wpisy w kontrolce ListBox. Jak przekonalimy si w punkcie


2.4.1, wpisy tej kontrolki rzeczywicie mog by cakowicie dowolnego typu
(mwic dokadniej, w programie dostpne s dwa rodzaje wpisw umieszczanych
w kontrolce ListBox DesktopChangeEvent i AlarmEvent).

Rozdzia 3. Jzyk Delphi w rodowisku .NET

329

Wszystkie kontrolki umieszczane na formularzu rwnie s polimorficzne.

Wewntrz formularza przechowywane s one po prostu jako kolekcja obiektw


typu Control (na jej temat mwiem w punkcie 2.1.3), cho rzeczywiste
kontrolki zawsze s klasy wywiedzionej z klasy Control, a jak wiemy,
w Delphi dostpnych jest wiele rnych kontrolek.

Klasy abstrakcyjne
W wielkich hierarchiach dziedziczenia, takich jak biblioteka FCL lub VCL.NET, bardzo
czsto zdarza si, e klasy wykorzystywane s tylko do tego, eby zdefiniowa czci
wsplne pewnych innych klas. W klasach tych moe si zdarzy, e co prawda deklarowane s metody wirtualne, ktre pokrywane s we wszystkich klasach wywiedzionych, ale wewntrz klasy bazowej w ogle nie s implementowane.
Deklarowanie takiej metody, ktra stanowi tylko rezerwacj miejsca dla metod w klasach
wywiedzionych, wymaga zastosowania w deklaracji sowa kluczowego abstract,
ktre okrela tak metod jako abstrakcyjn. Dyrektywa abstract musi znajdowa si
zaraz za dyrektyw virtual, poniewa metoda abstrakcyjna, ktra nie jest jednoczenie metod wirtualn, nie ma racji bytu. W przykadowej klasie KlasaAbstrakcyjna
metoda Pracuj mogaby by w takim razie zadeklarowana tak:
procedure Pracuj; virtual; abstract;

Taka deklaracja ma takie zalety, e metody tej nie trzeba definiowa w czci implementacji moduu, a jej przypadkowe wywoania s automatycznie blokowane, poniewa kompilator nie pozwala na tworzenie obiektu na podstawie klasy z metodami
abstrakcyjnymi. Kada prba utworzenia takiego obiektu spowoduje wywietlenie
bdu ju w czasie kompilacji programu.
W bibliotece VCL.NET znale mona kilka przykadw klas abstrakcyjnych: TStream,
TStrings i TPersistent. W rodowisku .NET klasy abstrakcyjne zdarzaj si duo rzadziej, poniewa abstrakcyjne elementy klas najczciej definiowane s jako interfejsy.
Dan klas mona te jawnie zadeklarowa jako abstrakcyjn:
type
Class1 = class abstract
end;

Kompilator nie pozwala na tworzenie obiektw na podstawie klas abstrakcyjnych,


nawet jeeli sama klasa nie ma adnych abstrakcyjnych metod.

3.3.4. Konwersja typw i informacje o typach


Czasami ju w czasie dziaania programu konieczne jest sprawdzenie, jakiego typu
jest dany obiekt. W ramach wasnych klas mona by to zrealizowa za pomoc specjalnej metody wirtualnej JakiegoJestemTypu, ale na szczcie w Delphi istniej bardziej
standardowe rozwizania tego problemu.

330

Delphi 2005

Operator is
Oba operatory is i as przeznaczone s do wykonywania bardzo eleganckiej konwersji
typw i sprawdzania zwizkw dziedziczenia czcych klasy. Po prawej stronie operatora zawsze znajduje si zmienna obiektowa (lub wyraenie, ktrego wynik jest
wskazaniem na obiekt) albo referencja klasy, natomiast po lewej stronie operatora moe
znajdowa si wycznie referencja klasy. Operator is sprawdza, czy klasa prawego
operandu jest jednym z potomkw klasy podanej w lewym operandzie. W punkcie
2.4.1 znale mona przykadowy kod (jego cz podaj na listingu 3.55), w ktrym,
w zalenoci od klasy aktualnie wybranego wpisu kontrolki ListBox, wywietlane jest
okno dialogowe przeznaczone do modyfikowania danych danej klasy wpisw.
Listing 3.55. Sprawdzanie klasy podanej zmiennej obiektowej
if ListBox1.SelectedItem is DesktopChangeEvent then
// Wywoanie okna dialogowego dla obiektw DesktopChangeEvent
end else // w przeciwnym wypadku prawdziwe jest: ListBox1.SelectedItem is AlarmEvent
// Wywoanie okna dialogowego dla obiektw AlarmEvent

Konwersja typw
Po sprawdzeniu za pomoc operatora is, czy podana zmienna wskazuje na obiekt
pewnej konkretnej klasy, chcielibymy skorzysta z pewnych specjalnych elementw
tej klasy. W przypomnianym wyej przykadzie chcielimy z kontrolki ListBox odczyta warto zmiennej SelectedItem i przekaza j do okna dialogowego, poniewa
to wanie w tej zmiennej zapisane s wszystkie dane, ktre chcemy edytowa w oknie
dialogowym. Ze wzgldu na uprzednie sprawdzenie typu obiektu przeprowadzone
operatorem is wiemy, e zmienna SelectedItem ma nie tylko zadeklarowany typ
System.Object, ale rwnie specjalny typ DesktopChangeEvent.
Chcc uzyska dostp do tych specjalnych elementw na przykad zmiennej ImageFileName moemy skorzysta z dwch wariantw skadniowych konwersji typw,
przedstawionych na listingu 3.56.
Listing 3.56. Dwa warianty zapisu konwersji typw
DesktopChangeEvent(ListBox1.SelectedItem).ImageFileName := NowaNazwa;
// lub:
(ListBox1.SelectedItem as DesktopChangeEvent).ImageFileName := NowaNazwa;

W rodowisku Win32 te dwa warianty zachowuj si nieco odmiennie: Przedstawione wyej operacje kontrolne wykonywane s tylko w wariancie z operatorem as.
Oznacza to, e pierwszy wariant jest nieco wydajniejszy pod warunkiem, e ju
wczeniej sami skontrolowalimy typ obiektu i mamy cakowit pewno, e bdzie
on zgodny z typem konwersji. Jeeli jednak bdziemy prbowa uzyska dostp do
elementw obiektu nieobsugiwanych przez jego typ i w zwizku z tym skonwertowa
go na typ niezgodny, to wywoany zostanie odpowiedni wyjtek.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

331

Oba warianty powoduj dokadnie takie samo dziaanie na platformie .NET: Przed
konwersj sprawdzane jest, czy podany obiekt jest zgodny z podanym typem (jest to
odpowiednik jawnego zastosowania operatora is). Jeeli tak jest, to umoliwiany jest
dostp do wybranego elementu obiektu, a w przeciwnym wypadku wywoywany jest
wyjtek.

Inne informacje o typie


Za pomoc operatora is mona stwierdzi w czasie dziaania programu, czy dany
obiekt jest zgodny z podanym typem. To tylko jedna z moliwych do uzyskania informacji o typach, ktre szeroko udostpniao ju Delphi dla Win32, a w rodowisku .NET
liczba tych informacji wzrosa jeszcze bardziej, dziki metadanym zapisywanym
w kadym kompilacie.
Metadane, jakich wymaga CLR, dla kadej klasy zachowywane s w specjalnym obiekcie klasy System.Type. Taki obiekt typu Type mona odczyta w czasie dziaania programu z dowolnego obiektu w systemie. W tym celu naley wywoa metod GetType
danego obiektu. Poprzez uzyskany w ten sposb obiekt Type mona odczyta pozostae dane o danym typie, takie jak jego klasa bazowa, nazwa samej klasy i kompilat,
w ktrym zdefiniowany jest ten typ.
Mona na przykad w jednym z formularzy powiza zdarzenia MouseMove wszystkich
kontrolek z metod podan na listingu 3.57, w ktrej przy kadym poruszeniu myszy
na pasku tytuu wywietlane bd informacje o klasie kontrolki, nad ktr znajduje si
aktualnie kursor myszy.
Listing 3.57. Metoda sprawdzajca klas kontrolki wskazywanej przez kursor myszy
procedure TWinForm.AnyControl_MouseMove(sender: System.Object; e:
System.Windows.Forms.MouseEventArgs);
begin
Text := 'Myszka jest nad kontrolk ' + sender.GetType.Name
+ ' o klsie bazowej: ' + sender.GetType.BaseType.Name;

Alternatyw w stosunku do metody GetType jest wykorzystanie wbudowanej w kompilator funkcji typeof. Jej zalet w stosunku do metody GetType jest to, e nie potrzebuje ona obiektu, eby odczyta metadane jego typu, ale wystarczy poda jej w parametrze sam typ, tak jak na listingu 3.58.
Listing 3.58. Wykorzystanie funkcji typeof wbudowanej w kompilator
// TypeData: System.Type;
TypeData := typeof(sender); // Odpowiada podanemu wyej wywoaniu funkcji GetType
TypeData := typeof(Integer); // Zwraca metadane typu Integer

Dobre przykady zastosowania funkcji typeof znale mona w podpunktach Typy


wyliczeniowe w rodowisku .NET (strona 360) i Serializacja XML (strona 213).

332

Delphi 2005

Referencje klas
Przedstawiona wyej klasa System.Type dostpna jest wycznie w rodowisku .NET,
ale samo Delphi dla wszystkich platform udostpnia te tak zwane referencje klas,
ktre pozwalaj w sposb przenony uzyskiwa najwaniejsze informacje o danym typie. Modu System przechowuje deklaracj typu opisujcego najbardziej podstawow
referencj klasy:
type
TClass = class of TObject;

Nie naley miesza identyfikatora TClass ze zwykymi klasami, ktre rwnie deklarowane s z wykorzystaniem sowa kluczowego class, ale bez sowa of. Typ referencji klasy nazywany jest te metaklas, poniewa referencje klas przechowuj tylko informacje o klasach, takie jak ich nazwa i dane klasy bazowej. Jeeli typ referencji
klas zadeklarowany zostanie zapisem of TMojaKlasa, to w ten sposb zawany jest
zakres klas, ktrych dotyczy mog takie referencje, do klasy TMojaKlasa i klas z niej
wywiedzionych. Wynika z tego, e typ TClass jest najbardziej oglnym typem referencji klas, poniewa umoliwia on odczytywanie informacji o dowolnych klasach.
Referencj klasy otrzyma mona przypisujc do zmiennej typu referencji nazw interesujcej nas klasy lub wywoujc metod ClassType dowolnego obiektu. Tak otrzyman referencj klasy mona zapisa do zmiennej o typie referencji klasy, tak jak to pokazano na listingu 3.59.
Listing 3.59. Sposb uzyskiwania referencji klasy
var
ClassRef: TClass;
begin
ClassRef := Button; // Moliwo 1.: "Przypisanie" nazwy klasy
ClassRef := Button1.ClassType; // Moliwo 2.: Wywoanie metody danego obiektu

Metody klasy TObject


W tabeli 3.2 podane zostay metody dostpne w standardowej klasie Delphi TObject
(oznacza to, e mona je wywoa w kadym obiekcie w Delphi, nawet w tych implementowanych w innych jzykach, w ktrych nie istnieje klasa TObject4), bezporednio zwizane z referencjami klas.
Funkcje typu class function podawane w tabeli mog by wykorzystywane niezalenie od samych obiektw albo by wywoywane na rzecz konkretnego obiektu, tak
jak na listingu 3.60.

Do rozbudowywania takich obcych obiektw firma Borland stosuje specjaln technik tak zwanych
klas pomocniczych (ang. Class helpers), ktrych nie mona myli z mechanizmem dziedziczenia.
S one krtko opisywane w systemie aktywnej pomocy Delphi, ale dla normalnego tworzenia aplikacji
w Delphi nie maj praktycznie adnego znaczenia.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

333

Tabela 3.2. Metody zwizane z referencjami klas


Pocztek
funkcji

Funkcja

Typ
zwracany

Wynik

class function

ClassName

String

Nazwa obiektu lub klasy

class function

ClassNamels(Str)

Boolean

Sprawdza, czy nazwa klasy jest zgodna


z podanym cigiem znakw

class function

ClassParent

TClass

Klasa bazowa obiektu lub klasy

function

ClassType

TClass

Klasa obiektu

class function

ClassInfo

Type

Zwraca, uzaleniony od platformy, obiekt


zawierajcy dokadne informacje o typie.
Na platformie .NET metoda ta odpowiada
metodzie Object.GetType i zwraca obiekt
typu System.Type.

Class function

InheritsForm(AClass)

Boolean

Sprawdza, czy klasa wywiedziona jest


z klasy AClass

Listing 3.60. Sposoby wywoywania metody ClassName


// Zmienna Nazwa moe by zadeklarowana z typem String
Nazwa := TButton.ClassName;
Nazwa := Button1.ClassName;

Ten drugi wariant tak naprawd nie powinien by dopuszczalny, jako e wywoywana
jest w nim metoda statyczna ClassName na rzecz konkretnego obiektu. To rozwizanie
dziaa, poniewa w czasie pracy programu sprawdza on, jakiej klasy jest zmienna
Button1, i wywouje metod ClassName na rzecz tej wanie klasy, a nie na rzecz obiektu.
W rzeczywistoci drugie wywoanie metody ClassName wyglda nastpujco:
Name := Button1.ClassType.ClassName;

3.3.5. Konstruktory wirtualne


Na koniec, w tym punkcie przedstawi jeszcze jedn specjalno Delphi: konstruktory
wirtualne. Realizacja takich konstruktorw moliwa jest wycznie dziki omawianym
w poprzednim rozdziale referencjom klas i tworzonemu przez nie polimorfizmowi.
W punkcie 3.3.3 przedstawiaem kod, w ktrym na podstawie wyboru uytkownika
tworzony by obiekt okrelonej klasy (kod ten powtarzam na listingu 3.61).
Listing 3.61. Tworzenie obiektu na podstawie wyboru uytkownika
case WyborUzytkownika of
{ "WyboremUzytkownika" moe by na przykad warto (Sender as TButton).Tag }
Button1: ObiektRoboczy := Klasa1.Create;
Button2: ObiektRoboczy := Klasa2.Create;
...
Button10: ObiektRoboczy := Klasa10.Create;

334

Delphi 2005

Jak wida, moemy co prawda utworzy obiekt odpowiedni dla wyboru dokonanego
przez uytkownika, ale zdecydowanie niezadowalajcy jest brak moliwoci zapisania
w zmiennej wybranej klasy. Po utworzeniu zmiennej ObiektRoboczy, uzyskujemy dostp do wszystkich wirtualnych funkcji tego obiektu, tak jak tego chcielimy. Jeeli
jednak w innym miejscu w programie konieczne byoby utworzenie drugiego obiektu
o tym samym typie (uzalenionym od wyboru uytkownika), to ponownie zostaniemy
zmuszeni do wpisywania dugiej instrukcji case (albo przygotowania specjalnej procedury pozwalajcej wielokrotnie wykorzysta raz wpisan instrukcj case).
Problem ten mona rozwiza o wiele bardziej elegancko, wykorzystujc przy tym
referencje klas, tak jak na listingu 3.62.
Listing 3.62. Przechowywanie referencji klasy w zmiennej
type
ReferencjaKlasyAbstrakcyjnej = class of KlasaAbstrakcyjna;
var
WybranaKlasa: ReferencjaKlasyAbstrakcyjnej;
Objekt: KlasaAbstrakcyjna;
begin
Objekt := WybranaKlasa.Create;

Dziki przedstawionej wyej instrukcji uzyskujemy dokadnie ten sam efekt, jaki tworzony by przez dugi blok instrukcji case z poprzedniego listingu. Oczywicie zmienna
referencji klasy musi ju wczeniej otrzyma odpowiedni warto. Jeeli warto ta
uzaleniona byaby od wyboru dokonanego przez uytkownika, to nadal bdziemy
musieli skorzysta z odpowiednio zmodyfikowanej instrukcji case, na przykad takiej
jak na listingu 3.63.
Listing 3.63. Instrukcja case przygotowujca referencj klasy
case WyborUzytkownika of
Button1: WybranaKlasa := Klasa1;
Button2: WybranaKlasa := Klasa2;
... itd.

Jest to jednak niezaprzeczalna zaleta, e instrukcja case musi tylko raz obsuy wybr dokonany przez uytkownika i zapisa go jako referencj klasy. Referencj klasy
mona sobie te wyobrazi jako zmienn podobn do zmiennej obiektowej, ktra zamiast obiektu przechowuje klas.

Konstruktory wirtualne
Przedstawiony wyej kod niesie ze sob pewne zagroenie, znajdujce si w wierszu:
Objekt := WybranaKlasa.Create;

Wywoanie to zawsze przygotuje obiekt waciwej klasy, ale za kadym razem wywoywany bdzie tylko konstruktor klasy KlasaAbstrakcyjna, ktry dziedziczony jest
przez wszystkie klasy wywiedzione. Jeeli klasy te musz wykonywa w swoich konstruktorach inne wane operacje inicjalizacji obiektu, to w powyszym wierszu kodu

Rozdzia 3. Jzyk Delphi w rodowisku .NET

335

wywoywane musz by wanie konstruktory klas wywiedzionych. Takie funkcjonowanie


tego zapisu mona osign, deklarujc konstruktor Create klasy KlasaAbstrakcyjna
ze sowem kluczowym virtual, przez co konstruktor ten traktowany jest jako konstruktor wirtualny, a w klasach wywiedzionych (czyli klasach Klasa1, Klasa2 itd.) mona
w deklaracjach konstruktorw zastosowa sowo kluczowe override.
Konstruktory wirtualne mona sensownie wykorzysta tylko wtedy, gdy podobnie jak
w naszym przykadzie wywoywane s poprzez referencj klasy. We wszystkich pozostaych przypadkach ju w czasie kompilacji ustalany jest konstruktor wywoywany
w celu utworzenia obiektu, i w takim zakresie cakowicie wystarczajce jest dowizywanie statyczne.
Ten sam efekt uzyska mona stosujc rodki udostpniane przez rodowisko
.NET, cho nie jest to ju tak proste. Zamiast referencji klasy stosowany jest tu
obiekt typu System.Type, a wywoanie konstruktora trzeba w tym wypadku spowodowa zdalnie, wykorzystujc do tego metod System.Type.Invoke. Jeeli
tworzone przez nas klasy maj by te stosowane w innych jzykach programowania, to lepiej byoby zamiast wirtualnego konstruktora zastosowa konstruktor
statyczny i uzupeni go o wirtualn metod inicjalizujc, ktra musiaaby by
wywoywana oprcz samego konstruktora.

3.4. Typy interfejsw


W programowaniu zorientowanym obiektowo wszystkie elementy danej klasy, ktre
uywane s z zewntrz tej klasy, tworz tak zwany publiczny interfejs tej klasy. W procesie dziedziczenia klasa wywiedziona dziedziczy rwnie publiczny interfejs klasy bazowej.
Czci programu pracujce z obiektem klasy bazowej mog rwnie dobrze rozpocz
prac ze wszystkimi klasami z niej wywiedzionymi, co umoliwia wanie ta pena
zgodno interfejsw.
Konstrukcja jzykowa interfejsw powoduje oddzielenie interfejsu od samej klasy, dziki
czemu mona mwi o samym interfejsie niezwizanym z adn konkretn klas.
Przedstawianych tutaj interfejsw obiektw i klas nie naley myli z interfejsami
moduw, ktre w jzyku Object Pascal definiowane s tym samym sowem kluczowym interface. W tym podrozdziale sowo kluczowe interface dotyczy bdzie wycznie interfejsw klas i obiektw.

3.4.1. Czym jest interfejs?


Interfejs jest waciwie tylko list waciwoci i metod, przy czym metody mog by
w interfejsie wycznie deklarowane (czyli mog by wymieniane ich nazwy, parametry i typy zwracane), ale nie mog by wewntrz interfejsu implementowane. Przykadow posta interfejsu przedstawiam na listingu 3.64.

336

Delphi 2005

Listing 3.64. Przykadowy interfejs


type
IContainer = interface
procedure AddElement(ElementName: string);
procedure DeleteElement(ElementName: string);
function GetElementCount: integer;
function GetFirstElementName: string;
end;

Powysza deklaracja opisuje interfejs, za pomoc ktrego do obiektu kontenera moemy dodawa elementy opisywane cigiem znakw (AddElement), a take usuwa je
z tego kontenera podajc nazw elementu (DeleteElement). Pozostae dwie metody
pozwalaj na odczytanie liczby elementw zapisanych w kontenerze, jak rwnie na
uzyskanie nazwy pierwszego elementu. Sposb definiowania pierwszego elementu,
a take czci skadowych poszczeglnych elementw kontenera, nie jest okrelany
w samym interfejsie, ale musi by zdefiniowany w obiekcie kontenera. Przedstawiony
interfejs nie mwi te nic o samym obiekcie kontenera, poza tym, e przechowuje on
jakie nieokrelone elementy.
Przykad interfejsu przedstawiony na listingu 3.64 znale mona te na pycie CD
doczonej do ksiki, w projekcie InterfaceDemo. Projekt ten jest aplikacj
VCL.NET i ma demonstrowa wycznie zasad funkcjonowania interfejsw, jak
naley ich uywa i je implementowa. Z ca pewnoci nie jest to jednak przykad dobrego interfejsu. W rodowisku .NET bardzo szeroko stosowany jest wzorowy interfejs IList, ktry rwnie umoliwia dodawanie i usuwanie elementw ze
zbioru i w zwizku z tym w przykadowym programie mgby by wykorzystany
w miejscu samodzielnie definiowanego interfejsu IContainer. Interfejs IList jest
jednak o wiele bardziej rozbudowany, wobec czego w przykadowym programie atwiejsze byo zastosowanie znacznie mniejszego interfejsu IContainer.

Na rysunku 3.11 zostao przedstawione okno przykadowego programu. W czasie jego


pracy za pomoc przecznikw mona wybra jeden z dwch kontenerw, do ktrych
dodawane i usuwane s cigi znakw wprowadzane do pola edycyjnego:
Elementami kontenera Formularz s przyciski. Za kadym razem,
gdy wywoywana jest funkcja AddElement, w dolnej czci formularza
tworzony jest nowy przycisk. Podobnie, po wywoaniu funkcji DeleteElement

odpowiedni przycisk jest usuwany z formularza. Ten wyjtkowo mao


sensowny tryb dziaania wybrany zosta jako kontrast dla drugiego obiektu
kontenera.
Drugi z obiektw-kontenerw Lista po kadym wywoaniu metody
AddElement zachowuje wybrany element, ale nigdzie go nie wywietla.

Chcc przekona si, e dodane do listy elementy rzeczywicie si w niej


znajduj, trzeba odczyta z tej listy pierwszy element wywoaniem funkcji
GetFirstElementName, a nastpnie go usun i powtarza t operacj
z kolejnymi elementami.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

337

Rysunek 3.11. Okno programu stosujcego dwa cakowicie rne obiekty-kontenery obsugujce
dokadnie ten sam interfejs (przykadowy program InterfaceDemo)

Zmienne interfejsw
Programowanie z wykorzystaniem interfejsw polega na zadeklarowaniu zmiennej
o typie interfejsu i wykorzystywaniu jej tak jak zwyczajnego obiektu. W przykadowym
formularzu znajdziemy zmienn CurrentContainer wskazujc na kontener wybrany
przez uytkownika za pomoc przecznikw (jej deklaracj przedstawiam na listingu
3.65). O tym, jak inicjowane s poszczeglne kontenery i jak przypisywane s one do
zmiennej CurrentContainer, bdziemy mwi nieco pniej.
Listing 3.65. Deklaracja zmiennej interfejsu kontenera
var
TForm1 = class ...
CurrentContainer: IContainer;

Nacinicie przyciskw Dodaj, Usu i Odczytaj pierwszy element spowoduje w programie wywoanie odpowiednich metod interfejsu IContainer, ktre przedstawiam na
listingu 3.66.
Listing 3.66. Metody interfejsu IContainer wywoywane w przykadowym programie
procedure TForm1.AddButtonClick(Sender: TObject);
begin
CurrentContainer.AddElement(ElementName.Text);
end;
procedure TForm1.DeleteButtonClick(Sender: TObject);
begin
CurrentContainer.DeleteElement(ElementName.Text);
end;

338

Delphi 2005
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage('Pierwszy element to: '+
CurrentContainer.GetFirstElementName);
end;

W czasie kompilacji programu nie jest jeszcze okrelone, ktry rodzaj kontenera bdzie
zapisany w zmiennej CurrentContainer. Wynika z tego, e zmienna ta jest obiektem
polimorficznym (ang. Polymorphes object), ktry znamy ju z opisywanego wczeniej
podobnego traktowania klas. Jeeli nie wykorzystywalibymy interfejsw, to zmienna
CurrentContainer mogaby by te zadeklarowana tak:
var
CurrentContainer : TContainer

Przy czym TContainer musiaaby by wtedy klas, ktrej deklaracj przedstawiam na


listingu 3.67.
Listing 3.67. Deklaracja klasy TContainer
type
TContainer = class
procedure AddElement(ElementName: string); abstract;
procedure DeleteElement(ElementName: string); abstract;
function GetElementCount: integer; abstract;
function GetFirstElementName: string; abstract;
end;

Wielk przewag interfejsw nad klas TContainer jest w fakt, e interfejs moe by
implementowany w cakowicie dowolnej klasie. Jeeli chcielibymy uzyska konkretn implementacj abstrakcyjnej klasy TContainer, to musielibymy przygotowa
now klas wywiedzion z klasy TContainer. Nowa klasa mogaby mie tylko jedn
klas bazow, dlatego klasa ta nie mogaby by ju wywodzona z adnej innej klasy.
Stosujc interfejs IContainer nie nakadamy na swoje poczynania takiego ograniczenia,
wobec czego w przykadowym programie interfejs ten implementowany jest przez
klas formularza.

Dziedziczenie interfejsw
W przypadku interfejsw obowizuj zasady dziedziczenia podobne do tych, ktre
znamy ju z dziedziczenia klas. Oznacza to, e stosujc zapis z listingu 3.68 moemy
zdefiniowa nowy interfejs, ktry bdzie mia wszystkie metody interfejsu IContainer
i dokada do nich wasn metod Mix.
Listing 3.68. Przykad dziedziczenia interfejsw
type
IMixableContainer = interface(IContainer)
procedure Mix;
end;

Rozdzia 3. Jzyk Delphi w rodowisku .NET

339

Najwaniejsze jest w tym wszystkim jednak to, e w interfejsach moliwe jest te


dziedziczenie wielobazowe, czyli interfejs moe by wywiedziony z kilku interfejsw
naraz, mniej wicej tak, jak pokazano to na listingu 3.69.
Listing 3.69. Interfejsy mog stosowa dziedziczenie wielobazowe
type
IMixable = interface
procedure Mix;
end;
IMixableContainer = interface(IContainer, IMixable)
end;

3.4.2. Implementowanie interfejsu


Do implementowania interfejsu zawsze potrzebowa bdziemy jakiej klasy. Implementowany interfejs podawany jest w nagwku deklaracji klasy, zaraz za klas
bazow. Oczywicie, klasa moe mie tylko jedn klas bazow, ale jednoczenie moe
implementowa wiele interfejsw, ktre wymieniane s wanie w nagwku deklaracji
klasy. Co wicej, klasa musi implementowa wszystkie metody zapisanych w tym miejscu
interfejsw, co oznacza, e nagwki metod wymieniane w deklaracji interfejsu musz
zosta powtrzone w deklaracji samej klasy.
Z historycznych powodw zwizanych z konstrukcj Delphi, w jzyku Object Pascal
kada klasa implementujca pewien interfejs musi by jawnie wywiedziona z innej
klasy. Jeeli nasza klasa nie wymaga adnej klasy bazowej, to mona j zadeklarowa
zgodnie z wzorcem TMojaImplementacja(TObject, IMojInterfejs). W Delphi dla
Win32 interfejsy s dodatkowo powizane z modelem COM (ang. Component Object
Model), co oznacza, e w rodowisku Win32 jako klasy bazowej dla klasy implementujcej obiekty nie mona wykorzysta klasy TObject, ale trzeba skorzysta
z klasy TInterfacedObject. Jest to tylko jedna z wielu rzeczy, jak naley uwzgldni w czasie implementowania interfejsw w rodowisku Win32. Akurat w zakresie
obsugi interfejsw w rodowisku .NET nastpio znaczne uproszczenie obowizujcych procedur.

Cig dalszy przykadu z interfejsem IContainer


Jak ju wspominaem, przykadowy program wykorzystuje dwie niezalene implementacje interfejsu IContainer i w zwizku z tym konieczne jest w nim przygotowanie
dwch klas, z ktrych kada cakowicie odmiennie implementuje ten interfejs. Jedna
z tych klas, implementujca interfejs w postaci listy, przedstawiona zostaa na listingu 3.70.
Listing 3.70. Implementacja interfejsu IContainer
// uses Borland.Vcl.Classes
type
TListContainer = class(TStringList, IContainer)
procedure IContainer.AddElement = Append;
procedure DeleteElement(const ElementName: string);

340

Delphi 2005
function GetElementCount: integer;
function GetFirstElementName: string;
end;
implementation
procedure TListContainer.DeleteElement(const ElementName: string);
begin
if IndexOf(ElementName)>-1 then
Delete(IndexOf(ElementName));
end;
function TListContainer.GetElementCount: integer;
begin
Result := Count;
end;
function TListContainer.GetFirstElementName: string;
begin
if Count>0 then Result:=Strings[0]
else Result := '(Lista jest pusta)';
if Result='' then Result := '(Pierwszy element nie ma nazwy.)';
end;

Klasa TListContainer dziedziczy pen funkcjonalno listy z klasy TStringList pochodzcej z biblioteki VCL.NET, i na przykad udostpnia wykorzystywane na listingu
metody Append, Delete, IndexOf, Count i Strings. Jak wida, klasa TListContainer waciwie przenosi dziaanie funkcji klasy TStringList do metod wymienianych w interfejsie IContainer.

Zmiany nazw metod


W przypadku metody IContainer.AddElement operacje wykonywane w implementacji
interfejsu s wyjtkowo proste, poniewa dziaanie odziedziczonej metody Append jest
cakowicie zgodne z tym, co powinna robi metoda AddElement, a na dodatek ma ona
dokadnie taki sam format wywoania (jeden parametr typu String i brak wartoci zwracanej). Wanie dlatego w interfejsie IContainer nie ma potrzeby implementowania metody AddElement, ale wystarczy poczy j z istniejc metod TStringList.Append:
procedure IContainer.AddElement = Append;

Takie jawne powizanie metody interfejsu z metod implementacji moliwe jest rwnie
wtedy, gdy tworzymy wasn metod implementacji i nadajemy jest nazw zupenie
inn od nazwy metody interfejsu. W takiej sytuacji trzeba tylko pamita o uzupenieniu
nazwy metody interfejsu o odpowiedni klauzul zmiany nazwy.

Funkcje pomocy w programowaniu i uzupenianie klas


Dostpne w edytorze Delphi funkcje pomocy w programowaniu, w zakresie implementowania interfejsw oferuj jeszcze jedn ciekaw rzecz: Jeeli kursor edytora
znajduje si wewntrz deklaracji klasy, ktra obsuguje interfejsy, to po naciniciu
w pustym wierszu klawiszy Ctrl+Spacja wywietlona zostanie lista brakujcych jeszcze

Rozdzia 3. Jzyk Delphi w rodowisku .NET

341

implementacji metod interfejsw. Normalnie lista wyboru wywietlana wewntrz deklaracji klasy zawiera tylko metody odziedziczone z klasy bazowej, ktre mona pokry
w klasie wywiedzionej. Jeeli jednak w klasie tej brakuje jeszcze metod wymaganych
przez implementowany interfejs, to Delphi wywietla je na samym pocztku wywietlanej listy, a dodatkowo wyrnia kolorem czerwonym.
Oczywicie mona te wywoa funkcj uzupeniania klasy (naciskajc kombinacj
klawiszy Shift+Ctrl+C), eby w ten sposb przygotowa szkielety implementacji wszystkich metod wymienionych w deklaracji tej klasy.

Druga implementacja interfejsu IContainer


Druga klasa z przykadowego programu, implementujca interfejs IContainer, to klasa samego formularza aplikacji jak ju wspominaem, jest to aplikacja korzystajca
z biblioteki VCL.NET, dlatego formularz ten nie jest wywiedziony z klasy System.
Windows.Forms.Form, ale z klasy TForm:
type
TForm1 = class(TForm, IContainer)

Moemy sobie tu podarowa ponowne wypisywanie wszystkich metod interfejsu


IContainer. Podobnie niewiele do omawianego tematu wnosz nam implementacje
metod tego interfejsu. W ramach przykadu przedstawi zatem (na listingu 3.71) wycznie implementacj metody AddElement, ktra dynamicznie tworzy obiekty typu
TButton pochodzcego z biblioteki VCL.NET i dodaje go do obszaru na formularzu
(kontrolka typu TScrollBox) przeznaczonego na tworzone w ten sposb przyciski.
Listing 3.71. Implementacja metody AddElement w drugiej klasie implementujcej interfejs IContainer
procedure TForm1.AddElement(const ElementName: string);
var
B: TButton;
const
AddCount: Integer = 0;
begin
B := TButton.Create(self);
B.Parent := ScrollBox1;
B.Width := 100;
B.Top := (AddCount div 5)*B.Height;
B.Left := (AddCount mod 5)*B.Width;
B.Caption := ElementName;
inc(AddCount);
end;
procedure TForm1.DeleteElement(const ElementName: string);
var
i: Integer;
begin
for i := ScrollBox1.ControlCount-1 downto 0 do
if (ScrollBox1.Controls[i] as TButton).Caption = ElementName
then ScrollBox1.Controls[i].Free;
end;

342

Delphi 2005

Tworzenie obiektw z interfejsami


Teraz naleaoby utworzy obiekty na podstawie klas implementujcych interfejs
IContainer, co bdzie wymagao przypomnienia sobie kilku rzeczy na temat klas w jzyku Object Pascal. Obiekt implementujcy list utrzymamy w sposb przedstawiany
na listingu 3.72.
Listing 3.72. Tworzenie obiektu na podstawie klasy implementujcej interfejs
var
ListContainerObject: TListContainer;
begin
ListContainerObject := TListContainer.Create;
end.

Obiekt implementacji formularza jest automatycznie tworzony w kodzie przygotowanym przez Delphi, pamitamy wszak, e jest to formularz aplikacji.
Od tego momentu droga od obiektu do zmiennej interfejsu jest ju bardzo prosta
wystarczy do tej zmiennej przypisa istniejcy obiekt, tak jak na listingu 3.73.
Listing 3.73. Sposoby wizania zmiennej interfejsu z obiektem implementujcym interfejs
var
ListContainer: IContainer;
begin
ListContainer := ListContainerObject;
(* Mona te osign to samo nie wykorzystujc poredniczcej zmiennej ListContainerObject:
ListContainer := TListContainer.Create; *)

Z poziomu zmiennej interfejsu nie bdziemy mieli dostpu do wszystkich teoretycznie


istniejcych metod klasy FormContainer, poniewa przypisanie to jest rwnoznaczne
z konwersj typw z klasy wywiedzionej do jednej z klas wyszego poziomu.
Teraz moemy ju przej do zapisywania wartoci do zmiennej CurrentContainer,
o ktrej mwilimy ju w punkcie 3.4.1. W programie, po klikniciu na przeczniku
przypisywany jest do niej odpowiedni obiekt implementujcy interfejs IContainer.
Procedura obsugujca te przypisania przedstawiona zostaa na listingu 3.74.
Listing 3.74. Przypisanie wybranego przez uytkownika obiektu implementujcego interfejs
do zmiennej interfejsu
procedure TForm1.ContainerTypClick(Sender: TObject);
begin
case ContainerTyp.ItemIndex of
0: CurrentContainer := self;
1: CurrentContainer := ListContainer;
end;
end;

Rozdzia 3. Jzyk Delphi w rodowisku .NET

343

Obiekty z wieloma interfejsami


Jak ju mwiem, jedna klasa moe obsugiwa kilka interfejsw. Postaram si zademonstrowa to za pomoc naszego przykadowego programu, poniewa wie si z tym
interesujca moliwo zmiany jednego interfejsu w inny.
Po pierwsze, w przykadowym programie istnieje te drugi interfejs deklarujcy zaledwie
jedn metod, za pomoc ktrej mona uzyska informacj o tym, jaka konkretna klasa
ukrywa si za polimorficznym interfejsem. Deklaracja tego interfejsu widoczna jest
na listingu 3.75.
Listing 3.75. Deklaracja drugiego interfejsu z przykadowego programu
type
IClassDescription = interface
function GetClassDescription: string;
end;

Ten nowy interfejs implementowany jest w obu przedstawianych do tej pory klasach.
Na listingu 3.76 przedstawiam skrt tej implementacji.
Listing 3.76. Implementacja drugiego interfejsu w programie przykadowym
// TForm1 = class(TForm, IContainer, IClassDescription)
// TListContainer = class(TStringList, IItemContainer, IClassDescription)
function TListContainer.GetClassDescription: string;
begin
Result:='Lista cigw znakw';
end;
function TForm1.GetClassDescription: string;
begin
Result := 'TForm1';
end;

Chcc uywa nowego interfejsu, nie musimy ju tworzy adnych nowych zmiennych typu IClassDescription, bo w zupenoci wystarczy nam istniejca ju zmienna
CurrentContainer, cho jest ona typu IContainer, ktry nie ma adnych zwizkw
z interfejsem IClassDescription.
Cay czas pracujemy tutaj na interfejsach, w zwizku z czym za pomoc operatora as
moemy zmieni typ zmiennej CurrentContainer na IClassDescription i wtedy wywoa na jej rzecz metod GetClassDescription. W przykadowym programie, po klikniciu na przeczniku aktualizowana jest zawarto kontrolki typu Label wypisujcej
tekst, jakim przedstawia si aktualnie wybrana implementacja kontenera. Odpowiedni
kod przedstawiam na listingu 3.77.

344

Delphi 2005

Listing 3.77. Wywoanie metody pochodzcej z drugiego interfejsu implementowanego w obu obiektach
procedure TForm1.ContainerTypClick(Sender: TObject);
begin
...
Label2.Caption := (CurrentContainer as IClassDescription).GetClassDescription;
end;

Operator as powoduje, e w czasie dziaania programu rodowisko CLR sprawdza, czy


obiekt przypisany do zmiennej CurrentContainer obsuguje te wymieniony interfejs
IClassDescription. Jeeli tak nie bdzie, to w programie wygenerowany zostanie
wyjtek. W naszym przykadowym programie moemy jednak zakada, e interfejs
ten bdzie obsugiwany i przeksztacenie wykonywane przez operator as zakoczy si
sukcesem.

Dziedziczenie jedno- i wielobazowe


Podsumowujc, mona powiedzie, e w jzyku Object Pascal, podobnie jak i w caym
rodowisku .NET, obowizuje zasada mwica, e dziedziczenie wielobazowe moliwe jest wycznie w przypadku interfejsw. Jedna klasa moe implementowa kilka
interfejsw, a nowy interfejs moe rozbudowywa kilka innych interfejsw. Taki rodzaj
dziedziczenia dotyczy jednak wycznie samych interfejsw, a nie ich implementacji.
W przypadku implementacji jzyk Object Pascal, a take rodowisko .NET przewiduj wycznie moliwo dziedziczenia prostego (jednobazowego). Kada klasa moe
mie co najwyej jedn klas bazow, po ktrej dziedziczy implementacje wszystkich
jej metod. Nie ma przy tym adnego znaczenia to, czy metody te obsuguj jeden interfejs, kilka interfejsw, czy moe nie ma w nich adnego interfejsu. W zakresie stosowania dziedziczenia jedno- i wielobazowego jzyk Object Pascal i rodowisko .NET
s cakowicie zgodne z jzykiem Java. Z kolei jzyk C++ pozwala na stosowanie
dziedziczenia wielobazowego rwnie w implementacjach, ale jest to wyjtkowo problematyczna funkcja tego jzyka, przez ktr bardzo atwo mona wprowadzi zamieszanie do programu i dlatego jest niezwykle rzadko stosowana.

3.5. Podstawy jzyka Object Pascal


W dotychczasowych podrozdziaach omawiaem struktur moduw i model obiektw
jzyka Object Pascal, a take wynikajce z tych zagadnie czci programw, takie
jak moduy, klasy i obiekty. Teraz moemy przej do bardziej szczegowych elementw jzyka. W tym podrozdziale zajmiemy si najmniejszymi elementami programw, czyli poszczeglnymi sowami, z ktrych skada si kod rdowy programu
przygotowanego w jzyku Object Pascal, a nastpnie przedstawi kilka oglnych uwag
dotyczcych samego jzyka. W poprzednich podrozdziaach operowalimy tylko najprostszymi typami danych, takimi jak integer lub string, natomiast w podrozdziale 3.6
postaram si przedstawi wszystkie inne typy jakie dostpne s w jzyku Object Pascal.
W podrozdziale 3.7 analizowa bdziemy te poszczeglne instrukcje, z ktrych skada si kod implementacji metod obiektw.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

345

3.5.1. Elementy leksykalne


Kod rdowy programw w jzyku Object Pascal moe skada si wycznie z nastpujcych elementw:
z gry ustalonych sw kluczowych jzyka, operatorw i znakw interpunkcyjnych,
bezporednio wpisywanych wartoci staych rnych typw,
identyfikatorw,
komentarzy,
atrybutw.

Wszystkie te punkty przeglda bdziemy w odwrotnej kolejnoci.


Atrybuty wprowadzone zostay do Delphi w celu dostosowania jego jzyka do wymogw
rodowiska .NET. Mog si one znajdowa przed okrelonymi deklaracjami, gdzie
stanowi dodatkowe informacje opisujce deklarowany symbol. Mona je rozpozna
po obejmujcych je nawiasach prostoktnych. Wicej informacji na temat atrybutw
podawa bd w punkcie 3.5.6.
Komentarze mog by umieszczane w dowolnym miejscu w kodzie programu, pomidzy innymi elementami skadniowymi, i mog zawiera w sobie cakowicie dowolne
znaki, oczywicie z wyjtkiem znakw, ktre stosowane s jako oznaczenie koca komentarza. Jzyk Object Pascal pozwala nam wybiera spord trzech rnych oznacze
komentarzy w kodzie programu:
{ Komentarze mog by zamknite w nawiasach klamrowych. }
(* Mona te stosowa takie oznaczenia, ale nie wolno miesza ze sob rnych oznacze. *)
x := 0; // Ten komentarz zakoczy si "automatycznie" wraz z kocem wiersza

Pierwsze dwa rodzaje komentarzy mona zagnieda w sobie na dwch poziomach,


pod warunkiem jednak, e dla kadego poziomu stosowane bd inne znaki ograniczajce zakres komentarza.
Identyfikatory mog skada si z liter, cyfr oraz znaku podkrelenia, ale ze wzgldu na
konieczno zachowania jednoznacznoci zapisu nie mog si rozpoczyna od cyfry.
Bardzo interesujc nowink wprowadzon do Delphi 2005 jest to, e po 32 latach
istnienia jzyka Pascal nie musz skada si one wycznie z liter podstawowego zestawu znakw ASCII, ale dopuszczono w nich rwnie znaki alfanumeryczne Unicode.
Dziki temu w identyfikatorach mona stosowa znaki narodowe, takie jak , , lub .
Z tej moliwoci wykluczone zostay jednak opublikowane (ang. published) elementy
klas oraz ich typy. W podanym niej wierszu kodu ukrywa si kolejna regua jzyka:
Nie_ma_rozrnienia_pomidzy_Wielkimi_i_maymi_literami (* . *)

Stae wartoci w zalenoci od typu musz stosowa si do regu zapisu przedstawionych


w tabeli 3.3.

346

Delphi 2005

Tabela 3.3. Reguy zapisu wartoci staych


Typ

Budowa zapisanej wartoci

Przykad

Liczby cakowite

Cig cyfr z zakresu od 0 do 9

9876543210

Liczby szesnastkowe

Liczba szesnastkowa z umieszczonym


na pocztku znakiem dolara ($)

$F0D9

Liczby
zmiennoprzecinkowe

Liczba z uamkiem dziesitnym + wykadnik 3.4e5 lub 1.4e-100 lub 49e-4

Znaki

Znaki zamknite w apostrofach


lub #+kod znaku

'A' lub (rwnowane) #65

Cigi znakw

Cigi znakw zamknite w apostrofach

'XYZ'

Zbiory

Listy elementw zamknite w nawiasach


prostoktnych

[biSystemMenu, biMinimize,
biMaximize]

W przypadkach szczeglnych, w ktrych konieczne jest zastosowanie znaku ograniczajcego wewntrz cigu znakw albo jako pojedynczego znaku, wystarczy zapisa dwa
takie znaki jeden za drugim. Jeeli chcielibymy zapisa apostrof jako pojedynczy znak,
to naleaoby zastosowa zapis '''', natomiast cudzysw wewntrz cigu znakw
powinien wyglda tak: '"'.
Sowa kluczowe jzyka Object Pascal s sowami zarezerwowanymi, ktrych nie mona
stosowa w programach jako identyfikatory. Oto lista sw kluczowych jzyka:
and

array

as

asm

begin

case

class

const

constructor

destructor

dispinterface

div

do

downto

else

end

except

exports

file

finalization

finally

for

function

goto

if

implementation in

inherited

initialization inline

interface

is

label

library

mod

nil

not

object

of

or

out

packed

procedure

program

property

raise

record

repeat

resourcestring sealed

set

shl

shr

static

string

then

threadvar

to

try

type

unit

unsafe

until

uses

var

while

with

xor

Czci jzyka Object Pascal s rwnie sowa zapisane w nastpnej licie. Nazywane
s one dyrektywami standardowymi, a od sw kluczowych rni si tym, e nie s
zarezerwowane wycznie dla kompilatora. Dyrektywy te mog by uywane te jako
identyfikatory w tekcie programu, pod warunkiem jednak, e nie bd tworzy adnych dwuznacznoci w kodzie. Takie identyfikatory nie mog znale si tam, gdzie
najczciej uywane s dyrektywy standardowe, czyli na kocach nagwkw funkcji
i procedur. Z drugiej strony, dyrektywy standardowe nie mog by zapisywane w ciele
adnej metody. Sowa private, protected, public i published zarezerwowane zostay
w deklaracjach klas, dziki czemu unika si nieporozumie w tym zakresie.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

347

absolute

abstract

assembler

automated

cdecl

contains

default

deprecated

dispid

dynamic

export

external

forward

implements

index

library

local

message

name

nodefault

overload

override

package

pascal

platform

private

protected

public

published

read

readonly

register

reintroduce

requires

resident

safecall

stdcall

stored

varargs

virtual

write

writeonly

Niektre z tych dyrektyw, takie jak package i requires, maj jakiekolwiek znaczenie
wycznie w tekcie rdowym pakietw, dlatego w edytorze Delphi, w tekcie zwykego
moduu, w ogle nie s wyrniane. Pozostae wyrniane w edytorze sowa stosowane w jzyku Object Pascal, niebdce sowami kluczowymi, to sowa on oraz near
i far, ktre dzisiaj nie maj ju adnego praktycznego znaczenia, ale s obsugiwane
w jzyku w ramach zgodnoci z poprzednimi wersjami jzyka.
Podobnie jak w przypadku identyfikatorw, jzyk Object Pascal nie rozrnia wielkich
i maych liter rwnie w sowach kluczowych i dyrektywach standardowych.
Operatory i znaki interpunkcyjne opisywane s w miar potrzeb w dalszej czci rozdziau.

3.5.2. Instrukcje kompilatora


Wewntrz programu tworzonego w jzyku zapisywa mona nie tylko instrukcje przeznaczone do wykonania w ramach samego programu, ale rwnie instrukcje przeznaczone dla kompilatora. Te ostatnie nie s czci faktycznego jzyka programowania,
dlatego znaleziono dla nich miejsce, w ktrym nie mona ich pomyli z elementami
jzyka programowania komentarze. Te specjalne komentarze rni si jednak od
zwyczajnych komentarzy tym, e zaczynaj si od znaku dolara ($). Na przykad, do
wczenia opcji kompilatora o nazwie BoolEval naley posuy si zapisem {$BoolEval On} lub jego form skrcon {$B+}, umieszczajc j wewntrz kodu rdowego programu (znaczenie tego przecznika objaniam w podpunkcie Wyliczanie
wartoci wyrae logicznych z punktu 3.6.2). Wiele z opcji kompilatora mona zmienia
nie tylko w podany wyej sposb w kodzie programu, ale rwnie w oknie dialogowym
wywoywanym poprzez pozycj menu Projekt/Options. W tym drugim rozwizaniu
ustalone opcje zapisywane s w pliku opcji projektu. Pen list opcji kompilatora
znale mona w systemie aktywnej pomocy Delphi pod indeksem Compiler directives.

Wczanie zasobw
Oprcz przedstawionych wczeniej przecznikw dostpne s te inne specjalne instrukcje, spord ktrych najczciej wykorzystywana jest instrukcja $R, zawsze generowana razem z kodem przygotowywanym przez Delphi. W kadym nowo utworzonym pliku projektu znale mona taki wiersz:
{$R 'WinForm.TWinForm.resources' 'WinForm.resx'}

Zapis ten powoduje, e automatycznie generowany plik zasobw WinForm.resx kompilowany jest do pliku WinForm.TWinForm.resources i wczany do aktualnego kompilatu.

348

Delphi 2005

Kompilacja warunkowa
Kompilacja warunkowa umoliwia wygenerowanie kilku wersji danego programu na
podstawie jednego pliku z kodem rdowym. Takie zabiegi opacaj si wtedy, gdy
rnice pomidzy tymi wersjami ograniczaj si do zaledwie kilku wierszy. Na przykad,
na podstawie aplikacji VCL.NET mona przygotowywa wersj dziaajc w rodowisku
.NET, wersj dla systemw Windows, a nawet wersj dla Linuksa. Innym przykadem
mog by dodawane do programu dodatkowe instrukcje wspomagajce wyszukiwanie
bdw, ktre powinny by usunite z ostatecznej wersji programu (takie dodatkowe
instrukcje przedstawiam na listingu 3.78.
Listing 3.78. Instrukcje kompilacji warunkowej
{$ifdef WarunekKompilatora}
... Kod programu ...
{$else}
... Alternatywny kod programu ...
{$endif}

W podanym wyej przykadzie kompilator tylko wtedy skompiluje pierwsz cz


programu, gdy speniony bdzie WarunekKompilatora, a w przeciwnym wypadku skompilowana zostanie druga cz kodu (trzeba tu zaznaczy, e cz kodu za dyrektyw
$else jest opcjonalna). Zamiast instrukcji $ifdef mona te uy instrukcji $ifndef,
ktra stanowi odwrcenie logiki poprzedniej instrukcji (co w rodzaju jeeli nie). Symbol
WarunekKompilatora nie ma nic wsplnego z jakimkolwiek identyfikatorem stosowanym
w kodzie rdowym programu, ale musi by definiowany w cakowicie inny sposb:
za pomoc instrukcji kompilatora {$define WarunekKompilatora},
wpisujc dany symbol do opcji projektu na zakadce Directories/Contitionals,

w pole Conditional defines,


wykorzystujc do tego sam kompilator. W kompilatorze zdefiniowane s

symbole, ktre umoliwiaj rozrnianie jego wersji i przygotowanie dla


kadej z nich osobnego kodu. Wyliczanie kolejnych wersji kompilatorw
rozpoczo si ju od Turbo Pascala 1.0, dlatego aktualne systemy rozwojowe
ju od dawna operuj dwucyfrowymi numerami wersji. W tabeli 3.4
przedstawiam najwaniejsze symbole zdefiniowane w dotychczasowych
produktach firmy Borland, zgodnych w Delphi.
Tabela 3.4. Symbole kompilatora definiowane w rnych wersjach Delphi
Produkt

Zdefiniowane symbole

Delphi 1

VER80, Windows, CPU86

Delphi 2

VER90, Win32, CPU386

Delphi 7

VER150, Win32, MSWINDOWS, CPU386

Kylix

Linux, CPU386, wersje od VER140 (Kylix 1)

Delphi 8

VER160, CLR

Delphi 2005 dla .NET

VER170, CLR

Delphi 2005 dla Windows

VER170, CLR, MSWINDOWS, CPU386

Rozdzia 3. Jzyk Delphi w rodowisku .NET

349

Nowsze dyrektywy warunkowe


Od czasu wersji VER140 kompilator obsuguje jeszcze alternatywny zestaw dyrektyw
kompilacji warunkowej: $if, $elseif i $ifend. Za pomoc tych dyrektyw, oprcz sprawdzania prostych symboli, mona te kontrolowa wartoci caych wyrae:
const MojaStala = 'LN';
{$if MojaStala = 'XA'}

W dyrektywach tych mona stosowa te zadeklarowane stae jzyka Object Pascal,


takie jak przedstawiona wyej staa MojaStala. Szczeglnie ciekawe moliwoci daje
nam tu moliwo sprawdzania wartoci staej RTLVersion, ktra w Delphi 2005 zadeklarowana zostaa z wartoci 17.0 i tym samym odpowiada numerowi wersji kompilatora. Podstawowa rnica w stosunku do prostego sprawdzenia symbolu kompilatora
VER170 polega na tym, e pozwala ona na uwzgldnienie te wszystkich przyszych
wersji biblioteki:
{$ifdef VER170}
// dotyczy wycznie Delphi 2005
{$if RTLVersion >= 17} // warunek bdzie speniony rwnie w przyszych wersjach Delphi

W kocu, Delphi udostpnia nam w poczeniu z dyrektyw $if jeszcze dwie specjalne
funkcje: Declared(MojSymbol), pozwalajc skontrolowa, czy zadeklarowany zosta
symbol jzyka Object Pascal, oraz Defined(SymbolKompilatora), ktra sprawdza, czy
zdefiniowany zosta podany symbol kompilatora. Ta druga funkcja jest praktycznie
rwnoznaczna z dyrektyw $ifdef.

Ostrzeenia wane dla przenonoci programu


Od 14. wersji kompilatora obsugiwane s te trzy nowe dyrektywy, za pomoc ktrych
mona oznacza dowolne deklaracje (zmiennych, typw, funkcji, klas lub moduw) jako
niemoliwe do stosowania bez ogranicze:
deprecated oznacza deklaracj jako przestarza, i jednoczenie zaleca

stosowanie nowoczeniejszej alternatywy.


platform opisuje deklaracj jako zalen od platformy i w ten sposb

ostrzega przed ewentualnymi problemami w czasie prb przeniesienia


aplikacji na inn platform.
library oznacza deklaracj jako zalen od stosowanej aktualnie biblioteki.

Takimi dyrektywami oznacza mona rwnie dowolne obiekty programu, stae i funkcje.
Przykad takiego oznaczenia staej przedstawiam na listingu 3.79.
Listing 3.79. Oznaczenie staej jako zalenej od platformy
const
// Przykad pochodzi z pliku SysUtils.pas: Znacznik ReadOnly
// jest atrybutem obecnym wycznie w systemach plikw stosowanych
// w systemach Windows (w Linuksie dostp do plikw regulowany jest
// za pomoc uprawnie Uytkownika, Grupy i Wszystkich).
faReadOnly = $00000001 platform;

350

Delphi 2005

Oczywicie, podobnie mona oznacza te cae rekordy lub klasy, a nawet cae moduy
(odpowiednie deklaracje przedstawiam na listingu 3.80).
Listing 3.80. Oznaczenie klasy i moduu jako zalenych od platformy
unit ZbiorFunkcjiDlaWindows platform;
type
// Przykad z pliku SysUtils.pas: Klasa jzykw obsugiwanych
// przez system dostpna jest wycznie w systemie Windows
TLanguages = class
...
end platform;

Dziaanie tych dyrektyw polega na tym, e kompilator generuje ostrzeenia, gdy nasz program prbuje wykorzysta obiekt lub inny element oznaczony tak dyrektyw. Sposb
wypisywania tych ostrzee moe by sterowany za pomoc dyrektyw kompilatora $Hints
ON/OFF i $Warnings (wicej na ten temat znale mona w pomocy Delphi).

3.5.3. Typy i zmienne


Klasy i obiekty s w Delphi wbudowane w funkcjonujce od zawsze w jzyku Pascal
koncepcje zmiennych i typw. Koncepcje te przygotowa w roku 1972 Niklaus Wirth
w tworzonej przez siebie pierwszej wersji jzyka Pascal. I tak, klasy stanowi syntaktyczne rozwinicie rekordw, a w zwizku z tym obiekty s odpowiednikiem zmiennych, ktrych typowi przypisano klas tego obiektu. W tym punkcie podsumowane
zostan oglne reguy dotyczce typw i zmiennych, natomiast w podrozdziale 3.6 przygotujemy przegld przez wszystkie typy dostpne w Delphi.

Bloki deklaracji
Dziki strukturze programu w jzyku Object Pascal, bdcej poczeniem poszczeglnych sekcji struktury, znacznie atwiejsze jest odrnianie w programie zmiennych, typw i staych ni na przykad w jzykach C++ i Java. Kady z tych trzech
rodzajw identyfikatorw musi by deklarowany w sekcji kodu rdowego opatrzonej specjalnym podpisem. Sekcje te mog pojawia si w pliku wielokrotnie i w dowolnej kolejnoci, przy czym zawsze musz znajdowa si poza tymi czciami pliku,
ktre zawieraj w sobie kod programu (czyli poza wszystkimi blokami begin end).
Na przykad zmienne zapisywane s w sekcjach oznaczonych sowem var, tak jak
na listingu 3.81.
Listing 3.81. Deklarowanie zmiennych
var
PewnaLiczba: Integer;
Przycisk1, Przycisk2: Char;
begin
...

Rozdzia 3. Jzyk Delphi w rodowisku .NET

351

Deklaracja zmiennej przypisuje jednemu lub kilku identyfikatorom zmiennych rozdzielanych przecinkami typ znajdujcy si za dwukropkiem. Zastosowane w powyszym
listingu typy integer i char s ju zdefiniowane w Delphi.

Deklaracje typw
W kadym pliku formularza znajdziemy zapisan deklaracj typu formularza, czyli
klasy formularza, ktra jest wyjtkowo obszernym przykadem deklaracji typu. Najprostszy sposb zadeklarowania wasnego typu polega na przygotowaniu zastpczego
identyfikatora dla jednego ze standardowo zdefiniowanych typw, tak jak na listingu 3.82.
Listing 3.82. Deklarowanie typu jako innej nazwy typu standardowego
TIndeksTabeli = Integer; { deklaruje nowy typ o nazwie TIndeksTabeli }
TIndeksStron = Integer;
TIndeksKsiazki = Byte;

Takie samodzielnie zdefiniowane typy stosowa mona dokadnie tak samo jak typy
standardowe:
var
NumerStrony : TIndeksStron;

Rnice w zapisach stosowanych w bloku deklaracji typw, w stosunku do bloku deklaracji zmiennych, to gwnie sowo type rozpoczynajce ten blok, moliwo tworzenia tylko jednego identyfikatora w ramach jednej deklaracji i stosowanie w miejscu dwukropka znaku rwnoci. Dziki stosowaniu przedstawionych wyej deklaracji
typw mamy pniej moliwo atwego przestawienia typu TIndeksKsiazki z typu
standardowego Byte na Word. W ten sposb uzyskamy wikszy zakres wartoci dla indeksw ksiek, podczas gdy pozostae zmienne stosujce typ Byte pozostan niezmienione. Jest to rozwizanie pozwalajce na zaoszczdzenie pracy zwizanej z wyszukiwaniem i zastpowaniem odpowiednich zapisw w kodzie programu.

Pozostae bloki deklaracji


Oprcz segmentw var i type w plikach mona stosowa jeszcze segmenty const
(omawiane bd w punkcie 3.5.4), exports (stosowane w bibliotekach do eksportowania metod jako funkcji rodowiska Win32) oraz label (do tworzenia etykiet, do
ktrych mona w kodzie przeskakiwa za pomoc instrukcji goto w tej ksice nie
bdziemy rozpatrywa tej funkcji jzyka).

3.5.4. Stae i zmienne inicjowane


W punkcie 3.5.1 mwiem ju o bezporednio podawanych wartociach staych, jako
o jednym z najbardziej podstawowych skadnikw tekstu rdowego. Takim wartociom
staym rwnie mona przypisa nazwy i w ten sposb wykorzystywa je wielokrotnie.
Oczywicie, nawet bez takiej nazwy wpisan jawnie warto mona traktowa jak sta,
ale w jzyku Object Pascal terminem staa okrelane s wycznie identyfikatory opisujce warto sta.

352

Delphi 2005

W jzyku Object Pascal istniej dwa rne rodzaje staych. W rzeczywistych staych
danemu identyfikatorowi przypisywana jest warto staa, ktrej kompilator pniej
uywa wszdzie tam, gdzie w kodzie programu zastosowany zostanie ten identyfikator.
W tym procesie deklaracja identyfikatora staej jest cakowicie usuwana przez kompilator Delphi, w zwizku z czym w powstajcym kompilacie rodowiska .NET takiego identyfikatora ju nie znajdziemy. Zasada tych podmian zobrazowana zostaa
na listingu 3.83.
Listing 3.83. Zastosowanie identyfikatorw staych w programie
const
SzerokoscStandardowa = 200;

// Staa SzerokoscStandardowa nie jest


// czci kompilatu rodowiska .NET

begin
Width := SzerokoscStandardowa; // zapis traktowany jest jak "Width := 200;"

Co ciekawe, kompilator pozwala te na stosowanie wyrae w deklaracjach staych, pod


warunkiem jednak, e w tych wyraeniach stosowane bd wycznie wartoci stae.

Stae z przypisanymi typami i zmienne kocowe


Deklaracje staych nie musz skada si wycznie z przypisywanych do nazw jawnych
wartoci staych. Kadej deklarowanej staej mona te przypisa odpowiedni typ:
const
SzerokoscStandardowa: Integer = 200;

// Staa SzerokoscStandardowa jest


// czci kompilatu rodowiska .NET

Takie stae w jzyku Object Pascal s traktowane podobnie do zmiennych, ktrych


warto nie moe by jednak zmieniana po pierwotnej inicjalizacji. Odpowiada to w peni
zmiennym kocowym (ang. Final variable) funkcjonujcym w rodowisku .NET CLR i,
jak mona si domyla, kompilator Delphi dla .NET przeksztaca stae z przypisanymi
typami w zmienne kocowe.
Do czasu Delphi 5 takie deklaracje nie byy traktowane przez kompilator jak prawdziwe (czyli niezmienne) stae i mona im byo w tekcie programu przypisywa
nowe wartoci. Jeeli taki kod chcielibymy skompilowa w podobny sposb
w Delphi dla .NET, to musielibymy zastosowa opcj kompilatora {$J+} lub
{$WRITEABLECONST} albo w oknie dialogowym opcji kompilatora zmieni opcj Assignable typed constants.

Zmienne zainicjowane
W Delphi inicjowanie zmiennych ju w momencie ich deklaracji dozwolone jest tylko
w przypadku zmiennych globalnych. Wartoci inicjujce przypisywane s takim zmiennym
ju w momencie uruchomienia programu, zastpujc standardowe wartoci zerowe.
Skadnia takich deklaracji jest identyczna ze skadni stosowan przy deklarowaniu
staych z przypisanymi typami, a jedyna rnica polega na tym, e umieszczane s
one w sekcji var:
var
SampleRate: Word = 44100;

Rozdzia 3. Jzyk Delphi w rodowisku .NET

353

Lokalne stae z przypisanymi typami


Zmienne lokalne nie mog by automatycznie inicjowane. W jzyku Object Pascal mona
natomiast deklarowa lokalnie, w zakresie danej funkcji, stae z przypisanymi typami.
Taka staa jest wtedy wewntrz tej funkcji dostpna jako zmienna lokalna, chocia tak
naprawd bdzie ona zainicjowan zmienn globaln. Obrazujcy to wycinek kodu
przedstawiam na listingu 3.84.
Listing 3.84. Tworzenie zainicjowanych staych lokalnych
{$WRITEABLECONST ON} //<< Wymagane ze wzgldu na instrukcj inc.
procedure AnyProcedure;
const
CallCounter: Integer = 0;
begin
inc(CallCounter);

W tym przykadzie procedura AnyProcedure zlicza swoje wywoania w prywatnej zmiennej CallCounter, ktra przy uruchomieniu inicjowana jest zerem.
Podobnie jak wszystkie inne zmienne globalne, Delphi musi jako przedstawi tak zmienn w rodowisku .NET CLR. Przegldajc zawarto programu za pomoc
narzdzia Reflection i rozwijajc wzy podrzdne wza Unit, znajdziemy w nim
nazw @2$AnyProcedure$CallCounter.

3.5.5. Obszary widocznoci i zmienne lokalne


Kady identyfikator, jaki wykorzystywany jest w jzyku Object Pascal poprzez przypisanie mu nazwy albo caego wyraenia, musi zosta najpierw zadeklarowany. Taki
identyfikator mona stosowa od momentu zadeklarowania do koca jego obszaru widocznoci. Do obszarw widocznoci zaliczane s moduy, funkcje i klasy.
Identyfikatory, ktre zostay zadeklarowane wewntrz danej funkcji, procedury lub
metody s widoczne wycznie w ich obrbie i okrelane s jako identyfikatory lokalne.
Przykad takiego identyfikatora przedstawiam na listingu 3.85.
Listing 3.85. Identyfikatory lokalne
procedure TForm1.FormCreate(Sender: TObject);
var { Deklarowane bd zmienne lokalne }
LicznikLokalny: Integer;
begin
LicznikLokalny := 0; { zastosowanie zmiennej }
...

Jeeli bdziemy zagnieda funkcje, to kada funkcja zagniedona bdzie miaa dostp
do wszystkich wczeniej zadeklarowanych zmiennych lokalnych funkcji obejmujcej.

354

Delphi 2005

Identyfikatory deklarowane wewntrz deklaracji klasy dostpne s we wszystkich metodach tej klasy, a dodatkowo widoczne s te w klasach wywiedzionych, cho w tym zakresie trzeba jeszcze uwzgldnia atrybuty widocznoci (omawiane byy w punkcie 3.2.2).
Identyfikatory zadeklarowane poza wymienionymi przed chwil obszarami nazywane
s identyfikatorami globalnymi, ktre widoczne s w ramach caego moduu, rozpoczynajc od wiersza, w ktrym zostay zadeklarowane.

Zakrywanie
Identyfikatory mog zosta czasowo zakryte przez identyfikatory zadeklarowane w zagniedonych obszarach widocznoci. W przykadzie przedstawionym na listingu 3.86
deklarowana jest zmienna globalna o nazwie Start, a zaraz obok niej deklarowana jest
zmienna lokalna o tej samej nazwie, zakrywajca zmienn globaln. Mimo zakrycia
zmiennej globalnej, nadal moemy uzyska do niej dostp bezporednio podajc zakres jej widocznoci. Zakres widocznoci zmiennej globalnej ma zawsze nazw moduu,
w ktrym zostaa ona zadeklarowana.
Listing 3.86. Zakrywanie zmiennej globalnej przez zmienn lokaln
unit Unit1;
interface
var
Start: Integer;
implementation
procedure TForm1.FormCreate(Sender: TObject);
var
Start: Integer;
begin
Unit1.Start := 0; // zapisuje warto do zmiennej globalnej
Start := 0;
// zapisuje warto do zmiennej lokalnej
...

Komentarz dla programujcych w jzykach Java i C#


Kompilator jzyka Pascal zawsze dziaa na takiej zasadzie, e tylko jeden raz przeglda zawarto kodu rdowego programu i w zwizku z tym nie moe rozwizywa referencji na
zmienne, ktre deklarowane s pniej, tak jak robi to kompilator jzyka Java. W Delphi zastosowanie elementw klas moliwe jest dopiero po ich zadeklarowaniu (jedynym wyjtkiem
s metody Set i Get stosowane w deklaracjach waciwoci), wobec czego pierwsza implementacja metody moe zosta zapisana dopiero po zakoczeniu deklaracji klasy. Podobnie zmienne lokalne s zasadniczo deklarowane przed sowem kluczowym begin rozpoczynajcym blok kodu, w ktrym widoczne maj by te zmienne. Jak wida, fakt, e kompilator
tylko raz przeglda tekst rdowy programu, nie jest tutaj prawie adnym ograniczeniem
wyjtkiem s tu tylko procedury i funkcje, ktre nie s deklarowane jako cz klasy, ani
czci interfejsu moduu (czyli stanowi prywatne procedury pomocnicze moduu). W ich wypadku moliwe jest, e zostan przypadkowo wywoane jeszcze przed ich zdefiniowaniem, co
spowoduje wygenerowanie bdu kompilatora. To samo dotyczy prywatnych zmiennych globalnych moduu lub prywatnych deklaracji typw (przy czym prywatne oznacza tutaj: niezadeklarowane w czci interfejsu moduu).

Rozdzia 3. Jzyk Delphi w rodowisku .NET

355

3.5.6. Atrybuty
Atrybuty s najbardziej podstawow nowoci wprowadzon do Delphi dla .NET. Mona
je umieszcza przed kad deklaracj identyfikatora, dodajc w ten sposb uzupeniajce
informacje do deklarowanego identyfikatora, ktre co prawda nie graj adnej roli dla
kompilatora, ale znaczenia nabieraj w czasie dziaania programu. Kady atrybut staje
si zatem w czasie dziaania programu obiektem. Na przykad deklaracja przedstawiona na listingu 3.87 czy obiekt klasy CategoryAttribute z waciwoci NoSelection.
Listing 3.87. Atrybuty umieszczane przed deklaracjami
[Category(ColorPalCategory)]
property NoSelection: boolean read FNoSelection write FNoSelection;

Definicja atrybutu umieszczona w nawiasach kwadratowych jest skadniowo zgodna


z wywoaniem funkcji, ale wewntrznie powoduje wywoanie konstruktora nowego
obiektu typu CategoryAttribute. Wszystkie parametry atrybutw znale mona w dokumentacji Delphi. Wystarczy odszuka w niej stron opisujc klasy Attribute i z niej
przej do stron opisujcych konstruktory atrybutw.
Moliwe jest te powizanie z jednym identyfikatorem wielu atrybutw. Wystarczy
rozdziela je przecinkami, umieszczajc wewntrz jednej pary nawiasw kwadratowych. Atrybuty mog by te deklarowane tak, e dotyczy bd tylko okrelonych
typw identyfikatorw, a na dodatek w Delphi mona definiowa nowe klasy atrybutw
i w czasie pracy programu samodzielnie odczytywa obiekty atrybutw poszczeglnych
identyfikatorw.
Wszystkie te zagadnienia zajyby tutaj zbyt duo miejsca, dlatego w tej ksice z atrybutw korzysta bdziemy tylko tam, gdzie bd nam one niezbdne do osignicia
konkretnych celw. W zwizku z tym podrozdzia ten zakocz kilkoma przykadami
zastosowania atrybutw, o ktrych dokadniej mwi bdziemy w innych miejscach
w ksice. W punkcie 2.6.1 atrybuty stosowane byy do przekazania serializerowi XML
informacji o przekazywanych mu typach:
[Serializable(), XmlInclude(TypeOf(AlarmEvent)),
XmlInclude(TypeOf(DesktopChangeEvent))]
EventList = class(ArrayList)
end;

Narzdzia projektowe stosowane w Delphi bardzo szeroko korzystaj z atrybutw, na


przykad podczas edytowania komponentw i kontrolek na formularzu oraz ustawiania ich waciwoci w inspektorze obiektw. Oto bardzo prosty przykad: podany na
listingu 3.88 atrybut dopisuje do waciwoci NoSelection atrybut kategorii, przez co
waciwo ta w inspektorze obiektw pojawia si w podanej w atrybucie kategorii
waciwoci.
Listing 3.88. Atrybut kategorii inspektora obiektw
[Category('Color palette')]
property NoSelection: boolean read FNoSelection write FNoSelection;

356

Delphi 2005

W punkcie 6.6.3 wykorzystywanych bdzie jeszcze wiele innych atrybutw stosowanych w czasie projektowania.
W punkcie 3.1.3 wymienianych jest kilka atrybutw dotyczcych samych kompilatw.
W tym miejscu konieczne jest zastosowanie skadni rozszerzonej, poniewa atrybuty
te nie dotycz zapisanego za nimi elementu, ale wpywaj na cay tworzony aktualnie
kompilat i zapisywane s do jego manifestu:
[assembly: AssemblyTitle('Tytu mojego kompilatu')]
[assembly: AssemblyVersion('1.0.0.0')]

3.6. Typy
Podstawowe typy jzyka Object Pascal mniej wicej odpowiadaj rnym typom wartoci waciwoci wywietlanych w inspektorze obiektw, a dodatkowo umoliwiaj
tworzenie kolejnych wariacji typw danych.

3.6.1. Typy proste


Do typw prostych nale na przykad typy liczb zmiennoprzecinkowych oraz typy
umoliwiajce prezentowanie wartoci jako liczb cakowitych, czyli tak zwane typy
porzdkowe (ang. Ordinal types). Najwaniejsz grup typw porzdkowych s typy
z rodziny integer.

Typy liczb cakowitych


Wszystkie typy liczb cakowitych (integer) rni si od siebie zapotrzebowaniem na
pami oraz faktem, e jeden z bitw moe przechowywa informacj o znaku liczby
(jeeli dany typ tego bitu nie obsuguje, to moe przechowywa wycznie liczby dodatnie). Wszystkie typy liczb cakowitych przedstawiam w tabeli 3.5.
Tabela 3.5. Typy liczb cakowitych
Typ

Zakres wartoci

Wielko

klasa .NET

ShortInt

-128 127

1 bajt

SByte

Byte

0 255

1 bajt

Byte

SmallInt

-32768 32767

2 bajty

Int16

Word

0 65535

2 bajty

UInt16

LongWord

0 42949672950

4 bajty

UInt32

LongInt

-2147483648 2147483647

4 bajty

Int32

8 bajtw

Int64

Int64

-2

63

63

2 -1

Typy oglne:
Integer

-2147483648 2147483647

4 bajty

Int32

Cardinal

0 42949672950

4 bajty

UInt32

Rozdzia 3. Jzyk Delphi w rodowisku .NET

357

Typy Cardinal i Integer nie maj ustalonej z gry wielkoci, ale dopasowuj si do
aktualnej architektury procesora (na przykad w Delphi 2 w momencie przejcia z 16bitowej na 32-bitow architektur systemw Windows wielko tych dwch typw podwoia si z dwch do czterech bajtw) i dlatego nazywane s one typami oglnymi (ang.
Generic types), w przeciwiestwie do typw fundamentalnych (ang. Fundamental types),
ktre na pewno nie bd si zmienia w rnych architekturach procesorw. Najprawdopodobniej w przyszych architekturach 64-bitowych oglne typy liczb cakowitych
nie bd ju rozbudowywane, dlatego kady, kto chce korzysta z 64-bitowych liczb,
powinien ju teraz korzysta bezporednio z typu Int64.

Typy Int64 i LongWord w praktyce


Typy LongWord i Int64 mona w zasadzie stosowa dokadnie tak samo jak pozostae
typy cakowite. Typy LongWord i LongInt zachowuj si dokadnie tak samo jak typy
Word i SmallInt, dlatego przyjrzymy si teraz szczeglnemu zachowaniu typu Int64.
Dobra wiadomo jest taka, e od czasu wprowadzenia typu Int64 kompilator pozwala te
na podawanie bezporednich staych tego typu, czyli akceptuje liczby cakowite z nawet
osiemnastoma zerami, takie jak ponisza:
ShowMessage('Liczba Int64: ' + IntToStr(1000000000000000000));

Kolejny przykad wskazuje na to, e stosujc typ Int64 trzeba zawsze pamita o tym,
e standardowy typ liczby cakowitej w Delphi ma tylko szeroko 32-bitw. Ponisza
instrukcja w programie konsolowym spowoduje bd przepenienia:
Writeln('Wynik testu Int64: ' + Int64(1000000000 * 1000000000).ToString);

Problem polega na tym, e Delphi kad z podanych jawnie liczb interpretuje jako liczb
32-bitow, poniewa liczby te mona przedstawi w takiej postaci. W zwizku z tym
kompilator przygotuje na wynik mnoenia kolejn zmienn 32-bitow, chocia tym
przypadku potrzebna byaby akurat liczba 64-bitowa. Problem ten rozwiza mona
przeksztacajc obie liczby w wartoci typu Int64 jeszcze przed wykonaniem mnoenia:
Writeln(Int64(Int64(1000000000) * Int64(1000000000)).ToString);

Typ Currency
Typ Currency (waluta) moe przechowywa wartoci do 922 337 203 685 477,5807
i podobnie w zakresie liczb ujemnych, wobec czego nadaje si do wykonywania najdziwniejszych oblicze finansowych. Mimo e pozwala on zapisywa wartoci po
przecinku dziesitnym, to jednak nie jest to typ zmiennoprzecinkowy, poniewa moe
obsuy najwyej cztery pozycje po przecinku.
Jeeli zapomnimy na chwil o przecinku dziesitnym, to zauwaymy, e typ Currency
jest wartoci 64-bitow, ktra po prostu umoliwia zapisanie wartoci do dziesiciotysicznej czci jednostki monetarnej. Obliczenia, w ktrych stosowane s wycznie
wartoci typu Currency, mog by wykonywane w procesorze tak jak zwyczajne operacje na liczbach cakowitych (cho na procesorach 32-bitowych bd one nieco wolniejsze ni naturalne dla nich operacje 32-bitowe).

358

Delphi 2005

Typ Currency moe by wykorzystywany w dokadnie taki sam sposb jak wszystkie
pozostae typy, co oznacza, e mona na nich wykonywa te same operacje, a w razie
potrzeby zamienia go w wartoci zmiennoprzecinkowe (co moe spowodowa
pewn utrat dokadnoci). Oczywicie mona posugiwa si tym typem tak, jakby by
on typem wartoci zmiennoprzecinkowych i w podobny sposb czy z typami liczb cakowitych (zmieniajc liczb cakowit w liczb zmiennoprzecinkow albo typ Currency
przeksztacajc w liczb cakowit wywoaniami funkcji Trunc lub Round).
Wskazwka do rodowiska .NET
Typ Currency wystpuje wycznie w Delphi i implementowany jest w systemowym module
Delphi w specjalnej klasie. Typ ten nie jest identyczny z klas Decimal pochodzc ze rodowiska .NET, ktra rwnie stosowana jest w matematycznych operacjach finansowych, ale
zajmuje w pamici 96, bitw przez co udostpnia miejsce dla jeszcze dziewiciu dodatkowych cyfr znaczcych. Zakres liczb obsugiwanych przez typ Currency w rodowisku .NET dokadnie odwzorowuje typ System.Data.SqlTypes.SQLMoney.

Typy liczb zmiennoprzecinkowych


Typy liczb zmiennoprzecinkowych stosowane w Delphi s dokadnymi odpowiednikami typw zmiennoprzecinkowych stosowanych przez procesory firmy Intel. Standardowym typem zmiennoprzecinkowym jest typ Double, a typy dostpne oprcz niego
wykorzystuj 4 bajty pamici (Single) lub 10 bajtw (Extended) i przez to uzyskuj
inn dokadno i zakres przechowywanych liczb. Specyfikacje tych typw przedstawiam w tabeli 3.6.
Tabela 3.6. Typy zmiennoprzecinkowe w Delphi
Typ

Zakres

Single

1,5*10

-45

-324

Double

5*10

3,4*10

1,7*10

-4932

38

308
4932

Extended

3,4*10

Real

Typ oglny, w Delphi 2005


odpowiada typowi Double

1,1*10

Dokadno

Wielko

Klasa .NET

7.8. pozycja

4 bajty

Single

15.16. pozycja

8 bajtw

Double

19.20. pozycja

10 bajtw

Double

Znaki: AnsiChar, WideChar i Char


Oglny typ opisujcy pojedynczy znak w jzyku Pascal nosi nazw Char, przy czym
odpowiada on typowi WideChar (zapisuje on znak w dwch bajtach) istniejcemu w rodowisku .NET oraz typowi AnsiChar dostpnemu w rodowisku Win32 (ten typ zapisuje
znak w jednym bajcie).
W typie AnsiChar zapisane mog by wszystkie znaki z zestawu ANSI (wzgldnie
ASCII, jeeli odczytywane s stare pliki z systemu DOS), a w staych typu char znaki
zapisywane s pomidzy dwoma apostrofami. Te znaki, ktrych nie mona znale na
klawiaturze, naley podawa z zastosowaniem znaku krzyyka (#) i kodu liczbowego,
na przykad #10.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

359

Typ WideChar stosowany jest do zapisywania znakw z zestawu znakw Unicode.


Pierwszych 256 znakw Unicode jest identycznych ze znakami zestawu ANSI, wobec
czego do zmiennych typu WideChar mona bez adnych problemw przypisywa wartoci
typu Char. Podobnie, konwersja znakw z WideChar na Char rwnie moe przebiega
bez adnych strat, pod warunkiem, e w konwertowanym cigu nie ma znakw spoza
pierwszych 256 znakw Unicode.

Typy wyliczeniowe
W jzyku Pascal funkcjonuj dwa typy proste, ktre nie s definiowane adnym sowem
kluczowym, ale za pomoc specjalnie przygotowanej definicji: typy zbiorw i typy
wyliczeniowe.
Typy wyliczeniowe stosowane s w wielu waciwociach standardowych komponentw biblioteki VCL.NET. Na przykad waciwo WindowState klasy TForm zostaa
zdefiniowana z wykorzystaniem nastpujcego typu wyliczeniowego:
type
TWindowState: (wsNormal, wsMinimized, wsMaximized);

Jak wida, typ wyliczeniowy skada si z listy nazwanych stanw, ktre mona przypisywa zmiennej danego typu. Kompilator wewntrznie obsuguje wszystkie te wartoci w postaci staych, ktrym wartoci przypisywane s automatycznie. W powyszym
przykadzie staa wsNormal otrzyma warto 0, a staa wsMaximized warto 2.
Najczciej w typach wyliczeniowych w bibliotece VCL pierwsze znaki nazw poszczeglnych staych s skrtami od nazwy typu, na przykad skrt ws powsta z nazwy
waciwoci WindowState. W ten sposb unika si konfliktw z innymi typami wyliczeniowymi, w ktrych rwnie mgby wystpi stan o nazwie Normal.
W przeciwiestwie do praktyk stosowanych w jzykach C# i Java, w jzyku Object Pascal nie trzeba podawa nazwy typu wyliczeniowego przed wpisywan nazw stanu. Rnice pomidzy praktykami stosowanymi w tych jzykach przedstawiam na listingu 3.89.
Listing 3.89. Rnice w stosowaniu typw wyliczeniowych w rnych jzykach
var
ws: TWindowState;
begin
ws := wsNormal;
ws := TWindowState.wsNormal;

// Stan okna formularza VCL


// Taki zapis jest prawidowy w jzyku Object Pascal
// W C# potrzebna jest te nazwa typu

Firma Borland ze wzgldu na konieczno zapewnienia zgodnoci z poprzednimi


wersjami Delphi zapewnia poprawno pierwszego z powyszych zapisw wycznie
w programach tworzonych z jzyku Object Pascal.
Jeeli jednak stosowalibymy typy wyliczeniowe pobrane z biblioteki klas rodowiska .NET, to potrzebn nam warto musimy zawsze poprzedza nazw typu, tak jak
w przykadzie z listingu 3.90.

360

Delphi 2005

Listing 3.90. Stosowanie typw wyliczeniowych z biblioteki klas rodowiska .NET


var
// Stan okna formularza Windows-Forms
ws : System.Windows.Forms.FormWindowState;
begin
ws := FormWindowState.Maximized;
// lub:
ws := System.Windows.Forms.FormWindowState.Maximized;

Mimo tego wszystkiego nie moemy jednak zapomina o tym, e zarwno typ Borland.
Vcl.Forms.TWindowState, jak i typ System.Windows.Forms.FormWindowState na poziomie rodowiska CLR s rwnoprawnymi typami wyliczeniowymi wywodzcymi si
z bazowej klasy Enum.

Typy wyliczeniowe w rodowisku .NET


Za pomoc klasy Enum mona zamieni poszczeglne wartoci, jakie przyjmowa ma
typ wyliczeniowy, w cigi znakw, ktre nadaj si do wywietlania tych wartoci
w interfejsie uytkownika. Statyczna metoda Enum.GetValues przede wszystkim zwraca
tablic, w ktrej zapisane s wszystkie wartoci wyliczenia opakowane w osobne obiekty,
natomiast metoda Enum.GetName zwraca cig znakw z nazw danej wartoci. Na pocztek potrzebny bdzie nam obiekt typu Type, ktry przechowuje informacje o typie
kontrolowanego typu wyliczeniowego. Najprostsz metod uzyskania takiego obiektu
jest wywoanie wbudowanej w kompilator funkcji typeof.
W wycinku programu podanym na listingu 3.91 kadej wartoci typu wyliczeniowego
FormBorderStyle przypisywany jest jeden przecznik:
Listing 3.91. Wypisanie wartoci typu wyliczeniowego
var
EnumType: System.Type;
values: System.Array;
i: Integer;
rb: RadioButton;
begin
EnumType := typeof(System.Windows.Forms.FormBorderStyle);
values := Enum.GetValues(EnumType); // Uzyskanie tablicy wartoci
// przegldanie tablicy:
for i := 0 to values.GetLength(0)-1 do begin
rb := RadioButton.Create;
rb.Text := Enum.GetName(EnumType, values.GetLength(i));
rb.Bounds := Rectangle.Create(8, i*16+8, 200, 12);
GroupBox1.Controls.Add(rb);
end;
end;

Rwnomierne rozoenie tworzonych automatycznie przecznikw, takie jak w powyszym przykadzie, uzyska mona stosujc komponent GroupBox (opisywany
bdzie w punkcie 6.7.1). Komponent ten moe te samodzielnie przygotowywa
przeczniki dla wartoci typu wyliczeniowego, jeeli taki typ zostanie przypisany
do waciwoci EnumType komponentu.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

361

Wartoci porzdkowe staych wyliczenia


Od roku 2001 wszystkie kompilatory Delphi tworzone przez firm Borland (Delphi 6,
Kylix1) pozwalaj te samodzielnie przypisywa wartoci do poszczeglnych staych
wyliczenia, tak jak na listingu 3.92.
Listing 3.92. Samodzielnie wybierane wartoci staych wyliczenia
type
TBits = (bPierwszyBit = $01, bDrugiBit = $02,
bTrzeciBit = $04, bCzwartyBit = $08);

Za pomoc typw wyliczeniowych mona te przeprowadza obliczenia albo przeglda wszystkie wartoci wyliczenia w ptli, wystarczy tylko wykorzysta porzdkow
natur tych typw (o typach porzdkowych mwi bd na stronie 362).

Typy zakresw czciowych


Definiujc typ zakresu czciowego informujemy kompilator o tym, e w danym typie
dopuszczalny jest ograniczony zakres wartoci. Przykadow definicj typu zakresu
czciowego przedstawiam na listingu 3.93.
Listing 3.93. Definicje typu zakresu czciowego
type
ArrayIndex = 0..55;
Temperature = -200..10000;

Kompilator bdzie kontrolowa, eby zmiennym o tym typie przypisywane byy wartoci wycznie ze zdefiniowanego w nim zakresu. Jeeli zmiennej przypisywany bdzie
wynik wyraenia, ktry okrelany jest dopiero w czasie dziaania programu, to kompilator nie bdzie mg przewidywa, czy wynik tego wyraenia bdzie zawiera si
w zakresie typu. W zwizku z tym moemy skorzysta z opcji kompilatora $R+, wczajcej sprawdzanie zakresw, przez co zakresy sprawdzane bd take w czasie dziaania
programu, a w przypadku ich przekroczenia wywoywany bdzie wyjtek ERangeError.
Typy zakresw czciowych s specjaln funkcj dostpn wycznie w Delphi,
dlatego rodowisko CLR nie moe wykrywa ewentualnych przekrocze dopuszczalnego zakresu.

Typy logiczne
Typ logiczny (boolean) mona traktowa jak predefiniowany typ wyliczeniowy zawierajcy tylko dwie wartoci. S to wartoci prawdy (True) i faszu (False), ktre
powstaj w czasie okrelania wartoci wyrae logicznych, takich jak (x < MaxX) and
(y < MaxY).

362

Delphi 2005

Typy porzdkowe
Wszystkie typy proste z wyjtkiem typw zmiennoprzecinkowych i typu wielkich
liczb cakowitych Int64 okrelane s jeszcze jedn nazw: typy porzdkowe (ang.
Ordinal types). Ich najwaniejsz cech wspln jest to, e mog by bardzo atwo
przeksztacane w liczby cakowite, o ile ju nimi nie s. Tak dodatkow warto liczbow zmiennej porzdkowej uzyska mona poprzez wywoanie funkcji ord. Za poszczeglnymi typami porzdkowymi ukrywaj si nastpujce liczby:
liczba porzdkowa znaku z zestawu znakw ANSI lub zestawu znakw Unicode
(dla typu WideChar),
w przypadku zmiennych logicznych warto True reprezentuje 1, a warto
False 0,
stae danego typu wyliczeniowego standardowo numerowane s od zera w gr.

Oczywicie mona te wykonywa operacje odwrotne i wartoci liczbowe zamienia


w pozostae typy porzdkowe, na przykad wyraenie Boolean(1) zwrci nam logiczn
warto prawdy True.

Funkcje High i Low


Z typami porzdkowymi wi si jeszcze dwie wane funkcje: High i Low. Wywoujc
funkcj High(IdentyfikatorTypu) lub High(IdentyfikatorZmiennej), otrzymujemy najwysz warto dostpn w zakresie wartoci podanego typu lub zmiennej. Na przykad, wywoanie High(Boolean) zwraca warto True, a funkcja High wywoana
z przekazanym typem TWindowState na dzie dzisiejszy zwraca warto wsMaximized.
Odpowiednio, najmniejsz warto zakresu uzyska mona wywoujc funkcj Low.
Stosujc te dwie funkcje mona bardzo atwo przejrze wszystkie wartoci kadego
typu wyliczeniowego, co pokazano na listingu 3.94.
Listing 3.94. Wykorzystanie funkcji High i Low
type
TWartosci = (Zero, Jeden, Dwa);
{ Jeeli powyszy typ zostanie zmieniony na przykad tak:
"TWartosci = (Zero, Jeden, Dwa, Trzy, Cztery);"
to przedstawiony niej kod nie musi by modyfikowany }
var
Licznik: TWartosci;
begin
for Licznik := low(TWartosci) to high(TWartosci) do
... { Tutaj zmienna Licznik przyjmuje wartoci 'Zero', 'Jeden' i 'Dwa' }

Reguy zgodnoci typw


W jzyku Object Pascal obowizuje wiele regu okrelajcych zgodno typw danych,
ktre na przykad pozwalaj w jednym wyraeniu obliczeniowym stosowa zmienne
liczb cakowitych o kilku rnych wielkociach (na przykad Byte i Integer) albo

Rozdzia 3. Jzyk Delphi w rodowisku .NET

363

zmienne zmiennoprzecinkowe o rnych wielkociach. Pozostae reguy dotycz midzy


innymi zgodnoci rnych typw procedur i rekordw, ale wykorzystywane s na tyle
rzadko, e pozwol sobie w tym zakresie odesa czytelnika do specyfikacji jzyka
Object Pascal.
Zupenie innym i znacznie powaniejszym kryterium jest tu zgodno przypisa, ktra na przykad nie wystpuje w czasie, gdy prbujemy upchn warto typu Int64
w zmiennej typu Byte, poniewa doprowadzioby to do zniszczenia tej wartoci, jeeli
byaby ona wiksza ni 255 lub mniejsza ni 0. W zwizku z tym kompilator zawsze
dba o to, eby w przypisaniach prawa strona zawsze pasowaa do lewej strony (w razie
koniecznoci dokonuje te automatycznej konwersji wartoci cakowitych w wartoci
zmiennoprzecinkowe).

Konwersja wartoci
Doskonaym przykadem konwersji wartoci jest prba przypisania zawartoci wikszej zmiennej do mniejszej zmiennej. Na przykad wiedzc, e w jednej zmiennej typu
Word z ca pewnoci nie znajd si wartoci wiksze ni 1000, moemy podzieli jej
warto przez 10 i spokojnie dokona konwersji na typ Byte. Docelowy typ konwersji
stosuje si w takiej sytuacji podobnie jak funkcj, a konwertowan warto podaje si
w nawiasach, tak jak na listingu 3.95.
Listing 3.95. Konwersja typw wartoci
ZmiennaTypuByte := Byte(ZmiennaTypuWord div 10);
Znak := AnsiChar(KodZnaku);
// Zamiana liczby w znak
Falsz := Boolean(0);
// Zamiana liczby w warto logiczn

Traktowanie zmiennych typw prostych jak obiektw


Na poziomie rodowiska CLR obiektami s nawet najprostsze typy danych jzyka
Object Pascal i w zwizku z tym dziedzicz na przykad metod ToString pochodzc
z klasy System.Object lub TObject. I rzeczywicie, Delphi pozwala na wywoanie
metody ToString na rzecz zmiennej typu Integer, a take wszystkich innych zmiennych pozostaych typw. Teoretycznie moliwe jest te wywoywanie takich metod na
rzecz wartoci podawanych bezporednio, przy czym najpierw trzeba wykona jawn
konwersj podawanej wartoci do odpowiedniej klasy typu, na przykad: Int16(145).
ToString lub Double(0.944).ToString.
Takie wywoania maj sens tylko wtedy, jeeli podawane wartoci maj by konwertowane na format ustalany dopiero w czasie dziaania programu, bo w przeciwnym wypadku mona jawnie poda odpowiedni cig znakw, taki jak '0.944'.
Wicej informacji na ten temat uzyska mona w dokumentacji Delphi, przegldajc
opisy przecionych wersji metody ToString obsugujcych parametry, jakie opisywa bd w podpunkcie Formatowanie cigw znakw ze strony 373.

364

Delphi 2005

3.6.2. Operatory i wyraenia


W jzyku Object Pascal operatory arytmetyczne czterech podstawowych dziaa matematycznych, a take operatory porwnania wartoci wygldaj dokadnie tak samo
jak znaki, do ktrych ju wszyscy przywyklimy. Jedynie operatory nierwnoci (<>),
mniejszoci lub rwnoci (<=) i wikszoci lub rwnoci (=>) musz by skadane z dwch
znakw, co wynika z braku odpowiednich znakw na klawiaturze.
Wikszo pozostaych operatorw to sowa kluczowe, ktre s bardzo atwe do zapamitania z powodu nazw dokadnie opisujcych penione przez nie funkcje.

Priorytety operatorw
W zalenoci od swojego priorytetu, operatory podzielone zostay na cztery poziomy
priorytetw (priorytety te podaj w tabeli 3.7). Jeeli w wyraeniu stosowanych jest
kilka operatorw o jednakowym priorytecie, to zapisane w nich operacje wykonywane
s w kolejnoci od lewej do prawej. Jeeli chcielibymy, aby obliczenia wykonywane
byy w innej kolejnoci ni ta standardowa, to naley skorzysta z odpowiednio umiejscowionych nawiasw.
Tabela 3.7. Priorytety operatorw arytmetycznych
Priorytet

Operator

Opis

()

nawiasy okrge (w jzyku Pascal nie s traktowane


jak operatory)

@, not

operatory jednoargumentowe

*, /, div, mod, and, shl, shr

operatory mnoce

+, -, or, xor

operatory sumujce

<>, <, >, =, <=, >=, in

operatory porwnujce

Specjalne operatory liczb cakowitych


Niklaus Wirth twrca jzyka Pascal wpad na ciekawe rozwizanie zwizane
z dzieleniem liczb cakowitych. Do wykonania takiego dzielenia nie wykorzystuje si
standardowego symbolu dzielenia (/), ale uywa naley specjalnego operatora div.
Reszt z tak wykonanego dzielenia uzyska moemy za pomoc operatora mod (na przykad 7 mod 3 = 1).

Operatory bitowe
Do czenia ze sob liczb za pomoc operacji wykonywanych na poziomie pojedynczych bitw wykorzystywane s operatory, ktrych nazwa okrela ju rodzaj wykonywanej operacji. Wszystkie rodzaje operatorw bitowych podaj w tabeli 3.8.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

365

Tabela 3.8. Operatory bitowe w jzyku Pascal


Operator

Poczenie

Przykad

not

bitowa negacja

not $F0 = $0F

and

bitowy iloczyn logiczny (operacja i)

$0F and $11 = $01

or

bitowa suma logiczna (operacja lub)

$40 or $04 = $44

xor

alternatywa wykluczajca

$A8 xor $A0 = $08

shl

przesuwa wszystkie bity w lewo

$01 shl 4 = $10

shr

przesuwa wszystkie bity w prawo

$20 shr 2 = $08

Operatory logiczne
Operatory not, and, or i xor mona te wykorzysta do czenia ze sob wartoci logicznych i w tym zastosowaniu oznaczaj operacje negacji, iloczynu i sumy logicznej,
a operator xor oznacza mniej wicej operacj jeden z dwch. W wyniku tych operacji na podstawie dwch wartoci logicznych tworzona jest trzecia warto logiczna.
Na przykad wyraenie:
Button1.Checked and Button2.Checked

sprawdza, czy zaznaczone zostay dwa przyciski specjalne, i moe by wykorzystane


w instrukcji kontrolujcej przebieg programu albo zosta przypisane do zmiennej logicznej.

Wyliczanie wartoci wyrae logicznych


Wyliczanie wartoci wyrae logicznych moe by realizowane na kilka sposobw.
Jeeli czci wyraenia s wywoania funkcji, a w przedstawianych tu przykadach
kady identyfikator oznacza bdzie wanie funkcj, to wyliczanie takiego wyraenia
moe mie wielki wpyw na prdko dziaania programu:
if True or PolgodzinneObliczenia then
...

albo na logik programu:


if PustyNosnik and PozwolenieUzytkownika and FormatujNosnik
then WyswietlenieKomunikatu('Nonik zosta sformatowany.');

Jeeli prawdziwy jest pierwszy operand operacji or, tak jak w pierwszym przykadzie,
to z gry wiadomo, e cae wyraenie bdzie miao warto prawdy, nawet jeeli drugi
operand bdzie faszywy. W takim razie, jeeli w drugiej czci wyraenia wykonywane maj by czasochonne, ale w takiej sytuacji niepotrzebne ju obliczenia, to dobrym rozwizaniem jest zaniechanie wykonywania tych oblicze i zwrcenie wycznie wartoci True z pierwszej czci wyraenia, przez co wykonane zostan instrukcje
po sowie kluczowym then.
W drugim przykadzie pene wyliczanie wartoci wyraenia mogoby mie te katastrofalne skutki. Jeeli uytkownik w ostatniej chwili rozmyliby si i chcia zablokowa
formatowanie nonika, w wyniku czego funkcja PozwolenieUzytkownika zwrciaby

366

Delphi 2005

warto False, ale program mimo to wykonaby ostatni funkcj tego wyraenia (FormatujNosnik), to mimo protestw uytkownika nonik i tak zostaby sformatowany.
Metoda wyliczania wyrae stosowana przez kompilator uzaleniona jest od ustawie
opcji Complete boolean eval dostpnej w oknie dialogowym opcji kompilatora lub
ustawienia dyrektywy kompilatora $B. Domylnie kompilator stara si zakoczy wyliczanie wyraenia tak szybko, jak tylko jest to moliwe, przez co oba podane wyej
przykady okazuj si bardzo sensowne.
Czasami przydaje si te wczenie penego wyliczania wartoci wyraenia. Podany
niej przykad wyraenia kompilowany jest z zaoeniem penego wyliczania wartoci,
co wynika z zastosowania opcji kompilatora $B+:
{$B+}
CalyTestOK := CDROM_Dziala and TwardyDyskOK;

W podanym przykadzie testowane maj by napdy CD-ROM oraz twarde dyski zainstalowane w systemie. Jeeli w czasie testowania napdu CD-ROM i jego sterownika
wykryta zostanie jaka nieprawidowo, to co prawda oglny wynik testw nie moe ju
ulec zmianie, ale uytkownik mimo to otrzyma jeszcze informacje o prawidowym (lub
nie) dziaaniu dyskw twardych (nastpi to w efekcie wykonania funkcji TwardyDyskOK).

Operatory porwnania
Operatory porwnania rwnie zwracaj w wyniku wartoci logiczne. Trzeba jednak
pamita o tym, e maj one niszy priorytet ni przedstawione wyej operatory logiczne, w zwizku z czym w przykadzie przedstawionym na listingu 3.96 konieczne
jest zastosowanie nawiasw, w przeciwnym razie bowiem kompilator poczyby najpierw wartoci zmiennych MaxX i Y operacj sumy logicznej (or), a potem zgosi bd
wynikajcy z nieprawidowego zastosowania operatora wikszoci (>).
Listing 3.96. Operatory porwnania maj mniejszy priorytet od operatorw operacji logicznych
if (X > MaxX) or (Y > MaxY) then
raise EIndexOutOfRange.Create('X lub Y'+
' poza dopuszczalnym zakresem wartoci');

Operatory i inne typy


Cz prezentowanych do tej pory operatorw ma natur polimorficzn i mog by
traktowane podobnie do funkcji wirtualnych. Operatory te powoduj wykonanie rnych operacji, w zalenoci od typu, na jakim maj dziaa. Z takimi operatorami mog
by czone te typy danych niebdce zwykymi typami porzdkowymi albo zmiennoprzecinkowymi, takie jak zbiory lub cigi znakw.

Operatory przecione w rodowisku .NET


rodowisko CLR pozwala te na stosowanie przecionych operatorw wobec samodzielnie zdefiniowanych klas, a biblioteka FCL korzysta z tej moliwoci do czsto.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

367

Przykadem moe by tutaj klasa DateTime, w ktrej przecione operatory porwnania


wykorzystane zostay do porwnywania dwch dat lub czasw, a oprcz tego zdefiniowane zostay te przecione operatory dodawania i odejmowania.
Jzyk Object Pascal rwnie oferuje pen obsug operatorw przecionych, dziki
czemu okrelenie daty, jaka bdzie za sto dni, mona zrealizowa za pomoc kodu
przedstawionego na listingu 3.97.
Listing 3.97. Przecione operatory w operacjach na datach
var
date: DateTime;
span: TimeSpan;
begin
date := DateTime.Now;
span := TimeSpan.FromDays(100);
date := date + span;
MessageBox.Show('Za sto dni bdzie: '+date.ToString('dd.MM.yyyy'));

Dziaanie operatora dodawania wykorzystanego w powyszym przykadzie definiowane


jest wycznie wewntrz klasy DateTime. Wynika z tego, e przy kadym wykorzystaniu
operatorw z dowolnymi obiektami naley skonsultowa si z dokumentacj tej klasy,
sprawdzajc w niej dziaanie operatora.

3.6.3. Tablice
Tablice s indeksowanym uszeregowaniem w pamici wartoci tego samego typu.
W jzyku Object Pascal dostpne s te indeksowane waciwoci (mwiem o nich
w punkcie 3.2.4), ktre dla uytkownika wygldaj dokadnie tak samo jak tablice.
Dostp do poszczeglnych elementw tablic i indeksowanych waciwoci realizowany
jest w taki sam sposb: za nazw tablicy lub waciwoci podawany jest indeks elementu
zamknity w nawiasach kwadratowych. Indeksy nie musz by wcznie liczbami; tablice
mog by indeksowane dowolnymi typami porzdkowymi, a w przypadku waciwoci
dopuszcza si indeksowanie dowolnym typem.
W przykadzie przedstawionym na listingu 3.98 definiowane s trzy tablice, wrd ktrych
pierwsza wykorzystuje chyba najczciej stosowane indeksy typu integer, elementy
drugiej indeksowane s typem wyliczeniowym, a w trzeciej uyty zosta typ zakresu
czciowego.
Listing 3.98. Do indeksowania tablic mona stosowa rne typy porzdkowe
type
// dwa samodzielnie zdefiniowane typy porzdkowe
SkladowaKoloru = (czerwony, zielony, niebieski);
IndeksyTabeli = 1..100;
var
WartosciPomiarow: array[0..1000] of Double;
Kolor: array[SkladowaKoloru] of Integer;
ArkuszKalkulacyjny: array[IndeksyTabeli, IndeksyTabeli] of Integer;

368

Delphi 2005

Jak wida, pierwszemu elementowi tablicy mona nada indeks 0, 1 lub dowolny inny
z dopuszczalnego zakresu wartoci indeksw. Na listingu 3.99 przedstawiam zatem
sposoby odwoywania si do jednego z elementw trzech tablic zdefiniowanych na listingu 3.98.
Listing 3.99. Sposoby uzyskiwania dostpu do elementw rnie indeksowanych tablic
WartosciPomiarow[0] := WartoscPoczatkowa;
SkladowaZielona := Kolor[zielony];
ArkuszKalkulacyjny[5, 6] := '4*b';

Tablice dynamiczne
Jzyk Object Pascal oferuje szczegln obsug tablic dynamicznych, czyli tablic, dla
ktrych pami rezerwowana jest dopiero w czasie dziaania programu. Tablica dynamiczna deklarowana jest tak:
var
intArray1: array of Integer;

Podobnie jak w przypadku dynamicznych obiektw, ktre odkadane s na stert, musimy si specjalnie upewni, e na tablic przygotowana zostanie wystarczajca ilo
pamici. Odpowiedni procedur przedstawiam na listingu 3.100.
Listing 3.100. Na tablic dynamiczn trzeba jawnie zarezerwowa pami
begin
intArray1[1] := 100; // << Bd, pami nie jest jeszcze zarezerwowana
SetLength(intArray1, 100);
intArray1[100] := 100; // << Bd, dostpne s indeksy od 0 do 99
intArray1[0] := 100; // Warto zapisywana jest do pierwszego elementu

Najciekawsze w tym wszystkim jest to, e funkcj SetLength mona wywoywa wielokrotnie i w ten sposb dostosowywa wielko tablicy do aktualnych potrzeb. W czasie
takich zmian wielkoci tablicy jej zawarto moe by uszkodzona tylko w przypadku
zmniejszania jej rozmiaru.
Szczegln nowoci wprowadzon w rodowisku .NET w zwizku z dynamicznymi
tablicami jest fakt, e teraz ju funkcje samego systemu operacyjnego (w tym
miejscu mam na myli bibliotek FCL) zwracaj atwe w obsudze tablice dynamiczne, a nie wiele powizanych ze sob wskanikw na rne obszary pamici,
ktre zwracane s przez funkcje Windows API. Na przykad w punkcie 1.5.4 mwilimy o funkcji Process.GetProcessesByName zwracajcej warto typu array of
Process.

Klasa Array
rodowisko CLR traktuje kad tablic jak obiekt, podobnie jak i wszystkie inne typy
proste. W rodowisku .NET klas bazow dla wszystkich tablic jest klasa System.Array,
jednak zawarto takiej tablicy nie jest dostpna poprzez indeksy zapisane w nawiasach

Rozdzia 3. Jzyk Delphi w rodowisku .NET

369

prostoktnych, a wycznie poprzez wywoywanie metody GetValue i SetValue. Pozostae metody klasy Array pozwalaj na odczytanie indeksw granicznych, przeszukiwanie i sortowanie tablicy, kopiowanie i usuwanie wartoci, a take odwracanie kolejnoci elementw (Reverse). Dokadne dane na temat tych metod uzyska mona
w dokumentacji Delphi.
Dynamiczn tablic jzyka Object Pascal mona te przedstawia jako obiekt klasy
System.Array. W przykadowym kodzie z listingu 3.101 przygotowywany jest specjalny,
abstrakcyjny widok AbstractArray opisujcy istniejc tablic dynamiczn ProcessArray.
Listing 3.101. Reprezentowanie tablicy dynamicznej jako obiektu klasy System.Array
var
ProcessArray: array of Process;
AbstractArray: System.Array;
begin
ProcessArray := Process.GetProcessesByName('Idle');
AbstractArray := ProcessArray;

W klasie System.Array implementowane s interfejsy IList, ICollection i Ienumerable. W zwizku z tym wszystkie tablice dynamiczne tworzone w Delphi mona
obsugiwa tak samo jak kolekcje przedstawiane w punkcie 2.2.5.

Tablice wielowymiarowe
Wszystkie przedstawione do tej pory zasady mona atwo przenie na tablice wielowymiarowe. Musimy tylko wiedzie, jak deklarowane s takie tablice:
var
IntMatrix: array of array of Int64;

i jak ustala ich wielko wywoaniami procedury SetLength:


SetLength(IntMatrix, 100, 100);

// rezerwuje pami na 100*100 liczb typu Int64

Jeeli wyobrazimy sobie, e deklaracj array of Int64 wycigniemy za nawias, to


okae si, e deklaracja zmiennej IntMatrix jest waciwie deklaracj tablicy jednowymiarowej, ktrej wielko ustala mona nastpujcym wywoaniem:
SetLength(IntMatrix, 100);

// Rezerwuje sto tablic wartoci typu Int64

Kady ze stu elementw takiej tablicy mona traktowa tak samo jak zwyczajn
zmienn typu array of Int64. Oznacza to, e kady element takiej dynamicznej tablicy moe by osobno inicjowany poprzez przypisanie lub wywoanie procedury
SetLength. Jak wiemy, tablice dynamiczne mog mie rne wielkoci, wobec czego
kademu elementowi takiej tablicy moemy przypisa tablic o innej wielkoci, przez
co tablica IntMatrix nie byaby prostoktna, ale na przykad trjktna lub cakowicie
nieregularna.

370

Delphi 2005

Stae tablicowe
Tablice mog by te deklarowane jako stae, ktre przy okazji mona inicjowa
wartociami podawanymi bezporednio. W deklaracjach takich tablic naley wykorzystywa nastpujc skadni:
ArrayConst: array[0..10] of Byte = (0, 0, 0, 100, 1, 100, 0, 0, 0, 1, 0);

3.6.4. Rne typy cigw znakw


Rodzaje cigw znakw dostpnych w Delphi s bardzo podobne do rodzajw pojedynczych znakw typu Char: Cigi skadajce si ze znakw typu AnsiChar nosz nazw
AnsiString, natomiast te skadajce si ze znakw WideChar nazywaj si WideString.
Oglny typ String w rodowisku .NET odpowiada typowi WideString i jednoczenie
zdefiniowanej w rodowisku .NET klasie System.String. Z kolei typ AnsiString zdefiniowany jest w module Bornald.Delphi.System (w rodowisku Win32 oglny typ
String odpowiada typowi AnsiString).
Typ String pod pewnymi wzgldami podobny jest do oglnych typw Integer i Char,
poniewa jego wewntrzna budowa jest nieco inna w rodowiskach Win32 i CLR, ale
zastosowanie w obu rodowiskach jest takie samo.

Typ System.String a typ String jzyka Pascal


Delphi umoliwia stosowanie tych samych operacji, typowych dla jzyka Pascal, na
obu rodzajach cigw znakw. Oprcz tego, dla kadego cigu znakw typu String
(lub WideString) wywoywa mona metody udostpniane przez klas System.String.
Trzeba przy tym pamita, e klasa System.String stosuje nieco inny sposb indeksowania cigw znakw: pozycja pierwszego znaku otrzymuje indeks zerowy, podobnie jak ma to miejsce w tablicach dynamicznych, natomiast w jzyku Pascal od
zawsze pierwszy znak cigu mia indeks o wartoci 1. Podobne rnice zauway
mona na przykad w dziaaniu funkcji Copy pochodzcej z jzyka Pascal i metody
String.SubString dostpnej w rodowisku .NET. W przykadzie przedstawionym na
listingu 3.102 obie funkcje wywoywane s z tymi samymi parametrami, ale zwracaj
nieco inne wyniki.
Listing 3.102. Rnice w indeksowaniu cigw znakw w jzyku Pascal i rodowisku .NET
// Aplikacja konsolowa (menu File/New/Other/Console application)
var
MyString: String
begin
MyString := 'Test';
writeln(Copy(MyString, 1, 3));
// wypisuje 'Tes'
writeln(MyString.SubString(1, 3)); // wypisuje 'est'

Rozdzia 3. Jzyk Delphi w rodowisku .NET

371

Operatory dziaajce na cigach znakw


Pascalowe operatory dziaajce na cigach znakw dostpne s rwnie w Delphi dla
.NET. Do poczenia ze sob dwch cigw znakw stosowany jest operator dodawania (+), a do porwnania alfabetycznej kolejnoci cigw wykorzystywane s operatory mniejszoci (<) lub wikszoci (>). Z kolei operator rwnoci (=) pozwala na
wykonywanie szybkich porwna cigw znakw. Operatory porwnujce wykonuj
dokadne porwnanie cigw znakw i w zwizku z tym odpowiadaj metodzie rodowiska .NET System.String.CompareOrdinal.
Jeeli w czasie porwnywania cigw znakw nie maj by brane pod uwag rnice
midzy wielkimi i maymi znakami, to mona w tym celu wykorzysta funkcj CompareText dostpn rwnie w innych wersjach Delphi albo statyczn metod System.String.
Compare, podajc im w dwch pierwszych parametrach porwnywane cigi znakw,
a w trzecim parametrze o nazwie ignoreCase warto True.

Operacje na cigach znakw


W tym podpunkcie przyjrzymy si innym operacjom wykonywanym na cigach znakw w jzyku Pascal i porwnamy je z podobnymi operacjami wykonywanymi przez
metody klasy System.String.
We wszystkich operacjach przedstawionych na poniszej licie trzeba zwraca uwag
na rny sposb indeksowania znakw. Jak pamitamy, indeks numer 1 w jzyku Pascal
oznacza pierwszy znak w cigu znakw, ale w klasie System.String ten sam indeks
wskazuje na drugi znak cigu.
Copy(s, index, count) Tworzy nowy cig znakw (nie zmienia przy tym
cigu s) zawierajcy wycinek podanego w parametrze cigu s, rozpoczynajcy
si od pozycji index i skadajcy si z count znakw. Odpowiednik tej funkcji
z klasy System.String ma nieco czytelniejsz nazw SubString. Oczywicie
dostpna jest te metoda String.Copy, ale robi ona dokadnie to, co sugeruje

nazwa, czyli tworzy dokadn kopi penego cigu znakw.


Pos(substr, s) Wyszukuje w cigu znakw s podcig substr i zwraca

pozycj wyszukanego cigu znakw lub warto zera, jeeli podcig nie
zosta znaleziony. Podobn metod jest metoda System.IndexOf, ktra
pozwala dodatkowo na nieco bardziej elastyczne dziaanie, poniewa
umoliwia okrelenie pozycji startowej, od ktrej rozpocz ma si szukanie.
Alternatywnym rozwizaniem jest te metoda String.LastIndexOf, ktra
przeszukuje podany cig znakw od tyu. Obie metody IndexOf
i LastIndexOf w przeciwiestwie do funkcji Pos w przypadku
nieznalezienia szukanego podcigu zwracaj warto -1, a nie 0.
Delete(s, index, count) Usuwa count znakw z podanego cigu znakw s,
rozpoczynajc od pozycji index. W przeciwiestwie do metody String.Remove,
funkcja Delete nie zwraca odpowiednio zmodyfikowanej kopii cigu znakw,

ale dokonuje modyfikacji bezporednio w cigu znakw przekazanym


w parametrze s.

372

Delphi 2005
Insert(substr, s, index) Do cigu znakw s wstawia na pozycji index
dodatkowy cig znakw substr. Jej odpowiednikiem jest metoda String.Insert,

ktra jednak nie modyfikuje rdowego cigu znakw, ale zwraca cig wynikowy.
Z kolei ponisze procedury s cakowicie niezalene od stosowanej metody indeksowania:
Length(s) Zwraca dugo cigu znakw liczon w znakach (odpowiada
waciwoci String.Length).
IntToStr(int) i IntToHex(int) Obie funkcje zamieniaj podan w parametrze

liczb w cig znakw zawierajcy jej reprezentacj dziesitn lub szesnastkow.


W klasie String podobne efekty uzyska mona wywoujc metody
Integer(int).ToString lub Integer(int).ToString('X').
StrToInt(str) Zwraca liczb cakowit tworzon na podstawie zapisu
przekazanego w parametrze str. Jeeli cigu znakw str nie da si przeksztaci
w liczb, to wywoywany jest wyjtek EConvertError. Biblioteka FCL

udostpnia nieco bardziej zoon, ale za to bardziej wszechstronn metod


zamiany cigu znakw str w liczb cakowit i, ktr przedstawiam
na listingu 3.103.
Listing 3.103. Konwersja cigu znakw na liczb cakowit
// Wersja prosta:
i := Int16.Parse(str); // Int16 = klasa rodowiska .NET odpowiadajca typowi integer
// Wersja zoona, bardziej oglna i wszechstronna:
i := Integer(TypeDescriptor.GetConverter(i.GetType).ConvertFromString(str));

LowerCase i UpperCase Zamieniaj wszystkie litery w cigu na litery wielkie


lub mae i odpowiadaj metodom ToLower i ToUpper z klasy String.

Manipulacje na cigach znakw na poziomie pojedynczych znakw


Funkcj typow dla jzyka Pascal jest moliwo odwoywania si do poszczeglnych
znakw w cigu, tak jakby byy one elementami tablicy:
DziesiatyZnak := s[10];
s[9] := DziesiatyZnak;

Te znaki, ktre le poza aktualnym kocem cigu znakw, mog by zapisywane dopiero
po jawnym powikszeniu dugoci cigu znakw. Pocztkowo cig znakw ma dugo zerow, poniewa w Delphi inicjowany jest cigiem pustym. Jeeli teraz chcielibymy zapisa pity znak cigu, to moglibymy to wykona za pomoc kodu przedstawionego na listingu 3.104.
Listing 3.104. Zapisywanie pojedynczych znakw w cigu
var
s: String;
begin
// Cig znakw s ma dugo zerow
SetLenght(s, 5);
// Cig znakw s ma dugo piciu znakw
s[5] := NowyZnak;

Rozdzia 3. Jzyk Delphi w rodowisku .NET

373

W rodowisku .NET takie bezporednie manipulacje na pojedynczych znakach cigu


powoduj wykonywanie dugotrwaych operacji, poniewa klasa System.String
pochodzca ze rodowiska .NET nie pozwala na zmian swojej zawartoci. Z tego
wynika, e przedstawiony wyej kod Delphi zamieniajcy tylko jeden znak w cigu
powoduje utworzenie cakowicie nowego cigu znakw, ktry od poprzedniego cigu znakw rni si tylko jednym, zmienionym w danej instrukcji znakiem. Zmiennej
typu string przypisywany jest nowy cig znakw, a stary cig po pewnym czasie
usuwany jest z pamici przez mechanizm oczyszczania pamici. Jeeli chcemy
skada duszy cig znakw z pojedynczych znakw, to powinnimy skorzysta
z klasy StringBuilder oferowanej przez rodowisko .NET do takich wanie operacji. W klasie tej mona bezporednio zmienia poszczeglne znaki cigu (poprzez
waciwo Chars), a na zakoczenie przygotowany cig znakw zmieni w faktyczn
warto typu String wywoujc metod ToString.

Cigi znakw o staej dugoci


Osoby znajce starsze wersje Delphi bd si teraz zapewne zastanawia, czy w Delphi
dla .NET nadal funkcjonuj stare cigi znakw jzyka Pascal. Owszem, cigi te s nadal
dostpne i wewntrznie posuguj si specyficznym formatem: Po pierwsze, s one
tablic znakw, ktrej indeksowanie zaczyna si od wartoci 0. Na pozycji zerowej
zapisywany jest bajt dugoci, ktry okrela, z ilu znakw skada si dany cig. W Delphi
dla .NET nie ma jednak bezporedniego dostpu do tego bajtu dugoci, a jego warto
mona modyfikowa i odczytywa wycznie za pomoc metod SetLength i Length.
W tym rodzaju cigu znakw, niezalenie od rzeczywistej dugoci cigu, ilo zajtej
przez niego pamici jest staa. Na przykad, za pomoc poniszej deklaracji rezerwowanych jest 101 bajtw pamici (wcznie z bajtem dugoci) przeznaczonych na nowy
cig znakw:
var
StuznakowyCiag: string[100];

Jeeli takiej zmiennej przypiszemy teraz cig znakw krtszy ni zadeklarowane 100
znakw, to pozostaa pami bdzie niezagospodarowana.
Ten rodzaj cigu znakw wykorzystywany jest dokadnie tak samo jak dugie cigi
znakw, a dodatkowo cigi znakw starego typu mona atwo zamienia na typ System.
String.
Cigi znakw o staej dugoci na poziomie CLR implementowane s w postaci typw wartoci ich zamiana na typ System.String do pewnego stopnia odpowiada
opisywanemu w punkcie 3.6.6 mechanizmowi Boxingu.

Formatowanie cigw znakw


W czasie tworzenia cigu znakw na podstawie kilku danych wejciowych mamy moliwo skorzystania z metody oferowanej przez Delphi, ktra dostpna jest te w Delphi dla Win32 i w pakiecie Kylix, a take z metody rodowiska .NET funkcjonujcej
wycznie z bibliotek FCL.

374

Delphi 2005

Funkcja formatujca dostpna w Delphi umieszczona zostaa w module SysUtils i nazywa si Format. W jej pierwszym parametrze podawa naley formatujcy cig znakw, na przykad taki:
Wynik := Format('Przebieg %3d Diagnoza: %s.',[Count, Diag]);

Formatujcy cig znakw skada si z tekstowego szablonu, ktry z ca pewnoci


zostanie zapisany do zmiennej Wynik, oraz ze znacznikw (ang. Placeholders), zamiast
ktrych do tekstu wstawiane bd inne parametry w okrelonym formacie. Kady taki
znacznik zaczyna si od znaku procentu (%), a koczy si znakiem okrelajcym typ
wstawianego w tym miejscu parametru. Na przykad litera d oznacza liczb cakowit,
e liczb zmiennoprzecinkow, a s cig znakw. Pomidzy znakiem pocztkowym
i kocowym znajdowa si mog te informacje o tym, ile znakw zajmowa ma w cigu
wstawiany parametr.
Do funkcji Format oprcz formatujcego cigu znakw naley te przekaza, zamknit
w nawiasach kwadratowych, tablic wartoci umieszczanych w formatowanym cigu
znakw w miejsce znacznikw. W przedstawionym wyej przykadzie w takiej tablicy
znalaza si warto liczby cakowitej reprezentowana przez zmienn Count oraz cig
znakw zapisany w zmiennej Diag.
Wynikowy cig znakw generowany przez przykadowe wywoanie funkcji formatujcej mgby wyglda tak: Przebieg 33 Diagnoza: Brak bdw., przy czym
przed liczb 33 zapisana zostaaby dodatkowa spacja dopeniajca dugo wstawianego parametru do wymaganych trzech znakw.
Klasa System.String rwnie oferuje metod o nazwie Format, ktra take wsppracuje z formatujcym cigiem znakw zawierajcym znaczniki zamieniane wartociami parametrw. Tym razem jednak znaczniki podawane s w nawiasach klamrowych
i nie jest konieczne okrelanie typu danych wstawianego parametru, jako e biblioteka
FCL sama rozpozna jego typ. W stosowanych przez t metod znacznikach podawa
naley indeks elementu tablicy podawanej za formatujcym cigiem znakw, dziki
czemu kolejno znacznikw umieszczanych w cigu znakw nie musi by identyczna
z kolejnoci parametrw wstawianych do tablicy. Poniej przedstawiam wywoanie metody String.Format rwnowane z przedstawionym wyej wywoaniem metody Format:
Wynik := System.String.Format('Przebieg {0,3} Diagnoza: {1}.',[Count, Diag]);

Formatowanie liczb zmiennoprzecinkowych i dat


Dokadniejsz kontrol nad sposobem tworzenia cigu znakw na podstawie wartoci liczbowych oraz dat uzyska mona, wykorzystujc procedury FormatDateTime, FormatFloat
oraz inne oferowane przez Delphi. Dwie wymienione funkcje dziaaj waciwie dokadnie tak samo jak funkcja Format, z tym, e pobieraj w parametrze formatujcy
cig znakw tworzony zgodnie z nieco innym wzorcem i tylko jedn warto przeznaczon do formatowania.
W bibliotece FCL wszystkie operacje dotyczce formatowania wartoci liczbowych
i informacji o datach wykonywane s przez wymienion przed chwil funkcj System.
String.Format.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

375

3.6.5. Typy strukturalne


W jzyku Pascal klasycznym typem strukturalnym jest rekord. Deklaracja rekordu
od zawsze skada si z ze sowa kluczowego record, listy elementw danych rekordu
i kocowego sowa end, za ktrym musi znale si jeszcze rednik. Przykadow deklaracj podaj na listingu 3.105.
Listing 3.105. Przykadowa deklaracja rekordu
type
TPlik = record
Nazwa: String;
Sciezka: String;
Wielkosc: Int64;
end;
var
Plik: TPlik;
begin
Plik.Nazwa := 'Zycie bez komputerow.doc'

W rodowisku .NET Delphi implementuje rekordy w postaci klasy, ktra jest automatycznie wywodzona z klasy System.ValueType (na temat typu System.ValueType
mwi bd w punkcie 3.6.6) i pozwala te na definiowanie dla tej klasy nowych metod.
Oto podstawowe rnice tej klasy w stosunku do normalnych klas:
Rekordy nie musz by tworzone wywoaniem konstruktora (wszystkie zmienne

typu rekordowego automatycznie otrzymuj przypisane obszary pamici,


a zmienne lokalne s automatycznie tworzone na stosie).
Rekordy nie mog korzysta z destruktorw i nie mog pokrywa metody
Finalize.
W czasie przekazywania rekordu w parametrze metody tworzona jest jego

kopia, chyba e dany parametr definiowany jest jako parametr referencyjny


(na ten temat mwi bd w punkcie 3.8.1).
Wszystkie te rnice powoduj, e konieczne jest tu rozrnianie typw referencyjnych
i wartoci, o ktrych dokadniej bd mwi w punkcie 3.6.6. Poza tym rekordw
uywa mona jak zwyczajnych obiektw.

Stae rekordw
Jzyk Object Pascal dopuszcza te moliwo definiowania rekordw jako wartoci
staych, tak jak pokazano na listingu 3.106.
Listing 3.106. Definicja staej rekordu
const
ObjectConst: TPlik = (Nazwa: 'FileDump.exe'; Sciezka: 'd:\Programy\BinUtils');

376

Delphi 2005

Jak wida, nie trzeba inicjowa wszystkich elementw rekordu, ale elementy inicjowane musz by podawane we waciwej kolejnoci. Wane jest te to, e za zapisem
inicjalizacji ostatniego elementu rekordu nie moe znale si znak rednika.

Zbiory
Zajmiemy si teraz typem, ktry w bibliotece FCL stosowany jest nader czsto typem zbiorw. Oglnie, skadnia przedstawiona na listingu 3.107 suy do deklarowania
typu zbioru oraz okrelania wartoci, jakie w tym zbiorze maj si znajdowa.
Listing 3.107. Deklarowanie zbioru
var
CharSet: set of AnsiChar;
begin
CharSet := ['a'..'z'];

Typ bazowy zbioru (czyli typ podawany za sowem kluczowym of) musi by typem
porzdkowym, ktry dodatkowo nie moe przyjmowa wicej ni 256 rnych wartoci.
W zmiennej zbioru dla kadej wartoci rezerwowany jest jeden bit, ktry ma okrela,
czy dana warto jest obecna w zbiorze, czy te nie. Przedstawiony powyej kod oznacza,
e zdefiniowany w nim zbir znakw ma wielko 32 bajtw.
Typ zbioru jest oczywicie interesujcy tylko wtedy, gdy mona do niego szybko dodawa i usuwa elementy oraz moliwe jest atwe sprawdzanie, czy dany element jest
zawarty w zbiorze. Operacje dodawania elementw do zbioru i usuwania ich z niego
uatwiaj dwie funkcje standardowe przedstawione na listingu 3.108.
Listing 3.108. Standardowe funkcje obsugujce dodawanie i usuwanie elementw ze zbioru
{ Dopisywanie elementu do zbioru: }
Include(CharSet, 'd');
{ Usuwanie elementu ze zbioru: }
Exclude(CharSet, 'x');

Funkcji tych nie mona jednak uywa z waciwociami, poniewa w parametrach


przyjmuj wycznie zmienne. Do waciwoci zbioru now warto dopisa mona
na przykad tak:
BorderIcons := BorderIcons+[biMaximize];

Do obsugi zbiorw, obok operatora dodawania (+), mona wykorzystywa jeszcze inne
operatory, wypisane w tabeli 3.9.

3.6.6. Kategorie typw w CLR


System typw stosowany w rodowisku .NET (CTS ang. Common Type System) przede
wszystkim rozrnia dwa rodzaje typw: typy wartoci i typy referencji. W tym punkcie
wyjani, na czym polegaj rnice miedzy tymi rodzajami typw i jak dopasowywane
s typy jzyka Object Pascal to tego podziau.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

377

Tabela 3.9. Operatory obsugujce operacje na zbiorach


Operator

Funkcja

Wynik

Przykad

czenie

Zbir

[1..3,5,9]+[4..6]=[1..6,9]

Rnica

Zbir

['c'..'h']-['a'..'z']=[]

Cz wsplna

Zbir

[1,3,5,6,]*[1,2,3]=[1,3]

Rwno

Boolean

([niebieski]=[niebieski])=True

<>

Nierwno

Boolean

<=

Podzbir

Boolean

>=

Nadzbir

Boolean

<

Rzeczywisty podzbir

Boolean

>

Rzeczywisty nadzbir

Boolean

in

Zawieranie elementu

Boolean

([5]<=[1,5,10]=True)
([czerwony]<[czerwony])=False
(zielony in [czerwony, zielony])=True

Typy referencji
Typy referencji (nazywa s te typami wskanikowymi) charakteryzuj si tym, e wewntrznie realizowane s one wycznie poprzez wskanik. Zmienna typu referencyjnego przechowuje wycznie wskazanie na obiekt, ktry zosta dynamicznie utworzony
na stercie. Dziki tej waciwoci tego rodzaju zmiennych, kilka zmiennych moe
wskazywa na ten sam obiekt. Jeeli obiekt ten ulegnie jakiejkolwiek zmianie, to zmiana
ta widoczna bdzie natychmiast we wszystkich tych zmiennych. Zamy, e zmienna
ListView1 zadeklarowana zostaa jako typ referencyjny wskazujcy na kontrolk ListView.
W takich warunkach wywoanie:
MojObiekt := MojFormularz.ListView1;

spowoduje zapisanie do zmiennej MojObiekt wycznie referencji zapisanej w zmiennej ListView1, ale w systemie bdzie nadal istnia tylko jeden egzemplarz kontrolki
ListView. (Oprcz tego powikszona zostanie jeszcze warto licznika referencji kontrolki stosowanego przez mechanizm automatycznego zwalniania pamici). Podobnie,
w czasie przekazywania typu referencyjnego w parametrze metody kopiowana jest
zawsze wycznie referencja, co odpowiada metodzie przekazywania parametrw
przez referencj. Jak wida, nie da si przekaza zawartoci typu referencyjnego
przez warto.
W rodowisku .NET wszystkie typy s typami referencji, oprcz tych typw, ktre
wywiedzione s z klasy System.ValueType.
W jzyku Object Pascal typami referencji s:
klasy zadeklarowane sowem kluczowym class,
tablice,
wskaniki metod.

378

Delphi 2005

Typy wartoci
Typy wartoci nie s przechowywane na stercie, ale na stosie. Klasa typu wartoci zawsze
musi by klas wywiedzion z klasy System.ValueType. W jzyku Object Pascal kryteria te speniaj nastpujce typy:
typy proste, takie jak integer, boolean lub double (typy te odpowiadaj typom

rodowiska .NET wymienianym w rnych miejscach tego rozdziau; wszystkie


te typy zostay w rodowisku .NET wywiedzione z klasy System.ValueType),
typy wyliczeniowe i typy zakresw czciowych (wywodz si z klasy
System.Enum, a klasa ta zostaa wywiedziona z klasy System.ValueType),
rekordy, ktre na swj sposb rwnie reprezentuj klasy, ale definiowane s
sowem kluczowym record i w zwizku z tym s zawsze niejawnie wywodzone
z klasy System.ValueType.

Kada zmienna typu wartoci ma przydzielon swoj wasn pami na stosie i wasn kopi typu wartoci (wyjtkiem s parametry metod, ktre zadeklarowane zostay
jako parametry referencyjne). Przypisujc jedn zmienn typu wartoci do drugiej,
zawsze tworzymy pen kopi danych zapisanych w tej zmiennej, na przykad:
Integer2 := Integer1;
Record2 := Record1;

Po wykonaniu powyszych przypisa mona zmienia wartoci zmiennych Integer2


i Record2, bez jednoczesnego naruszania zawartoci zmiennych Integer1 i Record1.
W rodowisku CLR funkcjonuje jeszcze jedna klasa, ktra obok klasy ValueType
stanowi podstawow rnic pomidzy rodzajami typw danych MarshalByRefObject. Obiekty wywodzce si z tej klasy przekazywane s jako referencja w przypadku wywoa pochodzcych z innej domeny aplikacji, z kodu niezarzdzanego
albo zewntrznego komputera, podczas gdy wszystkie pozostae obiekty przekazywane s jako kopia wartoci. Podana tutaj rnica nie ma nic wsplnego z rnicami pomidzy omawianymi typami referencji i typami wartoci. Naley tu jednak
zaznaczy, e z klas MarshalByRefObject wsppracowa mog wycznie typy
referencyjne.

Mechanizm Boxingu
W rodowisku CLR mechanizm Boxingu tworzy moliwo przeksztacenia typu wartoci
w typ referencji, poprzez przeksztacenie jej w klas TObject, tak jak na listingu 3.109.
Listing 3.109. Przykad wykorzystania mechanizmu Boxingu
var
o: System.Object;
begin
o := System.Object(Integer(4));

Rozdzia 3. Jzyk Delphi w rodowisku .NET

379

Zastosowany przy tym mechanizm jest tutaj cakowicie logiczny i wynika z innych regu
obowizujcych w jzyku programowania. Klasa Object nie zostaa wywiedziona z klasy
System.ValueType i w zwizku z tym nie jest typem wartoci. Klasa ta jest jednak klas
nadrzdn w stosunku do klasy System.ValueType, co oznacza, e typ ValueType moe
by przeksztacony w typ Object.
W czasie dziaania programu cao nie przedstawia si jednak a tak atwo, poniewa
wartoci typu ValueType s odkadane na stosie i w czasie ich przeksztacania do typu
referencyjnego konieczne jest rezerwowanie pamici na stercie, a zawarto stosu musi
zosta skopiowana do sterty. W podanym wyej przykadzie powstajcy tak obiekt
zapisywany jest w zmiennej o, i w zwizku z tym moe by ona dowolnie przekazywana dalej, a sam obiekt zostanie usunity z pamici dopiero po wyzerowaniu wartoci
licznika jego referencji.

3.7. Instrukcje
W jzyku Object Pascal rozrnia si instrukcje proste i instrukcje strukturalne. Do instrukcji prostych zalicza si operacje przypisania za pomoc operatora przypisania (:=)
oraz wywoania procedur, funkcji i metod. Wikszo instrukcji strukturalnych suy
do kontrolowania przebiegu programu; zalicza si do nich ptle, sprawdzenia warunkw i skoki. W jzyku Pascal funkcjonuje jeszcze instrukcja with, ktra przeznaczona
jest przede wszystkim do uatwiania tworzenia tekstu programu i nie ma nic wsplnego
z przepywem programu.

redniki koczce instrukcje


W jzyku Pascal redniki su do rozdzielania od siebie dwch nastpujcych po sobie
instrukcji, co oznacza, e ostatnia instrukcja w bloku nie musi by zamykana rednikiem. Na szczcie jzyk obsuguje te instrukcje puste, dlatego ostatnie instrukcje
w bloku rwnie mog by zakoczone rednikiem (a nawet kilkoma). W programach
przedstawionych w tej ksice dla uproszczenia redniki umieszczaem za wszystkimi
instrukcjami.

Ptla for
Najprostsz struktur sterujc jest ptla for, wielokrotnie wykonujca jedn lub kilka
instrukcji. Ptla ta wystpuje w dwch wariantach: w pierwszym z nich poda naley
przedzia wartoci, w jakim ptla ma si wykonywa, a liczba obiegw ptli znana
jest jeszcze przed wykonaniem pierwszego obiegu. Przykad takiej ptli podaj na listingu 3.110.
Listing 3.110. Przykadowa ptla for
x := a + b
for i := a to b do
PowtarzanaInstrukcja;

380

Delphi 2005

Na pocztku ptla ta ustala warto zmiennej i na warto pobran ze zmiennej a i po


kadym obiegu ptli o jeden powiksza warto zmiennej i. Trwa to tak dugo, a zmienna
i osignie warto, jak miaa zmienna b przed rozpoczciem ptli. Jak wida, zmiany
wartoci zmiennych a i b wykonywane ju w czasie dziaania ptli nie maj adnego
wpywu na jej funkcjonowanie.

Ptla forin
Drugi wariant ptli for przygotowany zosta specjalnie dla Delphi dla .NET, ale firma
Borland wprowadzia go dopiero do Delphi 2005. Ptla ta suy do przegldania kolekcji rnych wartoci za pomoc jednej zmiennej. Jako takie kolekcje wykorzystywane mog by tablice, cigi znakw, zbiory oraz obiekty kolekcji. W przykadzie
przedstawionym na listingu 3.111 przegldana jest kolekcja cigw znakw zgromadzona we waciwoci TextBox1.Lines (tablica). W procesie tym wykorzystywana
jest zmienna s typu String, a cakowita dugo wszystkich cigw znakw jest sumowana i wypisywana w oknie programu.
Listing 3.111. Przykad zastosowania ptli forin
procedure TWinForm2.TextBox1_TextChanged(sender: System.Object; e:
System.EventArgs);
var
s: String;
OgolnaDlugosc: Integer;
begin
for s in TextBox1.Lines do
inc(OgolnaDlugosc, s.Lenght);
Text := OgolnaDlugosc.ToString;
end;

W podobny sposb mona przeglda wszystkie znaki cigu znakw, wykorzystujc


przy tym zmienn typu char. Do przegldania elementw zbioru zastosowa trzeba
zmienn o takim samym typie, jaki maj poszczeglne elementy tego zbioru.
Przegldanie kolekcji odbywa si mniej wicej tak, jak przegldanie tablic, co wynika
z tego, e w rodowisku .NET kada tablica jest kolekcj (klasa System.Array implementuje interfejs ICollection, a kada dynamiczna tablica w Delphi wywodzi si
z klasy System.Array). Przykad przegldania zawartoci kolekcji za pomoc ptli
forin znale mona w punkcie 2.2.5, w ktrym omawiany jest te wewntrzny mechanizm funkcjonowania tego rodzaju ptli.

Bloki
Wszystkie struktury sterujce mog wykonywa nie tylko pojedyncze instrukcje, ale
rwnie cae bloki kodu. We wszystkich przypadkach z wyjtkiem ptli repeatuntil
konieczne jest zamykanie takiego bloku kodu pomidzy sowami kluczowymi begin
i end, co doskonale wida na przykadzie ptli for przedstawionej na listingu 3.112.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

381

Listing 3.112. Przykad ptli for wykonujcej blok kodu


for i := 1 to 100 do begin
Instrukcja1;
Instrukcja2;
...
end;

Warunki
Wszystkie pozostae instrukcje dotyczce kontroli przepywu programu operuj na
jawnie sprecyzowanych warunkach. W czasie oceny takiego warunku w jzyku Object Pascal zawsze powstaje warto True (jeeli warunek zosta speniony) lub False
(w przypadku niespenienia warunku). Taki wynik logiczny moe zosta zwrcony przez
funkcje lub operatory (operatory porwnujce i operatory logiczne). Wszystkie operatory dostpne w jzyku Pascal oraz ich priorytety omawiaem ju w punkcie 3.6.2.

Instrukcja if
Instrukcja if skada si z wyraenia, ktrego warto musi zosta sprawdzona i maksymalnie dwch alternatywnych instrukcji lub blokw instrukcji. Instrukcja ocenia
podany warunek i w przypadku, gdy zwrci on warto True, wykonuje pierwsz instrukcj lub blok instrukcji, a jeeli warunek zwrci warto False, instrukcja wykona
instrukcj lub blok instrukcji zapisany po sowie kluczowym else. Przykad takiej
postaci instrukcji if przedstawiam na listingu 3.113.
Listing 3.113. Przykadowa instrukcja if
if Wyrazenie
then jestPrawdziwe
else jestFaszywe;

Nie chc tu wprowadza wykadu na temat rnic instrukcji if w jzyku Pascal w stosunku do podobnych instrukcji w innych jzykach programowania (na przykad w porwnaniu do jzykw wielkiej rodziny C). Musz jednak zaznaczy, e w jzyku Pascal
instrukcja if traktowana jest w caoci jako jedna instrukcja, razem z jej czci else.
Pamitamy, e znak rednika nie moe by umieszczany wewntrz instrukcji, w zwizku
z czym nie mona te umieszcza rednika przed sowem kluczowym else, tak jak
maj to w zwyczaju jzyki C# i Java. Na tej samej zasadzie opiera si te rozpoznawanie zagniede instrukcji if, takie jak na listingu 3.114.
Listing 3.114. Zagniedone instrukcje if
if Alternatywa1 then
if Alternatywa2 then
Instrukcja1
else Instrukcja2;

W powyszym kodzie powstaje pytanie, do ktrej instrukcji if naley instrukcja Instrukcja2. W jzyku Pascal kompilator oczekuje, e po pierwszym sowie kluczowym then
zapisana bdzie pena instrukcja, a ta nie koczy si jeszcze wraz z kocem instrukcji

382

Delphi 2005
Instrukcja1, poniewa nie zosta za ni umieszczony znak rednika (jeeli rednik

znalazby si w tym miejscu, to spowodowaby bd, poniewa przerywaby instrukcj zewntrzn). Z tego wynika, e w jzyku Pascal instrukcja Instrukcja2 zaliczona
zostanie jako cz drugiej instrukcji if. Podany wyej przykad mona by uzupeni
o jeszcze jedno sowo kluczowe else nalece do pierwszej instrukcji if, ale wtedy
konieczne byoby usunicie rednika zamykajcego instrukcj Instrukcja2.
Te wszystkie wtpliwoci z ca pewnoci nie bd si pojawia, jeeli instrukcje
przypisane do rnych czci poszczeglnych instrukcji if zamyka bdziemy pomidzy sowami kluczowymi begin i end. Na listingu 3.115 pokazano, e takie rozwizanie
pozwoli te na zmian przypisania czci else do innej instrukcji if ni na listingu 3.114.
Listing 3.115. Sowa kluczowe begin i end pozwalaj precyzyjnie umieci instrukcje w programie
if Alternatywa1 then begin
if Alternatywa2 then
Instrukcja1;
end
else Instrukcja2;

Ptla repeat
Ptla repeatuntil tak dugo wykonuje instrukcje zapisane pomidzy tymi dwoma sowami kluczowym, a warunek zapisany za sowem kluczowym until bdzie prawdziwy. Na listingu 3.116 przedstawiam nieskoczon ptl, ktra sama w sobie nie
wykonuje adnych sensownych operacji.
Listing 3.116. Przykadowa ptla repeatuntil
repeat
if a > 5 then a := a div 2;
a := a + 1;
until a > 9;

Ptla while
Cech ptli repeatuntil jest to, e warunek jej wykonania sprawdzany jest dopiero
po wykonaniu zawartych w niej instrukcji. Fakt ten czyni t ptl przypadkiem specjalnym najczciej stosowanej w jzyku Pascal ptli while. Ta ostatnia ju na samym
pocztku sprawdza, czy zawarto ptli w ogle powinna by wykonana. Przykad
ptli while przedstawiam na listingu 3.117.
Listing 3.117. Przykadowa ptla while
{ Oprnianie stosu }
while not Stack.Empty do
Stack.Pop;

W tym miejscu obowizuj oczywicie reguy zapisu blokw instrukcji oraz zasady
tworzenia warunku umieszczanego za sowem kluczowym while.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

383

Instrukcja case
Instrukcja case pozwala na uniknicie bardzo zoonych konstrukcji z instrukcjami
if, tworzc tabel moliwych wartoci zwracanych i powizanych z nimi funkcji. Przykad
zastosowania instrukcji case podaj na listingu 3.118.
Listing 3.118. Przykad instrukcji case
case RadioGroup.ItemIndex of
0: Instrukcja1;
1: begin
... // kilka instrukcji
end;
2, 3: Instrukcja2;
else ...
end;

Zmienna zapisywana za sowem kluczowym case musi by zmienn typu porzdkowego. Jak wida na przykadzie, kilka warunkw rozdzielanych w kodzie przecinkami
moe by obsugiwanych przez ten sam wycinek kodu. Wszystkie warunki niewymienione w kodzie instrukcji case mog by obsugiwane przez kod umieszczony za
sowem kluczowym else.
Jeeli jeden z warunkw zostanie odnaleziony na przygotowanej licie, to wykonywane
s instrukcje z bloku kodu znajdujcego si bezporednio za warunkiem. Wynika z tego,
e nie ma potrzeby stosowania znanej z jzyka C instrukcji break.

Skoki
Jzyk Object Pascal moe realizowa cztery rodzaje skokw:
Stosujc wywoanie Exit, ktre nie jest sowem kluczowym, powodujemy

natychmiastowe wyjcie z procedury lub funkcji, co jest rwnoznaczne ze skokiem


do sowa kluczowego end oznaczajcego koniec funkcji. Dziki takiemu rozwizaniu
unikn mona stosowania jednego lub kilku poziomw instrukcji if.
Wyjtki s najnowoczeniejszym rodzajem skokw, ktre po uwzgldnieniu

innych struktur sterujcych pozwalaj na atwe wyeliminowanie z kodu


skokw wykonywanych instrukcjami exit i goto. Wyjtkom powicony
zostanie podrozdzia 3.9.
Skoki w ptlach. Za pomoc instrukcji continue wykonywany jest skok na

pocztek ptli, tak jakby ju w tym miejscu zakoczy si jej poprzedni obieg.
Z kolei instrukcja break powoduje natychmiastowe wyjcie z ptli.
W kocu, nadal dostpne s instrukcje goto, ale s one ju tylko reliktem

z bardzo starych czasw i w tej ksice nie odgrywaj adnej roli.


Pod pewnymi wzgldami, z instrukcjami break i exit spokrewniona jest te instrukcja
halt. Powoduje ona natychmiastowe zakoczenie programu i dlatego wewntrznie jest
znacznie bardzie rozbudowana ni zwyczajny skok, poniewa po jej wywoaniu musz
by midzy innymi wykonane sekcje finalization wszystkich moduw programu.

384

Delphi 2005

Instrukcja with
Jedyn struktur sterujc jzyka Object Pascal, ktrej odpowiednika nie znajdziemy
w adnym z jzykw wywodzcych si z jzyka C, jest instrukcja with. W instrukcji
tej podawany jest zakres widocznoci, ktry w podanym bloku kodu ma by przez
kompilator uznawany za zakres domylny. Korzystajc z instrukcji with mona zamiast zapisu:
DefaultFontStruct.Name := 'Phantom';
DefaultFontStruct.Height := 20;
DefaultFontStruct.UseRaytracing := True;

zastosowa znacznie prostszy wariant:


with DefaultFontStruct do begin
Name := 'Phantom';
Height := 20;
UseRaytracing := True;
end;

Jak mona si domyla, instrukcja with pozwala przesoni pewne identyfikatory aktualnego zakresu widocznoci. Co wicej, za sowem kluczowym with podawa mona
kilka zakresw widocznoci rozdzielanych przecinkami, a nawet zagnieda bloki
tworzone tymi instrukcjami. Te dwie ostatnie moliwoci s jednak bardzo rzadko wykorzystywane praktycznie.

3.8. Procedury i funkcje


W jzyku Object Pascal wszystkie metody dzielone s na dwie kategorie: procedur i funkcji. Funkcja rni si od procedury wycznie sposobem zwracania wartoci wynikowych. Wewntrz ciaa funkcji zwracana warto musi by przypisywana do specjalnej
zmiennej o nazwie Result, a typ wartoci zwracanej przez funkcj podawany jest za
dwukropkiem umieszczonym za list parametrw funkcji. Przykad takiej funkcji podaj na listingu 3.119.
Listing 3.119. Przykadowa funkcja i procedura
function Math.WyliczTangens(f: Double): Double;
begin
Result := sin(f) / cos(f);
end;
procedure Math.WyswietlWartosc(f: Double);
begin
MessageBox.Show(FloatToStr(f));
end;

Z reguy listy parametrw s identyczne w funkcjach i procedurach. Skadnia parametrw


jest identyczna ze stosowan przy deklarowaniu zmiennych, za sowem kluczowym
var (sowo to moe pojawia si te na licie parametrw, ale ma tam inne znacznie,
ktre omawiane bdzie za chwil).

Rozdzia 3. Jzyk Delphi w rodowisku .NET

385

Wywoanie procedury wymaga zapisania jej nazwy i podania w nawiasach listy jej
parametrw, na przykad tak:
WyswietlWartosc(3.3);

Przy wywoywaniu procedur niepobierajcych adnych parametrw w jzyku Object


Pascal zazwyczaj nie zapisuje si pustych nawiasw za nazw procedury (jest to typowe dla jzykw wywodzcych si z jzyka C), cho w Delphi 2005 zapisywanie
pustych nawiasw zostao dopuszczone.
To samo dotyczy te funkcji, a jedyn rnic jest to, e wynik jej dziaania powinien
zosta w jaki sposb zagospodarowany, tak jak w podanym wyej przykadzie wykorzystane zostay wyniki zwracane przez funkcje cos i sin. Oczywicie, wartoci
zwracane przez funkcje mog by te ignorowane, przez co wywoanie funkcji upodabnia si do wywoania procedury (musi by jednak wczona opcja kompilatora $X
(EntendedSyntax), ktra wczona jest standardowo).

Deklaracje uprzedzajce
Jeeli dwie funkcje z jednego moduu, niebdce metodami i niezadeklarowane w czci
interfejsu moduu, wykorzystuj si wzajemnie, to o jednej z nich trzeba uprzedzajco
poinformowa kompilator. Przykad takiej uprzedzajcej deklaracji funkcji podaj na
listingu 3.120.
Listing 3.120. Przykad uprzedzajcej deklaracji funkcji
procedue ProcBuzywaA; forward; { deklaracja uprzedzajca }
procedue ProcAuzywaB;
begin
ProcBuzywaA;
end;
procedue ProcBuzywaA;
{ definicja procedury z deklaracji uprzedzajcej }
begin
ProcAuzywaB;
...

Jeeli nie przygotowalibymy dla kompilatora deklaracji uprzedzajcej, to pierwsza


funkcja nie mogaby wywoywa drugiej funkcji.
Takie deklaracje uprzedzajce nie s potrzebne w przypadku metod, poniewa deklaracja klasy jest niejako jedn wielk deklaracj uprzedzajc dla wszystkich metod.

3.8.1. Typy parametrw


Na pocztek musz poda wyjanienia pewnych poj: parametry, jakie podawane s
w deklaracji funkcji, nazywane s parametrami formalnymi (ang. Formal parameters),
natomiast parametry, z ktrymi funkcja pracuje w czasie swojego dziaania, nazywane
s parametrami aktualnymi (ang. Actual parameters).

386

Delphi 2005

Parametry wartoci
W jzyku Pascal, podobnie jak i w wikszoci innych jzykw programowania, istniej dwie moliwoci przekazywania parametrw. W aktualnych parametrach funkcji
znale mog si oryginalne zmienne przekazane bezporednio do funkcji albo tylko
ich kopie. W tym drugim przypadku wywoywana funkcja moe dowolnie zmienia
zawarto otrzymanego parametru, bez wpywu na warto przechowywan w funkcji
wywoujcej. Takie parametry nazywane s parametrami wartoci, a ich przykad podaj
na listingu 3.121.
Listing 3.121. Przykad parametrw wartoci przekazywanych do funkcji
procedure PaintCells(od, do: Integer);
begin
for od := od to do do begin
...
end;
end;
var
Start, Stop: Integer;
begin
Start := 0;
Stop := 10;
PaintCells(Start, Stop);

W powyszym przykadzie zawarto zmiennej Start nie zostanie zmieniona, poniewa funkcja PaintCells otrzyma tylko kopi tej wartoci zapisan w zmiennej od.
Zmienna ta utworzona jest na stosie i zostanie zniszczona, gdy tylko funkcja PaintCells zakoczy swoj prac.
Jeeli parametr wartoci jest typu referencyjnego, czyli na przykad jest obiektem,
to nie jest tworzona kopia tego obiektu, ale do funkcji przekazywana jest tylko
kopia wskazania na ten obiekt. Dziki temu wewntrz procedury mona przypisa
do parametru inny obiekt, nie wpywajc tym samym na obiekt otrzymany z procedury wywoujcej. Jeeli jednak zmienimy wartoci waciwoci obiektu otrzymanego
w parametrze, to zmiany te widoczne bd rwnie w procedurze wywoujcej.

Parametry referencyjne
Jeeli chcielibymy, eby w poprzednim przykadzie wywoywana procedura otrzymywaa faktyczn warto zmiennej Start, to musielibymy nastpujco zmieni deklaracj
procedury PaintCells:
procedure PaintCells(var od: Integer; do: Integer);

Sowo kluczowe var umieszczone w licie parametrw funkcji informuje kompilator,


e do funkcji przekazywany ma by wskanik na orygina parametru. Dziki temu nazwa od opisywaaby dokadnie ten sam wycinek pamici, co nazwa zmiennej Start.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

387

Oczywicie, takie parametry referencyjne (w nomenklaturze jzyka Pascal nazywane


s one parametrami zmiennymi) pozwalaj nie tylko na zmiany wartoci zmiennych, ale
te na oszczdnoci pamici stosu i przyspieszenie wywoa procedur i funkcji. Przykad takiego przyspieszenia wywoania funkcji podaj na listingu 3.122.
Listing 3.122. Parametry referencyjne pozwalaj na oszczdno czasu i pamici
type
TBigArray: array[0..100] of Longint;
procedure CheckArray(var A: TBigArray);

Jeeli w deklaracji funkcji CheckArray nie byoby sowa kluczowego var, to przy kadym jej wywoaniu w programie rezerwowanych byoby 400 bajtw w pamici stosu,
do ktrych kopiowana byaby zawarto tablicy. Dziki sowu kluczowemu var do
funkcji przekazywany jest tylko wskanik na tablic, ktry ma wielko czterech bajtw.
Niebezpieczestwo polega na tym, e w tym rozwizaniu procedura moe zmienia
wartoci zapisane w tablicy. Ta wada parametrw referencyjnych zostaa usunita dziki
wprowadzeniu do listy parametrw sowa kluczowego const.
Ostatni przykad zadziaa tylko dlatego, e typ TBigArray jest typem wartoci jzyka
Object Pascal, a nie dynamiczn tablic rodowiska CLR. Takie tablice dynamiczne s po prostu typami referencji, ktre zawsze przekazywane s przez referencj,
i w zwizku z tym nie powinny by deklarowane jako parametry ze sowem kluczowym var. Zadeklarowanie w taki sposb parametru typu referencyjnego ma tak
zalet, e wywoywana funkcja moe w otrzymanym parametrze zapisywa wartoci zwracane.

Parametry stae
Jzyk Object Pascal umoliwia te zadeklarowanie parametrw z wykorzystaniem
sowa kluczowego const. Kompilator nie pozwala wtedy na zmian tak zadeklarowanych parametrw wewntrz wywoywanej procedury. Jeeli w parametrze i tak przekazywana miaaby by tylko kopia wartoci, to takie ograniczenie nie miaoby sensu,
dlatego parametry zadeklarowane ze sowem kluczowym const traktowane s jak parametry referencyjne. Jest to bardzo efektywne rozwizanie w przypadku zmiennych
typw wartoci, ktre zajmuj wiele pamici, poniewa zamiast kopiowania caej zmiennej, do procedury przekazywany jest tylko wskanik.

Tablice otwarte
Ostatnim wariantem parametru przekazywanego do procedury s tablice otwarte (ang.
Open array), a w przypadku cigw znakw otwarte cigi znakw (ang. Open string).
Z tego rodzaju parametrw korzysta mona pod warunkiem, e nie zostaa wyczona
opcja kompilatora $P+ (Open parameters standardowo jest ona wczona). Parametry te nazywane s otwartymi, poniewa w parametrach formalnych (czyli w deklaracji) nie okrela si, ile elementw ma mie tablica albo z ilu znakw skada si ma
cig znakw. Oznacza to, e w (aktualnych) parametrach funkcji przekazywane mog
by tablice i cigi znakw o dowolnej wielkoci. W przypadku tablic otwartych wystarczy,

388

Delphi 2005

e podany zostanie typ elementw tablicy, a przy okazji mona te wybiera tryb przekazywania tablicy do funkcji: normalny, var lub const (odpowiednie deklaracje przykadowe przedstawiam na listingu 3.123).
Listing 3.123. Przykadowe deklaracje parametrw otwartych tablic
function SumujTablice_Kopia(a: array of Integer): Integer;
function SumujTablice_Bezposrednio(const a: array of Integer): Integer;
procedure SortujTablice(var a: array of Integer);

Rnice w wywoaniach funkcji z parametrami deklarowanymi normalnie i ze sowami


kluczowymi var lub const s identyczne z tymi opisywanymi w przypadku zmiennych.
Oznacza to, e funkcja SumujTablice_Kopia jest w czasie dziaania programu nieco
mniej wydajna ni funkcja SumujTablice_Bezposrednio, poniewa nie wymaga ewentualnego kopiowania duych iloci danych tablicy, ktrej wielkoci nie mona przewidzie, ze wzgldu na jej otwarty charakter.
Formalny parametr tablicy otwartej mona wypeni parametrem aktualnym na dwa
sposoby, przedstawione na listingu 3.124.
Listing 3.124. Przekazywanie tablic w parametrach tablic otwartych
var
ParamArray: array[0..10000] of Integer;
begin
SortujTablice(ParamArray); { przekazujc zmienn tablicow }
SumujTablice_Kopia([322, 223, 443, 988]); { przekazujc tablic tymczasow }

Funkcja SortujTablice pobiera w parametrze dowoln tablic liczb cakowitych, dlatego bez adnych kopotw mona przekazywa jej zmienn ParamArray. W wywoaniu drugiej funkcji nie bya stosowana zmienna tablicowa, ale konstrukcja opisywana
w nastpnym podpunkcie.

Konstruktory tablic otwartych


W wywoaniu drugiej funkcji z poprzedniego przykadu zastosowany zosta konstruktor tablicy otwartej (ang. Open array constructor), bdcy bardzo wydajnym mechanizmem jzyka Object Pascal, ktry w tekcie programu zapisywany jest w postaci
niewyrniajcych si nawiasw kwadratowych. W nawiasach tych podawana jest lista
wartoci zapisywanych do tworzonej tablicy tymczasowej, ktra nastpnie przekazywana jest do wywoywanej funkcji. W funkcji SumujTablice_Kopia przekazana tablica
wyglda tak, jakby zostaa ona przygotowana za pomoc kodu przedstawionego na
listingu 3.125.
Listing 3.125. Hipotetyczny kod konstruktora tablicy otwartej
var
TempArray: array of Integer;
begin
SetLength(TempArray, 4);
TempArray[0] := 322; TempArray[1] := 223;
TempArray[2] := 443; TempArray[3] := 988;

Rozdzia 3. Jzyk Delphi w rodowisku .NET

389

Jak mona si domyla, przekazywanie tymczasowej tablicy utworzonej konstruktorem


tablic jako parametru referencyjnego nie ma wikszego sensu, poniewa wartoci, jakie
w tablicy mogaby zmieni wywoywana funkcja, zostan utracone natychmiast po jej
zakoczeniu.

Praca z tablicami otwartymi


Waciwa praca z tablicami otwartymi moliwa jest tylko wtedy, gdy wewntrz procedury mamy moliwo sprawdzenia, jaka jest rzeczywista wielko przekazywanej
tablicy. W zwizku z tym wewntrz procedury mona wykorzystywa funkcje przedstawione na listingu 3.126, ktre pozwalaj na sprawdzenie wielkoci tablicy.
Listing 3.126. Funkcje sprawdzajce wielko tablicy otwartej
StartIndex := Low(a); { indeks pierwszego elementu tablicy (zawsze 0) }
EndIndex := High(a); { indeks ostatniego elementu tablicy }
TotalArraySize := SizeOf(a);

Pierwszy element tablicy zawsze ma indeks zerowy, dlatego oglna liczba elementw
zapisanych w tablicy wynosi EndIndex+1. Funkcje Low i High s w Delphi funkcjami
niezalenymi od platformy, ktre umoliwiaj okrelenie granicznych indeksw tablicy.
W rodowisku .NET dostpna jest niezalena od jzyka metoda System.Array.Length,
ktr mona te wywoywa dla kadej tablicy otwartej jzyka Object Pascal w celu
uzyskania informacji o oglnej dugoci tablicy (wczajc w ni element o indeksie
zerowym).

3.8.2. Przecianie metod i parametry standardowe


Jeeli pracujemy z metod, w ktrej pewne parametry mog otrzymywa warto
standardow, to w zapisie wywoania takiej metody moemy te parametry pomin.
W takim wypadku metoda otrzyma w parametrach tak warto, ktra zostaa zapisana
jeszcze w jej deklaracji. Przykad takiej deklaracji podaj na listingu 3.127.
Listing 3.127. Przykad deklaracji funkcji z parametrami standardowymi
// Lista cigw znakw skadana jest w jeden dugi cig znakw.
// Opcjonalnie poda mona maksymaln dugo poszczeglnych cigw znakw
// oraz znak rozdzielajcy te cigi:
function MakeString(Strings: TStringList; SeparatorChar: char = ';';
MaxLen: Integer = 0) // 0 oznacza brak ograniczenia dugoci
:String;

W deklaracji tej najwaniejsze jest to, e wszystkie parametry, ktre maj pewn warto
standardow, musz by podawane na kocu listy parametrw. Dziki temu kompilator
przy wywoywaniu metody moe okreli, ile parametrw ma otrzyma warto standardow, kierujc si wycznie liczb parametrw wpisanych w wywoaniu. Wynika
z tego, e przykadow funkcj z listingu 3.127 mona wywoywa na trzy sposoby:

390

Delphi 2005
Str := MakeString(Lista);
Str := MakeString(Lista, ',');
Str := MakeString(Lista, ',', 30);

//
//
//
//

Tworzenie cigu znakw z ustawieniami domylnymi


Do rozdzielania cigw stosowany jest przecinek
Dodatkowo, aden z wejciowych cigw znakw
nie moe by duszy ni 30 znakw.

W wywoaniach takiej funkcji obowizuje ta sama zasada, ktra obowizywaa w czasie


jej deklarowania: nie mona opuszcza dowolnego z parametrw, ale wycznie te
znajdujce si na kocu listy parametrw. Oznacza to, e chcc poda specjaln warto
trzeciego parametru, nie moemy pomin drugiego parametru, nawet jeeli moe on
otrzymywa warto standardow.
Parametry standardowe doskonale sprawdzaj si te w przypadkach, gdy czsto
stosowan metod chcemy uzupeni o dodatkowy parametr, a jednoczenie nie
chcemy zmienia istniejcych ju wywoa tej metody. Taki dodatkowy parametr
mona dopisa na kocu listy parametrw metody i uzupeni o warto standardow,
dziki czemu nie bdzie trzeba wymienia go w wywoaniach metody, a to oznacza, e nie bdzie konieczne modyfikowanie istniejcych ju wywoa tej metody.

Przecianie metod
Przecianie metod pozwala na stosowanie tej samej nazwy dla wielu rnych metod,
rnicych si od siebie pobieranymi parametrami. W czasie tworzenia takich metod
trzeba zawsze zwraca uwag na nastpujce rzeczy:
Kady wariant przecianej metody musi by opatrzony dyrektyw overload.
Wszystkie przeciane funkcje otrzymujce t sam nazw musz si

jednoznacznie odrnia od pozostaych typami swoich parametrw


lub typem wartoci zwracanej.
Parametry standardowe mog utrudni rozrnianie przecianych metod,

poniewa funkcja zadeklarowana z jednym parametrem standardowym


moe by wywoywana z dwoma zestawami parametrw.
W bibliotece FCL znale mona wiele przykadw przecianych metod; nale do nich
midzy innymi metody rysujce, ktre zalenie od wyboru programisty pracowa mog
ze wsprzdnymi podawanym pojedynczo albo zgromadzonymi w ramach struktury
Rectangle. Z kolei same wsprzdne mog by podawane jako liczby cakowite lub
liczby zmiennoprzecinkowe. W Delphi metody DrawRectangle pochodzce z klasy Graphics
mona prbowa przedstawi tak jak na listingu 3.128.
Listing 3.128. Rekonstrukcja deklaracji metod DrawRenctangle w Delphi
type
Graphics = class
procedure DrawRectangle(aPen: Pen; aRectangle: Rectangle); overload;
procedure DrawRectangle(aPen: Pen; x, y, w, h: Integer); overload;
procedure DrawRectangle(aPen: Pen; x, y, w, h: Double); overload;
end;
implementation

Rozdzia 3. Jzyk Delphi w rodowisku .NET

391

// W implementacji nie stosujemy ju oznaczenia overload


procedure Graphics.DrawRectangle(aPen: Pen; aRectangle: Rectangle);
begin
...

3.8.3. Wskaniki metod


Dziki typom proceduralnym ju w historycznych wersjach Delphi moliwe byo
przekazywanie procedur i funkcji w parametrach lub zapisywanie ich wewntrz zmiennych. W poniszym przykadzie deklarowany jest typ wskanika na funkcj przyjmujc w parametrze liczb cakowit i podobnie zwracajc tak liczb w wyniku
swojego dziaania:
type
TIntFunction = function(x: Integer): Integer;

Deklaracja rni si od zwyczajnej deklaracji funkcji tym, e sowo kluczowe function


nie znajduje si na jej pocztku, ale wypisywane jest dopiero po znaku rwnoci. Deklarowana w ten sposb nazwa TIntFunction jest nazw typu funkcji, ktry moe by
podawany jako typ parametru przekazywanego do innej funkcji:
procedure RysujKrzywa(Function: TIntFunction);

Teraz moemy zaoy, e w programie dziaa bd dwie konkretne funkcje (Funkcja1


i Funkcja2) zgodne z zadeklarowanym typem:
function Funkcja1(x: Integer): Integer;
function Funkcja2(x: Integer): Integer;

W zwizku z tym moemy kad z tych funkcji przekaza w parametrze do przedstawianej przed chwil procedury RysujKrzywa:
RysujKrzywa(Funkcja1);
RysujKrzywa(Funkcja2);

Obiektowym wariantem typw proceduralnych s typy metod. Jeeli w podanym wyej przykadzie zamiast zwyczajnej funkcji w parametrze ma by przekazywana metoda pewnego obiektu, to musi zosta przygotowana specjalna deklaracja typu:
type
TIntMethod = function(X: Integer): Integer of object;

Typy metod nie s zgodne z typami proceduralnymi, poniewa metody wymagaj


podania dodatkowego, niewidocznego parametru self, bdcego wskanikiem na
obiekt, na rzecz ktrego wywoywana jest metoda (bya o tym mowa w punkcie 3.2.3).
Dodatkowo, wskaniki metod obok wskazania samej metody zapisuj te dane obiektu,
z ktrego pochodz, i ktre bd przekazywane metodzie w parametrze self (jest to
ogromna przewaga tych wskanikw nad podobnymi wskanikami metod stosowanymi
w jzyku C++).
Typy metod stosowane s przede wszystkim do definiowania zdarze i w zwizku z tym
dokadnie omwione zostan w punkcie 6.2.1.

392

Delphi 2005

3.9. Wyjtki
Wyjtki najczciej stosowane s jako mechanizm obsugi bdw, cho w rzeczywistoci mog one by stosowane do obsugiwania wszystkich rodzajw sytuacji wyjtkowych (angielskie sowo Exception oznacza wanie wyjtek). W momencie wystpienia wyjtku przerywany jest aktualny przepyw programu i poszukiwana jest
metoda, ktra mogaby przechwyci ten wyjtek. Moe to by te ta sama metoda,
w ktrej wyjtek wystpi. Jeeli jednak metoda ta nie przechwyci wyjtku, to jej wykonywanie jest przerywane, a kontrola przekazywana jest to metody wywoujcej. Jeeli
ona rwnie nie przechwyci wyjtku, to nastpuje przerwanie take jej dziaania. Dzieje
si tak do czasu, a znaleziona zostanie metoda obsugujca dany wyjtek, przy czym
kada przerywana metoda ma jeszcze szanse na kocow obsug wyjtku (blok finally),
w ktrej moe wykona wane prace czyszczce.
Jeeli adna z wywoujcych metod nie bdzie w stanie obsuy wyjtku, to jest on obsugiwany w rodowisku CLR, ktre wywietla specjalny komunikat i pozwala uytkownikowi wybra pomidzy zakoczeniem programu a uruchomieniem debugera w celu
sprawdzenia programu.
Wyjtki s w Delphi obsugiwane od pierwszej wersji pakietu, a dzisiaj, w rodowisku
.NET, stay si preferowan metod obsugi bdw w programie.

3.9.1. Wywoywanie wyjtkw


Na pocztek zajmiemy si rdem powstawania wyjtkw. Jako przykad przyjmiemy
tutaj zdefiniowany w rodowisku .NET wyjtek ArgumentException opisujcy sytuacj, w ktrej dana metoda wywoana zostaa z nieprawidowymi wartociami parametrw. Przygotujmy sobie przykadow metod pobierajc w parametrze kontrolk,
przy czym przekazywana jej kontrolka musi by czci jakiegokolwiek formularza.
Jeeli metoda stwierdzi, e przekazana jej kontrolka nie spenia tych podstawowych zaoe, to moe wywoa odpowiedni wyjtek stosujc kod przedstawiony na listingu 3.129.
Listing 3.129. Wywoanie wyjtku wewntrz metody
procedure MojaKlasa.UzyjKontrolki(Param:Control);
begin
if not ((Param as Control).TopLevelControl is Form)
then raise ArgumentException.Create('Parametr musi by kontrolk '
+'bdc czci formularza.');

Tworzenie obiektu wyjtku przebiega dokadnie tak samo jak w przypadku kadego
innego obiektu, czyli poprzez wywoanie konstruktora Create odpowiedniej klasy. Do
rozpoczcia specjalnego postpowania zwizanego z obsug wyjtkw konieczne jest
zastosowanie sowa kluczowego raise. Znaczenie tego sowa (podnie) doskonale
opisuje to, co dzieje si w momencie wywoania wyjtku. Przerywane jest wykonywanie aktualnej metody, a wyjtek przenoszony jest na wyszy poziom stosu wywoa. Moliwe sposoby reakcji metody wywoujcej przedstawiam w punkcie 3.9.4.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

393

3.9.2. Klasy wyjtkw


Zamiast dla kadego moliwego bdu przygotowywa kod obsugi, z ktrym najprawdopodobniej powizany bdzie jaki specjalny tekst komunikatu, w przestrzeniach nazw biblioteki FCL i w moduach biblioteki VCL przygotowano dla kadego
rodzaju bdu osobn klas wyjtku. Kada z takich klas zostaa bezporednio lub porednio wywiedziona z klasy System.Exception.
Klasa Exception deklaruje te wszystkie elementy, ktre s potrzebne we wszystkich
innych klasach wyjtkw: cig znakw z komunikatem o bdzie, ktry mona odczyta
z waciwoci Exception.Message, a take inne dane, ktre s automatycznie przygotowywane przez rodowisko CLR, takie jak dane metody, w ktrej wystpi wyjtek
oraz dane obiektu, na rzecz ktrego metoda ta bya wywoywana.

Definiowanie wasnych wyjtkw


Jeeli w naszym programie moe wystpowa nowy rodzaj bdu, ktry chcielibymy
uwzgldnia w procedurach obsugi bdw, to moemy przygotowa dla niego specjaln klas wyjtku. Najprawdopodobniej bdziemy chcieli umieci j gdzie w istniejcej ju hierarchii klas wyjtkw, dlatego jej deklaracja moe przypomina t
podan na listingu 3.130.
Listing 3.130. Deklaracja nowej klasy wyjtkw
type
// Klasa ApplicationException jest jedn z klas wyjtkw
// zdefiniowanych w przestrzeni nazw System biblioteki FCL
ETableOverflow = class(ApplictationException)
public
TableSize: LongInt;
end;

Wyjtek ten moe by wywoywany w programie w momencie, gdy w wewntrznej


tablicy stosowanej w programie nie bdzie ju miejsca na nowe elementy (niezalenie
do tego, jak moe doj do takiego ograniczenia). W takiej sytuacji mona te skorzysta z klasy ApplicationException albo nawet bezporednio z klasy Exception, jeeli
nie byby potrzebny zadeklarowany w nowej klasie element TableSize. W takiej sytuacji pozbawiamy si jednak moliwoci odrnienia w programie naszego wasnorcznie zdefiniowanego wyjtku od pozostaych (o przechwytywaniu wyjtkw mwi
bd w punkcie 3.9.4).
Podany wyej przykad wyjania te, dlatego klasy wyjtkw przechowuj tak niewielkie iloci danych. Zdefiniowany w naszej klasie wyjtku element TableSize najprawdopodobniej nie bdzie w programie w ogle potrzebny, poniewa moe on w inny
sposb ustali, jaka jest aktualna wielko tablicy.

394

Delphi 2005

3.9.3. Zabezpieczanie kodu


z wykorzystaniem sekcji finally
Przyjrzymy si tutaj oglnemu przypadkowi metody, ktra nie przeprowadza kocowej
obsugi wyjtkw, ale przekazuje wyjtki do funkcji nadrzdnych w stosie wywoa.
Jak si okazuje, w wielu przypadkach nie wystarcza proste przerwanie dziaania funkcji.
Jeeli funkcja w czasie swojego dziaania rezerwowaa jakie zasoby, to powinna je
zwalnia, gdy tylko wywoany zostanie jakikolwiek wyjtek. Bez wyjtkw kod takiej
metody mgby wyglda na przykad tak, jak pokazano na listingu 3.131.
Listing 3.131. Kod metody nieobsugujcej wyjtkw
Plik := FileStream.Create(...); // Rezerwowanie zasobu
if WywolaniePowodujaceBlad = -1 then begin // rdo bdu!!!
Plik.Close; // "Awaryjne" zwolnienie zasobu
exit;
end;
... pozostae instrukcje ...
Plik.Close; // normalne zwolnienie zasobw

W przykadzie tym zaoono, e funkcja WywolaniePowodujaceBlad zwraca informacj


o wystpieniu bdu w postaci wartoci -1. Przedstawiony na powyszym listingu kod
samodzielnie decyduje, czy moe kontynuowa prac, czy te powinien zakoczy
dziaanie funkcji wywoaniem exit. Ten ostatni wariant wie si oczywicie z koniecznoci zamknicia otwartego pliku.
Jeeli jednak funkcja WywolaniePowodujaceBlad nie przekazywaaby informacji o bdzie w zwracanej wartoci, ale generowaaby wyjtek, to kod podanej wyej metody
musiaby zosta zmieniony tak, jak pokazano na listingu 3.132, aby metoda nadal
moga zwalnia zarezerwowany plik w przypadku awarii.
Listing 3.132. Kod metody z obsug wyjtkw
Plik := FileStream.Create(...); // Rezerwowanie zasobu
try
WywolaniePowodujaceBlad;
// rdo bdu!!!
... pozostae instrukcje ...
finally
Plik.Close; // zwolnienie zasobu
end;

W przypadku wystpienia wyjtku wszystkie instrukcje zapisane pomidzy wywoaniem


funkcji WywolaniePowodujaceBlad, a sowem kluczowym finally zostan cakowicie
pominite w wykonywanej metodzie. Jak wida, tutaj program nie ma moliwoci wyboru, takiej jak w pierwszym wariancie.
Kod w konstrukcji tryfinallyend wykonywany jest nastpujco: jeeli w czasie wykonywania instrukcji umieszczonych pomidzy sowami kluczowymi try i finally
wystpi jakikolwiek wyjtek, to blok jest natychmiast opuszczany, a program przystpuje
do wykonywania instrukcji zapisanych pomidzy sowami kluczowymi finally i end.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

395

Po tym wszystkim metoda jest opuszczana bez wykonywania jakichkolwiek instrukcji


znajdujcych si za blokiem finally. Jedynym wyjtkiem jest sytuacja, w ktrej blok
try finally zamknity jest wewntrz innego bloku tryfinally. W takim wypadku
wykonywane s jeszcze instrukcje zapisane w sekcji finally bloku zewntrznego.
Jeeli w czasie pracy metody nie wystpi aden wyjtek, to jej kod wykonywany jest tak,
jakby w ogle nie byo w nim sw kluczowych try i finally: najpierw wykonywany
jest cay blok try, a nastpnie cay blok finally.

Zasoby chronione
Z powodu tego, e zasoby zarezerwowane w bloku try z ca pewnoci zostan zwolnione w bloku finally, blok try bardzo czsto nazywany jest te blokiem chronionym.
Jeeli wewntrz tego bloku rezerwowane s jeszcze inne zasoby, ktre rwnie musz
by odpowiednio chronione, to konieczne jest zagniedanie struktury sterujcej, tak
jak pokazano na listingu 3.133.
Listing 3.133. Zagniedone bloki chronione
AllocateRes1 { Zasb pierwszy }
try { blok chroniony dla zasobu pierwszego }
... { Pozycja (*) }
AllocateRes2 { zasb drugi }
try { blok chroniony dla zasobu drugiego }
...
finally
FreeRes2; { Zwolnienie zasobu drugiego }
end;
finally
FreeRes1; { Zwolnienie zasobu pierwszego }
end;

Jeeli w podanym kodzie wyjtek wystpi w pierwszym, ale jeszcze poza drugim
blokiem chronionym (pozycja oznaczona znakiem gwiazdki (*)), to wykonany zostanie
wycznie blok finally zwizany z zewntrznym blokiem chronionym. Wewntrzny
blok finally nie musi by wykonywany, poniewa drugi zasb nie zosta jeszcze zarezerwowany. Blok finally wczany jest do przewidywanego przepywu programu
dopiero wtedy, gdy wykonana zostanie pierwsza instrukcja z powizanego z nim bloku
try, a wtedy wykonywany jest nieodwoalnie, nawet jeeli wyjdziemy z bloku try
wywoujc instrukcj exit.

3.9.4. Obsuga wyjtkw


W poprzednim punkcie opisywane byy reakcje na wystpienie wyjtku, ktre jednak
nie powodoway jego zniesienia. Po wykonaniu instrukcji z bloku finally obiekt
wyjtku tak dugo przekazywany jest do funkcji wywoujcych na wyszych poziomach, a w ktrej z nich znajdzie si blok except obsugujcy ten wyjtek. Wszystkie funkcje, ktre nie bd miay takiego bloku, zostan natychmiast przerwane i wykonany zostanie w nich jedynie ewentualny blok finally.

396

Delphi 2005

Blok except (obsugujcy wyjtki) suy do przygotowania takiej obsugi bdu, eby
program mg poprawnie wznowi swoje dziaanie. Pocztek chronionego tak bloku
kodu rozpoczyna si od sowa kluczowego try, podobnie jak w blokach chronionych
sowem kluczowym finally.
W przykadzie podanym na listingu 3.134 informacja o wyjtku przekazywana jest bezporednio do uytkownika.
Listing 3.134. Przykadowa obsuga wyjtku w programie
try
stream := FileStream.Create(...);
except
on FileNotFoundException do
MessageBox.Show('Pliku nie znaleziono!');
end;

Sprawdzanie klasy wyjtku


Jak wida w przykadowym kodzie, bloki except s o wiele bardziej zoone ni bloki
finally, poniewa pozwalaj na sprawdzanie klasy przechwyconego wyjtku i w swojej strukturze s nieco podobne do instrukcji case. Po sowie kluczowym on poda
naley klas wyjtku, ktr chcielibymy obsuy. Jeeli wyjtek ma podan tu klas
lub klas z niej wywiedzion, to wykonywane bd instrukcje zapisane za sowem
kluczowym do. Po ich wykonaniu, blok except zostanie zakoczony, nawet jeeli
znajduj si w nim jeszcze inne klauzule ondo, ktre mogy obsuy przechwycony
wyjtek. W kodzie z listingu 3.135 przedstawiam tak wanie klauzul, ktra nigdy
nie zostanie wykonana, poniewa przed ni zapisana jest obsuga wyjtkw klasy bazowej
jej wyjtku.
Listing 3.135. Kolejno zapisywania klas wyjtkw ma due znaczenie
except
on SystemException do ...
on FormatException do ...
SystemException
end;

// Klasa FormatException jest klas wywiedzion z klasy

Obie klauzule ondo musz zosta zamienione miejscami, eby przedstawiony kod
mg funkcjonowa waciwie. Po takiej zamianie drugi warunek zapisany za sowem
kluczowym on obsugiwaby wycznie wyjtki klasy SystemException, ale nie obsugiwaby wyjtkw klasy FormatException.
Mona tu te wymieni inne podobiestwa bloku obsugi wyjtkw do instrukcji case:
wicej ni jedna instrukcja musi zosta zamknita wewntrz bloku beginend, po sowie
kluczowym on podawa mona wicej ni jedn klas wyjtku, a poza tym przewidziana zostaa ga else obsugujca niewymienione do tej pory klasy wyjtkw.
To ostatnie rozwizanie nie jest raczej zalecanie, poniewa w ten sposb obsugiwane
bd te te wyjtki, ktrych powodu wywoania nie bdziemy zna. W takiej sytuacji
nie bdzie moliwe wychwycenie takiego wyjtku w miejscu do tego przewidzianym.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

397

Przechwytywanie obiektu wyjtku


W przedstawionych wyej przykadach sprawdzana bya tylko klasa wyjtku. Mona
jednak przechwytywa te sam obiekt utworzony w instrukcji raise za pomoc konstruktora Create. W tym celu naley nada mu jakkolwiek nazw i zapisa j za sowem kluczowym on, stosujc zapis podobny do deklaracji zmiennej. Nastpnie mona
korzysta z elementw obiektu wyjtku, na przykad odczytywa tre komunikatu ze
zmiennej Message, ktra dostpna jest we wszystkich klasach wyjtkw, lub zawarto zmiennej TableSize dostpnej wycznie w klasie wyjtku ETableOverflow zdefiniowanej w punkcie 3.9.2. Przykadowy kod korzystajcy z tej moliwoci przedstawiam na listingu 3.136.
Listing 3.136. Przechwytywanie obiektu wyjtku
except
on E: ETableOverflow do begin
MessageBox.Show('Tablica jest zbyt maa. Aktualna wielko = '
+ E.TableSize.ToString);
end;
end;

Istnieje jeszcze jedna moliwo odebrania obiektu zdarzenia, ktr mona wykorzystywa w gazi else bloku except. Dostpna w Delphi funkcja ExceptObject zwraca
aktualny obiekt wyjtku w postaci referencji typu System.Object albo warto nil, jeeli
aktualnie nie ma adnego wyjtku.

Wznawianie wyjtku
W bloku obsugujcym wyjtek mona ponownie wykorzysta sowo kluczowe raise.
Jeeli zostanie ono wywoane bez adnych parametrw, to powoduje ponowienie tego
wyjtku, ktry jest aktualnie obsugiwany. Jeeli w naszej funkcji moemy tylko czciowo obsuy stan bdu, to dziki takiemu rozwizaniu mamy moliwo wykonania
odpowiednich prac w jednym bloku except i przekazania wyjtku do dalszej obrbki
w funkcji wywoujcej. Przykad takiej sytuacji przedstawiam na listingu 3.137.
Listing 3.137. Przekazanie czciowo obsuonego wyjtku do funkcji wywoujcej
try
...
except
on Exception do begin
{ Zwalniane s wszystkie zasoby, ktre nie bd potrzebne tylko w przypadku
wystpienia pewnego wyjtku, ale w przypadku bezbdnego wykonania
metody powinny pozosta zarezerwowane. }
...
raise; { Pozostaa cz obsugi bdu wykonywana jest w funkcji wywoujcej }
end;
end;

Kod przedstawiony w tym przykadzie w swoim dziaaniu podobny jest do bloku


finally, ktry jednak wykonywany jest wycznie w przypadku wystpienia wyjtku,
ale w sytuacji bezbdnego dziaania bloku try jest pomijany.

398

Delphi 2005

Jeeli w bloku except wywoana zostanie instrukcja raise tworzca nowy obiekt wyjtku,
to zastpi on obsugiwany do tej pory wyjtek, chyba e zostanie on obsuony w ramach
tego samego bloku except. Takie dziaanie jest specjalnym przypadkiem nazywanym
zagniedaniem wyjtkw.

Zagniedanie wyjtkw
W czasie wykonywania bloku except moe oczywicie zdarzy si sytuacja, w ktrej
wygenerowany zostanie kolejny wyjtek. Taki dodatkowy wyjtek moe by obsugiwany wewntrz kolejnej struktury tryexcept (lub finally), na przykad tak jak na
listingu 3.138.
Listing 3.138. Zagniedona obsuga wyjtkw
try
except
on Wyjatek1 do
try
except
on Wyjatek2 do
end;
end;

Jeeli w pierwszym bloku except nie zostanie obsuony wygenerowany w nim wyjtek, to przekazany zostanie on na poziom wyej, zastpujc tym samym aktualny
obiekt wyjtku.

Zakoczenie wyjtku
Wygenerowany w programie wyjtek uznawany jest za obsuony w momencie wywoania bloku obsugi wyjtku, w ktrym nie zosta on odnowiony. Blok obsugujcy
wyjtek moe przyjmowa trzy rne postaci:
Klauzuli on zgodnej z aktualn klas wyjtku, ktra koczy stan wyjtkowy

w sposb opisany powyej.


Czci else bloku obsugi wyjtkw, ktra moe obsuy wszystkie pozostae

wyjtki.
Bloku except, ktry zamiast listy obsugiwanych wyjtkw ujtych w klauzulach
on moe zawiera tylko cig instrukcji. Tak zapisany blok obsuguje wszystkie

przechwytywane wyjtki, co jest rwnoznaczne z blokiem zawierajcym


wycznie cz za sowem kluczowym else i nieposiadajcym adnej klauzuli on.
Funkcje nieposiadajce czci obsugi wyjtkw staj si dla wyjtkw tylko elementem
poredniczcym w przekazywaniu wyjtku do wyszego poziomu obsugi. Jeeli w programie nie znajdzie si aden blok obsugujcy aktualny wyjtek, to przekazywany
jest on ostatecznie do rodowiska CLR, ktre wywietla na ekranie komunikat o wystpieniu wyjtku i zamyka program.

Rozdzia 3. Jzyk Delphi w rodowisku .NET

399

Standardowa obsuga wyjtkw wykonywana w rodowisku CLR rni si w wielu


miejscach od schematu obsugi stosowanego w bibliotece VCL. Aplikacje VCL po
wystpieniu wyjtku najczciej kontynuuj swoj prac, poniewa biblioteka VCL
wywietla co prawda komunikat o bdzie dla wszystkich wyjtkw wystpujcych
w czasie pracy aplikacji i gwnego formularza, ale poza tym ignoruje nieobsuone wyjtki. IDE Delphi jest doskonaym przykadem programu, ktry moe cakiem
sprawnie pracowa po wystpieniu pewnych rodzajw wyjtkw. W rodowisku
.NET rwnie mona ignorowa wszystkie wystpujce w aplikacji wyjtki, wstawiajc w krytycznym miejscu w programie pusty blok except. W takiej sytuacji naley uzna, e rozwizanie stosowane przez rodowisko CLR jest waciwszym,
tym bardziej, e dostpna jest w nim rwnie stosowana w bibliotece VCL metoda
przymykania oczu na pojawiajce si wyjtki, cho nie jest ona uznawana za
rozwizanie domylne.

3.9.5. Asercja
Na zakoczenie zajmiemy si technik wyszukiwania bdw i diagnozowania problemw, w ktrej wykorzystywane s rwnie wyjtki, a konkretnie klasa wyjtkw
Borland.System.EAssertionFailed.
Korzystajc z funkcji debugera opisywanych w podrozdziale 1.7 moemy sprawdza
w czasie dziaania programu, czy wszystko przebiega zgodnie z oczekiwaniami, na
przykad to, czy ptla while prawidowo schodzi do wartoci zera. Mwic bardziej
oglnie moemy kontrolowa, czy speniane s pewne warunki dziaania programu.
Musz tu zaznaczy, e Delphi pozwala na wykorzystywanie specjalnych mechanizmw kontrolujcych takie warunki pracy programu:
Stosujc standardow instrukcj Assert mona sprawdza pewne warunki,

ktre musz by spenione w prawidowo dziaajcym programie. Jeeli


okrelony warunek nie jest speniony, to instrukcja Assert podnosi wyjtek
EAssertionFailed.
Szczeglny status instrukcji Assert polega jednak na tym, e w opcjach

kompilatora mona ustali, czy instrukcja ta ma by uwzgldniana w kodzie


rdowym programu, czy te nie (menu Project/Options/Compiler,
przecznik Assertions).
W czasie tworzenia programu, czste korzystanie z instrukcji Assert i zwizanych
z ni testw warunkw umoliwia szybkie wyszukiwanie wielu bdw. Po zakoczeniu tworzenia programu mona bardzo atwo wyczy wszystkie testy wykonywane instrukcjami Assert. W takich okolicznociach mona zakada, e w gotowym
programie wszystkie podstawowe warunki jego dziaania zostan spenione, wobec
czego wyczenie testw wykonywanych instrukcjami Assert ma jeszcze t zalet, e
nieznacznie podnosi prdko dziaania caej aplikacji i powoduje zmniejszenie rozmiarw pliku wykonywalnego. Na listingu 3.139 przedstawiam wyczerpujcy przykad.

400

Delphi 2005

Listing 3.139. Przykad wykorzystywania instrukcji Assert


i := 1;
repeat
Assert(i < 100, 'Warunek i < 100 nie jest speniany!');
i := i + 2;
until i = 100;

W podanym kodzie przykadowym chcemy si upewni, e ptla rzeczywicie wykonywana bdzie tak dugo, jak dugo zmienna i bdzie miaa warto mniejsz od 100.
Zmienna i przyjmuje jednak wycznie wartoci nieparzyste, przez co nigdy nie zostanie
speniony warunek kocowy i = 100, poniewa zmienna i osignwszy warto 101
nie speni warunku zakoczenia ptli. W tym miejscu wkroczy do dziaania instrukcja
Assert, wskazujc na przyczyn bdu.
Co prawda instrukcja Assert jzyka Object Pascal ma w porwnaniu do makrodefinicji
Assert z jzyka C++ t wad, e nie wywietla automatycznie w oknie komunikatu
penej treci warunku, ale za to pozwala unikn stosowania schematw kodu, takich
jak przedstawiony na listingu 3.140, ktry musiaby si wielokrotnie pojawia w kodzie
rdowym programu.
Listing 3.140. Mao praktyczna metoda wyczania sprawdzania warunkw dziaania programu
{$ifdef AsercjaAktywna}
if not (i < 100) then
// Wywietlenie komunikatu lub wywoanie wyjtku
{$endif}

rodowisko .NET oferuje te metod Debug.Assert, bdc pewn alternatyw


w stosunku do dostpnej w Delphi instrukcji Assert. Uycie tej metody wymaga
doczenia do moduu przestrzeni nazw System.Diagnostics. Jak si okazuje,
metoda Debug.Assert dziaa na zupenie innej zasadzie ni instrukcja Assert
z Delphi. Nie wywouje adnych wyjtkw, ale standardowo wywietla tylko okno
z komunikatem, przy czym komunikat ten mona te skierowa do debugera.
Ignorowanie wyjtku (czyli pozwolenie na dalsz prac programu) jest tu znacznie
atwiejsze ni w przypadku instrukcji Assert z Delphi, poniewa wymaga tylko kliknicia odpowiedniego przycisku w oknie komunikatu. Z drugiej strony, nieco trudniejsze jest debugowanie programu, poniewa po przejciu do debugera program
zostanie zatrzymany gboko wewntrz procedur biblioteki FCL, a nie w miejscu,
w ktrym nastpio wykrycie nieprawidowoci warunkw pracy programu przez
metod Debug.Assert.
Podobnie jak w przypadku instrukcji Assert z Delphi, moliwe jest grupowe wczanie
i wycznie kontroli wykonywanych przez metod Debug.Assert, a dodatkowo mona
dokonywa te zmian innych ustawie opisanych dokadnie w dokumentacji pakietu
SDK, na stronie opisujcej klas System.Diagnosics.Debug. Stosowanie instrukcji Assert z Delphi zalecane jest wszdzie tam, gdzie aplikacja nie powinna by
uzaleniona od rodowiska .NET, czyli na przykad w aplikacjach VCL.NET, ktre
maj by kompilowane rwnie w rodowisku Win32.

You might also like