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