Patrick A. Lorenz
C# Shortcut Programmierung im .NET Framework mit der C# Beta 2
Der Autor Patrick A. Lorenz Projektleiter Internet, combit GmbH, Konstanz, www.combit.net http://www.hanser.de Alle in diesem Buch enthaltenen Informationen wurden nach bestem Wissen zusammengestellt und mit Sorgfalt getestet. Dennoch sind Fehler nicht ganz auszuschließen. Aus diesem Grund sind die im vorliegenden Buch enthaltenen Informationen mit keiner Verpflichtung oder Garantie irgendeiner Art verbunden. Autor und Verlag übernehmen infolgedessen keine Verantwortung und werden keine daraus folgende oder sonstige Haftung übernehmen, die auf irgendeine Art aus der Benutzung dieser Informationen – oder Teilen davon – entsteht, auch nicht für die Verletzung von Patentrechten, die daraus resultieren können. Ebenso wenig übernehmen Autor und Verlag die Gewähr dafür, dass die beschriebenen Verfahren usw. frei von Schutzrechten Dritter sind. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt also auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.
Die Deutsche Bibliothek – CIP-Einheitsaufnahme Ein Titeldatensatz für diese Publikation ist bei Der Deutschen Bibliothek erhältlich.
Dieses Werk ist urheberrechtlich geschützt. Alle Rechte, auch die der Übersetzung, des Nachdruckes und der Vervielfältigung des Buches, oder Teilen daraus, vorbehalten. Kein Teil des Werkes darf ohne schriftliche Genehmigung des Verlages in irgendeiner Form (Fotokopie, Mikrofilm oder ein anderes Verfahren), auch nicht für Zwecke der Unterrichtsgestaltung, reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden. © 2002 Carl Hanser Verlag München Wien Gesamtlektorat: Fernando Schneider Copy-editing: Manfred Sommer, München Herstellung: Monika Kraus Umschlaggestaltung: Büro für Text und Gestaltung herbert und herbertsfrau, Augsburg Datenbelichtung, Druck und Bindung: Kösel, Kempten Printed in Germany ISBN 3-446-21959-5
Inhalt 1
Einföhrung............................................................................................................... 13
1.1 1.2 1.3 1.4
1.6
Wer sollte dieses Buch lesen? ................................................................................... 13 Was Sie brauchen ...................................................................................................... 14 Warum eine neue Sprache? ....................................................................................... 14 Das .NET Framework................................................................................................ 15 1.4.1 Installation der Beta 2.................................................................................. 16 Die Beispiele aus dem Buch...................................................................................... 18 1.5.1 Beispiele kompilieren .................................................................................. 18 1.5.2 Editoren ....................................................................................................... 18 Kontakt zum Autor.................................................................................................... 22
2
Erste Schritte in C# ................................................................................................. 25
2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10 2.11
Notation..................................................................................................................... 25 GROSS oder klein? ................................................................................................... 26 Variablen................................................................................................................... 27 Konstanten ................................................................................................................ 28 Einfache Datentypen ................................................................................................. 28 Einfache Datentyp-Konvertierung............................................................................. 29 Kommentare.............................................................................................................. 30 Namespaces............................................................................................................... 30 Konsolen-Applikationen ........................................................................................... 33 Die Methode Main .................................................................................................... 34 Hallo Welt ................................................................................................................. 35
3
Klassen & objektorientierte Programmierung ± Teil I ........................................ 39
3.1 3.2 3.3 3.4 3.5
Was ist ein Objekt? ................................................................................................... 39 Was ist eine Klasse?.................................................................................................. 40 Instanziierung einer Klasse........................................................................................ 41 Terminierung eines Objekts ...................................................................................... 42 Deklaration einer neuen Klasse ................................................................................. 43
1.5
6 _________________________________________________________________ Inhalt
3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 3.5.9
Dynamisch versus statisch........................................................................... 46 Eigenschaften deklarieren............................................................................ 48 Methoden deklarieren .................................................................................. 53 Ereignisse deklarieren.................................................................................. 56 Das Schlösselwort this................................................................................. 62 Modifikatoren.............................................................................................. 63 Konstruktor.................................................................................................. 67 Destruktor.................................................................................................... 72 Attribute ...................................................................................................... 74
4
Bedingungen ............................................................................................................ 79
4.1 4.2 4.3
Das if-Konstrukt........................................................................................................ 79 Das switch-Konstrukt................................................................................................ 82 Bedingungsoperator .................................................................................................. 86
5
Schleifen ................................................................................................................... 89
5.1 5.2 5.3 5.4 5.5
Die for-Schleife ......................................................................................................... 89 Die while-Schleife..................................................................................................... 91 Die do-Schleife ......................................................................................................... 92 Die foreach..in-Schleife............................................................................................. 92 Die Anweisungen break und continue....................................................................... 94
6
Sprunganweisungen ................................................................................................ 99
6.1 6.2 6.3 6.4 6.5
break.......................................................................................................................... 99 continue..................................................................................................................... 99 goto ......................................................................................................................... 100 return....................................................................................................................... 101 throw ....................................................................................................................... 102
7
Operatoren............................................................................................................. 105
7.1 7.2 7.3 7.4 7.5
Mathematische Standard-Operatoren ...................................................................... 105 Mathematische Kurz-Operatoren ............................................................................ 108 Zuweisungsoperatoren ............................................................................................ 110 Noch mehr Mathematik mit der Klasse System.Math ............................................. 112 Logische Operatoren ............................................................................................... 115 7.5.1 Vergleichsoperatoren................................................................................. 115 7.5.2 Verknöpfungsoperatoren ........................................................................... 116 7.5.3 Negationsoperator...................................................................................... 118 7.5.4 Bitweise (binÙre) Operatoren..................................................................... 118 Typenoperatoren ..................................................................................................... 123
7.6
Inhalt _________________________________________________________________ 7
7.6.1 7.6.2 7.6.3 7.6.4
typeof......................................................................................................... 123 is ................................................................................................................ 126 as................................................................................................................ 127 is versus as................................................................................................. 128
8
Arbeiten mit Zeichenketten .................................................................................. 131
8.1
Der Datentyp string ................................................................................................. 131 8.1.1 Einfache Zuweisungen............................................................................... 131 8.1.2 Escape-Zeichen.......................................................................................... 132 8.1.3 Formatierungen.......................................................................................... 134 8.1.4 String-Eigenschaften ................................................................................. 136 8.1.5 Weitere String-Operationen....................................................................... 138 Die Klasse StringBuilder......................................................................................... 148 8.2.1 StringBuilder-Eigenschaften...................................................................... 149 8.2.2 StringBuilder-Methoden ............................................................................ 150 RegulÙre Ausdröcke ................................................................................................ 152 8.3.1 Der Aufbau von regulÙren Ausdröcken ..................................................... 153 8.3.2 Die Klasse Regex....................................................................................... 157 8.3.3 Die Regex-Optionen .................................................................................. 164 8.3.4 Erweiterte Parsing-Mðglichkeiten ............................................................. 166
8.2
8.3
9
Erweiterte Datentypen .......................................................................................... 171
9.1
Arrays...................................................................................................................... 171 9.1.1 Initialisierung und Zugriff auf Arrays........................................................ 171 9.1.2 Mehrdimensionale Arrays ......................................................................... 174 9.1.3 Eigenschaften und Methoden der Array-Klasse ........................................ 177 Collections .............................................................................................................. 181 9.2.1 ArrayList ................................................................................................... 182 9.2.2 Hashtable ................................................................................................... 183 9.2.3 Queue ........................................................................................................ 185 9.2.4 SortedList .................................................................................................. 187 9.2.5 Stack .......................................................................................................... 188 Strukturen................................................................................................................ 189 AufzÙhlungen .......................................................................................................... 192 9.4.1 Standard-AufzÙhlungen ............................................................................. 192 9.4.2 Flag-AufzÙhlungen .................................................................................... 194
9.2
9.3 9.4
10
Konvertierungen.................................................................................................... 199
10.1 10.2
Konvertierung von numerischen Werten................................................................. 199 Konvertierung zu string........................................................................................... 201
8 _________________________________________________________________ Inhalt
10.3 10.4
Konvertierung von Verweistypen............................................................................ 204 Die Klasse Convert ................................................................................................. 209
11
Fehlerbehandlung ................................................................................................. 217
11.1 11.2 11.3 11.4 11.5 11.6 11.7
Was ist eine Ausnahme? ......................................................................................... 217 Das try..catch..finally-Konstrukt ............................................................................. 219 Die Klasse Exception .............................................................................................. 223 Ausnahmen auslðsen ............................................................................................... 225 Ausnahmen (kontrolliert) weiterreichen.................................................................. 228 Eigene Ausnahmen definieren................................................................................. 230 Zusammenfassung................................................................................................... 234
12
Klassen & objektorientierte Programmierung ± Teil II..................................... 237
12.1 12.2
šberladungen .......................................................................................................... 237 Vererbung ............................................................................................................... 241 12.2.1 Einfache Vererbungen ............................................................................... 242 12.2.2 šberschreiben von Klassen-Mitgliedern ................................................... 244 12.2.3 Verbergen von Klassen-Mitgliedern.......................................................... 247 12.2.4 Konstruktoren und die Vererbung ............................................................. 248 12.2.5 Klassen versiegeln ..................................................................................... 252 12.2.6 Abstrakte Klassen ...................................................................................... 253 Schnittstellen ........................................................................................................... 256 12.3.1 Deklaration einer Schnittstelle................................................................... 256 12.3.2 Implementierung einer Schnittstelle .......................................................... 257 12.3.3 Noch ein tierisches Beispiel....................................................................... 257 12.3.4 Mehrere gleichnamige Schnittstellen-Mitglieder....................................... 261 Benutzerdefinierte Operatoren erstellen .................................................................. 262 12.4.1 Arithmetische Operatoren.......................................................................... 262 12.4.2 Vergleichsoperatoren................................................................................. 266 12.4.3 Konvertierungsoperatoren ......................................................................... 270
12.3
12.4
13
Weitergehende Programmierung......................................................................... 277
13.1 13.2
Arbeiten mit Threads............................................................................................... 277 Reflections .............................................................................................................. 281 13.2.1 Eine Eigenschaft dynamisch abfragen ....................................................... 281 13.2.2 Eine Methode dynamisch aufrufen ............................................................ 282 13.2.3 Eine Methode mit Parametern dynamisch aufrufen................................... 283 13.2.4 Alle Eigenschaften und Methoden auflisten .............................................. 284 Arbeiten mit dem CodeDOM .................................................................................. 286 13.3.1 Ein Programm dynamisch erstellen ........................................................... 286
13.3
Vorwort Eigentlich hatte ich gar nicht vor, mich derart intensiv mit C# zu beschÙftigen. Das Thema war ursprönglich ein anderes. Doch wie jeder, der sich mit dem .NET Framework beschÙftigt, bin auch ich nicht um die neue Sprache C# herumgekommen. Was also anfÙnglich nur als eine grobe šbersicht geplant war, hat sich sehr schnell zu einer ausföhrlichen Einföhrung in das Flaggschiff der .NETSprachen entwickelt. Ich bin mit dieser Entwicklung durchaus zufrieden, denn ohne diesen Ausflug wörden Sie heute nicht das erste deutschsprachige Buch zur .NET und zur C# Beta 2 in den HÙnden halten. C# ist eine vðllig neue Sprache, die mit dem ungeheuren Potenzial eines Weltkonzerns daherkommt. Das erlebt man nicht alle Tage, und ich persðnlich habe die Entwicklung und die teilweise heftigen Diskussionen der vergangenen knapp 12 Monate mit Interesse und Spannung verfolgt. Wer als Windows-Entwickler am Ball der Zeit bleiben will, wird sich mit .NET und C# auseinandersetzen mössen. Und wenn diese neue Vision von Microsoft Ihr Berufsleben nicht heute beeinflusst, dann sicherlich morgen. C# vereint die herausragenden Merkmale der aktuellen Microsoft Programmiersprachen Visual C++ und Visual Basic. Die Kombination dieser beiden Sprachen in Verbindung mit einem standardisierten Klassen-Framework ist sinnvoll und wird es Ihnen als Entwickler leichter machen, Anwendungen för verschiedene Umgebungen wie Desktop, Internet oder gar mobile Systeme zu entwickeln. Auch die Arbeit im Team wird letztlich durch das Zusammenröcken der Sprachen viel einfacher und effektiver. Wenn Sie dieses Buch gekauft haben, dann sind Sie vermutlich oder hoffentlich an einer ganz neuen Sprache interessiert. Eine Sprache mit dem Potenzial, andere Sprachen vielleicht nicht vðllig, zumindest aber in den Hauptbereichen der Entwicklung zu verdrÙngen. Sie tun daher gut daran, sich mit diesem Buch einen šberblick zu verschaffen. Wenn Sie bereits mit Sprachen wie C, C++ oder Java gearbeitet haben, wird Ihnen jedenfalls die Syntax bekannt vorkommen. Dennoch gibt es viel Neues zu lesen und zu lernen. Die Kenntnisse verschiedener Konzepte um .NET sind zum VerstÙndnis der Sprache einfach unerlÙsslich.
10 _____________________________________________________________ Vorwort
Dieses Buch soll sowohl erfahrenen Entwicklern als auch Einsteigern mit eher geringen Programmiererfahrungen ein kompakter, aber dennoch umfassender Einstieg in die Sprache C# sein. Ich habe mich bemöht, alle wesentlichen Aspekte klar gegliedert zusammenzufassen. Die vielen Beispiele, die auf der bucheigenen Website zum Download zur Verfögung stehen, vereinfachen den praktischen Einstieg in die Sprache. Die sprachorientierte Gliederung ermðglicht zudem die Verwendung als Nachschlagewerk und Referenz, wann immer Sie bei der Arbeit Unterstötzung benðtigen. Sie halten mit diesem Buch mein Erstlingswerk in den HÙnden. Es ist mir daher sehr wichtig, Ihre Meinung zu hðren. Nehmen Sie dieses Angebot bitte wahr und schreiben Sie mir, wann immer Sie etwas zu diesem Buch loswerden mðchten. Die E-Mail-Adresse sowie die URL der bereits angesprochenen Website finden Sie in der Einföhrung weiter unten. Ein zweites Buch, das sich ebenfalls mit dem Themenkomplex .NET auseinandersetzt, ist öbrigens bereits in Arbeit und wird voraussichtlich Anfang 2002 nach dem Release von .NET erscheinen. Weitere Informationen hierzu finden Sie zu gegebener Zeit ebenfalls auf der Website zum Buch. Ich mðchte an dieser Stelle nicht versÙumen, einige Dankesworte an diejenigen zu richten, die dieses Projekt ermðglicht haben. Mein Dank gilt meinem Lektor Fernando Schneider, der mich quasi entdeckt und vom Fleck weg engagiert hat. Er ist an die Zusammenarbeit sicherlich nicht blauÙugig, aber doch mit sehr viel Vertrauen herangegangen und hat daför meinen Respekt verdient. Ich mðchte auch meinem Arbeitgeber, der combit GmbH, danken, dass Sie es mir ermðglicht haben, neben meiner regulÙren Arbeit an diesem Buch zu werkeln. Einen besonderen Dank und einen lieben Gruû mðchte ich an meine Familie richten. Neben meiner Mutter und meinen Groûeltern sind es nicht zuletzt auch meine Katzen Filou und Shari, die mich motiviert und inspiriert haben. Sie werden im Laufe des Buches verstehen, was ich meine. Viele Gröûe öbrigens auch an Mr. Jingles; die Cineasten unter Ihnen wissen, wen ich meine. Es bleibt mir, Ihnen viel Spaû an der Lektöre dieses Buches und viel Erfolg bei der Arbeit mit .NET und C# zu wönschen. Patrick A. Lorenz Konstanz, im Oktober 2001
1 Einführung
Wer sollte dieses Buch lesen? Was Sie brauchen Warum eine neue Sprache? Das .NET Framework Die Beispiele aus dem Buch Kontakt zum Autoren
12 ____________________________________________________________________
Inhaltsübersicht Kapitel 1
1
Einföhrung .........................................................................................................13
1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.6
Wer sollte dieses Buch lesen?..............................................................................13 Was Sie brauchen ................................................................................................14 Warum eine neue Sprache? .................................................................................14 Das .NET Framework..........................................................................................15 Installation der Beta 2..........................................................................................16 Die Beispiele aus dem Buch ................................................................................18 Beispiele kompilieren ..........................................................................................18 Editoren ...............................................................................................................18 Kontakt zum Autor ..............................................................................................22
____________________________________________________________________ 13
1 Einführung Wie sage ich eigentlich C#? Obwohl es mittlerweile vermutlich hinlÙnglich bekannt ist, darf die Sprechweise von C# an erster Stelle dieses Kapitels nicht fehlen. C# wird ¹C Sharpª gesprochen. Sharp ist Englisch und bedeutet öbersetzt scharf, stark oder durchdringend. ¹C Scharfª hðrt sich eigentlich ganz nett an und trifft den Kern in der šbersetzung vermutlich am ehesten.
1.1
Wer sollte dieses Buch lesen?
Wörde die Frage lauten, wer das Buch kaufen soll, wörde ich antworten: ¹Mðglichst vieleª. Aber das war nicht die Frage und insofern mðchte ich etwas differenzierter antworten. C# ist sicherlich die kommende Programmiersprache, wenn es um die Entwicklung im Windows-Umfeld geht. Natörlich werden auch andere Sprachen neben C# existieren, aber diese werden durch das .NET Framework viele typische Eigenschaften verlieren. Letztlich bleibt den Sprachen die Syntax und die Semantik. Das ist nicht viel, gerade wenn man bedenkt, dass C# optimal auf das .NET Framework abgestimmt ist. Es spricht also einiges för C# und somit auch dieses Buch zu lesen. Wer also sollte es lesen? Jeder, der öber ein wenig Programmier-Erfahrung verfögt und eine komplett neue Sprache bereits vor dem Erscheinen kennen lernen mðchte. Visual Basic-Entwickler lernen die Mðglichkeiten einer optimierten Syntax kennen und C++ oder Java-Entwickler werden sehen, dass vieles geblieben ist sich aber auch Vieles geÙndert hat. Zum Besseren, wohl gemerkt. Wenn es Sie also interessiert, die Geburt einer Sprache im Jahre 2002 zu erleben, gespickt mit Hintergrundwissen und der Mðglichkeit, die Sprache vom Start weg benutzen zu kðnnen, dann sind Sie hier richtig.
14 _________________________________________________ 1.2 Was Sie brauchen
1.2
Was Sie brauchen
In erster Linie Spaû am Lesen! Die richtige Technik natörlich, insbesondere wenn Sie die Programmbeispiele des Buches gleich ausprobieren wollen. Sie benðtigen das .NET Framework in der zur Drucklegung aktuellen Beta Version 2. Das Framework lÙuft unter Windows 2000 oder XP und reicht im Grunde schon aus. Sie sollten sich allerdings auch Gedanken öber einen Editor machen. Einige Mðglichkeiten finden Sie weiter unten in diesem Kapitel. Eine gute Mðglichkeit ist die Visual Studio.NET Entwicklungsumgebung. Wenn Sie die Beta-Version des Frameworks auf CD erhalten haben, sollte die Beta von Visual Studio ebenfalls mitgeliefert worden sein.
1.3
Warum eine neue Sprache?
Die bereits bestehenden Sprachen des .NET Frameworks Visual C++, Visual Basic.NET und JScript, haben allesamt schon einige Jahre auf dem Buckel. Dementsprechend haben sich bestimmte Befehle und Schreibweisen quasi historisch entwickelt. Diese Sprachen konsequent und ausschlieûlich auf die Visionen und Konzepte des .NET Frameworks umzuschreiben, wörde zu einem radikalen Bruch mit den vergangenen Versionen föhren. Auch wenn beispielsweise im Falle von Visual Basic.NET schon so keine vollstÙndige KompatibilitÙt besteht, so wurden doch viele Kompromisse geschlossen, die es Entwicklern erleichtern, bestehenden Code auf .NET umzuschreiben. ¹Wenn Du nicht vorwÙrts gehen kannst, gehe röckwÙrts!ª Es gibt ganz bestimmt ein altes japanisches Sprichwort, das so oder so Ùhnlich lautet. Vielleicht finden Sie es ja auf Ihrem nÙchsten Glöckskeks. Genau diesem Spruch ist Microsoft gefolgt. Wenn sich die bestehenden Sprachen also nicht ohne weiteres konsequent an dem .NET Framework ausrichten lassen, dann schreiben wir eben eine neue Sprache. Diese neue Sprache heiût C# und ist als Folge aus der Entwicklungsidee absolut konsequent auf .NET ausgerichtet. Das Framework, aber auch die Sprache C# vereinen die Erfahrungen und Vorteile der bisherigen Sprachen und Entwicklungswerkzeuge. Ich persðnliche sehe in C# die Kombination der leistungsfÙhigen Syntax von (Visual) C++ und der intuitiven Einfachheit von Visual Basic. Eine gelungene Kombination sowohl för erfahrene Entwickler als auch Neueinsteiger. Letztlich wird es in meinen Augen auch dieses Argument sein, das der Sprache C# gegenöber anderen eine nachhaltig bessere Position und Verbreitung sichern wird. Eine weitere Konsequenz sehe ich im langfristigen Wegfall von Visual Basic. Nicht, dass ich etwas gegen diese Sprache habe, ich bin ja beinahe damit ¹aufgewachsenª, aber sie wird durch C# im Grunde öberflössig.
1 Einführung ___________________________________________________________15
Nebenbei bemerkt mag ein nicht zu vernachlÙssigendes Argument för die Entwicklung von C# auch der Wegfall von Visual J++ gewesen sein. Dem ewigwÙhrenden Streit zwischen Microsoft und Sun sei Dank. Doch zu diesem Punkt hat sich Microsoft meines Wissens (noch) nicht geÙuûert.
1.4
Das .NET Framework
Seit Mitte 2000 versucht Microsoft ungehemmt, allen Entwicklern und solchen, die es noch werden wollen, eine neue Philosophie unterzujubeln: ¹Empower people through great software, any time, any place and on any deviceª Der Slogan lðst den bisherigen ± ¹Information at your fingertipsª ± ab. Warum? Die Frage ist eigentlich ganz einfach zu beantworten: Die alte Vision hat sich erföllt, unter jedem Schreibtisch steht ein PC und der Zugriff auf das Internet ist auch schon för so gut wie alle eine SelbstverstÙndlichkeit. Das Produkt, das die neue Vision mit Leben föllen soll, nennt sich Microsoft .NET. Die Bezeichnung ¹Produktª ist in diesem Zusammenhang vielleicht etwas irreföhrend, denn .NET ist eigentlich kein solches, sondern eine einheitliche Basis för Entwicklungen unter Windows und mðglicherweise spÙter auch anderen Betriebssystemen. Microsoft .NET, auch .NET Framework genannt, ist in der Vergangenheit von der Fachpresse oft als grðûte Revolution nach der Umstellung von DOS auf Windows bezeichnet worden. För Entwickler mag dies zustimmen und entsprechend heftig sind die Diskussionen in den populÙren Newsgroups und Fachtagungen. Die einen stimmen der neuen Philosophie zu und sehen mit ihr viele alltÙgliche Probleme eines Entwicklers gelðst. Andere weisen auf InkompatibilitÙten zu bestehenden Entwicklungen hin und sehen durch diese ihre Existenz gefÙhrdet. Letztlich muss jeder sich hier selbst eine Meinung bilden. Eines steht jedoch fest: Kein WindowsEntwickler wird an .NET vorbeikommen, daför wird Microsoft sorgen. Seien Sie also vorbereitet, bevor Sie die Welle unerwartet öberrennt. Betrachtet man .NET distanziert, erkennt man sehr schnell dessen Grundidee. Jegliche FunktionalitÙt wurde in ein einheitliches, hierarchischstrukturiertes Framework verpackt, das öber einheitliche Schnittstellen anzusprechen ist. Dies bezieht sich allerdings nicht nur auf spezielle Windows-Funktionen, sondern geht weiter, bis hin zur Programmiersprache. Die FunktionalitÙt wurde auch hier herausgelðst und in eine allgemeine Common Language Runtime (CLR) gesteckt. Die einzelnen Sprachen wurden auf die individuelle Semantik sowie die Befehlssyntax reduziert. Auf den ersten Blick klingt das albern, letztlich föhrt es jedoch nur Microsofts alte Idee eines Komponenten-basierten Systems eine Stufe weiter. Auch Aspekte wie Typensystem, Fehlermanagement, Debugging und vieles mehr werden einheitlich von der CLR beziehungsweise anderen Bereichen des
16 _______________________________________________ 1.4 Das .NET Framework
.NET Frameworks öbernommen. Auch an Punkten wie der Distribution von Komponenten und der Versionsverwaltung wurde gearbeitet. Eine Registrierung von Komponenten ist nicht mehr notwendig, ein einfaches File-Copy reicht vðllig aus. Verschiedene Versionen einer Komponente kðnnen so parallel verwendet werden. Auch der leidige Neustart der Internet Information Services bei Aktualisierungen von Internet-Komponenten entfÙllt dadurch beispielsweise. Eine einheitliche Zwischensprache lðst nicht nur die Konkurrenz-Situation diverser Programmiersprachen, sondern auch die Plattform-öbergreifende Distribution von Programmen. Diese werden fortan von jeder .NET Sprache in die Microsoft Intermediate Language (MSIL) kompiliert und anschlieûend verteilt. Auf dem System des Kunden angekommen, werden die Programme beim ersten Aufruf in spezifischen Maschinencode umgesetzt. Hierför sorgt der so genannte Just-In-TimeCompiler (JIT). Kennen Sie das etwa? Klar, Java und auch Teile von Windows CE machen das nicht viel anders. Und auch Microsoft hat wohl schon öber eine Portierung von .NET auf Linux nachgedacht. Eine interessante Perspektive... Groûartige Geschwindigkeitsverluste gegenöber reinem Maschinencode jetziger Programmiersprachen beförchtet Microsoft öbrigens nicht. Schlieûlich sind die aktuellen Rechnerkonfigurationen derart performant, dass die Unterschiede im Bereich von Millisekunden liegen sollen. Wer also alle drei Monate einen neuen Rechner kauft, wird voraussichtlich keine Probleme haben. Aber mal im Ernst, ein ernsthafter Unterschied lieû sich zumindest in meinem Test nicht feststellen.
1.4.1
Installation der Beta 2
Die mir vorliegende deutschsprachige Beta 2 kommt auf insgesamt fönf CD-ROMs daher. Die ersten drei enthalten die Visual Studio.NET Entwicklungsumgebung mit den Sprachen C#, Visual C++ .NET, Visual Basic.NET und Visual FoxPro.NET. Die vierte mit ¹Windows Component Updateª betitelte CD enthÙlt das eigentliche Framework, also alles, was Sie zum Loslegen brauchen. Die fönfte und letzte CD enthÙlt Visual Sourcesafe 6.0c. Zur Installation legen Sie die erste CD ein. Es erscheint der in der Abbildung 1.1 gezeigte Dialog. Sie werden nun in drei Schritten zum Ziel geföhrt: 1. Windows Component Update 2. Visual Studio.NET 3. Service Releases
1 Einführung ___________________________________________________________17
Der erste Punkt ist im Grunde der wichtigste. Sie werden aufgefordert, die vierte CD einzulegen, und diese enthÙlt wie beschrieben das eigentliche .NET Framework. Ihr System wird auf Vordermann gebracht und enthÙlt anschlieûend neben den benðtigten Komponenten auch das .NET Framework SDK mit allen erdenklichen Hilfe-Informationen, Referenzen und Beispielen sowie die Compiler för C# und Visual Basic.NET. Sofern Sie sich för Visual Studio.NET als Entwicklungsumgebung entscheiden (siehe unten), kommt nun wieder die erste CD ins Laufwerk. Natörlich kðnnen Sie auswÙhlen, welche Teile der Umgebung installiert werden sollen. Je nach Auswahl kommt hier schon das eine oder andere Gigabyte zusammen. Sofern Sie Visual Studio.NET installiert haben, sollten Sie unbedingt auch den dritten Punkt beachten und sich mit den aktuellsten Service Releases versorgen. Sie benðtigen hierzu zwar einen Internet-Zugang und mitunter ein wenig Geduld, aber es ist wirklich notwendig. Einige fröheren Versionen der Beta waren zeitlich eingeschrÙnkt, und so kðnnte Ihnen die Vorfreude auf VS.NET ziemlich schnell vergehen.
Abbildung 1.1 In drei Schritten zum Ziel: Die Installation von .NET
18 _________________________________________ 1.5 Die Beispiele aus dem Buch
1.5
Die Beispiele aus dem Buch
Alle im Buch vorgestellten Beispiele basieren auf der derzeit verfögbaren deutschen .NET Beta 2 und wurden mit dieser getestet. Aus AktualitÙtsgrönden habe ich darauf verzichtet, die Beispiele auf eine beiliegende CD-ROM zu pressen. Natörlich sollen Sie trotzdem keinen neuen Weltrekord im Schnell-Tippen aufstellen. Sie kðnnen vielmehr ganz entspannt die zum Buch gehðrende Website aufrufen und sich die Beispiele bequem herunterladen. Alles was Sie dazu brauchen, ist die Adresse der Website und ein Passwort, das Sie als rechtmÙûigen KÙufer dieses Buches ausweist. www.csharp-shortcut.de Passwort: XGHT5Z7T Auf der Website plane ich auch, Informationen zur Verfögung zu stellen, die sich nach Drucklegung ergeben haben. Das ein oder andere Fehlerchen wird sich trotz gewissenhafter Arbeit und gröndlicher Korrektur sicherlich eingeschlichen haben. Nötzliche Links zum Thema sollen das Angebot schlieûlich abrunden.
1.5.1
Beispiele kompilieren
Apropos Beispiele... Wenn Sie diese ausprobieren wollen, dann benðtigen selbstredend einen Compiler. Dieser kommt mit dem .NET Framework, das Sie mittlerweile vielleicht schon installiert haben. Der Compiler kann entweder indirekt durch die Visual Studio.NET Entwicklungsumgebung oder direkt als Konsolenapplikation aufgerufen werden. Der Compiler wird in diesem Fall unter dem Namen csc.exe gefolgt von der gewönschten C# Quellcode-Datei (.cs) aufgerufen. In der Regel reicht dies bereits aus, obwohl natörlich eine Reihe von Optionen angeboten werden, die ausföhrlich bei Aufruf von csc.exe /? beschrieben werden. Die Website enthÙlt die Beispiele öbrigens auch in kompilierter Form, Sie kðnnen sich diesen Schritt also gegebenenfalls zunÙchst einmal sparen.
1.5.2
Editoren
Zum Ausprobieren der Beispiele ist ein Editor nicht wirklich zu verachten. Verschiedene Alternativen stehen zur Verfögung. Ich mðchte Ihnen einige vorstellen und meine ganz persðnlichen Eindröcke vermitteln. Welche der vorgestellten Lðsungen för Sie richtig sind oder ob eine nicht erwÙhnte Mðglichkeit vielleicht viel besser ist, mðchte ich Ihnen öberlassen ± die Anforderungen unterscheiden sich teilweise doch sehr deutlich.
1 Einführung ___________________________________________________________19
Notepad Lachen Sie bitte nicht, aber die Beispiele dieses Buches wurden allesamt mit dem alten, ewig treuen und absturzsicheren Notepad entwickelt. Die Grönde hierför sind allerdings weniger in einem sentimentalen Hang zu diesem Entwickler-Freund als in der Schlankheit des Programms zu suchen. För die meist relativ öberschaubar gehaltenen Beispiele reicht dieser Editor aus und bietet die notwendige GrundfunktionalitÙt. Auf jeglichen Komfort muss man allerdings zur GÙnze verzichten. Wenn Sie Ùhnlich wie ich öber Notepad denken, dann mag der Einsatz zu öberlegen sein. Wer allerdings auf ein klein wenig Komfort bei der Arbeit nicht verzichten mðchte und auf keinen Fall Zeile 38 manuell suchen mðchte, der sollte sich an eine der folgenden Alternativen halten.
Abbildung 1.2 Lang lebe das Notepad!
20 _________________________________________ 1.5 Die Beispiele aus dem Buch
Visual Studio.NET Wie schon weiter oben erwÙhnt, wird die Beta der neuen Visual Studio.NET Entwicklungsumgebung gemeinsam mit der Beta des Frameworks ausgeliefert. Die Ingesamt fönf CD-ROMs installieren auf Ihrem System eine ausgereifte und bereits jetzt sehr stabile Plattform zur Entwicklung jeglicher .NET Plattform. Neben C# werden auch C++ und Visual Basic.NET unter einem Dach angeboten. Die Features sind den Erfahrungen der VorgÙngerversionen entwachsen und machen dem Namen EntwicklungsUMGEBUNG wirklich alle Ehre. Sie finden hier so ziemlich alles, was Sie zur Entwicklung benðtigen unter einer einheitlichen OberflÙche. Auch eine kleine Aufgabenverwaltung und das Framework SDK als Online-Hilfe wurden integriert. Wer ein komplettes und vollstÙndiges Entwicklungssystem prÙferiert, der ist mit dem Visual Studio.NET sicher gut beraten. Die Abbildung zeigt den Editor, die Aufgabenverwaltung (unten) sowie den Projekt-Explorer (rechts). Weitere Toolfenster blenden sich bei Bedarf ein und werden symbolisiert am Fenster dargestellt (rechts oben und unten links).
Abbildung 1.3 Überzeugt mit komfortablen Hilfsmitteln wie einer Aufgabenverwaltung und vielem mehr: Visual Studio.NET IDE Beta 2.
1 Einführung ___________________________________________________________21
SharpDevelop SharpDevelop ist vermutlich eines der Ùltesten und erfolgsreichsten, nicht kommerziellen Projekte, die bereits vollstÙndig mit C# entwickelt wurden. Der Entwickler Mike Kröger (nein, nicht der mit der Nase und dem HollÙnder als Freund) hat bereits im September 2000 (!) mit der Entwicklung begonnen. SharpDevelop ist ein C# Code-Editor mit zahlreichen nötzlichen Features. In meinen Augen handelt es sich um eine echte Alternative zur Visual Studio.NET Entwicklungsumgebung. Das Projekt wird unter der GNU-Lizenz verðffentlicht und kommt daher optional mit dem vollstÙndigen Quellcode daher. Sie kðnnen den Editor auf diese Weise vollstÙndig Ihren eigenen Wönschen anpassen und die …nderungen gegebenenfalls auch wieder verðffentlichen. Und selbst wenn Sie keine …nderungen vornehmen wollen, kðnnen Sie den Quellcode als Beispiel för das sehen, was mit C# mðglich ist. Hierför spricht nicht zuletzt die sehr konsequente Umsetzung der .NET-Ideen. Zur Drucklegung dieses Buches war die Milestone Beta Version 0.75 aktuell. Die Abbildung zeigt diese Version im Einsatz. Die jeweils aktuelle Version finden Sie im Internet. Auf der Web-Site zum Buch unter www.csharp-shortcut.de finden Sie den jeweils aktuellen Download-Link.
Abbildung 1.4 SharDevelop ist eine echte und kostengünstige Alternative zu Visual Studio.NET.
22 _________________________________________________ 1.6 Kontakt zum Autor
1.6
Kontakt zum Autor
Wenn Sie Glöckwönsche, Lob oder BHs loswerden wollen, dann kðnnen Sie gerne Kontakt mit mir aufnehmen. Sie erreichen mich unter
[email protected] Falls Sie allerdings Fragen, Anregungen, Kritik oder gar Beschwerden haben, dann wenden Sie sich bitte an
[email protected]. Nein, nein, keine Angst, selbstverstÙndlich stelle ich mich Ihren Mails und freue mich öber jegliches konstruktive Feedback zu diesem Buch. Mangels Zeit kann ich allerdings keine Fragen beantworten, die ganz allgemein C# und nicht speziell dieses Buch betreffen. Ich bitte hierför um VerstÙndnis. Ich werde versuchen, Links zu entsprechenden Foren und Newsgroups unter www.csharp-shortcut.de för Sie bereitzuhalten.
2 Erste Schritte in C#
Notation GROSS oder klein? Variablen Konstanten Einfache Datentypen Einfache Datentyp-Konvertierung Kommentare Namespaces Konsolen-Applikationen Die Methode Main Hallo Welt
24 ____________________________________________________________________
Inhaltsübersicht Kapitel 2
2
Erste Schritte in C# ...........................................................................................25
2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10 2.11
Notation ...............................................................................................................25 GROSS oder klein? .............................................................................................26 Variablen .............................................................................................................27 Konstanten...........................................................................................................28 Einfache Datentypen............................................................................................28 Einfache Datentyp-Konvertierung.......................................................................29 Kommentare ........................................................................................................30 Namespaces .........................................................................................................30 Konsolen-Applikationen......................................................................................33 Die Methode Main...............................................................................................34 Hallo Welt ...........................................................................................................35
____________________________________________________________________ 25
2 Erste Schritte in C# Das VerstÙndnis dieses Kapitels erfordert grundlegende Programmierkenntnisse. Es wird nicht detailliert auf die einzelnen Aspekte der Programmierung, wohl aber auf deren Syntax in C# eingegangen. Sie mössen beispielsweise nicht wissen, wie eine if-Bedingung in C# formuliert wird, Sie mössen aber wissen, was eine solche Bedingung öberhaupt ist.
2.1
Notation
• Befehle werden in C# Befehlszeilen-orientiert abgearbeitet. Das Ende einer Befehlszeile wird dabei nicht durch den Umbruch, sondern durch ein Semikolon markiert. Auf diese Weise kann sich eine Befehlszeile öber mehrere reale Zeilen erstrecken oder kðnnen mehrere Befehlszeilen in einer realen Zeile notiert werden.
Befehlszeilen
Befehlszeile;
• Göltigkeitsbereiche werden mit geschweiften Klammern notiert. Zuvor wird das einleitende Schlösselwort notiert. Diesem Schlösselwort folgt in diesem Fall kein (!) Semikolon. Alle Befehlszeilen, die sich innerhalb der Klammerung befinden, zÙhlen zum Göltigkeitsbereich des Schlösselwortes. Schlösselwort { ... }
Gültigkeitsbereiche
26 ________________________________________________ 2.2 GROSS oder klein?
Die im Göltigkeitsbereich notierten Befehlszeilen werden der šbersicht halber eingeröckt. So ist eine gute Lesbarkeit gewÙhrleistet. Schlösselwort { Befehlszeile1; Befehlszeile2; } Objekte
• Der Zugriff auf Mitglieder eines Objektes, wie dessen Eigenschaften und Methoden, erfolgt immer durch Trennung mit einem Punkt. Andere Trennzeichen wie beispielsweise in C++ sind nicht vorgesehen. Objekt.Methode(); Variable = Objekt.Eigenschaft;
Parameter
• Parameter von Methodenaufrufen werden Komma-separiert in runden Klammern hinter der Methode notiert. Werden keine Parameter angegeben, werden die runden Klammern dennoch (leer) notiert. Methode(Parameter1, Parameter2)
Indexer
• Der Zugriff auf Indexer erfolgt in eckigen Klammern: Variable = Indexer[Index];
• Einzelne Zeichen werden in einfachen Anföhrungsstrichen notiert: Variable = ©x©; Zeichenketten
• Zeichenketten werden in doppelten Anföhrungszeichen notiert: Variable = "abc";
2.2
GROSS oder klein?
C# achtet auf die Groû-Klein-Schreibung und daher mössen Sie ebenfalls darauf achten. Die Sprache ist also casesensitive. Wenn Sie einen Befehl nicht ganz korrekt notieren, wird dieser einfach als unbekannt zuröckgewiesen. Die Variable...
2 Erste Schritte in C# ____________________________________________________27
variable
...ist nicht identisch mit... Variable
Auch wenn es auf diese Weise mðglich ist, zwei gleichnamige Variablen zu verwenden, die sich lediglich in der Goû-Klein-Schreibung unterscheiden, sollten Sie dies unbedingt unterlassen. Dies wörde unter UmstÙnden zu einem sehr unöbersichtlichen Programm föhren und Sie wörden sich so nicht wirklich einen Gefallen tun.
2.3
Variablen
Variablen werden in C# in der folgenden Form deklariert: Datentyp Variablenname;
Sollten mehrere Variablen vom selben Datentyp deklariert werden, werden diese durch Kommata separiert: Datentyp, Variablenname1, Variablenname2;
Mðchte man der Variable direkt einen Wert zuweisen, ist dies bereits in der Zeile der Deklaration mðglich. Datentyp Variablenname = AndereVariable;
Mðchte man hingegen eine neu instanziierte Variable zuweisen, wird das Schlösselwort new gefolgt vom Datentyp und runden Klammern verwendet. Datentyp Variablenname = new Datentyp();
Um einen Datentyp neu zu instanziieren und direkt einen initiellen Wert zu öbergeben, kann dieser als Parameter öbergeben werden. Datentyp Variablenname = new Datentyp(InitiellerWert);
28 _______________________________________________________ 2.4 Konstanten
2.4
Konstanten
Konstanten werden wie Variablen definiert, es wird jedoch zu Beginn der Befehlszeile der Modifikator const notiert. const Datentyp Konstantenname = Wert;
2.5
Einfache Datentypen
Das .NET Framework unterstötzt eine ganze Reihe von unterschiedlichen Datentypen. In C# werden diese durch integrierte Datentypen angesprochen. Die in der folgenden Tabelle vorgestellten Datentypen sind Wertetypen, das heiût diese werden direkt auf dem Stack abgelegt. Einzige Ausnahme bildet der Datentyp string, der einen Verweistyp darstellt und dessen Inhalt somit auf dem Heap abgelegt wird.
Tabelle 2.1 Einfache Datentypen in der Übersicht Datentyp
Bytes
Beschreibung
byte
1 Byte ohne Vorzeichen
sbyte
1 Byte mit Vorzeichen
short
2 Short mit Vorzeichen (16 Bit)
ushort
2 Short ohne Vorzeichen (16 Bit)
int
4 Integer mit Vorzeichen (32 Bit)
uint
4 Integer ohne Vorzeichen (32 Bit)
long
8 Integer mit Vorzeichen (64 Bit)
ulong
8 Integer ohne Vorzeichen (64 Bit)
float
4 Gleitkommazahl (32 Bit)
double
8 Gleitkommazahl (64 Bit)
Decimal bool
12 Dezimalzahl mit hoher Genauigkeit (96 Bit) 1 Bit Logischer Wert, ja oder nein, true oder false
char
2 Unicode-Zeichen
string
4 Unicode-Zeichenkette, Verweistyp
2 Erste Schritte in C# ____________________________________________________29
2.6
Einfache Datentyp-Konvertierung
C# ist ein typensichere Sprache. Daher mössen Konvertierungen von einem Datentyp zu einem anderen meist explizit vorgenommen werden. Implizite Konvertierungen werden nur dann vom Compiler akzeptiert, wenn diese von den entsprechenden Datentypen unterstötzt werden und sichergestellt ist, dass durch die Konvertierung keine Datenverluste auftreten kðnnen Die implizite Konvertierung erfolgt durch bloûe Zuweisung. Die explizite Konvertierung hingegen wird mit Hilfe des folgenden Schemas realisiert: NeueVariable = (NeuerDatentyp) AlteVariable;
Das folgende Beispiel konvertiert eine Variable vom Typ short implizit in den Datentyp int.
Implizite Konvertierung
Listing 2.1 convert1.cs short n1 = 15; int n2; n2 = n1; Console.WriteLine(n2.ToString());
Da int der grðûere Datentyp ist, kann ein short problemlos implizit konvertiert werden, denn es kðnnen keine Datenverluste auftreten. Andersherum ist jedoch die Konvertierung von short zu int nur explizit mðglich. Hier kðnnen unter UmstÙnden Datenverluste auftreten, da short der kleinere Datentyp ist. Listing 2.2 convert2.cs int n1 = 15; short n2; n2 = (short) n1; Console.WriteLine(n2.ToString());
Interessant ist auch die Verwendung der Methode ToString(). Die Methode wird von jedem (!) Objekt und somit auch von jeder Klasse unterstötzt, da sie von der Mutter aller Objekte geerbt wird.
Explizite Konvertierung
30 ______________________________________________________ 2.7 Kommentare
2.7
Kommentare
Kommentare lassen sich auf zwei unterschiedliche Arten einfögen. Um eine Zeile (nicht Befehlszeile) als Kommentar auszuweisen, werden an der gewönschten Stelle zwei Slashs notiert. Alles, was innerhalb der Zeile nach diesen Zeichen steht, gilt als Kommentar. Anweisungen; // Kommentar
Um einen ganzen Bereich als Kommentar auszuweisen, wird dieser mit /* und */ eingeklammert. Anweisungen /* Anfang des Kommentars blablabla Ende des Kommentars */ Anweisungen
2.8
Namespaces
Das .NET Framework gliedert die von ihm zur Verfögung gestellte FunktionalitÙt in einer Namens-Hierarchie. Innerhalb jeder Ebene dieser Hierarchie sind Elemente wie Klassen hinterlegt. Die einzelnen Ebenen werden durch mit Punkten separierte Namen angesprochen. Man nennt eine einzelne Ebene Namespace. Ein typischer Name för einen verschachtelten Namespace ist beispielsweise... System.Windows.Forms
Der Zugriff auf die Elemente eines Namespace erfolgt durch Angabe des vollstÙndigen Namespace-Namens und anschlieûend mit einem Punkt separat das gewönschte Element. Der Zugriff auf die Ausgabe-Konsole sieht beispielsweise wie folgt aus: System.Console.WriteLine("Hallo Welt");
¹Systemª ist der Name des Namespace, ¹Consoleª der Name der Klasse und ¹WriteLineª die Methode zur Ausgabe des öbergebenen Textes auf der Konsole. Alternativ kðnnen ein oder auch mehrere Namespaces direkt in das Programm eingebunden werden. Hierzu wird der Name des Namespace mit Hilfe des usingSchlösselwortes zu Beginn des Programms notiert. Die Mitglieder des oder der
2 Erste Schritte in C# ____________________________________________________31
referenzierten Namespaces stehen anschlieûend ohne Angabe des Namens zur Verfögung. using System; Console.WriteLine("Hallo Welt");
Als weitere Alternative kann einem Namespace ein Alias zugewiesen werden. Dies ist insbesondere wichtig, wenn zwei Namespaces mit gleichnamigen ElementNamen verwendet werden sollen, der Zugriff aber nicht jeweils durch den mitunter langen Namespace-Namen, sondern durch einen kurzen Alias erfolgen soll. Dieser Alias wird wie folgt hinter dem Schlösselwort using notiert: using Alias = Namespace; Die Ausgabe auf der Konsole kðnnte demnach zum Beispiel so aussehen: using sys = System; sys.Console.WriteLine("Hallo Welt");
Die vom .NET Framework angebotenen FunktionalitÙten wurden unterhalb des Namespace System zusammengefasst. Die Tabelle 2.2 enthÙlt eine šbersicht der wichtigsten, untergeordneten Namespaces und deren Funktion. Neben diesem Namespace existieren weitere Hersteller-spezifische. Daröber hinaus ist es mðglich, eigene NamensrÙume in Ihren Programmen anzulegen. Hierför wird das Schema Herstellername.Produktname.EventuelleUnterelemente
empfohlen. Die Deklaration eines neuen Namespace erfolgt öber das Schlösselwort namespace und erhÙlt den anschlieûend notierten Namen. Der Inhalt des Namespaces wird im folgenden durch geschweifte Klammern gekennzeichneten Göltigkeitsbereich vorgenommen. namespace pal.csharpbuch { // . . . }
32 _____________________________________________________ 2.8 Namespaces
Tabelle 2.2 Die Namespace-Hierarchie unterhalb von „System“ in der Übersicht Namespace
Beschreibung
System
Im Namespace System beginnt die NamespaceHierarchie des .Net Frameworks. Die zum Framework gehörende Funktionalität ist unterhalb angeordnet. Direkt im Namespace enthalten sind die angebotenen Datentypen und andere grundlegende Elemente. System.CodeDom
Klassen zur Erstellung von .NET Programmen zur Laufzeit
System.Collections
Verschiedene Grund-Collections Verwendung oder Ableitung
System.ComponentModel
Funktionalitäten zur Erstellung, Lizenzierung und Distribution von erstellten Komponenten
System.Configugration
Konfigurationsdaten einer Applikation
System.Data
Zugriff und Management von Daten(banken), zum Beispiel ADO.NET und SQL Server 2000
System.Diagnostics
Möglichkeiten zur Diagnose und zum Debugging von Programmen
System.DirectoryServices
Zugriff auf Active Directory Services (ADSI) wie die Internet Information Services oder den Exchange Server 2000
System.Drawing
Reichhaltige 2D Grafikbibliotheken und Zugriff auf GDI+
System.Globalization
Möglichkeiten zur Globalisierung von international eingesetzten Applikationen
System.IO
Zugriff auf das Datei-System, inklusive Operationen auf Datei-Ebene (Storage, etc.)
System.Management
Funktionen und Tools zur Zusammenarbeit mit dem Web-Based Enterprise Management (WBEM)
System.Messaging
Übermittlung von Nachrichten im Netzwerk und Internet, zum Beispiel Microsoft Message Queue (MSMQ)
System.Net
Unterstützung von Netzwerk-Zugriffen und Standard Netzwerk-Protokollen
System.Reflection
Zugriff und Manipulation von Meta-Daten eines Objektes
System.Ressources
Ressourcen-Verwaltung und -Lokalisierung
System.Security
Zugriff auf die Sicherheitsfunktionen des .NET Frameworks
System.ServiceProcess
Unterstützung für die Entwicklung von WindowsDiensten (Services)
zur
direkten
Daten-
2 Erste Schritte in C# ____________________________________________________33
Namespace
2.9
Beschreibung
System.Text
Funktionen zur Bearbeitung von strings, reguläre Ausdrücke etc.
Zeichenketten,
System.Threading
Zugriff auf Thread-Eigenschaften und Erstellung von eigenen Threads
System.Timers
Zeitgesteuerte Ereignisse erstellen
System.Web
Unterstützung von Server- und Client-seitiger InternetAnbindung, enthält auch die Infrastruktur von ASP.NET inklusive WebForms
System.Windows.Forms
Windows-Formulare und Dialoge werden auf Basis dieser Funktionen erstellt
Konsolen-Applikationen
Die Ausgabe von Texten wurde bereits im vorangegangenen Abschnitt kurz gezeigt. Alle Beispiele dieses Buches wurden als Konsolen-Applikationen realisiert. Jegliche Ausgabe von Beispiel-Daten erfolgt also immer im Konsolenfenster. Der Vorteil hiervon ist die Schlankheit der Beispiele, die ohne den Ballast von Formularen daherkommen. Zur Interaktion mit dem Benutzer stehen innerhalb der Klasse Console mehrere Methoden zur Ein- und Ausgabe von Daten im Konsolenfenster zur Verfögung. Die wichtigsten drei Methoden werden in der Tabelle 2.3 aufgelistet und beschrieben. Alle Methoden sind statisch und werden daher direkt öber den Namen der Klasse aufgerufen. So erfolgt beispielsweise die Ausgabe eines Textes, wie bereits weiter oben gezeigt, mit Hilfe der folgenden Zeilen: using System; Console.WriteLine("Hallo Welt");
Tabelle 2.3 Die Methoden der Klasse Console in der Übersicht Methode
Beschreibung
ReadLine
Ermöglicht dem Benutzer der Konsolen-Applikation, Daten einzugeben. Die Methode liefert die eingegebenen Daten zurück, sobald der Benutzer diese mit Enter bestätigt. Die Methode kann auch dazu verwendet werden, um die Ausgabe bis zur nächsten Interaktion des Benutzers anzuhalten. Von dieser Möglichkeit wird in den Beispielen des Buches Gebrauch gemacht.
Write
Gibt einen übergebenen Text im Konsolenfenster aus, schließt die Zeile jedoch nicht ab
WriteLine
Arbeitet wie die Methode Write, schließt die aktuelle Zeile allerdings ab und setzt die Position des Cursors an den Beginn der folgenden Zeile
34 ________________________________________________ 2.10 Die Methode Main
2.10 Die Methode Main Die Ausföhrung eines Programms beginnt beim Start des selbigen nicht wahllos an einer zufÙlligen Stelle, sondern gezielt in einer bestimmten Methode. Es handelt sich um die statisch deklarierte Methode Main, also die ¹Hauptmethodeª eines Programms. Die Methode muss in jedem Programm enthalten sein und zudem einem bestimmten Aufbau entsprechen. Im einfachsten Fall sieht dieser wie folgt aus: static void Main() { // Hier startet das Programm }
Die Notation von void gibt an, dass die Methode keinen Röckgabewert hat. Auch werden keine Parameter an die Methode öbergeben. Dies ist wie beschrieben der einfachste Fall, es gibt also auch noch andere Mðglichkeiten.
Übergabe von Kommandozeilen-Parametern Oft sollen einem Programm öber die Kommandozeile Dateinamen, Optionen oder …hnliches öbergeben werden. Auch mit C# ist dies problemlos mðglich; das öbergeordnete Framework öbernimmt die erforderliche Arbeit. Damit die Informationen an die Methode weitergereicht werden, muss lediglich ein entsprechender Parameter för die Methode Main deklariert werden. Es handelt sich dabei um ein Array vom Datentyp string. Die Deklaration erfolgt auf die nachstehende Weise: static void Main(string[] args) { // Hier startet das Programm }
Der Zugriff auf die einzelnen Elemente des Arrays mit dem hier verwendeten Namen args erfolgt wie in Kapitel 9.1 beschrieben. Jedes Element des Arrays enthÙlt einen Bereich der mit Leerzeichen separierten Kommandozeile des Programmaufrufes.
2 Erste Schritte in C# ____________________________________________________35
Rückgabe eines eventuellen Fehlercodes Bei Windows-Programmen besteht die Mðglichkeit, einen Röckgabewert an das aufrufende Programm zuröckzuliefern. Dieses kann unter UmstÙnden einen eventuellen Fehlercode auswerten. Es handelt sich um einen Röckgabewert vom Typ int. Mðchten Sie einen solchen Wert zuröckliefern, ersetzen Sie in der Deklaration der Methode Main einfach void durch int. Die Röckgabe des gewönschten Wertes erfolgt anschlieûend öber die Sprunganweisung return direkt innerhalb der Methode. static int Main() { // Hier startet das Programm return(0); }
Beachten Sie, dass die Sprunganweisung return das Programm an der aktuellen Position beendet, es muss sich also um den letzten Befehlsaufruf handeln. Der Röckgabewert sollte im Normalfall 0 entsprechen. Nur im Fehlerfall sollte ein anderer Wert zuröckgeliefert werden.
Parameter und Fehlercode Sie kðnnen die beiden zuvor beschriebenen Mðglichkeiten selbstverstÙndlich auch in Kombination verwenden. Die Deklaration der Methode Main sÙhe in diesem Fall wie folgt aus: static int Main(string[] args) { // Hier startet das Programm return(0); }
2.11 Hallo Welt Ein erstes ¹Hallo Weltª darf an dieser Stelle nicht fehlen. Es werden die Informationen dieses Kapitels in wenigen Zeilen zusammengefasst.
36 _______________________________________________________ 2.11 Hallo Welt
Im Beispiel wird zunÙchst der Namespace System referenziert. Alle Elemente dieses Namespace stehen fortan direkt zur Verfögung. Innerhalb eines neuen Göltigkeitsbereiches der Klasse ConsoleApp wird die statische Methode Main ohne Röckgabewert und ohne Parameter deklariert. Diese wird bei der Ausföhrung des Programms aufgerufen. Die Methode enthÙlt die Deklaration einer neuen Variable vom Typ string, der bereits in der Zeile der Deklaration dem gewönschten Inhalt zugewiesen wird. Dieser wird anschlieûend mit Hilfe der Methode WriteLine der Klasse Console im Konsolenfenster ausgegeben. Die Methode ReadLine sorgt daför, dass die Konsole nicht geschlossen wird, bis die Eingabeaufforderung mit Enter bestÙtigt wird. Die Abbildung zeigt das Konsolenfenster mit der Ausgabe.
Listing 2.3 hallowelt.cs using System; class ConsoleApp { static void Main() { string s = "Hallo Welt"; Console.WriteLine(s); Console.ReadLine(); } }
Abbildung 2.1 Klappe die erste...
3 Klassen & objektorientierte Programmierung – Teil I
Was ist ein Objekt? Was ist eine Klasse? Instanziierung einer Klasse Terminierung eines Objekts Deklaration einer neuen Klasse
38 ____________________________________________________________________
Inhaltsübersicht Kapitel 3
3
Klassen & objektorientierte Programmierung ± Teil I ..................................39
3.1 3.2 3.3 3.4 3.5 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 3.5.9
Was ist ein Objekt?..............................................................................................39 Was ist eine Klasse? ............................................................................................40 Instanziierung einer Klasse..................................................................................41 Terminierung eines Objekts.................................................................................42 Deklaration einer neuen Klasse ...........................................................................43 Dynamisch versus statisch ...................................................................................46 Eigenschaften deklarieren....................................................................................48 Methoden deklarieren ..........................................................................................53 Ereignisse deklarieren..........................................................................................56 Das Schlösselwort this.........................................................................................62 Modifikatoren ......................................................................................................63 Konstruktor..........................................................................................................67 Destruktor............................................................................................................72 Attribute...............................................................................................................74
____________________________________________________________________ 39
3 Klassen & objektorientierte Programmierung – Teil I ¹Das ganze Leben ist ein Objekt.ª Dieser Spruch trifft zwar auf das reale Leben nicht unbedingt zu, för C# jedoch schon. Als vollstÙndig objektorientierte Programmiersprache sieht C# in jeder Variablen und jedem Element ein Objekt.
3.1
Was ist ein Objekt?
Ein Objekt ist eine logische Informationseinheit. Ein Objekt fasst eine oder auch mehrere Informationen zusammen und stellt Mðglichkeiten zur Bearbeitung dieser Informationen zur Verfögung. Nehmen Sie zum Beispiel Ihre Katze, die Ihnen vielleicht auf dem Schoû sitzt, wÙhrend Sie dieses Buch studieren. Auf die Programmierung umgesetzt, wÙre Ihre Katze ein Objekt. Mal angenommen, Ihre Katze wÙre wirklich nur ein Objekt und nicht das liebe, warme und schnurrende Geschðpf, das sie ist, dann gelten folgende Beobachtungen: • Das Objekt hat feste Eigenschaften wie die Augenfarbe. • Das Objekt hat dynamische, also sich Ùndernde Eigenschaften wie zum Beispiel den Zufriedenheitsstatus. • Das Objekt hat Methoden, die von auûen gesteuert werden. Hierzu gehðrt bei Hauskatzen beispielsweise die Versorgung mit Nahrung. • In vielen FÙllen kann das Objekt auch Ereignisse auslðsen. Bei einer Katze ist das ganz sicher der Fall, denn wenn sie laut miaut oder ein HÙufchen gemacht hat, dann ist das durchaus ein Ereignis, das gewisse Reaktionen zur Folge hat. In C# werden alle Informationen als Objekte dargestellt. Alle diese Objekte werden von der Mutter aller Objekte, dem Datentyp mit dem treffenden Namen object, abgeleitet. Dieses Basisobjekt liefert bereits eine Reihe von Methoden. Diese sind in allen Unterobjekten vorhanden. Bei dem der Katze öbergeordneten Objekt, dem Tier, wÙre eine solche grundsÙtzlich vorhandene Methode die Versorgung mit Nahrung, denn jedes Tier braucht schlieûlich etwas zu futtern. Weitere individuelle Eigenschaften, Methoden und eventuell auch Ereignisse werden spezifisch vom Objekt angeboten.
object
40 _______________________________________________ 3.2 Was ist eine Klasse?
Abbildung 3.1 Hilfe, meine Katze ist ein Objekt!
3.2
Was ist eine Klasse?
Die objektorientierte Programmierung wird hÙufig auch als Klassenprogrammierung bezeichnet. Eine Klasse ist quasi das Grundgeröst eines Objektes, also der Plan, nach dem dieses Objekt definiert ist. Auf das Beispiel der Katze angewandt, wörde die Klasse alle verfögbaren Eigenschaften, Methoden und Ereignisse einer Katze definieren und die dahinterliegende Funktion implementieren. So wörde beispielsweise die Methode zur Versorgung mit Nahrung diese aufnehmen, verarbeiten und zu guter Letzt das Ereignis HÙufchen triggern. Die Klasse ist also die Definition eines Objektes, wÙhrend dieses eine Instanz, also die Anwendung der Klasse ist.
3 Klassen & objektorientierte Programmierung – Teil I __________________________41
Abbildung 3.2 Eine Katze – ganz „Objektiv“ betrachtet
3.3
Instanziierung einer Klasse
Die Instanziierung einer Klasse erfolgt wie bei Variablen , die zu Beginn des Kapitels vorgestellt wurden. Das ist auch ganz logisch, denn schlieûlich handelt es sich auch hierbei um Objekte von einem bestimmten Typ. Konkret sieht das wie folgt aus: Katze meineKatze = new Katze();
Klasse instanziieren
42 __________________________________________ 3.4 Terminierung eines Objekts
ZunÙchst wird der Datentyp, also der Name der Klasse, notiert. Anschlieûend folgt der Name der gewönschten Variablen und danach deren Zuweisung. In diesem Fall wird öber das Schlösselwort new eine neue Instanz der Klasse Katze instanziiert. Es wird also quasi eine neue Katze geboren, in diesem Fall ohne Mutter und Vater ± langsam schweifen wir also in die virtuelle RealitÙt ab. Speicher reservieren
Durch die Instanziierung weiû C#, dass Sie nun auf das Objekt zugreifen mðchten, und kömmert sich automatisch um die Reservierung des daför notwendigen Speichers. Sie mössen diesen also nicht wie bei anderen Sprachen oder Systemen zuvor explizit anfordern. Die Variable meineKatze enthÙlt nicht die Katze selbst, sondern lediglich einen Verweis auf dieses Objekt. Auf diese Weise ist es auch mðglich, dass mehrere Variable auf ein und das selbe Objekt verweisen. Das folgende Beispiel macht dieses Prinzip deutlich.
Verweis kopieren
Katze meineKatze = new Katze(); Katze KatzeMeinerFrau = meineKatze;
Ihre Katze ist auch die Katze Ihrer Frau und somit halten Sie beide einen Verweis auf ein und dieselbe Katze, also dasselbe Objekt.
3.4
Garbage Collection
Terminierung eines Objekts
Nachdem Sie die Klasse instanziiert haben, befindet sich ein entsprechendes Objekt im Speicher. Wird der Geltungsbereich der Variablen verlassen und besteht kein anderer Verweis auf das Objekt, wird dieses nicht lÙnger benðtigt. Die Entfernung des Objektes aus dem Speicher, also die Terminierung, öbernimmt im .NET Framework und somit auch in C# die so genannte Garbage Collection. Es handelt sich quasi um die Möllabfuhr, die unbenðtigten Speicher einsammelt und der Wiederverwertung zuföhrt. Wie es bei der Möllabfuhr halt so ist, steht diese nicht vor dem Haus und wartet den ganzen Tag darauf, dass Sie etwas Speicher loswerden wollen. Vielmehr kommt die Möllabfuhr bei Ihnen vorbei, wenn sie gerade etwas (CPU-) Zeit hat. Eine explizite Freigabe des Speichers ist also nicht notwendig. Falls erwönscht, kðnnen Sie eine Objektinstanz aber explizit terminieren. Hierzu setzen Sie die verweisende Variable einfach ins Nirwana, also gleich null. Beachten Sie, dass dies nur bei Verweistypen, nicht aber bei Wertetypen wie short, int, long, etc. mðglich ist. Zudem lðscht die Zuweisung lediglich diesen einen Verweis. Sofern also weitere Verweise auf das Objekt existieren, bleibt dieses weiter bestehen.
3 Klassen & objektorientierte Programmierung – Teil I __________________________43
Angewandt auf unser kleines Beispielobjekt, wirkt die Terminierung auf den ersten Blick ein wenig brutal: meineKatze = null;
Objekt terminieren
Auf den zweiten Blick ist es aber gar nicht sooo schlimm, denn Ihre Frau hÙlt ja noch einen Verweis auf das Objekt und somit wurde Ihre Katze nicht wirklich terminiert.
3.5
Deklaration einer neuen Klasse
Um alle Ihre Haustiere abzubilden, kðnnen Sie selbstverstÙndlich auch eigene Klasse deklarieren. Neben dem grundsÙtzlichen Aufbau wird dabei auch die interne Programmierung vorgenommen. Erst diese Kombination aus Deklaration und Implementation ergibt letztlich die Klasse. Jede Klasse und somit jedes Objekt ist beziehungsweise sollte so angelegt sein, dass es eine bestimmte logische Informationseinheit (und nur diese) mit allen Aspekten reprÙsentiert. WÙhrend dies bei der Katze ganz klar vorgegeben ist, liegt genau in diesem Punkt die Schwierigkeit der objektorientierten Programmierung. Ein Entwickler muss planen und differenzieren, was seine Informationseinheit ist und wie sich diese von anderen abgrenzt. Je allgemeiner die Definition erfolgt, desto besser lassen sich Klassen spÙter wieder verwenden. Die Deklaration einer Klasse erfolgt mit dem Schlösselwort class gefolgt von dem gewönschten Namen der Klasse. Alles Weitere wird anschlieûend dem Göltigkeitsbereich der Klasse zugeordnet und daher eingeröckt in geschweiften Klammern notiert.
Listing 3.1 class1.cs class Katze { // Implementation der Klasse Katze }
Innerhalb des Göltigkeitsbereiches kðnnen Sie nun Variablen, Eigenschaften, Methoden und so weiter definieren. Variablen, so genannte Mitglieder-Variablen der Klasse, werden dabei einfach wie gewohnt notiert. Solche Variablen sind dann ausschlieûlich innerhalb der Klasse verfögbar und kðnnen nicht von auûen angesprochen werden.
Schlüsselwort class
44 _______________________________________ 3.5 Deklaration einer neuen Klasse
Listing 3.2 class2.cs class Katze { string Name; } ... Katze meineKatze = new Katze(); meineKatze.Name = "Filou";
Dieser Aufruf föhrt zu einem Fehler, da die Variable nur lokal innerhalb der Klasse verfögbar ist. Die Abbildung zeigt die Ausgabe der Fehlermeldung durch den Compiler.
Abbildung 3.3 Auf die Variable kann nur innerhalb der Klasse zugegriffen werden. Modifikator public
Sofern eine Variable auch von auûen angesprochen werden soll, muss diese mit einem so genannten Modifikator versehen werden. Oft verwendet wird der Modifikator public (öbersetzt: ¹ðffentlichª), der bewirkt, dass die Variable beliebig von auûen gelesen und gesetzt werden kann. Das folgende Beispiel verwendet den beschriebenen Modifikator und erlaubt so den Zugriff auf die Variable Name.
3 Klassen & objektorientierte Programmierung – Teil I __________________________45
Listing 3.3 class3.cs using System; class Katze { public string Name; } class ConsoleApp { static void Main() { Katze meineKatze = new Katze(); // Ich gebe der Katze einen Namen meineKatze.Name = "Filou"; Console.WriteLine("Meine Katze heiût: "); Console.WriteLine(meineKatze.Name + ""); // Ich gebe der Katze einen neuen Namen meineKatze.Name = "Hansi"; // Doch wie heiût die Katze nun? Console.WriteLine("Meine Katze heiût: "); Console.WriteLine(meineKatze.Name + ""); Console.ReadLine(); } }
Die Abbildung zeigt die geÙnderte Ausgabe im Konsolenfenster. Der Zugriff der Variablen ist ganz beliebig mðglich und so kðnnen Sie der Katze statt des schðnen Namens Filou einen anderen verpassen. Ob Hansi allerdings wie ein Wellensittich piepsen wird, ist ungewiss.
46 _______________________________________ 3.5 Deklaration einer neuen Klasse
Abbildung 3.4 Die öffentliche Variable kann sowohl gesetzt als auch gelesen werden.
Es stehen eine Reihe weiterer Modifikatoren zur Verfögung. Diese werden weiter unten unter der gleichnamigen šberschrift vorgestellt.
3.5.1
Dynamisch versus statisch
Ein Objekt ist immer die Instanziierung einer Klasse. Ruft man eine Eigenschaft des Objektes, also der Instanz einer Klasse auf, so kann diese einen vollkommen anderen Wert enthalten als eine andere Instanz derselben Klasse. Die Eigenschaften und Methoden eines Objektes sind also dynamische Mitglieder. Das folgende Beispiel zeigt zwei Instanzen einer Klasse, deren Eigenschaft Name einen jeweils anderen Namen enthÙlt. Listing 3.4 class4.cs class Katze { public string Name; } class ConsoleApp { static void Main() { Katze meineKatze1 = new Katze(); meineKatze1.Name = "Filou"; Katze meineKatze2 = new Katze(); meineKatze2.Name = "Shari";
3 Klassen & objektorientierte Programmierung – Teil I __________________________47
Console.WriteLine("Meine erste Katze heiût: " + meineKatze1.Name); Console.Write ("Meine zweite Katze heiût: " + meineKatze2.Name); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Meine erste Katze heiût: Filou Meine zweite Katze heiût: Shari
Neben dynamischen existieren auch statische Mitglieder einer Klasse. Diese Mitglieder stehen global und ohne Instanz einer Klasse zur Verfögung. Sie werden auch nicht auf Basis der Instanz, sondern auf Basis des Klassennamens angesprochen. Aus diesem Grund kann eine statische Eigenschaft immer nur ganz genau einen Wert enthalten. Die Implementierung der statischen Mitglieder erfolgt innerhalb der Klasse analog zu den dynamischen. Allerdings muss eine besondere Kennzeichnung durch den Modifikator static erfolgen. Es ergibt sich folgendes Schema: public static Mitgliedsname() { // Befehlszeilen }
Der Aufruf sieht so aus: Klassenname.Mitgliedsname()
Statische Mitglieder bieten sich immer dann an, wenn eine FunktionalitÙt unabhÙngig von einer bestimmten Instanz, aber dennoch im logischen Verbund mit der Klasse verwendet werden soll. Das nachfolgende Beispiel implementiert auf Basis der Klasse Katze eine statische Methode zur Ermittlung eines zufÙlligen Namens för die Katze. Dieser wird aus einem Fundus an hinterlegten Namen zufÙllig ausgewÙhlt.
static
48 _______________________________________ 3.5 Deklaration einer neuen Klasse
Listing 3.5 class5.cs using System; class Katze { public string Name; public static string GetRandomName() { string[] Names = {"Pussi", "Muschi", "Mausi", "Mieze"}; Random rnd = new Random(); int rndValue = rnd.Next(0, Names.GetUpperBound(0)); return(Names[rndValue]); } } class ConsoleApp { static void Main() { Katze meineKatze = new Katze(); meineKatze.Name = Katze.GetRandomName(); Console.WriteLine("Meine Katze heiût: " + meineKatze.Name); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster sieht ganz unterschiedlich aus, denn schlieûlich wird der Name der Katze zufÙllig ausgewÙhlt: Meine Katze heiût: Pussi
3.5.2
Eigenschaften deklarieren
Der ðffentliche Zugriff auf eine Variable ist zwar prinzipiell gut, allerdings nicht kontrollierbar, da dieser Zugriff direkt erfolgt und Sie somit keinerlei Einflussmðglichkeiten haben. –ffentliche Variablen werden daher selten verwendet und stattdessen in Form von Eigenschaften implementiert.
3 Klassen & objektorientierte Programmierung – Teil I __________________________49
Eigenschaften werden Ùhnlich deklariert, bieten aber eine kontrollierte ZugriffsfunktionalitÙt und enthalten anders als Variablen direkt keine Daten. Die Funktionen zum Abfragen und Setzen der Eigenschaft werden öber die Schlösselwðrter get und set implementiert. Diese verfögen jeweils öber einen eigenen (eingeklammerten) Geltungsbereich und kommunizieren mit der Auûenwelt öber die Schlösselwðrter return zur Röckgabe einer abgefragten Eigenschaft und value zur Ermittlung des zu setzenden Wertes. Es ergibt sich folgendes Grundschema för die Deklaration einer Eigenschaft. Modifikator Datentyp Eigenschaftsname { get { return(Mitgliedsvariable); } set { Mitgliedsvariable = value; } }
Im folgenden Beispiel wurde die Benennung der Katze aufgenommen. Der Name der Katze wird nun als Eigenschaft implementiert. Sowohl get als auch set sind vorhanden. Da die Eigenschaft selbst keine Daten speichern kann, wird zusÙtzlich die lokale Variable _Name benðtigt. Der Name kann allerdings beliebig gewÙhlt, da eine Zuordnung explizit durch entsprechende Zuweisung unterhalb von get beziehungsweise set vorgenommen werden muss. Das Problem der unschðnen Umbenennung wurde hier öbrigens gelðst. Durch eine einfach Abfrage ist sichergestellt, dass nur einmal ein Name för die Katze vergeben werden kann. Dieser kann anschlieûend nicht mehr geÙndert werden, schlieûlich hat sich die Katze an den Namen gewðhnt.
Listing 3.6 class6.cs using System; class Katze { string _Name; public string Name { get {
get und set
50 _______________________________________ 3.5 Deklaration einer neuen Klasse
return(_Name); } set { if(_Name==null) _Name = value; } } } class ConsoleApp { static void Main() { Katze meineKatze = new Katze(); // Ich gebe der Katze einen Namen meineKatze.Name = "Filou"; Console.WriteLine("Meine Katze heiût: "); Console.WriteLine(meineKatze.Name + ""); // Ich gebe der Katze einen neuen Namen meineKatze.Name = "Hansi"; // Doch wie heiût die Katze nun? Console.WriteLine("Meine Katze heiût: "); Console.WriteLine(meineKatze.Name + ""); Console.ReadLine(); } }
Der Zugriff erfolgt komplett analog zum vorherigen Beispiel. Eine besondere Behandlung bei Eigenschaften im Vergleich zu ðffentlichen Variablen ist also nicht notwendig. Die Abbildung zeigt das geÙnderte Ergebnis.
3 Klassen & objektorientierte Programmierung – Teil I __________________________51
Abbildung 3.5 Filou bleibt Filou.
Das
folgende
Beispiel
zeigt
zwei
weitere
Eigenschaften:
Alter
und
Menschenalter. Diese greifen die gebrÙuchliche Rechnung auf, dass ein
tatsÙchliches Katzenalter in etwa sieben Menschenjahren entspricht. ZunÙchst wird das Alter öber die gleichnamige Eigenschaft gesetzt und in der lokalen Variable _Alter abgelegt. Die Eigenschaft MenschenAlter greift ebenfalls auf die Variable zu, gibt diese jedoch multipliziert mit 7 zuröck. Listing 3.7 class7.cs using System; class Katze { string _Name; int _Alter; public string Name { get { return(_Name); } set { if(_Name==null) _Name = value; } } public int Alter
52 _______________________________________ 3.5 Deklaration einer neuen Klasse
{ get { return(_Alter); } set { _Alter = value; } } public int MenschenAlter { get { return(_Alter*7); } } } class ConsoleApp { static void Main() { Katze meineKatze = new Katze(); meineKatze.Name = "Filou"; meineKatze.Alter = 12; Console.WriteLine("Meine Katze heiût "); Console.WriteLine(meineKatze.Name); Console.WriteLine(" und ist umgerechnet "); Console.WriteLine(meineKatze.MenschenAlter.ToString()); Console.WriteLine(" Menschenjahre alt."); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster zeigt, dass die Umrechnung von tatsÙchlichem Alter in ein angenommenes Menschenalter innerhalb der Eigenschaft funktioniert. Meine Katze heiût Filou und ist umgerechnet 84 Menschenjahre alt. Schreibgeschütze Eigenschaften
Sie haben vielleicht gesehen, dass die Definition des Schlösselwortes set för die Eigenschaft MenschenAlter fehlt. Das ist keine Unachtsamkeit, sondern volle Absicht gewesen. LÙsst man eines der beiden Schlösselworte get oder set einfach
3 Klassen & objektorientierte Programmierung – Teil I __________________________53
weg, so ergibt sich ein geÙndertes Verhalten der Eigenschaft. Diese kann dann nur gesetzt (write-only) oder wie in diesem Fall nur abgefragt (read-only) werden.
3.5.3
Methoden deklarieren
Methoden werden Ùhnlich wie Eigenschaften deklariert. Nach dem Zugriffsmodifikator folgt der Datentyp sowie der Name der Methode. In runden Klammern folgen nun die Parameter der Methode. Diese werden wiederum mit dem Datentyp sowie dem gewönschten Namen des Parameters notiert. Die Parameter stehen anschlieûend in Form von lokalen Variablen zur Verfögung. Die FunktionalitÙt der Methode wird in einem eigenen Geltungsbereich hinterlegt, der in geschweifte Klammern eingerahmt ist. Der Röckgabewert der Methode wird wie bei Eigenschaften mit dem Schlösselwort return angegeben. Das Schlösselwort kann beliebig hÙufig beispielsweise innerhalb von Bedingungen oder switchKonstrukten innerhalb der Methode verwendet werden und föhrt zur sofortigen Röckkehr zur aufrufenden Programmzeile. Es ergibt sich das folgende Schema. Modifikator Datentyp Methodenname(Datentyp Parameter) { Befehlszeilen; // weitere Abarbeitung der Methode return(Röckgabewert); }
Auch auf unsere Beispiel-Klasse einer Katze lassen sich Methoden anwenden. Im folgenden Beispiel wird die Methode Fuettern implementiert. Diese erwartet als Parameter die Nahrung sowie die Anzahl der Portionen. Gehðrt die Nahrung zu einer bestimmten Gruppe, erhðht sich der Zufriedenheitsindex der Katze um die Anzahl der Portionen. Bei jeder anderen Nahung verringert sich der Index entsprechend. Listing 3.8 class8.cs using System; class Katze { int _Zufriedenheitsindex; public int Zufriedenheitsindex { get {
return
54 _______________________________________ 3.5 Deklaration einer neuen Klasse
return(_Zufriedenheitsindex); } set { _Zufriedenheitsindex = value; } } public bool Fuettern(string Nahrung, int Portionen) { switch(Nahrung.ToLower()) { case "thunfisch": case "rinderhack": case "höhnchen": this.Zufriedenheitsindex+=Portionen; return(true); default: this.Zufriedenheitsindex-=Portionen; return(false); } } } class ConsoleApp { static void Main() { Katze meineKatze = new Katze(); meineKatze.Zufriedenheitsindex = 5; Console.WriteLine("Zufriedenheitsindex vor Fötterung: "); Console.WriteLine(meineKatze.Zufriedenheitsindex.ToString() + ""); if(meineKatze.Fuettern("thunfisch", 3)) Console.WriteLine("Das hat geschmeckt!"); else Console.WriteLine("Das war keine gute Idee!"); Console.WriteLine("Zufriedenheitsindex nach Fötterung: "); Console.WriteLine(meineKatze.Zufriedenheitsindex.ToString() + ""); Console.ReadLine(); } }
3 Klassen & objektorientierte Programmierung – Teil I __________________________55
Die Methode liefert einen logischen (booleschen) Wert zuröck, ob die Nahrung genehm war oder nicht. Dieser wird öber eine if-Bedingung abgefragt und ein entsprechender Text im Konsolenfenster ausgegeben. Die vollstÙndige Ausgabe des Beispiels sieht so aus: Zufriedenheitsindex vor Fötterung: 5 Das hat geschmeckt! Zufriedenheitsindex nach Fötterung: 8
Glöck gehabt, die Katze mag den Tunfisch scheinbar. Der Zufriedenheitsindex hat sich um die Anzahl der Nahrungsportionen erhðht. Nicht immer sind Sie am Röckgabewert eine Methode interessiert und manchmal gibt es gar keine Information, die zuröckgegeben werden kðnnte. In diesem Fall geben Sie als Datentyp void an. Der Aufruf von return ist in diesem Fall öberflössig beziehungsweise nicht mðglich. Am Beispiel der Karte kðnnte die Methode Streicheln ohne Röckgabewert auskommen. Listing 3.9 class9.cs using System; class Katze { int _Zufriedenheitsindex; public int Zufriedenheitsindex { get { return(_Zufriedenheitsindex); } set { _Zufriedenheitsindex = value; } } public void Streicheln() { this.Zufriedenheitsindex+=10; } } class ConsoleApp {
Schlüsselwort void
56 _______________________________________ 3.5 Deklaration einer neuen Klasse
static void Main() { Katze meineKatze = new Katze(); meineKatze.Zufriedenheitsindex = 5; Console.WriteLine("Zufriedenheitsindex vor dem Streicheln: "); Console.WriteLine(meineKatze.Zufriedenheitsindex.ToString() + ""); meineKatze.Streicheln(); Console.WriteLine("Zufriedenheitsindex nach dem Streicheln: "); Console.WriteLine(meineKatze.Zufriedenheitsindex.ToString() + ""); Console.ReadLine(); } }
Die Ausgabe zeigt, dass auch dies der Katze gefallen hat: Zufriedenheitsindex vor dem Streicheln: 5 Zufriedenheitsindex nach dem Streicheln: 15
3.5.4
delegate
Ereignisse deklarieren
Nach den Eigenschaften und Methoden sind Ereignisse ein drittes Charaktermerkmal von Klassen. Allerdings bietet nur ein Bruchteil der verfögbaren Klassen Ereignisse an, denn diese werden nur dann benðtigt, wenn auf bestimmte …nderungen eines Objektes reagiert werden soll. Eigenschaften und Methoden wurden nach einem einheitlichen und sehr einfachen Schema deklariert. Auch för Ereignisse gibt es ein solches Schema, dieses ist aber ein Stöck weit komplexer. Das Ereignis besteht aus mehreren Einheiten: der Deklaration (delegate), der Instanziierung (event), dem Aufruf und schlieûlich der Behandlung. Die Deklaration eines Ereignisses erfolgt öber das Schlösselwort delegate. Wie bei einer Methode wird zunÙchst der Modifikator notiert, es folgt der Datentyp des Röckgabewertes (beziehungsweise void, wenn es keinen geben soll), der Name der Deklaration und anschlieûend die Parameter in runden Klammern. Modifikator delegate Datentyp DelegateName(Datentyp Parameter);
event
Nachdem eine Ereignis-Deklaration allgemein verwendet werden kann, definiert sich öber das Schlösselwort event ein spezifisches Ereignis. …hnlich wie bei einer Klasse handelt sich bei einem event also um die Instanziierung der Deklaration
3 Klassen & objektorientierte Programmierung – Teil I __________________________57
(delegate). Da der Aufbau bereits feststeht, werden lediglich der Name der Deklaration sowie der Name dieses speziellen Ereignisses angegeben. Modifikator event DelegateName EreignisName
Damit ein Ereignis nicht nur eintritt, sondern auch bemerkt wird, benðtigt man eine Ereignisbehandlung. Es handelt sich dabei um eine Methode, die dem Aufbau der Ereignisdeklaration (delegate) entspricht. Typischerweise wird diese Methode in der aufrufenden Klasse implementiert. Hier wird angegeben, was bei dieser speziellen Instanz einer Klasse im Falle eines eintretenden Ereignisses geschehen soll.
Eventhandler
MethodenName(Datentyp Parameter) { Ereignisbehandlung }
Damit klar ist, welche Ereignisbehandlung verwendet werden soll, muss diese bei der Instanz der Klasse angemeldet werden. Hierzu wird das folgende Schema verwendet. KlassenInstanz.EreignisName += new DelegateName(MethodenName);
Auch ein Abmelden der Ereignisbehandlung sollte unbedingt vorgenommen werden, wenn diese nicht weiter benðtigt wird. Der Aufruf erfolgt analog. KlassenInstanz.EreignisName -= new DelegateName(MethodenName);
Sehr wichtig in beiden FÙllen ist die Verwendung von += beziehungsweise -=, denn es kðnnen auch mehrere Ereignisbehandlungen för ein und dasselbe Ereignis angemeldet werden. Diese Zeichen sind Zuweisungsoperatoren. Der Wert rechts wird zu dem Wert links addiert beziehungsweise von diesem subtrahiert und anschlieûend der links notierten Variable zugewiesen. Eine šbersicht dieser und weiterer Operatoren finden Sie öbrigens im gleichnamigen Kapitel weiter unten. Zu guter Letzt benðtigen wir noch einen Auslðser för das Ereignis. Dieser befindet sich in der Regel innerhalb der Klasse und wird innerhalb einer Methode oder Eigenschaft ausgeföhrt. Unbedingt erforderlich ist dabei immer eine Abfrage, ob zumindest eine Ereignisbehandlung angemeldet wurde. Dies geschieht durch den Vergleich der Ereignisinstanz mit null. Es ergibt sich das folgende Schema. if(EreignisName != null) EreignisName(Parameter);
Eventhandler anmelden
58 _______________________________________ 3.5 Deklaration einer neuen Klasse
Wie Sie sehen, sind Ereignisse auf den ersten Blick nicht ganz einfach. Hat man sich jedoch einmal auf das System eingestellt, wird man die daraus resultierenden Vorteile schnell schÙtzen lernen. Im Folgenden ein kleines Beispiel, das das Beschriebene verdeutlichen soll. Die Klasse Test enthÙlt eine Ereignisdeklaration und eine Ereignisinstanz. Die Methode EreignisInitiieren lðst das Ereignis aus. Auûerhalb der Klasse ist eine Behandlung implementiert, die die öbergebene Zeichenkette im Konsolenfenster ausgibt. Listing 3.10 class10.cs using System; class Test { public delegate void EreignisDeklaration(string Parameter); public event EreignisDeklaration EreignisInstanz; public void EreignisInitiieren() { if(EreignisInstanz != null) EreignisInstanz("Das Ereignis ist eingetreten!"); } } class ConsoleApp { static void EreignisBehandlung(string p) { Console.WriteLine(p); }
static void Main() { Test TestKlasse = new Test(); TestKlasse.EreignisInstanz += new Test.EreignisDeklaration(EreignisBehandlung); TestKlasse.EreignisInitiieren();
3 Klassen & objektorientierte Programmierung – Teil I __________________________59
TestKlasse.EreignisInstanz -= new Test.EreignisDeklaration(EreignisBehandlung); Console.ReadLine(); } }
Eigentlich ist es doch ganz einfach, ein Ereignis zu erstellen. Die Ausgabe im Konsolenfenster zeigt, dass es funktioniert: Das Ereignis ist eingetreten!
Werden mehrere Ereignisbehandlungen angemeldet, so werden diese der Reihe ihrer Anmeldung nach abgearbeitet. Im nun folgenden Beispiel wird der Text einfach doppelt ausgegeben, da zwei Behandlungen implementiert und angemeldet wurden. Listing 3.11 class11.cs using System; class Test { public delegate void EreignisDeklaration(string Parameter); public event EreignisDeklaration EreignisInstanz; public void EreignisInitiieren() { if(EreignisInstanz != null) EreignisInstanz("Das Ereignis ist eingetreten!"); } } class ConsoleApp { static void EreignisBehandlung(string p) { Console.WriteLine(p + ""); } static void EreignisBehandlung2(string p) { Console.WriteLine(p + " --> Nummer 2"); }
60 _______________________________________ 3.5 Deklaration einer neuen Klasse
static void Main() { Test TestKlasse = new Test(); TestKlasse.EreignisInstanz += new Test.EreignisDeklaration(EreignisBehandlung); TestKlasse.EreignisInstanz += new Test.EreignisDeklaration(EreignisBehandlung2); TestKlasse.EreignisInitiieren(); TestKlasse.EreignisInstanz -= new Test.EreignisDeklaration(EreignisBehandlung2); TestKlasse.EreignisInstanz -= new Test.EreignisDeklaration(EreignisBehandlung); Console.ReadLine(); } }
Auch hier zeigt die Ausgabe im Konsolenfenster, dass beide Ereignisbehandlungen abgearbeitet wurden: Das Ereignis ist eingetreten! Das Ereignis ist eingetreten! --> Nummer 2
Natörlich soll auch das Katzenbeispiel ein Ereignis erhalten, schlieûlich haben Sie sich doch schon an meine Katze Filou gewðhnt, oder? Das Ereignis soll eintreten, wenn die Katze hðchst zufrieden ist. Hierzu wurde eine entsprechende delegate sowie ein event erstellt. Das Ereignis wird ausgelðst, sobald die Eigenschaft Zufriedenheitsindex neu gesetzt wird und öber dem Wert 10 liegt. Ein indirekter Auslðser hierför ist im Beispiel die Methode Streicheln, die den Index um 10 erhðht. Der aktuelle Index wird öber das Ereignis an die Behandlung weitergegeben und von dort im Konsolenfenster ausgegeben. Listing 3.12 class12.cs using System; class Katze { public delegate void SuperZufriedenDelegate(int AktuellerIndex); public event SuperZufriedenDelegate SuperZufrieden; int _Zufriedenheitsindex;
3 Klassen & objektorientierte Programmierung – Teil I __________________________61
public int Zufriedenheitsindex { get { return(_Zufriedenheitsindex); } set { _Zufriedenheitsindex = value; if(_Zufriedenheitsindex>10) { if(SuperZufrieden != null) SuperZufrieden(_Zufriedenheitsindex); } } } public void Streicheln() { this.Zufriedenheitsindex+=10; } }
class ConsoleApp { // Hier wird das Ereignis verarbeitet static void SuperZufriedenEvent(int Index) { Console.WriteLine("Die Katze ist SUPER ZUFRIEDEN (" + Index + ")!"); } static void Main() { Katze meineKatze = new Katze(); meineKatze.Zufriedenheitsindex = 5; meineKatze.SuperZufrieden += new Katze.SuperZufriedenDelegate(SuperZufriedenEvent); Console.WriteLine("Zufriedenheitsindex vor dem Streicheln: "); Console.WriteLine(meineKatze.Zufriedenheitsindex.ToString() + ""); meineKatze.Streicheln();
62 _______________________________________ 3.5 Deklaration einer neuen Klasse
Console.WriteLine("Zufriedenheitsindex nach dem Streicheln: "); Console.WriteLine(meineKatze.Zufriedenheitsindex.ToString() + ""); meineKatze.SuperZufrieden -= new Katze.SuperZufriedenDelegate(SuperZufriedenEvent); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster zeigt, dass das Ereignis direkt beim Hochsetzen des ZufriedenheitsIndex durch die Methode Streicheln ausgelðst und ein entsprechender Text ausgegeben wird. Zufriedenheitsindex vor dem Streicheln: 5 Die Katze ist SUPER ZUFRIEDEN (15)! Zufriedenheitsindex nach dem Streicheln: 15
3.5.5
Das Schlüsselwort this
Oft existieren innerhalb einer Klasse konkurrierende Namen, insbesondere zwischen Mitgliedern der Klasse und Parametern. Die lokal deklarierten Parameter haben dabei Vorrang vor den weiter oben deklarierten Mitgliedern der Klasse. Doch wie sollen diese angesprochen werden? Das Schlösselwort this liefert einen Verweis auf die aktuelle Instanz der eigenen Klasse. So lassen sich nicht nur die Mitglieder der Klasse direkt ansprechen, es kann auch ein Verweis auf die Instanz an eine andere Klasse weitergegeben werden. Das Beispiel zeigt zwei konkurrierende Variablen. Es existiert einmal eine Eigenschaft Name und innerhalb der Methode SetName ein Parameter mit der gleichen Namensgebung. Innerhalb der Methode wird das Schlösselwort this verwendet, um der Eigenschaft den Inhalt des Parameters zuzuweisen. this Listing 3.13 this1.apsx using System; class Test { string _Name; public string Name {
3 Klassen & objektorientierte Programmierung – Teil I __________________________63
get { return(_Name); } set { _Name = value; } } public void SetName(string Name) { this.Name = Name; } } class ConsoleApp { static void Main() { Test t = new Test(); t.SetName("Irgendein Name ;-)"); Console.WriteLine("Name: " + t.Name); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Name: Irgendein Name ;-)
Das Schlösselwort this kann in Instanz-Mitglieder und Konstruktoren aufgerufen werden. Eine Verwendung innerhalb von statischen Mitgliedern ist hingegen nicht mðglich. Eigentlich nur logisch, denn auf welche Instanz sollte das Schlösselwort sich beziehen, wenn ein statisches Mitglied doch direkt aus der Klasse und nicht aus einer Instanz dieser aufgerufen wird.
3.5.6
Modifikatoren
Das Stichwort Modifikatoren ist in den vorangegangenen Abschnitten bereits des ðfteren gefallen. Aber was sind Modifikatoren eigentlich? Es handelt sich um Schlösselwðrter, die die nachfolgende Deklaration modifizieren. Angewendet
64 _______________________________________ 3.5 Deklaration einer neuen Klasse
werden kðnnen Modifikatoren zum Beispiel auf Variablen, Klassen, Eigenschaften Methoden und Ereignisse. Oft lassen sich auch mehrere Modifikatoren in Kombination anwenden, zum Beispiel public und readonly. Die Tabelle zeigt die in C# zur Verfögung stehenden Modifikatoren. Zugriffsmodifikatoren sind mit einem * gekennzeichnet. Tabelle 3.1 Modifikatoren in der Übersicht Modifikator
Beschreibung
abstract
Der Modifikator kann auf Klassen, Eigenschaften und Methoden angewandt werden. Er kennzeichnet das jeweilige Element als abstrakt. Dies bedeutet, dass dieses nicht implementiert ist und dies in einer abgeleiteten Klasse explizit überschrieben werden muss (vergleiche „Klassen & objektorientierte Programmierung – Teil II“).
const
Dieser Modifikator kann auf eine Variable oder ein Feld angewandt werden und kennzeichnet dieses als Konstante (vergleiche „Konstanten“ weiter oben).
event
Kennzeichnet ein Ereignis.
extern
Der Modifikator extern weist den Compiler an, dass die Funktionalität der nachfolgenden Methode extern implementiert ist. Häufig wird der Modifikator in Verbindung mit dem Attribut DllImport verwendet, um die Funktionalität einer (veralteten) Win32 DLL zu verwenden.
internal*
Der Zugriffsmodifikator internal legt fest, dass auf eine Klasse oder ein Mitglied einer Klasse nur innerhalb des aktuellen Projektes (Assembly) zugegriffen werden kann.
override
Um ein Mitglied einer abgeleiteten Klasse zu überschreiben und somit eine neue oder geänderte Implementation zu hinterlegen, muss der Modifikator override verwendet werden. Er stellt das Gegenstück zu virtual dar.
private*
Der Zugriffsmodifikator kennzeichnet eine Klasse oder ein Element als privat. Private Elemente sind nur innerhalb Ihrer Definition gültig. Klassen können nur lokal instanziiert und Klassenmitglieder nur innerhalb der Klasse angesprochen werden.
protected*
Dieser Modifikator regelt ebenfalls den Zugriff auf ein Element und legt fest, dass auf das entsprechende Element nur innerhalb der aktuellen oder einer abgeleiteten Klasse zugegriffen werden kann.
public*
Dieser Zugriffsmodifikator stellt das Gegenstück zu private dar und erlaubt den öffentlichen und unbeschränkten Zugriff auf das jeweilige Element.
readonly
Definiert, dass die nachfolgende Variable nur gelesen werden kann. Der einmalige Schreibzugriff zur Initialisierung der Variable ist bei der Deklaration oder innerhalb eines Klassen-Konstruktors möglich.
sealed
Dieser Modifikator versiegelt eine Klasse und stellt somit sicher, dass keine anderen Klassen von dieser abgeleitet werden können.
static
static kennzeichnet ein Mitglied einer Klasse als statisch. Dieses steht dadurch global über den Klassennamen und nicht über die Instanz der Klasse zur Verfügung.
3 Klassen & objektorientierte Programmierung – Teil I __________________________65
Modifikator
Beschreibung
virtual
Damit ein Klassen-Mitglied in einer abgeleiteten Klasse mit Hilfe von override überschrieben werden kann, muss dieses Mitglied in der Basisklasse mit dem Modifikator virtual versehen werden. Wird das Mitglied nicht überschrieben, so wird in der abgeleiteten Klasse die ursprüngliche Implementation verwendet. In diesem Punkt unterscheidet sich der Modifikator von abstract, da das Mitglied überschrieben werden kann, aber nicht muss.
unsafe
Um Programmcode zu erzeugen, der nicht von der Common Language Runtime überprüft wird, ist der Modifikator unsafe notwendig. Innerhalb des nachfolgenden Elements kann unsicherer Code wie Zeiger verwendet werden. Zur Verwendung des Modifikators muss zusätzlich die CompilerOption /unsafe verwendet werden.
private und public Die Mitglieder eine Klasse sind standardmÙûig als privat gekennzeichnet. Im folgenden Beispiel ist daher der Zugriff auf die Methode PrivateMethod nicht mðglich. Der externe Zugriff auf die Methode PublicMethod ist hingegen mðglich, da diese explizit als ðffentlich gekennzeichnet ist. Listing 3.14 privatepublic1.cs using System; class Test { public void PublicMethod() { } void PrivateMethod() { } } class ConsoleApp { static void Main() { Console.ReadLine(); } }
66 _______________________________________ 3.5 Deklaration einer neuen Klasse
static Um eine statisches Mitglied einer Klasse zu implementieren, muss dieses mit dem Modifikator static versehen werden. Der Unterschied zwischen dynamischen und statischen Mitgliedern wurde bereits weiter oben ausföhrlich beschrieben. Hier noch einmal ein kleines Beispiel, das den Unterschied deutlich macht. Listing 3.15 static1.cs using System; class Test { public static void StaticMethod() { } public void InstanceMethod() { } } class ConsoleApp { static void Main() { Console.ReadLine(); } }
Der Aufruf der Methode StaticMethod erfolgt öber den Klassennamen: Test.StaticMethod();
Damit auf die dynamische Methode zugegriffen werden kann, muss zunÙchst eine Instanz der Klasse instanziiert werden: Test objTest = new Test(); objTest.InstanceMethod();
3 Klassen & objektorientierte Programmierung – Teil I __________________________67
abstract, override, sealed und virtual Diese Modifikatoren werden in Verbindung mit dem Ableiten oder Vererben von Klassen verwendet. Eine ausföhrliche Beschreibung dieser Techniken finden Sie in den weitergehenden Mðglichkeiten der objektorientierten Programmierung in C# unter der šberschrift ¹Klassen & objektorientierte Programmierung ± Teil IIª.
3.5.7
Konstruktor
Bei der Instanziierung einer Klasse wird automatisch eine Standard-Methode der Klasse aufgerufen. Diese Methode wird in der Regel verwendet, um initielle Einstellungen vorzunehmen, Variablen zu initialisieren und so weiter. Diese Standard-Methode hat den Namen der Klasse und wird Konstruktor genannt. Wenn Ihre Klasse einer Initialisierung bedarf, dann kðnnen Sie jederzeit einen Konstruktor nach dem folgenden Schema implementieren: class Test { public Test() { // Initialisierung der Klasse } }
Ihre wirkliche LeistungsfÙhigkeit entfalten Konstruktoren allerdings erst, wenn sie mit Parametern versehen werden. Sie kðnnen neben dem Standard-Konstruktor beliebig viele weitere Konstruktoren implementieren, die öber jeweils unterschiedliche Parameter verfögen. Man nennt dieses Vorgehen šberladen, eine weitergehende Beschreibung finden Sie unter ¹Klassen & objektorientierte Programmierung ± Teil IIª. Das Schema sieht in diesem Fall wie folgt aus: class Test { public Test() { // Initialisierung der Klasse }
Konstruktor mit Parameter
68 _______________________________________ 3.5 Deklaration einer neuen Klasse
public Test(Datentyp Parameter) { // Initialisierung der Klasse mit Parametern } }
Vielleicht erinnern Sie sich an das Beispiel der Katze, die ihren Namen nicht Ùndern wollte. Hierzu wurde weiter oben eine Eigenschaft mit get und set implementiert, bei der der Aufruf von set nur ein einziges Mal vorgenommen wurde. Dieser Ansatz lÙsst sich mit einem öberladenen Konstruktor sehr viel schðner lðsen, wie das nachfolgende Beispiel zeigt: Dem Konstruktor wird der Name der Katze öbergeben, der anschlieûend der lokalen Mitgliedsvariablen zugewiesen wird. Die Eigenschaft Name wird nur mit get implementiert, so dass ein explizites Setzen des Namens gar nicht erst mðglich ist.
Listing 3.16 class13.cs using System; class Katze { string _Name; public Katze(string Name) { _Name = Name; } public string Name { get { return(_Name); } } } class ConsoleApp { static void Main() { Katze meineKatze = new Katze("Filou");
3 Klassen & objektorientierte Programmierung – Teil I __________________________69
Console.WriteLine("Meine Katze heiût: "); Console.WriteLine(meineKatze.Name + ""); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster zeigt, dass der Parameter des Konstruktors korrekt an die Eigenschaft öbergeben wurde: Meine Katze heiût: Filou
Wie beschrieben, lassen sich beliebig viele verschiedene Konstruktoren implementieren. Die Unterscheidung erfolgt auf Basis einer individuellen Parameter-Sequenz. Die Sequenz för den Standard-Konstruktor sind keine Parameter. Die Sequenz im vorangegangenen Beispiel wÙre ein Parameter vom Typ string. Sie kðnnen der Klasse natörlich weitere Konstruktoren hinzufögen, för die eine andere Sequenz gelten. Der Compiler sucht sich jeweils den Konstruktor heraus, der för die beim Aufruf angegebene Parameter-Sequenz zutrifft. Im folgenden Beispiel wird die Klasse Katze um zwei weitere Konstruktoren ergÙnzt. Listing 3.17 class14.cs using System; class Katze { string _Name; int _Zufriedenheitsindex; public Katze(string Name) { _Name = Name; } public Katze(string Name, int Zufriedenheitsindex) { _Name = Name; this.Zufriedenheitsindex = Zufriedenheitsindex; }
Mehrere Konstruktoren
70 _______________________________________ 3.5 Deklaration einer neuen Klasse
public Katze(int Zufriedenheitsindex) { this.Zufriedenheitsindex = Zufriedenheitsindex; } public string Name { get { return(_Name); } } public int Zufriedenheitsindex { get { return(_Zufriedenheitsindex); } set { _Zufriedenheitsindex = value; } } } class ConsoleApp { static void Main() { Katze meineKatze = new Katze("Filou", 25); Console.WriteLine("Meine Katze heiût: "); Console.WriteLine(meineKatze.Name + ""); Console.WriteLine("Der Zufriedenheitsindex liegt bei "); Console.WriteLine(meineKatze.Zufriedenheitsindex.ToString()); Console.ReadLine(); } }
Die Klasse erlaubt nun neben der Angabe des Namens im Konstruktor auch die šbergabe des initiellen Zufriedenheitsindex. Insgesamt wurden hierzu drei Konstruktoren öbergeben. Einer nur för den Namen, einer nur för den Index und einer för Name und Index. Im Beispiel-Aufruf der Klasse wurde Letzterer verwendet. Die Ausgabe im Konsolenfenster sieht wie folgt aus:
3 Klassen & objektorientierte Programmierung – Teil I __________________________71
Meine Katze heiût: Filou Der Zufriedenheitsindex liegt bei 25
Neben einem oder mehreren Konstruktoren för die Instanziierung einer Klasse steht auch ein statischer Konstruktor zur Verfögung. Dieser wird ausgeföhrt, wenn eine Klasse das erste Mal benutzt wird. Der statische Konstruktor wird parallel zum dynamischen deklariert und öber den Modifikator static gekennzeichnet. Dem statischen Konstruktor kðnnen keine Parameter öbergeben werden. Das nachfolgende Beispiel zeigt einen statischen Konstruktor, der einer ebenfalls statischen Mitgliedsvariable einen zufÙlligen Wert zuweist. Dieser Wert steht öbrigens auch öber mehrere Seiten hinweg zur Verfögung. Dies ist so lange der Fall, bis die Garbage Collection die Information aus dem Speicher entfernt. Listing 3.18 class15.cs using System; class Test { public static int RandomID; static Test() { Random rnd = new Random(); RandomID = rnd.Next(); } } class ConsoleApp { static void Main() { Console.WriteLine("Die Klasse Test hat die RandomID: "); Console.WriteLine(Test.RandomID.ToString() + ""); Console.ReadLine(); } }
Statischer Konstruktor
72 _______________________________________ 3.5 Deklaration einer neuen Klasse
Die Ausgabe im Konsolenfenster ist ganz zufÙllig und sieht beispielsweise wie folgt aus: Die Klasse Test hat die RandomID: 1195392319
3.5.8
Destruktor
Ein Destruktor ist quasi das Gegenstöck zu einem Konstruktor. Es handelt sich um eine private Methode ohne Parameter, die bei der Terminierung einer Klasse aufgerufen wird. Der Destruktor wird för eventuell notwendige AufrÙumarbeiten verwendet, beispielsweise zum Lðschen von temporÙr angelegten Dateien, etc. Die Deklaration erfolgt Ùhnlich dem Konstruktor entsprechend dem folgenden Schema: ~Klassenname() { // Befehlszeilen }
Es wird also auch hier der Name der Klasse för die Benennung der Methode verwendet, allerdings diesmal mit einer Tilde ~ als PrÙfix zur Kennzeichnung des Destruktors. Beachten Sie, dass jede Klasse nur einen Destruktor implementieren und dieser nie Parameter enthalten kann. Das folgende Beispiel implementiert auf Basis der Klasse Katze eine statische, ðffentliche Variable. Diese wird durch den Konstruktor der Klasse inkrementiert und durch den Desktrukor dekrementiert. Auf diese Weise kann öber die Eigenschaft die Anzahl der instanziierten Klassen abgefragt werden und so in diesem Beispiel die Anzahl der Katzen. Listing 3.19 class16.cs using System; class Katze { public static int Anzahl = 0; public string Name; public Katze() { Anzahl++; }
3 Klassen & objektorientierte Programmierung – Teil I __________________________73
~Katze() { Anzahl--; } } class ConsoleApp { static void Main() { Katze meineKatze1 = new Katze(); Katze meineKatze2 = new Katze(); Console.WriteLine("Ich habe insgesamt "); Console.WriteLine(Katze.Anzahl.ToString() + " Katzen"); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Ich habe insgesamt 2 Katzen
Bereits bei der ErklÙrung zur Terminierung einer Klasse wurde auf die Garbage Collection hingewiesen. Diese öbernimmt das AufrÙumen des Speichers und ist in diesem Sinne verantwortlich för den Aufruf des Destruktors. Dieser wird also erst dann aufgerufen, wenn die Gargabe Collection den Speicher der Klasse freigibt. Dies ist typischerweise in Leerlaufzeiten des Rechners oder bei akutem Speicherplatzbedarf der Fall. Das folgende Beispiel demonstriert dies sehr anschaulich. Listing 3.20 class17.cs // Klassenimplementation analog zu class15.cs ... Katze meineKatze1 = new Katze(); Katze meineKatze2 = new Katze(); Katze meineKatze3 = new Katze(); Console.WriteLine("Ich habe insgesamt "); Console.WriteLine(Katze.Anzahl.ToString() + " Katzen"); meineKatze3 = null;
Garbage Collection
74 _______________________________________ 3.5 Deklaration einer neuen Klasse
Console.WriteLine("Ich habe insgesamt "); Console.WriteLine(Katze.Anzahl.ToString() + " Katzen");
Es werden zunÙchst drei Instanzen der Klasse Katze instanzziert und anschlieûend die Eigenschaft Anzahl ausgegeben. Nun wird eine der Instanzen terminiert. Man wörde erwarten, dass sich die Eigenschaft Anzahl nun entsprechend verkleinert, doch ist das nicht der Fall, da die Garbage Collection noch keine Zeit hatte, den Speicher aufzurÙumen. Die Anzahl bleibt daher unverÙndert, wie die Ausgabe im Konsolenfenster zeigt: Ich habe insgesamt 3 Katzen Ich habe insgesamt 3 Katzen
Dieser Zustand kann öbrigens lÙnger andauern. Sie sollten daher unbedingt darauf achten, dass Sie vorsichtig mit dem Destruktor umgehen und diesem keine zeitkritischen Aufgaben öberlassen. Von einer Verwendung als ZÙhler wie in diesem Beispiel sei daher ausdröcklich abgeraten.
3.5.9
Attribute
C# beziehungsweise das .NET Framework bieten mit Attributen eine Mðglichkeit, einer Klassen oder auch nur einzelnen Mitgliedern dieser Klasse standardisierte Eigenschaften mitzugeben. Diese Meta-Daten kðnnen unterschiedlichste Informationen enthalten, die das jeweilige Element in einer bestimmten Hinsicht nÙher beschreiben. Die Angabe dieser Informationen erfolgt öber so genannte Attribute. Attribute werden vor der Deklaration des jeweiligen Elements in eckigen Klammern notiert: [AttributName(Parameter)] class Klassenname { [AttributName(Parameter)] Modifikator Datentyp Methodennamen { // Befehlszeilen } } Reflections
Einsatzgebiete för Attribute gibt es wie Sand am Meer. Diese werden entweder durch das .NET Framework vorgegeben oder individuell angelegt. Attribute kðnnen nicht nur zur Entwicklungszeit im Programmcode hinterlegt werden, sondern auch spÙter öber Reflections (vergleiche gleichnamiges Kapitel weiter unten) angelegt
3 Klassen & objektorientierte Programmierung – Teil I __________________________75
werden. Auch ein lesender Zugriff sowie die Manipulation von Attributen ist öber Reflections mðglich. Einige Beispiele för im .NET Framework fest integrierte Attribute sind das ConditionalAttribute zur bedingten Ausföhrung von Programmcode, das ObsoleteAttribute zur Kennzeichnung von veraltetem Programmcode, das WebServiceAttribute zur Verðffentlichung von Mitgliedern als WebServices und so weiter. Per Definition werden alle Attribute in der Form ¹NameAttributeª benannt. In C# ist allerdings nur die Angabe des Namens, ohne das abschlieûende ¹Attributeª, notwendig. Um einen Programmbereich als veraltet zu kennzeichnen, kðnnen Sie das folgende Beispiel anwenden: Listing 3.21 attribute1.cs using System; class Test { [Obsolete("Veraltete Methode, bitte NeueMethode() verwenden!")] public void AlteMethode() { } public void NeueMethode() { } } class ConsoleApp { static void Main() { Console.ReadLine(); } }
Verwendet man die alte Methode, wörde der Compiler einen Warnungstext ausgeben. Wörde man hingegen den zweiten booleschen Parameter des Attributes mit true öbergeben, wörde der Compiler einen Fehler melden und die Verwendung der neuen Methode erzwingen.
4 Bedingungen
Das if-Konstrukt Das switch-Konstrukt Bedingungsoperator
78 ____________________________________________________________________
Inhaltsübersicht Kapitel 4
4
Bedingungen.......................................................................................................79
4.1 4.2 4.3
Das if-Konstrukt ..................................................................................................79 Das switch-Konstrukt ..........................................................................................82 Bedingungsoperator.............................................................................................86
____________________________________________________________________ 79
4 Bedingungen Nahezu jedes Programm kommt an den Punkt, da es eine Entscheidung treffen muss. Ob es sich um die Auswertung einer Benutzereingabe oder um die Anzahl von EintrÙgen in einer Datenbank handelt. C# stellt zur Erstellung solcher Abfragen oder Bedingungen verschiedene Konstrukte zur Verfögung.
4.1
Das if-Konstrukt
Mit dem if-Konstrukt lassen sich einfache Wenn..Dann-Bedingungen realisieren. Es wird dabei ein boolescher Ausdruck, also die kleinste mðgliche Information ausgewertet: ja oder nein. Die folgenden Zeilen zeigen bereits eine solche Bedingung: Listing 4.1 if1.cs using System; class ConsoleApp { static void Main() { int i = 1; if(i==1) Console.WriteLine("i entspricht 1"); Console.ReadLine(); } }
ZunÙchst wird die Variable i mit dem Wert 1 initialisiert. Nun erfolgt die Abfrage der Bedingung mit Hilfe des Schlösselwortes if(). Die öberpröfende Bedingung wird in Klammern hinter das Schlösselwort gesetzt. Falls die Bedingung zutrifft, wird die anschlieûend angegebene Aktion ausgeföhrt. In diesem Fall wird der Text ¹i entspricht 1ª im Konsolenfenster ausgegeben.
80 ___________________________________________________ 4.1 Das if-Konstrukt
Sollen statt nur einer Aktion gleich mehrere aufgerufen werden, wenn die Bedingung zutrifft, so werden diese Aktionen einfach mit geschweiften Klammern eingeklammert. Listing 4.2 if2.cs using System; class ConsoleApp { static void Main() { int i = 1; if(i==1) { Console.WriteLine("i entspricht 1"); Console.WriteLine(""); Console.WriteLine("Schwups, sind wir in der nÙchsten Zeile"); } Console.ReadLine(); } }
Die Anforderungen an eine Abfrage sind ganz unterschiedlich und hÙufig nicht so simpel, wie in den vorherigen FÙllen. Soll beispielsweise eine alternative Aktion durchgeföhrt werden, falls die Bedingung nicht zutrifft, kann das Schlösselwort else verwendet werden. Dieses wird einfach im Anschluss an das bestehende Konstrukt angeföhrt. if..else Listing 4.3 if3.cs using System; class ConsoleApp { static void Main() { int i = 2; if(i==1) Console.WriteLine("i entspricht 1"); else Console.WriteLine("i entspricht leider nicht 1"); Console.ReadLine(); } }
4 Bedingungen _________________________________________________________81
Die Ausgabe dieses Beispiels wörde ¹i entspricht leider nicht 1ª lauten. Auch hier kðnnen mehrere Aktionen durchgeföhrt werden, indem diese entsprechend eingeklammert werden. Listing 4.4 if4.cs using System; class ConsoleApp { static void Main() { int i = 2; if(i==1) { Console.WriteLine("i entspricht 1"); } else { Console.WriteLine("i entspricht leider nicht 1"); Console.WriteLine(""); Console.WriteLine("Das geht auch bei else"); } Console.ReadLine(); } }
Bisher bezogen sich die Abfragen immer auf eine Bedingung. Es lassen sich aber auch sequenziell mehrere Bedingungen abfragen. Hierzu wird hinter dem else einfach ein neues if() angeföhrt. Beachten Sie hierbei, dass es aus rein logischen Grönden nur ein einziges else ohne zusÙtzliche Angabe einer Bedingung geben kann. if..else if..else Listing 4.5 if5.cs using System; class ConsoleApp { static void Main() { int i = 2; if(i==1) Console.WriteLine("i entspricht 1"); else if(i==2) Console.WriteLine("i entspricht 2");
82 _______________________________________________ 4.2 Das switch-Konstrukt
else Console.WriteLine("i entspricht weder 1 noch 2"); Console.ReadLine(); } }
ZunÙchst erfolgt ein Vergleich der Variable i mit dem Wert 1. Stimmt 1 nicht mit 1 öberein, erfolgt ein Vergleich mit 2. Trifft dies ebenfalls nicht zu, wird schlieûlich die alternative Aktion ausgeföhrt.
4.2
Das switch-Konstrukt
if()..else if()..else-Konstrukte lassen sich beliebig tief verlÙngern. Dies ist aber weder öbersichtlich noch besonders schnell. Insbesondere wenn eine Variable mit mehreren mðglichen Werten verglichen werden soll, bietet sich das switchKonstrukt an. Umgesetzt auf dieses Konstrukt sieht das vorangegangene Beispiel wie folgt aus: case, break und default
Listing 4.6 switch1.cs using System; class ConsoleApp { static void Main() { int i = 2; switch(i) { case 1: Console.WriteLine("i entspricht 1"); break; case 2: Console.WriteLine("i entspricht 2"); break;
4 Bedingungen _________________________________________________________83
default: Console.WriteLine("i entspricht weder 1 noch 2"); break; } Console.ReadLine(); } }
Der Ablauf erscheint bereits auf den ersten Blick öbersichtlicher und bringt dank interner Optimierungsmðglichkeiten wie zum Beispiel Hashtables einen Performance-Gewinn. ZunÙchst wird hinter dem Schlösselwort switch die Variable genannt, die verglichen werden soll. Nun folgen eingeklammert die mðglichen Vergleichswerte, jeweils eingeleitet durch Schlösselwort case. Dahinter folgen die auszuföhrenden Aktionen, die durch das Schlösselwort break abgeschlossen werden mössen. Die optionale Standard-Aktion kann (analog zu else) hinter dem Schlösselwort default folgen. Sofern in einem Block mehrere Aktionen ausgeföhrt werden sollen, mössen diese öbrigens nicht eingeklammert werden. Soll eine identische Aktion bei mehreren mðglichen Vergleichswerten ausgeföhrt werden, so kðnnen die einzelnen case-Anweisungen einfach hintereinander aufgeföhrt werden. Das Schlösselwort break wird dabei weggelassen, so dass das Programm quasi durch die Vergleiche ¹durchfallenª kann. Listing 4.7 switch2.cs using System; class ConsoleApp { static void Main() { int i = 2; switch(i) { case 1: case 2: Console.WriteLine("i entspricht 1 oder 2"); break; default:
Mehrere Fälle
84 _______________________________________________ 4.2 Das switch-Konstrukt
Console.WriteLine("i entspricht weder 1 noch 2"); break; } Console.ReadLine(); } } Sprünge
Alternativ sind innerhalb eines switch-Konstruktes auch absolute Sprönge mðglich. Dabei kann entweder zu einem anderen case-Block oder zum default-Block gesprungen werden. In beiden FÙllen wird die Anweisung goto verwendet (¹Gehe zuª). Listing 4.8 switch3.cs using System; class ConsoleApp { static void Main() { int i = 2; switch(i) { case 1: Console.WriteLine("i entspricht 1 oder 2"); break; case 2: goto case 1; break; case 3: goto default; default: Console.WriteLine("i entspricht weder 1 noch 2"); break; } Console.ReadLine(); } }
Funktionell entspricht dieses Beispiel dem vorangegangenen. Entspricht i 1 oder 2, wird letztlich der case 1 Block ausgeföhrt, in jedem anderen Fall der default-Block.
4 Bedingungen _________________________________________________________85
Sofern i dabei 2 oder 3 entspricht, wird ein kleiner Umweg öber einen separaten Block eingelegt, um von dort zu einem anderen zu springen. Anders als bei C++ kann das switch-Konstrukt in C# auch in Verbindung mit Zeichenketten eingesetzt werden. Im Folgenden wird ein Sprach-Code verglichen und die korrespondierende Sprache im Klartext ausgegeben. Listing 4.9 switch4.cs using System; class ConsoleApp { static void Main() { string sLangCode = "de"; string sLangName; switch(sLangCode) { case "de": sLangName = "Deutsch"; break; case "en": sLangName = "Englisch"; break; default: sLangName = "Unbekannt"; break; } Console.WriteLine("Der Sprachen-Code \"" + sLangCode + "\" entspricht der folgenden Sprache: " + sLangName + "."); Console.ReadLine(); } }
Interessant zu sehen ist hierbei die gelungene Unterstötzung des C# Compilers gegenöber anderen Sprachen. Wörde man beispielsweise die Zuweisung der Variable sLangName in einem beliebigen der drei case-Blðcke entfernen, wörde es zu einem Compiler-Fehler kommen. Es wörde so nÙmlich die Mðglichkeit bestehen, dass die Variable nicht initialisiert ist, wenn sie weiter unten verwendet wird. Durch den zusÙtzlichen default-Block im vorliegenden Beispiel ist dies in jedem Fall gewÙhrleistet. Diese und andere Sicherheiten schötzen den Entwickler
switch mit Zeichenketten
86 ________________________________________________ 4.3 Bedingungsoperator
vor vielen Standard-Fehlern, die bei anderen Sprachen wie C und C++ hÙufig erst wÙhrend der Ausföhrung (beim Kunden?) bemerkt werden.
4.3
Bedingungsoperator
Der Bedingungsoperator ist ein Operator und daher eigentlich besser im Kapitel ¹Operatorenª aufgehoben. Der Operator kann jedoch in einigen FÙllen als Alternative zur if-Bedingung verwendet werden und bedarf daher auch an dieser Stelle der ErwÙhnung. Das Konstrukt arbeitet als Operator ¹inlineª, also innerhalb einer Zeile, und liefert wie eine Funktion einen Wert zuröck. Listing 4.10 Bedingungsoperator1.cs int i = 1; Console.WriteLine("i entspricht " + ((i==1) ? "1" : "nicht 1"));
Das Beispiel gibt im Konsolenfenster den Text ¹i entspricht 1ª aus und entspricht funktionell dem einfachen, weiter oben skizzierten if..else-Beispiel. ZunÙchst werden in Klammern die Bedingungen notiert. Anschlieûend folgt ein Fragezeichen und der Wert, der zuröckgeliefert werden soll, wenn die Bedingung zutrifft. Ist dies nicht der Fall, wird der nach einem Doppelpunkt notierte Wert zuröckgeliefert. Auch wenn der Bedingungsoperator sicherlich die ein oder andere Zeile Tipparbeit spart, ist dieser doch unöbersichtlich und somit schwer zu lesen. Es gibt sicherlich sinnvolle Einsatzgebiete, eine öbermÙûige Benutzung sollte aber zu Gunsten der besseren Lesbarkeit vermieden werden.
5 Schleifen
Die for-Schleife Die while-Schleife Die do-Schleife Die foreach..in-Schleife Die Anweisungen break und continue
88 ____________________________________________________________________
Inhaltsübersicht Kapitel 5
5
Schleifen .............................................................................................................89
5.1 5.2 5.3 5.4 5.5
Die for-Schleife ...................................................................................................89 Die while-Schleife ...............................................................................................91 Die do-Schleife ....................................................................................................92 Die foreach..in-Schleife .......................................................................................92 Die Anweisungen break und continue .................................................................94
____________________________________________________________________ 89
5 Schleifen Schleifen sind ein unverzichtbares Element moderner Programmiersprachen. Sie ermðglichen es, sich wiederholende Aktionen durchzuföhren wÙhrend (und nicht wenn!), eine Bedingung erföllt ist. Schleifen werden im Deutschen hÙufig mit dem Schlösselwort ¹Solangeª öbersetzt.
5.1
Die for-Schleife
Die for-Schleife eignet sich besonders gut zum Durchlaufen von Werten. HÙufig wird die for-Schleife auch als Iteration bezeichnet. Der Iterator ist dabei fast immer ein Integer-Wert, der inkrementiert wird. Dem Schlösselwort for folgen drei Parameter: 1. Initialisierung, 2. Bedingung und 3. Iteration. Die im ersten Parameter initialisierte Variable wird, wie im dritten Parameter beschrieben, iteriert, solange die Bedingung im zweiten Parameter zutrifft. Ein einfaches Beispiel: Listing 5.1 for1.cs using System; class ConsoleApp { static void Main() { for(int i=1; i<=10; i=i+1) Console.WriteLine("i entspricht " + i + ""); Console.ReadLine(); } }
Iterator
90 ____________________________________________________ 5.1 Die for-Schleife
Die Variable i wird mit dem Wert 1 intialisiert und so lange um 1 inkrementiert, wie diese kleiner oder gleich 10 ist. Letztlich wird die Schleife dadurch 10 mal durchlaufen. Das Beispiel gibt folgenden Text im Konsolenfenster aus: i i i i i i i i i i
entspricht entspricht entspricht entspricht entspricht entspricht entspricht entspricht entspricht entspricht
1 2 3 4 5 6 7 8 9 10
Die oben verwendete Schreibweise för den Iterator ist öbrigens absolut unöblich, da C# hier eine sehr schðne Kurform in Form von speziellen Operatoren anbietet. Ein Beispiel hierför sind die Operatoren ++ beziehungsweise --, die eine zuvor notierte Variable um eins inkrementieren beziehungsweise dekrementieren. šblicherweise wörde man das obige Beispiel daher wie folgt notieren. Listing 5.2 for2.cs using System; class ConsoleApp { static void Main() { for(int i=1; i<=10; i++) Console.WriteLine("i entspricht " + i + ""); Console.ReadLine(); } }
SelbstverstÙndlich kðnnen auch mehrere Aktionen innerhalb der Schleife durchgeföhrt werden. Hierzu werden diese nach dem Schlösselwort for geschweift eingeklammert. Listing 5.3 for3.cs using System; class ConsoleApp { static void Main() {
5 Schleifen ____________________________________________________________91
for(int i=1; i<=10; i++) { Console.WriteLine("i entspricht "); Console.WriteLine(i.ToString()); Console.WriteLine(""); } Console.ReadLine(); } }
5.2
Die while-Schleife
While bedeutet öbersetzt ¹so langeª und trifft die deutsche Bezeichnung von Schleifen daher am ehesten. Die Schleife wird ausgeföhrt, solange eine Bedingung zutrifft und verzichtet auf jeglichen zusÙtzlichen Komfort wie eine integrierte Iteration. while lÙsst sich daher universell einsetzen und somit kann auch das Beispiel der einfachen for-Schleife mit while realisiert werden. Listing 5.4 while1.cs using System; class ConsoleApp { static void Main() { int i = 1; while(i<=10) { Console.WriteLine("i entspricht " + i + ""); i++; } Console.ReadLine(); } }
Solange die Variable i einen Wert kleiner oder gleich 10 enthÙlt, wird die Schleife ausgeföhrt. Innerhalb dieser wird dann erneut der ++ Operator zur Inkrementierung der Variable verwendet.
92 ____________________________________________________ 5.3 Die do-Schleife
5.3
Die do-Schleife
Die do-Schleife arbeitet analog zu while, allerdings wird die Bedingung hierbei am Ende der Schleife ausgewertet. Infolgedessen wird diese Schleife immer mindestens einmal durchlaufen, egal ob die Bedingung zutrifft oder nicht. Umgesetzt sÙhe das vorangegangene Beispiel wie folgt aus. Listing 5.5 do1.cs using System; class ConsoleApp { static void Main() { int i = 1; do { Console.WriteLine("i entspricht " + i + ""); i++; } while(i<=10); Console.ReadLine(); } }
Der Unterschied wird erst richtig auffÙllig, wenn man die Variable i mit dem Wert 11 initialisiert. Die while-Schleife (vergleiche while2.cs) wörde in diesem Fall keinerlei Ausgaben erzeugen, da die Bedingung (<=10) nicht zutrifft. Die doSchleife hingegen hat folgendes Ergebnis. i entspricht 11
5.4
Die foreach..in-Schleife
Die foreach..in-Schleife wird zum Durchlaufen aller Elemente einer Kollektion oder Arrays verwendet. Die deutsche šbersetzung bringt es auf den Punkt: ¹... för jedes [Element] in ...ª Auf diese Weise lassen sich sehr viele unterschiedliche Arten Kollektionen ansprechen, doch nicht alle. Der Grund hierför ist, dass diese Kollektionen bestimmte Schnittstellen unterstötzen mössen, die von der foreach..inSchleife verwendet werden. Mehr zu Kollektionen und Arrays finden Sie im Abschnitt ¹Arraysª im Kapitel ¹Erweiterte Datentypenª.
5 Schleifen ____________________________________________________________93
Listing 5.6 foreach1.cs using System; class ConsoleApp { static void Main() { string[] sArray = new string[10]; for(int i=0; i<sArray.Length; i++) sArray[i] = "Array-Element Nummer " + (i+1); foreach(string s in sArray) Console.WriteLine(s + ""); Console.ReadLine(); } }
Die Ausgabe dieses Beispieles im Konsolenfenster sieht wie folgt aus: Array-Element Array-Element Array-Element Array-Element Array-Element Array-Element Array-Element Array-Element Array-Element Array-Element
Nummer Nummer Nummer Nummer Nummer Nummer Nummer Nummer Nummer Nummer
1 2 3 4 5 6 7 8 9 10
Das Beispiel definiert zunÙchst ein neues Zeichenfolgen-Array mit 10 Elementen. Mit Hilfe einer normalen for-Schleife wird jedes Element des Arrays mit einer Zeichenkette geföllt. Anschlieûend wird eine foreach..in-Schleife verwendet, um den Inhalt wieder auszulesen und im Konsolenfenster auszugeben. Es wird dabei der Unterschied zwischen der for- und der foreach..in-Schleife deutlich. Benðtigt man im ersten Fall noch genaue Informationen öber den Aufbau, die Zugriffsmðglichkeit und die Dimension, sind diese Angaben im zweiten Fall nicht mehr notwendig. Auch kann auf die zusÙtzliche Definition einer Index-Variable (i) verzichtet werden. Die foreach..in-Schleife ist somit komfortabler und öbersichtlicher zu lesen. SelbstverstÙndlich ist wie gewohnt durch Einklammerung die Ausföhrung mehrerer Aktionen problemlos mðglich.
94 __________________________________ 5.5 Die Anweisungen break und continue
Viele vom .NET Framework zur Verfögung gestellten Informationen werden in Kollektionen angeboten und ermðglichen somit den Zugriff öber eine foreach..inSchleife. Die folgenden Zeilen demonstrieren die Ausgabe einer zuvor erstellten Hasttable öber eine foreach..Schleife. Listing 5.7 foreach2.cs using System; using System.Collections; class ConsoleApp { static void Main() { Hashtable ht = new Hashtable(); ht.Add("Wort 1", "Hallo"); ht.Add("Wort 2", "Welt"); ht.Add("Wort 3", "!"); foreach(string s in ht.Keys) Console.WriteLine(s + " = " + ht[s] + ""); Console.ReadLine(); } }
5.5
Die Anweisungen break und continue
Die FlexibilitÙt der vorgestellten Schleifen wird durch zwei zusÙtzliche Schlösselwðrter erweitert, die in jeder Schleifenart verwendet werden kðnnen. Die Anweisung break dient zum vorzeitigen, vollstÙndigen Verlassen einer Schleife. In der Regel steht der Anweisung eine Bedingung vor. break Listing 5.8 break1.cs using System; class ConsoleApp { static void Main() { for(int i=1; i<=10; i++) { if(i==4) break; Console.WriteLine("i entspricht " + i + "");
5 Schleifen ____________________________________________________________95
} Console.ReadLine(); } }
Bei jedem Durchlauf der Schleife wird der Inhalt der Variablen i verglichen. Stimmt diese mit 4 öberein, wird die Anweisung break aufgerufen. Das Ergebnis im Konsolenfenster sieht daher wie folgt aus. i entspricht 1 i entspricht 2 i entspricht 3
…hnlich wie die break-Anweisung arbeitet auch continue. Allerdings wird bei dieser Anweisung lediglich die aktuelle Iteration unterbrochen und mit der folgenden fortgefahren. continue Listing 5.9 continue1.cs using System; class ConsoleApp { static void Main() { for(int i=1; i<=10; i++) { if(i%2==0) continue; Console.WriteLine("i entspricht " + i + ""); } Console.ReadLine(); } }
Wie kðnnte wohl die Ausgabe dieser Schleife aussehen? Der Operator % berechnet den Restbetrag (Modulo) einer Division. i%2==0 trifft also för alle geraden Werte zu. Das Ergebnis im Konsolenfenster ist daher folgerichtig auf die Ausgabe der ungeraden Werte beschrÙnkt. i i i i i
entspricht entspricht entspricht entspricht entspricht
1 3 5 7 9
6 Sprunganweisungen
break continue goto return throw
98 ____________________________________________________________________
Inhaltsübersicht Kapitel 6
6
Sprunganweisungen...........................................................................................99
6.1 6.2 6.3 6.4 6.5
break ....................................................................................................................99 continue ...............................................................................................................99 goto....................................................................................................................100 return .................................................................................................................101 throw..................................................................................................................102
____________________________________________________________________ 99
6 Sprunganweisungen C# kennt fönf verschiedene Anweisungen, die einen Sprung der Programmausföhrung vom aktuellen zu einem zu definierenden Standpunkt erlauben. Die meisten Sprunganweisungen wurden bereits im Verlaufe des Buches im jeweiligen Kontext angesprochen. Diese šbersicht fasst sie noch einmal zusammen.
6.1
break
Die Sprunganweisung break wird verwendet, um die Durchföhrung von Schleifen zu unterbrechen. Die Schleife wird dabei unabhÙngig vom aktuellen Status komplett abgebrochen und die Programmausföhrung beim ersten Befehl nach der Schleife fortgesetzt. Ein Beispiel hierzu finden Sie in ¹Die Anweisungen break und continueª. Ein zweites Einsatzgebiet der Sprunganweisung break ist das switch-Konstrukt. Hierbei muss för jeden mðglichen Vergleichsfall (Schlösselwort case) die breakAnweisung notiert werden. Ein Beispiel hierzu finden Sie in ¹Das switchKonstruktª.
6.2
continue
Auch die continue-Anweisung wird innerhalb von Schleifen verwendet. Im Gegensatz zur break-Anweisung wird die Schleife jedoch nicht komplett verlassen, sondern lediglich der aktuelle Durchlauf beendet und mit dem nÙchsten fortgesetzt. Ein Beispiel hierzu finden Sie ebenfalls in ¹Die Anweisungen break und continueª.
100 ____________________________________________________________ 6.3 goto
6.3
goto
goto erlaubt den direkten Sprung an eine notierte Position. Diese Position, genannt
Label, wird an einer beliebigen Stelle im gleichen Geltungsbereich gefolgt von einem Doppelpunkt notiert. Die Anweisung goto gefolgt vom Namen der Position springt die Position an. Listing 6.1 goto1.cs using System; class ConsoleApp { static void Main() { goto meinlabel; Console.WriteLine("Dieser Text wird niemals ausgegeben"); meinlabel: Console.WriteLine("Dieser schon :-)"); Console.ReadLine(); } }
Zu beachten ist dabei, dass sich beide Anweisungen innerhalb eines Göltigkeitsbereiches befinden. Man kann beispielsweise nicht in eine Schleife oder Bedingung hinein springen. Listing 6.2 goto2.cs using System; class ConsoleApp { static void Main() { goto meinlabel; Console.WriteLine("Dieser Text wird niemals ausgegeben"); for(int i=1; i<=10; i++) { meinlabel: Console.WriteLine("Dieser schon :-)"); }
6 Sprunganweisungen __________________________________________________101
Console.ReadLine(); } }
Dieses Beispiel wird mit einem Fehler quittiert, da die Position ¹meinlabelª nicht im Göltigkeitsbereich der goto-Anweisung gefunden werden kann.
Abbildung 6.1 Ein Sprung in einen anderen Gültigkeitsbereich quittiert der C#-Compiler mit einer Fehlermeldung – Recht hat er.
Eine weitere Mðglichkeit zur Verwendung von goto bietet das switch-Konstrukt. Hier kann zu einem angegebenen Vergleichsfall (case) oder dem Standardfall (default) gesprungen werden. Ein Beispiel hierzu finden Sie in ¹Das switchKonstruktª. Von der Verwendung der goto-Anweisung sei an dieser Stelle ausdröcklich gewarnt. Man sollte diese nur in grðûter Not und in AusnahmefÙllen benutzen. Ansonsten kann es sehr schnell zu unöbersichtlichem Code kommen, der oft als Spagetti-Code bezeichnet wird. Folgt man streng den Regeln der objektorientierten Programmierung, ist der Befehl ohnehin öberflössig.
6.4
return
Mit return kann die Ausföhrung der aktuellen Prozedur oder Funktion unterbrochen und zur Position des Aufrufes zuröckgekehrt werden. Bei Funktionen wird öber diese Anweisung zudem der Röckgabewert gemeldet. Der Aufruf der Anweisung ist daher zwingend erforderlich.
102 ___________________________________________________________ 6.5 throw
Das folgende Beispiel enthÙlt eine Prozedur Write, die eine öbergebene Zeichenkette an die Methode WriteLine des Console-Objektes weitergibt. För den Fall, dass die öbergebene Zeichenkette leer, also die LÙnge gleich 0 ist, wird die Abarbeitung der Prozedur abgebrochen. private void Write(string text) { if(text.Length == 0 ) return; Console.WriteLine(text); } ... Write("hallo welt"); Write("");
6.5
throw
Diese Anweisung ¹wirftª eine Ausnahme, also einen Fehler, und springt so weit im Programm nach oben, bis eine entsprechende Ausnahmebehandlung gefunden wird. Eine ausföhrliche Beschreibung der Anweisung finden Sie im Kapitel ¹Fehlerbehandlungenª.
7 Operatoren
Mathematische Standard-Operatoren Mathematische Kurz-Operatoren Zuweisungsoperatoren Noch mehr Mathematik mit der Klasse System.Math Logische Operatoren Typenoperatoren
104 ___________________________________________________________________
Inhaltsübersicht Kapitel 7
7
Operatoren .......................................................................................................105
7.1 7.2 7.3 7.4 7.5 7.5.1 7.5.2 7.5.3 7.5.4 7.6 7.6.1 7.6.2 7.6.3 7.6.4
Mathematische Standard-Operatoren.................................................................105 Mathematische Kurz-Operatoren.......................................................................108 Zuweisungsoperatoren.......................................................................................110 Noch mehr Mathematik mit der Klasse System.Math .......................................112 Logische Operatoren..........................................................................................115 Vergleichsoperatoren.........................................................................................115 Verknöpfungsoperatoren ...................................................................................116 Negationsoperator..............................................................................................118 Bitweise (binÙre) Operatoren.............................................................................118 Typenoperatoren................................................................................................123 typeof.................................................................................................................123 is ........................................................................................................................126 as........................................................................................................................127 is versus as.........................................................................................................128
___________________________________________________________________ 105
7 Operatoren Der Syntax der C# Operatoren richtet sich ganz nach C++. Wer in dieser Sprache bereits Erfahrungen gesammelt hat, kann dieses Unterkapitel daher getrost öberspringen. Alle anderen, gerade auch Visual Basic± oder VBScript-Entwickler, werden hier einige interessante, weil komfortable Konstrukte entdecken.
7.1
Mathematische Standard-Operatoren
Natörlich kann C# rechnen und beherrscht alle Grundrechenarten aus dem Effeff. Die Addition zweier Werte erfolgt mit dem Plus-Zeichen ¹+ª, die Subtraktion mit ¹±ª, die Multiplikation mit ¹*ª und die Division mit ¹/ª. Ein einfache Addition mit dem Plus-Operator sieht zum Beispiel wie folgt aus: Addition Listing 7.1 addition.cs using System; class ConsoleApp { static void Main() { int n1 = 1; int n2 = 2; int n3; n3 = n1+n2; Console.WriteLine("Das Ergebnis lautet "); Console.WriteLine(n3.ToString()); Console.ReadLine(); } }
106 _________________________________ 7.1 Mathematische Standard-Operatoren
Das Ergebnis dieses Beispiels ist ± natörlich ± 3. Die drei anderen Grundrechenarten lassen sich analog verwenden. Bei der Division muss beachtet werden, dass bei Nachkommastellen der jeweils genaueste Datentyp för die Berechnung und das Ergebnis verwendet wird. Division Listing 7.2 division1.cs using System; class ConsoleApp { static void Main() { int n1 = 15; int n2 = 4; Console.WriteLine("Das Ergebnis lautet "); Console.WriteLine((n1/n2).ToString()); Console.ReadLine(); } }
Das Ergebnis dieses Beispiels ist 3, also die Ganzzahl der Berechnung, da der Datentyp int keine Nachkommastellen unterstötzt. Ganz anders hingegen der Datentyp double. Dementsprechend ist das (mathematisch richtige) Ergebnis der Berechnung in diesem Fall 3,75. Im šbrigen folgt die .NET zu Grunde liegende Common Language Runtime bei der Berechnung von Gleitkommawerten den in IEEE 754 beschriebenen Regeln. Listing 7.3 division2.cs using System; class ConsoleApp { static void Main() { double n1 = 15; int n2 = 4; Console.WriteLine("Das Ergebnis lautet "); Console.WriteLine((n1/n2).ToString()); Console.ReadLine(); } }
7 Operatoren _________________________________________________________107
SelbstverstÙndlich werden auch mathematische Regelungen wie die Punkt-vorStrich-Rechnung sowie die Klammerungen unterstötzt. Es werden hierzu die normalen Klammern ¹(ª und ¹)ª verwendet. Eine etwas ¹komplexereª Berechnung kðnnte beispielsweise so aussehen: Listing 7.4 komplex1.cs using System; class ConsoleApp { static void Main() { int n = 1 + 3 * 5 - ((2 + 8) / 2); Console.WriteLine("Das Ergebnis lautet "); Console.WriteLine(n.ToString()); Console.ReadLine(); } }
Welches Ergebnis erzielen Sie aus dieser Berechnung? C# kennt sich aus in der Mathematik und hÙlt 11 för die richtige Antwort. Neben den regulÙren Grundrechenarten kennt C# auch die Berechnung des Restbetrages einer Division. Man nennt diese Berechnung Modulo. Als OperatorZeichen wird hierzu ¹%ª verwendet Ein Beispiel: Modulo Listing 7.5 modulo1.cs using System; class ConsoleApp { static void Main() { int n1 = 123; int n2 = 10; int n3; n3 = n1%n2; Console.WriteLine("Der Restbetrag lautet "); Console.WriteLine(n3.ToString()); Console.ReadLine(); } }
108 _____________________________________ 7.2 Mathematische Kurz-Operatoren
Das Ergebnis der Modulo-Berechnung ist 3, denn 123 dividiert durch 10 ergibt einen nicht teilbaren Restbetrag von 3. Tabelle 7.1 Die mathematischen Standard-Operatoren in der Übersicht Operator
Beschreibung
+
Addition
–
Subtraktion
*
Multiplikation
/
Division
%
Modulo (Restbetrag)
7.2
Mathematische Kurz-Operatoren
Mit mathematischen Kurz-Operatoren lassen sich hÙufig benðtigte mathematische Operatoren komfortabel vereinfachen. Wie oft verwenden Sie die Variable i, um diese in einer Schleife zu inkrementieren. Das kðnnte in C# beispielsweise wie folgt aussehen: Listing 7.6 kurzoperatoren1.apsx using System; class ConsoleApp { static void Main() { int i = 0; do { i = i + 1; Console.Write ("i ist gleich "); Console.WriteLine(i.ToString()); } while(i<10); Console.ReadLine(); } }
C# bietet eine wesentlich bequemere Mðglichkeit, eine numerische Variable zu inkrementieren. Jeder Entwickler, dem das Handgelenk schmerzt, wird sich freuen, ein paar Zeichen weniger tippen zu mössen. Folgender Aufruf vereinfacht die Inkrementation:
7 Operatoren _________________________________________________________109
Listing 7.7 kurzoperatoren2.cs
++ Operator
using System; class ConsoleApp { static void Main() { int i = 0; do { i++; Console.WriteLine("i ist gleich "); Console.WriteLine(i.ToString()); } while(i<10); Console.ReadLine(); } }
Das Ergebnis ist identisch. Natörlich kann man analog auch dekrementieren, indem man das ¹++ª durch ¹± ±ª ersetzt; eigentlich ganz logisch, oder? – – Operator Listing 7.8 kurzoperatoren3.cs using System; class ConsoleApp { static void Main() { int i = 10; do { Console.Write ("i ist gleich "); Console.WriteLine(i.ToString()); i--; } while(i<10); Console.ReadLine(); } }
110 _____________________________________________ 7.3 Zuweisungsoperatoren
Tabelle 7.2 Die mathematischen Kurz-Operatoren in der Übersicht Operator
Beschreibung
++
Inkrementiert die zuvor notierte Variable
––
Dekrementiert die zuvor notierte Variable
7.3
Zuweisungsoperatoren
Neben den beiden beschriebenen Kurz-Operatoren kennt C# eine weitere Mðglichkeit, Ihnen Tipparbeit abzunehmen und so wertvolle Zeit zu sparen: Zuweisungsoperatoren. Bei dieser Art von Operatoren wird parallel eine Rechenoperation und eine erneute Zuweisung durchgeföhrt. Das folgende Beispiel zeigt die Verwendung im Vergleich zu regulÙren Operatoren. += Listing 7.9 zuweisungsoperatoren1.cs using System; class ConsoleApp { static void Main() { int i1 = 3; int i2 = 3; i1 = i1 + 4; Console.WriteLine("i1 = i1 + 4 ergibt " + i1.ToString()); i2 += 4; Console.WriteLine("i2 += 4 ergibt " + i2.ToString()); Console.ReadLine(); } }
Diese Art von Operator existiert för alle vier Grundrechenarten sowie die Berechnung des Restbetrages, Modulo. Das Vorgehen ist dabei jeweils identisch. Der Wert der links vor dem Operator notierten Variable wird mit dem rechts davon notierten Wert berechnet und dann wieder der ersten Variable zugewiesen. Eine Zusammenstellung der weiteren Zuweisungsoperatoren finden Sie im folgenden Beispiel.
7 Operatoren _________________________________________________________111
Listing 7.10 zuweisungsoperatoren2.cs using System; class ConsoleApp { static void Main() { int i1 = 3; int i2 = 6; int i3 = 9; int i4 = 12; i1 -= 2; Console.WriteLine("i1 -= 2 ergibt " + i1.ToString()); Console.WriteLine(""); i2 *= 3; Console.WriteLine("i2 *= 3 ergibt " + i2.ToString()); Console.WriteLine(""); i3 /= 3; Console.WriteLine("i3 /= 3 ergibt " + i3.ToString()); Console.WriteLine(""); i4 %= 5; Console.WriteLine("i4 %= 5 ergibt " + i4.ToString()); Console.WriteLine(""); Console.ReadLine(); } }
Natörlich kðnnen Sie rechts statt einem Wert auch eine zweite Variable oder gar das Ergebnis einer weiteren Berechnung oder Funktion notieren. Lediglich links vom Operator muss immer eine Variable, ein Indexer oder …hnliches notiert werden, um das Ergebnis der Operation aufzunehmen. Alles andere weist der C#-Compiler mit einer Fehlermeldung zuröck. So auch das folgende Beispiel: Listing 7.11 zuweisungsoperator3.cs 3 += 3;
–=
112 _____________________ 7.4 Noch mehr Mathematik mit der Klasse System.Math
Die Fehlermeldung lautet in diesem Fall ganz richtig: ¹Compilerfehlermeldung: CS0131: Die linke Seite einer Zuweisung muss eine Variable, eine Eigenschaft oder ein Indexer sein.ª Tabelle 7.3 Die mathematischen Zuweisungsoperatoren in der Übersicht Operator
Beschreibung
+=
Addiert die linke Variable mit dem rechten Wert und weist das Ergebnis der Variablen zu.
–=
Subtrahiert den rechten Wert von der linken Variable und weist das Ergebnis der Variablen zu.
*=
Multipliziert die linke Variable mit dem rechten Wert und weist das Ergebnis der Variablen zu.
/=
Dividiert die linke Variable durch den rechten Wert und weist das Ergebnis der Variablen zu.
%=
Dividiert die linke Variable durch den rechten Wert und weist den Restbetrag der Variablen zu.
7.4
Noch mehr Mathematik mit der Klasse System.Math
Die im Namespace System verankerte Klasse Math bietet zwar keine Operatoren, kann daför aber mit vielen wichtigen mathematischen Methoden aufwarten und ist daher in diesem Kontext durchaus erwÙhnenswert. In anderen Umgebungen wÙren die Methoden dieser Klasse vermutlich als Funktionen in der Sprache selbst verankert. Nicht so bei C# respektive .NET. Hier sind FunktionalitÙt und Sprache streng getrennt. Sie kðnnen daher die hier beschriebene Klasse auch ohne weiteres in Visual Basic.NET, JScript 7 oder auch jeder anderen .NET-Sprache verwenden, die uns die Hersteller bescheren werden. Alle Methoden der Klasse sind statisch implementiert. Sie mössen die Klasse daher nicht instanzzieren, sondern kðnnen die Methode direkt aufrufen. Viele der Methoden sind öberladen und passen sich so flexibel Ihren Bedörfnissen und den öbergebenen Daten an. Die folgende Tabelle enthÙlt eine šbersicht der unterstötzten Methoden. Tabelle 7.4 Die Methoden der Klasse Math in der Übersicht Methode
Beschreibung
Abs
Berechnet den absoluten Betrag eines übergebenen Wertes
Acos
Berechnet den Arcuscosinus eines übergebenen Cosinus-Wertes
Asis
Berechnet den Arcussinus eines übergebenen Sinus-Wertes
Atan
Berechnet den Arcustangens eines übergebenen Tangens-Wertes
7 Operatoren _________________________________________________________113
Methode
Beschreibung
Atan2
Berechnet den Arcustangens des Quotienten zweier übergebener Werte (x, y)
Ceiling
Rundet den übergebenen Wert zur nächsthöheren oder gleichen Ganzzahl auf
Cos
Berechnet den Cosinus
Cosh
Berechnet den hyperbolischen Cosinus
Exp
Berechnet e hoch den übergebenen Wert
Floor
Das Gegenstück zu Ceiling rundet den übergebenen Wert zur nächstkleineren oder gleichen Ganzzahl ab.
Log
Berechnet den Logarithmus eines übergebenen Wertes
Log10
Berechnet den Logarithmus der Basis 10 eines übergebenen Wertes
Max
Gibt den größeren zweier übergebener Werte zurück
Min
Gibt den kleineneren zweier übergebener Werte zurück
Pow
Potenziert einen übergebenen Wert mit einem zweiten, x hoch y
Round
Rundet einen Wert mathematisch zur nächsten Ganzzahl auf oder ab. Über einen optionalen Parameter kann die Anzahl der zu berechnenden Nachkommastellen angegeben werden.
Sign
Liefert einen Hinweis auf das Vorzeichen eines übergebenen Wertes, 1 für positive Werte und –1 für negative
Sin
Berechnet den Sinus
Sinh
Berechnet den hyperbolischen Sinus
Sqrt
Berechnet die Wurzel eines übergebenen Wertes
Tan
Berechnet den Tangens
Tanh
Berechnet den hyperbolischen Tangens
Das folgende Beispiel wendet eine kleine Auswahl der angebotenen Methoden in simplen Beispielen an. In der Regel sind die Methoden und deren Parameter selbst erklÙrend. Listing 7.12 math1.cs using System; class ConsoleApp { static void Main() { Console.WriteLine("Was ist kleiner, 6 oder 5? Na klar: "); Console.WriteLine(Math.Min(6, 5).ToString()); Console.WriteLine(""); Console.WriteLine("Und wie ist die gerundete Zahl von 5,577? "); Console.WriteLine(Math.Round(5.577).ToString());
114 _____________________ 7.4 Noch mehr Mathematik mit der Klasse System.Math
Console.WriteLine(""); Console.WriteLine("Ich liebe Wurzelberechung!"); Console.WriteLine("Wie lautet die Wurzel von 169? "); Console.WriteLine(Math.Sqrt(169).ToString()); Console.WriteLine(""); Console.WriteLine("Und wie habe ich das ausgerechnet?"); Console.WriteLine("šber den Umweg 13 hoch 2: "); Console.WriteLine(Math.Pow(13, 2).ToString()); Console.WriteLine(""); Console.ReadLine(); } }
ZusÙtzlich zu den beschriebenen Rechenfunktionen enthÙlt die Klasse auch noch zwei oft verwendete mathematische Werte als Eigenschaften. Es handelt sich um den Exponenten e und die Zahl Pi. Beide Werte kðnnen öber die gleichnamigen Eigenschaften abgefragt werden. Achtung: Notieren Sie die Namen unbedingt komplett in Groûbuchstaben, sonst kann C# nichts damit anfangen. Listing 7.13 math2.cs using System; class ConsoleApp { static void Main() { Console.WriteLine("Wie lautet der Exponent e? "); Console.WriteLine(Math.E.ToString()); Console.WriteLine(""); Console.WriteLine("Und wie sieht es mit Pi aus? "); Console.WriteLine(Math.PI.ToString()); Console.WriteLine(""); Console.ReadLine(); } }
7 Operatoren _________________________________________________________115
7.5
Logische Operatoren
Tief im Inneren kennt Ihr PC nur zwei ZustÙnde: Strom an oder Strom aus. Auf die Programmierung öbertragen, handelt es sich um einen logischen, genannt booleschen, Zustand: ja oder nein. Logische Operatoren basieren auf dieser kleinstmðglichen Information. Die Werte hierför sind ¹trueª und ¹falseª.
7.5.1
Vergleichsoperatoren
Vergleichsoperatoren dienen dem Vergleich zweier links beziehungsweise rechts vom Operator notierter Werte und liefern das Ergebnis des Vergleichs als logischen (booleschen) Wert zuröck. Die folgende Tabelle enthÙlt eine šbersicht der zur Verfögung stehenden Vergleichsoperatoren. Das anschlieûende Beispiel zeigt die Verwendung des Kleiner-gleich-Vergleiches. Tabelle 7.5 Die Vergleichsoperatoren in der Übersicht Operator
Beschreibung
x == y
x gleich y?
x != y
x ungleich y?
x
x kleiner als y?
x>y
x größer als y?
x <= y
x kleiner oder gleich y?
x >= y
x größer oder gleich y? <=
Listing 7.14 vergleich1.cs using System; class ConsoleApp { static void Main() { Console.WriteLine("Ist 2 kleiner-gleich 3? "); if(2<=3) Console.WriteLine("Na klar!"); else Console.WriteLine("Auf keinen Fall!"); Console.WriteLine("Und wie sieht es mit 5 und 1 aus? "); if(5<=1) Console.WriteLine("Na klar!"); else
116 ______________________________________________ 7.5 Logische Operatoren
Console.WriteLine("Auf keinen Fall!"); Console.ReadLine(); } }
Ob und ± wenn ja ± wie die Operatoren verwendet werden kðnnen, hÙngt ganz von den verwendeten Objekttypen ab. Zwei Zahlen werden selbstverstÙndlich mathematisch verglichen. Bei Zeichenketten wird der jeweilige Inhalt verglichen. Bei anderen Verweisobjekten werden die Operatoren der zu Grunde liegenden Klasse aufgerufen. Diese vergleichen allerdings nicht den Inhalt der Objekte, sondern den Verweis. Zwei Objekte mössen daher auf die gleiche Instanz einer Klasse verweisen, damit sie öbereinstimmen. In jedem Fall sind beide Objekte nicht identisch, selbst wenn der jeweilige Inhalt identisch sein sollte. Im zweiten Teil des Kapitels zur objektorientierten Programmierung wird eine Mðglichkeit vorgestellt, eigene Operatoren zu implementieren. För diese gilt die EinschrÙnkung unter UmstÙnden nicht.
7.5.2
Verknüpfungsoperatoren
Operationen lassen sich auch logisch verknöpfen. C# stellt hierzu ein logisches ¹undª sowie ein logisches ¹oderª zur Verfögung. Beide arbeiten ausschlieûlich auf Basis von logischen Operationen, also booleschen Werten. Die Tabelle zeigt beide Operatoren in der šbersicht. Die Listings zeigen verschiedene Verknöpfungen im Beispiel. Tabelle 7.6 Die Verknüpfungsoperatoren in der Übersicht Operator
Beschreibung
&&
Logisches „und“ („and“) der beiden Operanden
||
Logisches „oder“ („or“) der beiden Operanden
&& Listing 7.15 verknüpfungsoperatoren1.cs using System; class ConsoleApp { static void Main() { bool b1 = true; bool b2 = false; if(b1 && b2)
7 Operatoren _________________________________________________________117
Console.WriteLine("b1 und b2 sind beide wahr"); else Console.WriteLine("b1 und/oder b2 sind nicht wahr"); Console.ReadLine(); } }
Da b2 nicht wahr (true) ist, wird der else-Zweig der if-Abfrage durchgeföhrt und ein negativer Text ausgegeben. Anders sieht es im folgenden Beispiel för den Verknöpfungsoperator ¹oderª aus, denn hier muss nur eine der beiden Variablen wahr sein. Entsprechend trifft die if-Abfrage zu und ein positiver Text wird ausgegeben. || Listing 7.16 verknüpfungsoperatoren2.cs using System; class ConsoleApp { static void Main() { bool b1 = true; bool b2 = false; if(b1 || b2) Console.WriteLine("b1 oder b2 sind wahr"); else Console.WriteLine("b1 und b2 sind nicht wahr"); Console.ReadLine(); } }
Die beiden Verknöpfungsoperatoren weisen im Gegensatz zu den weiter unten beschriebenen bitweisen Operatoren eine gewisse ¹Intelligenzª auf. Ist beim logischen ¹undª der erste Teil bereits Falsch, wird der nachfolgende Operand nicht mehr ausgewertet, da die Verknöpfung ohnehin falsch ergeben wörde. …hnlich verhÙlt es sich beim logischen ¹oderª. Hier wird der zweite Operand nur dann ausgewertet, wenn der erste falsch ist, da die Verknöpfung in anderen immer wahr sein muss.
118 ______________________________________________ 7.5 Logische Operatoren
7.5.3
Negationsoperator
Der Negationsoperator negiert einen logischen Wert. Aus ja wird also nein und umgekehrt. Dieser Operator kommt implizit bereits beim ungleich-Vergleich weiter oben zum Einsatz und wird mit dem Ausrufezeichen ¹!ª notiert. ! Listing 7.17 negation1.cs using System; class ConsoleApp { static void Main() { if(!true) Console.WriteLine("Ja!"); else Console.WriteLine("Nein!"); Console.ReadLine(); } }
7.5.4
Bitweise (binäre) Operatoren
Bitweise Operatoren erlauben den Zugriff sowie die Manipulation von Werten auf Bit-Ebene. Es kðnnen dabei sowohl numerische als auch logische Datentypen angegeben werden. Es existieren fönf verschiedene Operatoren, die in der folgenden Tabelle aufgelistet werden. Tabelle 7.7 Bitweise Operatoren in der Übersicht Operator
Beschreibung
&
Bitweise „und“-Verknüpfung der beiden Operanden
|
Bitweise „oder“-Verknüpfung der beiden Operanden
^
Bitweise exklusive „oder“-Verknüpfung (XOR) der beiden Operanden
>>
Verschieben aller Bits des Operanden um die angegebene Anzahl nach rechts
<<
Verschieben aller Bits des Operanden um die angegebene Anzahl nach links
Die ersten beiden Operanden sind schnell erklÙrt, denn diese sind den weiter oben bereits beschriebenen logischen Verknöpfungsoperatoren sehr Ùhnlich. Der einzige Unterschied: beide Operatoren arbeiten auf Bit-Ebene, sofern mit numerischen
7 Operatoren _________________________________________________________119
Werte operiert wird. Bei logischen (booleschen) Werten hingegen, stimmt das Ergebnis mit den logischen Verknöpfungsoperatoren öberein. Allerdings wird die Operation hierbei in jedem Fall durchgeföhrt, unabhÙngig vom Wert des ersten Operanden. Die bitweise oder binÙre Verknöpfung zweier binÙrer Operanden liefert einen dritten binÙren Wert zuröck, bei dem diejenigen Bits gesetzt werden, die bei beiden Operanden gesetzt sind. Das folgende Beispiel erlÙutert das Vorgehen: & =
0 1 0 1 1 0 0 1 1 0 0 0 1 0 1 1 0 1 0 0 0 1 0 0 0 0 1
Im Dezimalsystem entsprechen diese binÙren Werte 179 und 45. Das Ergebnis ist 33. Das folgende Beispiel zeigt die Verknöpfung im Einsatz. Binäres Und Listing 7.18 bitweise1.apsx using System; class ConsoleApp { static void Main() { int n1 = 179; int n2 = 45; Console.WriteLine("179 & 45 ergibt "); Console.WriteLine((n1 & n2).ToString()); Console.ReadLine(); } }
Mðchte man feststellen, ob ein bestimmtes Bit eines Wertes gesetzt ist, dann muss man nur die beiden Werte miteinander bitweise mit dem ¹undª-Operator verknöpfen. Falls das Bit gesetzt ist, muss das Ergebnis dem Vergleich entsprechen. Mðchte man beispielsweise ermitteln, ob das erste Bit gesetzt ist, reicht eine Verknöpfung mit der Dezimalzahl 1. Liefert diese Verknöpfung erneut den Dezimalwert 1 zuröck, dann ist das erste Bit gesetzt, ansonsten nicht.
Listing 7.19 bitweise2.cs using System; class ConsoleApp {
120 ______________________________________________ 7.5 Logische Operatoren
static void Main() { int n1 = 15; // = binÙr 1111 Console.WriteLine("Das erste Bit des Dezimalwertes "); Console.WriteLine(n1.ToString() + " ist "); if((n1 & 1) == 1) Console.WriteLine("gesetzt!"); else Console.WriteLine("nicht gesetzt!"); Console.ReadLine(); } }
Analog zu der bitweisen ¹undª-Verknöpfung arbeitet die ¹oderª-Verknöpfung. Allerdings wird hier ein Wert zuröckgeliefert, bei dem alle Bits gesetzt sind, die bei mindestens einem der beiden Operanden (oder beiden!) gesetzt sind. Das vorangegangene Beispiel zeigt sich wie folgt: | =
0 1 0 1 1 0 0 1 1 0 0 0 1 0 1 1 0 1 0 1 0 1 1 1 1 1 1
Das dezimale Ergebnis der Operation ist in diesem Fall 191. Binäres Oder Listing 7.20 bitweise2.cs using System; class ConsoleApp { static void Main() { int n1 = 179; int n2 = 45; Console.WriteLine("179 | 45 ergibt "); Console.WriteLine((n1 | n2).ToString()); Console.ReadLine(); } }
Die exklusive ¹oderª-Verknöpfung unterscheidet sich ganz entscheidend von der regulÙren (nicht exklusiven) ¹oderª-Verknöpfung. Diese liefert einen Wert zuröck, bei dem alle Bits gesetzt sind, die bei einem der beiden Operanden und nur bei
7 Operatoren _________________________________________________________121
einem gesetzt sind. Ist ein Bit bei beiden Operanden gesetzt, wird dieses Bit im Ergebnis nicht gesetzt. Auch dieser Operator lÙsst sich mit booleschen Werten verwenden. Die beiden folgenden Beispiele zeigen daher sehr deutlich den Unterschied zwischen den beiden ¹oderª-Verknöpfungen. Listing 7.21 bitweise4.cs using System; class ConsoleApp { static void Main() { bool b1 = true; bool b2 = true; if(b1 | b2) Console.WriteLine("b1 | b2 liefert true"); else Console.WriteLine("b1 | b2 liefert false"); Console.ReadLine(); } }
Die Operation liefert wahr zuröck, denn mindestens einer der beiden Operanden (oder wie in diesem Fall beide) sind wahr. Ganz anders sieht es im zweiten Beispiel aus, das falsch zuröckliefert, da mehr als einer der beiden Operanden wahr ist. Exklusives Oder Listing 7.22 bitweise5.cs using System; class ConsoleApp { static void Main() { bool b1 = true; bool b2 = true; if(b1 ^ b2) Console.WriteLine("b1 ^ b2 liefert true"); else Console.WriteLine("b1 ^ b2 liefert false"); Console.ReadLine(); } }
122 ______________________________________________ 7.5 Logische Operatoren
Shiften mit << und >>
Zu guter Letzt bietet C# noch zwei weitere Verknöpfungsoperatoren an. Das Verschieben (genannt ¹Shiftenª) aller Bits eines Wertes um einen angegebenen Wert nach rechts oder links. Die Operatoren werden als ¹>>ª beziehungsweise ¹<<ª notiert. Anders als man annehmen mag, rotieren die Bits nicht. Bei jeder Operation fÙllt daher auf der Seite, in die man wechselt, ein Bit heraus. Auf der gegenöberliegenden Seite wird ein Bit nachgeschoben. Dies Bit orientiert sich am Vorzeichen des Wertes. Ist dieser positiv, wird eine 0 nachgeschoben, ansonsten eine 1. Die bitweise Verschiebung lÙsst sich sehr gut mit einem oft verwendeten Beispiel veranschaulichen. Dabei werden alle Bits eines dezimalern Wertes (int) in einer Schleife jeweils um ein Bit nach rechts verschoben. Dann wird gepröft, ob das erste Bit von rechts gesetzt ist. Auf diese Weise lÙsst sich eine Zeichenkette zusammensetzen, die den korrespondierenden BinÙrwert enthÙlt. Listing 7.23 bitweise6.cs using System; class ConsoleApp { static void Main() { const int ndecstart = 24540545; int ndecvalue = ndecstart; string sbinvalue = ""; for(int i=1; i<=32; i++) { if((ndecvalue & 1) == 1) sbinvalue = "1" + sbinvalue; else sbinvalue = "0" + sbinvalue; ndecvalue = ndecvalue >> 1; } Console.WriteLine("Der Dezimalwert "); Console.WriteLine(ndecstart.ToString()); Console.WriteLine(" entspricht dem BinÙrwert "); Console.WriteLine(sbinvalue); Console.ReadLine(); } }
7 Operatoren _________________________________________________________123
Das Beispiel liefert för den angegebenen dezimalen Wert den korrespondierenden BinÙrwert. Die Ausgabe sieht wie folgt aus: Der Dezimalwert 24540545 entspricht dem BinÙrwert 00000001011101100111010110000001
7.6 7.6.1
Typenoperatoren typeof
Der Operator typeof liefert eine Instanz der Klasse System.Type för eine öbergebene Klasse. Die Klasse bietet unter dem Stichwort ¹Reflectionsª Zugriff auf die Typeninformationen eines Objektes, also dessen Methoden, Eigenschaften, Ereignisse, und so weiter. Dieser Zugriff ist Ùuûerst detailliert. Das folgende Beispiel ist ein wenig komplex und greift bereits dem weiter unten folgenden Kapitel ¹Reflectionsª voraus. Die prinzipiellen Mðglichkeiten der Klasse System.Type werden aber bereits beim šberfliegen des Beispiels deutlich. Listing 7.24 typeof1.cs using System; using System.Reflection; class ConsoleApp { static void Main() { Type t = typeof(string); Console.WriteLine("
Methoden der Klasse string
"); MethodInfo[] methods = t.GetMethods(); foreach (MethodInfo method in methods) { Console.WriteLine(method.ToString() + ""); } Console.WriteLine("
Eigenschaften der Klasse string
"); PropertyInfo[] properties = t.GetProperties(); foreach (PropertyInfo property in properties) { Console.WriteLine(property.ToString() + "");
System.Type
124 _________________________________________________ 7.6 Typenoperatoren
} Console.ReadLine(); } }
Was passiert hier? Zuerst wird eine neue Instanz der Klasse System.Type durch Aufruf des typeof-Operators erzeugt. In diesem Fall wird der Datentyp string öbergeben. Die Instanz der System.Type-Klasse bezieht sich also auf diesen Datentyp. šber die Methode GetMethods wird nun ein Array aller Methoden der Klasse erzeugt. Anschlieûend werden diese ausgegeben. Auf die gleiche Art werden nun alle Eigenschaften der Klasse aufgelistet. Wenn man diese Zeilen vor Augen hat, ist es eigentlich gar nicht mehr so schwierig. SelbstverstÙndlich funktioniert das Beispiel auch und liefert die folgenden (gekörzten) Ausgaben. Methoden der Klasse string System.String ToString(System.IFormatProvider) System.TypeCode GetTypeCode() System.Object Clone() Int32 CompareTo(System.Object) Int32 GetHashCode() Boolean Equals(System.Object) System.String ToString() System.String Join(System.String, System.String[]) System.String Join(System.String, System.String[], Int32, Int32) Boolean Equals(System.String) Boolean Equals(System.String, System.String) Boolean op_Equality(System.String, System.String) Boolean op_Inequality(System.String, System.String) Char get_Chars(Int32) Void CopyTo(Int32, Char[], Int32, Int32) Char[] ToCharArray() Char[] ToCharArray(Int32, Int32) Int32 get_Length() System.String[] Split(Char[]) System.String[] Split(Char[], Int32) System.String Substring(Int32) System.String Substring(Int32, Int32) System.String Trim(Char[]) System.String TrimStart(Char[]) System.String TrimEnd(Char[]) ... Eigenschaften der Klasse string Char Chars [Int32] Int32 Length
7 Operatoren _________________________________________________________125
Falls Sie sich wundern, warum eine Methode mehr als einmal auftaucht, beachten Sie bitte die unterschiedliche(n) Parameter(-anzahl). Es handelt sich in diesen FÙllen um öberladene Methoden. Beachten Sie bitte auch, dass dem Operator typeof nur ein Typ öbergeben werden kann, nicht aber eine Instanz dieses Typs. Mðchten Sie den Typ einer bestehenden Instanz erfahren, verwenden Sie einfach die Methode GetType, die von jedem Objekt implementiert wird. Das vorangegangene Beispiel sÙhe in diesem Fall wie folgt aus.
Listing 7.25 typeof2.cs using System; using System.Reflection; class ConsoleApp { static void Main() { string text = "hallo welt"; Type t = text.GetType(); Console.WriteLine("
Methoden der Klasse string
"); MethodInfo[] methods = t.GetMethods(); foreach (MethodInfo method in methods) { Console.WriteLine(method.ToString() + ""); } Console.WriteLine("
Eigenschaften der Klasse string
"); PropertyInfo[] properties = t.GetProperties(); foreach (PropertyInfo property in properties) { Console.WriteLine(property.ToString() + ""); } Console.ReadLine(); } }
126 _________________________________________________ 7.6 Typenoperatoren
7.6.2 Datentyp überprüfen
is
Mit dem is-Operator lÙsst sich abfragen, ob ein Objekt einem angegebenen Datentyp entspricht beziehungsweise eine bestimmte Schnittstelle implementiert. Im praktischen Einsatz wird gerade letztere Mðglichkeit hÙufig benutzt, wenn man ein fremdes Objekt öbergeben bekommt und wissen mðchte, ob die benðtigte Schnittstelle vorhanden ist. Das Beispiel zeigt die šberpröfung. Listing 7.26 is1.cs using System; class ConsoleApp { static string GetString(object obj) { if(obj is string) { string s = (string) obj; return("Das Objekt enthÙlt den String \"" + s + "\""); } else return("Objekt ist kein string"); } static void Main() { string s = "hallo welt"; int n = 123; Console.WriteLine(GetString(s)); Console.WriteLine(GetString(n)); Console.ReadLine(); } }
Im Beispiel wird der Methode GetString ein beliebiges Objekt öbergeben. Die Methode pröft, ob das Objekt vom Typ string ist beziehungsweise von diesem Datentyp abgeleitet wurde. Ist dies der Fall, erfolgt eine Konvertierung in den Typ string und der Inhalt der Zeichenkette wird zuröckgegeben. Ist dies nicht der Fall, wird ein negativer Text zuröckgegeben. Die Ausgabe des Beispiels fÙllt erwartungsgemÙû wie folgt aus. Das Objekt enthÙlt den String "hallo welt" Objekt ist kein string
7 Operatoren _________________________________________________________127
7.6.3
as
Der Operator as ist dem vorangegangenen is sehr Ùhnlich. Allerdings föhrt as eine explizite Konvertierung in den angegebenen Datentyp durch und liefert das konvertierte Objekt zuröck. Ist eine Konvertierung nicht mðglich, da das Objekt den gewönschten Datentyp nicht unterstötzt, gibt der Operator null zuröck. Das vorangegangene Beispiel lÙsst sich mit wenigen Handgriffen auf den asOperator umstellen; das Ergebnis ist identisch. Listing 7.27 as1.cs using System; class ConsoleApp { public static string GetString(object obj) { string s = obj as string; if(s != null) return("Das Objekt enthÙlt den String \"" + s + "\""); else return("Objekt ist kein string"); } static void Main() { string s = "hallo welt"; int n = 123; Console.WriteLine(GetString(s)); Console.WriteLine(GetString(n)); Console.ReadLine(); } }
Datentyp konvertieren
128 _________________________________________________ 7.6 Typenoperatoren
7.6.4
is versus as
Der is-Operator sollte verwendet werden, wenn man lediglich die Information benðtigt, ob das Objekt einem bestimmten Datentyp entspricht oder eine Schnittstelle implementiert. Mðchte man hingegen auf das entsprechend konvertierte Objekt anschlieûend zugreifen, sollte der as-Operator verwendet werden, da auf diese Weise eine nochmalige Konvertierung wegfÙllt. In beiden Beispielen lÙsst sich dieser Vorteil durch den Wegfall der folgenden Zeile deutlich erkennen: ... string s = (string) obj; ...
8 Arbeiten mit Zeichenketten
Der Datentyp string Die Klasse StringBuilder Reguläre Ausdrücke
130 ___________________________________________________________________
Inhaltsübersicht Kapitel 8
8
Arbeiten mit Zeichenketten ............................................................................131
8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.1.5 8.2 8.2.1 8.2.2 8.3 8.3.1 8.3.2 8.3.3 8.3.4
Der Datentyp string ...........................................................................................131 Einfache Zuweisungen.......................................................................................131 Escape-Zeichen..................................................................................................132 Formatierungen..................................................................................................134 String-Eigenschaften .........................................................................................136 Weitere String-Operationen...............................................................................138 Die Klasse StringBuilder ...................................................................................148 StringBuilder-Eigenschaften..............................................................................149 StringBuilder-Methoden ....................................................................................150 RegulÙre Ausdröcke ..........................................................................................152 Der Aufbau von regulÙren Ausdröcken .............................................................153 Die Klasse Regex...............................................................................................157 Die Regex-Optionen ..........................................................................................164 Erweiterte Parsing-Mðglichkeiten .....................................................................166
___________________________________________________________________ 131
8 Arbeiten mit Zeichenketten Als moderne Programmiersprache bietet C# beziehungsweise das .NET Framework umfangreiche Mðglichkeiten zur Anlage und Modifikation von Zeichenketten. Je nachdem, welche Anforderungen an die Verarbeitung gestellt werden, kðnnen unterschiedliche Objekte verwendet werden.
8.1
Der Datentyp string
Der Datentyp string gehðrt zu den Standard-Datentypen. Als Einziger dieser Standard-Datentypen handelt es sich beim string um einen Verweistyp und nicht um einen Wertetyp. Aus diesem Umstand erklÙrt sich die Tatsache, dass ein string in der Grðûe nur durch den zur Verfögung stehenden Arbeitsspeicher beschrÙnkt ist. Der zur Ablage notwendige Speicherplatz wird auf dem Heap alloziert.
8.1.1
Einfache Zuweisungen
Sofern Sie eine Zeichenkette direkt und somit fest (¹hartª) eingeben, wird diese in Anföhrungszeichen notiert. Eine einfache string-Zuweisung sieht daher so aus: string s1 = "Hallo Welt";
Auch Zusammensetzungen sind problemlos mðglich. Hier kðnnen die bekannten Operatoren wie + und += verwendet werden:
Listing 8.1 string2.cs using System; class ConsoleApp { static void Main() { string s1 = "Hallo Welt";
Zeichenketten zusammensetzen
132 _______________________________________________ 8.1 Der Datentyp string
Console.WriteLine(s1); Console.ReadLine(); } }
Wie schaut die Ausgabe dieses Beispiels aus? Natörlich so: Hallo Welt, was ist hier los?
8.1.2
Escape-Zeichen
Bestimmte Sonderzeichen wie Anföhrungszeichen, Backslash und so weiter haben in C# eine bestimmte Sonderbedeutung. Sie tragen die Namen Escape-Zeichen und kðnnen Zeichenketten nicht direkt zugewiesen werden. Es ist eine Maskierung dieser Zeichen mit Hilfe des Backslash notwendig. Um also einen Text mit Anföhrungszeichen einem string zuzuweisen, verwenden Sie die folgende Notierung: Listing 8.2 escape1.cs using System; class ConsoleApp { static void Main() { string s1 = "Dieser Text steht in \"Anföhrungszeichen\""; Console.WriteLine(s1); Console.ReadLine(); } }
Die Ausgabe schaut so aus: Dieser Text steht in "Anföhrungszeichen"
šberlegen Sie nun bitte einmal, welche Zeichenketten typischerweise Sonderzeichen wie den Backslash enthalten. Richtig, dies sind beispielsweise jegliche Art von Verzeichnis- und Dateipfaden. Diese mössen daher immer mit doppelten Backslashs angegeben werden:
8 Arbeiten mit Zeichenketten _____________________________________________133
Listing 8.3 escape2.cs using System; class ConsoleApp { static void Main() { string filename = "c:\\windows\\test.xml"; Console.WriteLine(filename); Console.ReadLine(); } }
Gerade bei Pfaden ist dies aber sehr umstÙndlich, da gegebenenfalls vor der Speicherung Ersetzungen vorgenommen werden mössen. Sie kðnnen die Verwendung von Escape-Zeichen daher för eine Zeichenkette abschalten. Hierzu verwenden Sie einfach das @-Zeichen vor der Angabe der Zeichenkette. Und schwups kðnnen Sie auf die Maskierung verzichten: string filename = @"c:\windows\test.xml"; Response.Write(filename);
Falls Sie sich jetzt fragen, warum öberhaupt Escape-Zeichen verwendet werden und diese nicht grundsÙtzlich ausgeschaltet sind, mðchte ich Ihnen einige weitere vorstellen. Die Angabe dieser Zeichen erleichtert Ihnen beispielsweise das Einfögen eines Zeilenumbruches. Auch beliebige andere Zeichen kðnnen Sie ganz einfach in Form eines hexadezimalen Wertes angeben. Die Tabelle enthÙlt eine šbersicht der gÙngigsten Escape-Zeichen. Tabelle 8.1 Escape-Zeichen in der Übersicht Escape-Zeichen
Beschreibung
\’
Einfaches Anführungszeichen
\“
Doppeltes Anführungszeichen
\\
Backslash
\0
Null
\a
Löst einen Alarm (Systembeep) aus
\b
Backspace
\f
Zeilenvorschub (Form feed)
\n
Zeilenumbruch (New line)
\r
Wagenrücklauf (Carriage return)
Das Zeichen @
134 _______________________________________________ 8.1 Der Datentyp string
Escape-Zeichen
Beschreibung
\t
Horizontaler Tabulator
\v
Vertikaler Tabulator
\xxxx
Beliebiges Zeichen in Form eines hexadezimalen Wertes
8.1.3
Formatierungen
Sie kðnnen die statische Methode Format der string-Klasse verwenden, um einen string aus verschiedenen Werten mit einer bestimmten Formatierung zu erzeugen. Der Format-Methode wird zunÙchst eine Zeichenkette mit Platzhaltern in der Form {0}, {1}, etc. öbergeben. Als weitere Parameter folgen die Werte, die för die Platzhalter eingesetzt werden sollen. Die Ausgabe eines numerischen Wertes kann daher sehr einfach wie folgt aussehen: Listing 8.4 format1.cs using System; class ConsoleApp { static void Main() { int n = 12345; string s = string.Format("Meine Zahl ist {0}.", n); Console.WriteLine(s); Console.ReadLine(); } }
Die Ausgabe zeigt durch den Punkt nach der Zahl deutlich, dass der Platzhalter ersetzt wurde: Meine Zahl ist 12345. Ausgabe als Währung
Neben einfachen Ersetzungen von Platzhaltern lassen sich die Werte auch noch weitergehend manipulieren. Der folgende Aufruf formatiert beispielsweise die öbergebene Zahl beispielsweise als WÙhrung. Es werden dabei die lokal eingestellten LÙnderinformationen zu Grunde gelegt (bei mir derzeit: Deutsch = WÙhrung DM).
8 Arbeiten mit Zeichenketten _____________________________________________135
Listing 8.5 format2.cs using System; class ConsoleApp { static void Main() { int n = 12345; string s = string.Format("WÙhrungsformatierung: {0:C}", n); Console.WriteLine(s); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: WÙhrungsformatierung: 12.345,00 DM
Nach der fortlaufenden Nummer des Platzhalters und einem Doppelpunkt wird die gewönschte Formatierungsart angegeben. Neben der WÙhrungsformatierungen stehen weitere Mðglichkeiten zur Verfögung. Die Tabelle zeigt eine šbersicht.
Tabelle 8.2 Die Formatierungszeichen in der Übersicht Format-Zeichen
Beschreibung
Cn
Formatiert den Wert als Währung entsprechend Landeseinstellungen, optional mit n Nachkommastellen.
Dn
Formatiert den Wert als Dezimalzahl, optional mit n Nachkommastellen.
En
Formatiert den Wert als Exponential, optional mit n Nachkommastellen.
Fn
Formatiert den Wert Nachkommastellen.
Nn
Formatiert den Wert numerisch, optional mit n Nachkommastellen.
P
Formatiert den Wert als Prozentwert, optional mit n Nachkommastellen.
R
Stellt sicher, dass ein zum string konvertierter numerischer Wert wieder korrekt zurück konvertiert werden kann (Round-trip).
Xn
Formatiert den Wert als Hexadezimalzahl, optional mit n Stellen.
als
Gleitkommazahl,
optional
mit
n
136 _______________________________________________ 8.1 Der Datentyp string
8.1.4
String-Eigenschaften
Die Klasse string kennt drei Eigenschaften, die bei der Bearbeitung von Zeichenketten nötzlich sein kðnnen.
Empty (statisch) Die Eigenschaft Empty liefert einen leeren string zuröck und entspricht somit "". Sie kðnnen die Eigenschaft verwenden, um eine leere Zeichenkette zuzuweisen und so beispielsweise einen string zu initialisieren. Beachten Sie, dass es sich um eine statische Methode handelt und diese daher nicht vom Objekt, sondern vom Datentyp her aufgerufen werden muss. Die folgenden beiden Aufrufe sind identisch:
Listing 8.6 empty1.cs string s1 = string.Empty; string s2 = "";
Chars/ [ ] Diese Eigenschaft liefert ein Array mit den einzelnen Zeichen der Zeichenkette. Unter C# steht diese Eigenschaft nicht unter diesem Namen zur Verfögung, daför aber als Indexer des string-Objektes. Im folgenden Beispiel wird der Indexer verwendet, um alle Zeichen der Zeichenkette einzeln auszugeben. Es wird ein neuer string instanziiert und anschlieûend mit einer Schleife Zeichen för Zeichen durchlaufen. Der Indexer (gekennzeichnet durch die eckigen Klammern []) liefert das jeweilige Zeichen. Listing 8.7 chars1.cs using System; class ConsoleApp { static void Main() { string s1 = "Hallo Welt"; for(int i = 0; i<s1.Length; i++) { string s2 = string.Format("Zeichen {0:D2} = {1}", i+1, s1[i]);
8 Arbeiten mit Zeichenketten _____________________________________________137
Console.WriteLine(s2); } Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Zeichen Zeichen Zeichen Zeichen Zeichen Zeichen Zeichen Zeichen Zeichen Zeichen
01 02 03 04 05 06 07 08 09 10
= = = = = = = = = =
H a l l o W e l t
Length Diese Eigenschaft wurde bereits im vorangegangenen Beispiel verwendet; sie liefert einen int-Wert mit der LÙnge der Zeichenkette zuröck. Listing 8.8 length1.cs using System; class ConsoleApp { static void Main() { string s1 = "Hallo Welt"; string s2 = string.Format("\"{0}\" ist {1} Zeichen lang.", s1, s1.Length); Console.WriteLine(s2); Console.ReadLine(); } }
138 _______________________________________________ 8.1 Der Datentyp string
Ausgabe im Konsolenfenster: "Hallo Welt" ist 10 Zeichen lang.
8.1.5
Weitere String-Operationen
Neben Format enthÙlt die Klasse string eine ganze Reihe weiterer statischer und dynamischer Methoden zur Bearbeitung von Zeichenketten. Die Tabelle fasst die wichtigsten Methoden zusammen. Mit * gekennzeichnete Methoden sind statisch und mössen daher öber den Datentyp, nicht aber öber eine Instanz aufgerufen werden. Statischer Aufruf: string.Methode(...)
Dynamischer Aufruf (öber eine Instanz): string s = "xyz" s.Methode
Beachten Sie, dass die Instanz-Operationen die aktuelle Instanz der Zeichenkette nicht verÙndern. Die Methoden liefern daher gegebenenfalls eine neue (und entsprechend modifizierte) Zeichenkette auf Basis der aktuellen Instanz zuröck. Tabelle 8.3 Die Methoden der Klasse string im Überblick Methode
Rückgabewert
Beschreibung
Compare*
int
Vergleicht zwei als Parameter übergebene Zeichenketten und liefert 0 zurück, wenn diese übereinstimmen. Über einen optionalen dritten, booleschen Parameter kann angegeben werden, ob die Groß-Klein-Schreibung ignoriert werden soll.
Concat*
string
Verbindet zwei Zeichenketten.
Copy*
string
Kopiert die übergebene Zeichenkette und liefert eine neue Zeichenkette mit dem gleichen Inhalt zurück.
Equals*
bool
Prüft, ob die beiden übergebenen Zeichenketten identisch sind.
Format*
string
Formatiert eine Zeichenkette, Beschreibung siehe weiter oben unter „Formatierungen“.
Intern*
string
Liefert einen Zeichenkette.
oder
Verweis
mehrere
auf
die
übergebene
übergebene
8 Arbeiten mit Zeichenketten _____________________________________________139
Methode
Rückgabewert
Beschreibung
Join*
string
Verbindet zwei oder mehrere übergebene Zeichenketten. Ein zusätzlicher erster Parameter gibt dabei an, mit welchem Zeichen die einzelnen Zeichenketten separiert sind.
CompareTo
int
Vergleicht die aktuelle Instanz einer Zeichenkette mit einer zweiten als Parameter übergebenen Zeichenkette und liefert 0 zurück, wenn diese übereinstimmen.
CopyTo
string
Kopiert die aktuelle Instanz einer Zeichenkette und liefert einen neue Zeichenkette mit dem gleichen Inhalt zurück.
EndsWith
bool
Prüft, ob die Zeichenketten mit dem übergebenen Wert endet.
Equals
bool
Prüft, ob die aktuelle Instanz einer Zeichenkette mit einer zweiten übergebenen Zeichenkette identisch ist.
IndexOf
int
Gibt den Index des ersten Vorkommens eines Zeichens oder einer Zeichenkette in der aktuellen Instanz einer Zeichenkette zurück. Über einen optionalen zweiten Parameter kann ein gewünschter Startindex angegeben werden.
IndexOfAny
int
Gibt den Index des ersten Vorkommens eines beliebigen Zeichens aus dem übergebenen charArray in der aktuellen Instanz einer Zeichenkette zurück.
Insert
string
Fügt an einer als Parameter angegebenen Position eine Zeichenkette in die aktuelle Instanz einer Zeichenkette ein.
LastIndexOf
int
Gibt den Index des letzten Vorkommens eines Zeichens oder einer Zeichenkette in der aktuellen Instanz einer Zeichenkette zurück. Über einen optionalen zweiten Parameter kann ein gewünschter Startindex angegeben werden.
LastIndexOfAny
int
Gibt den Index des letzten Vorkommens eines beliebigen Zeichens aus dem übergebenen charArray in der aktuellen Instanz einer Zeichenkette zurück.
PadLeft
string
Füllt die aktuelle Zeichenkette mit Leerzeichen oder einem optional übergebenen Zeichen von links bis zur gewünschten Länge auf.
PadRight
string
Füllt die aktuelle Zeichenkette mit Leerzeichen oder einem optional übergebenen Zeichen von rechts bis zur gewünschten Länge auf.
140 _______________________________________________ 8.1 Der Datentyp string
Methode
Rückgabewert
Beschreibung
Remove
string
Entfernt ab der angegebenen Startposition die angegebene Anzahl an Zeichen aus der aktuellen Zeichenkette.
Replace
string
Ersetzt ein Zeichen oder eine Zeichenkette innerhalb der aktuellen Zeichenkette durch eine andere
Split
string[]
Separiert die aktuelle Zeichenkette in ein string-Array. Die möglichen Separatoren werden als Parameter in Form eines char-Arrays übergeben.
StartsWiths
bool
Prüft, ob die Zeichenkette mit dem übergebenen Wert beginnt.
Substring
string
Liefert einen Teil der aktuellen Zeichenkette ab der angegebenen Position. Über einen optionalen zweiten Parameter kann die Anzahl der auszuschneidenden Zeichen angegeben werden. Wird dieser Parameter weggelassen, schneidet die Methode alle Zeichen ab der angegebenen Position ab.
ToCharArray
char[]
Liefert ein char-Array mit den einzelnen Zeichen der Zeichenkette.
ToLower
string
Gibt aktuelle Instanz Kleinbuchstaben zurück.
einer
Zeichenkette
in
ToUpper
string
Gibt aktuelle Instanz Großbuchstaben zurück.
einer
Zeichenkette
in
Trim
string
Gibt eine neue Zeichenkette auf Basis der aktuellen Instanz zurück, bei der alle Leerzeichen rechts und links entfernt wurden.
TrimEnd
string
Gibt eine neue Zeichenkette auf Basis der aktuellen Instanz zurück, bei der alle Leerzeichen rechts entfernt wurden.
TrimStart
string
Gibt eine neue Zeichenkette auf Basis der aktuellen Instanz zurück, bei der alle Leerzeichen links entfernt wurden.
Einige der aufgeföhrten Methoden haben eine besondere ErwÙhnung verdient und sollen daher an Hand einiger Beispiele veranschaulicht werden.
EndsWith Letztes Zeichen
EndsWith pröft, ob die aktuelle Zeichenkette mit einem bestimmten Zeichen oder wie im folgenden Beispiel mit einer anderen Zeichenkette aufhðrt.
8 Arbeiten mit Zeichenketten _____________________________________________141
Listing 8.9 endswith1.cs using System; class ConsoleApp { static void Main() { string s1 = "Hallo Welt"; if(s1.EndsWith("Welt")) Console.WriteLine("Der String hðrt mit \"Welt\" auf."); else Console.WriteLine("Der String hðrt nicht mit \"Welt\" auf."); Console.ReadLine(); } }
Ausgabe im Konsolenfenster Der String hðrt mit "Welt" auf.
IndexOf Diese Methode gibt die Position eines Zeichens oder einer Zeichenkette in der aktuellen Zeichenkette zuröck. Wird die Zeichenkette nicht gefunden, wird ±1 zuröckgeliefert. Listing 8.10 indexof1.cs using System; class ConsoleApp { static void Main() { string s1 = "Hallo Welt"; Console.WriteLine(s1.IndexOf("Welt")); Console.WriteLine(s1.IndexOf("Universum")); Console.ReadLine(); } }
Enthält
142 _______________________________________________ 8.1 Der Datentyp string
Ausgabe im Konsolenfenster: 6 -1
Insert Die Methode fögt an der angegebenen Position der Zeichenkette ein Zeichen oder eine andere Zeichenkette ein. Listing 8.11 insert1.cs using System; class ConsoleApp { static void Main() { string s1 = "Hallo Welt"; Console.WriteLine(s1.IndexOf("Welt")); Console.WriteLine(s1.IndexOf("Universum")); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Hallo Welt
PadLeft Diese Methode formatiert die Zeichenkette auf eine bestimmte Anzahl von Zeichen und föllt diese dazu links mit Leerzeichen beziehungsweise einem optional öbergebenen Zeichen auf. Listing 8.12 padleft1.cs using System; class ConsoleApp {
8 Arbeiten mit Zeichenketten _____________________________________________143
static void Main() { string s1 = "125,55 Euro"; string s2 = s1.PadLeft(15, ©*©); Console.WriteLine(s2); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: ****125,55 Euro
Replace Die Methode Replace wird immer dann verwendet, wenn eine Zeichenkette durch eine andere ersetzt werden soll. Im folgenden Beispiel werde ich ein wenig grðûenwahnsinnig oder oute mich als AnhÙnger des SETI-Programms. Listing 8.13 replace1.cs using System; class ConsoleApp { static void Main() { string s1 = "Hallo Welt"; string s2 = s1.Replace("Welt", "Universum"); Console.WriteLine(s2); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Hallo Universum
Ersetzen
144 _______________________________________________ 8.1 Der Datentyp string
Split Die Methode Split wird verwendet, um eine Zeichenkette in mehrere andere aufzusplitten. Es kann eine beliebige Anzahl an Separatoren in Form eines charArrays angegeben werden. Die Methode liefert ein string-Array mit den separierten Inhalten zuröck. Im Beispiel wird ein Satz in die einzelnen Wðrter zerlegt. Als Separatoren sind das Leerzeichen sowie das Komma angegeben. In einer foreach..in-Schleife werden die einzelnen Wðrter (= Elemente des string-Arrays) zeilenweise ausgegeben. Listing 8.14 split1.cs using System; class ConsoleApp { static void Main() { string s1 = "Hallo Welt, dies ist ein wirklich sinnvoller Satz"; char[] c1 = {© ©, ©,©}; string[] s2 = s1.Split(c1); foreach(string s3 in s2) { Console.WriteLine(s3 + ""); } Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Hallo Welt dies ist ein wirklich sinnvoller Satz
8 Arbeiten mit Zeichenketten _____________________________________________145
Substring Mit Substring kðnnen Sie einen Teil aus einer Zeichenkette herauslðsen. Im Beispiel wird die Methode in Verbindung mit IndexOf verwendet. ZunÙchst wird dabei die Position des Wortes ¹Weltª gesucht und anschlieûend wird dieses ausgeschnitten und im Konsolenfenster ausgegeben. Listing 8.15 substring1.cs using System; class ConsoleApp { static void Main() { string s1 = "Hallo Welt"; int n1 = s1.IndexOf("Welt"); string s2 = s1.Substring(n1); Console.WriteLine(s2); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Welt
ToCharArray Mit dieser Methode lÙsst sich allerlei Unfug anstellen. Sie liefert ein char-Array mit den einzelnen Zeichen der Zeichenkette zuröck. Die Weiterverarbeitung wird uns durch die Methoden der Array-Klasse leicht gemacht. Das folgende Beispiel konvertiert eine Zeichenkette zunÙchst als Groûbuchstaben. Nun wird diese mittels der Methode ToCharArray in ein gleichnamiges kopiert. Die statische Methode Sort der Array-Klasse sorgt nun daför, dass die Zeichen alphanumerisch sortiert werden. Die nun neu angelegte Zeichenkette fögt alle Zeichen wieder zusammen. Das nachfolgende (Ùhnliche Konstrukt) verwendet die Methode Reverse der Array-Klasse, um die Zeichenkette umzudrehen.
146 _______________________________________________ 8.1 Der Datentyp string
Listing 8.16 tochararray1.cs using System; class ConsoleApp { static void Main() { string s1 = "Hallo Welt"; string s2 = s1.ToUpper(); char[] c1 = s2.ToCharArray(); Array.Sort(c1); string s3 = new string(c1); Console.WriteLine(s3 + ""); char[] c2 = s2.ToCharArray(); Array.Reverse(c2); string s4 = new string(c2); Console.WriteLine(s4 + ""); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: AEHLLLOTW TLEW OLLAH
ToLower/ ToUpper Klein und groß
Die Methode ToUpper wurde bereits im vorangegangenen Beispiel verwendet. Sie liefert eine Kopie der aktuellen Zeichenkette in Groûbuchstaben zuröck. Analog verhÙlt sich die Methode ToLower, bei der die Kopie in Kleinbuchstaben angelegt wird. Listing 8.17 tolowertoupper1.cs using System; class ConsoleApp { static void Main() { string s1 = "Hallo Welt";
8 Arbeiten mit Zeichenketten _____________________________________________147
string s2 = s1.ToUpper(); Console.WriteLine("Grðûenwahnsinnig: " + s2 + ""); string s3 = s1.ToLower(); Console.WriteLine("Kleinkariert: " + s3 + ""); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Grðûenwahnsinnig: HALLO WELT Kleinkariert: hallo welt
Trim (TrimEnd/ TrimStart) Mðchten Sie Leerzeichen oder auch andere Zeichen entfernen, die eine Zeichenkette rechts und/oder links begrenzen, verwenden Sie eine der drei TrimMethoden. Trim selbst kombiniert dabei TrimEnd und TrimStart. Das erste Beispiel zeigt das Entfernen von Leerzeichen; die Ausgabe im Konsolenfenster zeigt deutlich, dass die Zeichenkette tatsÙchlich um diese erleichtert wurde. Listing 8.18 trim1.cs using System; class ConsoleApp { static void Main() { string s1 = "
Hallo Welt
string s2 = s1.Trim(); Console.WriteLine("--->" + s2 + "<---"); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: --->Hallo Welt<---
";
148 ___________________________________________ 8.2 Die Klasse StringBuilder
Das folgende, zweite Beispiel zeigt das Entfernen von explizit angegebenen Zeichen. Der Trim-Methode wird hierzu als Parameter ein char-Array mit den gewönschten Zeichen öbergeben. Die Ausgabe zeigt, dass auf diese Weise das Wort ¹Halloª vollstÙndig entfernt wurde. Listing 8.19 trim2.cs using System; class ConsoleApp { static void Main() { string s1 = "
Hallo Welt
";
char[] c1 = {© ©, ©H©, ©a©, ©l©, ©o©}; string s2 = s1.Trim(c1); Console.WriteLine("--->" + s2 + "<---"); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: --->Welt<---
8.2
Die Klasse StringBuilder
Mit der StringBuilder-Klasse bietet das .NET Framework eine zweite effiziente Mðglichkeit zur Bearbeitung von Strings an. Im Gegensatz zum Datentyp string kann der Inhalt der Klasse StringBuilder nachtrÙglich verÙndert werden. Auch wenn die angebotenen Methoden auf den ersten Blick denen von string gleichen, ergibt sich eine andere FunktionalitÙt, da der Aufruf die aktuelle Instanz manipuliert und keine neue zuröckliefert.
8 Arbeiten mit Zeichenketten _____________________________________________149
8.2.1
StringBuilder-Eigenschaften
Capacity šber diese Eigenschaft kann die Anzahl der för die Klasse reservierten Zeichen im Speicher abgefragt beziehungsweise gesetzt werden. Sofern Sie der Klasse einen (neuen) Inhalt zuweisen, wird die Eigenschaft gegebenenfalls automatisch auf den nÙchsten durch 16 teilbaren Wert aufgerundet. Durch explizites Setzen der Eigenschaft kðnnen Sie Speicher explizit anfordern. Listing 8.20 capacity1.cs using System; using System.Text; class ConsoleApp { static void Main() { StringBuilder s1 = new StringBuilder("Hallo Welt"); Console.WriteLine(s1.Capacity); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: 16
Chars/ [ ] Die Eigenschaft liefert ein char-Array mit allen Zeichen der Zeichenkette zuröck. In C# steht die Eigenschaft nicht unter diesem Namen, daför aber als Indexer der Klasse zur Verfögung. Ein Beispiel finden Sie in der Beschreibung der gleichnamigen Eigenschaft der Klasse string weiter oben in diesem Kapitel.
150 ___________________________________________ 8.2 Die Klasse StringBuilder
Length Die Eigenschaft Length liefert die aktuelle LÙnge der Zeichenkette zuröck und entspricht somit der gleichnamigen Eigenschaft der Klasse string; ein Beispiel finden Sie in der entsprechenden Beschreibung weiter oben in diesem Kapitel.
MaxCapacity Diese Eigenschaft gibt den maximal zur Verfögung stehenden Platz för die Klasse zuröck. Normal: 2147483647.
8.2.2
StringBuilder-Methoden
Die Methoden der StringBuilder-Klasse sind denen des Datentyps string Ùhnlich. Wie beschrieben wirken sich diese jedoch auf die aktuelle Instanz aus und unterscheiden sich auf diese Weise elementar von der Klasse string. Aus diesem Grund besitzen die Methoden zur Manipulierung keinen Röckgabewert. Die StringBuilder-Klasse kommt auf Grund dieser Mðglichkeit immer dann zum Einsatz, wenn eine Zeichenkette ein- oder mehrmals nach der Zuweisung manipuliert oder mehrere Zeichenketten formatiert zusammengefögt werden sollen. Tabelle 8.4 Die Methoden der Klasse string im Überblick Methode
Rückgabewert
Beschreibung
Append
-
Fügt die übergebene Zeichenkette beziehungsweise ein char-Array an die Zeichenkette an.
AppendFormat
-
Fügt die übergebene Zeichenkette beziehungsweise ein char-Array an die Zeichenkette an und verwendet dabei die allgemeinen Formatierungsmöglichkeiten; vergleiche „Formatierungen“ weiter oben.
EnsureCapacity
int
Prüft, ob die zur Verfügung stehende Kapazität mindestens dem übergebenen Wert entspricht.
Equals
bool
Prüft, ob die aktuelle Instanz einer Zeichenkette mit einer zweiten übergebenen Zeichenkette identisch ist.
Insert
-
Fügt an einer als Parameter angegebenen Position eine Zeichenkette in die aktuelle Instanz einer anderen Zeichenkette ein.
8 Arbeiten mit Zeichenketten _____________________________________________151
Methode
Rückgabewert
Beschreibung
Remove
-
Entfernt ab der angegebenen Startposition die angegebene Anzahl an Zeichen aus der aktuellen Zeichenkette.
Replace
-
Ersetzt ein Zeichen oder eine Zeichenkette innerhalb der aktuellen Zeichenkette durch eine andere.
AppendFormat Mit Hilfe der Methode AppendFormat kðnnen Sie einer bestehenden Instanz der StringBuilder-Klasse formatiert Zeichenketten hinzufögen. Die Formatierungsanweisungen entsprechen den bereits bekannten und weiter oben beschriebenen. Es kðnnen daher neben Zeichenketten auch numerische Werte, WÙhrungen und so weiter formatiert angehÙngt werden. Das folgende Beispiel wurde basiert auf einem bereits weiter oben verwendeten und realisiert dies mit Hilfe einer Instanz der StringBuilder-Klasse. Eine Zeichenkette wird zunÙchst in die einzelnen Wðrter zerlegt. Anschlieûend lÙuft eine for-Schleife durch das entstandene string-Array und fögt die einzelnen Wðrter formatiert der StringBuilder-Instanz hinzu. Die vollstÙndige Zeichenkette wird zu guter Letzt ausgegeben. Listing 8.21 appendformat1.cs using System; using System.Text; class ConsoleApp { static void Main() { string s1 = "Hallo Welt, dies ist ein wirklich sinnvoller Satz"; char[] c1 = {© ©, ©,©}; string[] s2 = s1.Split(c1); StringBuilder sb1 = new StringBuilder(); for(int i=0; i<s2.Length; i++) { sb1.AppendFormat("Wort {0}: {1}\n", i, s2[i]); }
152 _______________________________________________ 8.3 Reguläre Ausdrücke
Console.WriteLine(sb1); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Wort Wort Wort Wort Wort Wort Wort Wort Wort
8.3
0: 1: 2: 3: 4: 5: 6: 7: 8:
Hallo Welt dies ist ein wirklich sinnvoller Satz
Reguläre Ausdrücke
RegulÙre Ausdröcke (englisch: ¹regular expressionsª) sind eine sehr leistungsfÙhige Idee zum Durchsuchen und Manipulieren von Zeichenketten. Die regulÙren Ausdröcke sind eine Beschreibungssprache, die das gewönschte Ergebnis beschreibt. Es lassen sich auf diese Weise komplexe Konstrukte und Operationen auf Basis des Datentyps string oder der Klasse StringBuilder mit wenigen Zeichen umschreiben. Zudem sind regulÙre Ausdröcke leichter zu pflegen als individuelle Such- und Manipulationskonstrukte. Es bestehen starke Parallelen zu SQL, denn auch hier wird das Ergebnis beschrieben, ohne dass der eigentliche Weg dorthin erwÙhnt wird. Und genau hier liegen die Vorteile, die regulÙre Ausdröcke zu einem unverzichtbaren Hilfsmittel för alle Entwickler machen, die mit Zeichenketten arbeiten. Die interne Verarbeitung von regulÙren Ausdröcken unterscheidet sich im .NET Framework auf Wunsch ganz erheblich von alternativen Implementierungen. In anderen Sprachen werden die Ausdröcke zur Laufzeit interpretiert und umgesetzt. Nicht so bei .NET; hier wird vom Compiler ein Programmteil erzeugt, der die notwendigen Konstrukte för diesen speziellen Ausdruck implementiert. Auf diese Weise ergibt sich eine deutliche Leistungsoptimierung, die regulÙre Ausdröcke auch zur Bearbeitung von sehr groûen Zeichenketten einsetzbar macht. Die verschiedenen Objekte, die das .NET Framework zur Bearbeitung von Zeichenketten mit regulÙren Ausdröcken zur Verfögung stellt, werden im Namespace System.Text.RegularExpressions zusammengefasst.
8 Arbeiten mit Zeichenketten _____________________________________________153
Im Folgenden wird die Verwendung von regulÙren Ausdröcken prinzipiell vorgestellt. Dabei wird insbesondere auf die weiteren Unterschiede und Besonderheiten der .NET-Implementierung eingegangen. Eine daröber hinausgehende, erschðpfende Beschreibung findet sich in der entsprechenden Fachliteratur.
8.3.1
Der Aufbau von regulären Ausdrücken
Als Beschreibungssprache verwenden regulÙre Ausdröcke eine bestimmte Syntax, um das gewönschte Ergebnis zu definieren. Es handelt sich dabei um eine Mischung aus normalen Zeichen, die för sich selbst stehen, und Anweisungszeichen, die för bestimmte Zeichengruppen oder Ausdröcke wie einen Umbruch stehen. Aus der Kombination dieser Zeichen lÙsst sich nahezu jede TeilZeichenkette beschreiben, die einem bestimmten Muster folgt. Das folgende Beispiel demonstriert, wie man mit Hilfe von regulÙren Ausdröcken sehr einfach Dezimalzahlen aus einer Zeichenkette extrahieren kann. Die Zahlen sind dabei beliebig in der Zeichenkette verstreut. Zahlen suchen Listing 8.22 regex1.cs using System; using System.Text.RegularExpressions; class ConsoleApp { static void Main() { string s1 = "Addiert man 5 und 9, ergibt dies 14."; Regex r = new Regex(@"\d"); foreach(Match m in r.Matches(s1)) { Console.WriteLine(m.Value + ""); } Console.ReadLine(); } }
154 _______________________________________________ 8.3 Reguläre Ausdrücke
Die Ausgabe im Konsolenfenster zeigt, dass das Beispiel funktioniert. Eine aufwÙndiges Parsen und Vergleichen der Zeichenkette entfÙllt vðllig. 5 9 1 4
Das Beispiel ist im Grunde sehr trivial, denn die Mðglichkeiten von regulÙren Ausdröcken gehen sehr viel weiter. Aber dennoch zeigt das Beispiel deren Anwendung sehr gut. Der Ausdruck ¹\dª kennzeichnet eine Dezimalzahl; eine einzige. Auf diese Weise werden zusammenhÙngende Zahlen einzeln extrahiert, deutlich erkennbar an der Ausgabe der Zahl 14 als 1 und 4. Schðner wÙre es natörlich, wenn die tatsÙchliche Zahl ausgegeben wörde, unabhÙngig, wie viele Stellen diese hat. Das Quantifizierungszeichen ¹+ª kann hier Abhilfe schaffen. Das Zeichen kennzeichnet, dass das zuvor notierte Element (in diesem Fall ¹\dª) ein oder mehrmals hintereinander auftauchen kann. Im Beispiel eingefögt, ergibt sich das ursprönglich gewönschte Ergebnis. Listing 8.23 regex2.cs ... Regex r = new Regex(@"\d+"); ...
Die Ausgabe im Konsolenfenster erscheint nun wie gewönscht: 5 9 14
Mit Hilfe der vorgestellten Zeichen und Anweisungen lassen sich komplexe Ausdröcke zusammenstellen. So kðnnte man ein Datum aus einer beliebigen Zeichenkette extrahieren. Datum suchen Listing 8.24 regex3.cs using System; using System.Text.RegularExpressions; class ConsoleApp { static void Main() { string s1 = "Heute ist der 25.08.2001, ein Samstag!";
8 Arbeiten mit Zeichenketten _____________________________________________155
Regex r = new Regex(@"\d{1,2}\.\d{1,2}\.\d{4}"); // Console.WriteLine("Datum: " + r.Match(s1).Value); Console.ReadLine(); } }
Auch hier wird wieder eine Dezimalzahl extrahiert. Allerdings muss diese aus drei Blðcken aus je 1 bis 2 Ziffern för Tag und Monat beziehungsweise 4 Ziffern för das Jahr bestehen. Hierzu wird die Anzahl nach dem Element ¹\dª in geschweiften Klammern notiert. Minimal- und maximal-Anzahl werden mit einem Komma getrennt. Ausgabe im Konsolenfenster: Datum: 25.08.2001
Neben ¹\dª stehen eine ganze Reihe weiterer Zeichen zur Verfögung. Die Tabelle enthÙlt eine Auflistung der wichtigsten von .NET angebotenen Zeichen und Anweisungen. Beachten Sie, dass Sie bei Sprachen wie C#, C++ und JScript den Backslash ¹\ª immer doppelt angeben mössen, auch beziehungsweise gerade weil diesem bei regulÙren Ausdröcken eine Sonderbedeutung zu Gute kommt. Bei C# kðnnen Sie hierauf verzichten, wenn Sie das @-Zeichen vor der Zeichenkette notieren. Unter Visual Basic.NET ist keine besondere Beachtung notwendig. Tabelle 8.5 Die wichtigsten Anweisungen und Zeichen der regulären Ausdrücke in der Übersicht Zeichen
Beschreibung
Gewöhnliche Buchstaben
Sofern die Buchstaben nicht zu . $ ^ { [ ( | ) * + ? \ gehören entsprechen diese Zeichen sich selbst. Möchte man also nach einem kleinen „a“ suchen, notiert man dieses auch als „a“.
\
Wird der Backslash von einem der Zeichen . $ ^ { [ ( | ) * + ? gefolgt, so entspricht die Kombination diesem Zeichen. Die Sonderbedeutung des Zeichens wird auf diese Weise also aufgehoben.
\a
Alarm-Zeichen (beep/ bell)
\b
Backspace
\t
Horizontaler Tabulator
\r
Wagenrücklauf (Carriage return)
\v
Vertikaler Tabulator
\f
Zeilenvorschub (Form feed)
\n
Zeilenumbruch (New line)
156 _______________________________________________ 8.3 Reguläre Ausdrücke
Zeichen
Beschreibung
\nnn
Beliebiges ASCII-Zeichen mit der Dezimalzahl nnn
\xnn
Beliebiges ASCII-Zeichen mit der Hexadezimalzahl nn
Zeichengruppen
Beschreibung
\unnnn
Beliebiges Unicode-Zeichen mit der Dezimalzahl nnnn
.
Entspricht einem beliebigen Zeichen ausgenommen \n (New line)
[abc]
Entspricht einem der innerhalb der Klammer notierten Zeichen, also a, b oder c
[^abc]
Entspricht einem beliebigen Zeichen, das nicht (!) in der Klammer notiert ist, also allen Zeichen außer a, b und c
[0-9°-zA-Z]
Entspricht einem Zeichen, das innerhalb der notierten Zeichenbereiche liegt, in diesem Fall alle Zahlen sowie Kleinund Großbuchstaben
\w
Entspricht jedem Wortzeichen, identisch zu [0-9a-zA-Z]
\W
Entspricht jedem Zeichen, das kein Wortzeichen ist, identisch zu [^0-9a-zA-Z]
\s
Entspricht einem beliebigen Whitespace-Zeichen, identisch zu [\f\n\r\t\v]
\S
Entspricht einem beliebigen Zeichen, das kein WhitespaceZeichen ist, identisch zu [^ \f\n\r\t\v]
\d
Entspricht einem dezimalen Zeichen, identisch zu [0-9]
\D
Entspricht einem beliebigen Zeichen, das kein dezimales Zeichen ist, identisch zu [^0-9]
Meta-Zeichen
Beschreibung
^
Bezeichnet den Anfang der Zeichenkette; sofern die Option MultiLine gesetzt ist, den Anfang einer Zeile
$
Bezeichnet das Ende der Zeichenkette; sofern die Option MultiLine gesetzt ist, das Ende einer Zeile
\A
Bewirkt, dass die Übereinstimmung am Anfang der Zeichenkette liegen muss, und ignoriert somit die eventuell gesetzte Option MultiLine
\z
Bewirkt, dass die Übereinstimmung am Ende der Zeichenkette liegen muss, und ignoriert somit die eventuell gesetzte Option MultiLine
\Z
Bewirkt, dass die Übereinstimmung am Ende der Zeichenkette oder vor einem \n am Ende der Zeichenkette liegen muss, und ignoriert somit die eventuell gesetzte Option MultiLine
8 Arbeiten mit Zeichenketten _____________________________________________157
Quantifizierungszeichen
Beschreibung
*
Bewirkt, dass das zuvor notierte Element nicht oder beliebig häufig vorkommen kann
+
Bewirkt, dass das zuvor notierte Element mindestens einmal vorkommen muss
?
Bewirkt, dass das zuvor notierte Element nicht oder einmal vorkommen muss
{n}
Bewirkt, dass das zuvor notierte Element n mal vorkommen muss
{n,}
Bewirkt, dass das zuvor notierte Element mindestens n mal vorkommen muss
{n,m}
Bewirkt, dass das zuvor notierte Element mindestens n mal, aber maximal m mal vorkommen muss
Alternierungszeichen
Beschreibung
|
Das links oder übereinstimmen
(?(Ausdruck)ja|nein)
Entspricht dem ja-Ausdruck, sofern der in Klammern notierte Ausdruck übereinstimmt, ansonsten dem optionalen NeinAusdruck
Gruppierungen
Beschreibung
()
Gruppiert den in Klammern eingeschlossenen Ausdruck, um einen späteren Zugriff auf diesen Teilbereich zu ermöglichen. Der Zugriff kann sowohl innerhalb des Gesamtausdrucks als auch später zum Beispiel bei einer Ersetzung mit Replace erfolgen. Der Zugriff erfolgt in beiden Fällen über die nachfolgend beschriebenen Referenzen über eine fortlaufende Nummerierung.
(?
)
Gruppiert analog, vergibt aber einen Namen für die Referenz
Referenzen
Beschreibung
\n
Greift auf den Inhalt einer Gruppierung über die fortlaufende Nummer n zu, beim Ersetzen oder Zugriff über die ResultMethode des Match-Objektes wird $n angegeben
\k
Greift auf den Inhalt einer Gruppierung über den vergebenen Namen zu, beim Ersetzen oder Zugriff über die Result-Methode des Match-Objektes wird ${name} angegeben
8.3.2
das
rechts
notierte
Element
muss
Die Klasse Regex
Bereits in den ersten beiden Beispielen weiter oben wurde die Klasse Regex verwendet. Sie ist Dreh- und Angelpunkt der regulÙren Ausdröcke innerhalb des .NET Frameworks. Die Klasse bietet Methoden zum Suchen, Ersetzen und Aufsplitten von Zeichenketten an. Die meisten Methoden sind sowohl in einer
158 _______________________________________________ 8.3 Reguläre Ausdrücke
statischen als auch in einer dynamischen Version verfögbar. Die dynamische Version auf der Basis einer Instanz der Klasse ist in der Regel vorzuziehen. Der oder die regulÙren Ausdröcke werden in diesem Fall einmal an den Konstruktur der Klasse öbergeben. Alle Methoden-Aufrufe beziehen sich dann auf diesen Ausdruck. Auf diese Weise kann der Ausdruck fest in das Programm kompiliert und somit schneller als die Interpretation zur Laufzeit abgearbeitet werden.
IsMatch Diese Methode wird hÙufig verwendet, um Validierungen durchzuföhren. Dabei wird neben einer zu pröfenden Zeichenkette ein Ausdruck öbergeben, der deren Aufbau beschreibt. IsMatch liefert nun zuröck, ob der Ausdruck mindestens eine šbereinstimmung ergeben hat. Fögt man dem Ausdruck zu Beginn ¹^ª und am Ende ¹$ª hinzu, kann man IsMatch verwenden, um den gesamten Ausdruck auf šbreinstimmung zu pröfen. Ein oft verwendetes Beispiel hierför ist die Pröfung von E-Mail-Adressen: E-Mail-Adresse prüfen
Listing 8.25 ismatch1.cs using System; using System.Text.RegularExpressions; class ConsoleApp { public static void IsEmailOK(string email) { Console.Write("Die Adresse " + email + " ist "); Regex r = new Regex(@"^.+@.+\..+$"); if(r.IsMatch(email)) Console.Write("korrekt"); else Console.Write("leider nicht korrekt"); Console.WriteLine(""); } static void Main() { IsEmailOK("[email protected]"); IsEmailOK("test@test"); IsEmailOK(",[email protected]"); Console.ReadLine(); } }
8 Arbeiten mit Zeichenketten _____________________________________________159
Ausgabe im Konsolenfenster: Die Adresse [email protected] ist korrekt Die Adresse test@test ist leider nicht korrekt Die Adresse ,[email protected] ist korrekt
Der verwendete Ausdruck ist relativ trivial, da dieser die E-Mail-Adresse lediglich nach dem Muster [email protected] öberpröft. Es werden daher durchaus offensichtlich falsche E-Mail-Adressen als korrekt erkannt. Das Beispiel zeigt dies an Hand der Adresse ¹,[email protected]ª, die ganz sicher nicht korrekt ist. Sehr viel effektiver ist hingegen der folgende Ausdruck: Listing 8.26 ismatch2.cs
Erweiterte Prüfung
... Regex r = new Regex(@"^[\w\.\-]+@([\w\-]+\.)*[\w\-]{2,63}\. [a-zA-Z]{2,4}$"); ...
Die Ausgabe im Konsolenfenster stimmt zunÙchst mit der des vorangegangenen Beispiels öberein. Bei der dritten E-Mail-Adresse wird jedoch deutlich, dass der hier verwendete Ausdruck wesentlich genauer ist ± probieren Sie es aus! Die Adresse [email protected] ist korrekt Die Adresse test@test ist leider nicht korrekt Die Adresse ,[email protected] ist leider nicht korrekt
Match Die Methode Match wird verwendet, um einen regulÙren Ausdruck innerhalb einer Zeichenkette zu suchen. Das Beispiel verwendet den vorherigen Ausdruck, um eine E-Mail-Adresse innerhalb einer beliebigen Zeichenkette zu suchen. Aus dem Ausdruck wurden allerdings die Zeichen ¹^ª und ¹$ª entfernt, da die Adresse an einer beliebigen Stelle innerhalb der Zeichenkette vorkommen kann. Listing 8.27 match1.cs using System; using System.Text.RegularExpressions; class ConsoleApp { static void Main() { string s1 = "Hallo Welt, Ihr kðnnte mich unter " +
Einen Treffer finden
160 _______________________________________________ 8.3 Reguläre Ausdrücke
"der Adresse [email protected] erreichen."; Regex r = new Regex(@"[\w\.\-]+@([\w\-]+\.)*[\w\-]{2,63}\. [a-zA-Z]{2,4}"); Match m = r.Match(s1); Console.WriteLine("eMail: " + m.Value); Console.ReadLine(); } } Response.Write("eMail: " + m.Value);
Ausgabe im Konsolenfenster: eMail: [email protected]
Matches Mehrere Treffer finden
Die Methode Matches arbeitet Ùhnlich wie Match, liefert allerdings eine Collection mit allen Fundstellen zuröck. Diese lÙsst sich anschlieûend beispielsweise mit einer foreach..in-Schleife durchlaufen. Das Beispiel zeigt die Anwendung des Ausdrucks bei mehreren zu suchenden E-Mail-Adressen. Listing 8.28 matches1.cs using System; using System.Text.RegularExpressions; class ConsoleApp { static void Main() { string s1 = "Hallo Welt, Ihr kðnnte mich unter " + "der Adresse [email protected] erreichen, " + "alternativ kðnnt Ihr mir auch eine eMail an [email protected] " + "schicken."; Regex r = new Regex(@"[\w\.\-]+@([\w\-]+\.)*[\w\-]{2,63}\ .[a-zA-Z]{2,4}"); MatchCollection mc = r.Matches(s1); foreach(Match m in mc) Console.WriteLine("eMail: " + m.Value + "");
8 Arbeiten mit Zeichenketten _____________________________________________161
Console.ReadLine(); } }
Ausgabe im Konsolenfenster: eMail: [email protected] eMail: [email protected]
Replace Der Name erklÙrt schon, welchem Zwecke diese Methode dient: Ersetzungen vornehmen. Auf die LeistungsfÙhigkeit der regulÙren Ausdröcke kann hier allerdings nicht nur bei der Suche, sondern auch beim Vorgang des Ersetzens zuröckgegriffen werden. Im folgenden Beispiel werden die Werte vor und nach einem Doppelpunkt öber eine Referenz vertauscht. Listing 8.29 replace1.cs using System; using System.Text.RegularExpressions; class ConsoleApp { static void Main() { string s1 = "abc:def\nuvw:xyz"; Regex r = new Regex(@"(\w+)\:(\w+)"); string s2 = r.Replace(s1, @"$2:$1"); Console.WriteLine(s2); Console.ReadLine(); } }
Die beiden Referenzen ¹$1ª und ¹$2ª greifen auf die Gruppierung innerhalb des regulÙren Ausdrucks zuröck. Ausgabe im Konsolenfenster: def:abc xyz:uvw
162 _______________________________________________ 8.3 Reguläre Ausdrücke
Split Das weiter oben bereits mehrfach verwendete Beispiel zur Aufsplittung eines Satzes in die einzelnen Wðrter lÙsst sich auch mit regulÙren Ausdröcken realisieren. Dies kðnnte zum Beispiel wie folgt aussehen: Listing 8.30 regex1.cs using System; using System.Text.RegularExpressions; class ConsoleApp { static void Main() { string s1 = "Hallo Welt, dies ist ein wirklich sinnvoller Satz"; Regex r = new Regex(@"( |,)"); foreach(string s2 in r.Split(s1)) { if(s2.Trim().Length>0) Console.WriteLine("Wort: " + s2 + ""); } Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort:
Hallo Welt , dies ist ein wirklich sinnvoller Satz
Vielleicht fragen Sie sich, warum eine if-Abfrage vor der Ausgabe notwendig ist. Ich frage mich das auch, denn eigentlich wörde ich davon ausgehen, dass die Separatoren analog zu anderen Split-Methoden ausgelassen wörden. Dies ist aber leider nicht der Fall. Verzichtet man auf die Abfrage, ergibt sich das folgende, inkonsistente Ergebnis:
8 Arbeiten mit Zeichenketten _____________________________________________163
Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort:
Hallo Welt ,
dies ist ein wirklich sinnvoller Satz
Die Methoden in der Übersicht Die Tabelle enthÙlt eine šbersicht der statischen und dynamischen Methoden der Klasse Regex. Die statischen Methoden sind mit einem * gekennzeichnet und mössen öber den Klassennamen, die dynamischen öber eine Instanz der Klasse aufgerufen werden. Tabelle 8.6 Die Methoden der Klasse Regex im Überblick Methode
Rückgabewert
Beschreibung
CompileToAssembly*
-
Kompiliert eine Regex-Klasse als MS Intermediate Language und legt diese als Datei (Assembly) auf der Festplatte ab.
Escape*
string
Konvertiert alle Zeichen mit Sonderbedeutung (\, *, +, ?, |, {, [, (,), ^, $, ., #) einer normalen Zeichenkette in die entsprechenden maskierten Zeichen.
IsMatch*
bool
Prüft, ob ein regulärer Ausdruck mindestens einmal auf eine übergebene Zeichenkette zutrifft.
Match*
Match
Sucht den regulären Ausdruck in der übergebenen Zeichenkette und gibt die erste Fundstelle (optional: ab einer bestimmten Position) zurück.
Matches*
MatchCollection
Gibt analog zu Match eine MatchCollection mit allen Fundstellen zurück.
Replace*
string
Ersetzt Zeichen in einer Zeichenkette mit anderen auf Basis von regulären Ausdrücken.
164 _______________________________________________ 8.3 Reguläre Ausdrücke
Methode
Rückgabewert
Beschreibung
Split*
string[]
Splittet eine Zeichenkette in mehrere Bestandteile; die Separatoren werden als regulärer Ausdruck übergeben.
Unescape*
string
Konvertiert die maskierten Zeichen eines regulären Ausdrucks in die regulären Zeichen und stellt somit das Gegenstück zur Methode Escape dar.
GetGroupNames
string[]
Liefert ein Array mit den Namen aller Gruppen der Fundstellen.
GetGroupNumbers
int[]
Liefert ein Array mit den Nummern aller Gruppen der Fundstellen.
GroupNameFromNumber
string
Liefert den Namen einer Gruppe über deren Nummer.
GroupNumberFromName
int
Liefert die Nummer einer Gruppe über deren Namen.
IsMatch
bool
Arbeitet analog zur gleichnamigen statischen Methode auf Basis der aktuellen Instanz; die Übergabe eines regulären Ausdrucks als Parameter entfällt.
Match
Match
Arbeitet analog zur gleichnamigen statischen Methode auf Basis der aktuellen Instanz; die Übergabe eines regulären Ausdrucks als Parameter entfällt.
Matches
MatchCollection
Arbeitet analog zur gleichnamigen statischen Methode auf Basis der aktuellen Instanz; die Übergabe eines regulären Ausdrucks als Parameter entfällt.
Replace
string
Arbeitet analog zur gleichnamigen statischen Methode auf Basis der aktuellen Instanz; die Übergabe eines regulären Ausdrucks als Parameter entfällt.
Split
string[]
Arbeitet analog zur gleichnamigen statischen Methode auf Basis der aktuellen Instanz; die Übergabe eines regulären Ausdrucks als Parameter entfällt.
8.3.3
Die Regex-Optionen
Neben dem regulÙren Ausdruck kðnnen dem Konstruktur der Regex-Klasse eine Reihe von Optionen öbergeben werden. Diese werden von dem Enumerator RegexOptions angeboten und bitweise verodert an den Konstruktor öbergeben. SpÙter lassen sich die gesetzten Optionen öber die Eigenschaft Options abfragen,
8 Arbeiten mit Zeichenketten _____________________________________________165
allerdings nicht mehr Ùndern! Die Tabelle zeigt die zur Verfögung stehenden Optionen in der šbersicht. Tabelle 8.7 Die RegexOptions in der Übersicht Option
Beschreibung
Compiled
Bestimmt, ob der reguläre Ausdruck kompiliert oder zur Laufzeit interpretiert werden soll
ExplicitCapture
Bestimmt, dass nur explizit benannte oder nummerierte Gruppen in die GroupCollection der Match-Klasse aufgenommen werden sollen. Dies ermöglicht, Klammern zur Gliederung von Ausdrücken zu verwenden, ohne dass diese als Gruppen definiert werden.
IgnoreCase
Bestimmt, dass die Groß-/ Kleinschreibung ignoriert wird
IgnorePatternWhitespace
Entfernt unmaskierte Ausdruck
Multiline
Ändert die Bedeutung der Zeichen „^“ und „$“. Ist die Option gesetzt, beziehen sich diese Zeichen nicht auf Anfang und Ende der gesamten Zeichenkette, sondern nur auf jede einzelne Zeile.
None
Setzt alle Optionen zurück
RightToLeft
Bestimmt, ob die Suche von rechts nach links erfolgt soll
Singleline
Ändert die Bedeutung des Zeichens „.“, sodass dieses auf jedes beliebige Zeichen (einschließlich \n) zutrifft
Whitespaces
aus
dem
übergebenen
Das folgende Beispiel zeigt die Verwendung der Optionen MultiLine und RightToLeft. Es soll von rechts nach links nach allen Zeilen gesucht werden, die nicht mit einer Raute beginnen und somit als Kommentar gekennzeichnet sind. Listing 8.31 regexoptions1.cs using System; using System.Text.RegularExpressions; class ConsoleApp { static void Main() { string s1 = "# 1. Kommentar\n1. Zeile mit Text\n" + "2. Zeile mit Text\n# 2. kommentar"; RegexOptions ro = RegexOptions.Multiline | RegexOptions.RightToLeft; Regex r = new Regex(@"^[^#].+", ro);
Multiline und RightToLeft
166 _______________________________________________ 8.3 Reguläre Ausdrücke
foreach(Match m in r.Matches(s1)) Console.WriteLine("Zeile: " + m.Value + ""); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster zeigt nicht nur, dass die Erkennung der einzelnen Zeilen funktioniert, sondern auch, dass die Suche von rechts nach links erfolgt ist ± die zweite Zeile steht vor der ersten: Zeile: 2. Zeile mit Text Zeile: 1. Zeile mit Text
8.3.4
Erweiterte Parsing-Möglichkeiten
Als ErgÙnzung zu den gÙngigen Mðglichkeiten von regulÙren Ausdröcken bietet das .NET Framework weitergehende Mðglichkeiten zum Parsen von Zeichenketten an. Die Basis hierzu liefern benannte Gruppierungen innerhalb eines regulÙren Ausdrucks. Diese Gruppierungen werden in der Form (?Ausdruck) notiert. Der Zugriff auf die einzelnen Elemente erfolgt öber die Groups-Eigenschaft des Match-Objektes. Diese Eigenschaft liefert eine GroupCollection, die den Zugriff auf die einzelnen Gruppen in Form der Group-Klasse erlaubt. Da es sich um eine Collection handelt, kann der Indexer [..] verwendet werden. Das folgende Beispiel sucht eine E-Mail-Adresse aus einem Text ± das kennen Sie ja. Die Adresse wird aber in diesem Fall in die Elemente Benutzername (¹userª), Domain (¹domainª) und Top-Level-Domain (¹tldª) gruppiert. Die einzelnen Elemente werden anschlieûend im Konsolenfenster ausgegeben. eMail zerlegen Listing 8.32 parsing1.cs using System; using System.Text.RegularExpressions; class ConsoleApp { static void Main() { string s1 = "Hallo Welt, Ihr kðnnte mich unter " + "der Adresse [email protected] erreichen."; Regex r = new Regex(@"(?<user>[\w\.\-]+)@(?<domain>([\w\-]+\.) *[\w\-]{2,63})\.(?[a-zA-Z]{2,4})"); Match m = r.Match(s1);
8 Arbeiten mit Zeichenketten _____________________________________________167
Console.WriteLine("eMail: " + m.Value + ""); Console.WriteLine("Benutzer: " + m.Groups["user"].Value + ""); Console.WriteLine("Domain: " + m.Groups["domain"].Value + ""); Console.WriteLine("TLD: " + m.Groups["tld"].Value + ""); Console.ReadLine(); } }
Die einzelnen Elemente der E-Mail-Adresse werden korrekt erkannt und wie folgt im Konsolenfenster ausgegeben: eMail: [email protected] Benutzer: feedback Domain: csharp-shortcut TLD: de
Auch im folgenden Beispiel werden Gruppierungen verwenden. Der Zugriff erfolgt in diesem Fall allerdings öber die Result-Methode der Match-Klasse. Auch hier lassen sich die Gruppierungen einfach abfragen. Die Syntax lautet $n för den Zugriff öber die Gruppierungsnummer oder $[name} för den Zugriff öber den vergebenen Namen. Listing 8.33 parsing3.cs using System; using System.Text.RegularExpressions; class ConsoleApp { static void Main() { string s1 = "http://www.combit.net:80/"; Regex r = new Regex(@"^(?<proto>\w+)://[^/]+?:(?<port>\d+)?/", RegexOptions.Compiled); string s2 = r.Match(s1).Result("${proto}:${port}"); Console.WriteLine(s2); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: http:80
URL zerlegen
9 Erweiterte Datentypen
Arrays Collections Strukturen Aufzählungen
170 ___________________________________________________________________
Inhaltsübersicht Kapitel 9
9
Erweiterte Datentypen ....................................................................................171
9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3 9.4 9.4.1 9.4.2
Arrays ................................................................................................................171 Initialisierung und Zugriff auf Arrays................................................................171 Mehrdimensionale Arrays .................................................................................174 Eigenschaften und Methoden der Array-Klasse ................................................177 Collections.........................................................................................................181 ArrayList ...........................................................................................................182 Hashtable ...........................................................................................................183 Queue ................................................................................................................185 SortedList ..........................................................................................................187 Stack ..................................................................................................................188 Strukturen ..........................................................................................................189 AufzÙhlungen ....................................................................................................192 Standard-AufzÙhlungen .....................................................................................192 Flag-AufzÙhlungen ............................................................................................194
___________________________________________________________________ 171
9 Erweiterte Datentypen 9.1
Arrays
In den bisher vorgestellten Datentypen konnte jeweils ein Element abgelegt werden. Mðchte man mehrere Elemente des gleichen Typs ablegen, kann man entweder jeweils eine separate Variable anlegen oder ein Datenfeld verwenden. Diese Datenfelder nennt man Arrays. Alle Elemente werden hier unter einem gemeinsamen Variablennamen abgelegt. Der Zugriff erfolgt öber diesen Namen sowie einen fortlaufenden Index des jeweiligen Elements, immer beginnend bei 0. Arrays kðnnen von beliebigen Datentypen gebildet werden. Handelt es sich um einen Wertetyp, werden die Werte direkt im Array abgelegt, bei Verweistypen werden die jeweiligen Verweise im Array gespeichert.
9.1.1
Initialisierung und Zugriff auf Arrays
Ein Array wird wie eine regulÙre Variable deklariert, es erfolgt lediglich nach dem Datentyp eine eckige Klammer auf und zu. Im Folgenden wird ein Array des Datentyps string deklariert. Die Grðûe des Arrays wird hierbei noch nicht angegeben. Das Array enthÙlt zunÙchst keine Daten, sondern verweist auf null. string[] s;
Die Initialisierung der hinterlegten Werte kann auf zweierlei Weisen geschehen. Entweder es wird mit Hilfe des Schlösselwortes new ein neues Array von einem Datentyp erstellt. Hierbei wird auch die Grðûe des Arrays angegeben. Alternativ kðnnen die einzelnen Elemente dem Array direkt zugewiesen werden. Die Angabe der Werte erfolgt Komma-separiert in geschweiften Klammern. Die notwendige Grðûe ermittelt das Array selbststÙndig, auf diese Angabe kann daher verzichtet werden.
Datenfelder
172 __________________________________________________________ 9.1 Arrays
new
Intitialisierung mit new: string[] s1 = new string[2];
Direkte Zuweisung
Direkte Zuweisung: string[] s1 = {"xxx", "yyy"};
Initialisiert man ein Array öber das Schlösselwort new, so mössen die Inhalte separat zugewiesen werden. Hierzu bietet jedes Array einen Indexer an. In eckigen Klammern wird hierbei der fortlaufende und immer bei 0 beginnende Index angegeben. Ist ein Array wie im vorliegenden Beispiel auf 2 Elemente initialisiert, so erfolgt der Zugriff öber den Index 0 bis 1. string[] s1 = new string[2]; s1[0] = "xxx"; s1[1] = "yyy"; Indexer
Auch der spÙtere Lesezugriff auf die Elemente eines Arrays erfolgt öber den Indexer. Das folgende Beispiel gibt die zuvor initialisierten Elemente eines stringArrays im Konsolenfenster aus. Listing 9.1 arrays1.cs using System; class ConsoleApp { static void Main() { string[] s1 = new string[2]; s1[0] = "xxx"; s1[1] = "yyy"; // Alternativ: // string[] s1 = {"xxx", "yyy"}; Console.WriteLine(s1[0] + ""); Console.WriteLine(s1[1] + ""); Console.ReadLine(); } }
9 Erweiterte Datentypen ________________________________________________173
Ausgabe im Konsolenfenster: xxx yyy
Es gibt aber auch andere Mðglichkeiten, um auf die Elemente zuzugreifen. Durch den fortlaufenden Index bietet sich insbesondere eine for-Schleife an. Um also beispielsweise alle Elemente des zuvor initialisierten string-Arrays auszugeben, kann man eine for-Schleife wie folgt verwenden.
for-Schleife
Listing 9.2 arrays2.cs using System; class ConsoleApp { static void Main() { string[] s1 = new string[2]; s1[0] = "xxx"; s1[1] = "yyy"; // Alternativ: // string[] s1 = {"xxx", "yyy"}; for(int i=0; i<s1.Length; i++) Console.WriteLine(s1[i] + ""); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster ist identisch zum vorherigen Beispiel. Die Vorteile von Arrays werden hier aber wesentlich deutlicher, denn es ist vollkommen irrelevant, ob das Array 2, 200 oder 2000 Elemente enthÙlt. Die Schleife zur Ausgabe ist in jedem Fall identisch. Die Grðûe des Arrays wird öber die Eigenschaft Length abgefragt. Beachten Sie, dass diese Eigenschaft nur bei eindimensionalen Arrays verwendet werden kann. Bei mehrdimensionalen Arrays liefert die Eigenschaft die Gesamtanzahl aller Elemente in allen Dimension zuröck. Mehr zu mehrdimensionalen Arrays finden Sie weiter unten. Eine alternative Mðglichkeit zum Durchlaufen aller Elemente eines Arrays ist die foreach..in-Schleife. Die Ausgabe des folgenden Beispiels ist mit den vorherigen identisch, man spart sich allerdings die Index-Variable i und öberhaupt wird besser deutlich, dass man alle Elemente des Arrays durchlaufen mðchte: ¹För jeden string s2 im Array s1...ª.
foreach..in
174 __________________________________________________________ 9.1 Arrays
Listing 9.3 arrays3.cs using System; class ConsoleApp { static void Main() { string[] s1 = new string[2]; s1[0] = "xxx"; s1[1] = "yyy"; // Alternativ: // string[] s1 = {"xxx", "yyy"}; foreach(string s2 in s1) Console.WriteLine(s2 + ""); Console.ReadLine(); } }
9.1.2
Mehrdimensionale Arrays
Die bisher vorgestellten Arrays haben Elemente in einer Dimension abgelegt. Es kðnnen aber auch mehrdimensionale Arrays angelegt werden, bei denen der Zugriff auf einzelne Elemente nicht öber einen Index, sondern öber mehrere erfolgt. Es entsteht eine Matrix, die mit einem Koordinatensystem vergleichbar ist; um einen Punkt zu beschreiben, benðtigt man den x- und den y-Wert. Auch bei der Deklaration eines mehrdimensionalen Arrays mössen alle Dimensionen angegeben werden. Listing 9.4 arrays1.cs using System; class ConsoleApp { static void Main() { string[,] s1 = new string[2, 2]; s1[0, 0] = "xxx"; s1[0, 1] = "abc"; s1[1, 0] = "yyy"; s1[1, 1] = "def";
9 Erweiterte Datentypen ________________________________________________175
// Alternativ: //string[,] s1 = {{"xxx", "abc"}, {"yyy", "def"}}; Console.WriteLine(s1[0, Console.WriteLine(s1[0, Console.WriteLine(s1[1, Console.WriteLine(s1[1,
0] 1] 0] 1]
+ + + +
""); ""); ""); "");
Console.ReadLine(); } }
Ausgabe im Konsolenfenster: xxx abc yyy def
Auch bei mehrdimensionalen Arrays kðnnen Schleifen verwendet werden, um diese zu durchlaufen. Im Falle einer for-Schleife benðtigt man zwei Schleifen mit zwei Index-Variablen. Listing 9.5 arrays2.cs using System; class ConsoleApp { static void Main() { string[,] s1 = new string[2, 2]; s1[0, 0] = "xxx"; s1[0, 1] = "abc"; s1[1, 0] = "yyy"; s1[1, 1] = "def"; // Alternativ: //string[,] s1 = {{"xxx", "abc"}, {"yyy", "def"}}; for(int i=0; i<s1.GetLength(0); i++) { for(int j=0; j<s1.GetLength(1); j++) Console.WriteLine(s1[i,j] + ""); }
for-Schleife
176 __________________________________________________________ 9.1 Arrays
Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster ist erneut identisch. Auch die Verwendung einer foreach..in-Schleife ist mðglich. Hier benðtigt man allerdings wie im Fall eines eindimensionalen Arrays nur eine Schleife. foreach..in liefert in jedem Fall alle Elemente und durchlÙuft das Array im Fall mehrerer Dimensionen von oben nach unten und links nach rechts. foreach..in Listing 9.6 arrays3.cs using System; class ConsoleApp { static void Main() { string[,] s1 = new string[2, 2]; s1[0, 0] = "xxx"; s1[0, 1] = "abc"; s1[1, 0] = "yyy"; s1[1, 1] = "def"; // Alternativ: //string[,] s1 = {{"xxx", "abc"}, {"yyy", "def"}}; foreach(string s2 in s1) Console.WriteLine(s2 + ""); Console.ReadLine(); } }
Auch hier ergibt sich die identische Ausgabe im Konsolenfenster: xxx abc yyy def
9 Erweiterte Datentypen ________________________________________________177
9.1.3
Eigenschaften und Methoden der Array-Klasse
Ein Array ist im Grunde nichts anderes als ein spezieller Datentyp, der durch die Klasse mit dem Namen Array reprÙsentiert wird. Wie jede Klasse verfögt auch das Array öber Eigenschaften und Methoden. Eine Auswahl interessanter Mitglieder wird im Folgenden vorgestellt. Eine šbersicht finden Sie in der Tabelle weiter unten. Statische Methoden sind mit einem * gekennzeichnet.
IndexOf* Die statische Methode liefert den ersten Index eines bestimmten Objektes in einem eindimensionalen Array zuröck. Es handelt sich also um eine Mðglichkeit, ein Array zu durchsuchen. Intern wird för jedes Element die von object abgeleitete Methode Equals aufgerufen. Es hÙngt daher vom Typ der Elemente ab, ob der Wert oder der Verweis verglichen wird. Im nachfolgenden Beispiel wird in einem string-Array gesucht. Hier wird der Wert verglichen. Position ermitteln Listing 9.7 indexof1.cs using System; class ConsoleApp { static void Main() { string[] s1 = new string[4]; s1[0] = "xxx"; s1[1] = "yyy"; s1[2] = "aaa"; s1[3] = "bbb"; int i = Array.IndexOf(s1, "yyy"); Console.WriteLine("Der gesuchte String befindet sich " + "an der Position " + i.ToString()); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Der gesuchte String befindet sich an der Position 1
178 __________________________________________________________ 9.1 Arrays
Enthält?
Wird das öbergebene Objekt nicht gefunden, liefert die Methode ±1 zuröck. Auf diese Weise lÙsst sich sehr einfach eine Contains-Methode realisieren. Listing 9.8 indexof2.cs using System; class ConsoleApp { public static void Contains(object[] arr, object value) { Console.Write("Das Objekt " + value.ToString() + " ist "); if(Array.IndexOf(arr, value)==-1) Console.Write("nicht "); Console.Write("im Array enthalten\n"); } static void Main() { string[] s1 = new string[4]; s1[0] = "xxx"; s1[1] = "yyy"; s1[2] = "aaa"; s1[3] = "bbb"; Contains(s1, "abc"); Contains(s1, "yyy"); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Das Objekt abc ist nicht im Array enthalten Das Objekt yyy ist im Array enthalten
Weitere Suchmðglichkeiten stehen mit den Methoden BinarySearch und LastIndexOf zur Verfögung.
Reverse* Array umdrehen
Auch diese Methode ist statisch in der Klasse Array implementiert. Sie dreht die Ordnung der Elemente eines öbergebenen eindimensionalen Arrays um. Das erste Element steht anschlieûend an letzter Stelle, das letzte an erster.
9 Erweiterte Datentypen ________________________________________________179
Listing 9.9 reverse1.cs using System; class ConsoleApp { static void Main() { string[] s1 = new string[4]; s1[0] = "xxx"; s1[1] = "yyy"; s1[2] = "aaa"; s1[3] = "bbb"; Array.Reverse(s1); foreach(string s2 in s1) Console.WriteLine(s2 + ""); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: bbb aaa yyy xxx
Sort* Die Methode Sort Ùndert ebenfalls die Reihenfolge der Elemente eines eindimensionalen Arrays. Allerdings werden diese entsprechend Ihrem Inhalt alphanumerisch sortiert. Listing 9.10 sort1.cs using System; class ConsoleApp { static void Main() { string[] s1 = new string[4]; s1[0] = "xxx"; s1[1] = "yyy";
Array sortieren
180 __________________________________________________________ 9.1 Arrays
s1[2] = "aaa"; s1[3] = "bbb"; Array.Sort(s1); foreach(string s2 in s1) Console.WriteLine(s2 + ""); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: aaa bbb xxx yyy
Übersicht Die folgenden beiden Tabellen enthalten alle Eigenschaften und Methoden der Array-Klasse in der šbersicht. Statische Methoden sind mit einem * gekennzeichnet. Diese Methoden werden öber den Namen der Klasse, Array, aufgerufen, alle anderen Methoden werden öber eine Instanz der Klasse aufgerufen. Tabelle 9.1 Die Eigenschaften der Array-Klasse in der Übersicht Eigenschaft
Rückgabewert
Beschreibung
IsFixedSize
bool
Gibt zurück, ob das Array eine feste Größe hat.
IsReadOnly
bool
Gibt zurück, ob das Array schreibgeschützt ist.
Length
int
Gesamtanzahl aller Elemente eines Arrays.
Rank
int
Anzahl der Dimensionen eines Arrays minus 1; bei eindimensionalen Arrays immer 0.
Tabelle 9.2 Die Methoden der Array-Klasse in der Übersicht Methode
Rückgabewert Beschreibung
BinarySearch*
int
Sucht einen übergebenen Wert binär in einem Array und liefert den Index der ersten Fundstelle zurück.
Clear*
-
Setzt den Inhalt eines Arrays komplett zurück.
IndexOf*
int
Sucht ein übergebenes Objekt in einem Array und liefert den Index der ersten Fundstelle zurück.
9 Erweiterte Datentypen ________________________________________________181
Methode
Rückgabewert Beschreibung
LastIndexOf*
int
Sucht ein übergebenes Objekt in einem Array und liefert den Index der letzten Fundstelle zurück.
Reverse*
-
Dreht die Anordnung aller Elemente eines eindimensionalen Arrays um; das erste Element steht anschließend an letzter Stelle, das letzte an erster.
Sort*
-
Sortiert alle Elemente eines eindimensionalen Arrays.
GetLength
int
Gibt die Anzahl der Elemente in einer angegebenen Dimension. Es wird der Index der Dimension beginnend bei 0 angegeben. Bei eindimensionalen Arrays kann alternativ die Eigenschaft Length verwendet werden.
GetLowerBound
int
Gibt die untere Begrenzung einer Dimension des Arrays zurück, muss immer 0 sein.
GetUpperBound
int
Gibt die obere Begrenzung einer Dimension des Arrays zurück, entspricht immer GetLength–1
GetValue
object
Gibt den Inhalt eines angegebenen Elements des Arrays zurück. Der Zugriff kann analog auch über den Indexer erfolgen, daher ist die Verwendung dieser Methoden in der Regel nicht notwendig.
Initialize
-
Initialisiert alle einzelnen Elemente eines Arrays explizit, indem es den jeweiligen Konstruktur aufruft.
SetValue
-
Setzt den Inhalt eines angegebenen Elements des Arrays. Der Zugriff kann analog auch über den Indexer erfolgen, daher ist die Verwendung dieser Methoden in der Regel nicht notwendig.
9.2
Collections
Innerhalb des Namespaces System.Collection fasst das .NET Framework eine ganze Reihe von unterschiedlichen Collections und Listen zusammen. Die einzelnen Klassen unterscheiden sich in der Art und Weise, wie die Elemente abgelegt und wie wieder auf diese zugegriffen werden kann. Die Tabelle fasst einige wichtige Collections zusammen. Tabelle 9.3 Wichtige Collections in der Übersicht Collection
Beschreibung
ArrayList
Stellt ein dynamisches Array zur Verfügung, zu dem Elemente hinzugefügt und auch wieder entfernt werden können.
Hashtable
Stellt eine Hashtable zur Verfügung, bei der Elemente mit einem Namen versehen werden können. Der Zugriff auf die Elemente kann anschließend über den vergebenen Namen erfolgen.
182 ______________________________________________________ 9.2 Collections
Collection
Beschreibung
Queue
Stellt eine Queue nach dem First-in-first-out-Prinzip (fifo) zur Verfügung. Elemente werden an die Collection angehängt und können später hier herausgenommen werden. Hierbei wird das Objekt aus der Queue entfernt.
SortedList
Arbeitet ähnlich wie eine Hashtable, stellt die Elemente aber sortiert nach deren Elementnamen zur Verfügung.
Stack
Dieses Gegenstück zur Queue implementiert eine Collection nach dem Last-in-first-out-Prinzip (lifo). Der letzte hinzugefügte Wert wird als Erster wieder herausgenommen.
9.2.1
ArrayList
Die ArrayList stellt ein dynamisches Array zur Verfögung, zu dem Elemente hinzugefögt und auch wieder entfernt werden kðnnen. Der Zugriff erfolgt öber den Index oder öber eine foreach..in-Schleife Dynamisches Array
Listing 9.11 arraylist1.cs using System; using System.Collections; class ConsoleApp { static void Main() { ArrayList al = new ArrayList(); al.Add("Hallo"); al.Add("Welt"); al.Add("!"); for(int i=0; i
Ausgabe im Konsolenfenster: Hallo Welt !
9 Erweiterte Datentypen ________________________________________________183
Listing 9.12 arraylist2.cs using System; using System.Collections; class ConsoleApp { static void Main() { ArrayList al = new ArrayList(); al.Add("Hallo"); al.Add("Welt"); al.Add("!"); foreach(string s in al) Console.WriteLine(s + ""); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster ist identisch.
9.2.2
Hashtable
Die Hashtable stellt eine Liste zur Verfögung, bei der Elemente mit einem eindeutigen Namen versehen werden kðnnen. Der Zugriff auf die Elemente kann anschlieûend öber den vergebenen Namen erfolgen. Listing 9.13 hastable1.cs using System; using System.Collections; class ConsoleApp { static void Main() { Hashtable ht = new Hashtable(); ht.Add("Wort 1", "Hallo"); ht.Add("Wort 2", "Welt"); ht.Add("Wort 3", "!"); Console.WriteLine(ht["Wort 1"] + ""); Console.WriteLine(ht["Wort 2"] + ""); Console.WriteLine(ht["Wort 3"] + "");
184 ______________________________________________________ 9.2 Collections
Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Hallo Welt !
Alternativ ist auch bei Hashtables der Zugriff öber eine foreach..in-Schleife mðglich. Hierbei kommt das Objekt DictionaryEntry zum Einsatz, das einen einzelnen Eintrag reprÙsentiert. Man kann sich bei einer Hashtable allerdings nicht darauf verlassen, dass die öbergebenen Werte in der Reihenfolge der Ablage gespeichert werden. Im folgenden Beispiel ergibt sich daher eine geÙnderte Ausgabe. foreach..in Listing 9.14 hashtable2.cs using System; using System.Collections; class ConsoleApp { static void Main() { Hashtable ht = new Hashtable(); ht.Add("Wort 1", "Hallo"); ht.Add("Wort 2", "Welt"); ht.Add("Wort 3", "!"); foreach(DictionaryEntry de in ht) Console.WriteLine(de.Value + ""); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Welt ! Hallo
9 Erweiterte Datentypen ________________________________________________185
Die Hashtable-Klasse implementiert die IDictionaryEnumerator-Schnittstelle. Auf diese Weise ist ein Zugriff öber eine AufzÙhlung (Enumerator) Ùhnlich wie beim Durchlaufen einer Tabelle mðglich. Die Ausgabe im Konsolenfenster ist hier analog zum vorherigen Beispiel scheinbar unsortiert.
Enumerator
Listing 9.15 hashtable3.cs using System; using System.Collections; class ConsoleApp { static void Main() { Hashtable ht = new Hashtable(); ht.Add("Wort 1", "Hallo"); ht.Add("Wort 2", "Welt"); ht.Add("Wort 3", "!"); IDictionaryEnumerator denum = ht.GetEnumerator(); while(denum.MoveNext()) Console.WriteLine(denum.Key + ": " + denum.Value + ""); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Wort 2: Welt Wort 3: ! Wort 1: Hallo
9.2.3
Queue
Eine Queue wird in der Regel zur Abarbeitung von AuftrÙgen verwendet. Die AuftrÙge werden dabei in der Queue abgelegt (Methode Enqueue) und zum Zeitpunkt der Verarbeitung wieder abgeholt (Methode Dequeue). Das erste Element, das abgelegt wird, ist auch das erste Element, das wieder abgeholt wird. Es entsteht ein Ringpuffer nach dem First-in-first-out-Prinzip (fifo).
first-in-first-out
186 ______________________________________________________ 9.2 Collections
Listing 9.16 queue1.cs using System; using System.Collections; class ConsoleApp { static void Main() { Queue q = new Queue(); q.Enqueue("Hallo"); q.Enqueue("Welt"); q.Enqueue("!"); while(q.Count>0) { string s = (string) q.Dequeue(); Console.WriteLine(s + ""); } Console.ReadLine(); } }
Ausgabe im Konsolenfenster : Hallo Welt !
Mit Hilfe der Eigenschaft Count und einer while-Schleife lassen sich die Elemente sehr gut abarbeiten, denn durch den Aufruf von Dequeue wird das Element nicht nur abgefragt, sondern auch aus der Queue entfernt. Mðchte man allerdings lediglich auf das Objekt zugreifen und dieses nicht aus der Queue lðschen, kann die Methode Peek verwendet werden. Diese fragt das oberste Element der Queue ab und verÙndert die Queue nicht. Der Zugriff auf die weiteren Elemente der Queue ist auf diese Weise nicht mðglich. Listing 9.17 queue2.cs using System; using System.Collections; class ConsoleApp { static void Main() { Queue q = new Queue();
9 Erweiterte Datentypen ________________________________________________187
q.Enqueue("Hallo"); q.Enqueue("Welt"); q.Enqueue("!"); string s = (string) q.Peek(); Console.WriteLine(s + ""); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Hallo
9.2.4
SortedList
Die Klasse SortedList arbeitet Ùhnlich wie die Hashtable. Auch hier werden mit einem eindeutigen Namen versehene Elemente abgelegt. Der Zugriff ist auch hier öber den Namen sowie den Index mðglich. Dieser Index und somit die Anordnung der Elemente innerhalb der Collection erfolgt automatisch sortiert nach dem Namen. Obwohl im folgenden Beispiel die Werte unsortiert öbergeben werden, erfolgt die Ausgabe in der richtigen Reihenfolge. Auch hier kommt die DictionaryEntryKlasse zum Einsatz, die einen einzelnen Eintrag in der SortedList reprÙsentiert. Listing 9.18 sortedlist1.cs using System; using System.Collections; class ConsoleApp { static void Main() { SortedList sl = new SortedList(); sl.Add("Wort 2", "Welt"); sl.Add("Wort 3", "!"); sl.Add("Wort 1", "Hallo"); foreach(DictionaryEntry de in sl) Console.WriteLine(de.Value + "");
188 ______________________________________________________ 9.2 Collections
Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Hallo Welt !
9.2.5 last-in-first-out
Stack
Der Stack ist das Gegenstöck zur Queue. Die Klasse geht Ùhnlich vor, implementiert die Collection allerdings nach dem Last-in-first-out-Prinzip (lifo). Der letzte hinzugefögte Wert wird als Erster wieder herausgenommen. Zum Ablegen eines neuen Elements wird die Methode Push verwendet. Die Methode Pop fragt ein Element ab und lðscht dieses aus dem Stack. Die Methode Peek fragt wie bei der Klasse Queue den obersten Wert ab, lðscht ihn aber nicht. Listing 9.19 stack1.cs using System; using System.Collections; class ConsoleApp { static void Main() { Stack st = new Stack(); st.Push("Hallo"); st.Push("Welt"); st.Push("!"); while(st.Count>0) { string s = (string) st.Pop(); Console.WriteLine(s + ""); } Console.ReadLine(); } }
9 Erweiterte Datentypen ________________________________________________189
Da nach dem lifo-Prinzip gearbeitet wird, erfolgt die Ausgabe umgekehrt zur Ablage. Ausgabe im Konsolenfenster: ! Welt Hallo
9.3
Strukturen
Strukturen sind Klassen sehr Ùhnlich, unterscheiden sich aber intern in einem wichtigen Punkt. Strukturen sind Wertetypen und nicht wie Klassen Verweistypen. Aus diesem Grund eignen sich Strukturen besonders för kleine Datenmengen, die wie ein Datentyp behandelt werden sollen und auf diese Weise unter UmstÙnden sehr hÙufig in einem Programm verwendet werden. Die Syntax von Strukturen entspricht der von Klassen, sie werden im Unterschied zu diesen aber mit dem Schlösselwort struct eingeleitet. Das folgende Beispiel zeigt ein sehr hÙufig verwendetes Beispiel för eine Struktur. Zwei Dezimalwerte x und y sollen einen Punkt (Point) in einem Koordinatensystem reprÙsentieren. Listing 9.20 struct1.cs using System; class ConsoleApp { public struct Point { public int x; public int y; } static { Point p.x = p.y =
void Main() p = new Point(); 15; 9;
Console.WriteLine("x: " + p.x.ToString() + ""); Console.WriteLine("y: " + p.y.ToString() + ""); Console.ReadLine(); } }
Eigene Datentypen
190 _______________________________________________________ 9.3 Strukturen
Ausgabe im Konsolenfenster: x: 15 y: 9
Die Syntax von Strukturen entspricht ± wie beschrieben ± der von Klassen. Insofern kðnnen Sie durchaus auch einen Konstruktor definieren, dem die Werte för x und y öbergeben werden. Es ist allerdings nicht mðglich, einen Konstruktor ohne Parameter explizit zu deklarieren. Dieser wird von .NET implizit angenommen. Alle Werte werden in diesem Fall auf null gesetzt. Eine explizite Deklaration der einzelnen Werte ist ebenfalls nicht mðglich. Ein Punkt als Datentyp
Listing 9.21 struct2.cs using System; class ConsoleApp { public struct Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } } static void Main() { Point p = new Point(15, 9); Console.WriteLine("x: " + p.x.ToString() + ""); Console.WriteLine("y: " + p.y.ToString() + ""); Console.ReadLine(); } }
Die Ausgabe entspricht dem vorigen Beispiel. Das Schlösselwort this im neu angelegten Konstruktur ist notwendig, damit deutlich wird, dass sich die Zuweisung auf das Element der Struktur und nicht auf die gleichnamige lokale Variable bezieht.
9 Erweiterte Datentypen ________________________________________________191
Oft wird auch die von object implizit geerbte ToString-Methode öberschrieben. Das folgende Beispiel zeigt, wie x und y öber eine implizite Typenumwandlung formatiert ausgegeben werden. Mit Ausnahme dieser impliziten Vererbung ist es öbrigens nicht mðglich, Strukturen von anderen Klassen oder Strukturen abzuleiten. Auch kðnnen andere Klassen nicht von Strukturen abgeleitet werden. Listing 9.22 struct3.cs using System; class ConsoleApp { public struct Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return(string.Format("x: {0}, y: {1}", x, y)); } } static void Main() { Point p = new Point(15, 9); Console.WriteLine(p); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: x: 15, y: 9
192 ____________________________________________________ 9.4 Aufzählungen
9.4 Enumerations
Aufzählungen
AufzÙhlungen oder auch Enumerations sind immer dann nötzlich, wenn eine Variable nur eine Auswahl von verschiedenen Status annehmen kðnnen soll. Es handelt sich also um eine Art Zustandsbeschreibung. Strukturen werden mit dem Schlösselwort enum eingeleitet. Innerhalb des folgenden Blocks werden die mðglichen Werte Komma-separiert notiert. Der Zugriff auf diese Elemente erfolgt spÙter öber den Namen der AufzÙhlung, gefolgt vom gewönschten Elementnamen. Intern arbeiten AufzÙhlungen soweit nicht explizit anders angegeben auf Basis eines int-Wertes, der fortlaufend beginnend bei 0 för jedes Element vergeben wird.
9.4.1
Standard-Aufzählungen
Das folgende Beispiel zeigt eine AufzÙhlung, die öber drei verschiedene Status verfögt. Es soll das Startverhalten eines Windows-Dienstes beschrieben werden. Es kommen hierför nur drei mðgliche Werte in Frage. šber eine switch-Abfrage wird der Status in Klartext umgewandelt und ausgegeben. Listing 9.23 enum1.cs using System; class ConsoleApp { public enum ServiceStartup { Disabled, Automatic, Manual } static void Main() { ServiceStartup start = ServiceStartup.Automatic; string s = ""; switch(start) { case ServiceStartup.Disabled : s = "Deaktiviert"; break; case ServiceStartup.Automatic : s = "Automatisch";
9 Erweiterte Datentypen ________________________________________________193
break; case ServiceStartup.Manual : s = "Manuell"; break; } Console.WriteLine("Startverhalten: " + s); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Startverhalten: Automatisch
Es ist auch mðglich, die zu verwendenden Elementwerte explizit anzugeben. Hierzu wird der gewönschte Wert hinter dem Elementnamen notiert. Wird den nachfolgenden Elementen kein Wert zugewiesen, werden diese fortlaufend ab dem angegebenem Wert angenommen. Das folgende Beispiel visualisiert diesen Vorgang. För Disabled wird der Wert 5 angenommen, das nachfolgende Element Automatic mösste somit den Wert 6 erhalten. Der tatsÙchlich gesetzte Wert wird öber eine doppelte Typenumwandlung zunÙchst in int und dann in string ermittelt. Listing 9.24 enum2.cs using System; class ConsoleApp { public enum ServiceStartup { Disabled = 5, Automatic, Manual } static void Main() { ServiceStartup start = ServiceStartup.Automatic; string s = ""; switch(start) {
194 ____________________________________________________ 9.4 Aufzählungen
case ServiceStartup.Disabled : s = "Deaktiviert"; break; case ServiceStartup.Automatic : s = "Automatisch"; break; case ServiceStartup.Manual : s = "Manuell"; break; } Console.WriteLine("Startverhalten: " + s); Console.WriteLine(" => " + ((int) start).ToString()); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Startverhalten: Automatisch => 6
9.4.2 Flag Enumerations
Flag-Aufzählungen
Ein weiteres Einsatzgebiet von AufzÙhlungen sind Flags. Diese werden verwendet, um beispielsweise einer Methode einen Satz an Optionen mitzugeben. Es kðnnen dabei eine beliebige Kombination der verfögbaren Optionen gesetzt sein. Flags arbeiten intern auf der Basis von einem binÙren Wert, bei dem jedes Bit eine Option widerspiegelt. Dementsprechend mössen die Werte der zur Verfögung stehenden Elemente jeweils einem Bit zugeordnet werden. Dies kann entweder öber die entsprechende Dezimalzahl oder die besser lesbare Hexadezimalzahl geschehen. Um mehrere Optionen zu setzen, werden die gewönschten Elemente der AufzÙhlung verodert zugewiesen. Damit dies mðglich ist, muss das Attribut ¹Flagsª för die AufzÙhlung gesetzt sein.
9 Erweiterte Datentypen ________________________________________________195
Listing 9.25 enumflags1.cs using System; class ConsoleApp { [Flags] public enum ShowValues { Value1 = 0x00000001, Value2 = 0x00000002, Value3 = 0x00000004, } static void Main() { ShowValues show =
ShowValues.Value1 | ShowValues.Value3;
if((show & ShowValues.Value1) == ShowValues.Value1) { Console.WriteLine("Wert 1 wird ausgegeben"); } if((show & ShowValues.Value2) == ShowValues.Value2) { Console.WriteLine("Wert 2 wird ausgegeben"); } if((show & ShowValues.Value3) == ShowValues.Value3) { Console.WriteLine("Wert 3 wird ausgegeben"); } Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster zeigt, dass tatsÙchlich nur das erste und dritte Bit gesetzt sind: Wert 1 wird ausgegeben Wert 3 wird ausgegeben
Derartige Konstrukte finden im .NET Framework an vielen Stellen Anwendung, eine konsequente Fortföhrung dieses Konzeptes in eigenen Programmen ist daher schon aus diesem Grund sinnvoll.
10 Konvertierungen
Konvertierung von numerischen Werten Konvertierung zu string Konvertierung von Verweistypen Die Klasse Convert
198 ___________________________________________________________________
Inhaltsübersicht Kapitel 10
10
Konvertierungen.............................................................................................199
10.1 10.2 10.3 10.4
Konvertierung von numerischen Werten ...........................................................199 Konvertierung zu string .....................................................................................201 Konvertierung von Verweistypen ......................................................................204 Die Klasse Convert............................................................................................209
___________________________________________________________________ 199
10 Konvertierungen Um Daten von einem Datentyp in einen anderen umzuwandeln, ist eine Konvertierung der Datentypen notwendig. Dieser Vorgang wird im Englischen als type casting bezeichnet. Prinzipiell unterscheidet C# zwischen impliziten und expliziten Konvertierungen. Implizite Konvertierungen werden durch einfache Zuweisung der Variablen unterschiedlichen Typs vorgenommen. Hierbei kann der Zieldatentyp die Quelle vollstÙndig abbilden, sodass keinerlei Datenverluste durch die Konvertierung auftreten kðnnen. Sofern eine Implementierung einer impliziten Konvertierung vorliegt, wird diese vom Compiler akzeptiert und immer zu einem göltigen Ergebnis föhren. Anders sieht es bei der expliziten Konvertierung aus. Hier muss ausdröcklich eine Konvertierung notiert werden. Auch wenn der Compiler diese prinzipiell akzeptiert, kann sie spÙter fehlschlagen. Die explizite Konvertierung erfordert die Notation des gewönschten Ziel-Datentyps in runden Klammern. Es ergibt sich das folgende Schema: NeueVariable = (NeuerDatentyp) AlteVariable;
Eigentlich implizit mðgliche Konvertierungen kðnnen immer auch explizit nach diesem Schema vorgenommen werden.
10.1 Konvertierung von numerischen Werten Die Grundlagen der Konvertierung von numerischen Werten wurde bereits zu Beginn des Kapitels kurz beschrieben. Je nach Ziel- und Quelldatentyp steht eine implizite oder eine explizite Konvertierung zur Verfögung. Eine implizite Konvertierung ist immer in grðûere Datentypen mðglich, da hier keine Datenverluste auftreten kðnnen. Ist der Zieldatentyp hingegen kleiner als der Quelldatentyp, ist eine explizite Konvertierung erforderlich. Die beiden folgenden Beispiele demonstrieren noch einmal das Vorgehen. Der Datentyp int ist grðûer als short und daher ist eine Konvertierung von short zu int implizit mðglich. Andersherum ist eine Konvertierung von int zu short nur explizit mðglich.
200 ______________________________ 10.1 Konvertierung von numerischen Werten
Implizite Konvertierung
Listing 10.1 convert1.cs using System; class ConsoleApp { static void Main() { short n1 = 15; int n2; n2 = n1; Console.WriteLine(n2.ToString()); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: 15 Explizite Konvertierung
Listing 10.2 convert2.cs using System; class ConsoleApp { static void Main() { int n1 = 15; short n2; n2 = (short) n1; Console.WriteLine(n2.ToString()); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: 15
Die Tabelle zeigt, welche Datentypen implizit und somit sicher in welche anderen Datentypen konvertiert werden kðnnen.
10 Konvertierungen ____________________________________________________201
Tabelle 10.1 Implizite Konvertierungen von numerischen Werten Quelldatentyp
C# Alias
Mögliche Zieldatentypen
Byte
byte
Char, UInt16, Int16, UInt32, Int32, UInt64, Int64, Single, Double, Decimal, Object
SByte
sbyte
Int16, Int32, Int64, Single, Double, Decimal, Object
Int16
short
Int32, Int64, Single, Double, Decimal, Object
UInt16
ushort
UInt32, Int32, UInt64, Int64, Single, Double, Decimal, Object
Char
char
UInt16, UInt32, Int32, UInt64, Int64, Single, Double, Decimal, Object
Int32
int
Int64, Single, Double, Decimal, Object
UInt32
uint
Int64, Single, Double, Decimal, Object
Int64
long
Single, Double, Decimal, Object
UInt64
ulong
Single, Double, Decimal, Object
Single
float
Double, Object
Double
double
Object
Decimal
Decimal
Object
10.2 Konvertierung zu string Die Mutter aller Objekte, der Datentyp object, implementiert eine Methode ToString. Da sich alle anderen Objekte von diesem Datentyp ableiten,
implementiert jedes Objekt diese Methode. Wird beim Ableiten die Methode öberschrieben, kann ein sinnvoller Wert ausgegeben werden. Ist dies nicht der Fall, so wird lediglich der Name des Datentyps ausgegeben. Die Konvertierung einer numerischen Variable zur Ausgabe als string wurde bereits in den vergangenen Beispielen verwendet. Diese ist hier notwendig, um den Wert mit Hilfe der Methode Write in das Konsolenfenster auszugeben. ToString Listing 10.3 convertstring1.cs using System; class ConsoleApp { static void Main() { int n = 514044; string s; s = n.ToString(); Console.WriteLine(n);
202 ___________________________________________ 10.2 Konvertierung zu string
Console.ReadLine(); } }
Ausgabe im Konsolenfenster: 514044
Versucht man die Methode mit einem Objekt zu verwenden, das diese nicht explizit öberschreibt, so wird lediglich der Datentyp-Name des Objekts ausgegeben. Das folgende Beispiel zeigt die Verwendung einer Struktur Point, die einen Punkt im Koordinatensystem mit Hilfe zweier int-Werte x und y reprÙsentieren soll. Listing 10.4 convertstring2.cs using System; class ConsoleApp { public struct Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } } static void Main() { Point p = new Point(15, 9); Console.WriteLine(p); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster zeigt nicht das gewönschte Ergebnis, denn die Information öber den Objektnamen ist wenig aussagekrÙftig. ConsoleApp+Point
10 Konvertierungen ____________________________________________________203
Wird die Methode ToString jedoch öberschrieben und mit einer sinnvollen Implementierung versehen, ist das Ergebnis gleich viel ansehnlicher. Es wird ein formatierter string zuröckgegeben, der den Inhalt der beiden Mitgliedsvariablen x und y enthÙlt. Listing 10.5 convertstring3.cs using System; class ConsoleApp { public struct Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return(string.Format("x: {0}, y: {1}", x, y)); } } static void Main() { Point p = new Point(15, 9); Console.WriteLine(p); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: x: 15, y: 9
ToString überladen
204 ____________________________________ 10.3 Konvertierung von Verweistypen
10.3 Konvertierung von Verweistypen Die Konvertierung von Verweistypen funktioniert Ùhnlich wie bei den zuvor beschriebenen numerischen Wertetypen. Die Konvertierung erfolgt nach dem gleichen Schema: NeueVariable = (NeuerDatentyp) AlteVariable;
Bei Verweistypen wird allerdings nicht der numerische Inhalt der Variable zu Grunde gelegt, sondern die Erbhierarchie der jeweiligen Klasse. Die Vererbung von Klassen wird unter der šberschrift ¹Klassen & objektorientierte Programmierung ± Teil IIª weiter unten ausföhrlich erklÙrt. Das folgende Beispiel verwendet die dort erlÙuterten Techniken. Listing 10.6 converref1.cs using System; class BasisKlasse { public virtual string Test() { return("BasisKlasse.Test"); } } class ErbKlasse : BasisKlasse { public override string Test() { return("ErbKlasse.Test"); } public string Test2() { return("ErbKlasse.Test2"); } }
class ConsoleApp { static void Main() { ErbKlasse Erb = new ErbKlasse(); Console.WriteLine(Erb.Test()); Console.WriteLine(Erb.Test2());
10 Konvertierungen ____________________________________________________205
BasisKlasse Basis = (BasisKlasse) Erb; Console.WriteLine(Basis.Test()); // Wörde zu einem Fehler föhren: // Console.WriteLine(Basis.Test2()); ErbKlasse Erb2 = (ErbKlasse) Basis; Console.WriteLine(Erb2.Test2()); Console.ReadLine(); } }
Was passiert hier? Es existiert eine Basisklasse mit einer Implementierung der Methode Test. Eine zweite Klasse wird von der ersten abgeleitet und öberschreibt die Methode Test. Zudem stellt diese Klasse eine weitere Methode mit dem Namen Test2 zur Verfögung. Im Aufruf der Klassen wird die abgeleitete Klasse instanziiert. Es erfolgt der Aufruf der beiden enthaltenen Methoden Test und Test2. Anschlieûend erfolgt eine explizite Konvertierung in die Basisklasse. Diese ist mðglich, da eine Vererbung immer eine ¹ist ein(e)ª-Beziehung darstellt; ErbKlasse ¹ist ein(e)ª BasisKlasse. Es erfolgt erneut der Aufruf der Methode Test. Der Aufruf föhrt allerdings nicht zur BasisKlasse, sondern zur abgeleiteten Klasse. Dies ist richtig, denn es handelt sich ja ursprönglich um die Instanz der ErbKlasse, auch wenn die Sicht auf diese Instanz nun öber die BasisKlasse erfolgt. Dies wird deutlich, sofern man versucht, die Methode Test2 aufzurufen. Diese ist in der BasisKlasse nicht implementiert und daher wörde Aufruf zu einem Fehler föhren. Da die urspröngliche Instanz vom Typ ErbKlasse ist, ist auch eine RöckwÙrtskonvertierung vom Typ BasisKlasse in den Typ ErbKlasse mðglich. Der Aufruf der Methode Test2 beweist, dass diese erfolgreich war. Die Ausgabe im Konsolenfenster zeigt das Ergebnis des Beispiels: ErbKlasse.Test ErbKlasse.Test2 ErbKlasse.Test ErbKlasse.Test2
Dass eine explizite Konvertierung unsicher ist, zeigt das Beispiel recht deutlich. Die Konvertierung von BasisKlasse zu ErbKlasse ist mðglich. Doch was wÙre, wenn man wie im folgenden Beispiel versuchen wörde, eine Instanz der BasisKlasse in den Typ ErbKlasse zu konvertieren?
206 ____________________________________ 10.3 Konvertierung von Verweistypen
Listing 10.7 convertref2.cs // Klassen-Implementation entspricht dem // vorangegangenen Beispiel ... ErbKlasse Erb = new ErbKlasse(); Console.WriteLine(Erb.Test()); Console.WriteLine(Erb.Test2()); BasisKlasse Basis = new BasisKlasse(); Console.WriteLine(Basis.Test()); ErbKlasse Erb2 = (ErbKlasse) Basis; Console.WriteLine(Erb2.Test2()); ...
Abbildung 10.1 Die Konvertierung ist fehlgeschlagen
10 Konvertierungen ____________________________________________________207
Der Compiler akzeptiert die Konvertierung, da eine Konvertierung von BasisKlasse zu ErbKlasse theoretisch mðglich ist. Wie die Abbildung zeigt, wird allerdings eine Ausnahme erzeugt, denn praktisch ist die Konvertierung in diesem Fall nicht mðglich. Um einer solchen Ausnahme vorzubeugen, bietet C# Typenoperatoren an. Diese wurden bereits im Kapitel ¹Operatorenª vorgestellt und sollen an dieser Stelle nur kurz angerissen werden.
Der Typenoperator is Mit is lÙsst sich öberpröfen, ob ein Objekt einem bestimmten Datentyp entspricht beziehungsweise in diesen konvertiert werden kann. Hierzu wird links von is das Objekt und rechts der Datentyp notiert. Die Röckgabe ist ein boolescher Wert, der bestimmt, ob die Konvertierung mðglich ist oder nicht. Das folgende Schema veranschaulicht die Anwendung: if(Objekt is Datentyp) ...
Im folgenden Beispiel wird eine ErbKlasse instanziiert und diese anschlieûend in BasisKlasse konvertiert. Mit Hilfe des Operators is wird nun gepröft, ob eine Röckkonvertierung mðglich ist. Analog wird för eine neue Instanz der BasisKlasse verfahren. Listing 10.8 convertref3.cs // Klassen-Implementation entspricht dem // vorangegangenen Beispiel ... ErbKlasse Erb = new ErbKlasse(); BasisKlasse Basis1 = (BasisKlasse) Erb; if(Basis1 is ErbKlasse) Console.WriteLine("Basis1 unterstötzt ErbKlasse"); else Console.WriteLine("Basis1 unterstötzt ErbKlasse nicht"); BasisKlasse Basis2 = new BasisKlasse();
208 ____________________________________ 10.3 Konvertierung von Verweistypen
if(Basis2 is ErbKlasse) Console.WriteLine("Basis2 unterstötzt ErbKlasse"); else Console.WriteLine("Basis2 unterstötzt ErbKlasse nicht"); ...
Die Ausgabe im Konsolenfenster zeigt, dass das Objekt Basis1 in den Typ ErbKlasse konvertiert werden kann, bei Basis2 ist diese Konvertierung hingegen nicht mðglich. Basis1 unterstötzt ErbKlasse Basis2 unterstötzt ErbKlasse nicht
Der Typenoperator as Der Operator as arbeitet Ùhnlich wie is. Allerdings wird hier nicht nur verglichen, sondern eine Konvertierung auch tatsÙchlich durchgeföhrt, sofern diese denn mðglich ist. Ist die Konvertierung nicht mðglich, wird null zuröckgeliefert. Das Schema sieht wie folgt aus: NeuesObjekt = Objekt as Datentyp;
Das Ergebnis lieûe sich auch auf Basis des Operators is realisieren, dann wÙren aber mehrere Befehlszeilen notwendig: if(Objekt is Datentyp) NeuesObjekt = (Datentyp) Objekt; else NeuesObjekt = null;
Das nachfolgende Beispiel zeigt die Verwendung des Operators as. Es wird wieder versucht, die Objekte vom Typ BasisKlasse in den Typen ErbKlasse zu konvertieren. Die Abfrage erfolgt hier mit Hilfe des Vergleichs mit null.
Listing 10.9 convertref4.cs // Klassen-Implementation entspricht dem // vorangegangen Beispiel ...
10 Konvertierungen ____________________________________________________209
ErbKlasse Erb = new ErbKlasse(); BasisKlasse Basis1 = (BasisKlasse) Erb; ErbKlasse Erb2 = Basis1 as ErbKlasse; if(Erb2 != null) Console.WriteLine("Basis1 unterstötzt ErbKlasse"); else Console.WriteLine("Basis1 unterstötzt ErbKlasse nicht"); BasisKlasse Basis2 = new BasisKlasse(); ErbKlasse Erb3 = Basis2 as ErbKlasse; if(Erb3 != null) Console.WriteLine("Basis2 unterstötzt ErbKlasse"); else Console.WriteLine("Basis2 unterstötzt ErbKlasse nicht"); ...
Die Ausgabe im Konsolenfenster ist analog zum vorhergehenden Beispiel: Basis1 unterstötzt ErbKlasse Basis2 unterstötzt ErbKlasse nicht
Der Operator as wird immer dann eingesetzt, wenn auf ein konvertiertes Objekt auch zugegriffen werden soll. Soll lediglich die Mðglichkeit der Konvertierung öberpröft werden, bietet sich der Operator is an.
10.4 Die Klasse Convert Die Klasse Convert aus dem Namespace System ermðglicht eine Vielzahl von expliziten Konvertierungen zwischen verschiedenen Datentypen. Hier kðnnen auch eher ungewðhnliche Konvertierungen wie string zu bool und string zu DateTime vorgenommen werden. Die Tabelle zeigt die allesamt statischen Methoden der Klasse sowie die jeweils mðglichen Quelldatentypen.
210 ______________________________________________ 10.4 Die Klasse Convert
Tabelle 10.2 Die Methoden der Klasse Convert in der Übersicht Methode
Rückgabewert
Quelldatentypen
ChangeType
object
object; ermöglicht eine beliebige Konvertierung eines angegebenen Objektes in einen anderen Typ und arbeitet somit ähnlich wie der Typenoperator as. Ist die Konvertierung nicht möglich, wird hier allerdings eine Ausnahme vom Typ InvalidCastException geworfen.
ToBase64String
string
byte[]
ToBoolean
bool
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToByte
byte
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToChar
char
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToDateTime
DateTime
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToDecimal
Decimal
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToDouble
double
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToInt16
short
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToInt32
int
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToInt64
long
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToSByte
sbyte
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToSingle
float
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
10 Konvertierungen ____________________________________________________211
Methode
Rückgabewert
Quelldatentypen
ToString
string
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToUInt16
ushort
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToUInt32
uint
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToUInt64
ulong
bool, byte, char, DateTime, Decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong
ToBoolean Das folgende Beispiel zeigt die Verwendung der Methode ToBoolean. Es wird eine Zeichenkette mit dem Inhalt true öbergeben und in einen booleschen Wert umgewandelt. Mðgliche Werte för die Umwandlung sind true und false, wobei die Groû-Kleinschreibung nicht beachtet wird. Listing 10.10 tobool1.cs using System; class ConsoleApp { static void Main() { string s = "true"; bool b = Convert.ToBoolean(s); Console.WriteLine(b.ToString()); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: True
212 ______________________________________________ 10.4 Die Klasse Convert
ToDateTime Datumskonvertierung
Auch Datumskonvertierungen sind mðglich. Im folgenden Beispiel wird eine Zeichenkette mit Hilfe der Methode ToDateTime in eine Variable vom Typ DateTime konvertiert. Listing 10.11 todatetime1.cs using System; class ConsoleApp { static void Main() { string s = "02.09.2001"; DateTime dt = Convert.ToDateTime(s); Console.WriteLine(dt.ToString("dddd, d. MMMM yyyy")); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster zeigt deutlich, dass die Umwandlung in das Datumsformat tatsÙchlich erfolgreich war, denn wo kommt sonst der Wochentag her? Sonntag, 2. September 2001
ToInt32 Der Datentyp System.Int32 des .NET Frameworks ist nichts anderes als der von C# angebotene Datentyp int. Es wird lediglich ein Alias för den schnellen Zugriff angeboten. Das folgende Beispiel zeigt, wie eine Zeichenkette in einen int konvertiert wird. Besonders Benutzereingaben mössen oft auf diese Weise konvertiert werden.
10 Konvertierungen ____________________________________________________213
Listing 10.12 toint321.cs using System; class ConsoleApp { static void Main() { string s = "25"; int n = Convert.ToInt32(s); n = n*2; Console.WriteLine(n.ToString()); Console.ReadLine(); } }
Ausgabe im Konsolenfenster 50
Alternativ lÙsst sich die FunktionalitÙt auch wie folgt öber die statische Methode Parse des int-Objektes abbilden: Listing 10.13 toint322.cs using System; class ConsoleApp { static void Main() { string s = "25"; int n = int.Parse(s); n = n*2; Console.WriteLine(n.ToString()); Console.ReadLine(); } }
Um im Zuge der Konvertierung zu pröfen, ob eine Zeichenkette tatsÙchlich umgewandelt werden kann, lÙsst sich folgendes Konstrukt verwenden:
214 ______________________________________________ 10.4 Die Klasse Convert
Listing 10.14 toint323.cs using System; class ConsoleApp { static void TestInt(string s) { try { int n = Convert.ToInt32(s); n = n*2; Console.WriteLine(n.ToString() + ""); } catch { Console.WriteLine(s + " ist kein numerischer Wert"); } } static void Main() { string s1 = "25"; string s2 = "hjkhj"; TestInt(s1); TestInt(s2); Console.ReadLine(); } }
Das Ausnahmebehandlung versucht zunÙchst die Konvertierung durchzuföhren. SchlÙgt dies fehl, wird eine entsprechende Ausgabe im Konsolenfenster erzeugt: 50 hjkhj ist kein numerischer Wert
Eine ausföhrliche Beschreibung zu Ausnahmen und Ausnahmebehandlungen finden Sie im folgenden Kapitel ¹Fehlerbehandlungª.
11 Fehlerbehandlung
Was ist eine Ausnahme? Das try..catch..finally-Konstrukt Die Klasse Exception Ausnahmen auslösen Ausnahmen (kontrolliert) weiterreichen Eigene Ausnahmen definieren Zusammenfassung
216 ___________________________________________________________________
Inhaltsübersicht Kapitel 11
11
Fehlerbehandlung ............................................................................................217
11.1 11.2 11.3 11.4 11.5 11.6 11.7
Was ist eine Ausnahme? ....................................................................................217 Das try..catch..finally-Konstrukt........................................................................219 Die Klasse Exception.........................................................................................223 Ausnahmen auslðsen .........................................................................................225 Ausnahmen (kontrolliert) weiterreichen ............................................................228 Eigene Ausnahmen definieren ...........................................................................230 Zusammenfassung .............................................................................................234
___________________________________________________________________ 217
11
Fehlerbehandlung
Nahezu jedes Programm ist von Umgebungseinflössen abhÙngig. Dies kðnnen genauso gut Benutzerinteraktionen wie Systemvoraussetzungen oder die Existenz eines bestimmten Verzeichnisses sein. Alle Umgebungseinflösse haben eines gemeinsam, sie liegen auûerhalb des Einflussbereichs der Entwickler. Im Idealfall verhalten sich alle Faktoren wie gewönscht und das Programm kann tadellos abgearbeitet werden. Was aber, wenn einer der Faktoren sich nicht wie erwartet verhÙlt, das temporÙre Verzeichnis nicht existiert oder der Benutzer statt eines Datums die Zeichenkette ¹öbermorgenª eingegeben hat? In diesen FÙllen kommt es zu einem Fehler im Programm. Wenn dieser nicht in irgendeiner Form behandelt wird, kann es zu weitgehenden Fehlfunktionen, Abstörzen und anderen unliebsamen šberraschungen kommen. Als Folge sollten alle Umgebungseinflösse öberpröft werden und im Fehlerfall entweder tolerant oder mit einer geeigneten Maûnahme wie einer Benutzerausgabe reagiert werden. Man nennt dies Fehlerbehandlung. Viele Sprachen haben keine oder nur unterentwickelte Mechanismen zur Behandlung von Fehlern. Oft muss der Entwickler selbst Hand anlegen, mðgliche Fehlerquellen explizit öberpröfen und diese mit Röckgabewerten innerhalb der Programmlogik an die entsprechende Stelle weiterreichen. Dies ist einerseits wiederum fehleranfÙllig, da man einen Röckgabewert auch einfach ignorieren kann, und andererseits recht möhsam, denn mðgliche Fehlerquellen gibt es viele. Das Microsoft .NET Framework und somit alle .NET-Sprachen einschlieûlich C# haben ein einheitliches System zur Fehlerbehandlung: Ausnahmen (Englisch: Exeptions).
11.1 Was ist eine Ausnahme? Eine Ausnahme ist im Grunde nichts anderes als ein Objekt mit Eigenschaften und Methoden. Diese erlauben Zugriff auf weitere Informationen, wie die Art und den Ort der Ausnahme. Es gibt dabei ganz unterschiedliche Ausnahme-Objekte beziehungsweise -Typen. Diese leiten sich alle aus der ursprönglichen Klasse ¹Exceptionª ab. Diese Klasse wird also vererbt.
Exceptions
218 __________________________________________ 11.1 Was ist eine Ausnahme?
Sobald ein Fehler auftritt, wird eine neue Instanz eines passenden AusnahmeObjektes instanzziert und die Ausnahme geworfen1. Die aktuelle Programmausföhrung wird umgehend abgebrochen und die Ausnahme in der Hierarchie der Geltungsbereiche hochgereicht. Konkret bedeutet dies, dass eine Ausnahme innerhalb einer Methode geworfen wird, aber erst einen Geltungsbereich hðher im Aufruf dieser Methode behandelt wird. Sofern die Ausnahme nicht durch eine eigene Ausnahmebehandlung bearbeitet wird, öbernimmt das .NET Framework diese Aufgabe. Bei einem WindowsProgramm erscheint beispielsweise eine MessageBox mit weiteren Informationen, bei ASP.NET wird eine entsprechende Fehlermeldung im Browser ausgegeben. In beiden FÙllen wird die Programmausföhrung komplett beendet. Das folgende Beispiel zeigt eine oft erzeugte Ausnahme, die Division durch 0. DivideByZero Exception
Listing 11.1 ausnahme1.cs using System; class ConsoleApp { static void Main() { int n1 = 1; int n2 = 0; Console.WriteLine((n1/n2).ToString()); Console.ReadLine(); } }
Bei der Ausföhrung des Beispieles kommt es natörlich zu einem Fehler und eine entsprechende Ausnahme (System.DivideByZeroException) wird geworfen. Da keine Ausnahmebehandlung vorhanden ist, wird eine standardisierte Fehlermeldung mit weiteren Informationen erzeugt. Die Abbildung zeigt die Fehlermeldung. Sehr viel schðner wÙre es natörlich, wenn das Programm nicht beendet und dem Benutzer neben der Fehlermeldung weitere Mðglichkeiten zum Fortsetzen angeboten wörden. Auch kðnnte man eine Log-Datei erzeugen, eine E-Mail an den Administrator senden oder …hnliches. Der Fantasie sind kaum Grenzen gesetzt. Stellt sich also die Frage, wie man eine Ausnahme selbst behandeln kann. Hier kommt das try..catch..finally-Konstrukt zum Einsatz.
1
¹Werfenª klingt ein wenig, als ob die Ausnahme Welpen zur Welt bringen wörde. TatsÙchlich hat die Formulierung ihren Ursprung aber in der englischen Beschreibung des Vorgangs: [to] throw an exception.
11 Fehlerbehandlung ___________________________________________________219
Abbildung 11.1 Die Division durch 0 erzeugt eine Ausnahme
11.2 Das try..catch..finally-Konstrukt Das try..catch..finally-Konstrukt wird zum Erstellen eigener Ausnahmebehandlungen verwendet. Das Konstrukt besteht aus drei verschiedenen Blðcken. Im ersten Block try werden die Anweisungen notiert, die mðglicherweise zu einem Fehler, also einer Ausnahme föhren kðnnten. Kommt es tatsÙchlich zu einer Ausnahme, werden der oder die catch-Blðcke ausgeföhrt. Wenn es keine Probleme gibt, wird dieser Bereich ignoriert. Der dritte, optionale finally-Block wird, soweit vorhanden, in jedem Fall ausgeföhrt, unabhÙngig ob eine Ausnahme geworfen wurde oder nicht. Dieser Block ist för AufrÙumarbeiten gedacht, beispielsweise wenn Sie eine Datei schlieûen, eine Protokoll-Ausgabe durchföhren mössen oder …hnliches.
220 _____________________________________ 11.2 Das try..catch..finally-Konstrukt
Versehen wir also das vorangegangene Beispiel mit einer eigenen Ausnahmebehandlung und schauen wir, was passiert. try..catch Listing 11.2 try..catch..finally1.cs using System; class ConsoleApp { static void Main() { int n1 = 1; int n2 = 0; Console.WriteLine("Wen interessiert schon " + "eine Fehlermeldung?"); try { Console.WriteLine((n1/n2).ToString()); } catch {} Console.ReadLine(); } }
Zugegeben, dieses Beispiel sollte in Ihrem Programm mðglichst nie auftauchen, aber theoretisch ist es durchaus machbar. Im try-Block wird die ungöltige Division durchgeföhrt, was eine Ausnahme zur Folge hat. Aus diesem Grund wird der nachfolgende catch-Block abgearbeitet; da dieser leer ist, wird die Ausnahme ignoriert. Die Ausgabe im Konsolenfenster sieht wie folgt aus. Wen interessiert schon eine Fehlermeldung?
¹Michª, werden Sie sagen und ich gebe Ihnen Recht. Im Folgenden finden Sie das Beispiel in einer etwas eleganteren Form.
Listing 11.3 try..catch..finally2.cs using System; class ConsoleApp { static void Main() { int n1 = 1;
11 Fehlerbehandlung ___________________________________________________221
int n2 = 0; try { Console.WriteLine((n1/n2).ToString()); Console.WriteLine("Alles roger"); } catch(Exception e) { Console.WriteLine("Ups, das war keine gute Idee. " + "Die folgende Ausnahme ist aufgetreten: " + e.Message); } Console.ReadLine(); } }
Wie Sie erkennen kðnnen, wird hinter der catch-Anweisung das Ausnahme-Objekt quasi als Parameter angegeben. Wie bereits weiter oben beschrieben, lassen sich auch mehrere catch-Blðcke integrieren. Auf diese Weise lÙsst sich zwischen bestimmten Fehlern differenzieren. Wird eine Ausnahme geworfen, sucht das Programm die am besten passende Ausnahmebehandlung. Im Zweifelsfall ist dies immer die Klasse Exception, da diese die Mutter aller individuellen Ausnahmen darstellt, die von dieser Klasse abgeleitet werden (mössen). Rechnet man mit einem bestimmten Fehler, wird man för diesen in der Regel einen individuellen catch-Block integrieren. Alle anderen mðglichen Ausnahmen werden durch einen weiteren, allgemeinen Block behandelt. Das folgende Beispiel zeigt diese Differenzierung. Listing 11.4 try..catch..finally3.cs using System; class ConsoleApp { static void Main() { int n1 = 1; int n2 = 0; try { Console.WriteLine((n1/n2).ToString()); Console.WriteLine("Alles roger"); } catch(DivideByZeroException e)
Mehrere catchBlöcke
222 _____________________________________ 11.2 Das try..catch..finally-Konstrukt
{ Console.WriteLine("Och, Sie sollen doch nicht " + "durch 0 teilen. Das wissen Sie doch...!"); } catch(Exception e) { Console.WriteLine("Ups, das war keine gute Idee. " + "Die folgende Ausnahme ist aufgetreten: " + e.Message); } Console.ReadLine(); } }
Durch die Division durch 0 hebt die Seite die mahnende Hand sehr deutlich. Falls ein anderer Fehler Ursache för die Ausnahme ist, wird lediglich ein allgemeiner Text ausgegeben. Zu guter Letzt nun noch ein Beispiel zur Verwendung des finally-Blocks. Dieser ist wie beschrieben optional und sollte nur dann verwendet werden, wenn es Sinn ergibt. Zerbrechen Sie sich aber bitte nicht den Kopf, ob dies bei den folgenden Ausgaben wirklich der Fall ist, schlieûlich ist dies hier nur die graue Theorie. try..catch..finally Listing 11.5 try..catch..finally4.cs using System; class ConsoleApp { static void Main() { int n1 = 1; int n2 = 0; try { Console.WriteLine((n1/n2).ToString()); Console.WriteLine("Alles roger"); } catch(DivideByZeroException e) { Console.WriteLine("Och, Sie sollen doch nicht " + "durch 0 teilen. Das wissen Sie doch...!"); } catch(Exception e) { Console.WriteLine("Ups, das war keine gute Idee. " +
11 Fehlerbehandlung ___________________________________________________223
"Die folgende Ausnahme ist aufgetreten: " + e.Message); } finally { Console.WriteLine("Ob Fehler oder nicht, ich hoffe, " + "es hat Ihnen Spaû gemacht und dieser Text erscheint"); } Console.ReadLine(); } }
Natörlich mðchte ich Ihnen die Ausgabe im Konsolenfenster nicht vorenthalten, obwohl Sie diese vermutlich schon erahnen kðnnen. Beachten Sie bitte, dass der zusÙtzliche Text aus dem finally-Block auch dann erscheinen wörde, wenn es zu keiner Ausnahme kommt. Dieser Block wird ± soweit vorhanden ± immer ausgeföhrt. Och, Sie sollen doch nicht durch 0 teilen. Das wissen Sie doch...! Ob Fehler oder nicht, ich hoffe, es hat Ihnen Spaû gemacht und dieser Text erscheint
11.3 Die Klasse Exception Eine Ausnahme oder Exception ist ein Objekt, eine Klasse. In den bisherigen Beispielen wurde bereits die Message Eigenschaft dieser Klasse beschrieben. Die Klasse bietet eine Reihe weiterer Eigenschaften und Methoden an, die zur Behandlung einer Ausnahme nötzlich sein kðnnen. Wollen Sie beispielsweise ein Protokoll einer wichtigen Ausnahme erstellen, sind Ihnen sicherlich alle verfögbaren Informationen herzlich willkommen. Es folgt eine Beschreibung der hierför relevanten Mitglieder der Klasse. Beachten Sie, dass individuelle Ausnahmen immer von der Klasse Exception abgeleitet werden. Sie erben daher die Eigenschaften und Methoden der Mutterklasse und unterstötzen diese dementsprechend. Davon abgesehen kðnnen diese Klassen aber auch eigene zusÙtzliche Mitglieder anbieten. Beachten Sie hierzu gegebenenfalls die jeweilige Dokumentation.
224 _____________________________________________ 11.3 Die Klasse Exception
Tabelle 11.1 Die Eigenschaften der Klasse Exception im Überblick Eigenschaft
Rückgabewert
Beschreibung
HelpLink
string
Liefert oder setzt eine URL mit einer ausführlichen Beschreibung der Ausnahme, zum Beispiel Verweis auf lokale Hilfedatei oder Ähnliches.
HResult
int
Liefert oder setzt einen numerischen 32 Bit Wert, der der Ausnahme zur Identifizierung zugewiesen wurde. Diese Eigenschaft kann von abgeleiteten Klassen nicht überschrieben werden.
InnerException
Exception
Sofern der Ausnahme eine andere, behandelte Ausnahme vorausgegangen ist, kann ein Objektverweis auf die ursprüngliche Ausnahme in dieser Eigenschaft abgelegt sein.
Message
string
Liefert einen Text, der die Ausnahme beschreibt.
Source
string
Liefert oder setzt die Bezeichnung der Applikation oder des Objektes, in der die Ausnahme aufgetreten ist.
StackTrace
string
Liefert ein StackTrace der Ausnahme, in dem unter Umständen die auslösende Zeile identifiziert werden kann.
TargetSite
MethodBase
Liefert – soweit vorhanden – einen Verweis auf die Methode, die die Ausnahme verursacht hat. Auf diese Weise lassen sich unter Umständen detailliertere Informationen abfragen.
Tabelle 11.2 Die Methoden der Klasse Exception im Überblick Methode
Rückgabewert
Beschreibung
GetBaseException
Exception
Liefert einen Objektverweis auf die ursprüngliche Ausnahme. Sofern die Eigenschaft InnerException auf eine andere Ausnahme verweist, wird diese Ausnahme zurückgeliefert, ansonsten ein Verweis auf das aktuelle Objekt.
ToString
string
Liefert eine Zusammenfassung der wichtigsten Informationen der Ausnahme in Form einer Zeichenkette.
Das nachfolgende Beispiel gibt zu der bereits bekannten Ausnahme einige DetailInformationen aus und nutzt hierzu die verschiedenen Eigenschaften der Klasse Exception.
11 Fehlerbehandlung ___________________________________________________225
Listing 11.6 exception1.cs using System; class ConsoleApp { static void Main() { int n1 = 1; int n2 = 0; try { Console.WriteLine((n1/n2).ToString()); Console.WriteLine("Alles roger"); } catch(Exception e) { Console.WriteLine("Ups, das war keine gute Idee.\n" + "Die folgende Ausnahme ist aufgetreten:\n\n" + "Meldung: " + e.Message + "\n" + "Quelle: " + e.Source + "\n" + "Stack Trace: " + e.StackTrace); } Console.ReadLine(); } }
Es fehlt vielleicht noch ein wenig an optischer Eleganz, aber die Informationen an sich kðnnen sich durchaus sehen lassen: Ups, das war keine gute Idee. Die folgende Ausnahme ist aufgetreten: Meldung: Es wurde versucht, durch null zu teilen. Quelle: atbrbtd5 Stack Trace: at ConsoleApp.Main()
11.4 Ausnahmen auslösen Bisher haben Sie Ausnahmen als etwas kennen gelernt, das vom System auf Grund eines Fehler ausgelðst wird. Sie kðnnen aber auch selbst Ausnahmen auslðsen. Im Rahmen der objektorientierten Programmierung sollten Sie von dieser Mðglichkeit auch Gebrauch machen und sie Röckgabewerten oder anderen Informationswegen unbedingt vorziehen. Die (Sprung-) Anweisung zum Werfen einer Ausnahme
Ausnahmen mit Informationen versehen
226 _____________________________________________ 11.4 Ausnahmen auslösen
wurde bereits in einem fröheren Kapitel beschrieben. Es handelt sich um die Anweisung throw. Als Parameter folgt der Anweisung eine Instanz der gewönschten Ausnahme, zum Beispiel der Klasse Exception. Das folgende Beispiel enthÙlt eine Methode GetString. Diese soll lediglich den als Parameter öbergebenen String zuröckliefern. För den Fall, dass dieser auf null verweist, wird eine entsprechende Ausnahme geworfen. Diese wird von einem try..catch-Konstrukt behandelt. throw Listing 11.7 ausloesen1.cs using System; class ConsoleApp { public static string GetString(string s) { if(s == null) throw(new ArgumentNullException()); return s; } static void Main() { string s = null; try { Console.WriteLine(GetString(s)); } catch(Exception e) { Console.WriteLine("Ups, das war keine gute Idee. " + "Die folgende Ausnahme ist aufgetreten:" + "Meldung: " + e.Message); } Console.ReadLine(); } }
Die Ausgabe des Beispiels sieht ± erwartungsgemÙû ± wie folgt aus: Ups, das war keine gute Idee. Die folgende Ausnahme ist aufgetreten: Meldung: Wert darf nicht Null sein.
11 Fehlerbehandlung ___________________________________________________227
Die Meldung ist natörlich nicht besonders aussagekrÙftig, eine angepasste Meldung wÙre sehr viel informativer und ist mit einem Handgriff eingebaut. Der Konstruktor der Exception Klasse erlaubt die Angabe einer Zeichenkette, die automatisch der Eigenschaft Message zugewiesen wird. Der Konstruktur der hier verwendeten Ausnahme ArgumentNullException wurde öberschrieben. Hier bleibt der Meldungstext bestehen und wird durch die öbergebene Zeichenkette lediglich ergÙnzt. Eine minimale AbÙnderung des Beispiels föhrt so zu einem viel öbersichtlicheren Ergebnis. Listing 11.8 ausloesen2.cs using System; class ConsoleApp { public static string GetString(string s) { if(s == null) throw(new ArgumentNullException("string s der " + "Methode GetString ")); return s; } static void Main() { string s = null; try { Console.WriteLine(GetString(s)); } catch(Exception e) { Console.WriteLine("Ups, das war keine gute Idee. " + "Die folgende Ausnahme ist aufgetreten:" + "Meldung: " + e.Message); } Console.ReadLine(); } }
228 _______________________________ 11.5 Ausnahmen (kontrolliert) weiterreichen
Die Ausgabe enthÙlt nun sowohl den ursprönglichen Meldungstext als auch die individuelle ErgÙnzung (im Folgenden kursiv dargestellt). Ups, das war keine gute Idee. Die folgende Ausnahme ist aufgetreten: Meldung: Wert darf nicht Null sein. Parametername: string s der Methode GetString
11.5 Ausnahmen (kontrolliert) weiterreichen Wie weiter oben beschrieben, werden Ausnahmen so lange nach oben weitergereicht, bis sie behandelt werden. In manchen FÙllen mðchte man eine Ausnahme kontrolliert weiterreichen. Dabei wird diese zunÙchst lokal behandelt und dann neu geworfen. Diese Technik kann beispielsweise verwendet werden, wenn Sie die urspröngliche Ausnahme mit weiteren Informationen versehen wollen, oder im Gegenteil eine allgemein gefasstere Ausnahme weiterreichen mðchten. Listing 11.9 weiterreichen1.cs using System; class ConsoleApp { public static int Divide(int d1, int d2) { try { return(d1/d2); } catch(DivideByZeroException e) { throw(new DivideByZeroException("Division durch 0, " + "Parameter d2 ist 0", e)); } catch(Exception e) { return(0); } } static void Main() { int d1 = 5; int d2 = 0;
11 Fehlerbehandlung ___________________________________________________229
try { Console.WriteLine("d1/d2 ergibt: "); Console.WriteLine(Divide(d1, d2)); } catch(Exception e) { Console.WriteLine("Ups, das war keine gute Idee. " + "Die folgende Ausnahme ist aufgetreten:" + "Meldung: " + e.Message + ""); } Console.ReadLine(); } }
Die Methode Divide enthÙlt eine eigene Fehlerbehandlung. Diese differenziert zwischen einer Division durch 0 und allen anderen Ausnahmen. Sollte eine Division durch 0 tatsÙchlich erfolgen (in diesem Beispiel kein Wunder), wird die Ausnahme weitergereicht und vom try..catch-Konstrukt im öbergeordneten Göltigkeitsbereich behandelt. För den Fall, dass eine beliebige andere Ausnahme auftreten sollte, wird als Ergebnis einfach 0 zuröckgeliefert. Die Ausgabe des Beispiels sieht wie folgt aus: d1/d2 ergibt: Ups, das war keine gute Idee. Die folgende Ausnahme ist aufgetreten: Meldung: Division durch 0, Parameter d2 ist 0
Alternativ zu einer kontrollierten Weiterreichung von Ausnahmen kðnnen Sie sich die implizite und somit unkontrollierte Weiterreichung auch zu Nutze machen. Dabei verzichten Sie einfach auf eine explizite Fehlerbehandlung in unkritischen Methoden und behandeln eventuell auftretende Ausnahmen einfach im öbergeordneten Geltungsbereich. Auf diese Weise sparen Sie sicherlich die eine oder andere Zeile Code. Es empfiehlt sich dennoch eine lokale Behandlung von Ausnahmen, das folgende Beispiel dient daher lediglich der VollstÙndigkeit. Listing 11.10 weiterreichen2.cs using System; class ConsoleApp { public static int Divide(int d1, int d2) { return(d1/d2); }
Ausnahmen automatisch weiterreichen
230 ______________________________________ 11.6 Eigene Ausnahmen definieren
static void Main() { int d1 = 5; int d2 = 0; try { Console.WriteLine("d1/d2 ergibt: "); Console.WriteLine(Divide(d1, d2)); } catch(Exception e) { Console.WriteLine("Ups, das war keine gute Idee. " + "Die folgende Ausnahme ist aufgetreten:" + "Meldung: " + e.Message + ""); } Console.ReadLine(); } }
Natörlich mðchte ich Ihnen die Ausgabe dieser Zeilen im Konsolenfenster nicht vorenthalten; sie entsprechen grob denen aus dem vorangegangenen Beispiel. d1/d2 ergibt: Ups, das war keine gute Idee. Die folgende Ausnahme ist aufgetreten: Meldung: Es wurde versucht, durch null zu teilen.
11.6 Eigene Ausnahmen definieren Nachdem Sie bereits einiges öber den Umgang mit Ausnahmen gelesen haben, erfahren Sie nun, wie Sie eigene und somit ganz individuelle Ausnahmen definieren kðnnen. Diese individuellen Ausnahmen helfen Ihnen bei der genaueren Kontrolle einzelner Programm-Bereiche. Wenn Sie Ihre Projekte Komponenten-orientiert entwickeln, kðnnen Sie mit eigenen Ausnahmen eine flexible Fehlerinformation zwischen den einzelnen Komponenten beziehungsweise Schichten realisieren. Alle Ausnahmen sind im Grunde lediglich eine Klasse und leiten sich direkt oder indirekt von der Klasse Exception ab. Das folgende Beispiel zeigt die Verwendung und Auslðsung einer selbst definierten Ausnahme.
11 Fehlerbehandlung ___________________________________________________231
Listing 11.11 eigeneausnahme1.cs using System; class ConsoleApp { class GetStringMethodException : Exception {} public static string GetString(string s) { if(s == null) throw(new GetStringMethodException()); return s; } static void Main() { string s = null; try { Console.WriteLine(GetString(s)); } catch(GetStringMethodException e) { Console.WriteLine("Der Parameter string s der " + "Methode GetString darf nicht null sein"); } catch(Exception e) { Console.WriteLine("Irgendein anderer Fehler ;-)"); } Console.ReadLine(); } }
Bereits mit der ersten (fett markierten) Zeile wird eine individuelle Ausnahme definiert. šber den Doppelpunkt hinter dem Namen wird deutlich, dass die Klasse von der bestehenden Klasse Exception abgeleitet werden soll und somit deren FunktionalitÙt komplett erbt. Die neue Ausnahme wird im Verlaufe der Methode GetString instanziiert und geworfen. Die Ausnahmebehandlung fÙngt die Ausnahme differenziert ab und gibt eine entsprechende Fehlermeldung im Konsolenfenster aus. Natörlich kðnnen Sie die Ausnahme auch mit weiteren Anpassungen versehen. Im folgenden Beispiel wird beispielsweise ein neuer (öberladener) Konstruktur eingeföhrt. Dieser erwartetet neben der auszugebenden Message einen zweiten Parameter mit dem Inhalt des Hilfe-Links (Eigenschaft HelpLink).
Basisklasse Exception
232 ______________________________________ 11.6 Eigene Ausnahmen definieren
Listing 11.12 eigeneausnahme2.cs using System; class GetStringMethodException : Exception { public GetStringMethodException() : base() {} public GetStringMethodException(string Message) : base(Message) {} public GetStringMethodException(string Message, string HelpLink) : base(Message) { base.HelpLink = HelpLink; } } class ConsoleApp { public static string GetString(string s) { if(s == null) throw(new GetStringMethodException("Parameter s ist null", "help.cs")); return s; } static void Main() { string s = null; try { Console.WriteLine(GetString(s)); } catch(Exception e) { Console.WriteLine("Ups, das war keine gute Idee.\n" + "Die folgende Ausnahme ist aufgetreten:\n" + "Meldung: " + e.Message + ".\nWeitere Information " + "finden Sie hier: " + e.HelpLink); } Console.ReadLine(); } }
11 Fehlerbehandlung ___________________________________________________233
Auch ganz ausgefallene Erweiterungen sind denkbar. Beispielsweise kðnnten Sie eine Protokoll-Methode implementieren. Ein Aufruf wörde die Protokollierung starten. Im Folgenden der entsprechend angepasste Rumpf mit der ergÙnzten Methode LogException: Protokollierung Listing 11.13 eigeneausnahme3.cs using System; class GetStringMethodException : Exception { public GetStringMethodException() : base() {} public GetStringMethodException(string Message) : base(Message) {} public GetStringMethodException(string Message, string HelpLink) : base(Message) { base.HelpLink = HelpLink; } public void LogException() { // An dieser Stelle kðnnte eine // Protokollierung in eine Datei erfolgen } } class ConsoleApp { public static string GetString(string s) { if(s == null) throw(new GetStringMethodException("Parameter s ist null", "help.cs")); return s; } static void Main() { string s = null; try { Console.WriteLine(GetString(s)); } catch(GetStringMethodException e) { Console.WriteLine("Ups, das war keine gute Idee.\n" + "Die folgende Ausnahme ist aufgetreten:\n" +
234 _______________________________________________ 11.7 Zusammenfassung
"Meldung: " + e.Message + ".\nWeitere Information " + "finden Sie hier: " + e.HelpLink); e.LogException(); } catch(Exception e) { // ... } Console.ReadLine(); } }
11.7 Zusammenfassung Ausnahmen sind ein wirksames Mittel för ein öbergreifendes und ausgefeiltes Fehlermanagement. Machen Sie sich diese Mðglichkeiten zu Nutze. Lassen Sie Röckgabewerte und andere Mechanismen zur Seite und verwenden Sie in Zukunft lieber konsequent Ausnahmen. Ihr Code wird so öbersichtlicher und besser, weil weniger fehleranfÙllig. Denn fröher oder spÙter wird jede Ausnahme behandelt. Dies gilt aber nicht för Röckgabewerte, die schon einmal ungewollt untergehen kðnnen!
12 Klassen & objektorientierte Programmierung – Teil II
Überladungen Vererbung Schnittstellen Benutzerdefinierte Operatoren erstellen
236 ___________________________________________________________________
Inhaltsübersicht Kapitel 12
12 12.1 12.2 12.2.1 12.2.2 12.2.3 12.2.4 12.2.5 12.2.6 12.3 12.3.1 12.3.2 12.3.3 12.3.4 12.4 12.4.1 12.4.2 12.4.3
Klassen & objektorientierte Programmierung ± Teil II ...........................237 šberladungen ....................................................................................................237 Vererbung..........................................................................................................241 Einfache Vererbungen .......................................................................................242 šberschreiben von Klassen-Mitgliedern ...........................................................244 Verbergen von Klassen-Mitgliedern..................................................................247 Konstruktoren und die Vererbung .....................................................................248 Klassen versiegeln .............................................................................................252 Abstrakte Klassen ..............................................................................................253 Schnittstellen .....................................................................................................256 Deklaration einer Schnittstelle...........................................................................256 Implementierung einer Schnittstelle ..................................................................257 Noch ein tierisches Beispiel...............................................................................257 Mehrere gleichnamige Schnittstellen-Mitglieder...............................................261 Benutzerdefinierte Operatoren erstellen ............................................................262 Arithmetische Operatoren..................................................................................262 Vergleichsoperatoren.........................................................................................266 Konvertierungsoperatoren .................................................................................270
___________________________________________________________________ 237
12 Klassen & objektorientierte Programmierung – Teil II Zu Beginn des Kapitels haben Sie bereits öber einige Grundlagen zur objektorientierten Programmierung mit Klassen gelesen. Eine Reihe von weiteren Techniken machen die objektorientierte Programmierung aber erst zu dem, was sie ist: ein leistungsfÙhiges System zur Entwicklung komplexer und wieder verwertbarer Programm-Module. Auf den folgenden Seite mðchte ich Ihnen diese weitergehenden Mðglichkeiten der objektorientierten Programmierung mit C# vorstellen.
12.1 Überladungen Das šberladen ist eine effektive Technik, wenn man eine FunktionalitÙt för verschiedene Parameter-Strukturen implementieren mðchte. Sie haben dieses Vorgehen bereits in Teil I im Zusammenhang mit der Erstellung von Konstruktoren kennen gelernt. Dieses lÙsst sich aber auch auf jede andere Methode einer Klasse anwenden. Es werden dabei mehrere Methoden gleichen Namens, aber mit unterschiedlichen und jeweils eindeutigen Parameter-Strukturen erstellt. Beim Aufruf der Methode entscheidet die šbergabe der Parameter, welche der Implementierungen tatsÙchlich aufgerufen wird. In der Regel Ùhneln sich diese Implementierungen oder rufen sich sogar gegenseitig auf. Theoretisch sind sie aber vollkommen unabhÙngig voneinander. Es ergibt sich das folgende Schema: class KlassenName { public MethodenName() { // Implementation } public MethodenName(Datentyp Parameter)
Methoden mit dem gleichen Namen
238 ___________________________________________________ 12.1 Überladungen
{ // Implementation } public MethodenName(Datentyp Parameter, Datentyp Parameter) { // Implementation } }
Erinnern Sie sich noch an das Beispiel der Katze, die geföttert werden wollte? Im ersten Teil dieses Kapitels demonstrierte dieses Beispiel die prinzipielle Verwendung von Methoden. Der Methode Fuettern wurde eine Nahrung sowie die gewönschte Anzahl öbergeben. Ein optimaler Kandidat för eine šberladung, denn die Anzahl kðnnte man theoretisch auch weglassen und hierför einfach einen Standardwert wie eine Portion annehmen. Das folgende Beispiel basiert auf dem ursprönglichen, wurde jedoch dem Thema angepasst.
Listing 12.1 ueberladen1.cs using System; class Katze { public enum Nahrung { Thunfisch, Rinderhack, Huehnchen, Trockenfutter } int _Zufriedenheitsindex; public int Zufriedenheitsindex { get { return(_Zufriedenheitsindex); } set { _Zufriedenheitsindex = value; } }
12 Klassen & objektorientierte Programmierung – Teil II ______________________239
public bool Fuettern(Nahrung Futter) { return(Fuettern(Futter, 1)); } public bool Fuettern(Nahrung Futter, int Portionen) { switch(Futter) { case Nahrung.Thunfisch : case Nahrung.Rinderhack : case Nahrung.Huehnchen : this.Zufriedenheitsindex+=Portionen; return(true); default: this.Zufriedenheitsindex-=Portionen; return(false); } } } class ConsoleApp { static void Main() { Katze meineKatze = new Katze(); meineKatze.Zufriedenheitsindex = 5; Console.Write("Zufriedenheitsindex vor Fötterung: "); Console.WriteLine(meineKatze.Zufriedenheitsindex.ToString() + ""); if(meineKatze.Fuettern(Katze.Nahrung.Thunfisch, 5)) Console.WriteLine("Das hat geschmeckt!"); else Console.WriteLine("Das war keine gute Idee!"); if(meineKatze.Fuettern(Katze.Nahrung.Trockenfutter)) Console.WriteLine("Das hat geschmeckt!"); else Console.WriteLine("Das war keine gute Idee!"); Console.Write("Zufriedenheitsindex nach Fötterung: "); Console.WriteLine(meineKatze.Zufriedenheitsindex.ToString() + ""); Console.ReadLine();
240 ___________________________________________________ 12.1 Überladungen
} }
Ausgabe im Konsolenfenster: Zufriedenheitsindex vor Fötterung: 5 Das hat geschmeckt! Das war keine gute Idee! Zufriedenheitsindex nach Fötterung: 9
Die Methode Fuettern wurde doppelt implementiert, einmal mit zwei und einmal mit nur einem Parameter. Ruft man letztgenannte Methode auf, ruft diese wiederum die andere mit einem angenommenen Standard-Wert för den zweiten Parameter auf. Das Beispiel ist sehr typisch för die Verwendung von šberladungen. Es gibt aber auch ganz andere Einsatzmðglichkeiten, bei denen sich die Parameter vðllig unterscheiden. Im folgenden Beispiel wird eine Methode Add zur Addierung zweier Werte verwendet. Es kðnnen dabei ganz unterschiedliche Werte öbergeben werden: int, Decimal und auch string. Im Fall der Zeichenkette wird versucht, diese in einen numerischen Wert umzuwandeln, mit dem dann gerechnet werden kann. Gleiche Methoden für unterschiedliche Datentypen
Listing 12.2 ueberladen2.cs using System; class Test { public static int Add(int n1, int n2) { return(n1+n2); } public static Decimal Add(Decimal n1, Decimal n2) { return(n1+n2); } public static int Add(string s1, string s2) { int n1 = Int32.Parse(s1); int n2 = Int32.Parse(s2); return(Add(n1, n2)); } } class ConsoleApp { static void Main()
12 Klassen & objektorientierte Programmierung – Teil II ______________________241
{ Console.WriteLine(Test.Add("25", "16").ToString()); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: 41
Wie Sie sehen, kðnnen öberladene Methoden durchaus auch andere Röckgabewerte erhalten. Im Beispiel existieren insgesamt drei Implementierungen der Methode Add, die jeweils öber Parameter des gleichen Datentyps verfögen. Bei šbergabe von Zeichenketten wie im Beispiel wird eine Umwandlung vorgenommen und dann wieder die Methode zur Addition von int-Werten aufgerufen. Im Beispiel mag dies öbertrieben wirken, gewðhnt man sich jedoch an diese Wiederverwendung, kann diese bei komplexeren Methoden viel Zeit und Tipparbeit sparen und reduziert die FehleranfÙlligkeit.
12.2 Vererbung Der Themenkomplex Vererbung ist gleichwohl wichtig, interessant und komplex. Es ist ein Grundprinzip der objektorientierten Programmierung, bestehende Klassen zu beerben. Bei diesem Vorgang werden die Mitglieder einer Klasse wie Eigenschaften und Methoden sowie deren Implementierung öbernommen. Die geerbte oder auch abgeleitete Klasse besitzt also automatisch deren FunktionalitÙt und kann diese erweitern und modifizieren. Bei der Vererbung entsteht eine ¹ist ein(e)ª-Beziehung von der Erbklasse zur Basisklasse. Jede geerbte Klasse ¹ist ein(e)ª Basisklasse. Man kann zudem einer Variable vom Typ der Basisklasse in jedem Fall die Erbklasse zuweisen. Diese Mðglichkeit nennt man Polymorphie. Andersherum ist nicht jede Basisklasse einer Variable vom Typ der Erbklasse zuweisbar, denn von einer Basisklasse kðnnen ja ganz unterschiedlich Erbklassen mit jeweils unterschiedlichen Implementierungen abgeleitet werden. Beachten Sie, dass eine Klasse immer nur von genau einer anderen Basisklasse abgeleitet werden kann.
Basisklasse und Erbklasse
242 ______________________________________________________ 12.2 Vererbung
Ein tierisch gutes Beispiel
Abbildung 12.1 Eine Katze „ist ein“ Tier „ist ein“ object
Die Vererbung lÙsst sich mit dem Ihnen bereits wohlbekannten Beispiel der Katze hervorragend veranschaulichen. Eine Katze ist ein Tier und hat entsprechende Eigenschaften und Methoden, die för jedes Tier existieren. Insofern erbt die Katze diese vom Tier. Auch ein Hund wÙre ein Tier und somit von dieser Klasse abgeleitet. Nach den Regel von C# ist zudem jedes Objekt von der Mutter aller Objekte mit dem treffenden Namen object abgeleitet. Ein Tier ist also direkt oder indirekt auch wieder ein object. Verallgemeinerung ist also die Basis der Vererbung von Klassen. Die Abbildung zeigt die AbhÙngigkeit.
12.2.1 Einfache Vererbungen Vererbungen werden auf Basis der kompletten Klassen durchgeföhrt, nicht etwa auf Teilen dieser. Aus diesem Grund erfolgt die Notation bereits bei der Benennung der Klasse. Der Name der Basisklasse wird mit einem Doppelpunkt getrennt hinter dem Namen der neuen Erbklasse notiert. Es ergibt sich hierför folgendes Schema:
12 Klassen & objektorientierte Programmierung – Teil II ______________________243
class ErbklassenName : BasisklassenName { // Implementierung der abgeleiteten Klassen }
Das nachfolgende Beispiel realisiert die aufgestellte Erb-AbhÙngigkeit zwischen Katze und Tier. Es existiert eine Klasse Tier mit der Eigenschaft Name. Jedes Tier sollte einen Namen tragen. Die Klasse Katze leitet sich von der Klasse Tier ab und unterstötzt zusÙtzlich zu deren FunktionalitÙt eine neue Eigenschaft Fellfarbe, die uns öber selbige Auskunft geben soll. Listing 12.3 einfachevererbung1.cs using System; class Tier { string _Name; public string Name { get { return(_Name); } set { _Name = value; } } } class Katze : Tier { string _Fellfarbe; public string Fellfarbe { get { return(_Fellfarbe); } set { _Fellfarbe = value; } } }
244 ______________________________________________________ 12.2 Vererbung
class ConsoleApp { static void Main() { Katze meineKatze = new Katze(); meineKatze.Name = "Filou"; meineKatze.Fellfarbe = "rot"; Console.WriteLine("Meine Katze heiût " + meineKatze.Name + " und hat die Fellfarbe \"" + meineKatze.Fellfarbe + "\"."); Console.ReadLine(); } }
Die Verwendung der Klasse Katze zeigt, dass diese sowohl öber die eigene Eigenschaft Fellfarbe als auch öber die geerbte Eigenschaft Name verfögt. Beide Eigenschaften werden im Konsolenfenster ausgegeben: Meine Katze heiût Filou und hat die Fellfarbe "rot".
12.2.2 Überschreiben von Klassen-Mitgliedern Wie bereits beschrieben, lassen sich im Zuge der Vererbung auch Modifikationen vornehmen. Hierzu wird das jeweilige Mitglied der Klasse öberschrieben und neu implementiert. Die Basisklasse muss dies för das Mitglied explizit erlauben und hierför mit dem Modifikator virtual versehen. Die neue Implementierung in der abgeleiteten Klasse verwendet den Modifikator override zur Kennzeichnung der šberschreibung. Das Beispiel zeigt die beiden Klassen Tier und Katze. Die Klasse Tier implementiert eine Methode Fuettern, die den Zufriedenheitsindex des Tieres erhðht. Die Katze soll zudem schnurren, wenn sie geföttert wird. Die Methode wird daher öberschrieben und die neue boolesche Eigenschaft Schnurrt gesetzt. Listing 12.4 ueberschreiben1.cs using System; class Tier { int _Zufriedenheitsindex;
12 Klassen & objektorientierte Programmierung – Teil II ______________________245
public int Zufriedenheitsindex { get { return(_Zufriedenheitsindex); } set { _Zufriedenheitsindex = value; } } virtual public void Fuettern(string Nahrung) { Zufriedenheitsindex += 1; } } class Katze : Tier { bool _Schnurrt; public bool Schnurrt { get { return(_Schnurrt); } } override public void Fuettern(string Nahrung) { _Schnurrt = true; Zufriedenheitsindex += 1; } }
class ConsoleApp { static void Main() { Katze meineKatze = new Katze(); meineKatze.Fuettern("Thunfisch"); if(meineKatze.Schnurrt) Console.WriteLine("Meine Katze schnurrt :-)"); else
246 ______________________________________________________ 12.2 Vererbung
Console.WriteLine("Meine Katze schnurrt nicht :-("); Console.ReadLine(); } }
Die Katze ist sichtlich zufrieden: Meine Katze schnurrt :-) Basisklasse aufrufen
Das Beispiel funktioniert so weit, hat jedoch einen Schðnheitsfehler. Die urspröngliche FunktionalitÙt der Methode Fuettern, also das Hochsetzen des Zufriedenheitsindex, wurde in der abgeleiteten Klasse noch einmal implementiert. Was aber, wenn diese FunktionalitÙt wesentlich komplexer als im gezeigten Beispiel wÙre? Es widerspricht der Regel der objektorientierten Programmierung, Code nicht doppelt zu halten und vor allem auch bei …nderungen nicht doppelt pflegen zu mössen. Abhilfe schafft hier das Schlösselwort base mit dem analog zu this der Zugriff auf die Basisklasse mðglich ist. Auf diese Weise lÙsst sich innerhalb der neuen Methode die alte aufrufen. Das Beispiel wurde dahingehend geÙndert; im Folgenden nur der entsprechende Ausschnitt der abgeleiteten Klasse Katze: Listing 12.5 ueberschreiben2.cs ... override public void Fuettern(string Nahrung) { _Schnurrt = true; base.Fuettern(Nahrung); } ...
An der Ausgabe im Konsolenfenster hat sich natörlich nichts geÙndert, die Katze schnurrt weiterhin zufrieden: Meine Katze schnurrt :-)
12 Klassen & objektorientierte Programmierung – Teil II ______________________247
12.2.3 Verbergen von Klassen-Mitgliedern Die šberschreibung von Mitgliedern funktioniert so lange, wie diese in der Basisklasse explizit mit dem Modifikator virtual daför freigegeben wurde. Was aber, wenn ein Klassenmitglied nicht freigegeben wurde und Sie die Klasse nicht anpassen kðnnen, weil diese beispielsweise von einem Dritt-Hersteller stammt? In diesem Fall kðnnen Sie die urspröngliche Methode verbergen und durch eine neue ersetzen. Es wird hierzu das Schlösselwort new verwendet. Das folgende Beispiel wurde fast unverÙndert aus dem vorangegangenen Abschnitt öbernommen. Allerdings fehlt der ursprönglichen Methode Fuettern der Modifikator virtual. Aus diesem Grund musste die neue Methode mit dem Modifikator new versehen werden. Die zusÙtzliche Angabe von override ist nicht notwendig. new Listing 12.6 verbergen1.cs using System; class Tier { int _Zufriedenheitsindex; public int Zufriedenheitsindex { get { return(_Zufriedenheitsindex); } set { _Zufriedenheitsindex = value; } } public void Fuettern(string Nahrung) { Zufriedenheitsindex += 1; } } class Katze : Tier { bool _Schnurrt; public bool Schnurrt { get {
248 ______________________________________________________ 12.2 Vererbung
return(_Schnurrt); } } new public void Fuettern(string Nahrung) { _Schnurrt = true; base.Fuettern(Nahrung); } } class ConsoleApp { static void Main() { Katze meineKatze = new Katze(); meineKatze.Fuettern("Thunfisch"); if(meineKatze.Schnurrt) Console.WriteLine("Meine Katze schnurrt :-)"); else Console.WriteLine("Meine Katze schnurrt nicht :-("); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster ist natörlich identisch mit den vorherigen. Obwohl die Methode neu implementiert wurde, ist der Zugriff auf die urspröngliche Methode der Basisklasse mðglich. Ganz wie beim šberschreiben wird auch hier das Schlösselwort base verwendet.
12.2.4 Konstruktoren und die Vererbung Wenn Sie davon ausgegangen sind, dass auch Konstruktoren vererbt werden, dann haben Sie sich leider getÙuscht: sie werden es nicht. Ich lasse das folgende Beispiel einfach einmal för mich sprechen: Listing 12.7 konstruktoren1.cs using System; class Tier { string _Name;
12 Klassen & objektorientierte Programmierung – Teil II ______________________249
public Tier(string Name) { this.Name = Name; } public string Name { get { return(_Name); } set { _Name = value; } } } class Katze : Tier { string _Fellfarbe; public string Fellfarbe { get { return(_Fellfarbe); } set { _Fellfarbe = value; } } }
class ConsoleApp { static void Main() { Katze meineKatze = new Katze("Filou"); meineKatze.Fellfarbe = "rot";
250 ______________________________________________________ 12.2 Vererbung
Console.WriteLine("Meine Katze heiût " + meineKatze.Name + " und hat die Fellfarbe \"" + meineKatze.Fellfarbe + "\"."); Console.ReadLine(); } }
Sie kennen das Beispiel bereits aus den vorherigen Abschnitten, es wurde hier lediglich ein Konstruktor in der Basisklasse eingefögt, öber den sich die Eigenschaft Name einfach initialisieren lÙsst. Eigentlich sollte man annehmen, dass dies nun auch mit der abgeleiteten Klasse Katze mðglich ist, denn schlieûlich wurde ja auch die Eigenschaft geerbt. Die Abbildung belehrt eines Besseren.
Abbildung 12.2 Konstruktoren werden augenscheinlich nicht vererbt
Wenn also Konstruktoren nicht vererbt werden, mössen sie neu implementiert werden. Das ist im Grunde nur logisch, denn es sollen ja auch die lokalen Mitglieder der Klasse initialisiert werden. Damit auch die Basisklasse initialisiert werden kann, steht erneut das Schlösselwort base zur Verfögung. Dieses wird wie eine Methode mit den Parametern des Konstruktors aufgerufen und wie folgt notiert: base()
class Erbklasse: Basisklasse { public Erbklasse : base() { // Initialisierung } public Erbklasse(Datentyp Parameter) : base(Parameter)
12 Klassen & objektorientierte Programmierung – Teil II ______________________251
{ // Initialisierung } ... }
Die lokale Initialisierung ist optional, es kann auch nur der jeweilige Konstruktor der Basisklasse aufgerufen werden. Natörlich mðchte ich Ihnen nicht ein angepasstes Beispiel vorenthalten. Jetzt kðnnen Sie tatsÙchlich den Namen der Katze im Konstruktor öbergeben. Listing 12.8 konstruktoren2.cs using System; class Tier { string _Name; public Tier(string Name) { this.Name = Name; } public string Name { get { return(_Name); } set { _Name = value; } } } class Katze : Tier { string _Fellfarbe; public Katze(string Name) : base(Name) {} public string Fellfarbe { get {
252 ______________________________________________________ 12.2 Vererbung
return(_Fellfarbe); } set { _Fellfarbe = value; } } } class ConsoleApp { static void Main() { Katze meineKatze = new Katze("Filou"); meineKatze.Fellfarbe = "rot"; Console.WriteLine("Meine Katze heiût " + meineKatze.Name + " und hat die Fellfarbe \"" + meineKatze.Fellfarbe + "\"."); Console.ReadLine(); } }
Im Beispiel wurde keine lokale Initialisierung vorgenommen. Der šbersicht halber wurden daher die geschweiften Klammern direkt hinter dem Konstruktor notiert. Die Ausgabe im Konsolenfenster sieht jetzt schon wesentlich besser aus: Meine Katze heiût Filou und hat die Fellfarbe "rot".
12.2.5 Klassen versiegeln Angenommen, Sie entwickeln eine Komponente und mðchten unbedingt verhindern, dass andere Entwickler Klassen von Ihrer Komponente ableiten. Mit den bisher vorgestellten Mðglichkeiten lÙsst sich dies nicht verhindern, und auch wenn Sie Mitglieder nicht explizit zum šberschreiben freigeben, verfögen Sie mit dem Verbergen der ursprönglichen Mitglieder öber eine alternative Mðglichkeit. Mit Hilfe des Modifikators sealed kðnnen Sie eine Klasse komplett versiegeln und somit jeglichen Ableitungsversuch im Keim ersticken. Natörlich darf zur Demonstration mal wieder meine Katze herhalten. Im bekannten Beispiel wurde die Klasse Tier versiegelt, indem lediglich der Modifikator sealed vor dem Klassennamen notiert wurde.
12 Klassen & objektorientierte Programmierung – Teil II ______________________253
Listing 12.9 versiegelt1.cs sealed class Tier { ...
Wie die Abbildung zeigt, hat die Versiegelung den gewönschten Effekt. Eine Vererbung auf Basis der Klasse Tier ist nicht mehr mðglich, die Klasse ist geschötzt. Beachten Sie bitte, dass Vererbungsmodifikatoren wie abstract und virtual in einer versiegelten Klasse keinen Sinn ergeben und daher vom Compiler angemosert werden. Ein sehr bekanntes Beispiel för eine versiegelte Klasse ist öbrigens der Datentyp string ± schade eigentlich.
Abbildung 12.3 Von einer versiegelten Klasse kann nicht abgeleitet werden.
12.2.6 Abstrakte Klassen Abstrakte Klassen sind Klassen, die nicht instanziiert, sondern nur abgeleitet werden kðnnen. Abstrakte Klassen enthalten jedoch regulÙre Deklarationen för Mitglieder wie Eigenschaften und Methoden und auch entsprechende FunktionalitÙt. Man verwendet eine abstrakte Klasse zur Verallgemeinerung und zur Zusammenfassung, um so eine gemeinsame Grundlage för verschiedene abgeleitete Klassen zu bieten. Diese Klassenart wird mit dem Modifikator abstract notiert:
sealed
254 ______________________________________________________ 12.2 Vererbung
abstract
abstract class Klassenname { ... }
Ein prÙdestinierter Kandidat för solch eine abstrakte Klasse ist die bereits verwendete Basisklasse Tier. Haben Sie schon einmal ein Tier gesehen? Bestimmt nicht, denn es ist ja nur ein Oberbegriff, eine Verallgemeinerung. Es laufen keine Tier-Instanzen in unseren WÙldern herum! Definition eines allgemeinen Tieres
Listing 12.10 abstrakt1.cs using System; sealed class Tier { int _Zufriedenheitsindex; public int Zufriedenheitsindex { get { return(_Zufriedenheitsindex); } set { _Zufriedenheitsindex = value; } } public void Fuettern(string Nahrung) { Zufriedenheitsindex += 1; } } class Katze : Tier { ...
Sie kðnnen auch innerhalb einer abstrakten Klasse einzelne Mitglieder mit dem Modifikator abstract versehen. In diesem Fall wird lediglich das Grundgeröst des Mitglieds deklariert. Die abgeleitete Klasse ist auf diese Weise gezwungen, das abstrakte Mitglied zu ersetzen und somit zu implementieren.
12 Klassen & objektorientierte Programmierung – Teil II ______________________255
Listing 12.11 abstrakt2.cs using System; abstract class Tier { abstract public void Fuettern(string Nahrung); } class Katze : Tier { bool _Schnurrt; public bool Schnurrt { get { return(_Schnurrt); } } override public void Fuettern(string Nahrung) { _Schnurrt = true; } } class ConsoleApp { static void Main() { Katze meineKatze = new Katze(); meineKatze.Fuettern("Thunfisch"); if(meineKatze.Schnurrt) Console.WriteLine("Meine Katze schnurrt :-)"); else Console.WriteLine("Meine Katze schnurrt nicht :-("); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Meine Katze schnurrt :-)
256 ___________________________________________________ 12.3 Schnittstellen
Die Methode Fuettern wurde abstrakt in der Basisklasse Tier deklariert und muss daher von der abgeleiteten Klasse Katze explizit implementiert werden. Anderenfalls kommt es zu einem Compiler-Fehler.
12.3 Schnittstellen Schnittstellen sind abstrakten Klassen sehr Ùhnlich. Auch sie implementieren keinerlei FunktionalitÙt, sondern stellen lediglich den Rahmen för sie dar. Auch wenn die Notation einer Vererbung entspricht, so spricht man doch vom Unterstötzen oder auch Implementieren einer Schnittstelle. Dies ist unabhÙngig von der Vererbung zu sehen und kann auch parallel zu einer solchen verwendet werden. Die Unterstötzung mehrerer Schnittstellen ist ebenfalls ohne weiteres mðglich.
12.3.1 Deklaration einer Schnittstelle Schnittstellen werden mit dem Schlösselwort interface eingeleitet. Der weitere Aufbau entspricht ansonsten aber weitgehend einer regulÙren Klasse. interface
interface Schnittstellenname { // Deklaration der Schnittstellen-Mitglieder }
In Schnittstellen kðnnen keine Variablen oder Felder deklariert werden. Andere Mitglieder wie Eigenschaften, Methoden und Ereignisse sind aber mðglich. Diese werden allerdings nicht ðffentlich deklariert (also mit dem Modifikator public), vielmehr wird auf die Angabe eines Zugriffsmodifikators vðllig verzichtet. Namensgebung
Microsoft empfiehlt, den Namen einer Schnittstelle immer mit einem groûen I wie Interface beginnen zu lassen. Auf diese Weise sieht man auf den ersten Blick, ob es sich um eine Klasse oder eine Schnittstelle handelt. Die Deklaration von Eigenschaften innerhalb von Schnittstellen bringt eine kleine Besonderheit mit sich. Statt der Implementierung in geschweiften Klammern wird einfach ein Semikolon notiert. Natörlich ist es auch hier mðglich, nur eine der beiden Zugriffsmðglichkeiten get oder set zu implementieren.
12 Klassen & objektorientierte Programmierung – Teil II ______________________257
Datentyp Eigenschaftsname { get; set; }
12.3.2 Implementierung einer Schnittstelle Die Unterstötzung einer Schnittstelle wird analog zur Vererbung vorgenommen. class Klassenname : Schnittstellenname { // ... }
Sollen weitere Schnittstellen unterstötzt oder von einer Klasse abgeleitet werden, notiert man die Namen mit Kommata separiert. class Klassenname : Basisklasse, Schnittstelle1, Schnittstelle2 { // ... }
Die Mitglieder einer unterstötzten Schnittstelle mössen immer ðffentlich implementiert werden, da ansonsten ein Zugriff von auûen nicht mðglich wÙre. Durch die Mðglichkeit, parallel zu erben und mehrere Schnittstellen zu unterstötzen, kann es sehr leicht zu Namenskonflikten kommen. Aus diesem Grund muss beim Zugriff auf die Mitglieder einer unterstötzten Schnittstelle zuvor immer eine explizite Konvertierung stattfinden. Ein direkter Zugriff auf die SchnittstellenMitglieder öber die Instanz ist nicht mðglich. Schnittstelle Schnittstellen-Variable = (Schnittstelle) Klassen-Variable;
12.3.3 Noch ein tierisches Beispiel Ein Beispiel liefert natörlich wieder einmal die Katzenwelt. Tiere sind die Basisklasse und Katzen leiten sich von dieser ab. Aber es gibt ganz unterschiedliche Katzen. Die einen leben wild und die anderen beim Menschen im Haus. Wieder
Mehrere Schnittstellen unterstützen
258 ___________________________________________________ 12.3 Schnittstellen
andere leben zwar beim Menschen im Haus, sind aber dennoch wild und genieûen die Freiheit der Natur tagsöber. Die Klasse ist also weiterhin die Katze, die sich von der Klasse Tier ableitet. šber verschiedene Schnittstellen kann nun definiert werden, ob es sich um ein Haustier oder ein wildes Tier handelt. So ergibt sich beispielsweise die Hauskatze. Im konkreten Fall existiert die bereits bekannte abstrakte Klasse Tier. Zudem wird eine Schnittstelle IHaustier angeboten, die Eigenschaften wie Haart und Stubenrein deklariert. Die zwei Klassen Hauskatze und Haushund werden jeweils von der Klasse Tier abgeleitet und unterstötzen die genannte Schnittstelle. Auf diese Weise ist ein einheitlicher Zugriff auf die Methoden der Schnittstelle mðglich, unabhÙngig davon, ob es sich um einen Hund oder eine Katze handelt. Schnittstelle Haustier
Listing 12.12 schnittstellen1.cs using System; abstract class Tier { abstract public void Fuettern(string Nahrung); } interface IHaustier { bool Haart { get; } bool Stubenrein { get; } }
class { bool bool bool
Hauskatze : Tier, IHaustier _Schnurrt; _Haart = true; _Stubenrein = true;
public bool Schnurrt { get { return(_Schnurrt); } }
12 Klassen & objektorientierte Programmierung – Teil II ______________________259
public bool Haart { get { return(_Haart); } } public bool Stubenrein { get { return(_Stubenrein); } } override public void Fuettern(string Nahrung) { _Schnurrt = true; } } class { bool bool bool
Haushund : Tier, IHaustier _Bellt; _Haart = false; _Stubenrein = false;
bool Bellt { get { return(_Bellt); } } public bool Haart { get { return(_Haart); } } public bool Stubenrein { get {
260 ___________________________________________________ 12.3 Schnittstellen
return(_Stubenrein); } } override public void Fuettern(string Nahrung) { _Bellt = false; } } class ConsoleApp { static void Main() { Hauskatze meineKatze = new Hauskatze(); Haushund meinHund = new Haushund(); IHaustier Haustier1 = (IHaustier) meineKatze; IHaustier Haustier2 = (IHaustier) meinHund; if(Haustier1.Haart) Console.WriteLine("Haustier1 haart"); else Console.WriteLine("Haustier1 haart nicht"); if(Haustier2.Haart) Console.WriteLine("Haustier2 haart"); else Console.WriteLine("Haustier2 haart nicht"); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Haustier1 haart Haustier2 haart nicht
Streng gesehen mösste die Klasse Hauskatze natörlich von einer Klasse Katze abgeleitet werden und analog zum vorliegenden Beispiel die Schnittstelle Haustier unterstötzen ± aus šbersichtsgrönden habe ich hierauf jedoch verzichtet.
12 Klassen & objektorientierte Programmierung – Teil II ______________________261
12.3.4 Mehrere gleichnamige Schnittstellen-Mitglieder Weiter oben habe ich bereits angedeutet, dass Objekte explizit konvertiert werden mössen, damit auf Schnittstellen-Mitglieder zugegriffen werden kann. Als Begröndung habe ich mðgliche Namenskonflikte genannt. Doch lassen sich öberhaupt gleichnamige Schnittstellen-Mitglieder implementieren? Woher soll der Compiler wissen, welche Implementierung zu welcher Schnittstelle gehðrt? Die explizite Schnittstellen-Implementierung weiû diese Fragen zu beantworten. Hierbei wird einfach der Name der zugehðrigen Schnittstelle vor dem Namen des Mitglieds notiert. Das nachfolgende Beispiel zeigt die Schnittstellen ITest1 und ITest2. Beide deklarieren eine Methode Test und werden gemeinsam von einer Klasse TestKlasse unterstötzt. Damit kein Namenskonflikt auftritt, wird der Name der Schnittstelle vor der Implementierung der jeweiligen Methode Test notiert. Listing 12.13 schnittstellen2.cs using System; interface ITest1 { string Test(); } interface ITest2 { string Test(); }
class TestKlasse : ITest1, ITest2 { public string Name; public TestKlasse(string Name) { this.Name = Name; } string ITest1.Test() { return(string.Format("Test för ITest1, Instanz: {0}", this.Name)); } string ITest2.Test() { return(string.Format("Test för ITest2, Instanz: {0}", this.Name));
Explizite Implementierung
262 ______________________________ 12.4 Benutzerdefinierte Operatoren erstellen
} } class ConsoleApp { static void Main() { TestKlasse t = new TestKlasse("MeinName"); ITest1 t1 = (ITest1) t; ITest2 t2 = (ITest2) t; Console.WriteLine(t1.Test()); Console.WriteLine(t2.Test()); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster zeigt, dass jeweils die Implementierung der richtigen Schnittstelle aufgerufen wurde: Test för ITest1, Instanz: MeinName Test för ITest2, Instanz: MeinName
12.4 Benutzerdefinierte Operatoren erstellen Sie kðnnen mit C# wirklich fast alles machen. Ein gutes Beispiel hierför ist die Mðglichkeit, benutzerdefinierte Operatoren för Klassen zu implementieren. Sie kðnnen dem Entwickler so beispielsweise die Mðglichkeit geben, zwei Ihrer Klassen einfach zu addieren.
12.4.1 Arithmetische Operatoren
Grundrechenarten für Klassen
Die Implementierungen von arithmetischen Operatoren in Ihre Klassen kann Ihnen viel Zeit sparen, wenn Sie mit den mðglicherweise ganz unterschiedlichen Werten einer Klasse ¹addierenª mðchten. Es lassen sich alle verfögbaren arithmetischen Grundoperatoren öberschreiben, insbesondere wichtig sind natörlich die Grundrechenarten, symbolisiert durch +, -, * und /. Die Implementierung beziehungsweise šberladung eines Operators folgt Ùhnlichen Regeln wie die regulÙre šberladung von Methoden. Natörlich darf auch diesmal ein spezieller Modifikator nicht fehlen, der den Namen operator trÙgt. Als Name der
12 Klassen & objektorientierte Programmierung – Teil II ______________________263
vermeintlichen Methode wird das Operatorsymbol, also beispielsweise das Zeichen ¹+ª verwendet. Die beiden Operanden werden als Parameter definiert. Der Röckgabewert der Methode ist in der Regel identisch mit der Klasse, auf die sich der Operator bezieht und die diesen implementiert. Zu beachten ist, dass alle benutzerdefinierten Operatoren immer als statische Methoden implementiert werden mössen. Das ist auch ganz logisch, denn diese beziehen sich ja nicht auf eine bestimmte Instanz, sondern auf den Datentyp ganz allgemein. Es ergibt sich das folgende Schema: public static operator Datentyp Operator(Datentyp Parameter, Datentyp Parameter)
Das nachfolgende Beispiel zeigt einen benutzerdefinierten Operator im Einsatz. Gegeben ist die Klasse Point, die einen Punkt im Koordinatensystem reprÙsentiert. Zwei dieser Punkte werden addiert, wobei jeweils der x- und auch der y-Wert addiert werden und somit einen neuen Punkt ergeben sollen. Listing 12.14 arithmetisch1.cs using System; class Point { public int x; public int y; public Point() {} public Point(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return(string.Format("x: {0}, y: {1}", x, y)); } public static Point operator +(Point p1, Point p2) { Point newP = new Point(); newP.x = p1.x + p2.x; newP.y = p1.y + p2.y; return(newP); }
Zwei Punkte addieren
264 ______________________________ 12.4 Benutzerdefinierte Operatoren erstellen
} class ConsoleApp { static void Main() { Point Punkt1 = new Point(10, 15); Point Punkt2 = new Point(121, 12); Point Punkt3 = Punkt1 + Punkt2; Console.WriteLine(Punkt3.ToString()); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster zeigt, dass durch die vermeintliche Addition der beiden Klassen in Wahrheit sowohl der x- als auch der y-Wert korrekt addiert wurden. Zur Ausgabe wurde öbrigens die Methode ToString() öberschrieben, sodass die Methode beide Werte x und y formatiert ausgibt. Erinnern Sie sich? x: 131, y: 27
Im Beispiel sind der linke und rechte Datentyp der Operation identisch. Es geht aber auch anders. Beispielsweise kðnnte man annehmen, dass sich eine Addition der Klasse Point mit einem int immer auf den x-Wert bezieht. Das Beispiel wurde hierzu um einen Operator erweitert. Listing 12.15 arithmetisch2.cs using System; class Point { public int x; public int y; public Point() {} public Point(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return(string.Format("x: {0}, y: {1}", x, y));
12 Klassen & objektorientierte Programmierung – Teil II ______________________265
} public static Point operator +(Point p1, Point p2) { Point newP = new Point(); newP.x = p1.x + p2.x; newP.y = p1.y + p2.y; return(newP); } public static Point operator +(Point p, int x) { Point newP = new Point(); newP.x = p.x + x; newP.y = p.y; return(newP); } } class ConsoleApp { static void Main() { Point Punkt1 = new Point(10, 15); Point Punkt2 = Punkt1 + 8; Console.WriteLine(Punkt2.ToString()); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: x: 18, y: 15
So schðn die Mðglichkeit der šberladung von Operatoren ist, so unangenehm kann man sich bei der Implementierung irren. Im vorangehenden Beispiel wurde eine Instanz der Klasse Point mit einem int-Wert addiert. Doch angenommen, man wörde die Notation umdrehen und die Point-Instanz zu dem Wert 8 addieren. Point Punkt2 = 8 + Punkt1;
Was passiert dann? Ganz klar, es wörde ein Compiler-Fehler gemeldet werden, denn es existiert kein Operator för int + Point. Die Reihenfolge der Parameter eines Operators ist also ausschlaggebend för die Richtung der Berechnung. Der Hintergrund hierför ist beispielsweise bei der Subtraktion oder Division deutlich zu
266 ______________________________ 12.4 Benutzerdefinierte Operatoren erstellen
sehen, denn hier spielt die Reihenfolge ja eine elementare Rolle. Im vorliegenden Fall der Addition ist sie jedoch irrelevant. Ein weiterer Operator mit umgedrehten Parametern kann also ungehindert die Berechnung dem anderen Operator öberlassen. Die Deklaration sieht wie folgt aus:
Listing 12.16 arithmetisch3.cs // Implementierung analog zu arithmetisch2.cs ... public static Point operator +(int x, Point p) { return(p + x); } ...
12.4.2 Vergleichsoperatoren Auch Vergleichsoperatoren lassen sich auf einfache Weise öberladen. Das Prinzip entspricht dabei den arithmetischen Operatoren, lediglich der Röckgabewert ist hier immer bool. Wichtig ist, dass die Vergleichsoperatoren immer paarweise auftreten. Wenn Sie also == implementieren mössen Sie auch den Gegenspieler != aufnehmen. Gleiches gilt för < und > sowie <= und >=. Zwei Instanzen der Klasse Point sollen verglichen werden. Die regulÙren Operatoren wörden lediglich die Verweise vergleichen und feststellen, dass diese nicht öbereinstimmen. Die benutzerdefinierten Vergleichsoperatoren vergleichen hingegen die x- und y-Werte beider Instanzen. Sind diese jeweils identisch, handelt es sich um denselben Punkt. == und != Listing 12.17 vergleich1.cs using System; class Point { public int x; public int y; public Point() {} public Point(int x, int y) {
12 Klassen & objektorientierte Programmierung – Teil II ______________________267
this.x = x; this.y = y; } public override string ToString() { return(string.Format("x: {0}, y: {1}", x, y)); } public static bool operator ==(Point p1, Point p2) { return(p1.x == p2.x && p1.y == p2.y); } public static bool operator !=(Point p1, Point p2) { return(!(p1 == p2)); } public new bool Equals(Point p) { return(this == p); } } class ConsoleApp { static void Main() { Point Punkt1 = new Point(10, 15); Point Punkt2 = new Point(10, 15); if(Punkt1 == Punkt2) Console.WriteLine("Punkt1 und Punkt2 sind identisch."); else Console.WriteLine("Punkt1 und Punkt2 sind nicht identisch."); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Punkt1 und Punkt2 sind identisch.
Bei der Implementierung des Vergleichsoperators != habe ich es mir ein wenig einfach gemacht; dieser negiert einfach das Ergebnis des Operators ==; warum auch nicht? šbrigens: In diesem Zusammenhang habe ich auch die urspröngliche
268 ______________________________ 12.4 Benutzerdefinierte Operatoren erstellen
Methode Equals verborgen und durch eine neue Implementierung ersetzt. Diese pröft nun auch die Werte und nicht die Verweise der beiden Instanzen ab und entspricht somit dem Operator ==. Ich habe schon angedeutet, dass auch andere Vergleichsoperatoren wie grðûer und kleiner öberschrieben werden kðnnen. Auch dies lÙsst sich an Hand der Klasse Point veranschaulichen. Dabei wird jeweils das Ergebnis der Multiplikation von xund y-Wert verglichen. Der Punkt, der also weiter vom Nullpunkt 0, 0 entfernt ist, ist der grðûere und umgekehrt. Das nachfolgende Beispiel implementiert die Operatoren < und > sowie auch <= und >=. Letztere verwenden einfach das Ergebnis der bereits vorhandenen Operatoren und liefern dieses verodert zuröck, ganz nach dem Prinzip ¹grðûer oder gleichª. Vergleiche im Überblick
Listing 12.18 vergleich2.cs using System; class Point { public int x; public int y; public Point() {} public Point(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return(string.Format("x: {0}, y: {1}", x, y)); } public static bool operator ==(Point p1, Point p2) { return(p1.x == p2.x && p1.y == p2.y); } public static bool operator !=(Point p1, Point p2) { return(!(p1 == p2)); } public new bool Equals(Point p) { return(this == p); }
12 Klassen & objektorientierte Programmierung – Teil II ______________________269
public static bool operator <(Point p1, Point p2) { return((p1.x * p1.y) < (p2.x * p2.y)); } public static bool operator >(Point p1, Point p2) { return((p1.x * p1.y) > (p2.x * p2.y)); } public static bool operator <=(Point p1, Point p2) { return(p1 == p2 || p1 < p2); } public static bool operator >=(Point p1, Point p2) { return(p1 == p2 || p1 > p2); } } class ConsoleApp { static void Main() { Point Punkt1 = new Point(50, 15); Point Punkt2 = new Point(10, 11); if(Punkt1 >= Punkt2) Console.WriteLine("Punkt1 ist grðûer gleich Punkt2."); else Console.WriteLine("Punkt1 ist nicht grðûer gleich Punkt2."); Console.ReadLine(); } }
Ausgabe im Konsolenfenster Punkt1 ist grðûer gleich Punkt2.
270 ______________________________ 12.4 Benutzerdefinierte Operatoren erstellen
12.4.3 Konvertierungsoperatoren Arithmetische Operatoren und Vergleichsoperatoren haben Sie bereits kennen gelernt. Der dritte Teil der benutzerdefinierten Operatoren widmet sich der Konvertierung. Auch hier kðnnen Sie selbst Hand anlegen. Bereits im Kapitel zur allgemeinen Konvertierung von Datentypen ging ich auf die Unterschiede zwischen impliziter und expliziter Konvertierung ein. Die implizite Konvertierung sollte nur dann verwendet werden, wenn eine Röckkonvertierung mðglich und ohne Datenverluste durchföhrbar ist. Eine explizite Konvertierung ist in dieser Hinsicht keinen BeschrÙnkungen unterworfen. Ein Konvertierungsoperator wird wie die anderen mit dem Modifikator operator notiert. Zuvor muss jedoch angegeben werden, ob dieser implizit (Modifikator implicit) oder explizit (explicit) vorgenommen werden soll. Es ergeben sich die nachfolgenden Schemata: implizit
public static implicit operator Ziel-Datentyp(Quell-Datentyp Variable)
beziehungsweise explizit
public static explicit operator Ziel-Datentyp(Quell-Datentyp Variable)
Implizite Konvertierung Im nachfolgenden Beispiel wurde die Klasse Point umbenannt in Point2D. Des Weiteren wurde eine neue Klasse Point3D eingeföhrt. Diese kennt neben x- und yauch einen z-Wert för die dritte Dimension eines Punktes. Die implizite Konvertierung findet von Point2D nach Point3D statt. Hier kðnnen keine Datenverluste auftreten, da der Wertebereich des Zieldatentyps grðûer ist. Eine Variable vom Typ Point2D kann somit problemlos und ohne Notation eines Konvertierungskonstruktes einer Variablen vom Typ Point3D zugewiesen werden. Point2D à Point3D
Listing 12.19 konvertierung1.cs using System; class Point2D { public int x; public int y; public Point2D() {} public Point2D(int x, int y)
12 Klassen & objektorientierte Programmierung – Teil II ______________________271
{ this.x = x; this.y = y; } public override string ToString() { return(string.Format("x: {0}, y: {1}", x, y)); } public static implicit operator Point3D(Point2D p2d) { Point3D p3d = new Point3D(p2d.x, p2d.y, 0); return(p3d); } } class Point3D { public int x; public int y; public int z; public Point3D() {} public Point3D(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } public override string ToString() { return(string.Format("x: {0}, y: {1}, z: {2}", x, y, z)); } } class ConsoleApp { static void Main() { Point2D p2d = new Point2D(50, 15); Point3D p3d = p2d;
272 ______________________________ 12.4 Benutzerdefinierte Operatoren erstellen
Console.WriteLine(p3d); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: x: 50, y: 15, z: 0
Explizite Konvertierung Nachdem Sie einen zweidimensionalen Punkt in einen dreidimensionalen konvertiert haben, interessiert Sie nun der andere Weg. Natörlich lÙsst sich Point3D nicht ohne Datenverlust in Point2D konvertieren, schlieûlich geht die dritte Dimension unwiderruflich verloren. Es ist daher die Verwendung eines expliziten Konvertierungsoperators anzuraten. Das vorangegangene Beispiel wurde dazu wie folgt erweitert. Point3D à Point2D
Listing 12.20 konvertierung2.cs // Implementierung wie vorheriges Beispiel ... class Point3D { public int x; public int y; public int z; public Point3D() {} public Point3D(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } public override string ToString() { return(string.Format("x: {0}, y: {1}, z: {2}", x, y, z)); } public static explicit operator Point2D(Point3D p3d)
12 Klassen & objektorientierte Programmierung – Teil II ______________________273
{ Point2D p2d = new Point2D(p3d.x, p3d.y); return(p2d); } } ... class ConsoleApp { static void Main() { Point3D p3d = new Point3D(50, 15, 8); Point2D p2d = (Point2D) p3d; Console.WriteLine(p2d); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: x: 50, y: 15
13 Weitergehende Programmierung
Arbeiten mit Threads Reflections Arbeiten mit dem CodeDOM
276 ___________________________________________________________________
Inhaltsübersicht Kapitel 13
13
Weitergehende Programmierung ..................................................................277
13.1 13.2 13.2.1 13.2.2 13.2.3 13.2.4 13.3 13.3.1
Arbeiten mit Threads .........................................................................................277 Reflections.........................................................................................................281 Eine Eigenschaft dynamisch abfragen ...............................................................281 Eine Methode dynamisch aufrufen ....................................................................282 Eine Methode mit Parametern dynamisch aufrufen ...........................................283 Alle Eigenschaften und Methoden auflisten ......................................................284 Arbeiten mit dem CodeDOM ............................................................................286 Ein Programm dynamisch erstellen ...................................................................286
___________________________________________________________________ 277
13 Weitergehende Programmierung In diesem Abschnitt sollen weitergehende Programmiertechniken besprochen werden. Diese entstammen nicht dem Sprachumfang von C#, sondern der Klassenbibliothek des .NET Frameworks. Die Techniken werden nur kurz angerissen. Sollte Sie eines der Themen besonders interessieren, empfehle ich die Lektöre weitergehender Werke oder des .NET Framework SDK von Microsoft, das mit dem Framework installiert wird.
13.1 Arbeiten mit Threads Windows ist ein Multithreading Betriebssystem und auch in C# kðnnen Sie mit eigenen Threads programmieren. Aber was ist eigentlich ein Thread? Normalerweise kennen Sie Programme als sequenziellen Programmablauf. Die Befehle werden Zeile för Zeile abgearbeitet. Ein solcher Befehlsablauf ist ein Thread. In der Regel besteht ein Programm aus einem solchen Thread. Sie kðnnen aber auch individuell weitere Threads erstellen. Diese laufen dann parallel. Sie kðnnen Threads synchronisieren, Daten austauschen und so weiter. Das .NET Framework stellt hierzu ein ausgesprochen flexibles Klassensystem im Namespace System.Threading zur Verfögung. ZusÙtzliche Threads werden verwendet, wenn Sie Aktionen quasi gleichzeitig ausföhren mðchten. Ein Beispiel hierför ist das so genannte Scrapen von (fremden) Webseiten. Ein solches Beispiel finden Sie im nachfolgenden Listing. Es wird ein neuer Thread angelegt, der eine andere Internet-Seite abruft und deren Inhalt in einer Variable ablegt. Der Basis-Thread gibt Fortschrittsmeldungen aus, solange der zweite Thread nicht beendet ist. Wurde die Seite abgerufen, wird deren Inhalt im Konsolenfenster ausgegeben. Listing 13.1 thread1.cs using using using using
System; System.Threading; System.Net; System.IO;
Eine Website asynchron abrufen
278 _____________________________________________ 13.1 Arbeiten mit Threads
class FetchWebsite { public string Url; public string ResponseText; public FetchWebsite(string Url) { this.Url = Url; } public void Fetch() { WebRequest request = WebRequest.Create(Url); WebResponse response = request.GetResponse(); StreamReader reader = new StreamReader(response.GetResponseStream()); ResponseText = reader.ReadToEnd(); } } class ConsoleApp { static void Main() { // URL muss ersetzt werden!! string Url = "http://localhost/asp.net/thread1.htm"; FetchWebsite FW = new FetchWebsite(Url); Console.Write("Abfrage von " + FW.Url + " gestartet"); Thread FetchThread = new Thread(new ThreadStart(FW.Fetch)); FetchThread.Start(); while(FetchThread.IsAlive) { Console.Write("."); Thread.Sleep(100); } Console.WriteLine("\nAbfrage beendet!\n"); Console.WriteLine(FW.ResponseText); Console.ReadLine(); } }
Die Abbildung zeigt deutlich die Ausgabe der Fortschrittsinformation in Form von Punkten. Sobald die Seite gescrapt wurde, wird diese ausgegeben. Beide Aktionen erfolgen parallel.
13 Weitergehende Programmierung _______________________________________279
Was passiert genau? Der Thread wird öber das Schlösselwort neu instanziiert. šbergeben wird ein Delegate auf die gewönschte Start-Methode. Diese darf keine Parameter enthalten und keinen Röckgabewert liefern (void). šber die Methode Start wird der neue Thread gestartet. Die while-Schleife wird so lange durchlaufen, wie der Thread aktiv ist. Dies wird öber die Eigenschaft IsAlive abgefragt. Innerhalb der Schleife wird pro Aufruf ein Punkt ausgegeben, explizit mit der Flush-Methode an das Konsolenfenster öbergeben und der Thread för 100 Millisekunden angehalten. Dies ist notwendig, damit der andere Thread Leerlaufzeit zur Abarbeitung erhÙlt. Wird die Schleife verlassen, steht der Inhalt der gescrapten Internet-Seite zur Verfögung und wird ausgegeben. Auf die Methoden zum Scrapen soll hier nicht genauer eingegangen werden.
Abbildung 13.1 Die Ausgabe des Thread-Beispiels im Konsolenfenster
Übersicht Die Klasse Thread verfögt neben den bereits im Beispiel verwendeten Eigenschaften und Methoden natörlich noch öber weitere Mitglieder. Die beiden Tabellen listen die wichtigsten Eigenschaften und Methoden auf. Statische Mitglieder sind mit einem * gekennzeichnet.
280 _____________________________________________ 13.1 Arbeiten mit Threads
Tabelle 13.1 Die wichtigsten Eigenschaften der Klasse Thread in der Übersicht Eigenschaft
Rückgabewert
Beschreibung
CurrentThread*
Thread
Liefert den aktuellen Thread, in dem der Aufruf erfolgt, als Objekt vom Typ Thread zurück.
IsAlive
bool
Gibt zurück, ob der Thread gestartet und aktiv ist oder nicht.
IsBackground
bool
Liefert oder setzt, ob der Thread im Hintergrund läuft.
Name
string
Liefert oder setzt den Namen des Threads.
ThreadPriority
ThreadPriority
Liefert oder setzt die Priorität des Threads (Enumeration).
ThreadState
ThreadState
Liefert den (Enumeration).
Status
des
Threads
Tabelle 13.2 Die wichtigsten Methoden der Klasse Thread in der Übersicht Methode
Rückgabewert
Beschreibung
AllocateDataSlot*
LocalDataStoreSlot
Alloziiert einen Slot zum Austausch von Daten zwischen Threads.
AllocateNamedDataSlot*
LocalDataStoreSlot
Alloziiert einen Slot mit einem bestimmten Namen zum Austausch von Daten zwischen Threads.
GetData*
object
Liefert Daten aus einem übergebenen Slot aus.
GetNamedDataSlot*
LocalDataStoreSlot
Liefert einen zuvor erstellten Slot mit dem übergebenen Namen zurück.
ResetAbort*
void
Setzt alle eventuell gestarteten AbortAufrufe zurück.
SetData*
-
Setzt Daten in einen übergebenen Slot.
Sleep*
-
Hält den aktuellen Thread für die übergebene Anzahl an Millisekunden an.
Abort
-
Bricht einen Thread komplett ab.
Intrerrupt
-
Unterbricht einen Thread, wenn dieser das nächste Mal im Warte- oder Join-Modus ist.
Join
-
Führt einen Thread mit dem aktuellen zusammen und wartet so, bis dieser beendet wird.
Resume
-
Setzt einen zuvor angehaltenen Thread wieder fort.
Start
-
Startet einen Thread.
Suspend
-
Hält einen Thread an.
13 Weitergehende Programmierung _______________________________________281
13.2 Reflections šber Reflections lassen sich Meta-Daten von Objekten abfragen und manipulieren. Die hierzu verfögbaren Klassen werden im Namespace System.Reflection zusammengefasst. Auf die einzelnen Objekte soll an dieser Stelle nicht weiter eingegangen werden, allerdings mðchte ich Ihnen die prinzipiellen Mðglichkeiten an Hand einiger Beispiele demonstrieren.
13.2.1 Eine Eigenschaft dynamisch abfragen Im ersten Beispiel wird der Inhalt der Eigenschaft einer Klasse dynamisch abgefragt und im Konsolenfenster ausgegeben. PropertyInfo Listing 13.2 reflections1.cs using System; using System.Reflection; class Test { string _ID; public string ID { get { return(_ID); } set { _ID = value; } } }
class ConsoleApp { static void Main() { Test test = new Test(); test.ID = "hallo welt"; Type t = test.GetType();
282 _____________________________________________________ 13.2 Reflections
PropertyInfo property = t.GetProperty("ID"); Console.WriteLine(property.Name + " = " + property.GetValue(test, null).ToString()); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: ID = hallo welt
13.2.2 Eine Methode dynamisch aufrufen Auch der Zugriff auf Methoden ist mðglich. Im zweiten Beispiel wird die Methode einer Klasse aufgerufen und der Röckgabewert im Konsolenfenster ausgegeben. MethodInfo Listing 13.3 reflections2.cs using System; using System.Reflection; class Test { string _ID; public string ID { get { return(_ID); } set { _ID = value; } } public string TestMethode() { return(ID); } } class ConsoleApp {
13 Weitergehende Programmierung _______________________________________283
static void Main() { Test test = new Test(); test.ID = "hallo welt"; Type t = test.GetType(); MethodInfo method = t.GetMethod("TestMethode"); object retValue = method.Invoke(test, null); Console.WriteLine("Föhre Methode " + method.Name + " aus..."); Console.WriteLine("Röckgabewert: " + retValue.ToString()); Console.ReadLine(); } }
Ausgabe im Konsolenfenster: Föhre Methode TestMethode aus... Röckgabewert: hallo welt
13.2.3 Eine Methode mit Parametern dynamisch aufrufen Sofern eine Methode Parameter erfordert, lassen sich diese in Form eines objectArrays öbergeben. Das folgende Beispiel verwendet eine Methode Add zum Addieren von zwei öbergebenen int-Werten. Das Ergebnis der Addition wird im Konsolenfenster ausgegeben. Invoke Listing 13.4 reflections3.cs using System; using System.Reflection; class Test { public int Add(int n1, int n2) { return(n1+n2); } } class ConsoleApp {
284 _____________________________________________________ 13.2 Reflections
static void Main() { Test test = new Test(); Type t = test.GetType(); MethodInfo method = t.GetMethod("Add"); object[] parameters = {154, 55}; object retValue = method.Invoke(test, parameters); Console.WriteLine("Föhre Methode " + method.Name + " aus..."); Console.WriteLine("Röckgabewert: " + retValue.ToString()); Console.ReadLine(); } }
Die Ausgabe im Konsolenfenster zeigt, dass die Addition wirklich durchgeföhrt wird: Föhre Methode Add aus... Röckgabewert: 209
13.2.4 Alle Eigenschaften und Methoden auflisten Das vierte Beispiel zum Thema Reflections zeigt die Ausgabe aller Eigenschaften und Methoden einer Klasse. Im Beispiel wird die Klasse string verwendet, es lÙsst sich aber alternativ auch jede andere Klasse verwenden. Neben dem Namen der Eigenschaften wird auch deren Röckgabewert sowie ± wenn mðglich ± der Inhalt ausgegeben. Bei den Methoden werden der Name, der Röckgabewert und ein Sternchen ausgegeben, sofern es sich um eine statische Methode handelt. šber eine if-Abfrage und eine Variable mit dem Namen der letzten Methode wird sichergestellt, dass jeder Methodenname nur einmal ausgegeben wird. Ansonsten kðnnte dieser durch šberladungen auch mehrfach vorkommen. Listing 13.5 reflections4.cs using System; using System.Reflection; class ConsoleApp { static void Main() { string text = "hallo welt :-)";
13 Weitergehende Programmierung _______________________________________285
Type t = text.GetType(); Console.WriteLine("Eigenschaften der Klasse " + t.Name + ""); PropertyInfo[] properties = t.GetProperties(); foreach (PropertyInfo property in properties) { try { Console.Write(property.Name + " (" + property.PropertyType.Name + ")"); Console.Write(" = " + property.GetValue(text, null).ToString()); } catch{} finally { Console.WriteLine(""); } } Console.WriteLine("\nMethoden der Klasse " + t.Name + ""); MethodInfo[] methods = t.GetMethods(); string LastMethod = ""; foreach (MethodInfo method in methods) { if(LastMethod != method.Name) { Console.Write(method.Name + " (" + method.ReturnType.Name + ")"); if(method.IsStatic) Console.Write(" *"); Console.WriteLine(""); LastMethod = method.Name; } } Console.ReadLine(); } }
Die Abbildung zeigt die Ausgabe im Konsolenfenster bei Anwendung des Beispiels auf die Klasse string.
286 _______________________________________ 13.3 Arbeiten mit dem CodeDOM
Abbildung 13.2 Das Beispiel listet alle Eigenschaften und Methoden einer Klasse auf.
13.3 Arbeiten mit dem CodeDOM šber das .NET Framework lassen sich auch dynamisch Programme erzeugen. Hierzu stellt das Framework den Namespace System.CodeDom zur Verfögung. Die dort angebotenen Schnittstellen mössen för die jeweilige Programmiersprache individuell implementiert werden. För C# liegen diese Implementierungen im Namespace Microsoft.CSharp.
13.3.1 Ein Programm dynamisch erstellen Im folgenden Beispiel wird dynamisch öber ein Objektmodell ein C#-Programm mit einer Klasse und einer Methode erzeugt. Es handelt sich um eine KonsolenApplikation, die im Verlaufe des Beispiels kompiliert und auf der Festplatte abgelegt wird. Dort kann das Programm anschlieûend ausgeföhrt werden. Das Beispiel ist mit Kommentaren versehen, sodass Sie die einzelnen Schritte in Verbindung mit dem .NET Framework SDK gut nachvollziehen kðnnen. Listing 13.6 reflections5.cs using using using using using
System; System.CodeDom; System.CodeDom.Compiler; System.IO; Microsoft.CSharp;
13 Weitergehende Programmierung _______________________________________287
class ConsoleApp { static void Main() { // Neue Compile Unit erstellen CodeCompileUnit ccu = new CodeCompileUnit(); // Neuen Namespace erstellen und der Unit hinzufögen CodeNamespace cn = new CodeNamespace("pal.ReflectionTest"); ccu.Namespaces.Add(cn); // Namepace "Systems" importieren cn.Imports.Add(new CodeNamespaceImport("System")); // Klasse erstellen CodeTypeDeclaration ctd = new CodeTypeDeclaration("TestClass1"); // Methode erstellen CodeMemberMethod cmm = new CodeMemberMethod(); cmm.Attributes = MemberAttributes.Public | MemberAttributes.Static; cmm.Name = "Main"; // Variablen för Code deklarieren CodeMethodInvokeExpression cmie; CodeMethodReferenceExpression cmre; // Code hinzufögen cmie = new CodeMethodInvokeExpression(); cmre = cmie.Method; cmre.MethodName = "Console.WriteLine"; CodeSnippetExpression cse = new CodeSnippetExpression("\"Hallo Welt!\""); cmie.Parameters.Add(cse); cmm.Statements.Add(cmie); cmie = new CodeMethodInvokeExpression(); cmre = cmie.Method; cmre.MethodName = "Console.ReadLine"; cmm.Statements.Add(cmie); // Methode hinzufögen ctd.Members.Add(cmm); // Klasse hinzufögen cn.Types.Add(ctd); // C# CodeProvider initialisieren CSharpCodeProvider cp = new CSharpCodeProvider();
288 _______________________________________ 13.3 Arbeiten mit dem CodeDOM
// C# Code Compiler erstellen ICodeCompiler cc = cp.CreateCompiler(); // Verzeichnis ermitteln string CurDir = Directory.GetCurrentDirectory(); string AssemblyName = CurDir + "\\hallowelt.exe"; // Code compilieren CompilerParameters cparam = new CompilerParameters(); cparam.GenerateExecutable = true; cparam.OutputAssembly = AssemblyName; cc.CompileAssemblyFromDom(cparam, ccu); Console.WriteLine("Die Datei hallowelt.exe wurde erstellt."); Console.ReadLine(); } }
Die Abbildung zeigt die Konsolen-Applikation wÙhrend der Ausföhrung auf dem Client.
Abbildung 13.3 Es wurde tatsächlich ein funktionierendes Programm dynamisch erstellt.
Index
@
@ 133
A
Abort 280 Abs 112 abstract 64, 67, 253 Abstrakte Klassen 253 Acos 112 Addition 105 AllocateDataSlot 280 AllocateNamedDataSlot 280 Alternierungszeichen 157 and 116 Anföhrungszeichen 132 Append 150 AppendFormat 150, 151 Arcuscosinus 112 Arcustanges 112 Arithmetische Operatoren 262 Array 93 ArrayList 181, 182 Arrays 171 Dynamisch 182 Eigenschaften und Methoden 170, 177
Initialisierung und Zugriff 171 Mehrdimensionale 174 as 127, 208 Asis 112 Atan 112 Atan2 113 Attribute 74 AufzÙhlungen 192 Ausnahmen 216, 217 auslðsen 225 definieren 230 weiterreichen 216, 228
B
Backslash 132 Bedingungen 79 Bedingungsoperator 86 Betrag 112 BinarySearch 180 Bitweise Operatoren 118 Bool 28 break 83, 88, 94, 99 byte 28 Byte 201
290 _______________________________________________________________ Index
C
C Scharf 13 C Sharp 13 C# 13 C++ 13 Capacity 149 case 83 catch 216, 219 Ceiling 113 ChangeType 210 char 28, 201 Chars 136, 149 class 43 Clear 180 CLR 15 Collections 181 Common Language Runtime 15 Compare 138 CompareTo 139 Compiled 165 CompileToAssembly 163 Component Update 16 Concat 138 ConditionalAttribute 75 Console 33 const 64 continue 94, 99 Convert 209 Copy 138 CopyTo 139 Cos 113 Cosh 113 Cosinus 113 cs 18 csc.exe 18 CurrentThread* 280
D
Datentypen 28 Decimal 28, 201 default 84 delegate 56
Dequeue 185 Destruktor 72 Division 105 do 92 double 28, 201 Dynamische Mitglieder 38, 46
E
Eigenschaften 39 auflisten 284 deklarieren 38, 48 dynamisch abfragen 281 read-only 53 write-only 53 else 80 Empty 136 EndsWith 139, 140 Enqueue 185 EnsureCapacity 150 Enumerator 185 Equals 138, 139, 150 Ereignisse 39 deklarieren 56 Escape 163 Escape-Zeichen 132 event 56, 64 Exception 217, 223 Exp 113 ExplicitCapture 165 Explizite Konvertierung 272 Expliziten Konvertierung 199 extern 64
F
Fehlerbehandlung 217 fifo 185 finally 216, 219 first-in-first-out 185 Flag-AufzÙhlungen 194 float 28, 201 Floor 113 for 89
Index ______________________________________________________________ 291
foreach..in 92 Format 138 Format Methode 134 Formatierungen 134
G
Garbage Collection 71, 73 get 49 GetBaseException 224 GetData 280 GetGroupNames 164 GetGroupNumbers 164 GetLength 181 GetLowerBound 181 GetNamedDataSlot 280 GetType 125 GetUpperBound 181 GetValue 181 goto 84, 100 Groû-Klein-Schreibung 26 GroupNameFromNumber 164 GroupNumberFromName 164 Gruppierungen 157, 166 Göltigkeitsbereich 25
H
Hallo Welt 35 Hashtable 181, 183 HelpLink 224 HResult 224
I
IDictionaryEnumerator 185 if 78, 79 IgnoreCase 165 IgnorePatternWhitespace 165 Implizite Konvertierung 199, 270 Indexer 26, 172 IndexOf 139, 141, 177, 180 IndexOfAny 139 Initialize 181 inline 86
InnerException 224 Insert 139, 142, 150 Installation 12, 16 Int 28, 201 Int16 201 Intermediate Language 16 Intern 138 internal 64 Intrerrupt 280 is 126, 207 IsAlive 280 IsBackground 280 IsFixedSize 180 IsMatch 158, 163 IsReadOnly 180
J
Java 13 JIT 16 Join 139, 280 JScript 14 Just-In-Time-Compiler 16
K
Klassen 38, 40 abstrakte 253 Instanziierung 38, 41 versiegeln 252 Klassenprogrammierung 40 Kommentare 30 Konsolen-Applikationen 33 Konstanten 28 Konstruktor 67 statisch 71 Konstruktoren 236, 248 Konvertierung 29 Explizit 272 Implizit 270 von numerischen Werten 199 von Verweistypen 198, 204 zu string 198, 201
292 _______________________________________________________________ Index
Konvertierungsoperatoren 270 Kurz-Operatoren 108
L
LastIndexOf 139, 181 LastIndexOfAny 139 last-in-first-out 188 Length 137, 150, 180 lifo 188 Linux 16 Log 113 Log10 113 Logarithmus 113 Logische Operatoren 115 Logisches 116 long 28, 201
M
Main 34 Fehlercodes 35 Kommandozeilen-Parametern 34 Maskierung 132 Matches 159, 160, 163 Math 112 Max 113 MaxCapacity 150 Mehrdimensionale Arrays 174 Message 224 Meta-Daten 281 Meta-Zeichen 156 Methoden 39 auflisten 284 deklarieren 38, 53 dynamisch aufrufen 282 Microsoft Intermediate Language 16 Microsoft.CSharp 286 Min 113 Mitglieder 26 öberschreiben 244 verbergen 247 Modifikatoren 63 Modulo 107
Modulus 95 MSIL 16 Multiline 165 Multiplikation 105 Multithreading 277
N
Name 280 Namespaces 30 Negationsoperator 118 new 172 None 165 Notation 25 Notepad 19
O
object 39, 201 Objekt 38, 39 Deklaration 38, 43 Terminierung 38, 42 objektorientierte Programmierung 40 ObsoleteAttribute 75 Operatoren Arithmetische 262 Benutzerdefinierte erstellen 236, 262 BinÙre 118 Konvertierung 270 Kurz-Operatoren 108 logische 115 Negation 118 Typen 123 Vergleich 115, 266 Verknöpfung 116 Zuweisung 110 or 116 override 64, 67
P
PadLeft 139, 142 PadRight 139 Parameter 26 Peek 188
Index ______________________________________________________________ 293
Pi 114 Pop 188 Potenz 113 Pow 113 private 64, 65 protected 64 public 64, 65 Push 188
Q
Quantifizierungszeichen 157 Queue 182, 185
R
Rank 180 ReadLine 33 readonly 64 Referenzen 157 Reflections 75, 123, 281 Regex 157 Optionen 164 Regular expressions 152 RegulÙre Ausdröcke 152 Aufbau 153 šbersicht 155 Remove 140, 151 Replace 140, 143, 151, 161, 163 ResetAbort 280 Restbetrag 107 Resume 280 return 35, 55, 101 Reverse 178, 181 RightToLeft 165 Round 113 Runden 113
S
sbyte 28, 201 Schlösselwort 25 Schnittstellen 256 Deklaration 256 gleichnamige Mitglieder 236, 261
Implementierung 257 sealed 64, 67, 252 Service Releases 16 set 49 SetData 280 SetValue 181 SharpDevelop 21 short 28, 201 Sign 113 Sin 113 Single 201 Singleline 165 Sinh 113 Sinus 113 Sleep 280 Sonderzeichen 132 Sort 181 SortedList 182, 187 Source 224 Split 140, 144, 162, 164 Sqrt 113 Stack 182, 188 StackTrace 224 Standard-Konstruktor 69 Standard-Operatoren 105 Start 280 StartsWiths 140 static 47, 64, 66 Statische Mitglieder 46 string 28, 131, 201 StringBuilder 148 Eigenschaften 149 Methoden 150 Strukturen 189 Substring 140, 145 Subtraktion 105 Suspend 280 switch 78, 82 Zeichenketten 85 System 32 System.CodeDom 32, 286 System.Collection 32, 181
294 _______________________________________________________________ Index
System.ComponentModel 32 System.Configugration 32 System.Convert 209 System.Data 32 System.Diagnostics 32 System.DirectoryServices 32 System.DivideByZeroException 218 System.Drawing 32 System.Globalization 32 System.IO 32 System.Management 32 System.Math 112 System.Messaging 32 System.Net 32 System.Reflection 32, 281 System.Ressources 32 System.Security 32 System.ServiceProcess 32 System.Text 33 System.Text.RegularExpressions 152 System.Threading 33, 277 System.Timers 33 System.Type 123 System.Web 33 System.Windows.Forms 33
T
Tan 113 Tanges 113 Tanh 113 TargetSite 224 this 62, 190 ThreadPriority 280 Threads 277 ThreadState 280 throw 102, 226 Tilde 72 ToBase64String 210 ToBoolean 210, 211 ToByte 210 ToChar 210 ToCharArray 140, 145
ToDateTime 210, 212 ToDecimal 210 ToDouble 210 ToInt16 210 ToInt32 210, 212 ToInt64 210 ToLower 140, 146 ToSByt 210 ToSingle 210 ToString 211, 224 ToString() 29, 191, 201 ToUInt16 211 ToUInt32 211 ToUInt64 211 ToUpper 140, 146 Trim 140, 147 TrimEnd 140, 147 TrimStart 140, 147 try 216, 219 Typeninformationen 123 Typenoperatoren 123, 207 typeof 123
š
šberladungen 237 šberschreiben 236, 244
U
uint 28 UInt16 201 ulong 28 Unescape 164 unsafe 65 ushort 28
V
Variablen 27 Verbergen 236, 247 Vererbung 241 Konstruktoren 248 Vergleichsoperatoren 115, 266 Verknöpfungsoperatoren 116
Index ______________________________________________________________ 295
versiegeln 252 virtual 65, 67 Visual Basic.NET 14 Visual C++ 14 Visual FoxPro.NET 16 Visual J++ 15 Visual Sourcesafe 6.0c 16 Visual Studio.NET 14, 20 Vorzeichen 113
W
WÙhrung 134 WebServiceAttribute 75 Wenn..Dann 79 while 91 Windows 2000 14 Windows CE 16
Windows Component Update 16 Windows XP 14 Write 33 WriteLine 33 Wurzel 113
X
xor 120
Z
Zeichengruppen 156 Zeichenketten Eigenschaften 136 Methoden 138 Operationen 138 Zuweisung 131 Zuweisungsoperatoren 110