Einstieg in Visual C# 2005 - EDV

Werbung
Auf einen Blick
1 Einführung ....................................................................
17
Teil I Erste Schritte....................................................
27
2 Aufbau von C#-Programmen .........................................
29
3 Konstanten, Variablen & Datentypen ...........................
47
4 Zusammengesetzte Datentypen und Namensräume .....
81
5 Operatoren ................................................................... 101
6 Kontrollkonstrukte ....................................................... 127
Teil II Objektorientierte Programmierung mit C# ..... 153
7 Einführung in die objektorientierte Programmierung ... 155
8 Generische Klassen, Schnittstellen und statische
Klassenmitglieder ......................................................... 213
9 Strings & reguläre Ausdrücke ........................................ 245
10 Ausnahmen – Exceptions .............................................. 267
11 Überladen von Operatoren ........................................... 283
12 Delegates und Ereignisse .............................................. 291
13 Indizierer, Enumeratoren und Collections ..................... 315
14 Attribute und Metadaten .............................................. 341
15 XML-Dokumentation und Präprozessor ........................ 367
16 Threading ...................................................................... 389
Teil III Windows Forms .............................................. 423
17 Einführung .................................................................... 425
18 Steuerelemente ............................................................. 471
19 Grafik und Drucken in Windows FormsAnwendungen ............................................................... 505
20 Bibliotheken und CodeDOM ........................................ 555
A Fehlersuche in Programmen .......................................... 569
B Hilfequellen .................................................................. 579
Index ............................................................................. 583
Inhalt
1
Einführung
17
1.1
.NET ....................................................................................................................
1.1.1
Verwendung mehrerer Programmiersprachen für ein Projekt .....
1.1.2
Garbage Collection und Sicherheit .................................................
17
19
21
1.2
C# ........................................................................................................................
22
1.3
Zielgruppe .........................................................................................................
23
1.4
Danksagungen ..................................................................................................
23
1.5
Kontakt ..............................................................................................................
24
1.6
Aufbau des Buches ...........................................................................................
1.6.1
Anmerkungen zur verwendeten Notation .....................................
24
25
1.7
C# Version 1 oder 2? .......................................................................................
25
1.8
Einrichten einer Entwicklungsumgebung ....................................................
25
Teil I Erste Schritte
27
2
Aufbau von C#-Programmen
29
2.1
Hello, World! ....................................................................................................
2.1.1
Der Projektmappen-Explorer ...........................................................
2.1.2
Das Eigenschaftsfenster ....................................................................
2.1.3
Die Fehlerliste ....................................................................................
2.1.4
Der Texteditor ....................................................................................
29
32
33
33
33
2.2
Kommentare ......................................................................................................
2.2.1
Kommentarblöcke .............................................................................
2.2.2
Zeilenkommentare ............................................................................
35
36
37
2.3
Syntax und Semantik .......................................................................................
38
2.4
Verwendete Syntaxschreibweise ...................................................................
38
2.5
Eine kurze Einführung zum Thema »Klassen« .............................................
2.5.1
Deklaration von Klassen ...................................................................
2.5.2
Der Einsprungspunkt ........................................................................
38
39
40
2.6
Zusammenhang zwischen Klassen- & Dateiname ......................................
40
2.7
Ausgaben auf dem Bildschirm .......................................................................
2.7.1
Namensräume ....................................................................................
2.7.2
Umbruch der Ausgabe von WriteLine() ..........................................
41
41
42
Inhalt
5
2.8
Assembly ............................................................................................................
42
2.9
Zusammenfassung ............................................................................................
2.9.1
Bestandteile eines C#-Programmes .................................................
2.9.2
Zusätzliches über das .NET Framework ..........................................
2.9.3
C# Sprachelemente ...........................................................................
44
44
45
45
2.10
Übungen ............................................................................................................
46
3
Konstanten, Variablen & Datentypen
3.1
Das EVA-Prinzip ................................................................................................
47
3.2
Variablen ............................................................................................................
3.2.1
Primitive Datentypen in C# ..............................................................
3.2.2
Datentypen für ganzzahlige Werte (Menge der ganzen
Zahlen) ................................................................................................
3.2.3
Datentypen für gebrochene Werte (Menge der rationalen
Zahlen) ................................................................................................
3.2.4
Wahrheitswerte .................................................................................
3.2.5
Zeichenketten ....................................................................................
3.2.6
Namenskonventionen für Variablen ................................................
3.2.7
Deklaration von Variablen ................................................................
3.2.8
Wertzuweisung und Initialisierung von Variablen .........................
3.2.9
Ausgabe von Variableninhalten .......................................................
3.2.10 Gültigkeit von Variablen ...................................................................
48
49
3.3
Felder ..................................................................................................................
3.3.1
Deklaration und Initialisierung .........................................................
3.3.2
Zugriff und Ausgabe von Arrays: [..], foreach ................................
3.3.3
Mehrdimensionale Arrays .................................................................
3.3.4
Unregelmäßige Arrays .......................................................................
3.3.5
Speicherbereinigung ..........................................................................
68
69
70
72
73
73
3.4
Parameter der Main-Funktion ........................................................................
74
3.5
Typqualifizierer .................................................................................................
3.5.1
static ....................................................................................................
3.5.2
const ....................................................................................................
75
76
76
3.6
Einlesen von Variablenwerten über die Tastatur ........................................
76
3.7
Zusammenfassung ............................................................................................
77
3.8
Übungen ............................................................................................................
79
4
Zusammengesetzte Datentypen und
Namensräume
4.1
6
Inhalt
Strukturen ..........................................................................................................
4.1.1
Deklaration einer Struktur ................................................................
4.1.2
Eine Struktur zur Aufnahme von Adressen ....................................
4.1.3
Werte- und Verweistypen ................................................................
4.1.4
Boxing und Unboxing ........................................................................
47
50
51
52
53
53
55
56
62
67
81
81
81
84
85
89
4.2
Aufzählungen (Enumerationen) .....................................................................
4.2.1
Deklaration einer Aufzählung ..........................................................
4.2.2
Enumerationen und Zahlwerte ........................................................
4.2.3
Basistyp einer Enumeration ..............................................................
89
91
91
93
4.3
Namensräume ...................................................................................................
4.3.1
Definition eines Namensraums ........................................................
4.3.2
Die using-Klausel ...............................................................................
94
94
96
4.4
Zusammenfassung ............................................................................................
4.4.1
Strukturen ...........................................................................................
4.4.2
Enumerationen ..................................................................................
4.4.3
Namespaces .......................................................................................
97
97
98
98
4.5
Übungen ............................................................................................................
99
5
Operatoren
101
5.1
Operatoren in C# ............................................................................................. 101
5.2
Additive und multiplikative Operatoren ...................................................... 103
5.2.1
Addition, Subtraktion, Multiplikation und Division ...................... 103
5.2.2
Division mit Rest (Division modulo x) ............................................ 104
5.3
Der Zuweisungsoperator ................................................................................ 105
5.4
Primäre Operatoren .........................................................................................
5.4.1
Klammerung "()" ................................................................................
5.4.2
Memberzugriff "." .............................................................................
5.4.3
Methodenaufruf ................................................................................
5.4.4
Array-Zugriff "[ ]" ...............................................................................
5.4.5
Post-Inkrement und Post-Dekrement .............................................
5.4.6
Anlegen von Objekten – Der new-Operator .................................
5.4.7
Typ und Größe einer Variablen ........................................................
5.4.8
Geprüfte und ungeprüfte Ausführung von Operationen .............
106
106
107
107
108
108
109
111
113
5.5
Unäre Operatoren ............................................................................................
5.5.1
Vorzeichen ..........................................................................................
5.5.2
Negationen .........................................................................................
5.5.3
Pre-Inkrement und Pre-Dekrement ................................................
5.5.4
Typumwandlung ................................................................................
116
116
116
117
118
5.6
Schiebe-Operatoren ........................................................................................ 119
5.7
Relationale und Vergleichs-Operatoren ....................................................... 120
5.7.1
Vergleichsoperatoren ........................................................................ 120
5.7.2
Die Operatoren is und as ................................................................. 121
5.8
Logisches UND, ODER und EXKLUSIV ODER (XOR) ................................ 122
5.9
Bedingtes UND, ODER und EXKLUSIV ODER ............................................ 123
5.10
Bedingung ......................................................................................................... 124
5.11
Zusammenfassung ............................................................................................ 125
5.12
Übungen ............................................................................................................ 125
Inhalt
7
6
Kontrollkonstrukte
6.1
Nassi-Shneiderman .......................................................................................... 127
6.2
Schleifen .............................................................................................................
6.2.1
for ........................................................................................................
6.2.2
while ....................................................................................................
6.2.3
do-while ..............................................................................................
6.2.4
foreach ................................................................................................
6.2.5
Steuerung der Schleifenabläufe: break & continue .......................
6.3
Bedingungen (bedingte Anweisungen) ........................................................ 139
6.3.1
if ........................................................................................................... 139
6.3.2
switch-case ......................................................................................... 144
6.4
goto ..................................................................................................................... 148
6.5
Unterstützung durch die Entwicklungsumgebung ..................................... 149
6.6
Zusammenfassung ............................................................................................ 149
6.7
Übungen ............................................................................................................ 150
Teil II Objektorientierte Programmierung
mit C#
7
7.1
8
127
Einführung in die objektorientierte
Programmierung
128
129
133
136
138
138
153
155
Klassen und Objekte ........................................................................................
7.1.1
Die Klasse als programmiersprachliche Beschreibung von
realen Objekten .................................................................................
7.1.2
Objektmethoden ...............................................................................
7.1.3
Eine spezielle Methode: Der Konstruktor ......................................
7.1.4
Die Vererbungslehre in der Programmiersprache .........................
7.1.5
Gleichheit von Objekten ...................................................................
7.1.6
Überladen von Methoden ................................................................
7.1.7
Überschreiben von Methode: Virtuelle Methoden .......................
155
155
163
172
177
188
188
191
7.2
Abstrakte Klassen ............................................................................................. 194
7.2.1
Abstrakte Methoden ......................................................................... 195
7.3
Eigenschaften (Properties) ..............................................................................
7.3.1
get ........................................................................................................
7.3.2
set ........................................................................................................
7.3.3
Unterschiedliche Sichtbarkeitsstufen für Getter und Setter .........
7.3.4
Benutzung von Properties ................................................................
7.4
Klassen auf mehrere Dateien verteilen: partielle Klassen ......................... 202
7.5
Ref- & Out-Parameter von Methoden .......................................................... 203
7.5.1
Ref-Parameter .................................................................................... 203
7.5.2
Out-Parameter ................................................................................... 204
Inhalt
196
198
199
200
201
7.6
Die Speicherverwaltung von .NET ................................................................ 205
7.6.1
Finalize() und C#-Destruktoren ....................................................... 206
7.6.2
Referenzen auf Objekte während Finalisierung ............................. 208
7.7
Zusammenfassung ............................................................................................
7.7.1
Klassen ................................................................................................
7.7.2
Properties ...........................................................................................
7.7.3
Methoden ...........................................................................................
7.8
Übungen ............................................................................................................ 209
8
Generische Klassen, Schnittstellen und
statische Klassenmitglieder
208
208
209
209
213
8.1
Klassen mit Typparametern: Generische Klassen – Generics ...................
8.1.1
Anwendung auf die Klasse Stack .....................................................
8.1.2
Instanzen der Klasse Stack erstellen ................................................
8.1.3
Typparameter und Vererbung ..........................................................
8.1.4
Generische Methoden ......................................................................
213
214
216
216
217
8.2
Schnittstellen ....................................................................................................
8.2.1
Deklaration einer Schnittstelle .........................................................
8.2.2
Implementierung einer Schnittstelle ...............................................
8.2.3
Schnittstellen und Typparameter ....................................................
8.2.4
Einschränkungen für Typparameter festlegen ................................
218
218
220
222
223
8.3
Klassenmember (statische Member) ............................................................
8.3.1
Statische Methoden ..........................................................................
8.3.2
Statische Daten ..................................................................................
8.3.3
Statische Eigenschaften .....................................................................
8.3.4
Statische Daten in nicht-statischen Methoden ..............................
8.3.5
Statische Klassen ................................................................................
8.3.6
Erzeugung von Objekten mit privaten Konstruktoren ..................
224
225
225
226
226
226
227
8.4
Objektorientierte Schmankerl5 in C# ...........................................................
8.4.1
Neuimplementierung einer Methode .............................................
8.4.2
sealed vor virtuellen Methoden .......................................................
8.4.3
Zugriff auf Implementierung der Basisklasse: base Teil II .............
8.4.4
Dispose(): Eine bessere Lösung als Finalize() .................................
228
228
233
233
236
8.5
Weitere Elemente der Unified Modelling Language ................................. 237
8.5.1
Schnittstellen ...................................................................................... 237
8.5.2
Assoziationen ..................................................................................... 238
8.6
Unterstützung durch die Entwicklungsumgebung ..................................... 239
8.7
Zusammenfassung ............................................................................................
8.7.1
Generische Klassen ............................................................................
8.7.2
Schnittstellen ......................................................................................
8.7.3
Klassenmember ..................................................................................
8.7.4
Methoden und die Vererbung .........................................................
8.8
Übungen ............................................................................................................ 242
241
241
241
242
242
Inhalt
9
10
9
Strings & reguläre Ausdrücke
245
9.1
Zeichenketten ...................................................................................................
9.1.1
string vs. String ...................................................................................
9.1.2
Länge von Zeichenketten ..................................................................
9.1.3
Iteration über eine Zeichenkette .....................................................
9.1.4
Vergleich zweier Zeichenketten .......................................................
9.1.5
Untersuchung von Zeichenketten ...................................................
9.1.6
Splitting von Strings ...........................................................................
9.1.7
Zurechtschneiden von Zeichenketten .............................................
9.1.8
Groß- und Kleinschreibung ..............................................................
9.1.9
Löschen und Ersetzen von Zeichen .................................................
9.1.10 Einfügen von Zeichen ........................................................................
9.1.11 Teilstrings herauslösen ......................................................................
9.1.12 Weitere Operationen mit Strings im Überblick .............................
9.1.13 Format() ..............................................................................................
245
245
246
246
247
250
251
252
253
253
254
254
255
255
9.2
Dynamische Zeichenketten – StringBuilder .................................................
9.2.1
Instantiierung eines StringBuilder-Objektes ...................................
9.2.2
Eigenschaften und Methoden ..........................................................
9.2.3
ToString() ............................................................................................
255
256
256
257
9.3
Reguläre Ausdrücke .........................................................................................
9.3.1
Regex: Eine Klasse für reguläre Ausdrücke .....................................
9.3.2
Grundlegender Aufbau eines regulären Ausdrucks .......................
9.3.3
Optionen .............................................................................................
259
259
260
262
9.4
Zusammenfassung ............................................................................................ 265
9.5
Übungen ............................................................................................................ 266
10
Ausnahmen – Exceptions
267
10.1
Der klassische Ansatz: Rückgabewerte ........................................................ 267
10.2
Exception-Mechanismus .................................................................................
10.2.1 Exceptions – Allgemein .....................................................................
10.2.2 Exceptions – Wie funktioniert's? .....................................................
10.2.3 Exceptions – Nachteile ......................................................................
10.2.4 Exceptions und .NET .........................................................................
269
269
269
270
271
10.3
Exceptions in C# ...............................................................................................
10.3.1 Das Werfen einer Exception ............................................................
10.3.2 Das Fangen einer Exception .............................................................
10.3.3 Member der Klasse Exception .........................................................
10.3.4 Exception-Klassen ..............................................................................
10.3.5 Eigene Exception-Klassen .................................................................
10.3.6 Aufräumarbeiten: finally ...................................................................
10.3.7 Verhaltensweisen beim Auftreten einer Ausnahme ......................
271
271
272
273
274
277
278
279
10.4
Zusammenfassung ............................................................................................ 281
10.5
Übungen ............................................................................................................ 282
Inhalt
11
Überladen von Operatoren
283
11.1
Unäre Operatoren ............................................................................................ 283
11.2
Binäre Operatoren ........................................................................................... 285
11.3
Vergleichsoperatoren ...................................................................................... 287
11.3.1 Equals() ............................................................................................... 287
11.3.2 GetHashCode() .................................................................................. 288
11.4
Einschränkungen .............................................................................................. 289
11.5
Zusammenfassung ............................................................................................ 289
11.6
Übungen ............................................................................................................ 290
12
Delegates und Ereignisse
12.1
Delegates ...........................................................................................................
12.1.1 Beispiel: Motorüberwachung ...........................................................
12.1.2 Die Deklaration eines Delegates ......................................................
12.1.3 Die Verwendung von Delegates ......................................................
12.1.4 Erstellen eines Delegates ..................................................................
12.1.5 Multicast-Delegates ..........................................................................
12.1.6 Callback-Methoden und Ausnahmen .............................................
12.1.7 Delegates und Rückgabewerte ........................................................
291
291
292
293
295
297
301
301
12.2
Ereignisse ...........................................................................................................
12.2.1 Delegate vs. Ereignis .........................................................................
12.2.2 Ereignisse: Hintergrund ....................................................................
12.2.3 Deklaration eines Ereignisses ...........................................................
12.2.4 Hinweise .............................................................................................
12.2.5 Ereignisse und Rückgabewerte ........................................................
302
302
305
305
307
309
12.3
Generische Delegates ...................................................................................... 309
12.4
Anonyme Methoden ........................................................................................ 309
12.5
Leichtere Erzeugung von Delegate-Instanzen ............................................. 310
12.6
Unterstützung durch die Entwicklungsumgebung ..................................... 311
12.7
Zusammenfassung ............................................................................................ 312
12.8
Übungen ............................................................................................................ 313
13
Indizierer, Enumeratoren und Collections
13.1
Indizierer ............................................................................................................
13.1.1 Allgemeines über Indizierer .............................................................
13.1.2 Deklaration eines Indizierers ............................................................
13.1.3 Die Verwendung eines Indizierers ...................................................
13.1.4 Indizierer mit mehreren Parametern ...............................................
291
315
315
315
315
318
320
Inhalt
11
13.2
13.3
12
Enumeratoren ....................................................................................................
13.2.1 IEnumerable und IEnumerator .........................................................
13.2.2 Ein Enumerator für BitVector64 ......................................................
13.2.3 Erweiterungen für die Unterstützung des Enumerators an
BitVector64 ........................................................................................
13.2.4 Verwendung des Enumerators .........................................................
13.2.5 Zur Perfektion fehlt noch etwas ......................................................
13.2.6 Noch einmal foreach .........................................................................
321
321
323
Collections in .NET ...........................................................................................
13.3.1 Vorwort zu Collections für .NET ab Version 2.0 ...........................
13.3.2 ArrayList ..............................................................................................
13.3.3 Queue .................................................................................................
13.3.4 Stack ....................................................................................................
13.3.5 Hashtable ............................................................................................
330
331
331
333
335
337
326
327
328
330
13.4
Zusammenfassung ............................................................................................ 338
13.5
Übungen ............................................................................................................ 339
14
Attribute und Metadaten
14.1
Attribute ............................................................................................................
14.1.1 Attribute im Code platzieren ...........................................................
14.1.2 Reservierte Attribute .........................................................................
14.1.3 Globale Attribute ...............................................................................
14.1.4 Eigene Attribute entwickeln .............................................................
14.1.5 Ziele für Attribute ..............................................................................
341
342
342
349
349
352
14.2
Metadaten .........................................................................................................
14.2.1 Typinformationen zur Laufzeit ermitteln ........................................
14.2.2 Attribute auslesen ..............................................................................
14.2.3 Ausblick ...............................................................................................
353
354
362
364
341
14.3
Zusammenfassung ............................................................................................ 364
14.4
Übungen ............................................................................................................ 365
15
XML-Dokumentation und Präprozessor
15.1
XML-Dokumentation .......................................................................................
15.1.1 XML als Datenformat ........................................................................
15.1.2 Eine weitere Form des Kommentars: /// .....................................
15.1.3 Dokumentationstags .........................................................................
15.1.4 Textauszeichnung und Verweise ......................................................
15.2
Präprozessor ...................................................................................................... 381
15.2.1 Ein- und Ausblenden von Code ....................................................... 381
15.2.2 Weitere Direktiven ............................................................................ 384
367
367
367
371
372
378
15.3
Zusammenfassung ............................................................................................ 386
15.4
Übungen ............................................................................................................ 386
Inhalt
16
Threading
389
16.1
Betriebssystemhintergrund: Prozesse und Threads ...................................
16.1.1 Programme: Historie .........................................................................
16.1.2 Prozesse und Threads ........................................................................
16.1.3 Parallelität durch den Einsatz von Threads .....................................
389
389
391
392
16.2
Threads in C# ....................................................................................................
16.2.1 Thread-Erzeugung .............................................................................
16.2.2 Auch Threads brauchen ihren Schlaf ...............................................
16.2.3 Suspendieren von außen ..................................................................
16.2.4 Abbruch eines Threads .....................................................................
16.2.5 Warten auf das Ende eines Threads ................................................
16.2.6 Aktueller Thread-Zustand .................................................................
16.2.7 Thread-Prioritäten .............................................................................
16.2.8 Name eines Threads ..........................................................................
16.2.9 Zusammenfassung der wichtigsten Methoden und
Properties der Klasse Thread ............................................................
16.2.10 Übergabe und Rückgabe von Daten ...............................................
392
392
396
400
402
404
404
405
407
16.3
Synchronisierung ..............................................................................................
16.3.1 Die Gefahr von nicht synchronisierten Threads ............................
16.3.2 Die Klasse Monitor ............................................................................
16.3.3 Ein Monitor in C# ..............................................................................
408
408
410
412
16.4
Asynchrone Methodenaufrufe .......................................................................
16.4.1 BeginInvoke() und EndInvoke() .......................................................
16.4.2 Methodenaufruf ohne Parameter und Rückgabewert ..................
16.4.3 Methodenaufruf mit Parametern und Rückgabewert ...................
414
414
415
417
16.5
Zusammenfassung ............................................................................................ 420
16.6
Übungen ............................................................................................................ 421
407
408
Teil III Windows Forms
423
17
Einführung
425
17.1
Die Definition und Funktionsweise eines Fensters ................................... 425
17.2
Die Windows Forms-Bibliothek ....................................................................
17.2.1 Der Namensraum ..............................................................................
17.2.2 Fenster vs. Formulare .......................................................................
17.2.3 Grundgerüst für Windows Forms-Programme ..............................
427
427
427
428
17.3
Die Klasse Form im Detail ..............................................................................
17.3.1 Hinweise zur Online-Hilfe ................................................................
17.3.2 Wichtige Eigenschaften der Klasse Form .......................................
17.3.3 Wichtige Methoden der Klasse Form .............................................
17.3.4 Wichtige Ereignisse der Klasse Form ..............................................
430
430
430
437
441
Inhalt
13
17.4
17.5
14
Einfügen von Elementen in ein Fenster ........................................................
17.4.1 Einfügen einer Schaltfläche in ein Fenster ......................................
17.4.2 Das Koordinatensystem von Windows Forms ...............................
17.4.3 Die Positionierung von Elementen in einem Fenster mit
Hilfe der Dock- und Anchor-Eigenschaft ........................................
17.4.4 Das Baukastensystem von Windows Forms ...................................
Dialoge
17.5.1
17.5.2
17.5.3
447
448
450
451
455
............................................................................................................... 456
Dialoge mit ShowDialog() anzeigen ................................................ 456
Vorgefertigte Dialoge ........................................................................ 458
Die Klasse MessageBox ..................................................................... 458
460
460
462
465
17.6
Unterstützung durch die Entwicklungsumgebung .....................................
17.6.1 Ein neues Projekt anlegen ................................................................
17.6.2 Hinzufügen und Parametrieren eines Steuerelements ..................
17.6.3 Automatisch generierter Code .........................................................
17.7
Zusammenfassung ............................................................................................ 468
17.8
Übungen ............................................................................................................ 468
18
Steuerelemente
18.1
Die Basisklasse Control ...................................................................................
18.1.1 Eigenschaften .....................................................................................
18.1.2 Methoden ...........................................................................................
18.1.3 Ereignisse ............................................................................................
471
471
475
477
18.2
Containersteuerelemente ................................................................................
18.2.1 Formulare – Fenster ..........................................................................
18.2.2 Panels – Platten ..................................................................................
18.2.3 FlowLayoutPanel und TableLayoutPanel ........................................
18.2.4 SplitContainer ....................................................................................
18.2.5 GroupBox ............................................................................................
18.2.6 TabControl ..........................................................................................
18.2.7 Anmerkungen zum Umgang mit Containersteuerelementen ......
478
479
479
480
482
484
484
484
18.3
Schaltflächen, Checkboxen und Radiobuttons ............................................
18.3.1 Schaltflächen – Buttons .....................................................................
18.3.2 Optionsfelder Teil 1: Checkboxen ...................................................
18.3.3 Optionsfelder Teil 2: Radiobuttons .................................................
485
485
488
489
18.4
Textfelder ...........................................................................................................
18.4.1 Einfache Textein-/ausgaben: TextBox .............................................
18.4.2 Formatierte Eingaben: MaskedTextBox ..........................................
18.4.3 Dokumente mit Formatierungen: RichTextBox .............................
490
490
490
491
18.5
Beschriftungen .................................................................................................. 492
18.6
Bilder .................................................................................................................. 494
18.7
Listen und Tabellen ..........................................................................................
18.7.1 Einfache Listen: ListBox und CheckedListBox ................................
18.7.2 Aufklappbare Listen: ComboBox .....................................................
18.7.3 Listen und Tabellen: ListView ..........................................................
18.7.4 Bäume: TreeView ...............................................................................
Inhalt
471
494
495
495
496
497
18.8
Menüs, Symbolleisten und Kontextmenüs ..................................................
18.8.1 Menüs .................................................................................................
18.8.2 Symbolleisten .....................................................................................
18.8.3 Kontextmenüs ....................................................................................
497
497
499
500
18.9
Sonstige Steuerelemente ................................................................................
18.9.1 Darstellung von Objekteigenschaften: PropertyGrid ....................
18.9.2 Zeitlich wiederkehrende Aufgaben auslösen .................................
18.9.3 Fortschrittsanzeige und Schiebebalken: ProgressBar
und TrackBar ......................................................................................
500
500
501
502
18.10 Zusammenfassung ............................................................................................ 503
18.11 Übungen ............................................................................................................ 504
19
Grafik und Drucken in Windows FormsAnwendungen
505
19.1
Grafik – GDI+ ....................................................................................................
19.1.1 Einfache, grafische Ausgaben: Das Graphics-Objekt ....................
19.1.2 Der Garbage Collector und das Graphics-Objekt ..........................
19.1.3 Farben .................................................................................................
19.1.4 Stifte und Pinsel .................................................................................
19.1.5 Koordinaten und Punkte ..................................................................
19.1.6 Rechtecke ...........................................................................................
19.1.7 Größe des Zeichenbereichs ermitteln .............................................
19.1.8 Methoden zum Zeichnen .................................................................
19.1.9 Methoden zum Füllen von Flächen ................................................
19.1.10 Fehler in der grafischen Ausgabe ....................................................
19.1.11 Ausgabe einzelner Bildpunkte .........................................................
19.1.12 Grafische Pfade ..................................................................................
19.1.13 Regionen .............................................................................................
19.1.14 Erzeugen von Graphics-Objekten ...................................................
505
505
506
507
510
515
515
516
516
531
534
534
535
537
537
19.2
Icons und Bilder ............................................................................................... 537
19.2.1 Icons .................................................................................................... 538
19.2.2 Bilder ................................................................................................... 539
19.3
Drucken ..............................................................................................................
19.3.1 Ereignisse der Klasse PrintDocument .............................................
19.3.2 Eigenschaften der Klasse PrintDialog ..............................................
19.3.3 Erstellen einer Druckvorschau .........................................................
19.4
Unterstützung durch die Entwicklungsumgebung ..................................... 552
542
545
546
548
19.5
Zusammenfassung ............................................................................................ 552
19.6
Übungen ............................................................................................................ 553
20
Bibliotheken und CodeDOM
20.1
Bibliotheken ...................................................................................................... 555
20.1.1 Statische und dynamische Bibliotheken ......................................... 555
555
Inhalt
15
20.1.2
20.1.3
20.1.4
Unterschied zwischen einem Programm und einer Bibliothek .... 556
Ein einfaches Beispiel ........................................................................ 556
Projekte mit mehreren Code-Dateien ............................................ 559
CodeDOM .........................................................................................................
20.2.1 Ein Simulator für ein Makro .............................................................
20.2.2 Die Oberfläche der Anwendung ......................................................
20.2.3 Code zur Laufzeit erzeugen ..............................................................
20.2.4 Vorbemerkungen ...............................................................................
20.2.5 Die Methode OnClickedStart() ........................................................
20.3
Zusammenfassung ............................................................................................ 567
20.4
Übungen ............................................................................................................ 568
A
Fehlersuche in Programmen
A.1
Trace-Ausgaben ................................................................................................ 569
A.2
Der Debugger von Visual C# 2005 Express .................................................
A.2.1
Programme im Debugger starten ....................................................
A.2.2
Variablenwerte anzeigen ...................................................................
A.2.3
Weitere Debug-Fenster ....................................................................
B
Hilfequellen
569
571
571
574
576
579
B.1
Online-Hilfe ....................................................................................................... 579
B.2
Quickstart Tutorials ......................................................................................... 580
B.3
Webseiten .......................................................................................................... 581
B.4
Bücher ................................................................................................................ 582
Index
16
560
561
562
562
562
563
20.2
Inhalt
583
1
2
1
Einführung
3
»C# is a modern, object-oriented language that enables programmers to
quickly and easily build solutions for the Microsoft .NET platform.«
– Microsoft
4
5
6
»Warum eine neue Programmiersprache?« oder »Warum C#?«1: So oder ähnlich lautet vielleicht die Frage, die Sie sich gestellt haben, als Sie in den Buchladen gegangen
sind, um ein passendes Buch zu suchen. Auf diese beiden Fragen möchte ich zunächst
in diesem ersten Kapitel eingehen.
7
8
1.1
.NET
9
Bevor man sich auf die Suche nach den Gründen für die Einführung von C# begibt,
ist der Hintergrund zu betrachten, vor dem C# steht: .NET (gesprochen als DotNet).
10
Unter dem Schlagwort .NET (Microsoft selbst bezeichnet es auch gerne als Strategie)
verbirgt sich ein Modell, das damit wirbt, die Erstellung von Programmen (Anwendungen, Applikationen) für die Windows-Betriebssystemfamilie zu erleichtern. C#
wird dann als die Programmiersprache für dieses neue Programmiermodell bezeichnet: Dies lässt sich im Übrigen auch nicht abstreiten, wenn man berücksichtigt, dass
.NET selbst in großen Teilen mit Hilfe der Programmiersprache C# erstellt wurde.
11
12
13
.NET führt eine Menge neuer Sachen ein, die auch in der Tat die Programmierung
erleichtern:
14
왘 Garbage Collection2: Das System verwaltet den Arbeitsspeicher, so dass durch den
Programmierer reservierter Speicher automatisch freigegeben wird, sobald dieser
nicht mehr benötigt wird.
15
왘 Sicherheitskonzepte: Programme werden unter der Aufsicht von .NET ausgeführt.
16
Damit ist es möglich, Programmen nur bestimmte Rechte bei der Ausführung zuzuweisen.3
17
왘 .NET umfasst die Möglichkeit, viele Programmiersprachen gleichzeitig für ein Pro-
jekt zu verwenden, ohne dass durch den Programmierer zusätzliche Arbeiten verrichtet werden müssen. So kann stets die Sprache ausgewählt und verwendet werden, die für den jeweiligen Sachverhalt am besten geeignet ist. Als Konsequenz
daraus stehen nun allen Entwicklern dieselben Bibliotheken4 zur Erstellung von
Programmen zur Verfügung.
18
19
20
1 C# wird als C sharp gesprochen (engl.)
2 Garbage Collection kann man zwar als »Müllsammlung« ins Deutsche übersetzen, jedoch
empfiehlt es sich, den englischen Fachausdruck zu verwenden, da dieser normalerweise gebraucht wird.
3 Programme, die unter der Aufsicht von .NET ausgeführt werden, bezeichnet man auch als
managed (engl. verwaltet).
4 Als Bibliothek bezeichnet man eine Ansammlung von nützlichen Algorithmen oder auch
Steuerelementen (Schaltflächen, Fenster, Menüs ... )
Einführung
A
B
17
왘 Aus dem vorangehenden Punkt folgt unmittelbar, dass alle Sprachen unter .NET
dasselbe Objektmodell unterstützen müssen. Dazu gehört Vererbung und Polymorphie genauso wie ein Vater-Objekt für alle weiteren.
왘 Neben einem einheitlichen Objektmodell steht mit .NET auch endlich ein einheit-
liches Typsystem zur Nutzung bereit: Außerhalb von .NET existieren beispielsweise viele unterschiedliche Darstellungweisen und Typen für Zeichenfolgen
(»Text«): BSTR, WCHAR, char*, OLESTR, OLECHAR usw. Jede Programmiersprache
bzw. jedes Programmiermodell verwendet dabei normalerweise ihren Favoriten
aus dieser Menge. So müssen dann folglich diese Darstellungen laufend untereinander hin- und herkonvertiert werden.
왘 Einheitliches Fehlerkonzept: In .NET werden ausnahmslos Exceptions5 dazu ver-
wendet, um kritische Programmfehler anzuzeigen. In der Vergangenheit basierte
die Schnittstelle zwischen Betriebssystem und Programmen auf unterschiedlichen
Fehlerkonzepten.
왘 Allgemein hoher Abstraktionsgrad: Der Programmierer wird weiter von den In-
terna des Systems abgeschottet. Dies hat den Vorteil, dass Programme wenig vom
darunter liegenden System (Hardware, aber auch Software) abhängen. Damit sind
z.B. C#-Programme auf all den Systemen lauffähig, auf denen es .NET gibt. Mittlerweile gibt es auch Versionen von .NET für kleine Geräte wie z.B. PDAs. Auch versucht gerade eine Programmiergemeinde, .NET auf Linux zu portieren. Man findet
dieses Projekt unter dem Stichwort Mono im Internet. Trotzdem sollte man nicht
zu fest daran glauben, eine Anwendung schreiben zu können, die dann auf allen
Plattformen gleich abläuft: Dies hängt vielmehr davon ab, inwieweit die jeweilige
zu Grunde liegende Hard- und Software die Programme ausführen kann. So werden Sie sicher auf einem Handy keine großartigen Graphikausgaben erwarten können. Im Fall von Mono hängt dies auch davon ab, ob es gelingt, die doch sehr windowsspezifischen Features auf Linux abzubilden.
왘 Webservices: Dieses neue Modewort wird gerne im Zusammenhang mit auf Netz-
werken basierenden Programmen verwendet. Unter einem Webservice versteht
man das Angebot eines Dienstes (z.B. Suche in einer Datenbank) über das Internet.
Microsoft sieht dabei Webservices vor allem in Bezug auf bezahlte Dienste, am
besten mit dem hauseigenen Identifikationssystem Passport. Man sollte diese
Technologie allerdings auch ein wenig kritisch sehen: Webservices sind vor allem
für Firmen interessant, die damit Dienstleistungen Dritter integrieren. Auch darf
nicht der Fehler gemacht werden zu glauben, dass Webservices nur mit .NET erstellt werden können: Web-Diensten liegt allgemein ein offenes und frei verfügbares Protokoll6 zu Grunde, so dass diese mittlerweile auch mit Hilfsmitteln realisiert
werden können, die nicht aus dem Hause Microsoft kommen.
왘 Anbindung an die »alte« Programmierwelt vor .NET ist möglich. So kann Pro-
grammcode, der vor .NET geschrieben wurde, weiter verwendet werden.
5 Exception (engl. Ausnahme): ein besonderer Programmzustand, in dem eine Fortsetzung eines Teilabschnittes in einem Programm nicht mehr möglich ist.
6 Das Protokoll beschreibt, wie sich Dienstanbieter und Dienstkonsument über das Netzwerk
»unterhalten«.
18
Einführung
1
2
All diese Punkte und nicht die Propaganda von Microsoft machen .NET zu dem Standard für neue Programme. Man sollte aber auch hier .NET nicht überbewerten: Es
wird auch mit .NET noch längere Zeit Programme geben, die ohne .NET laufen werden! So sind z.B. Treiber für Hardware-Geräte ein Fall für »herkömmliche« Programmierung. Denn auch die Ausführung von Programmen unter der Aufsicht von .NET
hat ihren Preis: Erstens kann, begründet durch den hohen Abstraktionsgrad, nur auf
Umwegen direkt auf die Hardware zugegriffen werden, und zweitens kostet auch die
Kontrolle durch ein zusätzliches System einfach Zeit. Sicher ist diese Zeit äußerst
gering, doch gibt es Fälle, in denen es auf fast jede Millisekunde ankommt. Oder können Sie sich vorstellen, dass die Steuerung eines Atomkraftwerkes für einige Zeit
angehalten wird, da das System nach nicht mehr benötigtem Speicher sucht? Ich
würde mich an dieser Stelle unauffällig in Richtung Ausgang begeben ...
3
4
5
6
7
8
.NET wird sich für die Masse durchsetzen. Aber es wird auch weiterhin spezielle
Anwendungsfälle geben, in denen man .NET nicht einsetzen wird.
9
10
Da .NET nicht nur Aufsicht für Programme ist, sondern auch gleichzeitig Mittel zur
Programmentwicklung bereitstellt, trifft man auch häufig die Bezeichnung .NET Framework7 an.
11
Ich möchte noch kurz auf wichtige Neuerungen eingehen, die .NET mitbringt.
12
1.1.1
13
Verwendung mehrerer Programmiersprachen für ein Projekt
Programmiert man eine Anwendung, die nicht für .NET gedacht ist, so wird diese
normalerweise in den so genannten Maschinencode übersetzt. Der Maschinencode
ist eine spezielle »Sprache«, die auf den Prozessor zugeschnitten ist, für den das Programm entwickelt wird. Hinzu kommt weiterhin eine sehr große Abhängigkeit vom
darunter liegenden Betriebssystem.
14
15
16
Wurde ein Programm daher z.B. für die x86-Prozessoren entwickelt (Intel, AMD), so
bedeutet dies noch lange nicht, dass das Programm sowohl unter Windows als auch
z.B. unter Linux lauffähig ist.
17
Hinzu kommt, dass eine Vielzahl von Programmiersprachen existiert, deren jeweilige
Stärken man gerne ausnutzen möchte. Die Verwendung unterschiedlicher Sprachen
ist auf Basis des Maschinencodes oder einer anderen binären Form allerdings äußerst
schwierig und nur mit zusätzlichen Hilfsmitteln möglich. Solche Hilfsmittel sind z.B.
zusätzliche Dateien, die Elemente näher spezifizieren. Diese werden dann zumeist
wiederum von einem weiteren Programm verarbeitet, das diese Daten dann für die
jeweilige Sprache aufbereitet usw.
18
19
20
A
Auf alle Fälle sind Projekte, die unterschiedliche Sprachen einsetzen, schwer unter
ein gemeinsames Dach zu bringen. Denn auch zusätzliche Beschreibungsdateien bringen ein gewaltiges Fehlerpotential mit sich.
B
7 Framework (engl. Rahmen)
.NET
19
Unter .NET ist dies anders: .NET-Programme liegen nicht im Maschinencode vor,
sondern in der so genannten Intermediate Language8 oder kurz IL. Durch diese
gemeinsame Codebasis und der Tatsache, dass zu diesem Code zusätzliche Informationen angegeben werden können, ist es möglich, Sprachen zu mischen: Sowohl die
Übersetzung von der jeweiligen Sprache in IL als auch das Gewinnen von Informationen, wie der Aufruf einzelner Elemente vonstatten gehen muss (die sich in der IL
befinden), können von den .NET-Sprachen verarbeitet werden.
Für den Programmierer bringt dies einen gewaltigen Vorteil mit sich: Er kann Elemente, die in C# geschrieben sind, ohne Zutun eines einzigen externen Tools oder
einer einzigen zusätzlichen Datei, z.B. in Visual Basic oder einer anderen .NET-Sprache verwenden und umgekehrt.
Das heißt aber auch, dass alle .NET-Sprachen dieselben Mittel von .NET zur Verfügung gestellt bekommen, um Programme zu erstellen. Jede Programmiersprache, die
nicht mit .NET arbeitet, bringt dagegen ihre eigenen Bibliotheken für die Programmerstellung mit – oftmals sogar noch abhängig vom Betriebssystem.
Wie aber werden dann .NET-Programme ausgeführt? Nachdem diese nicht in einer
für den Prozessor ausführbaren Form vorliegen, müssen sie während des Programmablaufs (= Laufzeit) in die ausführbare Form gebracht werden. Dies übernimmt ein
spezieller Teil von .NET: Dieser übersetzt den IL-Code bei Bedarf automatisch in
Maschinencode unter Berücksichtigung des Betriebssystems. Man nennt dieses Übersetzungsprogramm Just-In-Time Compiler oder kurz JIT Compiler.
Und tatsächlich enthalten ausführbare .NET-Dateien einen zusätzlichen Code-Teil, der
nur dazu dient, die Laufzeitumgebung von .NET zu starten, die dann wiederum den
JIT Compiler anstößt.
Spinnt man diesen Gedanken weiter, so ergibt sich dadurch eine großartige Möglichkeit: Ein .NET Programm läuft auf allen erdenklichen Plattformen9, egal ob Linux,
Windows oder Kleinstgeräte. Dies ist möglich, solange auf diesen Geräten .NET läuft,
d.h. ein JIT Compiler für diese Geräte existiert.
So gibt es z.B. für Kleinstgeräte bereits heute von Microsoft eine Umsetzung von
.NET, den so genannten Compact Framework, und für Linux ist ebenfalls, dieses mal
nicht von Microsoft, eine Umsetzung in Arbeit. Diese kursiert im Internet unter dem
Stichwort Mono.
Aber auch hier ist nicht alles Gold, was glänzt: Es bleibt abzuwarten, in wieweit die
Portierung (= Umsetzung) von .NET auf Linux gelingt. .NET basiert in der Originalversion auf dem Microsoft-Betriebssystem Windows. Eine interessante Fragestellung
bleibt daher, ob Mono diese Bindung durchbrechen kann. Fest steht schon heute,
dass einige für Windows spezifische Sachen nur sehr schwer, wenn überhaupt, Einzug in Mono halten werden können. Dazu gehört z.B. das Thema COM Interop10.
8
9
engl. Zwischensprache
Unter einer Plattform versteht man ein Gerät (Hardware) in Verbindung mit dem darauf
laufenden Betriebssystem (Software).
10 Die Abkürzung COM steht für Component Object Model.
20
Einführung
1
2
COM Interop ist eine Technik, die so genannten COM-Komponenten aus der Zeit vor
.NET in die neue Programmierphilosophie von .NET einzubinden. COM selbst basiert
auf Windows, und eine Linux-Umsetzung dafür existiert nicht.
3
4
Im Übrigen ist die Nutzung einer gemeinsamen Code-Basis nichts Neues in der Programmierwelt: Die Programmiersprache Java setzt eine andere Form einer IL ein, die
Java Byte-Code genannt wird.
5
Dieser Code wird von einer virtuellen Maschine11 abgearbeitet. Java arbeitet dabei
mit denselben Methodiken wie auch .NET, um die Zwischensprache in Maschinencode zu überführen.
6
7
Ein großer Vorteil von Java ist die gute Portierbarkeit: Java-Programme, die unter
Windows geschrieben wurden, laufen auch auf jedem anderen System, für das Java
verfügbar ist.
8
Ein Nachteil von Java ist die etwas geringere Performance: Ich habe keine Messungen
angestellt, jedoch hält sich der dringende, subjektive Verdacht, dass Java-Anwendungen langsamer laufen – zumindest auf meinem Windows-System. Man sollte sich
aber nicht auf die Schiene »Java ist generell langsam« begeben: Es gibt auch hoch performante Java-Systeme für spezielle Anwendungen. Besonders im Bereich der
Kleinstsysteme findet man häufig Java-Plattformen: Denken Sie nur einmal an die bis
zur Schmerzgrenze im TV beworbenen Spiele für Handys.
9
10
11
12
Auch die Bedeutung von Java für das Internet trägt zur Popularität dieser Sprache bei:
Vielleicht verwenden Sie auch ein Java-Applet, wenn Sie sich bei Ihrer Bank zum
Online-Banking einloggen ...
1.1.2
13
14
Garbage Collection und Sicherheit
Ein weiterer wichtiger Pluspunkt von .NET ist die gesteigerte Sicherheit von Programmen: Über die Ausführungseinheit für .NET-Programme wird ein Programm laufend überwacht. So kann der Zugriff auf bestimmte Ressourcen eingeschränkt oder
sogar verboten werden.
15
Diese Kontrolle ermöglicht auch eine Überwachung des benötigten Speichers durch
das System. So kann es feststellen, ob Speicher von einem Programm nicht mehr
benötigt wird, und diesen dann gezielt freigeben und somit weiteren Programmen
oder derselben Applikation diesen wieder zur Verfügung stellen.
17
Diese »Müllsammlung«12 muss äußerst effizient gestaltet sein – und sie ist es auch.
Die Algorithmen, die man für die Suche nach nicht mehr benötigtem Speicher einsetzt, sind sehr schnell. Damit ist ein großes Problem der Welt außerhalb von .NET
beseitigt: Speicherlecks (Memory Leaks). Oftmals hat man nämlich das Freigeben von
19
16
18
20
A
11 Unter einer virtuellen Maschine kann man sich am besten einen »PC im PC« vorstellen: Mit
Hilfe von Software wird dort ein richtiger Rechner simuliert. Dieser führt dann den ByteCode aus. Virtuelle Maschinen haben den Vorteil, dass sie nach außen hin komplett abgeschirmt werden können und Programme so isoliert ablaufen und keinen Schaden anrichten
können (z.B. Viren).
12 Deutsche Übersetzung von Garbage Collection.
.NET
B
21
Speicher kurzerhand vergessen oder aus Unwissenheit über die Funktion eines Programmteils einfach weggelassen. Dieser Speicher war damit für die Nutzung durch
das Programm faktisch tot: Er kann nicht benutzt werden, da keine Verweise mehr
auf den Speicher im Programm existieren, und nicht neu belegt werden, da das System ihn immer noch als »belegt« markiert hat. Erst bei Programmende fand eine
Bereinigung des Speichers statt. Sie kennen sicherlich die Geschichtchen: Ein Programm läuft mehrere Tage und stürzt dann mit einer Fehlermeldung ab, dass nicht
mehr genügend Arbeitsspeicher vorhanden sein. Natürlich nicht, ohne vorher den
PC selbst gewaltig in seiner Geschwindigkeit zu drosseln.
Aber auch diese Tatsache ist kritisch zu betrachten: Für Anwendungen, die unter
Echtzeitbedingungen laufen (z.B. Steuerung von Produktionsstraßen) ist eine Garbage Collection Gift. Solange nämlich das System nach freizugebendem Speicher
sucht, bleibt das Programm stehen und damit auch die Reaktion auf eingehende
Ereignisse.
1.2
C#
Warum aber nun C# oder besser, warum eine neue Programmiersprache, es gibt
doch bereits eine Vielzahl? Diese Frage lässt sich einfach beantworten: Programmiersprachen sind für den Entwickler das, was Werkzeuge für den Handwerker sind: ein
Mittel zum Zweck.
Werkzeuge sind auf einen speziellen Fall zugeschnitten: Ein Hammer wird z.B. dazu
verwendet, Nägel in die Wand zu schlagen. Man kann zwar auch eine Säge dafür verwenden, jedoch liegt der Zeitaufwand oder allgemein die Kosten weitaus höher als
bei Verwendung des Hammers.
Genauso verhält es sich mit Programmiersprachen: Wollen Sie eine Tabellenkalkulation entwickeln, die über eine graphische Bedienoberfläche (Fenster, Menü, Buttons
...) verfügt, so greifen Sie sicher nicht auf die Maschinensprache (Nullen und Einsen)
zurück, sondern auf eine Hochsprache wie z.B. C#. C# ist wiederum nun die Sprache,
die von Microsoft für .NET entwickelt wurde. Andere wie Visual Basic oder Visual
C++ (ebenfalls aus dem Hause Microsoft) wurden lediglich so angepasst, dass sie für
.NET verwendet werden können. Insbesondere die Fan-Gemeinde von Visual Basic
hat bei so mancher Neuerung in der Version von Visual Basic für .NET entsetzt aufgeschrien.
So unterstützt bereits die Syntax13 von C# Techniken aus .NET. Das heißt,. wo Sie in
anderen Programmiersprachen eine Unmenge von Code schreiben müssten, ist dies
in C# meist mit wenigen Zeilen oder gar Zeichen erledigt. Ganz zu schweigen vom
Fehlerpotential, das hierdurch erheblich verringert wird.
Wenn Sie also C# lernen, haben Sie die besten Aussichten, effektiv unter .NET zu programmieren. Wollen Sie lieber Treiber entwickeln, sollten Sie Ihren Buchhändler bitten, dieses Exemplar gegen ein anderes Werk umzutauschen.
13 Syntax beschreibt den Aufbau eines Programms.
22
Einführung
1
2
Nachdem Sie dieses Buch studiert haben, werden Sie in der Lage sein, einfache Programme mit einer grafischen Oberfläche für .NET in C# zu entwickeln. Die dem Buch
beiliegende CD bringt dabei alles mit, was Sie hierfür brauchen: die Entwicklungsumgebung Visual C# 2005 Express von Microsoft für Windows.
1.3
3
4
Zielgruppe
5
Dieses Buch richtet sich an all diejenigen, die ernsthaft das Programmieren für Windows (und .NET) erlernen wollen und zuvor noch keine andere Programmiersprache
genutzt haben. Dieses Buch beginnt mit den Grundlagen der strukturierten Programmierung und geht erst dann auf die objektorientierte Programmierung ein, so dass
sich Umsteiger von anderen Programmiersprachen unter Umständen schnell in den
ersten Seiten verlieren werden.
6
7
8
Auch wenn das Buch für Programmiereinsteiger konzipiert ist, gibt es einige wenige
Grundvoraussetzungen, die Sie erfüllen sollten, ehe Sie sich mit diesem Buch beschäftigen. Dies sind:
9
10
왘 Sie müssen über Kenntnisse in der Bedienung Ihres Windows-Systems (Datei Ex-
plorer, Installation von Programmen) verfügen.
11
왘 Rudimentäre Englisch-Kenntnisse sind notwendig: Die Sprache der Informatik ist
weder C# noch Visual Basic oder eine andere Programmiersprache, sondern Englisch. Dies schlägt sich besonders in den Fachausdrücken nieder. Manche deutsche
Übersetzungen erfreuen sich zwar einer großen Beliebtheit, andere aber nicht:
Garbage Collector = Müllsammler ist so ein Negativbeispiel.
12
13
Sie müssen keine Angst davor haben, mit Fachausdrücken bombardiert zu werden,
die Sie nicht verstehen. Ich verwende viele Fachausdrücke, diese werden aber stets
bei ihrer ersten Verwendung erklärt oder umschrieben. Der Fachausdruck kann
Ihnen als Hilfe für eine Recherche in zusätzlicher Dokumentation dienen. Ebenso
werden englische Begriffe stets erläutert.
1.4
14
15
16
Danksagungen
17
Ohne den Rat und die Unterstützung einer Reihe von Leuten wäre dieses Buch sicher
nie fertig geworden, geschweige denn überhaupt entstanden. Bei allen Personen, die
mich bewusst oder unbewusst unterstützt haben, möchte ich mich daher auf das
Herzlichste bedanken. Im Einzelnen sind dies:
18
19
Peter Loos, Marcus Bürgel und Jürgen Haardörfer, die mich vor allem in fachlichen
Themen beraten haben und die ich mit so mancher Frage strapaziert habe.
20
Pascal Eversberg, Marc Düvel, Marcus Meyerhöfer, Michael Daum, Andreas Waigel
und Dagmar Metschl haben darüber gewacht, dass sich mein fränkischer Dialekt
nicht allzu stark auf die Rohfassung des Manuskriptes auswirkte, und mussten so
manche Wortneuschöpfung über sich ergehen lassen.
A
B
Markus Friedel für seine Verbesserungsvorschläge hinsichtlich der State-Machines
und seine weiteren Fehlerreports.
Zielgruppe
23
Meinem Lektor Stephan Mattescheck für seine kritische Begutachtung der Entwürfe
und die Bereitstellung der Buch-CD mit Visual C# 2005 Express.
Vielen Dank auch an meine Familie und besonders meine Frau Suhong, die mich
während der gesamten Zeit, in der ich an diesem Manuskript saß, hervorragend auf
viele unterschiedliche Arten unterstützt hat.
1.5
Kontakt
In diesem Buch sind viele Abbildungen und Programme enthalten. Es ist daher trotz
größter Sorgfalt möglich, dass sich ein Fehler bis in die endgültige Fassung tapfer
gehalten hat. Wenn Ihnen eine solche Verfehlung auffällt, bitte ich Sie, mich kurz darüber unter Angabe von Kapitel und Seitennummer per E-Mail zu unterrichten. Meine
Adresse lautet [email protected].
Falls Sie Fragen haben, können Sie diese ebenfalls an die genannte Adresse schicken
oder schreiben Sie doch einfach einen kleinen Beitrag im Forum zu diesem Buch – ich
bin mir sicher, dass Ihre Frage auch andere Leser interessiert!
1.6
Aufbau des Buches
Ich wurde und werde immer noch auf den speziellen Aufbau dieses Buches angesprochen: Zuerst auf die strukturierte Programmierung einzugehen, ehe dann zur objektorientierten Programmierung übergeleitet wird, ist vielen »alten Hasen« zunächst
zuwider. Dieser Aufbau basiert auf Erfahrungen, die ich im Laufe der letzten Jahre im
Unterricht für Programmieranfänger gewonnen habe: Vielen fällt die strukturierte
Programmierung leichter, da sie mehr dem natürlichen Denken des Menschen ähnelt
als die um Welten abstraktere objektorientierte Programmierung. Sind die strukturierten Elemente und Konzepte erst einmal verinnerlicht, fällt es leichter, auf den Zug
der objektorientierten Programmierung aufzuspringen. Fairerweise muss ich aber
auch zugegeben, dass dieser Aufbau das Buch für Programmierumsteiger weniger
interessant macht.
Das Buch gliedert sich in drei Teile: Der erste Teil beschreibt die strukturierten Elemente der Programmiersprache C#. Der zweite Teil leitet dann über zur objektorientierten Programmierung und erklärt die Konzepte und Sprachmittel, die C# in Version
2.0 für diese Art der Programmierung zur Verfügung stellt. Der dritte Teil stellt dann
die mit .NET ausgelieferte Windows Forms-Bibliothek zum Erstellen von WindowsAnwendungen mit grafischer Oberfläche vor. Der Anhang enthält dann eine kurze
Übersicht über den Debugger von Visual C# 2005 Express, ein Werkzeug, mit dessen
Hilfe man Fehler in Programmen aufspüren kann.
Ich empfehle Ihnen, zunächst die ersten drei Kapitel zu lesen; schauen Sie sich danach
die Bedienung des Debuggers im Anhang A an, ehe Sie mit Kapitel 4, Zusammengesetzte Datentypen und Namensräume, fortfahren. Damit sind Sie in der Lage, Ihre
selbst entwickelten Programme frühzeitig auf Fehler hin zu untersuchen.
Jedes Kapitel des Buches enthält am Ende eine Zusammenfassung der wichtigsten
Punkte; zusätzlich finden Sie Übungsaufgaben ebenfalls am Ende der Kapitel, die
24
Einführung
1
2
dazu dienen, die Themen zu vertiefen. Die Lösungen für diese Übungsaufgaben können Sie über das Internet von den Verlagsseiten ebenso herunterladen wie die angeführten Beispielprogramme. Um Ihnen die Suche in diesen zu erleichtern, finden Sie
unterhalb der abgedruckten Beispielprogramme den Namen des jeweiligen Visual C#
2005 Express Projekts (dieser ist jeweils in der Beschriftung des Beispielcodes in
Klammern angegeben). Wegen des eingeschränkten Umfangs dieses Buches – es soll
ja ein Anfängerbuch sein und darf auch nicht zu umfangreich werden – sind von manchen Beispielprogrammen nur die besprochenen, wichtigen Passagen abgedruckt.
Über den Download erhalten Sie aber immer Zugriff auf alle Projekte!
1.6.1
3
4
5
6
7
Anmerkungen zur verwendeten Notation
Für jede Programmiersprache gibt es Konventionen, wie Code geschrieben werden
soll, z.B. welchem Schema Variablen-, Methoden- und Klassennamen folgen sollen.
Für C# gilt, dass alle Methoden grundsätzlich mit einem Großbuchstaben im Namen
beginnen. Dies ist aber nur eine Konvention, die man durchaus auch kritisch betrachten darf: Ich persönlich halte diese für ungeschickt und weiche daher des Öfteren von
der Konvention ab.
10
1.7
11
8
9
C# Version 1 oder 2?
Dieses Buch ist eigentlich aus einer Überarbeitung der ersten, für C# Version 1
gedachten Auflage entstanden. Neuerungen, die mit dem Erscheinen der Version 2
Einzug in den Sprachstandard gehalten haben, sind als solche gekennzeichnet. Auch
hat sich an der einen oder anderen Stelle die .NET Klassenbibliothek geändert:
Methoden wurden als veraltet gekennzeichnet, andere in die Klassenbibliothek aufgenommen.
12
13
14
Um diesen Änderungen gerecht zu werden und gleichzeitig noch ein gewisses Maß
an Abwärtskompatibilität zu erhalten, sind solche Änderungen zwischen den einzelnen Versionen von C# und .NET im Text vermerkt.
1.8
15
16
Einrichten einer Entwicklungsumgebung
17
Bevor Sie loslegen können, sollten Sie sich zunächst eine geeignete Arbeitsumgebung
auf Ihrem PC einrichten. Dazu gehört mindestens die Installation von Visual C# 2005
Express – entweder aus dem Internet oder von der dem Buch beiliegenden CD. Alternativ können Sie auch andere Entwicklungsumgebungen oder einen Texteditor einsetzen; auf diese Möglichkeiten kann ich aber leider nicht mehr im Rahmen dieses
Buches eingehen.
18
19
20
Für Visual C# 2005 Express benötigen Sie einen Rechner, der mindestens über 600
MHz Prozessortakt verfügen sollte. Das Minimum für den Arbeitsspeicher beträgt
256 MB; es schadet aber nicht, wenn man etwas mehr im Rechner verbaut hat. Installiert man alle Komponenten, landen ca. 1.3 GB auf der heimischen Festplatte. Auf
dem Computer sollte auch Windows 2000, XP oder höher (Vista) installiert sein.
C# Version 1 oder 2?
A
B
25
Starten Sie für die Installation einfach das auf der Visual C# 2005 Express CD befindliche Setup-Programm, falls Ihr Rechner dieses nicht automatisch beim Einlegen der
CD startet, und folgen Sie den dortigen Anweisungen.
Nun wünsche ich Ihnen viel Spaß und Erfolg beim Erlernen dieser neuen Programmiersprache.
26
Einführung
1
2
2
Aufbau von C#-Programmen
3
In diesem Kapitel lernen Sie den grundlegenden Aufbau von C#-Programmen kennen. Des Weiteren werde ich Sie mit Begriffen wie »Klasse«,
»Objekt«, »namespace« und »Assembly« sowie Syntax und Semantik
einer Programmiersprache vertraut machen.
4
5
6
Bevor wir uns in eine Unmenge von neuen Begriffen stürzen, möchte ich Ihnen gerne
ein erstes, kleines Programm – in C# geschrieben – vorstellen. Es trägt den Namen
»Hello-World«, benannt nach der Ausgabe, die es am Bildschirm erzeugt.
7
8
Dieses Programm besitzt einen großen Bekanntheitsgrad in der Entwicklergemeinde:
Fast jeder Programmierer hat es mindestens einmal geschrieben. Es veranschaulicht
auf einfache Art und Weise, aus welchen Sprachelementen ein minimales Programm
besteht. Seit seiner Erfindung vor vielen Jahren hat es sich hartnäckig in der einschlägigen Literatur gehalten.
10
2.1
11
9
Hello, World!
Da ich mit dieser »Tradition« nicht brechen möchte, finden Sie im nachfolgenden Listing 2.1 die C#-Version1 des »Hello-World« Programms.
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
12
/*
HelloWorld.cs
Das Programm gibt "Hello, World!" am Bildschirm aus
und beendet sich danach
*/
13
14
15
class HelloWorld
{
public static void Main()
{
System.Console.WriteLine("Hello, World!");
}
}
16
17
18
19
Listing 2.1 Das erste C#-Programm (HelloWorld).
20
Es gilt nun zunächst, dieses Programm zum Laufen zu bringen. Starten Sie hierfür die
Entwicklungsumgebung Visual C# 2005 Express, indem Sie den Eintrag Visual C#
2005 Express Edition im Startmenü Ihres Windows-Systems anklicken. Danach
erscheint nach kurzer Wartezeit die Entwicklungsumgebung auf Ihrem Bildschirm
(Abbildung 2.1).
A
B
1 Im Buch »C#« von Eric Gunnerson (ebenfalls bei Galileo Computing erschienen), heißt es
auch »Hello, Universe!« – wiederum frei nach dem ausgegebenen Text.
Aufbau von C#-Programmen
29
Abbildung 2.1 Die Entwicklungsumgebung, wie sie sich nach dem Start präsentiert.
Wenn Sie die Entwicklungsumgebung gestartet haben, sehen Sie zunächst die Startseite: Auf ihr finden Sie Informationen über Visual C# 2005 Express Edition (in der
Mitte), die zuletzt bearbeiteten Projekte und kleine Anleitungen, genannt Erste
Schritte. Möchte man ein Programm erstellen, muss dazu zunächst ein Projekt erzeugt
werden: Projekte erfüllen Verwaltungsaufgaben; so enthalten sie Informationen darüber, welche Art von Programm2 erstellt und aus welchen Dateien der C#-Code für
die Anwendung entnommen werden soll.
Sie müssen also zunächst ein neues Projekt erstellen. Klicken Sie hierzu in dem
Bereich Zuletzt geöffnete Projekte hinter dem Eintrag Erstellen den Schriftzug Projekt
mit der linken Maustaste einmal an (Abbildung 2.2).
Wählen Sie in dem darauf folgenden Dialog den Eintrag Konsolenanwendung aus
(Abbildung 2.3); Sie können zudem im Feld Name einen anderen als den vorgeschlagenen Namen für das Projekt eingeben. Für alle Programme, die im Rahmen dieses
Buches im weiteren Verlauf angeführt werden, können Sie Projekte dieses Anwendungstyps erstellen; Ausnahmen werde ich als solche kennzeichnen. Wenn Sie diesen
Dialog durch einen Klick auf die Schaltfläche OK schließen, wird ein neues Projekt
angelegt und geöffnet.
2 Konsolen-, Windows-Anwendungen und Bibliotheken sind die Arten, die am häufigsten benötigt werden.
30
Aufbau von C#-Programmen
1
2
3
4
5
6
Abbildung 2.2 Sie können ein neues Projekt über die Startseite erstellen,
indem Sie auf den Eintrag Projekt hinter Erstellen klicken.
7
8
9
10
11
12
13
14
Abbildung 2.3 Erstellen Sie zunächst Konsolenanwendungen.
Der Arbeitsbereich von Visual C# 2005 Express ist üblicherweise in drei Teile geteilt:
In der Mitte sehen Sie einen Texteditor, am rechten Rand im oberen Teil den Projektmappen-Explorer und darunter das Eigenschaftsfenster. Am unteren Rand sehen Sie
eine – hoffentlich leere – Liste mit aufgetretenen Fehlern (Abbildung 2.4).
15
Falls Sie nicht das gleiche Fenster wie in Abbildung 2.4 sehen, kann dies daran liegen,
dass noch nicht alle Ansichten ausgeklappt sind. Fahren Sie in diesem Fall mit der
Maus auf die sich an den Rändern befindlichen Schaltflächen; wenn Sie mit dem
Mauszeiger kurz über diesen verweilen oder auf sie klicken, fährt das zugehörige
Fenster heraus. Sie können dies dann durch einen Klick auf das Pinnwand-NadelSymbol des jeweiligen Fensters verankern. Falls Ihnen die Gestaltung des Arbeitsbereichs nicht zusagt, können Sie die Fenster auch an andere Ränder verschieben: Ziehen Sie diese hierzu einfach mit der Maus an den entsprechenden Fensterrand.
Haben Sie ein Fenster aus Versehen geschlossen, können Sie dieses über das Menü
Ansicht wiederherstellen.
17
16
18
19
20
A
B
Hello, World!
31
Abbildung 2.4 Der Arbeitsbereich von Visual C# 2005 Express
2.1.1
Der Projektmappen-Explorer
Der Projektmappen-Explorer verwaltet die gerade geöffneten Projekte in Visual C#
2005 Express. Eine Projektmappe besitzt mindestens ein Projekt. Ein Projekt wiederum besteht aus Eigenschaften3, Verweisen und einzelnen Dateien mit C#-Code.
Die Eigenschaften eines Projekts werden in Form einer Datei, meist AssemblyInfo.cs genannt, angegeben. Diese enthält C#-Code, so genannte Attribute4. Man
kann hier die aktuelle Versionsnummer, den Autor usw. einstellen. Der Ordner Verweise enthält zusätzliche Abhängigkeiten, die die Anwendung benötigt, um später
ausgeführt werden zu können. Darunter finden Sie die einzelnen Dateien, aus denen
das spätere Programm zusammengebaut wird.
Abbildung 2.5 Der Projektmappen-Explorer zeigt Projekte in einer Ordnerstruktur an.
3 engl. Properties
4 Attribute werden im Kapitel 14 ausführlich erläutert.
32
Aufbau von C#-Programmen
1
2
2.1.2
Das Eigenschaftsfenster
3
Wenn Sie ein Element im Projektmappen-Explorer ausgewählt haben, werden in diesem Fensterchen die dem Element zugeordneten Eigenschaften angezeigt. Bitte
beachten Sie, dass Sie nicht alle Eigenschaften ändern können, da einige nur lesbar
sind. Diese werden dann in grauer Farbe dargestellt.
4
5
2.1.3
Die Fehlerliste
6
Die Fehlerliste wird erst ein wenig später benötigt; sie enthält eine Auflistung aller
Fehlermeldungen und Warnungen, die vom C#-Compiler während des Übersetzungsvorgangs erzeugt wurden. Lässt sich Ihr Programm nicht übersetzen, finden Sie meist
den Grund dafür in diesem Fenster. Ein Doppelklick auf einen Eintrag führt Sie dann
zu der entsprechenden Zeile in Ihrem Quellcode, an der der Fehler oder die Warnung
aufgetreten ist: Ein Programm muss fehlerfrei sein, sonst lässt es sich nicht ausführen.
Warnungen geben Hinweise an den Programmierer, dass er oder sie sich um eine
bestimmte Stelle im Code nochmals Gedanken machen sollte – diese können und
sollten beachtet werden, aber das ist kein Zwang.
10
2.1.4
11
7
8
9
Der Texteditor
Das Herzstück der Entwicklungsumgebung bildet der Texteditor in der Mitte des
Visual C# 2005 Express Fensters: Hier geben Sie Ihre Programme ein. Löschen Sie den
Text, der automatisch für Sie generiert wurde, und tippen Sie dafür den in Listing 2.1
gezeigten Code ein (Abbildung 2.6). Speichern Sie nach der Eingabe die Datei über
das entsprechende Symbol in der Symbolleiste oder über das Menü Datei.
12
13
14
Auch wenn das Kopieren der hier vorgestellten Programme sehr einfach geht,
schreiben Sie sie ab! Viele Dinge lernt man besser, wenn man sie praktisch übt.
Dazu gehören auch simpel aussehende Programme.
15
16
Wenn Sie alles richtig abgetippt haben, können Sie versuchen, das Programm mit
dem Compiler übersetzen: Wählen Sie hierfür den Eintrag Projektmappe erstellen aus
dem Menü Erstellen oder drücken Sie die Taste (F6).
17
18
Sollten im Programm Fehler vorhanden sein, bekommen Sie diese mit einer (mehr
oder weniger genauen) Fehlermeldung sowie der betreffenden Zeilennummer vom
Übersetzer gemeldet. Diese Fehlermeldungen werden in der Fehlerliste eingetragen.
19
20
C#-Programme sind casesensitive, d.h. es wird zwischen Groß- und Kleinschreibung unterschieden! Wenn Fehler auftreten, vergewissern Sie sich zunächst, dass
nicht nur das entsprechende Wort korrekt geschrieben wurde, sondern auch die
Schreibweise zu 100 % übereinstimmt!
Hello, World!
A
B
33
Abbildung 2.6 Das erste C#-Programm in Visual C# 2005 Express
Machen Sie sich frühestmöglich mit einfachen, schnell unterlaufenen Fehlern wie
vergessenen Zeichen5 vertraut – d.h. welche Fehlermeldungen in diesen Fällen
erzeugt werden. Und vor allem: Schrecken Sie nicht vor vielen Fehlermeldungen
zurück! Oftmals verursacht ein Fehler etliche weitere, und die Beseitigung des ersten
Fehlers »behebt« die nachfolgenden von alleine.
Sind keine Fehler in der Eingabedatei vorhanden, übersetzt der Compiler das Projekt
in eine ausführbare Anwendung. Sie können diese Anwendung starten, indem Sie
den Eintrag Starten ohne Debuggen im Menü Debuggen auswählen oder die Taste
(Strg) zusammen mit der Taste (F5) drücken. Alternativ können Sie das Programm
auch über den Windows Datei-Explorer starten. Wechseln Sie in das Projektverzeichnis: Sie finden dort unterhalb des Ordners bin im Verzeichnis Release eine ausführbare
Datei, die Ihr Programm darstellt und die Sie durch einen einfachen Doppelklick starten können (Abbildung 2.7).
Nach dem Start des neuen Programms erscheint ein weiteres Fenster auf dem Bildschirm, das den Text Hello, World! enthält (Abbildung 2.8). Wenn Sie eine Taste drücken, verschwindet dieses Fenster wieder.
Glückwunsch! Sie haben soeben Ihr erstes C#-Programm geschrieben, übersetzt und
ausgeführt! In den folgenden Abschnitten werden wir nun dieses Programm in seine
einzelnen Bestandteile zerlegen und eingehend betrachten.
5 Ein Paradebeispiel für ein solches Zeichen ist das Semikolon – jede Anweisung in C# muss
mit einem solchen Zeichen abgeschlossen werden.
34
Aufbau von C#-Programmen
1
2
3
4
5
6
7
8
Abbildung 2.7 Das fertige Programm finden Sie als ausführbare Datei unterhalb des Projektverzeichnisses.
9
10
11
12
Abbildung 2.8 Das erste C#-Programm wird in einem neuen Konsolenfenster ausgeführt.
13
2.2
Kommentare
14
Wie im Listing 2.1 in den ersten Zeilen auffällt, scheint der Text zwischen den Zeichen /* und */ scheinbar nichts mit dem Programm zu tun zu haben: Es handelt sich
hierbei um einen Kommentar des Programmautors.
15
Kommentare werden beim Übersetzen vom Compiler nicht weiter beachtet. Sie dienen dazu, Anmerkungen zum Programm im Code zu machen, die zu einem besseren
Verständnis des Quellcodes beitragen sollen.
16
17
Man setzt an den Anfang einer Datei für gewöhnlich zunächst immer einen Kommentar, der den Inhalt des Files beschreibt. Auch Angaben zu Autor, Version, Copyright
und Änderungen an der Datei sind in diesem Kommentar enthalten. Hierdurch erhält
man einen besseren Überblick über die Historie einer Datei (z.B. warum eine
bestimmte Änderung vorgenommen wurde). Insbesondere bei größeren Projekten,
an denen mehr als zwei Leute arbeiten, ist dies eine große Hilfe!
18
19
20
01: /*
02:
HelloWorld.cs
03:
...
04: */
A
B
Listing 2.2 Zu jedem Programm gehören erläuternde Kommentare am Anfang einer Datei.
Kommentare
35
Kommentare können aber auch dazu eingesetzt werden, komplizierte Algorithmen
zu erläutern. Dies erleichtert die Arbeit von nachfolgenden Entwicklern ungemein,
die diesen Code-Abschnitt verstehen müssen (um ihn z.B. zu erweitern).
2.2.1
Kommentarblöcke
Ein Kommentarabschnitt, d.h. eine Anmerkung, die sich über einen festgelegten
Bereich erstreckt, wird wie in Listing 2.2 gezeigt, durch die Zeichenfolge /* eingeleitet und durch die Zeichen */ abgeschlossen. Zeilenumbrüche dazwischen werden
ignoriert; d.h. der Kommentar kann sich über mehrere Zeilen hinweg erstrecken,
muss es aber nicht!
Ein weiterer Einsatzzweck dieser Art der Kommentierung ist das Ausblenden von
Code-Abschnitten, ohne diese aus der Datei zu entfernen. Man setzt Beginn- und
Endezeichen an die entsprechenden Code-Abschnitte (Listing 2.3) und verhindert
dadurch die Einbeziehung des so umschlossenen Code-Abschnittes in den Übersetzungsvorgang.
01:
02:
03:
04:
05:
06:
07:
...
public static void Main()
{
/* System.Console.WriteLine("ausgeblendet"); */
System.Console.WriteLine("aktiv");
}
...
Listing 2.3 Auskommentieren von Code verhindert die Einbindung in den Übersetzungsvorgang.
Dieses Vorgehen findet Anwendung, wenn man neuen Code einfügt und den bestehenden dabei so verändert, dass er nicht restauriert werden kann – also für Sicherungszwecke. Aber auch wenn »mal schnell« eine andere Variante des Programmcodes getestet werden soll6, greift man auf die Auskommentierung7 zurück.
Eine Schachtelung von Kommentaren (wie in Listing 2.4 dargestellt) ist nicht möglich. Der innere Kommentar beendet automatisch alle äußeren – im Beispiel beenden
die Endezeichen von Kommentar 2 automatisch den Kommentarblock 1. Auch eine
Schachtelung über Kreuz, d.h. Kommentar 2 wird außerhalb von Kommentar 1 beendet, ist nicht möglich!
01: /* Beginn Kommentar 1
02:
/* Beginn Kommentar 2
03:
Ende Kommentar 2 */
04: Ende Kommentar 1 */
Listing 2.4 Die Schachtelung von Kommentarblöcken erzeugt beim Übersetzen einen Fehler.
6 Das so genannte »try-and-error principle« erfreut sich besonders unter Anfängern in Sachen
Programmierung einer sehr großen Beliebtheit.
7 Auskommentieren sollte aber nicht dazu verwendet werden, um alten Code permanent zu
konservieren. Stattdessen sollte man auf ein Versionierungssystem zurückgreifen, das erlaubt, alte Versionen zu sichern und auch wieder herzustellen.
36
Aufbau von C#-Programmen
1
2
Achten Sie daher bei Verwendung dieser Methode darauf, dass keine Überschneidung von Kommentaren auftritt! Visual C# 2005 Express unterstützt Sie hierbei:
Kommentare im Programm werden farblich im Texteditor hervorgehoben.
3
4
2.2.2
Zeilenkommentare
5
Neben diesem Abschnittskommentar gibt es eine weitere Art, den Zeilenkommentar.
Er stammt im Gegensatz zu der bereits vorgestellten Art nicht von C ab, sondern von
C++. Die Zeichenfolge // leitet einen Zeilenkommentar für die aktuelle Zeile ab
Beginn der Zeichen // ein:
6
7
01: ...
02: System.Console.WriteLine("Hello, World!"); // Ausgabe
03: ...
8
Listing 2.5 Zeilenkommentare haben kein explizites Endezeichen.
9
Der Kommentar endet automatisch mit dem Ende der betreffenden Zeile; d.h. ein
Zeilenumbruch impliziert das Ende des Kommentars – im Gegensatz zum expliziten
Ende der vorhergehenden Variante.
10
11
Zeilen- und Abschnittskommentare können gemischt werden (Listing 2.6)!
12
01: /*
02:
System.Console.WriteLine("Hello, World!") // Ausgabe
03: */
13
14
Listing 2.6 Das Mischen von Zeilen- und Kommentarblöcken ist möglich.
15
Der Zeilenkommentar kommt in C# noch in einer weiteren Spezialform vor: Diese
kann zur automatischen Generierung der Programmdokumentation genutzt werden.
In Kapitel 15, XML-Dokumentation und Präprozessor, finden Sie hierzu weitere Informationen.8
16
17
Kommentare sollten kurz und prägnant ausfallen! Es gibt »schlecht« und »gut«
kommentierte Programme: Zu schlecht kommentierten zählt man neben denen,
die gar keine Kommentare enthalten, auch diejenigen, die zu viele enthalten oder
nur das Offensichtliche8 dokumentieren.
18
19
20
Selten aber trifft man auf das Extrem, dass ein Programm mit zu vielen Kommentaren
versehen wurde. Der Grund hierfür ist die Tatsache, dass das Kommentieren zu den
unbeliebtesten Arbeiten beim Schreiben von Software gehört. Oft trifft man daher
das andere Extrem an – zu wenig oder auch unverständliche Kommentare.
A
B
8 Ist aus dem Code ersichtlich, dass z.B. der Wert einer Variable um eins erhöht wird, so ist
dies nicht durch einen Kommentar zu erläutern – diese Zeile sollte jeder, der dieser Sprache
mächtig ist, ohne weitere Erläuterungen verstehen.
Kommentare
37
2.3
Syntax und Semantik
Unter der Syntax einer Programmiersprache versteht man umgangssprachlich das
»wie wird etwas ausgedrückt«. Syntaxdefinitionen beschreiben also, wie Konstrukte
aufgebaut sind und welche Variationsmöglichkeiten man hat.
Im Gegensatz zur Syntax, die das »wie« beschreibt, versteht man unter der Semantik
die Bedeutung des tatsächlich geschriebenen Ausdrucks.
Nehmen wir als Beispiel einen einfachen Ausdruck: »4 – 3«. Die Syntax beschreibt
nun, wie eine Subtraktion formal niedergeschrieben wird. Vertauscht man die beiden
Ziffern im Ausdruck zu »3 – 4«, ändert sich nicht die Syntax; es ist immer noch eine
gültige Subtraktion. Stattdessen ändert sich die Semantik: Das Ergebnis aus dem ersten Fall ist bezogen auf die numerischen Werte nicht gleich dem zweiten Ergebnis.
Im weiteren Verlauf dieses Buches werde ich Ihnen immer wieder Syntaxdefinitionen
für die unterschiedlichsten Ausdrücke und Konstrukte von C# vorstellen.
2.4
Verwendete Syntaxschreibweise
In diesem Buch werden sehr oft Ausdrücke in spitzen Klammern auftauchen (z.B.
<Klassenname>). Diese Schreibweise werde ich dann verwenden, wenn ich eine Syntaxdefinition beschreiben möchte. Die spitzen Klammern sind kein Teil der C#-Syntax
und müssen nicht mit abgeschrieben werden. Vielmehr stehen sie als eine Art »Platzhalter« für ein Stück Code bzw. Angaben des Programmierers.
Ähnlich den spitzen Klammern werde ich eckige Klammern dazu einsetzen, optionale
Elemente zu beschreiben. Diese Elemente können Sie mit angeben, sie müssen aber
nicht zwingend vorhanden sein.
Ich beschränke mich auf die Verwendung dieser beiden Zeichenpaare. Falls diese Teil
der Syntax sind, d.h. mit im Code geschrieben werden müssen, werde ich dies bei
jeder Syntaxdefinition entsprechend im Text vermerken.
2.5
Eine kurze Einführung zum Thema »Klassen«
Obwohl das Thema Objekte & Klassen in den zweiten Teil dieses Buches gehört,
möchte ich an dieser Stelle kurz darauf eingehen. Der Grund ist, dass C# immer mindestens eine Klasse benötigt, die den Einsprungspunkt9 für das Programm enthält.
Unter einer Klasse versteht man ein abstraktes Gebilde, das ein real existierendes
»Objekt« nachbildet. Es vereint dem Objekt zugehörige Daten und Methoden
(Aktionen, die das Objekt ausführen kann). Eine Klasse bezeichnet man auch als
Gesamtheit eines Objektes.
9 Jedes Programm enthält einen sogenannten Einsprungspunkt. Er kennzeichnet die Stelle im
Code, an dem die Ausführung des Programms beginnt.
38
Aufbau von C#-Programmen
1
2
Ein Objekt – im programmiertechnischen Sinn – ist eine Instanz einer Klasse, d.h.
eine zu Nullen und Einsen gewordene Abbildung im Speicher des Computers, die
nach der Vorlage der Klasse erzeugt wurde.
3
4
Diese beiden Definitionen erheben nicht den Anspruch, vollständig zu sein. Für den
jetzigen Zeitpunkt sind sie aber bei weitem ausreichend. Zu gegebener Zeit werde ich
sie ergänzen.
5
6
Wenn Sie Sekundärliteratur wie Dokumentationen aus dem Internet einsetzen oder
sich mit Entwicklern unterhalten, werden Sie sicher schnell feststellen, dass die
Begriffe Klasse und Objekt häufig durcheinander gebracht werden. Lassen Sie sich
daher nicht davon beirren! Im Folgenden gilt immer die oben angeführte Definition!
2.5.1
7
8
Deklaration von Klassen
9
Eine Klassendefinition wird in C# immer durch das Schlüsselwort10 class, gefolgt
vom Namen dieser Klasse, eingeleitet. Der »Inhalt«, d.h. die Methoden und Daten
einer Klasse werden anschließend – von geschweiften Klammern umrahmt – angefügt.
10
11
01: class HelloWorld
02: {
03:
/*
04:
Methoden & Daten der Klasse HelloWorld
05:
*/
06: }
12
13
14
Listing 2.7 Beispiel für eine Klassendefinition in C#
15
Im Listing 2.7 sehen Sie ein Beispiel für die Definition einer Klasse. Für den Klassennamen gelten bestimmte Voraussetzungen in Bezug auf die verwendeten Zeichen.
Für Klassennamen sind dieselben Regeln wie für die Benennung von Bezeichnern
anzuwenden. Sie finden diese in Abschnitt 3.2.6 des dritten Kapitels.
16
17
Wichtig zu wissen ist zunächst, dass der Name der Klasse mit einem Buchstaben oder
einem Unterstrich »_« beginnen muss. Im weiteren Verlauf (d.h. erst ab dem zweiten
Zeichen) dürfen auch Zahlen im Namen auftauchen. Zu der Liste der ungültigen Klassennamen zählen auch Schlüsselwörter. Ist der Name eine Kombination aus Schlüsselwörtern oder aus Buchstaben und Schlüsselwörtern, ist er gültig, aber ungeschickt
gewählt. Klassennamen sollten stets so gewählt werden, dass keine Verwechslung mit
Sprachelementen möglich ist. Eine Auflistung aller Schlüsselwörter finden Sie ebenfalls in Kapitel 3, Konstanten, Variablen & Datentypen, in Abschnitt 3.2.6.
18
19
20
A
B
10 Schlüsselwörter bezeichnen Zeichenketten, die eine feste Bedeutung in einer Programmiersprache haben. Keine Schlüsselwörter sind z.B. Namen von Methoden oder Variablen.
Eine kurze Einführung zum Thema »Klassen«
39
2.5.2
Der Einsprungspunkt
Aber dies alleine erzeugt noch keinen Einsprungspunkt, an dem die Ausführung des
Programms beginnt. Um solch eine Stelle in einem Programm zu erzeugen, muss eine
Funktion11 hinzugefügt werden, die den Namen Main trägt. Anstatt Funktion sagt
man auch Methode.
Dieser Methode müssen noch zusätzlich die Schlüsselwörter public, static und
void vorangestellt werden. Zu beachten ist hier nicht nur die Groß- und Kleinschreibung, sondern auch die Reihenfolge!
왘 public erläutere ich Ihnen in Kapitel 7, Einführung in die objektorientierte Program-
mierung, da es zur objektorientierten Programmierung gehört.
왘 static legt fest, dass die Methode auch ohne eine Instanz der HelloWorld-Klasse
aufgerufen werden kann, und beschreibe ich in Kapitel 8, Generische Klassen,
Schnittstellen und statische Klassenmitglieder, beschrieben.
왘 void letztendlich bestimmt, dass die Methode keinen Wert12 zurückgibt.
Nach dem Methodennamen folgen, von runden Klammern eingeschlossen, die Parameter der Methode. Die hier vorgestellte Implementierung der Main()-Methode
besitzt keine Parameter. Argumente, die dem Programm beim Start auf der Kommandozeile übergeben werden können, werden – falls dies gewünscht wird – in einem
Parameter der Main()-Methode mitgegeben. Zum jetzigen Zeitpunkt würde dies aber
zu weit führen. Ich führe dieses Thema daher ebenfalls in einem späteren Kapitel weiter aus.
Nach der Parameterliste folgt der Rumpf der Methode; dieser ist wiederum von
geschweiften Klammern eingeschlossen. Er beinhaltet den Code, der ausgeführt
wird, wenn man das Programm startet. Im Fall von Listing 2.1 ist dies die Methode
zur Ausgabe von Zeichen auf dem Bildschirm.
2.6
Zusammenhang zwischen Klassen- & Dateiname
Im Gegensatz zu der Programmiersprache Java gibt es in C# keinen Zusammenhang
zwischen Klassen- und Dateiname13, d.h. Sie können die Klasse HelloWorld auch in
einer Datei namens HalloWelt.cs oder Program.cs speichern, übersetzen und ausführen.
Sie sollten aber darauf achten, Ihren Dateien sprechende Namen zu geben, die nach
Möglichkeit keine Sonderzeichen oder Leerzeichen enthalten. Sonderzeichen kön11 Eine Funktion beschreibt eine Ansammlung von Programmcode unter einem Namen. Eine
Funktion kann dabei – wie ihr mathematisches Pendant – von Parametern abhängig sein.
Funktionen können auch einen Wert berechnen und diesen zurückgeben.
12 Gibt die Methode Main() einen Wert zurück, so ist dies automatisch der Rückgabewert
des Programms (z.B. ein Fehlerwert). Der Rückgabewert eines Programms wird in einem
späteren Kapitel behandelt. Dieser kann von Batch-Dateien auf der Kommandozeile aufgefangen und entsprechend abgearbeitet werden (Stichwort ERRORLEVEL für Batch-Programmierer).
13 In Java muß die Datei genauso wie die in der Datei enthaltene Klasse benannt werden.
40
Aufbau von C#-Programmen
1
2
nen insbesondere im Zusammenhang mit Systemen zur Versionsverwaltung oder zur
automatischen Generierung von neuen Versionen zu Problemen führen, die zu beheben unnötigen Aufwand erforderte. Achten Sie auch weiterhin darauf, Projekte nicht
auf Netzlaufwerken zu erstellen: Dies hat meiner Erfahrung nach immer wieder zu
den tollsten Problemen geführt, die einfach dadurch gelöst werden konnten, dass ein
Projekt auf eine Festplatte des Rechners kopiert wurde. Zudem erhöht der dauernde
Zugriff auf das Netzwerk dessen Auslastung, so dass unter Umständen für andere
Aufgaben weniger Ressourcen zur Verfügung stehen.
2.7
3
4
5
6
Ausgaben auf dem Bildschirm
7
01: public static void Main()
02: {
03:
System.Console.WriteLine("Hello, World!");
04: }
8
9
Listing 2.8 Die Ausgabe der Zeichenkette »Hello, World!« am Bildschirm übernimmt die WriteLine()-Methode.
10
Im Listing 2.8 sehen Sie die Methode, die für die Ausgabe von Zeichen auf dem Bildschirm verantwortlich ist. Sie trägt den Namen WriteLine() und bekommt – vorerst
– inen Parameter: den Text, der ausgegeben werden soll. WriteLine() gibt nicht nur
den ihr übergebenen Text aus, sondern setzt automatisch die Schreibmarke an den
Anfang der nächsten Zeile (= Zeilenumbruch).
11
12
13
Der (erste) Parameter der WriteLine()-Methode ist die auszugebende Zeichenkette;
Zeichenketten – auch als Strings, Konstanten oder Literale bezeichnet – werden von
doppelten Anführungszeichen eingeschlossen. Zu beachten ist dabei, dass eine Reihe
spezieller Zeichen nicht in Strings vorkommen darf, da ihnen eine Spezialbedeutung
zukommt. So ist das einfache oder doppelte Anführungszeichen selbst oder aber auch
der einfache Backslash \ tabu. Wie Sie diese Zeichen ausgeben können, werde ich
Ihnen in Kapitel 3, Konstanten, Variablen & Datentypen, zeigen (Abschnitt 3.2.8).
2.7.1
14
15
16
17
Namensräume
Der Aufruf von WriteLine(...) steht nicht »alleine«, sondern er ist zusätzlich von
weiteren Bezeichnern umgeben: Diese spezifizieren zunächst einen Namensraum14,
in dem die Methode (genauer die Klasse, die diese Methode implementiert) zu finden
ist.
18
01: System.Console.WriteLine(...);
20
19
Listing 2.9 Ein Aufruf der WriteLine()-Methode wird mit Namensraumbezeichner und Klassennamen dekoriert.
A
Namensräume werden verwendet, um Konflikten zwischen Klassen mit dem gleichen Namen vorzubeugen und um Programme logisch besser zu strukturieren. Wie
B
14 Namensraum wird ins Englische mit namespace übersetzt.
Ausgaben auf dem Bildschirm
41
bereits erwähnt, ist noch die Klasse angegeben, in der die Methode WriteLine()
implementiert ist. Namensraum, Klassenname und Methodenname werden jeweils
durch einen Punkt voneinander getrennt. Die Reihenfolge ist dabei wie angegeben
von links nach rechts. Auf diese Weise wird der komplette Pfad zur Implementierung
festgelegt; eine Verwechslung ist nun nicht mehr möglich.
Übertragen bedeutet dies, dass die (statische) Methode WriteLine() in der Klasse
Console implementiert ist, die im Namensraum System abgelegt ist.
2.7.2
Umbruch der Ausgabe von WriteLine()
Ist die auszugebende Zeile länger als die Bildschirmzeile, wird der Text automatisch
umgebrochen. Man spricht dabei von einem harten Umbruch, da er nicht wortweise
vorgenommen wird (d.h. wie von einer Textverarbeitung her gewohnt in einer
Spalte, in der das nächste Leerzeichen steht).15
WriteLine() ist kein Bestandteil der Sprache C#! Die Methode (eigentlich die
Klasse Console) ist Teil der .NET-Klassenbibliothek und kann von allen Programmiersprachen, die .NET unterstützen, verwendet15 werden!
2.8
Assembly
Früher – d.h. vor .NET – bekam man nach einem erfolgreichen Übersetzungsvorgang
als Ergebnis z.B. eine ausführbare Datei (in Form eines EXE-Files). Auch andere »Formate« wie DLLs16 oder statische17 Bibliotheken wurden in eine Datei geschrieben.
Mit .NET führt Microsoft nun einen weiteren Abstraktionsschritt ein: Dieser wird
Assembly (eingedeutscht als Assemblierung oder Paket bezeichnet) genannt. Eine
Assembly ist eine Organisationseinheit, in die einzelne Komponenten, aber auch ausführbare Programme eingepackt werden können. Außer den reinen Informationen,
welche Komponenten in einer Assembly enthalten sind, enthält diese noch zusätzliche Informationen – z.B. Versionsnummern. Dabei werden nicht nur die eigenen
Versionsnummern gesichert, sondern auch die der zur Erstellung verwendeten, dritten Assemblies, um Abhängigkeiten auflösen zu können. Hierdurch wird ermöglicht,
dass immer die richtigen Versionen von dynamischen Bibliotheken (DLLs) zur Laufzeit geladen werden können.
15 Man sagt sehr oft anstatt »eine Methode verwenden« auch »eine Methode aufrufen« (für
Interessierte: In Assembler wird ein Methodenaufruf mit dem Befehl call eingeleitet).
16 DLL (engl. Dynamic Link Library) bezeichnet eine Möglichkeit, Code zur Laufzeit dynamisch nachzuladen und wieder zu entladen. Programme, die DLLs nutzen, sind erfahrungsgemäß sehr klein, da der Code auf weitere Dateien verteilt werden kann. Eine passende
deutsche Bezeichnung existiert nicht; die Bezeichnung »DLL« hat sich auch im deutschen
Sprachraum durchgesetzt.
17 Statische Bibliotheken werden zur Übersetzungszeit zum Programm hinzu gebunden. Sie
vergrößern die ausführbare Datei des Programms, müssen aber nicht nachgeladen werden.
42
Aufbau von C#-Programmen
1
2
Diese Informationen werden in einem Manifest in der Assembly gesichert, das mit
Hilfe eines Tools aus dem .NET Framework SDK ausgelesen werden kann. Aber auch
die CLR18 – also die virtuelle Maschine, die das .NET-Programm überwacht – greift
auf diese Informationen zurück.
3
4
In Abbildung 2.9 sehen Sie das Manifest der Anwendung, wie es von ildasm, einem
Tool aus dem .NET Framework SDK, angezeigt wird. Deutlich sind zwei Assemblies
zu erkennen:
5
6
왘 mscorlib
benötigt19
Das Programm
die Datei MSCORLIB.DLL, in der die meisten Klassen des
.NET Frameworks enthalten sind – u.a. auch die Klasse Console mit der WriteLine()-Methode! Man erkennt unter dem Eintrag ».ver« deutlich die für die Erstellung dieser Applikation verwendete Version 2.0 von .NET.
7
8
Mit Hilfe dieser Versionsangabe wird .NET nun immer die richtige Version von
MSCORLIB.DLL zum Ausführungszeitpunkt der Applikation laden.
9
왘 ConsoleApplication1
10
Bezeichnet die Assembly, die durch die Applikation gebildet wird. Da keine Informationen über die Versionsnummer(n)20 hinterlegt wurden, ist auch dementsprechend die Standardangabe eingesetzt worden. Sie können die Versionsinformationen ändern, indem Sie im Ordner Properties des Projekts die Datei AssemblyInfo.cs editieren.
11
12
13
14
15
16
17
18
19
20
A
Abbildung 2.9 Das Manifest der erzeugten Anwendung.
18 Common Language Runtime: führt .NET Programme aus.
19 ersichtlich an dem Schlüsselwort extern.
20 Eine Versionsnummer setzt sich zumeist aus mehreren Teilen zusammen. Diese können
getrennt voneinander angegeben werden.
Assembly
B
43
Zu beachten ist, dass eine Assembly durchaus auch mehrere Dateien umfassen kann
(multi module assemblies). Weiterhin unterscheidet man private und shared Assemblies.
Eine Assembly wird als private bezeichnet, wenn sie sich nur auf eine Applikation
bezieht – normalerweise liegen dann die der Assembly angehörenden Dateien auch
im selben Verzeichnis wie das Programm selbst. Typische Beispiele für private Assemblies sind Bibliotheken, die vom Programm benötigte interne Komponenten oder für
das Programm wichtige, ausgelagerte Funktionalitäten enthalten. Nutzt ein Programm nur private Assemblies, beschränkt sich das Installieren dieser Applikation
auf ein einfaches Kopieren der benötigten Dateien – eine Vorgehensweise, die bereits
im Zeitalter von MS–DOS gebräuchlich war.
Kann eine Assembly auch für weitere Programme von großem Nutzen sein, so empfiehlt es sich, diese Assembly allen Programmen zugänglich zu machen. Hierzu dient
der so genannte Global Assembly Cache (GAC).
Im GAC werden alle shared Assemblies nach ihren Versionsnummern und strong
names21 verwaltet. Hierdurch wird es möglich, mehrere Versionen derselben Bibliothek parallel auf einem Rechner zu halten – mit herkömmlichen DLLs war dies nicht
möglich! Ein Programm kann dann eine bestimmte Version einer Assembly gezielt
anfordern. Des Weiteren wird eine Registrierung in der Windows-Datenbank
(»Registry«) hierdurch umgangen: eine große Fehlerquelle für nicht .NET-Programme
(fehlende Einträge oder fehlerhafte Verweise, falsche Versionen ...). Zum Registrieren einer Assembly im GAC gibt es im .NET Framework ein spezielles Tool – gacutil.exe.
Beispiele für shared Assemblies sind z.B. die Bibliotheken, die das .NET Framework
bei seiner Installation mitbringt22 – oder spezielle Oberflächenelemente (z.B. ein grafischer Drehregler für Audio-Applikationen). Copy-Deployment – also die Installation
per Kopierbefehl – ist bei shared Assemblies nicht möglich!
2.9
Zusammenfassung
Ich möchte zum Abschluss nochmals kurz zusammenfassen, was Sie in diesem Kapitel
gelesen haben.
2.9.1
Bestandteile eines C#-Programmes
왘 Klasse(n):
C#-Programme müssen mindestens eine Klasse enthalten. Dies ergibt sich aus der
Ausrichtung der Sprache selbst: C# ist objektorientiert. Wie man Klassen sinnvoll
einsetzt, lernen Sie im zweiten Teil dieses Buches.
21 strong name (dt. starker Name): Stellt eine Möglichkeit dar, eine Komponente anhand ihres
Namens eindeutig zu identifizieren. Um dies zu erreichen werden zusätzliche Informationen zum eigentlichen Namen hinzugeneriert (z.B. ein eindeutiger Schlüssel).
22 Das .NET Framework wird automatisch bei Installation von Visual C# 2005 Express mit auf
dem Rechner installiert.
44
Aufbau von C#-Programmen
1
2
왘 Main()-Methode:
Die Main()-Methode ist der Einsprungspunkt des Programmes, d.h. an dieser
Stelle beginnt die Ausführung des Programms. Die Methode muss dabei als public static void Main() definiert werden. Der Name sowie die Attribute public
und static müssen vorhanden sein und dürfen nicht anders lauten. Zunächst erhält die Main()-Methode keine Parameter und gibt auch keinen Wert zurück
(void).
2.9.2
3
4
5
6
Zusätzliches über das .NET Framework
왘 System.Console.WriteLine(...)
7
Die Klasse Console, die im .NET Framework im Namensraum System enthalten
ist, dient der Ausgabe von Zeichen auf dem Bildschirm. Die auszugebenden Zeichen werden der Methode als Parameter in doppelten Anführungszeichen übergeben. Ein Umbruch erfolgt, sobald die aktuelle Zeile kein weiteres Zeichen mehr
aufnehmen kann.
8
9
왘 Namensräume
10
Namensräume dienen der Vermeidung von Namenskonflikten und zur logischen
Strukturierung der Klassen und Typen eines Programmes. Sie ermöglichen eine zusätzliche Qualifizierung der Klassen nach deren übergeordnetem Namensraum.
Beispiele für Namensräume sind: System, System.IO und System.Windows.
Forms.
11
12
왘 Assemblies
13
Assemblies sind die übergeordnete Organisationseinheit für .NET-Programme
oder Bibliotheken. Mit Hilfe von Assemblies können z.B. unterschiedliche Versionen ein und derselben Bibliothek auf einem Rechner parallel gehalten werden.
14
Assemblies enthalten ein Manifest, indem alle Parametrierungen (Versionsinformationen und Abhängigkeiten) festgehalten sind. Eine Assembly kann mehrere
Dateien umfassen!
15
16
Man unterscheidet zwischen private und shared Assemblies: Erstere bezeichnen
Assemblies, die nur von der Applikation genutzt werden, die sie installiert hat.
Letztere die Assemblies, die von allen genutzt werden können. Shared Assemblies
werden im Global Assembly Cache registriert, private hingegen nicht.
2.9.3
17
18
C# Sprachelemente
19
왘 Kommentare
Jedes Programm sollte beschreibende Kommentare enthalten. Diese erläutern
schwer verständliche Programmzeilen (z.B. besonders trickreiche Algorithmen)
oder geben Auskunft über die Historie einer Quellcode-Datei. Kommentare sollten
knapp, aber ausreichend formuliert sein.
20
A
B
Zusammenfassung
45
2.10
Übungen
Die Anzahl der Sterne kennzeichnet den Schwierigkeitsgrad der Aufgabe. Daran können Sie ermessen, welche Fortschritte Sie bereits erzielt haben.
왘 Übung 2.1
*
Schreiben Sie das HelloWorld-Programm aus Listing 2.1 ab, übersetzen Sie es und
führen Sie es aus. Funktioniert alles?
왘 Übung 2.2
*
a) Prüfen Sie, welche Fehlermeldungen Ihnen der C#-Compiler meldet, wenn Sie
gezielt Fehler einbauen, indem Sie das Programm aus Übung 2.1 verändern (z.B. Zeichen löschen oder zusätzliche hinzufügen). Lokalisieren Sie die Fehlerquellen anhand
der erzeugten Meldungen – verstehen Sie diese! Was passiert, wenn der Text länger
als die Ausgabe-Zeile ist?
b) Schreiben Sie das Programm aus Übung 2.1 neu und verwenden Sie für die Klasse
einen anderen Namen.
왘 Übung 2.3
*
Modifizieren Sie das Programm aus Übung 2.1 so, dass es anstatt »Hello, World!« die
Meldung »Hallo« inklusive Ihres Namens ausgibt (in meinem Fall also »Hallo, Bernhard!«).
왘 Übung 2.4
**
Lassen Sie zusätzlich zu »Hallo, <IhrName>!« noch ein »Wie geht es Dir heute?« oder
ähnliches in einer neuen Textzeile ausgeben. Verwenden Sie hierzu einen zweiten
Aufruf der WriteLine()-Methode!
왘 Übung 2.5
***
Mit Hilfe der using-Klausel kann man gezielt einen Namensraum »öffnen«, d.h. die
enthaltenen Klassen müssen nicht mehr den vollen Pfad <Namensraum>.<Klassenname> tragen (<Namensraum> kann dabei auch eine tiefere Schachtelung z.B. System.IO enthalten). Die Syntax von using ist:
using <Namensraum>;
Schreiben Sie das Programm aus Übung 2.1 (oder 2.4) so um, dass es mit Hilfe der
using-Klausel den Namensraum System öffnet, und passen Sie die Aufrufe von
WriteLine() entsprechend der neuen Situation an.
46
Aufbau von C#-Programmen
1
2
7
Einführung in die objektorientierte
Programmierung
3
4
Die objektorientierte Programmierung ist mit Beginn der 90er Jahre des
letzten Jahrtausends zu dem Standard in der Programmierung schlechthin
geworden. C# ist eine rein objektorientierte Programmiersprache. Dieser
Aspekt soll in diesem Kapitel näher beleuchtet werden.
5
6
7
Die Einführung der objektorientierten Programmierung in der Softwareentwicklung
stellte einen tiefen Einschnitt in der bis dahin vorherrschenden Programmierphilosophie dar. Stand bis zu diesem Punkt der Fluss von Daten durch eine Anwendung im
Mittelpunkt, so wurde diese Sicht zwar weiterhin beibehalten, doch zunehmend
begannen die Daten autonom zu werden. Sie wurden zu komplexen Gebilden umgeformt, die dann als »Klassen« oder »Objekte« Einzug in den Fachjargon hielten.
8
9
10
7.1
Klassen und Objekte
Bereits im zweiten Kapitel dieses Buches habe ich versucht, Ihnen in einer kurzen
Definition darzulegen, was sich hinter dem Begriff einer Klasse bzw. eines Objektes
verbirgt. Diese Begrifflichkeiten werde ich nun endgültig einführen.
11
7.1.1
13
12
Die Klasse als programmiersprachliche Beschreibung von realen
Objekten
14
Stellen Sie sich als Beispiel vor, dass Sie mit der Erstellung einer Buchhaltungssoftware beauftragt werden. Diese soll in der Lage sein, die Mitarbeiter einer Firma zu
verwalten. Neben der reinen Speicherung der Mitarbeiterdaten wie Name, Adresse
usw. soll diese Applikation auch in der Lage sein, Mitarbeiter zu befördern, ihre Position zu beschreiben, das monatliche Gehalt auszubezahlen etc.
15
16
Was ist nun der Reihe nach zu tun? Als Erstes definiert man sicherlich eine geeignete
Datenstruktur, die einen Mitarbeiter im Programm repräsentiert. Diese verfügt dann
beispielsweise über die Felder Name, Adresse und Gehalt.
17
18
01: struct Mitarbeiter
02: {
03:
public string Name;
04:
public string Adresse;
05:
public double Gehalt;
06: }
19
20
A
Listing 7.1 Ein Mitarbeiter könnte durch eine einfache Datenstruktur beschrieben werden.
B
Einführung in die objektorientierte Programmierung
155
Aktionen wie Gehalt_Buchen() wären dann als Methoden zu realisieren, die ähnlich
wie die WriteLine()-Funktion jeweils den entsprechenden Mitarbeiter übergeben
bekommen.
Grundsätzlich ist dagegen nichts einzuwenden. Problematisch wird die Sache, wenn
man bedenkt, dass die Mitarbeiterdaten zur Verarbeitung durch die gesamte Anwendung geschoben werden müssen. Um die Daten aber überall verarbeiten zu können,
dürfen diese auch nicht über einen Zugriffsschutz verfügen. Damit sind Manipulationen an beliebigen Stellen (auch aus Versehen) möglich. Die Integrität der Daten
wäre folglich nicht sichergestellt.
Oder stellen Sie sich vor, diese Methodik der Mitarbeiterverwaltung läuft und soll
nun in ein zweites Projekt mit übernommen werden. Dadurch, dass die Manipulationsroutinen möglicherweise über das gesamte Projekt verstreut liegen können, ist
ein entsprechend hoher Aufwand erforderlich, diese herauszulösen. Damit sinkt der
Wiederverwendbarkeitsgrad von getestetem Code erheblich – ein zum Teil enormer
Kostenfaktor!
Diese zwei (wichtigen) Punkte und noch mehr verspricht die objektorientierte Programmierung zu beheben. Doch dazu wird zunächst ein »Objekt« bzw. eine Klasse
benötigt.
Eine Klasse fasst Daten und zugehörige Manipulationsroutinen in einem programmiersprachlichen Konstrukt zusammen.
Auf das Beispiel der Mitarbeiterverwaltungssoftware bezogen bedeutet dies, dass die
Mitarbeiterdaten (Name, Anschrift, Gehalt) mit der Funktionalität, die diese und
nur diese Daten manipuliert (Gehalt_Buchen(), Befoerdern() etc.), zu einer Einheit gebündelt werden. Dieses Gebilde aus Daten und Funktionen wird dann Klasse
genannt.
Eine Klasse beschreibt damit den Aufbau eines realen Objektes für eine Programmiersprache.
Sie werden im Verlauf dieses Buches sehen, dass auch nicht-existente (im Sinn von
»nicht greifbare«) Dinge wie Fenster einer Programmieroberfläche ebenfalls als Klassen beschrieben werden (Kapitel 17, Windows Forms – Einführung).
Man kann durch die Verwendung von Klassen eine Anwendung so aufbauen, dass sie
aus einem geschickt verwobenen Netz aus Objekten besteht; die Applikation »lebt«
dann alleine durch die Kommunikation zwischen diesen Objekten. Sie sehen, dass
dies ein vollkommen anders gelagertes Programmiermodell darstellt, als Sie es bislang in diesem Buch kennen gelernt haben. Aber die Mühe war nicht umsonst: Alles,
was Sie bisher gelernt haben, werden Sie nun einsetzen. Denn Klassen alleine
machen noch kein fertiges Programm. Die Programmlogik selbst muss nach wie vor
mit den Mitteln der strukturierten Programmierung erstellt werden.
156
Einführung in die objektorientierte Programmierung
1
2
UML
3
UML ist die Abkürzung für Unified Modelling Language. Diese Sprache ist eine
Beschreibungssprache, um Klassengefüge graphisch darzustellen. Abbildung 7.1 zeigt
als Beispiel eine Klasse, die den oben angeführten Mitarbeiter realisiert.
4
5
Mitarbeiter
+Name : string
+Adresse : string
+Gehalt : double
+Gehalt_Buchen()
+Befoerdern()
6
7
Abbildung 7.1 Eine Klasse in UML-Notation
8
Eine Klasse in UML-Notation besteht zunächst aus einem Kasten, der als Beschriftung
in der ersten Zeile den Namen der Klasse enthält. Unterhalb des Namens der Klasse
befinden sich die Daten und unter diesen die Methoden. Eine Beschreibung mehrerer Klassen in dieser Form, in der auch die Beziehungen zwischen Klassen eingetragen sind, bezeichnet man als Klassendiagramm.
9
10
Ich werde Ihnen Grundzüge von UML im Laufe dieses Kapitels vorstellen. Diese
Notationstechnik hat sich als Standard bei der Entwicklung von objektorientierten
Programmen etabliert. Es ist daher ratsam, sich mit dieser vertraut zu machen. Viele
Hilfsprogramme für Entwickler nutzen eine Darstellung von Klassen in UML, um aus
dieser Grundgerüste für Programme zu erzeugen!
11
12
13
Klassen in C#
14
Eine Klasse wird in C# über das Schlüsselwort class, gefolgt vom Klassennamen,
definiert. Die vollständige Syntax zeigt Listing 7.2.
15
[<Sichtbarkeit>] class <Name>
{
}
16
17
Listing 7.2 Die Syntax einer Klassendeklaration
18
Die Angabe eines Sichtbarkeitsspezifizierers ist nicht unbedingt notwendig, sollte
aber dennoch erfolgen. Gültige Werte für <Sichtbarkeit> sind:
19
왘 public
Diese Klasse kann auch außerhalb der Assembly genutzt werden, in der diese
Klasse definiert wurde. Dies ist vor allem für Klassen notwendig, die nach außen
hin für andere (Programme) nutzbar gemacht werden sollen. Per Definition ist
jede Klasse automatisch public, wenn keine Angabe zur Sichtbarkeit gemacht
wird.
20
A
B
왘 internal
Die Klasse ist nur innerhalb der Assembly verwendbar, in der sie definiert wurde.
Eine zweite Assembly kann nicht auf diese Klasse zugreifen.
Klassen und Objekte
157
<Name> muss ein noch freier und gültiger C#-Bezeichner sein. Aus dieser Syntax
ergibt sich die Beschreibung eines Mitarbeiters in C# wie in Listing 7.3 (zunächst
ohne irgendwelche Daten).
01: public class Mitarbeiter
02: {
03: }
Listing 7.3 Ein Mitarbeitergrundgerüst
Der Block, der nach dem Klassennamen geöffnet wird, wird später die Daten und
Methoden der Klasse enthalten. Definiert man auf diese Art eine neue Klasse, so
spricht man auch von einer Klassendeklaration.
Eine Klassendeklaration erzeugt einen neuen Datentyp. Dieser trägt den Namen
der Klasse und wird den Referenztypen zugeordnet.
Erzeugung von Objekten
Eine Klasse alleine ist noch nicht nutzbar, da eine Klasse nur beschreibt, wie ein reales
Objekt in einer Programmiersprache aussieht. Man braucht ein »Objekt«, das im Speicher des Computers residiert, das dann im Programm benutzt werden kann. Diese
Objekte nennt man Instanzen einer Klasse, den Vorgang der Instanz-Erzeugung
Instantiierung.
Instanzen werden mit Hilfe des new-Operators erstellt. Sie haben diesen Operator
bereits bei der Array-Erzeugung benutzt. Die Syntax zur Erzeugung einer Instanz
sehen Sie in Listing 7.4.
<Klassenname> <Bezeichner> = new <Klassenname>();
Listing 7.4 Die Syntax zur Erstellung einer Instanz
Übertragen auf das Beispiel der Klasse Mitarbeiter wird ein Mitarbeiter-Objekt
(wie in Listing 7.5 gezeigt) instantiiert.
01: Mitarbeiter m = new Mitarbeiter();
Listing 7.5 Die Instantiierung eines Mitarbeiter-Objektes
Die Variable m referenziert nun ein Objekt des Typs Mitarbeiter. Man kann diesen
Zusammenhang graphisch wie in Abbildung 7.2 darstellen.
Die Zahlen in der Spalte ganz links entsprechen den Adressen von Speicherzellen im
Rechner. Zur Vereinfachung wird davon ausgegangen, dass der Speicher, wie hier
dargestellt, linear organisiert ist und die Adressen fortlaufend ansteigen. In der rechten Spalte sehen Sie den »Namen«, den die entsprechende Speicherzelle trägt. Die
Zelle mit der Adresse 0001 trägt folglich den Namen m (dies ist die Variable m) und
referenziert ein Mitarbeiter-Objekt.
158
Einführung in die objektorientierte Programmierung
1
2
0001
m
3
0002
0003
0004
0005
4
p
Ty
om ter
v
i
t
j ek rbe
Ob Mita
5
6
0006
0007
7
0008
8
0009
9
0010
Abbildung 7.2 Ein Mitarbeiterobjekt wird über eine Referenz angesprochen.
10
Daten
11
Innerhalb einer Klasse können Daten (Variablen) nach der Syntax aus Listing 7.6
angelegt werden.
12
[<Sichtbarkeit>] <Typ> <Bezeichner> [= <Initialisierung>];
13
Listing 7.6 Syntax zur Deklaration eines Datenmembers innerhalb einer Klasse
Bis auf die Angabe eines Sichtbarkeitsspezifizierers ist dies die normale Deklaration
einer Variablen, wie Sie sie bereits in Kapitel 3, Konstanten, Variablen & Datentypen,
kennen gelernt haben.1
14
Daten, die auf diese Weise einer Klasse hinzugefügt werden, heißen auch Instanzdaten oder Memberdaten (kurz: Member1).
16
15
17
Instanzdaten sind immer einer Instanz zugeordnet. Das heißt, wenn zwei Instanzen
an unterschiedlichen Stellen im Speicher des Computers liegen, so verfügen sie auch
über unterschiedliche Instanzdaten. Diese können inhaltlich gleich sein, jedoch nicht
gleich im Sinn von »an der selben Adresse im Speicher liegend«. Vergleichen Sie
hierzu auch Abbildung 7.3.
18
19
20
In Abbildung 7.3 sind zwei Instanzen der Klasse Mitarbeiter im Speicher zu erkennen (vereinfachte Darstellung). Diese werden über die Referenzen a und b von außen
angesprochen. Jede Instanz verfügt über ihre eigenen Instanzdaten Name, Adresse
und Gehalt. Diese sind also voneinander unabhängig.
A
B
1 Unter dem Begriff Member fasst man sowohl Memberdaten als auch Methoden einer Klasse
zusammen (Beschreibung von Methoden folgt weiter unten in diesem Kapitel).
Klassen und Objekte
159
0001
a
0002
0003
Name
0004
Adresse
0005
Gehalt
0006
0007
0008
0009
0010
0011
0012
0013
Name
0014
Adresse
0015
Gehalt
0016
0017
0018
b
0019
0020
Abbildung 7.3 Zwei Instanzen vom Typ Mitarbeiter im Speicher
Ich möchte die Liste aller möglichen Sichtbarkeitsspezifizierer erst im Abschnitt über
Ableitungshierarchien besprechen, da diese dort thematisch passender angesiedelt
sind. Es kann aber mit Hilfe des Schlüsselwortes public die Klasse Mitarbeiter
(siehe Listing 7.7) geschrieben werden.
01: public class Mitarbeiter
02: {
03:
public string Name;
04:
public string Adresse;
05:
public double Gehalt;
06: }
Listing 7.7 Langsam nimmt die Mitarbeiterklasse Gestalt an.
160
Einführung in die objektorientierte Programmierung
1
2
Die Klasse Mitarbeiter verfügt nun über drei Memberdaten: Name, Adresse und
Gehalt.
3
Instantiiert man ein Objekt, kann man die Instanzdaten über den Memberzugriffsoperator . verändern. Die Syntax zum Zugriff auf ein Instanzdatum sehen Sie in Listing 7.8.
4
5
<Bezeichner>.<Member>
Listing 7.8 Syntax für Zugriff auf ein Memberdatum eines Objekts
6
Mit einer Instanz der Klasse Mitarbeiter ergibt das dann beispielsweise den Code,
den Sie in Listing 7.9 sehen.
7
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
12:
13:
Mitarbeiter m = new Mitarbeiter();
8
// schreibender Zugriff
m.Name = "Max Mustermann";
m.Adresse = "Musterstrasse 1, 99999 Musterstadt";
m.Gehalt = 1.0;
9
10
11
// lesender Zugriff
Console.WriteLine("Mitarbeiter: {0} ({1}, Gehalt = {2}",
m.Name,
m.Adresse,
m.Gehalt);
12
13
Listing 7.9 Zugriff auf den Member einer Instanz vom Typ Mitarbeiter
14
Zunächst wird in Zeile 1 von Listing 7.9 ein neues Objekt vom Typ Mitarbeiter
angelegt. Die Referenz, die der new-Operator zurückgibt, wird in der Variablen m gesichert. Über diesen Bezeichner und mit Hilfe des Operators . werden in den Zeilen
4–6 die Memberdaten Name, Adresse und Gehalt mit entsprechenden Werten belegt
(schreibender Zugriff).
15
16
17
In den Zeilen 9–13 wird dann der Inhalt der Instanzdaten auf dem Bildschirm ausgegeben.
18
Lebenszeit eines Objekts
19
Sie wissen bereits, dass eine Variable ihre Gültigkeit verliert, sobald der Block verlassen wird, in dem die Variable deklariert wurde. Was passiert dann, wenn eine Variable, die eine Referenz auf ein Objekt hält, ungültig wird?
20
Um auf diese Frage eine befriedigende Antwort geben zu können, möchte ich Ihnen
das Thema Referenz- bzw. Wertetypen in Erinnerung rufen: Wertetypen sind alle einfachen Datentypen der Sprache C# sowie Strukturen. Zu den Referenztypen gehören
neben den Arrays auch Klassen bzw. Objekte.
Klassen und Objekte
A
B
161
Der Datentyp string stellt eine Ausnahme dar: Er wird nicht wie die anderen Typen
zu den Wertetypen gerechnet, sondern zu den Referenztypen.
Weist man einer Variablen den Wert einer anderen Variablen zu, so wird dann der
Wert kopiert, wenn es sich bei dem Datentyp um einen aus der Kategorie Wertetypen
handelt (Strukturen werden dann Member für Member kopiert oder »geklont«).
Bei Referenztypen ist dies anders: Weist man eine Variable einer anderen zu und handelt es sich um Referenzen, wird nur die Referenz kopiert, jedoch nicht das Objekt
selbst vervielfältigt. Listing 7.10 zeigt ein Beispiel, wie dies in C#-Code aussehen
kann.
01:
02:
03:
04:
Mitarbeiter a = new Mitarbeiter();
Mitarbeiter b = a;
b.Name = "Bernhard Volz";
Console.WriteLine("Mitarbeiter: {0}", a.Name);
Listing 7.10 Zuweisung von Referenztypen
In Listing 7.10 verweisen beide Referenzen a und b auf dieselbe Instanz im Speicher
des Computers. Eine Änderung eines Memberdatums (egal ob über Referenz a oder
b) greift auf die gleichen Instanzdaten zurück wie die jeweilige andere Referenz. Dieser Zusammenhang ist in Abbildung 7.4 zum besseren Verständnis nochmals graphisch dargestellt.
0001
a
0002
0003
Name
0004
Adresse
0005
Gehalt
0006
0007
0008
b
0009
0010
Abbildung 7.4 Die gleiche Instanz wird von zwei Variablen referenziert.
Was passiert also hinter den Kulissen, wenn eine Referenz ungültig wird? Verliert
eine Variable, die auf ein Objekt verweist, ihre Gültigkeit, so muss nicht zwingend
das Objekt aus dem Speicher entfernt werden. Es kann noch andere Variablen geben,
die das gleiche Objekt referenzieren und noch gültig sind.
162
Einführung in die objektorientierte Programmierung
1
2
Bereits des Öfteren war die Rede vom Garbage Collector. Dieser überprüft bei Bedarf,
d.h. wenn der Speicher für ein Programm zur Neige geht bzw. eine bestimmte
Anzahl Speicherblöcke belegt ist, ob Objekte vorhanden sind, die nicht mehr referenziert werden. Findet der Garbage Collector solche Objekte, zerstört er sie und gibt
den belegten Speicher wieder frei.2
3
4
5
Um den Speicher zu überprüfen, muss ein Programm vom Garbage Collector angehalten werden. Dank eines trickreichen Algorithmus’ fällt die Zeit, in der die Anwendung inaktiv ist, äußerst kurz aus. Man kann diese daher in der Regel guten Gewissens vernachlässigen.
6
7
7.1.2
Objektmethoden
8
Daten alleine zeichnen noch keine Klasse aus. Hält eine Klasse nur Daten, ist sie im
Grunde nichts anderes als eine Struktur. Klassen können noch Methoden enthalten.
9
Methoden verleihen dem Objekt Funktionalität. Sie sind es, die normalerweise die
Datenmitglieder verarbeiten oder einfach Auskunft über sie liefern. Immer wenn Sie
in den vergangenen Kapiteln ein C#-Programm geschrieben haben, haben Sie eine
Methode deklariert: Main(). Lässt man bei dieser einmal das Schlüsselwort static
außer Acht, so ergibt sich daraus die allgemeine Form einer Methodendeklaration,
wie sie in Listing 7.11 zu sehen ist.
10
11
12
[<Sichtbarkeit>] <Rückgabetyp> <Name>(<Parameter>)
{
// Funktionsrumpf
}
13
14
Listing 7.11 Die Syntax zur Deklaration einer Objekt-Methode
15
Auch hier möchte ich die Anführung des Sichtbarkeitsspezifizierers auf später verschieben. Nehmen Sie einfach an, dieser ist im Moment wie bei den Memberdaten
auf public gesetzt.
16
Eine Methode kann ein Ergebnis an den Aufrufer zurückgeben, muss dies aber nicht.
Der Typ des Ergebnisses ist in das Feld <Rückgabetyp> einzusetzen. Erzeugt die
Methode keinen Rückgabewert, so ist dort der Datentyp void einzusetzen. Nehmen
Sie als Beispiel eine Methode, die das Quadrat einer Zahl errechnen und dieses an den
Aufrufer zurückgeben soll: Hier wäre je nach Anforderung als Rückgabetyp entweder
int oder double einzusetzen. Anstatt primitiver Datentypen kann eine Methode
auch komplexe Datenstrukturen (Strukturen, Enumerationen, Felder, Objekte etc.)
zurückgeben. Der Aufrufer kann das Ergebnis eines Methodenaufrufes verarbeiten
oder ignorieren.
17
18
19
20
A
B
2 In Wirklichkeit ist der Vorgang der Garbage Collection hochgradig verzwickt, da noch viele
weitere Faktoren Einfluss nehmen. Für das grundlegende Verständnis reicht der hier geschilderte Zusammenhang jedoch aus. Im Microsoft Developer Network finden sich viele weiterführende Artikel zum Thema Garbage Collection in .NET.
Klassen und Objekte
163
Der <Name> einer Methode ist ein normaler C#-Bezeichner mit einer Ausnahme: Er
muss ungleich dem Klassennamen sein. Methoden, die den Klassennamen tragen,
stellen einen Spezialfall dar und werden gesondert behandelt.
Die Funktion einer Methode kann davon abhängen, welche Parameter beim Aufruf
der Methode übergeben wurden. Man spricht in diesem Fall von einer Parameterliste, die hinter dem Methodennamen in runde Klammern gesetzt werden muss. Eine
Parameterliste kann auch leer sein! Eine Parameterliste lässt sich nach der in Listing
7.12 gezeigten Syntax konstruieren.
[<Datentyp> <Bezeichner>[, <Datentyp> <Bezeichner>[, ... ]]]
Listing 7.12 Syntax für den Aufbau einer Parameterliste
Eine Parameterliste zählt, außer sie ist leer, die Parameter der Reihe nach auf. Wichtig
ist, dass die Reihenfolge der Parameter in der Parameterliste und beim späteren Aufruf der Methode gleich ist.
Eine Parameterangabe setzt sich aus einem <Datentyp> und einem <Bezeichner>
zusammen, über den im Methodenrumpf auf den Wert des Parameters zugegriffen
werden kann. Mehrere Parameter werden durch ein Komma voneinander getrennt.
Eine Parameterliste kann sehr viele Parameter enthalten. In der Praxis sind Methoden
unbeliebt, die mehr als zehn Parameter besitzen, da man sehr leicht den Überblick
über diese verliert. In solchen Fällen empfiehlt es sich, die Parameter zu einer Struktur zu bündeln oder noch besser zu überprüfen, ob wirklich alle Parameter benötigt
werden oder ob nicht durch Aufteilung in mehrere Methoden die Parameterlisten
verkürzt werden können.
Innerhalb der Methode (d.h. im Methodenrumpf, der nun Code enthält, wie Sie ihn
in den letzten Kapiteln zu schreiben gelernt haben) kann nicht nur auf die dort deklarierten Variablen, sondern auch auf die Parameter und die Memberdaten des Objektes direkt zugegriffen werden.
Listing 7.13 zeigt ein Beispiel für eine Implementierung der Methode Befoerdern(),
das ein Mitarbeiterobjekt auf eine höhere Gehaltsstufe hebt. Hierzu greift die
Methode direkt auf die Instanzdaten des Objektes über den Operator . zu.
01: public class Mitarbeiter
02: {
03:
public string Name;
04:
public string Adresse;
05:
public double Gehalt;
06:
07:
public void Befoerdern()
08:
{
09:
Gehalt += 100;
164
Einführung in die objektorientierte Programmierung
1
2
10:
11: }
}
3
Listing 7.13 Die Klasse Mitarbeiter erhält eine Methode, die eine dramatische Erhöhung des
Memberdatums Gehalt als Beförderung des Mitarbeiters durchführt.
4
Der Aufruf einer Objekt-Methode
5
Der Aufruf einer Methode erfolgt wie der Zugriff auf ein Datum über den .-Operator
und den Methodenaufrufoperator (). Die Aufrufzeile muss dabei der Syntax aus Listing 7.14 genügen.
6
<Objektbezeichner>.<Methodenname>([<Parameter>]);
7
Listing 7.14 Syntax eines Methodenaufrufs
8
Es werden demzufolge für den Aufruf einer Methode eine Instanz sowie der Name
der aufzurufenden Methode benötigt. In runden Klammern werden Parameter übergeben, falls eine Methode über eine nicht leere Parameterliste verfügt. Erwartet eine
Methode keine Parameter, so sind die runden Klammern leer zu lassen.
9
10
In Listing 7.15 sehen Sie ein Beispiel für eine Main()-Methode, die zunächst ein
Objekt vom Typ Mitarbeiter erstellt und anschließend an diesem Objekt die
Methode Befoerdern() aufruft. Zum Schluss wird noch eine Ausgabe erzeugt, die
das aktuelle Gehalt ausgibt.
11
12
01: public class Mitarbeiter
02: {
03:
public string Name;
04:
public string Adresse;
05:
public double Gehalt;
06:
07:
public void Befoerdern()
08:
{
09:
Gehalt += 100;
10:
}
11:
12:
public static void Main()
13:
{
14:
Mitarbeiter m = new Mitarbeiter();
15:
m.Gehalt = 100;
16:
m.Befoerdern();// Mitarbeiter befoerdern
17:
Console.WriteLine("Gehalt = {0}", m.Gehalt);
18:
}
19: }
13
14
15
16
17
18
19
20
A
B
Listing 7.15 Der Aufruf einer Objektmethode erfolgt über den .-Operator.
Klassen und Objekte
165
Betrachten Sie einmal die Methode Main() aus Listing 7.15 näher: Die Methode steht
innerhalb der Klasse Mitarbeiter, erstellt ein Objekt vom Typ Mitarbeiter und
manipuliert dieses.
Bislang haben Sie aber nur gelernt, dass für Methoden wie für Daten immer eine
Instanz notwendig ist. Wie kann also die Methode Main() angesprungen werden,
wenn es noch kein Objekt Mitarbeiter gibt, das diese Methode enthalten könnte?
Die Antwort auf diese Frage liegt im Schlüsselwort static: Dieses verschiebt die
Methode Main() in den Kontext einer Klasse anstatt eines Objektes. Daher kann
diese Methode ohne eine Instanz angesprungen werden. Ich werde Ihnen in
Abschnitt 8.3.1 von Kapitel 8, Generische Klassen, Schnittstellen und statische Klassenmitglieder, dieses Prinzip noch detailliert erläutern.
Objektmethoden mit Parametern
Sie haben weiter oben gehört, dass eine Methode Parameter erwarten kann. In Listing 7.16 sehen Sie ein Beispiel für eine fiktive Methode Foo() in einer Klasse Demo,
die zwei Parameter vom Typ int erwartet. Foo() führt dabei keine Berechnungen
durch, sondern gibt die Werte der beiden Parameter auf dem Bildschirm aus.
01: public class Demo
02: {
03:
public void Foo(int a, int b)
04:
{
05:
Console.WriteLine("a = {0}, b = {1}", a, b);
06:
}
07:
08:
public static void Main()
09:
{
10:
int x = 3;
11:
Demo d = new Demo();
12:
13:
d.Foo(x, 2);
14:
}
15: }
Listing 7.16 Eine Methode, die beim Aufruf zwei Parameter erwartet
Beim Aufruf der Methode Foo() in Zeile 13 von Listing 7.16 werden die beiden Parameter der Methode a und b mit den Werten der Variablen x bzw. dem Literal 2 belegt.
x bzw. 2 bezeichnet man auch als Argumente der Methode Foo().
Man kann diesen Zusammenhang auch in so genannten Tracing-Tabellen darstellen:
Diese zeigen für jede interessante Code-Zeile den Wert von Parametern und lokalen
Variablen auf (welche dies sind, hängt vom jeweiligen Anwendungsfall ab).
166
Einführung in die objektorientierte Programmierung
1
2
Zeile
x
a
b
10
3
undef
undef
13
3
3
2
05
undef
3
2
3
4
5
Tabelle 7.1 Tracing-Tabelle für den Aufruf der Methode Foo() aus Listing 7.16
6
Tracing-Tabellen werden von oben nach unten und von links nach rechts gelesen. In
der linken Spalte stehen die Zeilennummern der interessanten Code-Stücke, in weiteren Spalten findet man die Werte für die in der Kopfzeile genannten Variablen.
7
Der Eintrag undef in einer Tracing-Tabelle bedeutet, dass die entsprechende Variable
in einer Zeile nicht definiert bzw. nicht zugreifbar ist.
8
9
Parameterübergabemechanismen
Ein Parameterübergabemechanismus beschreibt, wie ein Parameter an die aufgerufene Funktion weitergeleitet wird. In C# gibt es zwei Stück, die man sauber voneinander trennen muss. Außerhalb von .NET können sonst unbeabsichtigte Seiteneffekte3 auftreten, die im günstigsten Fall zum Programmabsturz führen.4 In .NET wird
der »günstigste Fall« durch das Auslösen einer Exception (Kapitel 10, Ausnahmen –
Exceptions) garantiert. Den folgenden Ausführungen liegt als Methodenbeispiel Listing 7.17 zu Grunde.
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
10
11
12
13
public void Foo(int a, int b)
{
a *= b;
b += b;
}
public static void Main()
{
int x = 3;
Foo(x, 2);
}
14
15
16
17
18
Listing 7.17 Beispiel für die Erläuterung der Parameterübergabemechanismen
19
Handelt es sich bei Parametern um Wertetypen, wird der Wert kopiert, d.h. es existiert eine Kopie im Speicher, die unabhängig vom Original manipuliert wird. Man
spricht dann von call-by-value. Abbildung 7.5 veranschaulicht die Situation beim Aufruf der Methode Foo() in Zeile 9 aus Listing 7.17.
20
A
3 Unter einem Seiteneffekt versteht man die Änderung von Daten, die sich nicht im selben
Kontext (Objekt) wie die manipulierende Methode befinden.
4 Diese Aussage stammt aus der vergangenen DOS-Ära: Es ist damit gemeint, dass ein Programm durch fehlerhafte Daten und einen dummen Zufall auch Code »produzieren« könnte,
der die Festplatte formatiert und damit alle Daten löschen würde.
Klassen und Objekte
B
167
0001
0002
0003
3
x
0006
3
a
0007
2
b
0004
0005
0008
0009
0010
Abbildung 7.5 Der Parameterübergabemechanismus call-by-value
Es ist zu erkennen, dass die Parameter a und b zwei eigene Plätze im Speicher bekommen haben, die unabhängig von der Variablen x sind. In die Speicherzellen wurden
die (Zahlen-)Werte beim Aufruf von Foo() kopiert.
Zeile
x
a
b
9
3
undef
undef
1
3
3
2
3
3
6
2
4
3
6
4
9 (Rückkehr aus
foo())
3
undef
undef
Tabelle 7.2 Tracing-Tabelle für call-by-value
Anhand der Tracing-Tabelle (Tabelle 7.2) erkennt man, dass Manipulationen an beiden Parametern nur zu einer lokalen Änderung der Werte führen. Auswirkungen auf
den Parameter x sind nicht zu verzeichnen.
Wird anstelle des Wertes ein Verweis (d.h. eine Referenz) auf eine Variable kopiert,
ergibt sich die Situation wie in Abbildung 7.6 dargestellt.5 Änderungen an den Parametern wirken dann nicht auf eine Kopie, sondern auf das Original und manipulieren
dieses (Tabelle 7.3).
5 Stellen Sie sich in diesem Zusammenhang eine Referenz als einen Pfeil vor, wie in der Abbildung angezeigt. Wie eine Referenz in der Realität aussieht, spielt für das Verständnis keine
Rolle.
168
Einführung in die objektorientierte Programmierung
1
2
0001
3
3
0002
0003
4
x
0004
2
5
0005
0006
a
0007
b
6
7
0008
8
0009
9
0010
Abbildung 7.6 Referenzen bei Übergabe an eine Methode
10
Wert
x
a
b
9
3
undef
undef
1
3
3
2
3
6
6
2
4
6
6
4
9 (Rückkehr aus
foo())
6
undef
undef
11
12
13
14
15
Tabelle 7.3 Tracing-Tabelle für den Fall, dass Parameter als Referenz übergeben wurden
16
Werden Referenzen übergeben, ändern sich also auch die Originale. Man nennt diese
Art von Parameterübergabe call-by-reference.
17
Referenzen auf Variablen können mit Hilfe der Schlüsselwörter ref und out erzeugt
werden (vergleichen Sie hierzu Abschnitt 7.5). C# verwendet beide Mechanismen:
Der Programmierer muss beim Aufruf einer Methode den Übergabemechanismus
der Parameter kennen.
18
19
Selbstreferenz: this
20
Trägt ein Parameter denselben Namen wie ein Memberdatum, so überdeckt der Parameter den Member (Listing 7.18).
A
01: public class Demo
02: {
03:
public int a;
B
Klassen und Objekte
169
04:
05:
06:
07:
08: }
public void Foo(int a)
{
// Datenmember a soll um Parameter a erhöht werden
}
Listing 7.18 Parameter überdecken Memberdaten, die denselben Namen tragen.
Um trotzdem auf die Daten eines Objekts in einer Methode zugreifen zu können, gibt
es in jeder Objektmethode implizit eine spezielle Referenz, die auf das umgebende
Objekt zeigt: Diese trägt den Namen this. Die this-Referenz kann wie eine normale
Referenz verwendet werden. Der .-Operator ermöglicht im Zusammenspiel mit ihr
den Zugriff auf die Member des Objektes.
Listing 7.19 zeigt das Beispiel aus Listing 7.18, so dass trotz des gleichen Namens mit
Hilfe der Selbstreferenz auf das Memberdatum a zugegriffen werden kann.
01: public class Demo
02: {
03:
public int a;
04:
public void Foo(int a)
05:
{
06:
this.a += a;
07:
}
08: }
Listing 7.19 Über den Bezeichner this steht in Objektmethoden eine Referenz auf das Objekt zur
Verfügung.
Über this.a wird auf den Datenmember a der Klasse Demo zugegriffen, mittels a auf
den Parameter der Methode Foo().
Methoden mit Rückgabewert
Es soll eine Methode implementiert werden, die die Summe der Quadrate zweier
Parameter ermittelt. Damit dieses Zwischenergebnis weiterverwendet werden kann,
ist es erforderlich, dass dieses Ergebnis an den Aufrufer zurückgegeben wird. Listing
7.20 zeigt den ersten Ansatz, eine solche Funktion zu schreiben.
01: public int Sum_quad(int a, int b)
02: {
03:
int result = (a * a) + (b * b);
04: }
Listing 7.20 Eine Methode, die die Summe der Quadrate zweier Parameter ermittelt
Die Methode Sum_quad() aus Listing 7.20 erwartet zwei Parameter vom Typ int und
zeigt an, dass sie ebenfalls einen Wert vom Typ int zurückgibt. Dies geht aus der
Deklaration der Methode hervor.
170
Einführung in die objektorientierte Programmierung
1
2
Der Methodenrumpf implementiert die Funktionalität, d.h. die Summenbildung aus
den Quadraten der Parameter a und b. Die Summe wird in der Variablen result zwischengespeichert (Zeile 3).
3
4
Es fehlt noch die Rückgabe des zwischengespeicherten Ergebnisses an den Aufrufer.
Diese erfolgt mit Hilfe des Schlüsselwortes return.
5
return <Wert>;
Listing 7.21 Die Syntax von return
6
return bewirkt, dass die Ausführung der Methode an der Stelle beendet wird, an der
das Schlüsselwort im Quellcode auftaucht. Der hinter return anzugebende Wert
7
wird dann an den Aufrufer zurückgegeben (Listing 7.22).
8
01: public int Sum_quad(int a, int b)
02: {
03:
int result = (a * a) + (b * b);
04:
return result;
05: }
9
10
11
Listing 7.22 Das Schlüsselwort return leitet Ergebnisse an den Aufrufer weiter und beendet die
Methode.
12
In Listing 7.23 sehen Sie einen Beispielaufruf der Methode Sum_quad(). Dazu wird
davon ausgegangen, dass die Variable x eine Referenz auf ein Objekt enthält, das über
die Methode Sum_quad() verfügt. Das Ergebnis der Methode wird zur weiteren Verarbeitung in der Variablen res gesichert.
13
14
01: /* x ist im folgenden ein Objekt, das die Methode
02:
Sum_quad implementiert */
03: int res = x.Sum_quad(2, 4);
15
Listing 7.23 Der Rückgabewert wird zur weiteren Verarbeitung in einer Variablen gesichert.
16
Zeigt eine Methode durch ihre Deklaration an, dass sie einen Wert zurückgibt, so
muss auch ein Wert zurückgegeben werden, d.h. es muss eine return-Anweisung
in der Methode auftauchen.
17
return kann auch in Methoden verwendet werden, die als Rückgabetyp void besitzen. In diesem speziellen Fall dient return nur zum Verlassen der Methode. Ein
<Wert> kann in diesem Fall nicht angegeben werden und muss daher weggelassen
19
18
20
werden.
A
01: public void Foo(int a)
02: {
03:
/* Verarbeitung hier */
04:
if (a == 0)
05:
return;
B
Klassen und Objekte
171
06:
07: }
/* weitere Verarbeitung hier */
Listing 7.24 return in Verbindung mit einer Methode, die kein Ergebnis liefert
Listing 7.24 zeigt die Verwendung des Schlüsselwortes return in Verbindung mit
dem Rückgabetyp void. Es macht zumeist nur dann Sinn, return in solchen Methoden zu verwenden, um im Fehlerfall die Verarbeitung abzubrechen. Der Grund dafür
ist, dass das Ende des Methodenrumpfes die Methode sowieso beendet.
Befindet sich hinter dem Schlüsselwort return noch Programmcode, so wird dieser
nie ausgeführt. Der Compiler erzeugt in diesem speziellen Fall eine Warnung, keinen
Fehler.
Eine Methode kann auch mehrere return-Anweisungen enthalten. Dies macht
durchaus Sinn, wenn beispielsweise mehrere Fehlerfälle abgefragt werden müssen.
Es ist aber darauf zu achten, dass jeder Pfad6 durch eine Methode dann auch einen
Wert zurückgibt. Andernfalls erhält man eine entsprechende Meldung vom Compiler.
Viele return-Anweisungen können aber auch hinderlich sein, insbesondere bei großen Methoden. Man kann dann sehr leicht den Überblick verlieren. Als Hinweis sei
Ihnen daher gesagt, dass fast jede return-Anweisung, die für einen Fehlerfall steht,
auch über entsprechend geschachtelte if-Abfragen dargestellt werden kann. Dies ist
dann möglich, wenn im Fehlerfall stets derselbe Wert zurückgegeben wird.
7.1.3
Eine spezielle Methode: Der Konstruktor
Eine spezielle Methode ist der Konstruktor einer Klasse. Er zeichnet sich dadurch aus,
dass er denselben Namen wie die Klasse trägt und keinen Rückgabewert (auch kein
void!) besitzt.
Ein Konstruktor wird, wie der Name vermuten lässt, immer dann durchlaufen, wenn
eine neue Instanz einer Klasse erzeugt (»konstruiert«) wird. Man kann ihn daher für
Initialisierungsarbeiten an den Memberdaten nutzen.
<Sichtbarkeit> <Klassenname>([<Parameter>])
{
// Konstruktorcode
}
Listing 7.25 Syntax für die Deklaration eines Konstruktors
Auch ein Konstruktor kann, der Syntax aus Listing 7.25 zufolge, über Parameter verfügen. Wie Parameter beim Erzeugen einer Instanz übergeben werden, sehen Sie später. Ich möchte mich zunächst mit dem Standardfall beschäftigen: keine Parameter.
6 Mit einem Pfad durch eine Methode ist jeder mögliche Weg, bezogen auf die Darstellung der
Methode durch ein NSD, gemeint.
172
Einführung in die objektorientierte Programmierung
1
2
Der Standardkonstruktor
3
Besitzt ein Konstruktor keine Parameter, so nennt man ihn Standardkonstruktor. Ein
weiterer Grund für diesen Namen ist, dass Klassen, die scheinbar keinen Konstruktor
besitzen, da eine entsprechende Deklaration fehlt, dennoch standardmäßig einen
Konstruktor ohne Parameter besitzen. Dieser wird wiederum vom Compiler erzeugt.
4
5
Wird dagegen ein Standardkonstruktor deklariert, so unterbleibt die Generierung
eines solchen durch den Übersetzer. Listing 7.26 zeigt die Klasse Mitarbeiter, nachdem sie einen Standardkonstruktor erhalten hat.
6
01: public class Mitarbeiter
02: {
03:
public string Name;
04:
public string Adresse;
05:
public double Gehalt;
06:
07:
public void Befoerdern()
08:
{
09:
Gehalt += 100;
10:
}
11:
12:
public Mitarbeiter()
13:
{
14:
Name = "<neuer Mitarbeiter>";
15:
Adresse = "<neuer Mitarbeiter>";
16:
}
17: }
7
8
9
10
11
12
13
14
15
Listing 7.26 Die Klasse Mitarbeiter erhält einen Standardkonstruktor.
Der Konstruktor der Klasse Mitarbeiter in Listing 7.26 belegt die Memberdaten
Name und Adresse mit vordefinierten Werten. Beachten Sie, dass der Member
Gehalt nicht gesondert auf 0 zurückgesetzt wird. Dies wäre unnötig, da er ja bereits
bei seiner Erstellung auf den Wert 0 gesetzt wird.
16
Schauen Sie sich nun noch einmal die Erstellung einer Instanz vom Typ Mitarbeiter
an (Listing 7.27).
18
17
19
01: Mitarbeiter m = new Mitarbeiter();
Listing 7.27 Die Erstellung einer Instanz durch den new-Operator
20
Lässt man den linken Teil von Zeile 1 aus Listing 7.27 einmal außer Acht, so hat man
nur noch new Mitarbeiter(). Dies sieht bis auf das Schlüsselwort new wie ein »normaler Methodenaufruf« aus. Tatsächlich wird auch durch diese Zeile der Standardkonstruktor aufgerufen.
Klassen und Objekte
A
B
173
Initialisierungsreihenfolge
Eine berechtigte Frage ist nun, in welcher Reihenfolge die Initialisierungen ausgeführt werden.
Die Antwort auf die Frage ist einsichtig, berücksichtigt man, dass ein Konstruktor für
Initialisierungsarbeiten genutzt werden kann. Kann er die Member einer Klasse
bereits manipulieren, müssen diese vor dem Aufruf des Konstruktors erzeugt worden
sein. Wurden sie erzeugt, sind auch die Initialisierungen (mit Default-Wert oder
einem anderen, gegebenen Wert) der Member bereits abgeschlossen.
Ein Konstruktor kann daher auf voll initialisierte Member zugreifen. Er kann ebenfalls Methoden des Objektes rufen und die this-Referenz nutzen.
Konstruktoren mit Parametern
Konstruktoren, die Parameter erwarten, werden dann genutzt, wenn ein Member der
Klasse mit einem Wert initialisiert werden soll. Ein Konstruktor mit Parametern verhindert die Erzeugung eines Standardkonstruktors durch den Compiler.
Listing 7.28 zeigt die Klasse Mitarbeiter mit einem Konstruktor, der als Parameter
das Anfangsgehalt des Mitarbeiters erwartet. Diese Angabe wird dann genutzt, um
das Memberdatum Gehalt entsprechend zu initialisieren.
01: public class Mitarbeiter
02: {
03:
public string Name;
04:
public string Adresse;
05:
public double Gehalt;
06:
07:
public void Befoerdern()
08:
{
09:
Gehalt += 100;
10:
}
11:
12:
public Mitarbeiter(double Gehalt)
13:
{
14:
Name = "<neuer Mitarbeiter>";
15:
Adresse = "<neuer Mitarbeiter>";
16:
this.Gehalt = Gehalt;
17:
}
18: }
Listing 7.28 Die Klasse Mitarbeiter mit einem Konstruktor, der als Parameter das Anfangsgehalt
des Mitarbeiters erwartet
Eine Erzeugung durch new Mitarbeiter() ist nun nicht mehr möglich, da nun kein
Standardkonstruktor mehr existiert und Konstruktoren mit Parametern auch eine entsprechende Angabe erwarten.
174
Einführung in die objektorientierte Programmierung
1
2
Daher muss die Syntax aus Listing 7.4 um die Angabe von Parametern zu einer allgemeinen Form erweitert werden. Diese allgemeine Syntax zur Erzeugung von Objekten zeigt Listing 7.29.
3
new <Klassenname>([<Parameter>])
4
Listing 7.29 Allgemeine Syntax zur Erzeugung von Objekten
5
Die Parameter der Konstruktoren müssen demzufolge in die runden Klammern eingeschoben werden.
6
01: Mitarbeiter m = new Mitarbeiter(1400);
7
Listing 7.30 Die Erzeugung eines Mitarbeiter-Objektes mit einem vorgegebenen Gehalt
8
Listing 7.30 zeigt die Erzeugung eines Mitarbeiter-Objektes, das als Grundgehalt
1400 (€) erhält. Besitzt ein Konstruktor mehr als nur einen Parameter, so müssen
diese wie bei einem Methodenaufruf als kommaseparierte Liste übergeben werden.
9
10
Mehrere Konstruktoren
Obwohl durch die Angabe eines Konstruktors mit einem (oder mehr) Parametern der
Standardkonstruktor überschrieben wurde, kann man dennoch einen neuen erzeugen. Dies lässt sich auch verallgemeinern zu:
11
Eine Klasse kann mehrere Konstruktoren enthalten, die sich im Typ und/oder in
der Anzahl der Parameter unterscheiden.
13
12
14
Diese Konstruktoren existieren parallel in einer Klasse, d.h. es wird zur Laufzeit (also
bei der Erstellung eines Objektes) durch die Angabe von Parametern bei Erzeugung
entschieden, welcher Konstruktor durchlaufen wird.
15
16
Listing 7.31 zeigt die Klasse Mitarbeiter mit nunmehr zwei Konstruktoren: einem
Standardkonstruktor und einem, der einen Parameter enthält und mit diesem den
Member Gehalt initialisiert.
17
01: public class Mitarbeiter
02: {
03:
public string Name;
04:
public string Adresse;
05:
public double Gehalt;
06:
07:
public void Befoerdern()
08:
{
09:
Gehalt += 100;
10:
}
11:
18
19
20
A
B
Klassen und Objekte
175
12:
13:
14:
15:
16:
17:
19:
20:
21:
22:
23:
24:
25: }
public Mitarbeiter()
{
Name = "<neuer Mitarbeiter>";
Adresse = "<neuer Mitarbeiter>";
}
public Mitarbeiter(double Gehalt)
{
Name = "<neuer Mitarbeiter>";
Adresse = "<neuer Mitarbeiter>";
this.Gehalt = Gehalt;
}
Listing 7.31 Die Klasse Mitarbeiter mit zwei Konstruktoren
Nach Listing 7.31 und der oben angeführten Definition ist es nun möglich, Mitarbeiterobjekte auf zwei unterschiedliche Arten zu erzeugen.
01: Mitarbeiter m1 = new Mitarbeiter();
02: Mitarbeiter m2 = new Mitarbeiter(1400);
Listing 7.32 Die Angabe von zwei Konstruktoren ermöglicht zwei unterschiedliche Aufrufe des
new-Operators.
Listing 7.32 erstellt in Zeile 1 ein Mitarbeiter-Objekt, indem es den Standardkonstruktor nutzt. In Zeile 2 wird dagegen ein Mitarbeiter-Objekt erstellt, das als Startgehalt 1400 besitzt, also der Konstruktor mit einem Parameter gerufen wird.
Beachten Sie, dass es möglich ist, zwei Konstruktoren zu deklarieren, die dieselbe
Anzahl Parameter besitzen. Die Parameterlisten müssen sich aber zumindest im Typ
eines Parameters unterscheiden!
Strukturen
In Kapitel 4, Zusammengesetzte Datentypen und Namensräume, haben Sie gehört, dass
auch Strukturen Konstruktoren enthalten können. Allerdings mit einer Einschränkung: Strukturen können keine Standardkonstruktoren enthalten!
Statische Konstruktoren
Klassen können statische Konstruktoren enthalten. Statische Konstruktoren werden
vom System vor der ersten Erzeugung einer Instanz bzw. vor dem ersten Zugriff auf
einen statischen Member (Kapitel 8, Generische Klassen, Schnittstellen und statische
Klassenmitglieder) aufgerufen. Meistens wird er dazu verwendet, um zusätzliche Meldungen in eine Protokolldatei zu schreiben.
Statische Konstruktoren werden vom System gerufen. Der Zeitpunkt, wann ein solcher Aufruf erfolgt, ist bis auf die Angabe oben nicht näher festgelegt.
176
Einführung in die objektorientierte Programmierung
1
2
Der Programmierer kann statische Konstruktoren nicht explizit aufrufen.
3
static <Klassenname>()
{
// Code des statischen Konstruktors
}
4
5
Listing 7.33 Syntax der Deklaration eines statischen Konstruktors
6
Bei der Deklaration eines statischen Konstruktors muss beachtet werden, dass dieser
weder eine Sichtbarkeitsangabe erlaubt noch Parameter erwarten darf.
7
01: class Demo
02: {
03:
static Demo()
04:
{
05:
Console.WriteLine("Statischer Konstruktor");
06:
}
07:
08:
public static void Main()
09:
{
10:
Console.WriteLine("In Main()");
11:
}
12: }
8
9
10
11
12
13
Listing 7.34 Ein Beispiel, das veranschaulicht, wann der statische Konstruktor aufgerufen wird
14
Bevor daher die Main()-Methode ihre Ausgabe in Zeile 10 tätigen kann, ruft das System den statischen Konstruktor auf. Dieser schreibt nun seinerseits eine kurze Meldung auf den Bildschirm.
15
16
Eine Struktur kann ebenfalls über einen statischen Konstruktor verfügen.
7.1.4
17
Die Vererbungslehre in der Programmiersprache
18
Die objektorientierte Programmierung (OOP) bringt als weiteren Vorteil mit sich,
dass Objekte Daten und Methoden von einem anderen Objekt erben können. Das
heißt, dass ein Objekt, das von einem Vaterobjekt erbt, über dieselben Daten und
Methoden wie das Vaterobjekt verfügt.
19
20
Im Klassendiagramm wird die Beziehung der Vererbung durch einen Pfeil eingetragen, dessen Spitze auf die Vaterklasse zeigt. Die umgekehrte Richtung der Ableitung
einer Klasse bezeichnet man in UML auch mit Generalisierung. Abbildung 7.7 zeigt
die Darstellung einer Ableitung in UML.
Klassen und Objekte
A
B
177
Vater
Kind
Abbildung 7.7 Ableitung in UML-Notation
Wie in der Biologie kann auch eine Klasse in C# nicht mehr als einen Vater besitzen,
d.h. eine Klasse kann nur von einem Vater Methoden und Memberdaten erben. Die
abgeleitete Klasse wird auch oft als Kind (engl. Child) bezeichnet. Die Klasse, von der
eine weitere abgeleitet ist, nennt man entweder Vater- oder Basisklasse (engl. base
class).
Sind in einem Klassendiagramm hauptsächlich Ableitungen dargestellt, so spricht
man auch von einer Ableitungshierarchie. Eine Ableitung kann sich auch über mehrere Ebenen erstrecken. Ein Kind erbt dann alle Member, die sein Vater besitzt, und
die, die der Vater ebenfalls geerbt hat. Vergleichen Sie hierzu auch Abbildung 7.8.
Klasse1
Klasse2
Klasse3
Klasse7
Klasse4
Klasse5
Klasse6
Abbildung 7.8 Eine komplexere Ableitungshierarchie
Eine Generalisierung oder Ableitung sollte immer sinnvoll sein. Da eine Ableitung
sprachlich durch eine »ist-ein«-Beziehung ausgedrückt werden kann, ist sehr leicht zu
erkennen, wann eine Ableitung Sinn macht und wann nicht.
Nimmt man aus Abbildung 7.9 den Abteilungsleiter heraus, so ist dieser auch ein
Führungskreismitglied. Ein Führungskreismitglied ist wiederum ein LeitenderAngestellter, der ein Mitarbeiter ist. Diese ist-ein-Beziehung funktioniert auf
der rechten Hälfte der Ableitungshierarchie genauso.
178
Einführung in die objektorientierte Programmierung
1
2
Mitarbeiter
3
4
LeitenderAngestellter
Angestellter
5
6
Projektleiter
Führungskreismitglied
Programmierer
Salesman
7
8
Firmenleiter
Abteilungsleiter
9
Abbildung 7.9 Eine Ableitungshierarchie
10
Man erhält durch Generalisierung die Möglichkeit, (Basis-)Klassen zu bilden, die
zunächst gemeinsame Member, aber auch gemeinsame Funktionalität in sich vereinen. Eine Doppelimplementierung7 wird hierdurch vermieden.
11
12
Ableitung bedeutet daher auch eine Spezialisierung der Basisklasse.
13
Syntax einer Ableitung
14
Eine Ableitung einer Klasse von einer Basisklasse wird in der Klassendeklaration eingefügt (Listing 7.35).
15
<Sichtbarkeit> class <Bezeichner> : <Basisklasse>
{
}
16
Listing 7.35 Erweiterte Syntax einer Klassendeklaration
17
Die Syntax aus Listing 7.35 zeigt, dass die Basisklassenangabe getrennt durch einen
Doppelpunkt dem Klassennamen folgt.
18
01:
02:
03:
04:
05:
19
public class Base
{ }
20
public class Derived : Base
{ }
A
Listing 7.36 Beispiel zur Ableitung
B
7 Eine doppelte oder allgemein eine mehrfache Implementierung derselben Funktionalität ist
zeit- und kostenintensiv und begünstigt Fehler.
Klassen und Objekte
179
Das Beispiel aus Listing 7.36 zeigt eine Klasse Derived, die sich von einer Klasse
namens Base ableitet.
Referenzen auf Basisklassen als universeller Speicher
Als weiteren Vorteil bietet eine Basisklasse auch, dass Variablen vom Typ der Basisklasse Referenzen auf abgeleitete Klassen aufnehmen können.
Schreibt man also eine Mitarbeiterverwaltung, so ist es irgendwann notwendig, alle
Mitarbeiter in einer geeigneten Datenstruktur abzulegen. Man kann hierzu Felder für
jeden Typ Mitarbeiter (Projektleiter, Firmenleiter, Programmierer etc.) anlegen. Von
diesem Vorgehen ist aber abzuraten, da bei einer späteren Erweiterung des Programms, z.B. durch Hinzufügen eines Werksstudenten, das gesamte Programm
umgebaut werden muss.
Eine andere, weitaus bessere Möglichkeit ist, alle Typen von Mitarbeiter in einem
einzigen Feld zu speichern. Dieses Feld muss nur einer Anforderung genügen: Es
muss in der Lage sein, Objekte vom Typ der Basisklasse Mitarbeiter aufzunehmen.
Listing 7.37 zeigt, wie das im Programmcode aussieht.
01:
02:
03:
04:
05:
06:
Mitarbeiter[] personal = new Mitarbeiter[500];
Firmenleiter leiter = new Firmenleiter();
Programmierer p1 = new Programmierer();
personal[0] = leiter;
personal[1] = p1;
Listing 7.37 Ein Feld, das Elemente vom Typ Mitarbeiter aufnimmt, kann auch von der Klasse Mitarbeiter abgeleitete Objekte aufnehmen.
Zunächst wird in Zeile 1 ein Feld angelegt, das maximal 500 Mitarbeiter aufnehmen
kann. In Zeile 2 und 3 werden dann als Beispiel ein Firmenleiter sowie ein Programmierer erstellt. Diese werden für Verwaltungszwecke in das Feld geschrieben (Zeile
5–6). Eine spezielle Behandlung ist dafür nicht notwendig.
Beachten Sie, dass keine Konvertierung von Typen durchgeführt wird, sondern nur
eine Referenz auf die Basisklasse erzeugt wird. Das Objekt selbst bleibt erhalten!
Die Operatoren as und is
Will man die einzelnen Mitarbeiter wieder aus dem Feld herauslösen, so muss nun
eine spezielle Vorgehensweise angewendet werden, die, einmal bekannt, aber sehr
leicht fällt.
Der Operator as prüft, ob ein Typ in einen anderen gewandelt werden kann. Ist dies
möglich, wird die Wandlung ausgeführt und das Objekt vom neuen Typ zurückgegeben. Ist eine solche Wandlung nicht möglich, gibt der Operator null zurück. Listing
7.38 zeigt die zugrunde liegende Syntax für den as-Operator.
180
Einführung in die objektorientierte Programmierung
1
2
<Objektbezeichner> as <Typ>
3
Listing 7.38 Die Syntax des as-Operators
Am besten kann man sich die Funktion dieses Operators merken, wenn man sich als
deutsche Übersetzung das Wort »als« merkt. Die Syntaxschreibweise könnte man
dann im Deutschen in etwa wie folgt beschreiben: Verwende <Objektbezeichner> als
<Typ>.
4
Der Operator is überprüft hingegen nur, ob eine Typwandlung durchgeführt werden
kann. Seine Syntax ähnelt der von as stark, jedoch ist das Ergebnis keine Referenz,
sondern ein boolescher Wert: true, falls eine Konvertierung durchgeführt werden
kann, und false, falls dies nicht möglich ist (Listing 7.39).
6
5
7
8
<Objektbezeichner> is <Typ>
Listing 7.39 Die Syntax des is-Operators
9
Damit kann nun die Mitarbeiterverwaltung ermitteln, von welchem Typ jedes einzelne Element des Feldes ist, und dann eine entsprechende Verarbeitung durchführen
(Listing 7.40).
10
11
01: foreach(Mitarbeiter m in personal)
02: {
03:
if (m is Firmenchef)
04:
{
05:
Console.WriteLine("Name des Chefs: {0}", m.Name);
06:
}
07:
else if (m is Programmierer)
08:
{
09:
Console.WriteLine("Name des Programmierers: {0}",
10:
m.Name);
11:
}
12: }
12
13
14
15
16
17
Listing 7.40 Mit Hilfe von is können die Elemente des Feldes personal wieder auseinandersortiert
werden.
18
Das foreach-Konstrukt in Zeile 1 aus Listing 7.40 ermittelt alle Elemente des Feldes
personal. Da das Feld Elemente vom Typ Mitarbeiter enthält, können diese auch
in einer Variablen vom Typ Mitarbeiter gesichert werden.
19
20
Mit jedem Element dieses Feldes wird so ein Test mit Hilfe des is-Operators durchgeführt, der prüft, in welchen Typ sich ein Element konvertieren lässt. Wird eine
Konvertierungsmöglichkeit gefunden, so kann das Objekt mit Hilfe des as-Operators
in den entsprechenden Typ konvertiert werden. Durch diesen Code wird somit eine
typabhängige Verarbeitung durchgeführt.
Klassen und Objekte
A
B
181
In Zeile 6 ist außerdem zu erkennen, dass die Eigenschaft Name, obwohl nur in der
Basisklasse deklariert, dennoch auch in der abgeleiteten Klasse existiert und von
außen genutzt werden kann. Vererbung macht's möglich!
Es ist üblich, eine Unterscheidung bezüglich des Elementtyps zu treffen. Unüblich ist
es aber, die Referenz m später explizit mit Hilfe des as-Operators umzuwandeln um
damit die Methodenimplementierungen der abgeleiteten Klasse aufzurufen. Hierfür
gibt es ein besseres Prinzip in der objektorientierten Programmierung: virtuelle
Methoden (Abschnitt 7.1.7). Diese beschreiben einen Mechanismus, der einen typspezifischen Methodenaufruf automatisch, d.h. ohne vorherige Typüberprüfung
durch den Programmierer durchführt.
Sichtbarkeitsspezifizierer
In den vorangehenden Abschnitten habe ich Sie immer wieder vertröstet: Jetzt
möchte ich Ihnen die Sichtbarkeitsspezifizierer sowie deren Bedeutung vorstellen.
Ich habe bis zu dieser Stelle gewartet, da nun eine umfassende Erläuterung möglich
ist, ohne anderen Fakten vorzugreifen.
Alle Sichtbarkeitsspezifizierer können auf die Member einer Klasse angewendet werden. Tabelle 7.4 bietet eine Übersicht über die möglichen Sichtbarkeitsspezifizierer.
Beachten Sie, dass Sie jeweils nur eine bei der Deklaration eines Members angeben
können.
Sichtbarkeitsspezifizierer
Zugriff von außen
Zugriff in Ableitung
public
möglich
ja
protected
nicht möglich
ja
private
nicht möglich
nein
internal
ja, innerhalb derselben Assembly
ja, innerhalb derselben
Assembly
internal protected
ja, innerhalb derselben Assembly
ja, innerhalb derselben
Assembly
Tabelle 7.4 Übersicht über die Sichtbarkeitsspezifizierer
Die Spezifizierer internal und internal protected sind nicht Teil der Beschreibungssprache UML. Im Folgenden ist jeder Spezifizierer noch einmal ausführlich
erläutert:
왘 public
Ein Member, der als public gekennzeichnet ist, kann von außen genutzt werden.
Ein Beispiel für public-Member haben Sie in diesem Kapitel immer wieder gesehen, z.B. Name in der Klasse Mitarbeiter.
01: public class Mitarbeiter
02: {
182
Einführung in die objektorientierte Programmierung
1
2
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
public string Name;
/* ... */
3
}
public class Demo
{
public static void Main()
{
Mitarbeiter m = new Mitarbeiter();
m.Name = "Hugo";
}
}
4
5
6
7
Listing 7.41 Der Zugriff auf public-Member
8
Listing 7.41 demonstriert den Zugriff auf den public-Member Name eines Objektes
vom Typ Mitarbeiter in Zeile 11. Man spricht auch von öffentlichen Membern. Name
wird ebenfalls an abgeleitete Klassen vererbt und kann von diesen benutzt werden
(Listing 7.42).
9
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
10
public class Angestellter : Mitarbeiter
{
}
11
public class Programmierer : Angestellter
{
public Programmierer()
{
Name = "<Neuer Programmierer>";
}
}
13
12
14
15
16
Listing 7.42 Der Zugriff auf einen public-Member der Vaterklasse in einer abgeleiteten Klasse
17
왘 protected
Auf Member, die als protected gekennzeichnet sind, kann nicht von außen zugegriffen werden, jedoch von abgeleiteten Klassen aus. Wäre der Datenmember Name
auf Sichtbarkeit protected gesetzt, wäre kein Zugriff wie in Listing 7.41 gezeigt
möglich, aber der wie in Listing 7.42.
18
19
왘 private
20
private Member können nur in der Klasse verwendet werden, in der sie deklariert wurden. Das heißt, es wären keine Zugriffe nach Listing 7.41 und Listing 7.42
möglich, wenn man davon ausgeht, dass Name auf private steht.
A
왘 internal
B
internal Member sind nur innerhalb der gleichen Assembly zugreifbar. Diese
Stufe ist dann nützlich, wenn Member nach außen hin für einige Assemblies offen
gelegt werden sollen, für andere nicht.
Klassen und Objekte
183
왘 internal protected
Gewährt überall dort Zugriff, wo entweder über internal oder protected Zugriff
erlangt werden kann. Dabei ist der Zugriff wiederum auf die aktuelle Assembly beschränkt.
Achtung mit Konstruktoren!
Eine Besonderheit gibt es bei Konstruktoren und deren Sichtbarkeitsstufe zu beachten: Da ein Konstruktor zur Instanzerstellung unbedingt benötigt wird, sollte man ihn
nur in Sonderfällen auf eine andere Stufe als public setzen. In einfachen Worten
heißt das, dass der Konstruktor eine Sichtbarkeitsangabe benötigt, so dass er vom
new-Operator außerhalb einer Klasse gerufen werden kann. Ist er beispielsweise auf
private gesetzt, so ist der Konstruktor nicht-öffentlich: new kann dann keine Instanzen dieser Klasse von außen erzeugen. In diesen Fällen stellt man eine statische
Methode zur Verfügung, die dann eine Instanz dieser Klasse erzeugt (Abschnitt
8.3.5). Man trifft dieses Verhalten in der Literatur auch unter dem Terminus Klassenfabrik an.
Abhängigkeit von der Klassensichtbarkeit
Alle fünf Sichtbarkeitsspezifizierer sind dabei abhängig von der Sichtbarkeitsstufe,
die der Klasse verliehen wurde. Die Bedeutung bleibt mit der oben beschriebenen
gleich, wenn die Klassensichtbarkeit auf public steht. Steht sie auf internal,
machen nur noch public, protected und private Sinn. Diese sind dann jedoch
auch auf die aktuelle Assembly beschränkt. Ein public-Mitglied macht daher die
internal-Definition für eine Klasse nicht rückgängig!
Aufruf des Basisklassenkonstruktors: base Teil I
Betrachten Sie das Szenario aus Listing 7.43. Es soll demonstrieren, wie in einer
Ableitungshierarchie Objekte erstellt werden. Denn wenn eine Instanz einer Klasse
erstellt wird, die sich von einer zweiten ableitet, so muss auch deren Konstruktor
durchlaufen werden. Dies ist notwendig, da sich im Konstruktor der Basisklasse Initialisierungscode befinden kann, der unbedingt ausgeführt werden muss bevor die
abgeleitete Klasse initialisiert wird (z.B. Herstellen einer Netzwerkverbindung, Öffnen einer Datei etc.)
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
184
public class Base
{
public Base()
{
Console.WriteLine("Konstruktor Base");
}
}
public class Derived
{
Einführung in die objektorientierte Programmierung
1
2
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
public Derived()
{
Console.WriteLine("Konstruktor Derived");
}
3
4
}
5
public class Demo
{
public static void Main()
{
Derived d = new Derived();
}
}
6
7
8
9
Listing 7.43 Mit diesem Programm kann ermittelt werden, wann welcher Konstruktor durchlaufen wird.
10
In Zeile 21 in Listing 7.43 wird ein Objekt der Klasse Derived erstellt, die sich von
der Klasse Base ableitet. Durch den Aufruf des new-Operators wird zunächst der Konstruktor von Derived gerufen. Bevor dieser aber seine Ausführung beginnt, setzt sich
der Aufruf zum Konstruktor der Basisklasse Base fort. Daher erscheint als Erstes die
Ausgabe des Basisklassenkonstruktors am Bildschirm und erst danach die des Konstruktors der abgeleiteten Klasse (Abbildung 7.10).
11
12
13
14
15
Abbildung 7.10 Das Programm aus Listing 7.43 in Aktion
16
Der Compiler fügt immer Code hinzu, der den Standardkonstruktor der Basisklasse
ruft. Ist der Aufruf eines anderen Konstruktors der Basisklasse erforderlich, so muss
der Programmierer per Hand eingreifen. Hierzu gibt es das Schlüsselwort base.
17
18
<Sichtbarkeit> <Klassenname>([<Parameter>]) :
base([<Parameter>])
{
/* Konstruktorcode */
}
19
20
Listing 7.44 Syntax zur Verwendung des Schlüsselwortes base
A
base wird, wie die Syntax in Listing 7.44 zeigt, durch einen Doppelpunkt getrennt,
hinter die Parameterliste des Konstruktors geschrieben. base selbst wiederum erhält
B
dann in runden Klammern die Parameter, die ein entsprechender Konstruktor der
Basisklasse erwartet.
Klassen und Objekte
185
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
public class Base()
{
public Base(int x)
{
/* ... */
}
}
public class Derived : Base
{
public Derived() : base(3)
{
/* ... */
}
}
Listing 7.45 base() ruft einen bestimmten Konstruktor der Basisklasse.
Listing 7.45 zeigt ein Beispiel für die Anwendung von base(). Erstellt man nun mittels new Derived() eine neue Instanz der abgeleiteten Klasse, so wird durch die Verwendung von base() der Konstruktor der Basisklasse mit einem Parameter (im Beispiel mit dem Zahlenwert 3 belegt), aufgerufen.
Anstelle eines fixen Wertes können auch Parameter, z.B. des Konstruktors der abgeleiteten Klasse, eingesetzt werden. Ein Beispiel hierfür sehen Sie in Listing 7.46.
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
public class Base
{
public Base(int x)
{
/* ... */
}
}
public class Derived : Base
{
public Derived(double b, int c) : base(c)
{
/* ... */
}
}
Listing 7.46 base() übergibt einen Parameter an den Konstruktor der Basisklasse.
186
Einführung in die objektorientierte Programmierung
1
2
Klassen, die nicht als Basisklassen eingesetzt werden können
3
Klassen, die von ihrer Natur her nicht als Basisklasse in Frage kommen, kann man
zusätzlich mit dem Schlüsselwort sealed bezeichnen. Dadurch wird unterbunden,
dass sich eine Klasse von der so markierten ableiten lässt.
4
01: public sealed class RationalNumber
02: {
03:
public int Zaehler;
04:
public int Nenner;
05:
/* ... */
06: }
5
6
7
Listing 7.47 Klassen, die als sealed gekennzeichnet sind, können keine Kinder besitzen.
8
Die Klasse RationalNumber (Repräsentation einer rationalen Zahl) aus Listing 7.47
kann, da sie als sealed gekennzeichnet ist, keine Kinder besitzen. Code wie in Listing
7.48 ist damit nicht möglich; es erfolgt eine Fehlermeldung durch den Compiler beim
Übersetzungsvorgang.
9
10
01: public class MyRationalNumber : RationalNumber
02: {
03: }
11
12
Listing 7.48 Ableitung von einer als sealed markierten Klasse ist nicht möglich.
13
System.Object
Eine (Basis-)Klasse in .NET leitet sich immer von der Klasse Object aus dem Namensraum System ab, ohne dass dies explizit angegeben werden muss. Damit verfügen
alle Klassen einer Hierarchie über die Methoden von System.Object. In C# gibt es
für System.Object eine andere Bezeichnung: object.
14
Tabelle 7.5 zeigt im Überblick die Methoden von System.Object. Die Bedeutung des
Schlüsselwortes virtual lernen Sie im Laufe dieses Kapitels kennen.
16
15
17
Methode
Bedeutung
public virtual bool
Equals(object o)
Überprüft, ob das aktuelle Objekt und das als Parameter übergebene gleich sind.
public virtual int
GetHashCode()
Ermittelt den Hash-Code für das aktuelle Objekt. Ein HashCode wird für effiziente Speicheralgorithmen benötigt (Stichwort für Interessierte: Streuspeicherung).
public Type GetType()
18
19
20
Ermittelt zur Laufzeit Informationen über das aktuelle Objekt
und gibt diese als ein Objekt vom Typ Type zurück. Über dieses
Objekt kann dann wiederum der Klassenname u.Ä. ermittelt
werden.
A
B
Tabelle 7.5 Die wichtigsten Methoden von System.Object
Klassen und Objekte
187
Methode
Bedeutung
public virtual string
ToString()
Erstellt eine textuelle Repräsentation des Objektes. Wird von
WriteLine() implizit aufgerufen.
Tabelle 7.5 Die wichtigsten Methoden von System.Object (Forts.)
Die wichtigste Methode von System.Object ist ToString(). ToString() soll eine
textuelle Repräsentation eines Objektes erzeugen und zurückgeben. Mehr zum
Thema ToString() erfahren Sie in Kapitel 9, Zeichenketten und reguläre Ausdrücke.
Da auch int und die anderen C#-Datentypen auf .NET-Klassen abgebildet werden
(im Fall von int: System.Int32) und diese ebenfalls von System.Object abgeleitet sind, verfügen auch die Datentypen über die Methoden von System.Object!
01: int x = 34;
02: Console.WriteLine("Wert von x = {0}", x.ToString());
Listing 7.49 Die Methode ToString() steht auch für primitive Datentypen zur Verfügung.
7.1.5
Gleichheit von Objekten
Die Gleichheit von Objekten lässt sich in mehrere Stufen aufteilen:
1. Gleich im Sinne von gleichen Referenzen: Zwei Objekte sind dann gleich (besser:
identisch), wenn die Referenzen dasselbe Objekt im Speicher bezeichnen.
2. Gleich im Sinne von gleicher Typ: Zwei Objekte besitzen denselben Typ, haben
aber unterschiedliche Instanzdaten.
3. Gleich: Zwei Objekte besitzen denselben Typ und die gleichen Werte in den Instanzvariablen, sind aber unabhängig im Speicher (werden über unterschiedliche
Referenzen angesprochen).
Mehr zu diesem Thema erfahren Sie in Kapitel 9 und 11.
7.1.6
Überladen von Methoden
Ich hatte Ihnen bereits die Summation der Quadrate zweier Parameter als Beispiel für
die Rückgabe eines Wertes mit Hilfe des return-Schlüsselwortes vorgestellt.
Die Implementierung nutzte damals zwei Parameter, deren Typ jeweils int war. Was
ist aber zu tun, wenn statt zwei Parametern vom Typ int zwei Parameter vom Typ
double quadriert und summiert werden müssen? Mit den bisherigen Mitteln haben
Sie zwei Möglichkeiten:
1. eine zweite Methode schreiben oder
2. die Parameter in ints konvertieren und einen möglichen Genauigkeitsverlust in
Kauf nehmen.
188
Einführung in die objektorientierte Programmierung
1
2
Beide Möglichkeiten haben Nachteile: Erstere erfordert einen zweiten Methodennamen für denselben Sachverhalt, Letztere schließt Zahlen wie 0.5 oder 0.25 einfach
aus bzw. erzeugt fehlerhafte Ergebnisse und ist daher denkbar ungeeignet.
3
4
Man kann aber eine zweite Methode zur Verfügung stellen, die zwei Parameter vom
Typ double erwartet und denselben Namen trägt.
5
Unterscheiden sich zwei (oder mehr) Methoden durch Parameterliste oder Rückgabetyp, spricht man von Methodenüberladung.
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
6
7
public class SumQuad
{
public int Sum_quad(int a, int b)
{
Console.WriteLine("int Sum_quad(int, int)");
return (a * a) + (b * b);
}
8
9
10
11
public double Sum_quad(double a, double b)
{
Console.WriteLine("double Sum_quad(double," +
" double)");
return (a * a) + (b * b);
}
12
13
}
14
public class Demo
{
public static void Main()
{
SumQuad sq = new SumQuad();
15
16
17
int res1 = sq.Sum_quad(2, 3);
double res2 = sq.Sum_quad(2, 3.0);
18
19
Console.WriteLine("res1 = {0}", res1);
Console.WriteLine("res2 = {0}", res2);
20
}
}
A
Listing 7.50 SumQuad zeigt die Überladung von Methoden.
B
Listing 7.50 zeigt, wie diese Methodenüberladung in der Realität aussieht. In der
Klasse Sum_quad() (Zeile 1 bis 15) gibt es zwei Methoden, die den Namen Sum_
Klassen und Objekte
189
quad() tragen. Die eine erwartet Parameter vom Typ int, die andere Parameter vom
Typ double.
Der Compiler löst diese Überladung beim Aufruf der Methode Sum_quad() in Zeile
23 und 24 dann auf eine der beiden Implementierungen auf, abhängig davon, welche
Parametertypen im jeweiligen Fall übergeben wurden.
Welche der beiden Implementierungen jeweils verwendet wird, hängt von drei Kriterien ab. Die Kriterien sind geordnet, d.h. trifft beispielsweise Kriterium 1 zu, so
wird Kriterium 2 und 3 nicht mehr betrachtet. Die Kriterien sind:
1. Stimmen die Parametertypen einer Methode mit denen der Argumente zu 100 %
überein, wird diese Methode aufgerufen.
2. Sind die Argumente implizit in die Typen der Parameter einer Methode konvertierbar, so wird diese Methode aufgerufen.
3. Ist der Typ des Argumentes ganzzahlig und vorzeichenbehaftet, wird versucht,
eine Methode zu finden, die ebenfalls ein ganzzahliges und vorzeichenbehaftetes
Argument erwartet, das aber unterschiedlichen Typs ist. Erst danach wird nach einer Methode mit ganzzahligem, vorzeichenlosen Parametertyp gesucht.
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
using System;
class Methods
{
public void Foo(long x)
{
Console.WriteLine("Foo(long)");
}
public void Foo(ulong x)
{
Console.WriteLine("Foo(ulong)");
}
}
class Demo
{
public static void Main()
{
Methods m = new Methods();
m.Foo();
}
}
Listing 7.51 Ein Beispiel für die Veranschaulichung von Regel 3
190
Einführung in die objektorientierte Programmierung
1
2
Betrachten Sie das Beispiel aus Listing 7.51. Es enthält eine Klasse Methods, die über
zwei überladene Methoden Foo() verfügt. Die erste Überladung erwartet einen ganzzahligen, vorzeichenbehafteten Parameterwert, die zweite ebenfalls einen ganzzahligen, aber vorzeichenlosen Wert. Der Compiler wählt nun in Zeile 22 beim Aufruf
gemäß Regel 3 die erste Überladung aus. Würde diese nicht existieren, wird der Compiler die zweite Methodenimplementierung verwenden.
3
4
5
Kann keine Methode gefunden werden, auf die eine dieser drei Regeln zutrifft,
erzeugt der Compiler eine Fehlermeldung.
6
7
Überladene Methoden müssen sich zumindest in einem Parametertyp oder der
Parameteranzahl unterscheiden.
8
Die Suche nach einer passenden Überladung beginnt immer an der höchsten Stelle in
einer Ableitungshierarchie, d.h. dem Typ der Referenz. Findet sich dort keine passende, wird die Suche eine Ebene tiefer, d.h. bei der Basisklasse fortgesetzt. Überladungen werden vom Compiler während des Übersetzungsvorganges aufgelöst.
10
7.1.7
11
9
Überschreiben von Methode: Virtuelle Methoden
Betrachten Sie das Programm aus Listing 7.52. Dort sind drei Klassen enthalten: Base
spielt die Rolle einer Basisklasse für die Klasse Derived. Beide Klassen (Base und
Derived) verfügen über zwei Methoden Foo() mit derselben Schnittstelle. Die dritte
Klasse, VirtualFunctionsDemo, enthält die Main()-Methode.
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
12
13
public class Base
{
public void Foo()
{
Console.WriteLine("Base::Foo()");
}
}
14
15
16
17
public class Derived : Base
{
public void Foo()
{
Console.WriteLine("Derived::Foo()");
}
}
18
19
20
A
public class VirtualFunctionsDemo()
{
public static void Main()
{
Derived d = new Derived();
B
Klassen und Objekte
191
22:
23:
24:
25:
26:
27: }
Base
b = d;
d.Foo();
b.Foo();
}
Listing 7.52 Welche Methode(n) werden gerufen?
In Listing 7.52 wird zunächst in Zeile 21 eine Instanz der Klasse Derived angelegt.
Im letzten Abschnitt haben Sie gelernt, dass eine Referenz auf ein Objekt auch in eine
Referenz auf die Basisklasse umgewandelt werden kann. Dies wird in Zeile 22 implizit durch die Zuweisung gemacht.
Danach wird einmal über die Referenz auf die Klasse direkt und einmal über die Referenz auf die Basisklasse in die Methode Foo() gerufen. Der Aufruf von d.Foo()
ergibt erwartungsgemäß das Ergebnis, dass wirklich die Methode in der abgeleiteten
Klasse durchlaufen wurde. Der Aufruf von b.Foo() führt aber nicht zu diesem Ergebnis. Es wird stattdessen die Methode Foo() der Basisklasse abgearbeitet.
Lassen Sie mich nun auf den vorangehenden Abschnitt zurückgreifen: Sie haben dort
gelernt, dass ein Feld, das Referenzen auf die Basisklasse aufnehmen kann, auch als
Speicher für abgeleitete Klassen genutzt werden kann. Wäre b.Foo() in der Lage,
wirklich die Methode in der abgeleiteten Klasse aufzurufen, könnte man sich langwierige Konvertierungsarbeiten sparen.
In der Tat gibt es so ein Sprachmittel: virtuelle Methoden. Bei virtuellen Methoden
wird zur Laufzeit entschieden, welcher Typ hinter einer Referenz steht. Die Methode
wird dann nicht, wie im Beispiel gezeigt, an der Basisklasse aufgerufen, sondern an
der abgeleiteten. Man bezeichnet dies mit dem Fachbegriff Polymorphie8. Damit dieser Mechanismus funktioniert, müssen zwei Vorkehrungen getroffen werden:
1. Die Methode muss in der Basisklasse als virtual deklariert werden und
2. damit die Methode in der abgeleiteten Klasse die Version der Basisklasse überschreibt, muss die Methode als override gekennzeichnet sein.
virtual und override sind Schlüsselwörter, die ähnlich wie static bei der Main()Methode vor den Rückgabetyp der Methode geschrieben werden.
Ist eine Methode als override gekennzeichnet und die entsprechende Methode in
der Basisklasse wurde nicht als virtual deklariert, bemängelt dies der Compiler mit
einer Fehlermeldung.
<Sichtbarkeit> virtual <Rückgabetyp> <Methodenname>()
<Sichtbarkeit> override <Rückgabetyp> <Methodenname>()
Listing 7.53 Syntax für den Einbau der Schlüsselwörter virtual und override in eine Methodendeklaration
8 Polymorphie = Vielgestaltigkeit
192
Einführung in die objektorientierte Programmierung
1
2
Listing 7.53 zeigt die Syntax für die Deklaration von virtuellen und überschriebenen
Methoden. Der Einfachheit halber wurde auf die Angabe einer Parameterliste in den
Methodenköpfen sowie auf die Methodenrümpfe verzichtet.
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
3
public class Base
{
public virtual void Foo()
{
Console.WriteLine("Base::Foo()");
}
}
4
public class Derived : Base
{
public override void Foo()
{
Console.WriteLine("Derived::Foo()");
}
}
8
5
6
7
9
10
11
public class VirtualFunctionsDemo()
{
public static void Main()
{
Derived d = new Derived();
Base
b = d;
12
13
14
15
d.Foo();
b.Foo();
16
}
}
17
Listing 7.54 Das Beispiel aus Listing 7.52 wurde mit Hilfe von virtual und override umgebaut.
18
Listing 7.54 zeigt nun das mit Hilfe von virtual und override umgebaute Beispiel
aus Listing 7.52.
19
Beim Aufruf von d.Foo() wird immer noch die Implementierung der abgeleiteten
Klasse gerufen; bei b.Foo() jedoch nicht mehr die der Basisklasse, sondern jetzt wird
durch die Kennzeichnung durch virtual und override ebenfalls die Implementierung der abgeleiteten Klasse herangezogen.
20
A
Derived.Foo() überschreibt damit Base.Foo().
B
Klassen und Objekte
193
7.2
Abstrakte Klassen
Modelliert man real existierende Objekte mit den hier vorgestellten Mitteln der OOP
(Klassen, Vererbung etc.), so trifft man oft die Anforderung an, eine Basisklasse zu
modellieren, von der keine Instanzen erstellt werden dürfen.
Betrachten Sie wiederum das Beispiel der Mitarbeiter: Da es Spezialisierungen für
jeden Beschäftigtentypus gibt, macht es keinen Sinn, Instanzen der Klasse Mitarbeiter zuzulassen.
Man bezeichnet Klassen, die als Basisklasse herangezogen werden können, aber
von denen keine Instanz erstellt werden kann, als abstrakte Klassen.
Bereits aus der Definition ergibt sich, dass abstrakte Klassen niemals mit dem Schlüsselwort sealed gekennzeichnet werden können! Zur Erinnerung: sealed-Klassen
sind Klassen, von denen Instanzen erstellt werden dürfen, die aber nicht als Basisklasse genutzt werden können. Listing 7.55 zeigt die Syntax, die der Deklaration
einer abstrakten Klasse zu Grunde liegt.
<Sichtbarkeit> abstract class <ClassName>
{
}
Listing 7.55 Syntax der Deklaration einer abstrakten Klasse
Im Gegensatz zur Definition einer normalen Klasse muss nur das Schlüsselwort
abstract zwischen die <Sichtbarkeit> und das Schlüsselwort class eingefügt
werden.
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
public abstract class Mitarbeiter
{
}
public class AbstractDemo
{
public static void Main()
{
/* Fehler ! */
Mitarbeiter m = new Mitarbeiter();
}
}
Listing 7.56 Die Verwendung des Schlüsselwortes abstract verhindert die Instantiierung von
Objekten.
In Listing 7.56 sehen Sie ein Beispiel für eine abstrakte Klasse. Der Einfachheit halber
wurde in dieser Klasse auf Methoden bewusst verzichtet.
194
Einführung in die objektorientierte Programmierung
1
2
Den Versuch, ein Objekt dieser Klasse zu instantiieren (Zeile 10), quittiert der Compiler während der Übersetzung mit einer Fehlermeldung.
3
Eine abstrakte Klasse kann auch über Methoden und Daten verfügen. abstract
bewirkt nur, dass keine Instanzen dieser Klasse gebildet werden können.
4
7.2.1
5
Abstrakte Methoden
In vielen Fällen ist zwar der Name einer Methode und deren Parameterliste bekannt,
doch die Implementierung kann sich für jede Spezialisierung zum Teil erheblich
unterscheiden. Demzufolge kann kein gemeinsamer Nenner für eine Implementierung in der Basisklasse gefunden werden.
6
7
Nehmen Sie als Beispiel eine Methode Gehalt_Berechnen() in der MitarbeiterVerwaltung. Für jeden einzelnen Typus kann sich das Gehalt auf eine andere Art und
Weise errechnen: Für den Programmierer kommt sicherlich eine entsprechende Tarifklasse zur Anwendung, für den Konzernchef eine Pauschale mit Zuschlägen, je nach
Situation des Unternehmens. Die Methode Gehalt_Berechnen() kann also nicht auf
eine gemeinsame Basis für alle Mitarbeiter-Typen gebracht werden.
8
9
10
01: public abstract class Mitarbeiter
02: {
03:
public double Gehalt_Berechnen()
04:
{
05:
return 0d;
06:
}
07: }
11
12
13
14
Listing 7.57 Ein erster Ansatz, die Methode Gehalt_Berechnen() in den Zusammenhang einzuordnen
15
Listing 7.57 zeigt einen ersten Ansatz, die Methode Gehalt_Berechnen() in den
Zusammenhang einzuordnen. Die Implementierung wurde einfach weggelassen, da
die Berechnung des Gehaltes ja nicht für alle gleich ist.
16
17
Der Nachteil dieser Lösung ist, dass eine abgeleitete Klasse diese Methode nicht überschreiben muss. Syntaktisch liegt kein Fehler vor, so dass der Compiler die Absicht,
diese Methode in jeder abgeleiteten Klasse zu implementieren, nicht überprüfen
kann.
18
19
Es gibt nun ein Sprachmittel in C#, mit dessen Hilfe man diesen Sachverhalt zum Teil
der Syntaxprüfung machen kann, d.h. der Compiler überprüft, ob eine Methode in
einer abgeleiteten Klasse implementiert wird und damit die Methode und Funktionalität der Basisklasse überschreibt. Man nennt dieses Sprachmittel abstrakte Methoden.
20
A
Wiederum ist das Schlüsselwort abstract beteiligt. Abstrakte Methoden können nur
in abstrakten Klassen deklariert werden und besitzen keinen Rumpf. Listing 7.58
zeigt die Syntax für die Deklaration einer abstrakten Methode.
Abstrakte Klassen
B
195
<Sichtbarkeit> abstract <Rückgabetyp> <Name>([<Parameter>]);
Listing 7.58 Syntax für die Deklaration einer abstrakten Methode
Damit lässt sich nun die Methode Gehalt_Berechnen() in der Klasse Mitarbeiter
(siehe Listing 7.59) deklarieren.
01: public abstract class Mitarbeiter
02: {
03:
public abstract double Gehalt_Berechnen();
04: }
Listing 7.59 Gehalt_Berechnen() als abstrakte Methode
Eine Klasse Programmierer, die sich von Mitarbeiter ableitet, muss nun diese
Methode zwingend überschreiben. Macht sie das nicht, generiert der Compiler eine
Fehlermeldung beim Übersetzen der Anwendung.
01: public class Programmierer : Mitarbeiter
02: {
03:
public override double Gehalt_Berechnen()
04:
{
05:
Console.WriteLine("Programmierer::"+
06:
"Gehalt_Berechnen()");
07:
return 3000;
08:
}
09: }
Listing 7.60 Die Klasse Programmierer muss die abstrakte Methode Gehalt_Berechnen() der
Basisklasse überschreiben (Abstrakt1).
Listing 7.60 zeigt, wie das Überschreiben einer abstrakten Methode vonstatten geht.
Die Methode Gehalt_Berechnen() der Klasse Programmierer wurde als override
gekennzeichnet. Damit überschreibt sie die gleichnamige Methode der Basisklasse.
Wird das Schlüsselwort override vergessen, erfolgt eine Fehlermeldung durch den
Compiler.
Damit lässt sich auch die Definition aus dem Abschnitt zu den virtuellen Methoden
verbessern: override kann nicht nur für virtuelle Methoden eingesetzt werden, sondern muss auch bei abstrakten Methoden angewendet werden.
7.3
Eigenschaften (Properties)
Gibt man Datenmember über public für die Benutzung frei, hat man den Nachteil,
dass der Zugriff unbeschränkt ist, d.h. sowohl lesend als auch schreibend.
Das ist dann fatal, wenn Daten unbeabsichtigt überschrieben werden. Ein anderer
Fall ist es, wenn beim Ändern von Daten zusätzliche Seiteneffekte mit beachtet werden müssen. Als klassisches Beispiel ist hier die Klasse zu nennen, die einen Waren-
196
Einführung in die objektorientierte Programmierung
1
2
korb eines Internet-Shops realisieren soll. Legt ein Kunde einen Artikel in den Warenkorb, muss nicht nur der Artikel mit aufgenommen werden, sondern gleichzeitig
auch der Preis, und der Warenbestand muss ebenfalls reduziert werden. Dies alles
könnte die Klasse Warenkorb erledigen.
3
4
Sicher geht das auch, wenn man normale Methoden für diese Zwecke einsetzt und
die Datenmember selbst auf private oder protected setzt. Allerdings hat das Ganze
dann einen kleinen Schönheitsfehler. Schließlich möchte man im Programm Daten
gerne wie Daten behandeln und nicht wie Methoden.
5
6
9
Zu diesem Zweck gibt es die Eigenschaften . Eigenschaften erlauben es, den Zugriff
einzuschränken und Seiteneffekte auszuführen. Im Programm fassen sich Eigenschaften dabei wie normale public-Memberdaten an.
7
8
Hinter einem Property steht ein echter Datenmember, der jedoch nur objektintern
(deklariert als private oder protected) verwendet werden kann.
9
<Sichtbarkeit> <Datentyp> <Name>
{
[[<Sichtbarkeit>] get
{
// Code für lesenden Zugriff
}]
[[<Sichtbarkeit>] set
{
// Code für schreibenden Zugriff
}]
}
10
11
12
13
14
Listing 7.61 Syntax für die Deklaration eines Properties in C#
15
Die Syntax zur Deklaration eines Properties in Listing 7.61 sieht auf den ersten Blick
verwirrend aus. Sie wird aber umso klarer, wenn man sie zunächst in ihre Bestandteile zerlegt.
16
17
Eingeleitet wird ein Property wie jeder Member mit einer Angabe zur <Sichtbarkeit>, gefolgt von dem <Datentyp> dieser Eigenschaft und einem <Namen>. Dies ist
dasselbe wie bei der Deklaration eines normalen Memberdatums. Aber anstatt die
Deklaration danach abzuschließen, geht ein Block auf.
18
19
Dieser Block enthält dann den Code, der bei lesendem bzw. schreibendem Zugriff auf
das Property ausgeführt wird. Dieser Code wird wiederum in zwei Blöcke gepackt,
wovon einer das Schlüsselwort get, der andere das Schlüsselwort set vorangestellt
bekommt. get bezeichnet den Block, der den Code für den lesenden Zugriff enthält,
und set den, der den Code für den schreibenden Zugriff enthält.
20
A
B
9 engl. Properties
Eigenschaften (Properties)
197
Beide Blöcke (get und set) sind optional, jedoch muss mindestens einer der beiden vorhanden sein. Der get-Block wird als Getter, der set-Block als Setter
bezeichnet.
Obwohl sich Properties wie normale Daten anfassen, stehen in Wirklichkeit Methoden dahinter.
7.3.1
get
Getter-Code ist zumeist sehr einfach zu schreiben; nachdem Properties unter der
Haube Methoden sind, ist der Getter eines Properties eine Methode, die einen Wert
vom Typ des Properties zurückliefert.
Stellen Sie sich vor, Sie sollen eine Klasse zur Implementierung einer rationalen
(gebrochenen) Zahl in C# implementieren. Diese verfügt über zwei Komponenten:
zaehler und nenner, jeweils vom Typ int. Die beiden Daten sollen über Properties
nach außen hin zugänglich gemacht werden.
01: public sealed class RationalNumber
02: {
03:
private int zaehler;
04:
private int nenner = 1;
05:
06:
public int Zaehler
07:
{
08:
get
09:
{
10:
return zaehler;
11:
}
12:
}
13:
public int Nenner
14:
{
15:
get
16:
{
17:
return nenner;
18:
}
19:
}
20: }
Listing 7.62 Ein erster Ansatz für die Implementierung einer rationalen Zahl in C# mit Hilfe von
Properties
Die Getter sind also einfach so zu implementieren, als würde eine ganz normale
Methode einen Rückgabewert an die aufrufende Methode weiterleiten. Sollen noch
zusätzliche Schritte durchgeführt werden, so sind diese entsprechend vor das return
einzufügen. In den Blöcken, egal ob Getter oder Setter, kann normaler Code stehen,
so wie Sie ihn in den ersten Kapiteln dieses Buches zu schreiben gelernt haben.
198
Einführung in die objektorientierte Programmierung
1
2
7.3.2
set
3
Setter sind ein wenig verzwickter, denn es muss ein Wert übernommen werden. Aber
woher kommt dieser Wert?
4
Innerhalb von Settern steht über eine vordefinierte Variable der Wert zur Verfügung,
der zugewiesen werden soll: value. Damit lassen sich die Setter von RationalNumber wie in Listing 7.63 schreiben.
5
6
01: public sealed class RationalNumber
02: {
03:
private int zaehler;
04:
private int nenner = 1;
05:
06:
public int Zaehler
07:
{
08:
get
09:
{
10:
return zaehler;
11:
}
12:
set
13:
{
14:
zaehler = value;
15:
}
16:
}
17:
public int Nenner
18:
{
19:
get
20:
{
21:
return nenner;
22:
}
23:
set
24:
{
25:
if (value != 0)
26:
nenner = value;
27:
}
28:
}
29: }
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Listing 7.63 Die Setter von Properties kann man nutzen, um ungültige Zuweisungen abzufangen.
A
Der Setter des Properties Zaehler aus Listing 7.63 (Zeile 12–15) führt die Zuweisung
aus, ohne vorher den Wert irgendeiner Überprüfung zu unterziehen.
B
Der Setter des Properties Nenner (Zeile 23–27) führt die Zuweisung aber nur aus,
wenn der neue Wert (steht in der Variablen value) ungleich 0 ist. Dies hat den
Grund, eine Division durch Null zu vermeiden. Wird versucht, den Nenner auf 0 zu
Eigenschaften (Properties)
199
setzen, wird stattdessen der alte Wert des Nenners belassen. Dann muss aber darauf
geachtet werden, dass der Nenner zu Beginn mit einem Wert ungleich 0 initialisiert
wurde! In der objektorientierten Programmierung wäre dies ein Fall für die Anwendung einer Exception. Exceptions lernen Sie in Kapitel 10, Ausnahmen – Exceptions,
kennen.
Zusätzlich könnte man beispielsweise noch beim Setzen beider Properties ein Kürzen
des Bruches (falls möglich) veranlassen.
7.3.3
Unterschiedliche Sichtbarkeitsstufen für Getter und Setter
Wie in Listing 7.61 gezeigt, erlaubt C# in der Version 2.0 des Standards die zusätzliche Angabe einer unterschiedlichen Sichtbarkeitsstufe für den Getter oder den Setter
einer Eigenschaft. Diese ist z.B. dann anzuwenden, wenn der Getter für den Zugriff
von außerhalb der Klasse zur Verfügung stehen soll, der Setter aber nur für die
interne Klassenhierarchie. In diesem Fall wird man das Property selbst auf public
setzen und den Setter auf protected.
Es gilt zu beachten, dass
왘 sowohl Getter als auch Setter vorhanden sein müssen, damit ein Sichtbarkeitsmo-
difizierer angegeben werden kann,
왘 entweder Getter oder Setter eine Angabe zur Sichtbarkeitsstufe enthalten dürfen,
aber keinesfalls beide gleichzeitig,
왘 Eigenschaften, die in Schnittstellen (Kapitel 8, Generische Klassen, Schnittstellen und
statische Klassenmitglieder) definiert wurden, keine Angaben zur Sichtbarkeitsstufe
für Getter und Setter enthalten dürfen sowie
왘 Eigenschaften, die mittels des override-Schlüsselworts überschrieben werden,
dieselbe Sichtbarkeitsstufe für Getter und Setter besitzen müssen wie die überschriebene Version.
Tabelle 7.6 gibt einen Einblick, welche Kombinationen von Sichtbarkeitsstufen
erlaubt sind, abhängig von der für die Eigenschaft verwendeten Stufe.
Sichtbarkeitsstufe
der Eigenschaft
Erlaubte Sichtbarkeitsstufe(n) von Getter oder Setter
Public
Jede erlaubte Kombination von Sichtbarkeitsstufen zulässig
protected
internal
internal
protected
private
internal protected
private
private
Angabe einer Sichtbarkeitsstufe nicht erlaubt
Tabelle 7.6 Erlaubte Kombinationen für die Sichtbarkeitsstufen von Eigenschaft und Getter oder
Setter
200
Einführung in die objektorientierte Programmierung
1
2
7.3.4
Benutzung von Properties
3
Angenommen, die Klasse RationalNumber ist wie in Listing 7.63 gegeben. Listing
7.64 demonstriert dann den Zugriff auf die Properties Zaehler und Nenner der
Klasse RationalNumber.
4
01: public class RationalNumberDemo
02: {
03:
public static void Main()
04:
{
05:
RationalNumber r = new RationalNumber();
06:
07:
r.Zaehler = 1;
08:
r.Nenner = 2;
09:
10:
int nenner = r.Nenner;
11:
int zaehler = r.Zaehler;
12:
}
13: }
5
6
7
8
9
10
11
Listing 7.64 Zugriff auf die Properties Zaehler und Nenner der Klasse RationalNumber.
12
Zunächst wird in Zeile 5 von Listing 7.64 eine neue Instanz der Klasse RationalNumber erzeugt. In Zeile 7 wird dann über den Getter des Properties Zaehler dem
(privaten) Member zaehler der Wert 1 und über das Property Nenner entsprechend
dem (privaten) Member nenner der Wert 2 zugewiesen.
13
14
Anschließend werden die Getter der beiden Properties dazu benutzt, die zwei gesetzten Werte wieder einzulesen. D.h. nach Durchlauf der Zeilen 10 und 11 steht in der
lokalen Variablen nenner der Wert des Members nenner und in der lokalen Variablen zaehler entsprechend der andere Member.
15
16
Sie sehen, dass es für die Verwendung keine Rolle spielt, ob Sie ein Property oder ein
Memberdatum nach außen hin offerieren. Das Property bietet ihnen aber die Möglichkeit, den Zugriff auf read-only, write-only oder read-write einzustellen, was mit
normalen Datenmembern nicht erreicht werden kann.
17
18
Des Weiteren kann durch die Verwendung von Properties die interne Darstellung
eines Objektes noch weiter von der Außenwelt gekapselt werden. Auch wenn Sie
sich hinterher dafür entscheiden, die Member umzubenennen, haben Sie nur Änderungen an der Klasse selbst vorzunehmen. Andere Stellen im Code können solange
unverändert bleiben, bis sich entweder die Funktionsweise der Klasse ändert oder Sie
die Properties umbenennen. Sollten Sie so etwas im Sinn haben, dann empfiehlt es
sich, eine neue Klasse zu erstellen, anstatt die alte zurechtzuschneiden – vor allem
wenn es sich um eine Programmbibliothek handelt, die von anderen exzessiv genutzt
wird.
Eigenschaften (Properties)
19
20
A
B
201
7.4
Klassen auf mehrere Dateien verteilen: partielle
Klassen
In C# hat mit der Version 2.0 ein neues Feature Einzug gehalten: Es ist nun möglich,
eine Klasse über mehrere Dateien hinweg zu implementieren. Frühere Versionen des
Compilers erlaubten dies nicht. Eine Klasse musste in einer einzigen Datei vollständig
implementiert werden, die Verteilung der Implementierung ihrer Methoden und
Eigenschaften auf mehrere Dateien war nicht möglich.
Man erstellt dazu zunächst eine Datei, in der eine Hälfte der Klasse implementiert
wird. In der Klassendeklaration brauchen dabei keine zusätzlichen Zeichen hinzugefügt werden (Listing 7.65).
01: public class MyClass
02: {
03:
public MyClass()
04:
{
05:
this.Foo(); // rufe Methode Foo() auf
06:
}
07: }
Listing 7.65 In der ersten Datei findet sich eine normal gestaltete Klassendeklaration.
Auffällig an Listing 7.65 ist, dass in Zeile 5 beim Durchlaufen des Konstruktors die
Membermethode Foo() aufgerufen werden soll – diese existiert aber nicht in Listing
7.65.
Daher wird eine zweite Datei erstellt und dem Projekt hinzugefügt.10 In dieser Datei
steht dann der Rest der Klasse MyClass: die Implementierung der Methode Foo()
(Listing 7.66).
01: public partial class MyClass
02: {
03:
public void Foo()
04:
{
05:
Console.WriteLine("in MyClass::Foo()");
06:
}
07: }
Listing 7.66 Der Rest der Implementierung der Klasse MyClass kann in einer zweiten Datei
stehen.
Die Klassendeklaration von MyClass in Listing 7.66 wurde um das Schlüsselwort
partial erweitert. Dieses zeigt dem Compiler an, dass diese Deklaration ein Teil
einer bereits vorhandenen Klassendeklaration ist.
10 Klicken Sie dazu mit der rechten Maustaste auf das Projekt im Projektmappen-Explorer und
wählen Sie den Eintrag Neues Element aus dem Untermenü Hinzufügen. Aus dem daraufhin
erscheinenden Dialog wählen Sie eine neue C#-Codedatei aus.
202
Einführung in die objektorientierte Programmierung
1
2
Ihre Entwicklungsumgebung Visual C# 2005 Express verwendet diese Methodik, um
bei der Erstellung von Windows-Anwendungen (Kapitel 17 bis 19) automatisch generierten Code vor den Händen des Programmierers zu schützen. Dieser sollte nämlich
nicht verändert werden und wird deshalb in eine separate Datei ausgelagert, um den
Programmierer ja nicht in Versuchung zu führen.
3
4
5
Das Schlüsselwort partial kann vor Klassen, Strukturen und Schnittstellen (Kapitel 8, Generische Klassen, Schnittstellen und statische Klassenmitglieder) stehen. Es ist
auch möglich, die Implementierung über mehr als zwei Dateien zu streuen!
6
7
7.5
Ref- & Out-Parameter von Methoden
8
7.5.1
Ref-Parameter
9
Oftmals tritt der Fall ein, dass eine Methode mehr als nur einen Wert zurückliefern
muss, oder etwas seltener, dass ein Parameter nicht nur lokal in einer Methode geändert werden muss, sondern auch außerhalb.
10
Dies kann über die Ref-Parameter (auch als Verweisparameter bezeichnet) erreicht
werden. Anstatt eine Kopie eines Wertetypen zu erzeugen, wird stattdessen im
Methodenaufruf dieselbe Variable verwendet. Dies führt dazu, dass eine Änderung
des Parameters in der Methode eine Änderung der Variable außerhalb der Methode
nach sich zieht (call-by-reference).
11
12
13
Betrachten Sie als Beispiel folgende Problemstellung: Eine Methode soll die Werte
zweier int-Parameter austauschen, und zwar nicht nur in der Methode selbst, sondern auch außerhalb der Methode.
14
15
Die Lösung ist, die Parameter als Ref-Parameter zu deklarieren. Dazu schreibt man das
Schlüsselwort ref vor den Typ der Parameter, die als Referenz übergeben werden sollen. Listing 7.67 zeigt die zu Grunde liegende Syntax.
16
public void Foo(ref <Parametertyp> <Bezeichner>)
17
Listing 7.67 Syntax zur Deklaration eines Ref-Parameters
18
Damit lässt sich eine Klasse RefSwap schreiben, die der obigen Spezifikation genügt
(Listing 7.68).
19
01: public class RefSwap
02: {
03:
public void Swap(ref int a, ref int b)
04:
{
05:
int temp = a;
06:
a = b;
07:
b = temp;
Ref- & Out-Parameter von Methoden
20
A
B
203
08:
09: }
}
Listing 7.68 RefSwap stellt eine Methode Swap() zur Verfügung, die zwei int-Parameter
vertauscht (RefSwap).
Durch die Deklaration der beiden Methodenparameter von Swap() greifen Zeile 6
und 7 von Listing 7.68 nicht auf lokale Kopien der Parameterwerte zu, sondern auf
die Variablen, die beim Methodenaufruf übergeben wurden.
Der Aufruf einer Methode, die einen oder mehrere Ref-Parameter erwartet, ändert
sich nur gering: Vor die Parameter, die als Referenz übergeben werden sollen, ist nun
ebenfalls das Schlüsselwort ref zu setzen.
01: public static void Main()
02: {
03:
RefSwap rs = new RefSwap();
04:
int x = 1;
05:
int y = 2;
06:
07:
Console.WriteLine("x = {0}, y = {1}", x, y);
08:
rs.Swap(ref x, ref y);
09:
Console.WriteLine("x = {0}, y = {1}", x, y);
10: }
Listing 7.69 Der Aufruf einer Methode, die zwei Ref-Parameter erwartet (RefSwap)
Listing 7.69 zeigt ein Beispiel für eine Main()-Methode, die die Swap()-Methode
eines Objektes vom Typ RefSwap aufruft (Zeile 8). Dazu werden die beiden Variablen
x und y mit dem Schlüsselwort ref gekennzeichnet. Das Ergebnis ist, dass in der Variablen x der Wert steht, der vormals in y stand und umgekehrt.
7.5.2
Out-Parameter
Theoretisch kann man Ref-Parameter also dazu einsetzen, eine Methode mehr als nur
einen Wert zurückgeben zu lassen. Etwas unangenehm ist, dass Ref-Parameter initialisiert werden müssen, ehe sie übergeben werden können. Der Compiler überprüft,
ob eine Initialisierung durchgeführt wurde, da es möglich ist, dass die verarbeitende
Methode die Parameterwerte zuerst noch für Berechnungen heranzieht, theoretisch
also nicht-initalisierte Variablen verwenden würde.
Man kann in den Fällen, in denen Ref-Parameter nur als zusätzlicher Rückgabeweg
genutzt werden soll, in der Methodendeklaration statt des Schlüsselwortes ref das
Schlüsselwort out verwenden. out kennzeichnet Parameter, die nur in einer Richtung »übergeben« werden: von der Methode zurück an den Aufrufer. Zu diesem
Zweck müssen sie aber vorher nicht initialisiert sein, da die Methode dann vorhandene Werte auf alle Fälle überschriebe.
01: public class OutParameter
02: {
204
Einführung in die objektorientierte Programmierung
1
2
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22: }
public int Foo(int inParam, out int outParam)
{
outParam = inParam * inParam;
return 2 * inParam;
}
3
4
5
public static void Main()
{
OutParameter op = new OutParameter();
int inP = 3;
int outP;
int retVal = op.Foo(inP, out outP);
6
7
8
Console.WriteLine(
"inP = {0}, outP = {1}, retVal = {2}",
inP,
outP,
retVal);
9
10
11
}
12
Listing 7.70 Eine Methode kann mit Hilfe von Out-Parametern mehr als nur einen Wert als
Ergebnis an den Aufrufer zurückgeben (OutParameter).
13
Listing 7.70 zeigt ein Beispiel, in dem eine Methode Foo() zwei Parameter erwartet:
Einer wird dazu genutzt, das normale Methodenergebnis in Form eines return-Wertes zu erstellen, der zweite Parameter ist ein Out-Parameter, der an den Aufrufer
zurückgegeben wird.
14
15
Wenn die Methode Foo() in Zeile 14 aufgerufen wird, kann outP, der Out-Parameter,
zwar initialisiert sein, muss es aber nicht. outP muss beim Methodenaufruf aber mit
dem Schlüsselwort out dekoriert werden, um zu kennzeichnen, dass an dieser Stelle
ein Verweis anstelle eines Wertes für den Aufruf verwendet wird.
16
17
Tatsächlich steht hinter einem Out-Parameter nur ein Ref-Parameter, der nicht initialisiert sein muss. Andere .NET-Sprachen treffen beispielsweise diese Unterscheidung
in Ref- und Out-Parameter nicht. Daher ist eine einheitliche Basis für alle erforderlich.
Die strenge Syntax von C# erlaubt aber hier eine zusätzliche Differenzierung.
7.6
18
19
20
Die Speicherverwaltung von .NET
Bereits mehrfach habe ich erwähnt, dass .NET über einen so genannten Garbage Collector verfügt. Ein Garbage Collector durchkämmt den Speicher nach Objekten, auf
die keine Referenzen mehr existieren. Diese Objekte sind nutzlos, da sie nicht mehr
verwendet werden können, und der belegte Speicher wird durch den Garbage Collector freigegeben und dadurch dem Programm wieder zur Verfügung gestellt.
Die Speicherverwaltung von .NET
A
B
205
Damit entfällt ein großes Problem, das bei vielen nicht .NET-Programmen bestand
und auch heute noch besteht: Speicher wird zwar reserviert, aber nie mehr freigegeben. Dadurch steigt der Speicherverbrauch einer Anwendung solange, bis diese keinen Speicher mehr vom System erhält und abstürzt. Natürlich nicht, ohne vorher den
Rechner in seiner Geschwindigkeit zu bremsen, da laufend Daten aus dem Speicher
auf die Festplatte und wieder zurück geschoben werden müssen, um dem enormen
Speicherverbrauch dieser Anwendung nachzukommen.
Diese so genannten Memory Leaks waren zudem äußerst schwer zu lokalisieren und
auszumerzen. Dies ging soweit, dass ein nicht unerheblicher Teil der Entwicklungszeit manchmal dafür eingeplant wird, diese Löcher zu stopfen!
Mit dem Garbage Collector unter .NET besteht dieses Problem für .NET-Programme
nun nicht mehr: Hier bereinigt das System selbst den Speicher, wenn dieser zu knapp
wird. Die Geschwindigkeit, die .NET dabei an den Tag legt, ist enorm – zumindest
habe ich noch nie einen Lauf des Garbage Collectors bemerkt, obwohl dieser die
betreffende Anwendung kurz anhält, um die Daten für eine Speicherbereinigung zu
sammeln!
Eine Schattenseite dieser Medaille ist, dass die Zerstörung von Objekten, also das Entfernen aus dem Speicher, praktisch zu jedem beliebigen Zeitpunkt eintreten kann, da
Objekte nicht mehr explizit freigegeben werden müssen.
Belegt ein Objekt daher Ressourcen, die nicht von .NET verwaltet werden und die
unbedingt freigegeben werden müssen (z.B. Netzwerkverbindungen etc.), bietet
.NET über die Klasse System.Object die Methode Finalize() an.
7.6.1
Finalize() und C#-Destruktoren
Allgemein gilt für .NET Objekte, dass diese über eine Methode Finalize() verfügen.
Diese wird vom Garbage Collector aufgerufen, bevor das Objekt aus dem Speicher
entfernt wird.
In C# gibt es für die Deklaration einer Finalize()-Methode eine spezielle Schreibweise. Diese ist der Schreibweise eines Destruktors11 in C++ sehr ähnlich. Ich möchte
diese Methode daher im Folgenden auch als Destruktor bezeichnen.
class <Name>
{
~<Name>()
{
// Aufräumarbeiten
}
}
Listing 7.71 Syntax zur Deklaration einer Finalize()-Methode in C#
11 Ein Destruktor ist eine spezielle Objektmethode in C++, die immer vor der Zerstörung
eines Objektes durchlaufen wird (Bezeichnung analog zum Konstruktor).
206
Einführung in die objektorientierte Programmierung
1
2
Ein Destruktor (Syntax in Listing 7.71) trägt den selben Namen wie die Klasse, außer
dass dem Namen noch das Tilde-Zeichen ~ vorangestellt wird. Der Destruktor erhält
keine Parameter und hat auch keinen Rückgabetyp oder eine Sichtbarkeitsangabe!
3
4
Der Aufruf des Basisklassendestruktors erfolgt implizit durch den Compiler. Sie müssen also keine Sorge dafür tragen, den Destruktor der Basisklasse aufzurufen.
5
Erfolgte der Aufruf des Basisklassenkonstruktors noch vor der Abarbeitung des Konstruktors der abgeleiteten Klasse, so ist dies bei Destruktoren genau entgegengesetzt:
Zuerst läuft der Destruktor der Kindklasse. Erst danach wird der Destruktor der Basisklasse aufgerufen.
6
7
In Listing 7.72 sehen Sie ein Beispiel für die Deklaration von Destruktoren. Beachten
Sie, dass diese erst nach der letzten Anweisung des Programms aufgerufen werden,
auch wenn vorher die Referenz im Leeren hängt und das Objekt eigentlich reif für
das Datennirwana wäre.
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
8
9
using System;
10
public class Base
{
~Base()
{
Console.WriteLine("Base::~Base()");
}
}
11
12
13
14
public class Derived : Base
{
~Derived()
{
Console.WriteLine("Derived::~Derived()");
}
}
15
16
17
18
public class Demo
{
public static void Main()
{
Derived d = new Derived();
19
20
A
d = null;
Console.WriteLine("Programm-Ende");
B
}
}
Listing 7.72 Beispiel, das die Aufrufreihenfolge der Destruktoren zeigt (Finalize)
Die Speicherverwaltung von .NET
207
Destruktoren sind in C# nicht vererbbar und können auch nicht überladen werden! Die Methode Finalize() ist nicht direkt aufrufbar!
7.6.2
Referenzen auf Objekte während Finalisierung
Ein Problem bei dieser Aufräummethode ist, dass keine Aussage darüber getroffen
werden kann, in welcher Reihenfolge Objekte finalisiert werden. Das bedeutet, dass
der Programmierer stets beachten muss, dass Objekte, auf die das gerade zu finalisierende Objekt noch Referenzen hält, bereits vorher ihren Destruktor durchlaufen
haben.
Referenziert ein Objekt weitere Objekte und sind keine weiteren Referenzen auf
diese im restlichen Programm enthalten, fallen diese Objekte zusammen mit dem ersten der Garbage Collection zum Opfer. Sie können aber in praktisch beliebiger Reihenfolge finalisiert werden!
7.7
Zusammenfassung
Objektorientierung bietet die Möglichkeit, leicht und einfach Modelle der Realität in
Computerprogramme zu fassen. Die Abstraktion, die dahinter steht (welche Daten
und Methode zu einer Klasse gehören), ermöglicht es, Programme aus einzelnen Bausteinen, den Objekten, aufzubauen. Diese Bausteine können dann leichter auch in
anderen Projekten wieder verwendet werden, da Daten und darauf arbeitende
Methoden durch die Klasse gekapselt werden.
7.7.1
Klassen
Das zentrale Element in der Objektorientierung ist die Klasse. Sie dient als Vorlage
für die Erzeugung von Instanzen. Erst Instanzen können dann Daten eines Objektes
aufnehmen. Eine Klasse wird mit Hilfe des Schlüsselwortes class deklariert. Klassen
sind dabei normalerweise in Namensräumen enthalten.
Klassen kapseln aber nicht nur Daten und Methoden, sondern sie versehen diese
noch mit einer Art Zugriffsschutz und können Daten und Methoden nach außen hin
verbergen und damit dem Zugriff entziehen.
Dies bringt den Vorteil mit sich, dass die Implementierung einer Klasse, also das »Wie
sieht die Verarbeitung aus«, getauscht werden kann, solange die nach außen hin sichtbaren Methoden und Daten nicht umbenannt werden.
Klasseninstanzen werden mit Hilfe des Operators new erstellt. new ruft bei der Erstellung eines Objekts eine spezielle Methode der Klasse auf, den Konstruktor. Dieser ist
dafür gedacht, Initialisierungsarbeiten auszuführen, die bei Erstellung einer Instanz
anfallen. Bevor er abgearbeitet wird, wird zunächst der Konstruktor der Basisklasse
durchlaufen. Dies setzt sich bis zur Spitze der Ableitungshierarchie hin fort.
Ein Destruktor wird dann ausgeführt, sobald der Garbage Collector von .NET das
Objekt aus dem Speicher entfernt. Dies kann ab dem Zeitpunkt eintreten, an dem die
208
Einführung in die objektorientierte Programmierung
1
2
letzte Referenz im Programm auf das Objekt wegfällt. Ein Destruktor wird zunächst
durchlaufen, bevor der Basisklassendestruktor aufgerufen wird. Dass ein Destruktor
keine Parameter hat, hat zur Konsequenz, dass er nicht überladen werden kann. Auch
werden Destruktoren nicht vererbt.
3
4
Referenzen auf die Basisklasse können für alle abgeleiteten Klassen eingesetzt werden. Oftmals werden solche Basisklassen dann als abstrakte Klassen implementiert.
Von abstrakten Klassen kann keine Instanz erstellt werden.
5
6
7.7.2
Properties
7
Properties sehen nach außen wie Datenmember aus. Properties erlauben aber im
Gegensatz zu Datenmembern, den Zugriff wahlweise auf nur Lesen, nur Schreiben
oder Lesen und Schreiben einzuschränken. Properties eignen sich insbesondere für
Daten, bei deren Änderung ein Seiteneffekt (z.B. Neuberechnung) eintritt. Sie können zudem die zuzuweisenden Werte überprüfen, bevor die Zuweisung ausgeführt
wird.
8
9
10
7.7.3
Methoden
11
Die Funktionalität von Objekten und Klassen liegt in den Methoden. Methoden sind
kleine Programme, die Daten manipulieren. Sie können Werte berechnen und diesen
zurückgeben.
12
In einer Ableitungshierarchie können Methoden je nach eingestellter Sichtbarkeitsstufe von der Vaterklasse an die Kinder vererbt werden. Für den Fall, dass eine Referenz auf eine Basisklasse als universeller Speicher verwendet wird, gibt es den virtuellen Dispatching-Mechanismus: Bei Aufruf einer als virtuell deklarierten Methode
erfolgt der Aufruf am tatsächlichen Typ des Objektes und ist nicht abhängig vom Typ
der Referenz.
13
14
15
Soll eine Methode mehr als nur einen Wert zurückgeben, können Ref- oder Out-Parameter verwendet werden. Eine andere Möglichkeit ist, die Rückgabewerte in einer
Struktur zusammenzufassen und diese an den Aufrufer zurückzugeben.
7.8
16
17
Übungen
왘 Übung 7.1
18
***
19
Es soll ein Stack implementiert werden. Ein Stack ist eine Speicherart, bei der die Elemente der Reihe nach im Speicher angeordnet werden. Das zuletzt eingefügte Element wird als erstes wieder entnommen.
20
Entwerfen Sie hierzu mit Hilfe von UML zunächst theoretisch eine Klasse Stack. Die
Klasse Stack soll dabei über die in Tabelle 7.7 gezeigte Funktionalität verfügen.
A
B
Übungen
209
Methode
Funktion
bool Push(object o)
Legt ein beliebiges Datum auf den Stack. Die Methode gibt true
zurück, falls das übergebene Datum erfolgreich auf den Stack gelegt
werden konnte, andernfalls false.
object Pop()
Liefert das als letztes auf den Stack gelegte Objekt. Ist der Stack leer,
gibt die Methode null zurück.
bool IsEmpty()
Liefert true zurück, falls der Stack leer ist, ansonsten false.
void Init()
Initialisiert den Stack, indem der Stack geleert wird. Ein darauf folgender Aufruf von IsEmpty() muss true als Ergebnis liefern.
Tabelle 7.7 Die Funktionalität eines Stacks
Verwenden Sie NSDs, um die Schritte zum Einfügen eines Datums und zum Herauslesen zu beschreiben.
Implementieren Sie Ihre Lösung. Schreiben Sie zusätzlich eine Klasse StackDemo, die
eine Main()-Methode enthält. In dieser legen Sie Werte an und legen Sie auf den
Stack und holen Sie diese wieder herunter.
Hinweis: Sie können davon ausgehen, dass ein Stack eine gewisse Maximalgröße
nicht überschreitet. Verwenden Sie für die Sicherung der Daten ein Array.
왘 Übung 7.2
****
Für eine Bank soll eine Kontenverwaltung geschrieben werden.
1. Entwerfen Sie eine geeignete Basisklasse Konto, die die gemeinsamen Daten (z.B.
Kontonummer, Sparbetrag, Zinssatz etc.) vereint. Die Kundendaten sollen in einer
extra Klasse Kunde verwaltet werden. Diese Klasse erhält im Konstruktor alle erforderlichen Daten. Die Kundendaten werden über eine Assoziation (* – 1) in das
Konto eingegliedert, d.h. zu einem Konto gibt es genau einen Kunden, ein Kunde
kann aber mehrere Konten besitzen. Denken Sie bei den gemeinsamen Daten auch
an die Sichtbarkeitsstufe nach außen hin!
2. Erstellen Sie eine abstrakte Methode Verzinsen() in der Klasse Konto.
3. In einer Bank gibt es kein allgemeines »Konto«, sondern es gibt Girokonten, Sparkonten etc. Erstellen Sie zwei Klassen GiroKonto und SparKonto, die von Konto
erben. In deren Konstruktoren muss der Zinssatz mit übergeben werden. Implementieren Sie nun die Methode Verzinsen() in den Klassen SparKonto und GiroKonto, so dass die abstrakte Version der Basisklasse überschrieben wird. Gehen
Sie davon aus, dass die Methode Verzinsen() die Zinsen für einen Monat berechnet und diese auf den aktuellen Sparbetrag aufaddiert.
4. Erstellen Sie zwei Methoden Einzahlen() und Auszahlen() in der Klasse Konto.
Wo müssen diese Methoden implementiert werden? Beide erhalten als Parameter
den Betrag, der eingezahlt oder abgehoben werden soll. Auszahlen() gibt zudem
einen Wert vom Typ bool zurück, der angibt, ob die Auszahlung ausgeführt werden konnte oder nicht. Wenn eine Auszahlung nicht ausgeführt werden kann, liegt
210
Einführung in die objektorientierte Programmierung
1
2
das daran, dass der Sparbetrag kleiner ist als der Auszahlungsbetrag. Der Einfachheit halber werden nur Konten betrachtet, die nicht überzogen werden können.
Zusätzlich geben die Methoden der Klasse Girokonto aber die jeweiligen Kontobewegungen als Textmeldungen auf dem Bildschirm aus.
3
4
5. Erstellen Sie ein read-only-Property Kontostand (Typ: double), das den aktuellen
Kontostand zurückliefert.
5
6. Erstellen Sie ein Property Eigentuemer (Typ: Kunde), das den Eigentümer dieses
Kontos ermittelt oder neu setzt.
6
7. Schreiben Sie ein Hauptprogramm Main() in einer separaten Klasse. Dieses soll
z.B. ein GiroKonto erstellen, den zugehörigen Kunden anlegen und Ein- bzw. Auszahlungen tätigen. Zur besseren Bedienung können Sie eine Steuerung über die
Tastatur einbauen und die Methoden mit Tasten verknüpfen. Wie Sie Werte von
der Tastatur einlesen können, haben Sie bereits in einem vorangehenden Kapitel
gelernt. Testen Sie auch den Fall, dass ein Kunde mehrere Konten besitzen kann!
7
8
9
Hinweis: Es ist sehr hilfreich, sich vor der Implementierung das Szenario mit Hilfe
von UML auf einem Blatt Papier aufzuzeichnen!
10
11
12
13
14
15
16
17
18
19
20
A
B
Übungen
211
Index
-- (Post-Dekrement) 108
-- (Pre-Dekrement) 117
- (Subtraktion) 103, 285
- (Vorzeichen) 116, 283
! 116, 283
-- 283
!= 120, 287
#define 382
#elif 382
#else 382
#endif 382
#endregion 385
#error 385
#if 382
! 384
!= 384
&& 384
== 384
|| 384
#line 385
#region 385
#undef 382
#warning 385
% 104, 285
%= 105
& 122, 285
&& 123, 289
&= 123
() (Methodenaufruf) 107, 165
() (runde Klammern) 106
() (Typumwandlung) 118
* 103, 285
*= 105
+ 103, 285
+ (Vorzeichen) 283
+ (Zeichenketten) 104
++ 283
++ (Post-Inkrement) 108
++ (Pre-Inkrement) 117
+= 105, 298, 300
, 132
. (Memberzugriff) 107, 161, 289
.NET
.NET Framework 19
Assembly 42
C# 22
COM Interop 20
Common Language Runtime (CLR) 43
Compact Framework 20
Exceptions 18
Garbage Collection 17
IL Disassembler 43
Intermediate Language 20
Just-In-Time Compiler 20
Linux 20
Memory Leaks 21
Mono 20
Polymorphie 18
Übersicht 17
Webservices 18
/ 103, 285
/* */ 371
// 371
/// 371
/= 105
/unsafe 112
< 120, 287
<< 119, 285
<<= 119–120
<= 287
<list>
bullet 380
number 380
table 380
-= 105, 298, 300, 304
= 105, 289
== 120, 249, 287
> 120, 287
>= 120, 287
>> 119, 285
>>= 119
?: 124, 289
[] 108, 319
^ 122–123, 285
^= 123
| 122, 285
|= 123
|| 123, 289
~ 117, 283
~ (Destruktor) 206
A
Ableitungshierarchie 218
abstract 194–195
Alignment 113
AnchorStyles 453
anonyme Methoden 309
AppDomain 76
Index
583
Application 468
ArgumentNullException 275
Array 68
Deklaration 69
foreach 70, 138
Initialisierung 69
mehrdimensionale Arrays 72
unregelmäßige Arrays 73
Zugriff 70, 108
ArrayList 331
Member 331
as 121, 180
Assembly 355, 486
GetExecutingAssembly() 355
GetModules() 357
GetName() 357
AssemblyCompany 364
AssemblyCopyright 364
AssemblyDescription 364
AssemblyInfo.cs 349
AssemblyName 357
CodeBase 357
Name 357
Version 357
AssemblyProduct 364
AssemblyTitle 364
AssemblyTrademark 364
AssemblyVersion 364
AsyncCallback 418
asynchrone Methodenaufrufe 414
BeginInvoke() 414
Callbacks und Zustandsobjekt 418
EndInvoke() 414
Methodenaufruf mit Parametern
und Rückgabewert 417
Methodenaufruf ohne Parameter
und Rückgabewert 415
AsyncResult 419
AsyncDelegate 419
AsyncWaitHandle 417
Attribute 341, 350
AssemblyInfo.cs 349
aus Metadaten auslesen 362
durch C# reservierte 342
eigene erstellen 349
erweiterte Syntax 348
globale 349
im Code platzieren 342
Syntax 342
Ziele 352
AttributeTargets 347
Werte 347
584
Index
AttributeUsage 342, 347
Properties 348
Aufzählung 89
Ausgabe 62
formatiert 63
Variablen 62
Währung 64
Ausnahme 167
B
base 184, 233
Bedingungen 139
if 139
switch-case 144
Benutzeroberfläche 425
Beschriftungen
Label 492
LinkLabel 492
Bezeichner 53
Bézier-Kurve 519
Bibliotheken 555
dynamische 555
Main() 556
statische 555
Bilder 539
Formate 539
Bildlisten 542
Bildschirmausgaben 41
binäre Periodizität 51
BindingFlags 359
Bitmap 539
GetPixel() 541
SetPixel() 541
Bitmaske 122
bool 52, 364
BorderStyle 479
Bottom-Up 362
Boxing 85, 89, 119, 213, 288
break 138, 144
Brush 506, 512
Brushes 515
Button 485, 561
Click 485, 567
ImageAlign 487
TextAlign 487
Buttons 485
byte 50, 364
C
CancelEventArgs 442–443
Cancel 442–443
CancelEventHandler 442
Cardinal-Spline-Kurve 521
case 144
Cast 118, 300
catch 272
Category 501
char 50, 247, 364
CheckBox 488
Checked 488
CheckedChanged 488
CheckState 488
CheckStateChanged 488
ThreeState 488
checked 113
CheckedListBox 495
CheckState 488
class 157
CloseReason 442
CodeDOM 560, 562
Collection 250
Collections 330
ArrayList 331
Hashtable 337
Queue 333
Stack 335
Color 507–508
FromArgb() 508
GetBrightness() 509
GetHue() 509
GetSaturation() 509
COM 222, 341
ComboBox 495
CompilerParameters 563
CompilerOptions 564
ReferencedAssemblies 564
ReferencedAssemblies.Add() 564
CompilerResult
Errors 565
CompilerResults 564
Component Object Model 222, 341
Conditional 342, 344
Console 42, 76, 569
const 76
Constraints 223
Container 238
Anmerkungen 484
Dock & Anchor 484
Fenster, Formulare 479
FlowLayoutPanel 480
GroupBox 484
Panels 479
SplitContainer 482
TabControl 484
TableLayoutPanel 480
Container-Controls 456
ContextMenuStrip 500
continue 138
Control 456, 471, 506
Anchor 473
BackColor 472
BackgroundImage 472, 494
BackgroundImageLayout 472
BeginInvoke() 474–475
Click 456, 477
ClientSize 516
Contains() 475
CreateGraphics() 475, 537
Cursor 472–473
Dispose() 475
Dock 473
DoubleBuffered 438
DoubleClick 477
Eigenschaften 471
Enabled 474–475
EndInvoke() 474–475
Ereignisse 477
FindForm() 475
Focus() 475
Font 472
ForeColor 473
GetChildAtPoint() 476
GetContainerControl() 476
GetNextControl() 476
GetStyle() 438, 476
Hide() 476
Invalidate() 476
Invoke 474
Invoke() 475, 501
InvokeRequired 474–475
KeyDown 477
KeyPress 477
KeyUp 477
Location 473
Margin 473
MaximumSize 473
Methoden 475
MinimumSize 473
MouseClick 477
MouseDoubleClick 477
MouseDown 477
MouseEnter 478
MouseHover 478
MouseLeave 478
Index
585
MouseMove 478
MouseUp 477
MouseWheel 478
Move 478
OnPaint() 541–542
Padding 473
Paint 478, 541
Parent 474
Refresh() 476
Resize 478
RightToLeft 473
SetStyle() 438, 476
Show() 476
Size 473
TabIndex 474, 476
TabStop 474, 476
Tag 473
Text 473
Update() 476
UseWaitCursor 473
Visible 474–475
ControlStyles 438
AllPaintingInWmPaint 438
OptimizedDoubleBuffer 438
CSharpCodeProvider 563
CYMK-Farbmodell 509
D
DashStyle
Enumerationsmember 510
DateTime 491
Debug 569
WriteLine() 569
Debuggen 124
Debugger 571
Haltepunkt 571
Tastenkombinationen 571
Variablenwerte anzeigen 574
decimal 52
default 144
Deklaration
Inline 309
Delegate
BeginInvoke() 416
EndInvoke() 416
delegate 292
Delegates 291
Aufruf 294
Combine() 298
Deklaration 292
Erzeugung 295
Exceptions im Callback 301
586
Index
Generische 309
GetInvocationList() 301
Multicast-Delegates 297
Remove() 298
Rückgabewerte 301
Schreibweise in C# 2.0 310
Typparameter 309
Verwendung 293
Description 501
Destruktor 206, 237
Deklaration 206
Dialoge
vorgefertigte 458
DialogResult 456
Dispose() 236
DivideByZeroException 275
DLL 555, 557
do 136
DockStyle 452
Dokumentations-Tags
371, 373–374, 376–380
Übersicht 372
double 52, 364
Double Buffering 438
do-while 136
Drucken 542
Druckvorschau 548
Seitenvorschau 551
E
Effizienz 216
Eigenschaftsfenster 33
Einschränkungen 223
Einsprungspunkt 38, 40
elementare Datentypen 49
else 139
enum 91
Enumeration 89
Basistyp 93
Deklaration 91
Zuweisung von Werten 91
Enumeratoren 321
Definition 321
typsicher 328
Verwendung 327
Ereignisse 302
Deklaration 305
Hintergrund 305
Rückgabewerte 309
Vergleich mit Delegate 302
Escape-Sequenzen 59–60, 261
Übersicht 59
EVA-Prinzip 47
event 305
EventArgs 307, 441
EventHandler 441–442, 447, 477–478
Events 304
Source und Sink 307
Exception 167, 200, 237
Exceptions 269
.NET 271
caller beware 281
caller confuse 281
caller inform 281
eigene Exception-Klassen 277
Exception-Klassen 274
fangen 269, 272
finally 278
Funktionsweise 269
in C# 271
in Delegates 301
Klasse 271
Member 273
Nachteile 270
Nachteile, Codierungsaufwand 270
Nachteile, Performance 270
Verhaltensweisen 279
Werfen 269, 271
F
false 52, 283
Farben 507
Fehlerliste 33
Feld 68
Fenster 425, 427
Definition und Funktionsweise 425
Einfügen von Elementen 447
Nachrichten 426
Titel 473
transparent 434
FIFO 333
FileNotFoundException 540
FillMode 532, 535
Finalize() 236
Finalizer 237
finally 278
Flag 148
float 52, 364
FlowLayoutPanel 480
for 129, 247, 321
foreach 70, 138, 247, 321, 327, 330
Form 427, 473, 479
Activate() 437
Anchor 451, 453
BackColor 431
BackColorChanged 441
BackgroundImage 431
BackgroundImageChanged 441
ClientSize 431, 436, 450, 516
Close() 429, 437, 456
Closed 442
Closing 440, 442
ContextMenu 431
ControlBox 431
Controls 431
DefaultSize 436
DesktopBounds 432
DesktopLocation 432
Details 430
Dock 451–452
DoubleBuffered 438
Einfügen von Elementen 447
EventHandler 446
Fenstertitel 473
FormBorderStyle 432
FormClosed 442
FormClosing 442
GetChildAtPoint() 446
GetStyle() 438
HelpButton 433
HelpButtonClicked 433, 443
HelpRequested 433, 443
Hide() 438
Icon 434
InitializeComponent() 450
Koordinatensystem 450
Load 446
Location 452
MaximizeBox 431, 433–434
MaximumSize 434
MinimizeBox 431, 433–434
MinimumSize 434
Name 434
OnClosing() 440
OnHelpButtonClicked() 443
OnHelpRequested() 445
OnPaint() 441, 506
Opacity 434
Paint 441, 446, 506
PointToClient() 445
Refresh() 440
Resize 446
ResizeBegin 446
ResizeEnd 446
ResizeRedraw 435
ResumeLayout() 467
Index
587
SetStyle() 438
Show() 438, 440, 456
ShowDialog() 440, 456, 550
ShowIcon 431, 435
ShowInTaskbar 435
Shown 447
Size 436
SizeChanged 446
SizeGripStyle 436
SuspendLayout() 467
Text 436
Visible 437–438, 447
VisibleChanged 447
wichtige Eigenschaften 430
wichtige Ereignisse 441
wichtige Methoden 437
WindowState 437
formatierte Ausgabe 63
Formatierungszeichen 65
FormBorderStyle 432
Aufzählungsmember 432
FixedDialog 456
FormClosedEventArgs
CloseReason 442
FormClosedEventHandler 442
FormClosingEventArgs 443
CloseReason 442
FormClosingEventHandler 442
Formulare 427
FormWindowState 437
G
ganzzahlige Werte 50
Garbage Collection 21, 73
Garbage Collector 74, 88, 163, 205, 236
GDI+ 427, 505
Bildlisten 542
Fehler in der Ausgabe 534
Füllen von Flächen 531
Grafische Pfade 535
Icons und Bilder 537
Koordinaten & Punkte 515
Methoden zum Zeichnen 516
Punkte ausgeben 534
Rechtecke 515
Regionen 537
Stifte & Pinsel 510
gebrochene Werte 51
GenerateMember 463
Generics 213
Collections in .NET ab v2.0 331
588
Index
generische Klassen
Vererbung 216
generische Methoden 217
get 198
Gleitkommadatentypen 52
Globale Attribute 349
goto 148
Sprungmarken 148
grafische Benutzeroberfläche 425
Graphics 505
DrawArc() 518, 528
DrawBezier() 519
DrawBeziers() 519
DrawClosedCurve() 521
DrawCurve() 521
DrawEllipse() 523
DrawIcon() 524
DrawIconUnstretched() 524
DrawImage() 525
DrawImageUnscaled() 525
DrawImageUnscaledAndClipped()
525
DrawLine() 526
DrawLines() 526
DrawPath() 527, 536
DrawPie() 528
DrawPolygon() 529
DrawRectangle() 530
DrawRectangles() 530
DrawString() 530
Erzeugen 537
FillClosedCurve() 532
FillPath() 532, 536
FillPolygon() 532
FillRegion() 534
Garbage Collection 506
GraphicsPath 514, 527, 535
CloseFigure() 536
Hinzufügen geometrischer Formen
535
GroupBox 484, 489
Gültigkeitsbereich 67
H
Haltepunkt 571
Handler 301
Hashtable 288, 337
Hashwert 288
Schlüssel 337
Hashwert 288
HatchBrush 511–512
HatchStyle 513
Hello, World! 29
HelpEventArgs 444
HelpEventHandler 444
HSB-Farbmodell 509
I
IAsyncResult 415, 418
AsyncState 420
ICodeCompiler 563
Icon 538
ExtractAssociatedIcon() 539
Icons 538
IDisposable 236
Dispose() 236
IEnumerable 321
Member 322
IEnumerator 321
Current 322
Member 322
MoveNext() 322
Reset() 322
if 139
Hintereinanderschaltung 142
Schachtelung (Kaskadierung) 141
Image 539
FromFile() 540
Save() 542
ImageList 542
Indexer 247, 315
Indizierer 315, 319
Deklaration 315
mit mehreren Parametern 320
this 319
Verwendung 318
Inline-Deklaration 309
Instanzdaten 159
int 50, 364
interface 218
Intermediate Language 20, 50
internal 157, 182–183
internal protected 182, 184
InvalidOperationException 323
is 121, 180
J
Java
Java 21
Java Byte-Code 21
Java-Applet 21
K
KeyEventHandler 477
KeyPressedEventHandler 477
Klassen 155–156
abstrakte Klassen 194
Aufruf des Basisklassenkonstruktors 184
Begriffsdefinition 156
Deklaration 39, 157
Finalize() und Destruktoren 206
generisch 213
Implementierung einer
Schnittstelle 220
Instantiierung 158
Instanz 158
Instanzdaten 159
Klassenmember 224
konkrete 216
Konstruktor 172
kurze Einführung 38
mehrere Konstruktoren 175
Memberdaten 159
partial 202
private Konstruktoren 227
Properties 196
Referenzen auf Basisklassen 180
Schnittstelle 218
sealed 187
Sichtbarkeitsspezifizierer 157, 182
Standardkonstruktor 173
statische 226
statische Konstruktoren 176
Überladen von Methoden 188
Überschreiben von Methoden 191
Umbenennung 96
Vererbung 177
virtuelle Methoden 191
Klassenbibliothek 556
Klassendiagramm 157
Klassenfabrik 184
Klassenmember 224
Daten 225
Eigenschaften 226
Methoden 225
Kommentare 35
Blockkommentar 36
Dokumentationskommentar 371
Zeilenkommentar 37, 371
Konkatenation 104
Konstante 41, 57
Konstruktor
private 184
Index
589
Konvertierungshierarchien 119
kritischer Fehler 267
L
Label 492
AutoEllipsis 493
AutoSize 493
Ländereinstellungen 64
LIFO 335
LinearGradientBrush 512
LinkLabel 492
LinkArea 493
LinkBehavior 493
LinkClicked 494
LinkColor 493
LinkVisited 493
ListBox 495
Items 495
MultiColumn 495
SelectedIndex 495
SelectedIndices 495
SelectedItem 495
SelectedItems 495
SelectionMode 495
Sorted 495
ListView 496, 542
Columns 496
Groups 496
Items 496
View 496
Literal 41, 57
boolesches 59
ganzzahlig 57
Qualifizierer 58
reales (Gleitkomma) 58
Stringliteral 60
Zeichenliteral 59
lock 402, 412
long 50, 364
M
Main() 40, 166, 225, 556
Parameter 74
Makros 560
Manifest 43
MaskedTextBox 490
Mask 491
Validate() 491
Masken 122
Match 263
Member 263
Mehrfachvererbung 218
590
Index
Member
öffentlich 183
Memberdaten 159
Initialisierungsreihenfolge 174
MemberInfo 359
MemberTypes
Werte 359
Memberzugriffsoperator 107
Menüs 497
Kontextmenüs 500
MenuStrip 498
GripStyle 498
MessageBox 446, 458, 485, 561, 565
Icon 459
MessageBoxIcon 459
Metadaten 353
Metafile 539
Metazeichen 260
Quantifizierer 260
Methode 40, 182
abstrakte 195
anonyme 309
Argumente 166
Aufruf 165
Aufruf (Operator) 107
Dispatching, virtuell 209, 232–233
generische 217
mit Parameter 166
mit Rückgabewert 170
Name 164
Neuimplementierung 228, 230
Out-Parameter 204
Parameterliste 164–165
Polymorphie 192
Ref-Parameter 203
Rückgabetyp 163
sealed vor virtuelle Methoden 233
statische 225
Überladen 188
Überladung 189
Verweis auf eine Methode 295
virtuelle 191–192
Zugriff auf statische Member 226
Methodenrumpf 40
MethodInfo 361, 566
GetCustomAttributes() 363
GetParameters() 361
Invoke() 566
Name 361
ReturnType 361
Microsoft.CSharp 563
Module 356
GetTypes() 358
Monitor 410
Enter() 410
Exit() 410
Hinweise zur Verwendung 412
in C#
lock 412
MouseEventArgs 477
MouseEventHandler 477–478
mscorlib 43
Multicast-Delegates 297
Multitasking 391
Multithreading 392
N
Nachrichten 426, 429
Nachrichtenschleife 429
Namenskonventionen für Variablen 53
Namensraum 41, 94
Definition 94
Schachtelung (Kaskadierung) 95
namespace 94
Nassi-Shneiderman 127
do-while 136
for 131
foreach 138
if 140
switch-case 146
while 134
NDoc 370
new 73, 88, 109, 158, 289, 295
new (Neuimplementierung von
Methoden) 230, 234
Notifizierung 292
null 180
O
Object 354, 359
Equals() 287
GetHashCode() 288
GetType() 354
Member 187
ToString() 257
object 89, 187, 354, 364
Objekte 155–156, 158
Erzeugung 158
Gleichheit 188
Konstruktor 172
Lebenszeit 161
mehrere Konstruktoren 175
Objektmethoden 163
Objektmethoden mit Parametern 166
Objektmethoden mit
Rückgabewert 170
Referenztypen 161
Selbstreferenz 169
Standardkonstruktor 173
statische Konstruktoren 176
Obsolete 342–343, 363
Parameter 343
Online-Hilfe 579
Operatoren 101
additive und multiplikative 103
arithmetische 103
bedingte 123
Bedingung 124
logische 122
primäre 106
relationale 120
Shift 119
unäre 116
Operatorüberladung
binäre Operatoren 285
Einschränkung 289
unäre Operatoren 283
Vergleichsoperatoren 287
zusammengesetzte Zuweisungsoperatoren 289
out 169
OutOfMemoryException 540
override 192, 196
P
PaintEventArgs 506
Eigenschaften 506
Panel 479
Parameterliste 164–165
Parameterübergabemechanismen 167
call-by-reference 169, 203
call-by-value 167
Parser 368
partial 202
PathGradientBrush 512
SurroundColors 515
Pen 506, 510
Brush 510
Color 510
DashStyle 510
Width 510
PictureBox 494
Image 494
SizeMode 494
PictureSizeMode 494
Index
591
Pinsel 512
Point 450, 515
Empty 451
PointF 515
Polymorphie 192
Präprozessor 367, 381, 570
Definieren von Symbolen 382
Präprozessordirektiven 367, 381
Ein- und Ausblenden von Code 381
Präprozessorsymbole 344
PreviewPageInfo 552
PreviewPrintController
GetPreviewPageInfo() 552
primitive Datentypen 49
PrintController 548
PrintControllerWithStatusDialog 548
PrintDialog 543
AllowCurrentPage 547
AllowPrintToFile 547
AllowSelection 547
AllowSomePages 547
Eigenschaften 546
PrintToFile 547
ShowHelp 547
ShowNetwork 547
PrintDocument 543
BeginPrint 546
EndPrint 546
Ereignisse 545
Print() 543
PrintPage 543, 545
QueryPageSettings 546
PrintEventArgs 546
PrintEventHandler 546
PrintPageEventArgs 544
PrintPreviewControl 550
Eigenschaften 550
PrintPreviewController 548
PrintPreviewDialog 548
ShowDialog() 550
private 82, 182–183
ProgressBar 502
Maximum 502
Minimum 502
Value 502
Projektmappe 32
Projektmappen-Explorer 32
Properties 196
Deklaration 197
Getter 198, 219
Nutzung 201
Setter 198, 219
592
Index
PropertyGrid 463, 500
SelectedObject 500
protected 182–183
Prozesse 389
Scheduling 390
public 82, 157, 182
Q
Qualifizierer 58
QueryPageSettingsEventArgs 546
QueryPageSettingsEventHandler 546
Queue 333
Member 334
Quickstart Tutorials 580
R
RadioButton 489
Checked 489
ReadLine() 76
Rectangle 432, 506, 515
Bottom 516
Contains() 516
Height 516
Inflate() 516
Intersect() 516
IntersectsWith() 516
Left 516
Location 516
Offset() 516
Right 516
Size 516
Top 516
Union() 516
Width 516
X 516
Y 516
RectangleF 516
ref 169
Reference 559
Referenzen 86
Reflection API 111, 341, 353, 562, 565
Regex 259
Match() 263
Optionen 262
RegexOption 262
Member 262
Region 534, 537
reguläre Ausdrücke 259
Assertionen 261
Aufbau 260
Metazeichen 260
Quantifizierer 260
Suchen und Ersetzen 263
Zeichenklassen 261
Ressourcen-Editor 487
return 171
RGB-Farbmodell 507
Alpha-Kanal 508
RichTextBox 491
Lines 491
LinkClicked 492
Select() 491
Selected 491
Text 491
Rückgabetyp 163
S
sbyte 50
Schaltfläche 448, 485
Schleifen 128
Abbruch 138
do-while 136
for 129
foreach 138
Fortsetzung mit nächstem
Durchlauf 138
while 133
Schlüsselwörter
Übersicht 53
Schnittstelle 218, 291
Deklaration 218
Properties 219
Typparameter 222
sealed 187, 233
Seiteneffekt 167
SelectionMode 495
Semantik 38
Serialisierung 341
set 198–199
short 50, 364
Sichtbarkeitsspezifizierer 157, 159–160,
163, 184
Size 431, 434, 436, 516
SizeGripStyle 436
sizeof 111
Smarttag 239
SolidBrush 512
Spaghetti-Code 148
Spezialisierung 179
SplitContainer 482
Sprungmarken 148
Stack 88, 335
Member 335
StandardPrintController 548
static 76, 224–225, 298
Steuerelemente
Bäume 497
Beschriftungen 492
Bilder 494
Checkboxen 485, 488
Container 478
Fortschrittsanzeige 502
Listen & Tabellen 494
Menüs, Symbolleisten & Kontextmenüs 497
PropertyGrid 500
Radiobuttons 485, 489
Schaltflächen 485
Schiebebalken 502
Textfelder 490
Zeitgeber 501
Stifte 510
string 53, 162, 245, 364, 490
CompareTo() 247, 289
EndsWith() 250
Format() 255
IndexOf() 251
Insert() 254
LastIndexOf() 251
Length 246
Remove() 253
Replace() 253
Split() 251
StartsWith() 250
Substring() 254
ToLower() 253
ToUpper() 253
Trim() 252
TrimEnd() 252
TrimStart() 252
weitere Methoden 255
StringBuilder 255
Konstruktoren 256
Properties und Methoden 256
Strings 41, 245
[] 246
Interning 249
Iteration über eine Zeichenkette 246
Konkatenation 104
Länge 246
Vergleich zweier Zeichenketten 247
struct 81
Struktur 81
Deklaration 81
Memberzugriff 83
Verschachtelung 84
Index
593
Strukturen
Konstruktor 172, 176
statische Konstruktoren 176
switch 142, 144
default-Alternative 144
mehrere Alternativen für einen Block
146
Synchronisation 408
Syntax 38
verwendete Schreibweise 38
System.CodeDom 563
System.CodeDom.Compiler 563
System.ComponentModel 442, 501
System.Console 45
System.Diagnostics 569
System.dll 564
System.Drawing 427, 450, 508, 512
System.Drawing.dll 564
System.Drawing.Drawing2D 510, 512,
532
System.Drawing.Printing 543
System.Object 187
System.Reflection 563
System.Reflection.Emit 568
System.Threading 393
System.Type 364
System.Windows.Forms 427, 471, 505
System.Windows.Forms.dll 564
SystemBrushes 515
SystemColors 509
SystemIcons 538
SystemPens 512
T
TabControl 484
TableLayoutPanel 480
Tags 369
Tastatureingaben 76
TextBox 490
Lines 490
Multiline 490
PasswordChar 490
Text 490
UseSystemPasswordChar 490
Textfelder
MaskedTextBox 490
RichTextBox 491
TextBox 490
TextureBrush 512
ToBitmap() 512
this 169, 226, 316, 319, 411
594
Index
Thread 389, 392
Abort() 403
aktueller 64
Beendigung von Threads 393
Erzeugung 392
Interrupt() 398
IsAlive 405
IsBackground 394
Join() 404
Ländereinstellung 64
Main-Thread 391
Member 407
Name 407
Priority 405
ResetAbort() 403
Resume() 400
Sleep() 396
Sleep(0) 398
Start() 393
Suspend() 400
suspendieren 400
Thread-Prozedur 392
Vorder- und HintergrundThreads 391, 394
Zustände 395
ThreadAbortException 403
Threading 389
ThreadInterruptedException 399
Thread-Pool 414
ThreadPriority 406
Thread-Prioritäten 406
ThreadStart 393
ThreadState 404
AbortRequested 403
Background 395
Running 393, 395
Stopped 395
Suspended 400
SuspendRequest 400
Unstarted 393, 395
WaitSleepJoin 396
ThreadStateException 393
throw 271
Timeout 398
Infinite 398
Timer 502
Enabled 502
Interval 502
Start() 502
Stop() 502
Tick 502
ToolBar 542
Toolbox 462, 483
ToolStrip 499
ToolStripContainer 497, 499
Top-Down 362
ToString() 257, 363
Trace-Ausgaben 569
Tracing-Tabellen 166
TrackBar 502
Maximum 502
Minimum 502
Scroll 503
Value 503
TreeView 497, 542
CheckBoxes 497
Nodes 497
SelectedNode 497
true 52, 283
Type 354, 357
BaseType 359
FullName 358
typeof 111
Typparameter 213, 222–223
Delegates 309
Einschränkungen 223
Vererbung 216
where 223
Typqualifizierer 59, 75
Typsicherheit 216
Typumwandlung 118, 180, 300
U
uint 50
ulong 50
UML 157
Assoziationen 238
Kardinalität 238
Klassendiagramm 178
Schnittstellen 237
Vererbung 177
Unboxing 85, 89, 119, 213, 288
unchecked 113
Unified Modelling Language 157
unsafe 111
ushort 50
using 46, 96, 342
using System.Runtime.Remoting.
Messaging 418
V
value 199
Variablen 48
Deklaration 55
Einlesen von der Tastatur 76
Initialisierung 56, 61
Wertzuweisung 56, 61
Variableninitialisierung 56, 61
Vererbung 177
Ableitungshierarchie 178
Basisklasse 178
Kind/Child 178
mehrfache 218
Referenzen auf Basisklassen 180
Syntax einer Ableitung 179
Typparameter 216
Vater/Parent 178
Zugriff auf Methoden der
Basisklasse 233
Verweis 559
Verweistypen 85
virtual 192
virtuelle Methoden 182
W
Wahrheitswerte 52
Währung
Ausgabe 64
WaitHandle
WaitOne() 417
Wertetypen 85
Größe 111
Wertzuweisung 56, 61
Wertzuweisung (Operator) 105
where 223
while 133, 136
Wildcard 259
Windows Forms 425, 427, 505, 560
Baukastensystem 455
Bibliothek 427
Container-Controls 456
Designer 471
Dialoge 456
Drucken 542
Fenster 425
Formulare 427
Grundgerüst für Programme 428
Koordinatensystem 450
Menüs 497
Namensraum 427
Symbolleisten 499
Visual C# 2005 Express 460
Zeitgeber 501
Windows-Anwendungen
Grundgerüst 428
Index
595
596
WinForms 560
WriteLine() 41, 62, 225
XML-Dokumentation 367
XPath 375
X
XML 367
Dokumentaufbau 368
Root-Element, Wurzel 369
Z
Zeichenketten 41, 53, 245
Zeitscheibe 390
Index
Herunterladen