Uwe Klappert
Coding For Fun mit C#
Liebe Leserin, lieber Leser, ich freue mich, dass Sie sich für dieses Coding for Fun-Buch entschieden haben. Sie sind Programmierer aus Berufung und Ihnen macht es Spaß, Aufgaben programmierend zu lösen? Dann sind Sie hier genau richtig! Dieses Buch bietet Ihnen sechs ungewöhnliche Programmierprojekte an, die Sie (Programmiererfahrung vorausgesetzt) ganz einfach nachvollziehen können. Die Projekte haben nur das eine Ziel: Sie zu unterhalten und zum Knobeln und Ausprobieren anzuregen. Wir versprechen Ihnen keine schlanken Lösungen, keine Snippets, die sie beruflich verwenden könnten, sondern reinen Programmierspaß. Dabei sind die Projekte so unterschiedlich und abwechslungsreich wie Aprilwetter: Eine Poker-Runde gefällig? Abendstimmung mit Sternenhimmel oder lieber Wecker-Tool? Oder wollten Sie schon immer den DAX manipulieren? Legen Sie einfach los. Die aktuelle und für die Beispiele benötigte Visual Studio Express Edition 2010 und der Beispielcode liegen auf der Buch-DVD für Sie bereit. Noch eine Anmerkung in eigener Sache: Dieses Buch wurde mit großer Sorgfalt geschrieben, begutachtet, lektoriert und produziert. Doch kein Buch ist perfekt. Sollte also etwas nicht so funktionieren, wie Sie es erwarten, dann scheuen Sie sich nicht, sich mit mir in Kontakt zu setzen. Ihre freundlichen Fragen und Anmerkungen sind jederzeit willkommen. Viel Freude beim Lesen und Programmieren wünscht
Judith Stevens-Lemoine Lektorat Galileo Computing
[email protected] www.galileocomputing.de Galileo Press · Rheinwerkallee 4 · 53227 Bonn
Auf einen Blick 1
Einleitung ............................................................................
9
2
Die Sache mit .NET .............................................................
13
3
Ausgeschlafen – das Wecker-Tool ......................................
41
4
Alles Täuschung oder was? Herr Hermann und sein Gitter ............................................
69
5
Mit Argusaugen – der nächtliche Sternenhimmel ..............
101
6
Garantiert ungefährlich – Manipulationen am DAX ...........
141
7
Im Labyrinth des Minotaurus .............................................
187
8
Pokern .................................................................................
235
A
Visual C# 2010 Express .......................................................
327
Der Name Galileo Press geht auf den italienischen Mathematiker und Philosophen Galileo Galilei (1564–1642) zurück. Er gilt als Gründungsfigur der neuzeitlichen Wissenschaft und wurde berühmt als Verfechter des modernen, heliozentrischen Weltbilds. Legendär ist sein Ausspruch Eppur se muove (Und sie bewegt sich doch). Das Emblem von Galileo Press ist der Jupiter, umkreist von den vier Galileischen Monden. Galilei entdeckte die nach ihm benannten Monde 1610. Gerne stehen wir Ihnen mit Rat und Tat zur Seite:
[email protected] bei Fragen und Anmerkungen zum Inhalt des Buches
[email protected] für versandkostenfreie Bestellungen und Reklamationen
[email protected] für Rezensions- und Schulungsexemplare Lektorat Judith Stevens-Lemoine Fachgutachten Thomas Theis, Monschau Korrektorat Friederike Daenecke, Zülpich Cover Barbara Thoben, Köln Coverillustration Graham Geary, Boston Typografie und Layout Vera Brauner Herstellung Frauke Kaiser Satz Typographie & Computer, Krefeld Druck und Bindung Bercker Graphischer Betrieb, Kevelaer Dieses Buch wurde gesetzt aus der Linotype Syntax Serif (9,25/13,25 pt) in FrameMaker. Gedruckt wurde es auf chlorfrei gebleichtem Offsetpapier.
Bibliografische Information der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar. ISBN
978-3-8362-1484-1
© Galileo Press, Bonn 2010 1. Auflage 2010 Das vorliegende Werk ist in all seinen Teilen urheberrechtlich geschützt. Alle Rechte vorbehalten, insbesondere das Recht der Übersetzung, des Vortrags, der Reproduktion, der Vervielfältigung auf fotomechanischem oder anderen Wegen und der Speicherung in elektronischen Medien. Ungeachtet der Sorgfalt, die auf die Erstellung von Text, Abbildungen und Programmen verwendet wurde, können weder Verlag noch Autor, Herausgeber oder Übersetzer für mögliche Fehler und deren Folgen eine juristische Verantwortung oder irgendeine Haftung übernehmen. Die in diesem Werk wiedergegebenen Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. können auch ohne besondere Kennzeichnung Marken sein und als solche den gesetzlichen Bestimmungen unterliegen.
Inhalt 1
Einleitung .......................................................................................
9
1.1 1.2 1.3
Grundlegendes zu diesem Buch .......................................................... Abseits von Klassen und Methoden ................................................... Eine notwendige Frage: Die Zielgruppe? ............................................
10 10 11
2
Die Sache mit .NET .......................................................................
13
2.1
2.9
Intelligent, aber nicht unergründlich – das .NET-Framework .............. 2.1.1 Sprachgewaltig: die Laufzeitumgebung .................................. 2.1.2 Ein Meer von Klassen – die Base Class Library (BCL) .............. Zwischengeschoben – der IL-Code ..................................................... 2.2.1 Kein Fabelwesen – der Jitter .................................................. Ein Raum ohne Tür – der Namensraum .............................................. Ein Wort wie ein Kosename – Assembly ............................................. Die »digitale Müllabfuhr« – der Garbage Collector .............................. Das Salz in der Suppe – Steuerelemente ............................................. .NET und kein Ende? .......................................................................... Aus der Art geschlagen – die Sprache C# ............................................ 2.8.1 Musik im Namen ................................................................... 2.8.2 Ein Kessel Buntes – die Ursprünge der .NET-Sprache C# ........ 2.8.3 Ein heikler Punkt – Pointer .................................................... 2.8.4 Ein geheimnisvoller Verein – Delegates .................................. Ereignisbehandlung ............................................................................
14 14 15 17 18 20 21 24 27 29 29 30 31 31 35 39
3
Ausgeschlafen – das Wecker-Tool ............................................
41
3.1 3.2
Als der Computer die Zeit entdeckte .................................................. Die Entwicklung der Bedienoberfläche ............................................... 3.2.1 Anlegen eines neuen Projekts ................................................ 3.2.2 Die benötigten Steuerelemente ............................................. 3.2.3 Ein EventHandler für das Beenden der Anwendung ... ........... 3.2.4 Implementierung und Test der Zeitanzeige ............................ 3.2.5 Von der Zeitanzeige zur Implementierung einer Uhr .............. 3.2.6 Zahlen für das »NumericUpDown«-Control ............................ 3.2.7 Mehr Lärm als Melodie – »tada« und »ir« ............................... 3.2.8 In einem Aufwasch – Einstellen der Weckzeit und Auslösen des Wecktons ......................................................... 3.2.9 Stop and Go – das Beenden des Wecktons ............................ Hätten Sie’s gewusst? .........................................................................
42 45 47 50 52 53 55 57 58
2.2 2.3 2.4 2.5 2.6 2.7 2.8
3.3
60 64 68 5
Inhalt
4 4.1
Alles Täuschung oder was? Herr Hermann und sein Gitter ...................................................
69
4.4
Die Entdeckung der geheimnisvollen Punkte ...................................... 4.1.1 Kaffeestunde beim Optiker – woher die Punkte kommen ...... Entwicklung der Benutzeroberfläche .................................................. 4.2.1 Was beabsichtigt ist .............................................................. 4.2.2 Die beteiligten Controls ......................................................... Entwicklung der Programmierlogik .................................................... 4.3.1 Viel Aufwand für den Hintergrund ......................................... 4.3.2 Zeichnung und Positionierung der Gitterquadrate .................. 4.3.3 Der Schalter für das Hermann-Gitter ...................................... 4.3.4 Ganz schön blass geworden – die Regelung des Alpha-Werts .......................................................................... 4.3.5 Aus groß mach klein – Skalierung der Quadrate durch ein TrackBar-Steuerelement ........................................................ 4.3.6 Die Verhältnisse auf den Kopf gestellt – Umkehrung der Farben ................................................................................... Hätten Sie’s gewusst? .........................................................................
5
Mit Argusaugen – der nächtliche Sternenhimmel ................. 101
5.1 5.2
5.5
Wie alles begann – Stippvisite in Padua .............................................. Subtil und verspielt – wo wir hin wollen ............................................. 5.2.1 Weitere Anforderungen an die Anwendung ........................... Entwicklung der Benutzeroberfläche .................................................. Entwicklung der Programmierlogik ..................................................... 5.4.1 Ein Klasse mit Ambitionen – Creating .................................... 5.4.2 Zurück zur Klasse »Form1« .................................................... 5.4.3 Sterne der 1. Kategorie .......................................................... 5.4.4 Sterne der 2. Kategorie .......................................................... 5.4.5 Zu guter Letzt – ein Stern der 3. Kategorie ............................. 5.4.6 Verzögertes Schließen der Anwendung .................................. 5.4.7 Aufgehoben ist nicht aufgeschoben – die Klasse »DelayTime« .......................................................................... Hätten Sie’s gewusst? .........................................................................
6
Garantiert ungefährlich – Manipulationen am DAX .............. 141
6.1
Im Schmelztiegel des großen Geldes .................................................. 141 6.1.1 Xetra – das elektronische Hirn der Börse ................................ 143 6.1.2 Am Puls der Wirtschaft – der DAX ......................................... 143
4.2
4.3
5.3 5.4
6
69 71 72 72 73 76 77 79 82 83 86 94 99
101 103 105 105 108 111 118 119 122 129 134 134 140
Inhalt
6.2 6.3
6.5
Was angedacht ist .............................................................................. Entwicklung der Benutzeroberflächen ................................................ 6.3.1 Das Fenster »Data« ................................................................ 6.3.2 Das Fenster »DAX« ................................................................ Entwicklung der Programmierlogik ..................................................... 6.4.1 Das große Zeichnen – die Klasse »Chart« ............................... Hätten Sie’s gewusst? .........................................................................
7
Im Labyrinth des Minotaurus ..................................................... 187
7.1
7.5
Und dann kam Dijkstra ... .................................................................. 7.1.1 Ein Quäntchen Graphentheorie ............................................. 7.1.2 Dijkstra in Worten ................................................................. 7.1.3 Listenplätze ........................................................................... »Verworrene« Absichten .................................................................... 7.2.1 »Schwaches Knotenkriterium« ............................................... 7.2.2 Nullsummenspiel ................................................................... 7.2.3 Wie es weiter geht ................................................................ Entwicklung der Benutzeroberfläche .................................................. 7.3.1 Ein Fall für sich – das »TableLayoutPanel« .............................. 7.3.2 Labels am laufenden Band ..................................................... 7.3.3 Drei Buttons und ein Textfeld ................................................ Entwicklung der Programmierlogik ..................................................... 7.4.1 Gute Eigenschaften ................................................................ 7.4.2 Definition der Knoten im EventHandler »button1_Click()« .................................................................. 7.4.3 Ausblenden der Knoten im EventHandler »button2_Click()« .................................................................. 7.4.4 Die Schaltfläche zum kürzesten Weg ..................................... Hätten Sie’s gewusst? .........................................................................
8
Pokern ............................................................................................. 235
8.1
Die Hand am Colt – Five Card Draw ................................................... 8.1.1 Die Regeln beim Five Card Draw ........................................... 8.1.2 Gewichtete Hände ................................................................. Draw Poker Light – unser Spiel ........................................................... 8.2.1 Die Wahl des Gegners ........................................................... 8.2.2 Erzwungener Tausch .............................................................. 8.2.3 Die Frage des Geldes ............................................................. Entwicklung der Benutzeroberfläche .................................................. 8.3.1 Vom Ordner zur Bedienoberfläche .........................................
6.4
7.2
7.3
7.4
8.2
8.3
144 146 146 150 151 159 184
188 189 191 192 192 194 195 195 196 196 198 200 201 202 206 209 210 234
236 237 238 240 240 241 241 241 242
7
Inhalt
8.4
8.5 8.6
Entwicklung der Programmierlogik ..................................................... 8.4.1 Addition und Subtraktion – der Ereignisbehandler »numericUpDown1_ValueChanged()« .................................... 8.4.2 PictureBoxen aufgelistet – die Methode »pictureboxList()« .... 8.4.3 Einsatz des Kartengebers ....................................................... 8.4.4 Basisarbeit – die Klasse »kartenListe« ..................................... 8.4.5 Zurück in der Klassendatei »Poker.js« .................................... 8.4.6 Tauschgeschäfte 1 – EventHandling für fünf PictureBoxen ...... 8.4.7 Tauschgeschäfte 2 – die Klasse »tauscheKarten« .................... 8.4.8 Tauschgeschäfte 3 – das finale Ereignis .................................. 8.4.9 Reduzierte Menge – die Klasse »kartenKombinationen« ........ 8.4.10 Wie viel auf dem Spiel steht – die Struktur »wertigkeitHand« .................................................................. 8.4.11 Schnelles Plätzetauschen – die Klasse »permutKarten« ........... 8.4.12 Gewonnen oder verloren – die Klasse »evaluiereHand« .......... 8.4.13 Letzte Schritte im EventHandler »tauschen_Click()« ............... 8.4.14 Die »Konditionierung« des Spiels ........................................... 8.4.15 Mit »this« und »Close()« zum geschlossenen Fenster .............. Hätten Sie‘s gewusst? ......................................................................... Zum guten Schluss .............................................................................
A
Visual C# 2010 Express ............................................................... 327
A.1
Anwendungen, die mit Visual C# 2010 Express erstellt werden können .................................................................................. Reduzierter Funktionsumfang ............................................................. Neues bei Visual C# 2010 Express ...................................................... Der Weg zu Visual C# 2010 Express ................................................... A.4.1 Was ist ein ISO-Image? .......................................................... A.4.2 Brennen einer Visual C# 2010 Express-CD ............................. A.4.3 Die Alternative – virtuelle Festplatte ...................................... Installation von Visual C# 2010 Express ............................................. Das Prinzip der integrierten Entwicklungsumgebung .......................... A.6.1 Projekte mit System ............................................................... A.6.2 Quell- versus Entwurfsmodus ................................................ A.6.3 Unverzichtbar – das »Eigenschaften«-Fenster ......................... Veröffentlichung einer Anwendung ....................................................
A.2 A.3 A.4
A.5 A.6
A.7
253 254 256 256 259 270 274 276 283 286 289 294 307 315 322 324 324 325
327 328 329 330 331 331 334 335 336 337 338 338 339
Index ............................................................................................................ 343
8
Wenn Baumeister Gebäude bauten, so wie Programmierer Programme schreiben, dann würde der erste Specht, der vorbeikommt, die Zivilisation zerstören. (Verfasser unbekannt)
1
Einleitung
»Coding for Fun mit C#« kann Sie, liebe Leserinnen und Leser, leider nicht in die Situation versetzen, ein Programm zu entwickeln, das New Yorks riesige Ampelschar koordiniert. Auch werden Sie am Ende der Lektüre nicht befähigt sein, Klassen, Methoden und Eigenschaften zu entwickeln, die im sinnvollen Zusammenspiel das erste Vorhandensein Ihres Geburtsjahrs im Nachkommabereich der unendlichen, weil irrationalen Zahl Pi ermitteln. Und auch die immerhin 500 000 Zeilen lange, jedoch keineswegs fehlerfreie Flugsoftware des betagten Space Shuttle (die in HAL, einer FORTRAN-ähnlichen Sprache implementiert ist) werden Sie nach dem Durcharbeiten der Lektüre nicht so ohne Weiteres auf C# portieren können. Das alles ist auch nicht unbedingt Ihre Erwartung? Gut so, denn das angenehm Knifflige eines programmiertechnischen Problems wächst nicht zwangsläufig mit der Dimension der definierten Aufgabe. Natürlich verfällt dieses Buch auch nicht ins Gegenteil. Aus dem zerstörerischen Specht wird nicht klammheimlich eine Biene werden. Die Mauern der Zivilisation stürzen nicht ein, weil Sie womöglich genötigt werden, wild darauflos zu programmieren. Ihr Wohlergehen ist demnach nicht gefährdet. Und vielleicht, ja vielleicht empfinden Sie nach sorgfältigem Durcharbeiten eines, mehrerer oder gleich aller Beispiele erst recht ein gewisses Unverständnis gegenüber dem sicherlich übertriebenen Zitat zu Beginn dieses Vorworts. Der kleine Anleser dürfte Sie darüber hinaus eher an die Zeiten bester »Spaghetti-Codierung« erinnern, als das Objekt der gesamte Quellcode und alles irgendwie eine – zumindest aus heutiger Sicht – schmunzelwürdige Klasse für sich war. Bei uns wird es beträchtlich strukturierter zugehen, was sich allein schon aus der Sprache ergibt, die zum Einsatz kommt: C#.
9
1
Einleitung
1.1
Grundlegendes zu diesem Buch
Praxisorientierte IT-Literatur folgt im Wesentlichen zwei Richtungen. Die eine mündet im Entwicklungsprozess eines ausführlichen Praxisbeispiels, die andere in mehreren, thematisch voneinander abgegrenzten Programmen. Beide Ansätze haben sowohl ihre didaktische Berechtigung als auch nicht zu unterschätzende Vorteile. »Coding for Fun mit C#« verwirklicht das zweite Konzept. Inhaltlich wohlunterscheidbare, vollkommen unabhängige Praxisbeispiele geben Ihnen die Möglichkeit, sich entweder erfolgreich auf gleich mehreren oder auf ausgesuchten Feldern zu versuchen. Terra incognita bleibt da nichts, wenn Sie das nicht wollen. Ganz gleich, ob Sie gern ein Wecker-Tool entwickeln, das angenäherte Abbild des abendlichen Sternenhimmels implementieren oder sich auf dem Bildschirm Punkte ansehen möchten, die Ihnen das Auge (selbst das fehlerfreie) vorgaukelt; diesbezüglich und darüber hinaus bleibt Ihre Suche nach Interessantem nicht erfolglos. Und schließlich: Ein wenig Fantasie, gepaart mit der Fähigkeit zur Extrapolation, vorausgesetzt, kann jedes der Praxisbeispiele auf eigene Szenarien übertragen werden. Sie müssen keine DAX-Kurve mögen. Irgendetwas wird sich aber sicher finden, das Sie gerne als Chart dargestellt sähen. Vielleicht können Sie sogar auf diese Weise darstellen, wie oft Sie bei jedem Kapitel gedacht haben: »Das hätte ich aber anders gemacht.« Es stimmt ja auch! Doch bitte: Viele Wege führen nach Rom – auch Programmierwege. Ferner wurden die Beispiele mit dem Anspruch gewählt, dass die Codierung möglichst einfach nachvollziehbar sein soll. Dass dieser Anspruch zuweilen auf Kosten dessen geht, was man als Programmierästhetik bezeichnen könnte, räume ich an dieser Stelle (mit der Bitte um Nachsicht) ein. In dem Zusammenhang erinnere ich mich jedoch gerne an die Bemerkung eines Kommilitonen, nach der nicht für jede Addition eine eigene Klasse entwickelt werden muss. Er hatte recht, obgleich ich ihm damals nicht so ohne Weiteres beipflichten mochte.
1.2
Abseits von Klassen und Methoden
Ihr literarischer Neuerwerb ist kein Fachbuch. Wenigstens keines im klassischen Sinne. Alles Wissensnotwendige erfahren Sie natürlich dennoch, eingeleitet durch kurzweilig gehaltene Informationen, die das jeweilige Programmiervorhaben einleiten. So schadet es nicht, wenn Sie beispielsweise etwas von der lange zurückliegenden Existenz eines computerähnlichen Geräts erfahren, das in einer ganz eigenen Beziehung zur Zeit stand. Auch wenn technische Welten Ihren und meinen Computer von jenem Exponat der Frühzeit trennen, so ist der Abstand
10
Eine notwendige Frage: Die Zielgruppe?
zwischen den am Abendhimmel sichtbaren Fixsternen und Ihren Augen noch viel größer. Wie sich Galileo den vermeintlich unendlichen Weiten näherte – selbst darüber erfahren Sie mehr.
1.3
Eine notwendige Frage: Die Zielgruppe?
Die Zielgruppe des Buches sind … vor allem Sie. Warum? Weil Sie ein grundlegendes Interesse an den sogenannten .NET-Sprachen (über den Begriff wird noch zu schreiben sein) im Allgemeinen und der .NET-Sprache C# im Speziellen haben. Weil Sie bereits mit dem Besuch einer einführenden Vorlesung, eines Seminars oder/und durch literaturgestütztes Selbststudium einen Schritt weitergegangen sind. Gefolgt von einem weiteren Schritt, indem Sie einfach angefangen haben zu programmieren. On the fly! Weil Ihnen das Prinzip der objektorientierten Programmierung im Zusammenhang mit C# aus unerfindlichen Gründen reizvoll erscheint. Durch Letzteres wird Ihnen natürlich (und sicher zu Recht) unterschwellig unterstellt, mehr über objektorientiertes Programmieren zu wissen, als einführende Beispiele zur Beschreibung einer Klasse in der Regel vermitteln. Die zum Objekt verkommene Katze werden Sie demnach in diesem Buch ebenso vergebens suchen wie die in private oder öffentliche Methoden gebannten Gewohnheiten des schnurrenden Vierbeiners. Über Derartiges sind Sie zwar nicht erhaben, gleichwohl fachlich weit hinaus. Es ist also an der Zeit, die Katze aus dem Sack zu lassen.
11
1.3
Künstliche Intelligenz ist gar nichts – verglichen mit natürlicher Dummheit. (Verfasser unbekannt)
2
Die Sache mit .NET
Auch nach über sieben Jahren ist .NET (zum Zeitpunkt der Niederschrift dieses Textes war Version 4.0 aktuell) oder, um es korrekt zu formulieren, die .NETTechnologie, ein kleines Mysterium geblieben. Doch leider ist ohne .NET nicht gut mit C-Sharp (C#) programmieren, und ohne einen Fundus an Grundwissen braust Ihr sicher vorhandener Elan an den Möglichkeiten des mächtigen Frameworks vorbei. Framework? Damit ist natürlich das .NET-Framework gemeint, und was es damit auf sich hat, darum werden wir uns im Folgenden kümmern. Stiefmütterlich behandelt werden dagegen Webservices sowie der .NET Enterprise Server. Beide spielen für die Entwicklung der Sie erwartenden Praxisbeispiele nämlich keine Rolle und somit auch nicht für Ihren literarischen Neuerwerb. Abgesehen davon ist das .NET-Framework primärer Bestandteil von .NET, und so betrachtet, kann es nicht falsch sein, mit .NET zunächst das .NET-Framework zu verbinden oder die beiden Begriffe sogar synonym zu verwenden. In der Zusammenfassung noch einmal: Bestandteile der viel gepriesenen .NETTechnologie sind: 왘
.NET-Framework
왘
Webservices
왘
.NET Enterprise Server
Für uns ist, wie ich bereits erwähnt habe, lediglich das .NET-Framework von Belang. Würden wir Webservices und .NET Enterprise Server mit ins Boot nehmen, hielten Sie ein praxisorientiertes Buch über die .NET-Technologie in den Händen. Trösten Sie sich: Das .NET-Framework allein füllt mühelos die Seiten kiloschwerer, mitunter schwer verdaulicher Fachbücher. Dasselbe komplexe Thema betreffend, begnügen wir uns mit vergleichweise wenigen Seiten, denn weniger ist manchmal (allerdings nicht immer) mehr. Vor allem dann, wenn die Intention des Buches eine etwas andere ist.
13
2
Die Sache mit .NET
Trivial wird es auf den folgenden Seiten gleichwohl nicht. Es geht nicht anders! Denn Sie sollten zumindest im Groben wissen, auf welcher softwaretechnischen Grundlage die Praxisbeispiele aufgesetzt sind. Schließlich unternehmen Sie mit Ihrem hypothetischen Wunschauto auch keine Probefahrt, ohne sich für die Raffinessen des vierrädrigen Objekts zu interessieren (und gegebenenfalls zu begeistern). Alles werden wir gleichwohl nicht besprechen können. Einige der wichtigsten Konzepte dagegen schon.
2.1
Intelligent, aber nicht unergründlich – das .NET-Framework
Die Frage, was unter dem .NET-Framework primär zu verstehen ist, führt beinahe zwangsläufig zu anderen, nicht immer tauglichen Begriffen: 왘
konzeptionelle Plattform
왘
Softwareplattform
왘
Klassenbibliothek
왘
Programmiermodell
All das haben Sie mit Sicherheit schon irgendwo gelesen. Und nichts von dem ist selbstredend falsch (schlechtestenfalls ist es unvollständig). Fragen wir deshalb anders: Was ist der springende Punkt beim .NET-Framework? Oder gibt es sogar mehrere?
2.1.1
Sprachgewaltig: die Laufzeitumgebung
Das .NET-Framework ist bedeutend mehr als eine Klassenbibliothek (und sei diese auch noch so groß). Im Wort Framework steckt schließlich neben Frame auch das Wort Work. Hier arbeitet notwendigerweise etwas, nämlich die Laufzeitumgebung (englisch Runtime Environment), genauer gesagt eine programmiersprachenunabhängige Laufzeitumgebung, die als Common Language Runtime oder kurz CLR bezeichnet wird. Die können Sie sich getrost als Computer im Computer (eine Art virtuelle Maschine also) vorstellen. Abbildung 2.1 zeigt schematisch den Aufbau der CLR.
14
Intelligent, aber nicht unergründlich – das .NET-Framework
Base Class Library (BCL-Support)
COM Marshaler
Thread Support
Type Checker
Exeption Manager
Debug Engine
Security Engine
IL to Native Computer
Code Manager
Garbage Collector
Class Loader
Abbildung 2.1
2.1.2
Vereinfachter schematischer Aufbau der Common Language Runtime (CLR)
Ein Meer von Klassen – die Base Class Library (BCL)
Ganz oben in Abbildung 2.1entdecken Sie die Base Class Library (BCL). So oft der Name auch in allen möglichen und schematischen Darstellungen zu finden ist, so wenig aktuell ist er in Wahrheit. Microsoft selbst spricht lieber von der .NET Framework Class Library (FCL), was sogleich die Frage aufwirft, wo in der Abkürzung das .NET abgeblieben ist. NFCL wäre genauer, allein schon deshalb, weil es neben dem .NET-Framework auch andere Frameworks gibt, nicht zuletzt den ewigen Konkurrenten Java mit seinem etablierten Runtime Environment (JRE). Anscheinend steckt jedoch mehr hinter der namentlichen Verwirrung, gibt es doch im Hause Microsoft an der Entwicklung des .NET-Frameworks beteiligte Programmierer (auf einen werden wir noch zurückkommen), die die BCL als Teil der FCL betrachten. Demnach wird also zwischen BCL und FCL unterschieden. Und wenn wir schon mal dabei sind, Konfusion zu erzeugen: Bei Ihrer Beschäftigung mit .NET wird Ihnen auch die Abkürzung CL begegnen, womit nichts anderes als die BCL gemeint ist. Schließen wir Frieden mit dem Namen- und Abkürzungswirrwarr, denn ändern lässt es sich gerade in einer wissenschaftlich-technischen Umgebung nicht, die von Anglizismen und Abkürzungen nur so wimmelt.
15
2.1
2
Die Sache mit .NET
Ganz gleich, ob Sie Windows-, Webanwendungen oder Webservices entwickeln möchten, an der Klassenbibliothek des .NET-Framework ist nur schwer vorbeizukommen. Nach was, glauben Sie, werden Sie im Installationsverzeichnis des .NET-Frameworks (in der Regel C:\WINDOWS\Microsoft.NET\Framework\) im Falle der, sagen wir, BCL suchen müssen? Nach einem oder gar mehreren Ordnern, gefüllt mit zahlreichen, unzusammenhängenden Klassendateien, in womöglich noch alphabetischer Reihenfolge? So wird es – vielleicht – irgendwann angefangen haben, als sich zur Laufzeit eines Programms im Wesentlichen nichts anderes vollzog als der Lauf des Programms. Klassenbibliotheken können (und das ist in der Regel der Fall) auch zur Laufzeit eingebunden werden (vereinzelt spricht man auch, und nicht frei von unfriedlichen Assoziationen, vom Nachladen der Bibliothek), womit wir es dann mit einer Dynamic Link Library (DLL) zu tun hätten, die Sie an der gleichlautenden, wenngleich zumeist kleingeschriebenen Dateiendung (.dll) erkennen. Statisch oder dynamisch? Faustischen Ursprungs ist die Frage zwar nicht, allerdings auch nicht unbedeutend. Programme, die auf DLLs zurückgreifen, sind zumeist kleiner als jene, die sich mit einer statischen Bibliothek begnügen (solche Programme werden tatsächlich noch geschrieben) – einfach weil der Code auf mehrere Dateien verteilt werden kann. Dagegen erfolgt das Hinzubinden einer statischen Bibliothek zum Programm zur Übersetzungszeit, mit der leicht zu erahnenden Konsequenz, dass die ausführbare Datei (.exe) vergrößert wird. Dafür wird nichts nachgeladen.
Im Kontext der BCL sind zwei Dateien (und aus mehr Files besteht die Base Class Library erstaunlicherweise nicht) mit der Endung .dll von einiger Bedeutung: 왘
system.dll
왘
mscorlib.dll
Beide Dateien sind mit im Mittel 3,5 MB wahrlich keine Leichtgewichte, was bei der Anzahl der in ihnen enthaltenen Klassen auch nicht verwundert. Gezählt habe ich sie nicht. Es werden einige Tausend sein, deren Aufgaben sich gleichwohl gut gruppieren lassen. Die beiden Dateien system.dll und mscorlib.dll enthalten: 왘
Basisdatentypen
왘
IO-Funktionen (Input/Output)
왘
Netzwerkfunktionen
왘
Reflection-API
16
Zwischengeschoben – der IL-Code
왘
COM-Interoperabilität
왘
Remoting
왘
Sicherheitskonfiguration
왘
Threading
왘
Zugriff auf Teile des Windows-Systems
2.2
Zwischengeschoben – der IL-Code
Die Unterstützung der Base Class Library (siehe Abbildung 2.1) ist Teil der Laufzeitumgebung (CLR), ohne die es schwierig wird, den in einer beliebigen Programmiersprache entwickelten Code auszuführen. Früher war mit dem Wechsel der Programmiersprache zwangsläufig ein Wechsel der Laufzeitumgebung verbunden, woran sich im Grunde nichts geändert hat – bis auf die erfolgreiche Sache mit .NET, wo eine Laufzeitumgebung gleich mehrere Sprachen bedient (nämlich die sogenannten .NET-Sprachen, u. a. auch das von uns gewählte C#). Ein intelligentes Konzept, fürwahr, nur: Warum funktioniert es so gut? Es hilft nichts, wir müssen noch weiter ins Kellergewölbe des .NET-Frameworks vordringen. Verstaubtes erwartet Sie dort nicht, wohl aber eine weitere, softwaretechnische Raffinesse. Eine stattliche Hürde Das Problem ist schlicht, einen in C# entwickelten Programmcode gleichermaßen für die Laufzeitumgebung des .NET-Frameworks verständlich zu machen, wie die potenziell in Visual Basic.NET oder einer anderen .NET-Sprache (z. B. C++.NET) entwickelten Sourcen. Die CLR spricht nur eine Sprache, .NET-Sprachen dagegen gibt es gleich mehrere. Zum Glück! Was es leider auch gibt, ist ein Problem.
Um zu verhindern, dass sich die Laufzeitumgebung an der Mehrsprachigkeit des .NET-Frameworks nachhaltig »verschluckt«, wird der Runtime Environment ein Zwischencode serviert, der IL- oder MSIL-Code genannt wird. (IL steht für Intermediate Language, MSIL für Microsoft Intermediate Language.) Anders formuliert: Die CLR weiß nichts vom relativen Sprachreichtum der Codierung, die ausgeführt werden soll. Die Laufzeitumgebung befasst sich lediglich mit dem ILCode. Bleibt die Frage zu klären, was der ominöse IL-Code eigentlich ist. Kurze Antwort: Es ist Bytecode. Und der besteht aus nichts anderem als aus Befehlen für eine virtuelle Maschine. Das Schöne am Bytecode (der auch P-Code genannt wird) ist die Unabhängigkeit vom Prozessor, was so formuliert ein wenig geschönt ist, verhält es sich doch eher so, dass der Bytecode vom physischen Prozessor nicht verstanden werden kann. Prozessorunabhängigkeit? So kann man es
17
2.2
2
Die Sache mit .NET
also auch nennen. Was die CPU Ihres und meines Computers versteht, sind Maschinenbefehle (man spricht auch von Native Code), die wiederum vom Typ des Prozessors abhängig sind. Dennoch ist das Thema Zwischencode damit nicht abgehandelt, denn nur wenig ist so einfach, wie es – dem Titel dieses Buches geschuldet – beschrieben werden sollte. Kurz mal verschnaufen, und dann folgt ein vermeintlich überflüssiger Satz: Die Übersetzung einer C#-Anweisung in den IL-Code (Zwischencode) übernimmt der Compiler. Im Falle der Sprache C# verbirgt sich der Compiler in der Datei csc.exe, die Sie, abhängig vom Ort der Installation, irgendwo in den unergründlichen Tiefen des .NET-Frameworks finden. Wenn es aber mehrere .NET-Sprachen, einen Zwischencode und eine Laufzeitumgebung gibt, muss es notwendigerweise auch mehrere Compiler geben, genau für jede der .NET-Sprachen einen. Dem ist auch so. Metadaten Neben dem IL-Code generieren die Compiler spezielle Informationsdaten. Diese sogenannten Metadaten beinhalten Informationen über Typen, Elemente oder Verweise, die für das C#-Programm auf die ein oder andere Art wichtig sind. Schließlich muss die Laufzeitumgebung beispielsweise wissen, wann eine Klasse oder ein anderes Programmelement geladen werden soll. Metadaten werden zur Laufzeit des Programms ausgelesen. Dazu bedarf es spezieller Klassen, die unter dem Namen Reflection API bekannt sind.
Selbst wenn es keine Metadaten gäbe: Fühlen Sie sich nicht zu sehr an Ihre Beschäftigung mit dem klassischen, in Native Code übersetzenden Compiler erinnert! Denn die hier angesprochenen »Compiler« kompilieren nicht in den Maschinencode, sondern in den IL-Code, P-Code, Bytecode oder welchen Namen Sie auch immer dem begabten Kind geben mögen. Auf alle Fälle ist der Zwischencode eben nicht für einen physikalischen Prozessor bestimmt.
2.2.1
Kein Fabelwesen – der Jitter
Die Weiterverarbeitung des IL-Codes könnte durchaus eine Art Interpreter erledigen, der den Code einliest, analysiert und schlussendlich ausführt. Der Nachteil eines Interpreters? Die Geschwindigkeit. Der Vorteil des interpretierten Codes: die weitgehende Unabhängigkeit von der Architektur des Rechners (zumindest dann, wenn der Interpreter selbst in C oder einem C-Dialekt geschrieben ist). Als Alternative käme der Einsatz eines klassischen Compilers in Betracht. Der Vorteil der Kompilierung: eine höhere Geschwindigkeit, denn Native Code ist nicht langsam. Der Nachteil? Tja, überlegen Sie! Maschinencode ist prozessorabhängig, somit erführe das .NET-Framework einen merklichen Ruck in Richtung
18
Zwischengeschoben – der IL-Code
Plattformabhängigkeit. Genau die gilt es zu vermeiden, mehr noch als eine langsame Ausführungsgeschwindigkeit. Eine Software, für die erst das Motherboard und/oder der Prozessor ausgetauscht werden müssen, wird nur schwerlich den Weg aus den Regalen des Herstellers finden. (Bei Computerspielen sieht das schon anders aus: Das nächste Weihnachtsfest kommt bestimmt.) Was bleibt, ist die Vereinigung der Vorteile von Interpreter und Compiler. Was dabei herausgekommen ist, wird umgangssprachlich Jitter genannt. Großzügig betrachtet, leitet sich der Begriff aus dem sperrig klingenden Just-In-Time-Compiler (kurz JIT-Compiler) ab. Wenn Sie sich den als Flasche vorstellen, ist in dieser allerdings beträchtlich mehr Compiler als Interpreter vorhanden. Drehen Sie das Behältnis um, um die Geister herauszulassen, tritt auch sogleich der Compiler in Aktion, genauer betrachtet allerdings nur dann, wenn Ihr Programm tatsächlich ausgeführt wird. Das Just-In-Time geht allerdings noch weiter: Ein Programm besteht (auch im weiteren Sinne) aus Modulen. Wird lediglich das Modul A verwendet, ist die Übersetzung des Moduls B (zunächst) überflüssig. Demnach ist nur das aktuell Benötigte zu kompilieren – zur Laufzeit und gerade noch rechtzeitig. Eben just in time.
Quellcode (Sourcecode)
Compiler (z.B. csc.exe)
IL-Code zuzüglich Metadaten
JIT-Compiler
Native Code Abbildung 2.2 Vom Quellcode zum Native Code – die Schritte bis zur Ausführung eines C#-Programms
19
2.2
2
Die Sache mit .NET
Vergessen Sie jedoch nicht: Der Ausgangspunkt des Kompilats war der Zwischencode. Von dem, was Sie in Kürze ins Editorfenster Ihrer Entwicklungsumgebung (Visual C# 2010 Express Edition) eingeben werden, sind wir relativ weit abgekommen. Daher ist es jetzt Zeit, den Weg zurück zu dem anzutreten, was spätestens ab dem nächstem Kapitel Ihre Aufgabe sein soll: zum Programmieren. Um genau zu sein: zum Programmieren mit C#. Zuvor sehen wir uns den Weg vom Quellcode zum Native Code in einer grobschematischen Darstellung (siehe Abbildung 2.2) an.
2.3
Ein Raum ohne Tür – der Namensraum
Die Geburtsstunde von .NET (Microsoft begann die Entwicklung in den späten 1990er-Jahren) ist nicht mit der Geburtsstunde der Namensräume gleichzusetzen, existiert das Konzept des Namensraums (engl. namespace) doch schon länger. Ferner sind Namensräume keine Domäne von Programmiersprachen; XML als Markup-Sprache nutzt Namensräume genauso wie UML als Modellierungssprache. Beide tun dies allerdings auf andere Art. Eigentlich geht es bei der Einrichtung von Namensräumen weniger um Ordnung als darum, die Möglichkeit von Verwechslungen zu minimieren. Schließlich besteht durchaus die Möglichkeit, zwei Klassen mit unterschiedlichen Aufgaben denselben Namen zu geben. Ferner können durch die Einrichtung von Namensräumen die Klassen eines Programms ausgezeichnet strukturiert werden, u. a. nach Aufgabengebieten. Beispielsweise wäre es überlegenswert, die Klasse, die für das Zeichnen zweier sich überlappender Kreise zuständig ist, einem anderen Namensraum zuzuweisen als jene, die die Größe der Überschneidungsfläche berechnet. Erstere würde in die Kategorie »Montagsmaler« fallen, Letztere wäre unter »fortgeschrittene mathematische Verfahren« zu verbuchen. Klassen In einem Namensraum werden weder Methoden noch Variablen deklariert, sondern primär Klassen. Ist eine statische (damit wir uns die Instanziierung sparen) Klasse MeineKlasse in einem Namensraum MeinNamensraum organisiert, sind damit natürlich auch die Elemente der Klasse (Methoden, Variablen etc.) dem entsprechenden Namensraum »zugewiesen«. Auf den Punkt gebracht: Für alles, was nicht im noblen Stande einer Klasse ist, existieren nur indirekt Namensräume – zumindest dann, wenn es um programmiersprachliche Namensräume geht. Doch gibt es Ausnahmen. Eine davon ist Ihnen bereits begegnet: die Struktur.
20
Ein Wort wie ein Kosename – Assembly
Die Gültigkeit der Klasse MeineKlasse ist auf den Namensraum MeinNamensraum beschränkt. Wir sollten nicht vergessen, der Klasse MeineKlasse noch eine Methode zu spendieren; die nennen wir einfach TuWas(). Wir entwickeln ein Szenario, in dem Sie aus einer selbst geschriebenen Methode MeineMethode() heraus die Methode TuWas() aufrufen möchten. Diese ist Teil der Klasse MeineKlasse, deren Gültigkeit wiederum auf den Namensraum MeinNamensraum beschränkt ist. Das hatten wir schon, okay. Wie kommen wir nun aber an die Methode TuWas()? Ein Namensraum hat keine Türen. Folgendes Listing gibt die Antwort: public void MeineMethode() { MeinNamensraum.MeineKlasse.TuWas(); }
Der Aufruf der Methode TuWas() gelingt, indem wir der Methode sowohl den Klassennamen als auch den Namen des Namensraums voranstellen, jeweils getrennt durch den leidlich bekannten Punktoperator (.). Schauen wir uns einige Namensräume mit wichtigen Typen im .NET-Framework an. Da wären: 왘
Windows.System.Forms, der klassische WindowsForms-Steuerelemente wie Button, CheckBox etc. zur Verfügung stellt.
왘
System.Web beinhaltet Klassen und Interfaces zur Client-Server-Kommunika-
tion. 왘
System.IO bietet elementare Funktionalitäten für Schreib- und Lesezugriffe.
왘
System.Data stellt Klassen zur Verwaltung von Daten aus unterschiedlichen
Datenquellen zur Verfügung. 왘
Windows.System.Controls kapselt Steuerelemente der Windows Presentation Foundation (WPF), wie beispielsweise Canvas, ListBox oder TextBox.
2.4
Ein Wort wie ein Kosename – Assembly
Konzeptionell betrachtet, existiert zwischen einem konventionell gefertigten Haus und einer .NET-Framework-Anwendung eine Gemeinsamkeit: Beide bestehen aus Bausteinen. Im Falle von .NET werden die Bausteine als Assemblys bezeichnet, ein melodisches, im Ursprung auf das beträchtlich steifer klingendere Assemblierung zurückgehendes Wort, was auf Deutsch so viel wie Baugruppe heißt.
21
2.4
2
Die Sache mit .NET
Eine Assembly besteht aus einer Auflistung von Typen und Ressourcen, die so erstellt wurden, dass ein Zusammenspiel möglich wird und darüber hinaus eine logisch funktionale Einheit gebildet wird. Dabei könnte das Aufgaben- und Funktionsspektrum einer Assembly nicht größer sein: 왘
Eine Assembly enthält Code, der von der Common Language Runtime ausgeführt wird, wobei jede Assembly nur über einen Einstiegspunkt verfügt. Dabei gelten als Einstiegspunkt die Schlüsselbegriffe Main DllMain WinMain
왘
Eine Assembly bildet eine sicherheitsrelevante Schranke dergestalt, dass sowohl das Anfordern einer Berechtigung als auch die Erteilung der Berechtigung durch die Assembly geschieht (ein weiteres Indiz dafür, wie genau es .NET mit Fragen der Sicherheit nimmt).
왘
Eine Assembly bildet eine typbezügliche Grenze, was einigermaßen nichtssagend klingt, weswegen uns ein Beispiel weiterhelfen muss: Wird ein Typ Beispieltyp in den Gültigkeitsbereich einer Assembly FirstAssembly.dll geladen, ist der Typ ein anderer, als wenn er in den Gültigkeitsbereich der Assembly SecondAssembly.dll geladen wird – was dem Konzept der Namensräume nicht unähnlich ist.
왘
Eine Assembly bildet eine Schranke für den Gültigkeitsbereich von Verweisen. Teil jeder Assembly ist das sogenannte Assembly-Manifest (siehe Abbildung 2.3), das Assembly-Metadaten enthält. Diese werden für das Auflösen von Typen ebenso verwendet wie für die Bereitstellung von Ressourcen (beispielsweise JPG- oder Ressourcendateien sowie Bitmaps). Die Assembly gibt Typen und Ressourcen an, die außerhalb der Assembly verfügbar gemacht werden dürfen.
왘
Eine Assembly stellt die kleinste, verwendbare versionierte Einheit der Laufzeitumgebung dar, wobei eine Assembly von der Common Language Runtime in verschiedenen Versionen verwendet werden kann. Die Typen und Ressourcen einer Assembly bilden eine Einheit, deren Version jener der Assembly entspricht, die die Typen und Ressourcen beinhaltet.
왘
Eine Assembly bildet eine Einheit bezüglich der parallelen Ausführung, also dem Ausführen unterschiedlicher Versionen einer Assembly.
Abbildung 2.3 soll den Aufbau einer Assembly veranschaulichen, wobei es sich um eine Einzeldatei-Assembly (englisch Single-File-Assembly) handelt, bei der alle Elemente einer Assembly physisch in einer Datei (TestAssembly.dll) gruppiert sind.
22
Ein Wort wie ein Kosename – Assembly
TestAssembly.dll Assemblymanifest Typmetadaten MSIL-Code Ressourcen Abbildung 2.3
Schematischer Aufbau einer Einzeldatei-Assembly
Daneben existieren auch Mehrfachdatei-Assemblys (englisch Multi-File-Assemblies), die nicht vom Dateisystem selbst, sondern vom Assembly-Manifest verbunden werden. Die CLR behandelt die verschiedenen Dateien der Assembly im Weiteren als logische Einheit. Bei einer Multi-File-Assembly müssen das AssemblyManifest und der Einsprungspunkt in ein und demselben Modul liegen. Auch eine Assembly besitzt eine Identität Die körperlichen Merkmale Ihrer eigenen Identität werden im Personalausweis eingetragen sein, so wie auch Ihre Anschrift. Die Identifikation einer Assembly geschieht über die Elemente 왘
Assembly-Name
왘
Versionsnummer
왘
Kultur, also die länderspezifischen Festlegungen (standardmäßig das Benutzergebietsschema des Betriebssystems)
왘
Zu dem kommen Informationen über den sogenannten starken Namen, der weniger ein Name im Buchstabensinne als eine 128-Byte-Zahl ist, die aus zwei großen Primärzahlen besteht, die miteinander multipliziert worden sind.
All diese Informationen finden Sie, neben anderen, im Assembly-Manifest, dem »Personalausweis einer Assembly«. Zweifelsohne sind Sie auch ohne einen (gültigen) »Perso« existenzfähig, wogegen eine Assembly ohne ihr Manifest keine wäre.
Natürlich stellt sich sofort die Frage, wann eine Einzeldatei-Assembly und wann eine Mehrfachdatei-Assembly erstellt werden sollte. Um das interessante Thema Assembly nicht ausufern zu lassen, wollen wir uns mit zwei Anwendungsfällen für Mehrfachdatei-Assemblys begnügen. Solche können Sie beispielsweise erstellen, wenn es darum geht, 왘
das Herunterladen einer .NET-Framework-Anwendung zu beschleunigen. Konkret legen Sie selten verwendete Typen in einem eigenen Modul ab, das dann nur bei Bedarf herunterzuladen wäre.
왘
Module zu kombinieren, die in verschiedenen .NET-Sprachen geschrieben sind.
23
2.4
2
Die Sache mit .NET
2.5
Die »digitale Müllabfuhr« – der Garbage Collector
Dass Sicherheit bei .NET großgeschrieben wird, gehört zu den echten Pluspunkten der Technologie. Nicht jedes Programmmodul sollte auf jede Ressource zugreifen dürfen. Das damit einhergehende Einschränken von Rechten wird uns noch in programmiernahem Zusammenhang (bei der .NET-Sprache C# ...) beschäftigen. Im Regelfall werden Programme von .NET ständig überwacht. Genauer gesagt, von der .NET-Laufzeitumgebung als – mit einer Einschränkung – stets präsenter Kontrollinstanz. Selbst die Vorgänge im zweiten »Heiligtum« des Computers, dem Arbeitsspeicher, entgehen dem wachen Auge der Laufzeitumgebung nicht. Wird im Speicher mit Ressourcen schluderig verfahren, d. h., erfolgt keine Freigabe nicht mehr benötigter Segmente durch das Programm (womit diese Speicherstellen für die Nutzung des Programms drastisch formuliert »tot« wären), tritt der sogenannte Garbage Collector (die Speicherbereinigung) als eine Art digitale Müllabfuhr auf den Plan. Allerdings nicht allein. Gemeinsam mit dem C#-Compiler wird der Code hinsichtlich der Frage analysiert, welche Verweise auf das Objekt weiterhin verwendet werden können und welche nicht. Im letzteren Falle erfolgt der Anstoß zum Aufruf des Destruktors. Exkurs: Die Zerstörung von Objekten durch den Destruktor Wird das Objekt – die Instanz – einer Klasse erzeugt, geht dem der Aufruf des Konstruktors voraus. Ob der standardisiert oder frei definiert ist, ist in dem Zusammenhang nicht von Belang. Die Lebenszeit des Objekts währt nur so lange, wie der Destruktor, als »Gegenmittel« zum Konstruktor, nicht aufgerufen wird. Der Destruktor enthält nicht selten Anweisungen zur Freigabe der vom Objekt beanspruchten Ressourcen. Das können auch fremde Ressourcen wie z. B. eine Datenbankverbindung sein, die in einer Referenz des Objekts vorgehalten wird. Aufgerufen wird der Konstruktor leider nicht durch kritisches Nachdenken des Programms über sich selbst. Vielmehr muss mindestens eine von zwei klar definierten Bedingungen erfüllt werden: 왘
Der Gültigkeitsbereich der Objektvariablen wurde verlassen.
왘
An die Objektreferenz wurde null zugewiesen.
Prima, ist eine oder sind gar beide Voraussetzungen erfüllt, erfolgt also der Aufruf des Destruktors. Danach geht es dem Speicher besser, weil »Objektleichen« entfernt wurden. So einfach funktioniert das jedoch nicht, ist der tatsächliche Aufruf des Destruktors doch selbst dann zeitlich relativ unbestimmt, wenn der Gültigkeitsbereich einer Objektvariablen verlassen und/oder an die Objektreferenz null zugewiesen wurde. »Programmiersprachlich« betrachtet, existiert danach zwar kein Objekt mehr, im Speicher dagegen dümpelt der digitale Müll auch weiterhin vor sich hin.
24
Die »digitale Müllabfuhr« – der Garbage Collector
Ist hinsichtlich dessen die externe Triggerung des Destruktors durch den Garbage Collector nicht eine famose Sache? Nein, ist sie nicht. Das Prinzip ist pfiffig und gut gemeint – mehr aber auch nicht. Sicher, das liest sich ein wenig wie das schulmeisterlich strenge Wort, doch sind mit dem Garbage Collector in der Tat einige nicht von der Hand zu weisende Ungereimtheiten verbunden, die sich durchaus unter dem gerne gebrauchten Oberbegriff »Performance-Verlust« zusammenfassen lassen. Zwar sind die Algorithmen, die zur Suche nach sogenannten Memory Leaks (also Speicherlecks) eingesetzt werden, von durchaus akzeptabler Geschwindigkeit (was auch immer hier das Kriterium sein mag), das ändert jedoch kaum etwas daran, dass im Hinterstübchen der Common Language Runtime ein Garbage Collector genanntes Etwas langsam, aber stetig auf die Bremse der Ausführungsgeschwindigkeit tritt. Abgesehen vom zeitlich Unbestimmten beim Aufruf des Destruktors, ließe sich Pro Garbage Collector natürlich der Hinweis auf Speicherbereiche anbringen, deren Freigabe allzu gern mal vergessen wird – mit dem bekannten Worst-CaseSzenario eines mit zunehmender Laufzeit immer langsamer werdenden Computers (inklusive eines möglichen Absturz des Programms). All das ist richtig und sollte vermieden werden. Nur: Wo bitte bleibt das Vertrauen in den Entwickler und seine Kunst? Das Zerstören nicht mehr benötigter Objekte zwecks Speicherbereinigung gehört genauso zum objektorientierten Handwerkszeug wie das Erstellen eines Objekts. In die Fahrschule sind Sie schließlich auch nicht nur deshalb gegangen, um zu lernen, wie ein Auto in Bewegung gesetzt wird. Zum Hornberger Schießen wird die automatische Speicherbereinigung im Zusammenhang mit Systemen, die unter Echtzeitbedingungen laufen (sogenannte Echtzeitsysteme, englisch Real Time System). Echtzeitsysteme Echtzeitsysteme haben es schwerer als konventionelle. Während es bei konventionellen Systemen »lediglich« darauf ankommt, ein richtiges Ergebnis zu liefern, wird bei Echtzeitsystemen ein solches Ergebnis innerhalb eines wohldefinierten Zeitintervalls erwartet. Nachrangig ist dabei die Größe des Intervalls, das von einigen Millisekunden bis hin zu Tagen reichen kann.
Sucht das System nämlich nach freizugebenden Speichersegmenten, ist das Programm so lange blockiert, wie der Vorgang nicht abgeschlossen ist. Überlegen Sie sich die Konsequenzen, wenn es beispielsweise um vollautomatisierte Produktionsstraßen geht! Jede Zehntelsekunde wird benötigt. Der Garbage Collector be-
25
2.5
2
Die Sache mit .NET
nötigt für seinen »Rundgang« durch die Codedateien mehr Zeit. Während dieser ist das System von seinen regulären Aufgaben gezwungenermaßen entbunden. Das Echtzeitsystem wird zum blockierten System, der Garbage Collector zur Vollbremsung. Wann der Garbage Collector nach verwaisten Objekten sucht, kann nicht vorhergesagt werden (was eine seltsam anmutende Gemeinsamkeit mit einem »ungetriggerten Destruktor« darstellt). Dass er irgendwann in Aktion tritt, ist dagegen sicher. Das passiert nämlich spätestens dann, wenn die Speicherressourcen weitgehend aufgebraucht sind. Auf eine Meldung à la »Es ist nicht mehr genügend Arbeitsspeicher vorhanden …« lässt es die automatische Speicherbereinigung nicht ankommen. Da können Sie ganz zuversichtlich sein. Es stellt sich die Frage, was vorher geschieht. Trotz funktionaler Wichtigkeit des Garbage Collectors genießt er als selbstständige Ausführungseinheit (im Englischen spricht man von einem Thread) keine erhöhte Priorität. Selbst die Speicherbereinigung hat demnach widerstandslos zu warten, bis die Anwendung ruht, wodurch Rechenleistung zum Zwecke der Speicherverwaltung freigegeben werden kann. Der Garbage Collector also darauf angewiesen, von dem Programm, das untersucht werden soll, (indirekt) Rechenzeit zugewiesen zu bekommen, worauf er bei hoch beanspruchten Programmen womöglich lange warten kann. Gibt es einen Ausweg aus dem Dilemma? Ja, indem Sie im C#-Programmcode die automatische Speicherverwaltung durch die nicht sonderlich schwierige Zeile GC.Collect();
selbst aufrufen. Was genau aufgerufen wird, ist die statische Methode Collect() der Klasse GC, die Sie im Namensraum System finden. Damit erzwingen Sie eine sofortige Garbage Collection, mit der das System gezwungen wird, so viel Speicher wie möglich freizugeben – ein im Grundsatz recht rabiates Vorgehen, das einigermaßen gut überlegt sein will. Einen chirurgischen Eingriff stellt die Arbeit des Garbage Collectors nicht unbedingt dar. So viel sollte inzwischen klar geworden sein. Entweder wird der Speicher im Rahmen des programmseitig Möglichen bereinigt – oder überhaupt nicht. Zum Schluss des Abschnitts habe ich doch noch einige lobende Worte für den schwer – und vielleicht nicht unbedingt zu Recht – gescholtenen Garbage Collector. Für diese muss ich allerdings ein wenig ausholen. Auch abseits seiner Aufgabe ist ein Objekt nicht gleich Objekt. Entscheidend ist der Augenblick seiner Erstellung. Sie und ich sind ein Objekt der Gattung Mensch, irgendwann geboren und somit zu einer bestimmten Generation gehö26
Das Salz in der Suppe – Steuerelemente
rend. In unserem Falle spricht man gleichwohl auch von Lebensjahren, bei digitalen Objekten im .NET-Kontext von einer Generationszahl als ein gegebenenfalls von der Implementierung definiertes Maß für die Lebensdauer eines Objekts. Null als Generationszahl wird für das zuletzt erstellte Objekt festgelegt, ältere Objekte erhalten eine dementsprechend höhere, bis hin zur höchsten Generationszahl. Die Größe der maximalen Generationszahl kann mittels der MaxGeneration-Eigenschaft der Klasse GC ermittelt werden. Diese Zahl, die der Collect()-Methode als Wert übergeben wird, bewirkt die Freigabe von Objekten bis einschließlich der höchsten Generationszahl, die unter dem Einfluss der automatischen Speicherbereinigung gleichwohl kein Fixum ist. Denn der Garbage Collector kennt die bis hier beschriebenen Zusammenhänge und weiß sie ausgesprochen intelligent zu nutzen. Auch vor dem Hintergrund der Annahme (die man kritisch hinterfragen kann), dass neuerer Speicher dringender einer Bereinigung bedarf als älterer.
2.6
Das Salz in der Suppe – Steuerelemente
Im Englischen heißen Steuerelemente Controls, und Sie werden im Verlaufe des Buches einige näher kennenlernen. Steuerelemente sind unverzichtbar, denn das, was wir bis jetzt in Sachen .NET-Framework besprochen haben, nützt Ihnen im praktischen Umgang, zumindest im Sinne einer wirklichen Arbeitserleichterung, wenig. Zu Beginn eines konkreten Projekts wird es um die funktionale Gestaltung einer Bedienoberfläche gehen, auf der Bedienelemente systematisch und optisch ansprechend anzuordnen sind, ähnlich wie Sie das von komfortablen, womöglich noch interaktiven Websites her kennen. Auch Webapplikationen können Sie mit dem .NET-Framework entwickeln, wobei sich prinzipiell dieselbe Frage stellt: Woher den exemplarischen Button, das Label, die CheckBox, kurz die Steuerelemente nehmen, wenn nicht selber programmieren? Um welche Steuerelemente es geht, können Sie Tabelle 2.1 entnehmen, in der einige wichtige Windows-Forms-Steuerelemente namentlich genannt und von der Funktion her beschrieben sind. Wohlgemerkt: Dies sind WindowsForms-Steurelemente.
27
2.6
2
Die Sache mit .NET
Name des Steuerelements
Funktion
Button
Darstellung einer Standardschaltfläche, auf die der Benutzer zur Ausführung von Aktionen klicken kann
BindingNavigator
Darstellung einer Benutzeroberfläche zum Bearbeiten und Navigieren durch Steuerelemente, die an Daten gebunden sind
CheckBox
Darstellung einer aktivierten oder deaktivierten Bedingung
CheckedListBox
Darstellung einer Liste von Elementen, wobei jedes Element der Liste mit einem Kontrollkästchen versehen ist
ComboBox
Darstellung von Daten in einem Dropdown-Kombinationsfeld
ContextMenuStrip
Darstellung eines Kontextmenüs (in älteren .NET-FrameworkVersionen lediglich als ContextMenu-Steuerelement bezeichnet)
DataGrid
Darstellung tabellarischer Daten aus einem Dataset. Zusätzlich ermöglicht das DataGrid-Steuerelement die Aktualisierung der Datenquelle.
DataGridView
Darstellung eines erweiterbaren Systems für die Anzeige und Bearbeitung von Tabellendaten
DateTimePicker
Tabellarische Darstellung von Datums- und Zeitangaben, aus denen ein Benutzer ein Element auswählen kann
DomainUpDown
Darstellung einer Textzeichenfolge, aus der Benutzer auswählen können und die durchsucht werden kann
FlowLayoutPanel
Darstellung eines Bereichs, dessen Inhalt dynamisch horizontal und vertikal verändert werden kann
GroupBox
Darstellung einer für andere Steuerelemente erkennbaren Gruppierung
HScrollBar VScrollBar
Die beiden Controls ermöglichen das horizontale (HScrollBar) bzw. vertikale (VScrollBar) Scrollen durch eine Elementliste oder – allgemeiner formuliert – durch eine große Anzahl von Informationen.
Label
Darstellung eines nicht zu bearbeitenden Textes
LinkLabel
Implementierung eines Hyperlinks
ListBox
Ermöglicht die Auswahl einer oder mehrerer Elemente aus einer vordefinierten Liste.
ListView
Dem Windows Explorer ähnliche Darstellung von Symbolen
MaskedTextBox
Einschränkung des Formats von Benutzereingaben in einem Formular
Tabelle 2.1 Wichtige Windows-Forms-Steuerelemente und ihre Funktion (Auflistung in alphabetischer Reihenfolge)
28
Aus der Art geschlagen – die Sprache C#
2.7
.NET und kein Ende?
Der kleine Exkurs in die ungeliebte Theorie brachte Ihnen eine ebenso interessante wie komfortable Softwaretechnologie hoffentlich ein Stück näher. Das hohe Lied auf die .NET-Philosophie habe ich dennoch nicht gesungen, muss doch .NET weder einen globalen Praxiseinsatz erfahren noch übermäßig beworben werden. Dafür ist der Erfolg zwischenzeitlich zu groß. Bei .NET sind bis zur Ausführung von Maschinencode vergleichsweise viele Schritte notwendig, von denen einige der Kontrolle durch die Laufzeitumgebung unterliegen. Auf den Punkt gebracht: .NET liegt weder eine unaufwendige Architektur zugrunde noch ist das Framework eine übermäßig schnelle Angelegenheit (was allerdings ausdrücklich nicht im Sinne von »langsam« verstanden werden sollte). Letzten Endes wird man auch bei .NET zwischen Komplexität der Aufgabe und dem dafür in Kauf zu nehmenden Aufwand abwägen müssen. Käme man beispielsweise auf die originelle Idee, einen Hardwaretreiber auf Grundlage der .NET-Plattform zu entwickeln, entstünde zweifelsohne ein grobes Missverhältnis – abgesehen davon, dass einem solchen ».NET-Treiber« nicht unbedingt zu trauen wäre. Nach .NET und seinem Framework soll nun endlich aber C# an die Reihe kommen. Hier allerdings fangen wir anders an.
2.8
Aus der Art geschlagen – die Sprache C#
Geständnisse sollte man nicht auf die lange Bank schieben, weniger noch, wenn eine Unterlassung Inhalt des Geständnisses ist: .NET versus COM Sollte bei Ihnen auf den vergangenen Seiten der Eindruck entstanden sein, mit C# ließen sich lediglich .NET-Komponenten entwickeln, ist dies nur insofern richtig, als dass COM (Component Object Model), die .NET-Vorgänger-Technologie, ebenfalls durch C# bedient werden kann, was allerdings nur selten geschieht. Deshalb ist die Entwicklung klassischer COM-Komponenten weder »out« noch eine auf an der Historie orientierten Programmierer beschränkte Angelegenheit. Sehen Sie die Dinge bitte dennoch so, wie sie sind: Überholt wurde COM von .NET mit einiger Geschwindigkeit, entsprechend hoch ist der Abstand zwischen den Technologien. Übrigens: Auch COM ist ein ausschließliches Erzeugnis der Redmonder Softwareschmiede Microsoft.
29
2.8
2
Die Sache mit .NET
2.8.1
Musik im Namen
Versuchen Sie, mit einer Standardtastatur ein Kreuz auf Ihren Bildschirm zu »zaubern«. Ein wenig verärgert, mögen Sie vielleicht denken: »Wozu existiert ein Plus-Zeichen?« Stimmt, nur ist das nicht gemeint, sondern jenes Kreuz, das einen Stammton um einen Halbton erhöht. Und so sieht das Kreuz aus der Welt der Noten aus: $. Direkt eingeben lässt sich das Erhöhungszeichen (im internationalen Zeichencodierungssystem Unicode finden Sie es unter U+266F) nicht, wohl aber das ähnlich aussehende Raute-Zeichen (#). Anderenfalls würden Sie im Verlauf des Buches nicht mit C#, sondern mit C arbeiten. Der Freude am Programmieren wäre damit kein Abbruch getan, und der Bezug zur Musik wäre schneller hergestellt – auch deswegen, weil C nicht nur der Urvater der mächtigen C-Programmierfamilie ist, sondern auch ein überaus beanspruchter Stammton. Erhöht man diesen Ton nun um unseren halben Ton, erhält man das erwähnte C. Im Lande Mozarts, Bachs und Beethovens heißt er Cis, im Englischen dagegen C sharp. Und da Englisch die Weltsprache der IT-Kundigen (und die Muttersprache des Branchenriesen Microsoft) ist, sprechen auch wir statt von Cis von C sharp. Das klingt gut, hat einen intelligenten Hintergrund und verleugnet die Musik, als wohl schönste der schönen Künste, nicht. War es das zu diesem Thema? Nein. Abgekupfert – Derivate Die Beliebtheit einer Programmiersprache lässt sich auch an der Anzahl vorhandener Derivate ablesen. C# hat einige Nachahmer gefunden, sogar im riesigen Hause des Sprachurhebers (Microsoft) selbst. Was einmal klingt, kann auch mehrfach klingen, am besten noch in unterschiedlichen Tönen, was im Kontext unserer kleinen Namensforschung natürlich nicht wörtlich zu nehmen ist. Schön klingt sie dennoch, ich meine die Programmiersprache Polyphonic C#, bei der es sich genau betrachtet allerdings nicht um ein Derivat, sondern um eine Erweiterung handelt. Andere C#-Derivate sind u. a.: 왘
Vala
왘
Metaphor
왘
Multiprocessor C# (MC C#)
왘
eXtensible C# (XC#)
왘
Sing#
왘
Spec#
30
Aus der Art geschlagen – die Sprache C#
2.8.2
Ein Kessel Buntes – die Ursprünge der .NET-Sprache C#
Fern aller mitunter philosophisch angehauchten Grundsatzdebatten sind .NET und C# für nicht wenige Freunde des .NET-Frameworks gedanklich genauso untrennbar miteinander verwoben, wie es Kreide und Tafel für den gestrengen Lehrer sind. C# ist elegant, typsicher (Typverletzungen werden spätestens zur Laufzeit erkannt) und nicht zuletzt objektorientiert. Vor allem jedoch ist C# –anders. Anders deswegen, weil die Sprache ein Konglomerat aus verschiedenen Hochsprachen darstellt. Im Einzelnen wären zu nennen: 왘
C
왘
C++
왘
Java
왘
Smalltalk
왘
Modula 2
왘
Delphi
Dazu kommt noch die Datenbankabfragesprache SQL. Primär orientiert sich C# allerdings an Mitgliedern der C-Familie (C, C++), was es Programmierern mit fachlichem Schwerpunkt auf den beiden Cs besonders leicht macht, auf das dritte C (C#) umzusteigen. »Anders« ist aber auch der Name des Mannes, der als Architekt primär für das Design der Sprache C# verantwortlich zeichnet: Anders Hejlberg. Microsoft war nie besonders zimperlich, wenn es darum ging, Spitzenleute von anderen namhaften Softwareschmieden abzuwerben, in diesem Falle von Borland. Und wer da im Jahre 1996 dem Schwergewicht der IT-Branche ins ausgedehnte Netz ging, war nicht irgendein Programmierer, war doch eben jener Anders Hejlberg der Urheber des »Pascal-All-in-one-Systems« (Editor, Compiler, Debugger in einem) Turbo Pascal, mit dem auch ich während meines Studiums (gerne) gearbeitet habe. Erschienen ist Turbo Pascal allerdings bereits zehn Jahre zuvor. Damals verwies die Entwicklungsumgebung, besonders der Compiler, nicht zuletzt aufgrund seiner beeindruckenden Geschwindigkeit alles bisher Dagewesene auf die hinteren Ränge. Übrigens: Hejlsberg ist darüber hinaus einer der Entwickler des .NET-Systems und Däne.
2.8.3
Ein heikler Punkt – Pointer
Fragen Sie einen eingefleischten C++-Programmierer, auf welches der Sprachkonzepte er entweder niemals oder nur allzu gerne verzichten würde, bekommen Sie vielleicht (und hinter vorgehaltener Hand) zur Antwort: Zeiger. Abgesehen vom grundsätzlich Schwierigen beim Einsatz von Zeigern, scheiden sich die gelehrten
31
2.8
2
Die Sache mit .NET
Geister nicht zuletzt in Hinblick auf die Sicherheitsaspekte. Das ist nicht so bei C#, denn dort gibt es keine Zeiger, wenigstens nicht durchgängig. Grundsätzlich versucht man den Einsatz von Zeigern bei .NET zu vermeiden. Werden Zeiger dennoch eingesetzt, spricht man so abschreckend wie naheliegend von unsicherem Code, der aus Bereichen mit eingeschränkten Rechten verbannt ist und ohne die Zuweisung erweiterter Rechte erst gar nicht zur Ausführung kommt. Ja, und in der Tat ist das .NET-Framework eine einigermaßen »rechthaberische Angelegenheit«. Um vermeintlich oder tatsächlich unsicheren Code zu produzieren, brauchen Sie den C#-Modifizierer unsafe, der damit zu einem Teil der Deklaration einer Klasse, einer Struktur, eines Interfaces oder eines Delegate wird. Womit die Einsatzgebiete des Modifizierers unsafe mitnichten ausgeschöpft wären, lässt er sich doch gleichermaßen in die Deklaration eines Felds, einer Methode, einer Eigenschaft, eines Ereignisses, Indexers, Operators, Instanzkonstruktors bzw. -destruktors integrieren. Allgemein ermöglicht die unsafe-Anweisung die Verwendung eines unsicheren Kontexts innerhalb eines Blocks. Alles, was sich im Block befindet, wird als unsicherer Kontext angesehen. Punkt. Was wollen Sie mehr? Zum Beispiel ein kleines, halbwegs einprägsames Beispiel? Gut, versuchen wir es mit dem Folgenden: public unsafe class Example { public char* C; public char* o; public char* d; public char* i; public char* n; public char* g; } Listing 2.1
Deklaration einer Klasse unter Einbeziehung des Modifizierers »unsafe«
Der Modifizierer unsafe in der Deklaration der Klasse Example bewirkt, dass deren gesamter Bereich, also alles, was sich innerhalb der geschweiften Klammern befindet, zum unsicheren Kontext erklärt wird. Und in dem haben wir es mit gleich sechs öffentlichen (public) Variablen vom Typ »Zeiger auf char« (char*) zu tun. Das Schlüsselwort unsafe ist selbsterklärend und missverständlich zugleich (desgleichen die Formulierung unsicherer Kontext). Abgesehen davon, dass Sie vermutlich bei Ihrer Arbeit mit .NET sehr genau wissen, was Sie tun, bedeutet un-
32
Aus der Art geschlagen – die Sprache C#
safe keineswegs das unbedingte Vorhandensein unsicherer Codeabschnitte. Erst im Zusammenhang mit der .NET-Laufzeitumgebung (CLR) machen die vermeintlichen Unsicherheiten im Code Sinn.
Zäumen wir das Pferd von der anderen Seite auf: Wie sieht es denn bei sicherem Code aus? Separat ausgewiesen braucht der nicht zu werden, ein Modifizierer safe existiert im Wortschatz der .NET-Sprache C# nicht (oder?). Sicherer Code ist deshalb sicher, weil er unter der Kontrolle der Laufzeitumgebung steht, weniger weil er per se sicher wäre. Beispielsweise verhindert die Laufzeitumgebung das Überschreiben von Array-Grenzen. Unsicherer Code genießt eine solche Protektion nicht. So einiges ist da möglich, bis hin zum »locker fröhlichen« Überschreiben ordentlich reservierter Speicherbereiche. Den Anwender »freut« es zuletzt. Strukturierung ist alles Erinnern Sie sich an einen nahen Verwandten der Klasse? Natürlich: die Struktur. Wird eine Struktur angelegt, erhält man einen komplexen Datentyp. Von diesem – ganz gleich wie komplex er nun wirklich ist – können Sie Variablen deklarieren, genauso wie Sie es von den elementaren Datentypen (int, char, float etc.) her kennen.
Na denn: Schreiben wir obiges Beispiel für eine Struktur desselben Namens um (Example): public unsafe struct Example { public char* C; public char* o; public char* d; public char* i; public char* n; public char* g; } Listing 2.2
Deklaration einer Struktur unter Einbeziehung des Modifizierers »unsafe«
Dasselbe Wort – unsafe – muss noch einmal vorkommen. Als Compiler-Direktive nämlich. Fehlt die Direktive, d. h., versuchen Sie, einen mit unsafe ausgewiesenen, unsicheren Code zu kompilieren, ohne das entlarvende Wort auch dem CSCCompiler (csc.exe) mitzugeben, verweigert dieser mit einer deutlichen Fehlermeldung den Dienst. Als Nutzer einer integrierten Entwicklungsumgebung (IDE) werden Sie in diese Verlegenheit jedoch nicht unbedingt kommen. Allerdings bleibt es Ihnen nicht erspart, der Entwicklungsumgebung mitzuteilen, dass der Compiler unsicheren Code im Projekt vorfinden wird. Alles Weitere erledigt dann die IDE. Dazu ist es notwendig, im Projektmappen-Explorer mit der
33
2.8
2
Die Sache mit .NET
rechten Maustaste auf das aktuelle Projekt zu klicken, um anschließend im Kontextmenü die Option Eigenschaften auszuwählen. Was Sie im Code-Editor daraufhin erwartet, ist die Eigenschaftenseite des Projekts, auf der Sie im rechtsseitigen Menü Erstellen wählen. Im Formular muss das Kontrollkästchen Unsicheren Code zulassen gesetzt werden (siehe Abbildung 2.4).
Abbildung 2.4 Über das grafische Interface der Entwicklungsumgebung (IDE) wird dem Compiler mitgeteilt, dass er unsicheren Code zu kompilieren hat.
Ein wenig aufwendiger gestaltet sich die Angelegenheit, wenn Ihnen der Sinn nach »manuellem Bedienen« des Compilers steht, also beispielsweise über die Kommandozeile des Betriebssystems. Im folgenden Beispiel wird der Compiler angewiesen, die Quellcodedatei test.cs für den unsicheren Modus zu kompilieren: csc /unsafe test.cs
Für Windows-Applikationen (und solche werden Sie erstellen) ist die Frage zeigerarithmetischer Codierung weniger relevant als für eine Web-Applikation; dessen ungeachtet sollte das Thema trotzdem nicht gemieden werden. Zudem: Vielleicht hegen Sie ja Sympathien für Zeiger. Ein wirklich großer Stein wird Ihnen vom .NET-Framework, ungeachtet aller Sicherheitsfragen, nicht in den Weg gelegt, geschweige denn von C# selbst. Dort können u. a. folgende Typen Zeigertypen sein:
34
Aus der Art geschlagen – die Sprache C#
왘
sbyte, byte, short, ushort, int, uint, long, ulog, char, float, double, decimal und bool
왘
enum-Typ (beliebig)
왘
ein beliebiger Zeigertyp
Ob Sie Zeiger einsetzen oder nicht, liegt natürlich bei Ihnen – genauso wie die Bewertung der Tatsache, dass Zeiger nicht vom bereits besprochenen (siehe Abschnitt 2.5) Garbage Collector überwacht werden. Der kennt weder Zeigertypen noch die Daten, auf die sie verweisen. Ein Fazit im Spaß Programmieren Sie unsicher, und Sie bewegen sich mit Zeigern auf der sicheren Seite.
2.8.4
Ein geheimnisvoller Verein – Delegates
Mit dem scherzhaft gemeinten, die Verhältnisse dreist auf den Kopf stellenden Fazit oben könnte das Kapitel über Zeiger abgeschlossen werden, gäbe es bei .NET nicht ein zeigerähnliches Sprachkonzept, dem die Beschränkung auf unsichere Codebereiche erspart bleibt. Gemeint sind sogenannte Delegates. Das klingt ein wenig nach einer Bedrohung aus der hintersten Ecke des Sonnensystems, ist jedoch eine durchweg friedliche, wenngleich nicht unbedingt triviale Angelegenheit. Und wirklich: Einfach zu verstehen ist das Konzept der Delegates nicht. Versuchen wir die Annäherung in kleinen Schritten. Delegates (dt. Delegierte) sind Stellvertreter, zumindest in der ursprünglichen Bedeutung des Wortes. Von politischen Delegierten bis apostolischen Legaten existieren unter dem Himmelszelt eine ganze Menge Stellvertreter. Softwaretechnisch betrachtet, sind Delegates jedoch mehr als »nur« Stellvertreter (mit meist eingeschränkten Befugnissen), nämlich Referenzen auf Methoden, was Sie womöglich und nicht zu Unrecht an Funktionszeiger bei C++ bzw. C erinnert. Bei einer kurzen, klaren Erinnerung sollte es allerdings bleiben, bieten Delegates doch einen größeren Funktionsumfang als die programmiersprachlichen Vorbilder, wird doch auf ungleich mehr als lediglich Methoden verwiesen. Genauer gesagt, verweisen Delegates auf: 왘
Instanzmethoden eines Typs sowie die Zielobjekte, die der Methode zugeordnet werden können
왘
statische (static) Methoden
Legen wir nach: Ein Delegate gilt als über seinem ersten Argument geschlossen, wenn er auf eine statische Methode und ein Zielobjekt verweist, das dem ersten Parameter der Methode zugeordnet werden kann.
35
2.8
2
Die Sache mit .NET
Von elementarer Wichtigkeit bei Delegates ist: 왘
deren unbedingte Typsicherheit
왘
der objektorientierte Ansatz
Allein mit diesen beiden Punkten wird dem klassischen Funktionszeiger bereits der Rang abgelaufen. Was Delegates mit Funktionen bzw. Methoden gemeinsam haben, ist das Vorhandensein einer Signatur. Alle Funktionen, auf die ein Delegate zeigen soll, müssen einer im Delegate festgelegten Signatur entsprechen. Wohlgemerkt: der Signatur. Nichts sonst. Einen Methodenrumpf werden Sie bei der Deklaration eines Delegate nicht finden. Damit Sie mir glauben, schauen Sie sich die folgende Deklaration des Delegate MeinDelegate an. Zuvor aber noch dies: Delegates werden zur Laufzeit des Programms erstellt, was es ermöglicht, das Verhalten eines Programms dynamisch zu ändern. Damit besteht zum Beispiel die Möglichkeit, den Delegate in Abhängigkeit von einer Bedingung zu generieren. Doch zurück zur Deklaration: public delegate void MeinDelegate(int buchseiten);
Auch wenn es allzu selbstverständlich erscheint: Dass die Sichtbarkeit des Delegate MeinDelegate auf public gesetzt ist und der Rückgabetyp void lautet, spielt nur eine sekundäre Rolle, genauso wie der exemplarische Parameter buchseiten vom naheliegenden Basistyp int. Entscheidend ist die Existenz einer Methode mit identischer Signatur, was selbstredend nicht bedeutet, dass die Methode denselben Namen wie der Delegate besitzt. Etwas anderes, was uns gleichwohl schneller zurück zum Delegate führt: Natürlich wissen Sie um die Bedeutung des Wortes Instanz im Kontext der Programmierung. Sinnbildlich gesprochen ist eine Instanz die Kopie einer Klasse. Wird eine Klasse instanziiert, ist damit nichts anderes gemeint, als dass von einer zunächst x-beliebigen Klasse eine Kopie (mit anderem Namen) erstellt wird. Mit dieser Kopie arbeiten Sie, während Ihnen der Zugriff auf die originäre Klasse nicht selten verwehrt bleibt. Wenn wir schon mal dabei sind: Nicht zuletzt im Hinblick auf das Vorhandensein der .NET-Klassenbibliothek ist die Tatsache, dass Klassen in der Regel nicht direkt verwendet werden können, durchaus zu verstehen (siehe dazu den folgenden Info-Kasten).
36
Aus der Art geschlagen – die Sprache C#
Besuch in einer Bibliothek In Ihrer Heimatstadt gibt es wahrscheinlich eine größere Bibliothek, die Sie als wissbegieriger Mensch womöglich (und trotz der Existenz des allgegenwärtigen World Wide Web) nutzen. Sie leihen Bücher oder schmökern in denen vor Ort. Vielleicht fertigen Sie auch Kopien einzelner Seiten, Kapitel oder Passagen an (oder lassen sich zu eigenem Schreiben inspirieren). Eines jedoch tun Sie gewiss nicht: Veränderungen am literarischen Gegenstand selbst vornehmen (schließlich wollen Sie wiederkommen dürfen).
Klassen, Instanzen, Bücher! Ihre Gedanken bewegen sich zweifelsohne in der richtigen Richtung: Wie so vieles im hochkomplexen ».NET-Gebälk« geht auch ein Delegate zunächst auf eine Klasse zurück, namentlich auf die Klasse Delegate (die sich im Namensraum System befindet). Sogleich spinnen Sie den Faden munter weiter: »Ich erstelle eine Instanz der Klasse Delegate und erhalte einen zur freien Verfügung stehenden Delegate, der dann auf Methoden und/oder Objekte verweist. Prima!« Beispielsweise und in altbekannter Manier: Delegate MeinDelegate = new Delegate();
In den Laboratorien Microsofts (vielleicht sogar auf Anraten von Herrn Hejlsberg selbst – wir wissen es nicht) wurde jedoch ein etwas anderer Weg beschritten. Ersparen wir aber dem, was enthüllt werden soll, den Trommelwirbel. Die Aufgabe der Klasse Delegate ist eine eher indirekte. Betrachtet wird Delegate nämlich nicht als Delegate-Typ, sondern als Klasse zum Ableiten von Dele-
gate-Typen, was bedeutend mehr als ein gradueller Unterschied ist. Delegate ist lediglich die Basisklasse für Delegate-Typen. Nebenbei bemerkt, existiert in der .NET-Bibliothek eine ganze Reihe von Basisklassen. Eine der bekanntesten ist die Klasse Form, mit Sitz im Namensraum System.Windows.Forms. Multicast-Delegate Im Zusammenhang mit Delegates steht die Klasse Delegate nicht allein da, leistet doch MulticastDelegate (die Sie ebenfalls im Namensraum System finden) prinzipiell dasselbe. Allerdings ist MulticastDelegate mächtiger als es Delegate ist, können in der Aufrufliste des Delegate doch gleich mehrere Elemente (auch im Sinne von einzelnen Delegates) vorhanden sein. Das alles klingt, als hätten Sie beim Programmieren die Wahl, entweder Delegate oder MulticastDelegate zu verwenden. Oberflächlich betrachtet ist das auch so. Verwenden Sie allerdings die Klasse MulticastDelegate, sitzt damit auch die Klasse Delegate mit im Boot des Projekts, wird die funktional erweiterte Klasse MulticastDelegate doch von Delegate abgeleitet – Stichwort: Vererbungshierarchie.
37
2.8
2
Die Sache mit .NET
Sie selbst können weder von der Klasse Delegate noch von MulticastDelegate ableiten bzw. eine Instanz erstellen. Das bleibt dem System sowie dem – in unserem Falle – C#-Compiler sowie diversen Tools vorbehalten. In der Deklaration eines Delegate bewirkt das Schlüsselwort delegate, dass der Compiler eine Klasse erzeugt, die von System.Delegate abgeleitet ist. Kommt MulticastDelegate ins Spiel, wird der Compiler durch das Schlüsselwort delgate zur Erzeugung einer von MulticastDelegate abgeleiteten Klasse veranlasst. Auch hier gilt: Dem Entwickler sind quasi die Hände gebunden. Das primär Erforderliche erledigt der Compiler. Ebenfalls ist es Ihnen nicht erlaubt, von einem Delegate-Typ einen neuen Typ abzuleiten (von der Möglichkeit, zu erben, ganz zu schweigen). Auf dieser Ebene kann ein Delegate-Typ nichts anderes als der einmal definierte Delegate-Typ sein. Wie also soll man es machen? Am besten richtig. Vollkommen falsch ist obiges Listing nicht, werden Delegates doch in der Tat wie auch Objekte über den hinlänglich bekannten Operator new erstellt (obwohl es sich bei Delegates streng genommen um Datenstrukturen handelt). Es gibt aber folgenden wichtigen Unterschied: Den Klassennamen Delegate suchen Sie in der Anweisung zur Erzeugung eines Delegate vergebens. Allgemein lautet die Syntax zur Erzeugung eines Delegate: = new (<Methode>);
Auch wenn es Ihnen klar sein dürfte: steht nicht für die Klasse Delegate, sondern für den Typ des Delegate. <Methode> bezeichnet den Namen einer Methode, die – es wurde bereits erwähnt
– der Signatur des Delegate entsprechen sollte. (Umgekehrt kann man es allerdings auch betrachten, d. h., die Signatur des Delegate hat der Signatur jener Methode zu entsprechen, auf die verwiesen werden soll.) Erweitert kann der Methodenname durch den Klassennamen und den Namensraum werden. Schließlich kann die Methode, auf die verwiesen werden soll, Teil einer Klasse sein (Instanzmethode), die sich wiederum in einem ganz anderen Namensraum befindet. Natürlich drängt sich die Frage auf, wofür das Sprachmittel Delegate eigentlich gut sein soll. Kurze Antwort vorweg: für gleich mehreres. Begnügen wir uns im Weiteren jedoch mit dem, was sich wie ein roter Faden durch den Praxisteil des Buches ziehen wird: Ereignisse. Auch für die sind Delegates beinahe unentbehrlich. Im letzten Abschnitt dieser kompakten Theorie-Einführung sollen die Ereignisse ihre Schatten vorauswerfen.
38
Ereignisbehandlung
2.9
Ereignisbehandlung
Sie klicken auf einen Button, auf ein Dropdown-Listenfeld, auf einen ImageButton. Sie fokussieren mit dem Cursor ein Textfeld, setzen einen RadioButton oder eine CheckBox und so weiter. Kurzum: Im Zuge Ihrer Arbeit mit einer Windowsoder Web-Applikation lösen Sie mehr als ein Ereignis (englisch Event) mehrfach aus (oft wird auch davon gesprochen, ein Ereignis zu »feuern«) Vom Auslösen eines Ereignisses leben zumindest benutzerorientierte Programme, weniger jene, die sich beispielsweise über Stunden mit einer numerischen Berechnung beschäftigen. Ereignisse müssen jedoch 왘
abgefangen und
왘
behandelt werden.
Letzter Punkt führt zum Begriff der Ereignisbehandlung, genauer zu einer Ereignisbehandlungsroutine (englisch Event Handler, etwas eingedeutscht Eventhandler). Einen Eventhandler stellen nur ganz bestimmte, Eventsink genannte Klassen zur Verfügung. Im Praxisteil werden Ihnen einige Eventhandler begegnen. Die meisten der abkürzend auch als Handler bezeichneten Ereignisbehandlungsroutinen besitzen folgende Signatur: void Mein_EventHandler(object sender, EventArgs e);
Zwei nicht unbedingt selbsterklärende Argumente werden der Methode Mein_ EventHandler() mitgegeben: 왘
sender ist eine Referenz auf das Objekt, das das Ereignis auslöst. Nicht selten
handelt es sich bei solchen Objekten um Steuerelemente, sprich Controls. Für eine auf Grundlage von .NET entwickelte Applikation (ganz gleich ob, es eine Web- oder Standalone-Applikation ist), sind Controls ungefähr so elementar wie die Farben auf der Palette des Malers. 왘
e bezeichnet eine Referenz auf ein Objekt, das zusätzliche ereignisbeschreibende Parameter enthalten kann.
Damit hätten Sie den anstrengendsten Abschnitt des Buches hinter sich gebracht.
39
2.9
Arbeit tendiert dazu, den zur Verfügung gestellten Zeitraum auszufüllen. (Parkinson)
3
Ausgeschlafen – das Wecker-Tool
Vermutlich kennen Sie das morgendliche »Drama«: Zur viel zu frühen Stunde kreischt oder piept es auf dem Nachtschränkchen. Der Arm wandert einigermaßen unkoordiniert zum vermuteten Standort der ungebetenen Geräuschquelle. Im besten Fall trifft Ihr Finger die Stopp-Taste, im schlechtesten Fall landet das lärmende Ding auf dem Boden – wo es fleißig weitertönt. Das, was Sie einigermaßen unsanft Morpheus‘ Reich entrissen hat, wird ein mechanischer Wecker oder ein Radiowecker sein, vielleicht auch nur die Weckfunktion des allgegenwärtigen Handys oder schlicht Ihre digitale Armbanduhr (die wir noch genauer unter die Lupe nehmen werden). Ein Notebook war es mit einiger Sicherheit nicht, von einem Desktop-PC ganz zu schweigen. Der steht natürlich auch bei Ihnen auf dem Schreibtisch – der wiederum seinen Platz nicht unbedingt im Schlafzimmer hat. Doch ehrlich gestanden: Wer von uns »PC-Getreuen« hat nicht schon mal ein Nickerchen vor dem Monitor gehalten? Ich selbst bevorzugt als Student, wenn morgens um drei der verd... Debugger immer noch Fehlermeldungen spie wie der kleine Grisu das Feuer. Nun ja, dann wurde die Tastatur halt dezent zur Seite geschoben und der rauchig müde Kopf in die umständlich auf der Schreibtischplatte verschränkten Arme gelegt. Geholfen hat es nur selten (am wenigsten dem Denkvermögen), eher war die Aufwachprozedur eine auch körperlich schmerzhafte Angelegenheit, was nicht hätte sein müssen, wenn, ja wenn mich der Computer zuvor geweckt hätte. Schaffen wir für den Fall vergleichbarer Situationen Abhilfe, indem wir ein Wecker-Tool entwickeln. Abbildung 3.1 zeigt Ihnen, wie die Eingabe- und Anzeigemaske aussehen sollen. Darüber hinaus kann Ihnen das kleine Tool wertvolle Dienste beim zyklischen Lernen oder Arbeiten (als wäre Lernen nicht auch Arbeit) leisten: Stellen Sie einfach unter Einstellen der Weckzeit die Dauer der beabsichtigten »mentalen Betriebsamkeit« ein, klicken Sie den Button Stellen an, und danach geht – was auch immer – los. 41
3
Ausgeschlafen – das Wecker-Tool
Abbildung 3.1
Eingabe- und Anzeigemaske des Wecker-Tools
Wurde das Intervall im Wortsinne abgearbeitet, ist es Zeit für eine wohlverdiente Pause, nach deren Ende der Wecker erneut gestellt werden kann und so weiter. Primär geht es beim Wecker-Tool um Zeit. (Wann geht es nicht darum?) Die natürlich digitale Anzeige derselben, zumeist in der rechten unteren Ecke des Monitors (auf der Windows-eigenen Startleiste) ist für Sie wie auch für mich vollkommen selbstverständlich. Gleichwohl wird es Sie vielleicht interessieren, wie der Computer zur Darstellung der Zeit kommt, seit wann es das Feature gibt und wie es ganz früher um das Verhältnis zwischen Computer und Zeit bestellt war. Werfen wir zunächst also einen möglichst neugierigen Blick in die Vergangenheit. Lange werden wir in der allerdings nicht bleiben können.
3.1
Als der Computer die Zeit entdeckte
Geht man 387 Jahre zurück, müssten die zwei Hauptwörter in der Überschrift eigentlich ihre Plätze tauschen. Entdeckt hat nämlich nicht der Computer die Zeit, sondern eher die Zeit den Computer. Zumindest dann, wenn wir uns die legitime Freiheit erlauben, den Begriff »Computer« zunächst von seiner Fähigkeit abzukoppeln, Berechnungen auf Grundlage des binären Zahlensystems durchzuführen. Mehr als eine knackige Kaffeebeilage – Leibniz 1703 erfand der Leipziger Philosoph, Mathematiker, Physiker, Historiker, Politiker, Diplomat und Bibliothekar Gottfried Wilhelm Leibniz (1646–1716) die Darstellung von Zahlen im Dualsystem, das zur Grundlage der »digitalen Revolution« wurde.
42
Als der Computer die Zeit entdeckte
Historisch betrachtet, ist der Begriff »digitaler Computer« ebenso wenig ein »weißer Schimmel«, wie die Formulierung »analoger Computer« Unsinn ist. Anderenfalls würde der Astronom, Geograf und Orientalist Wilhelm Schickard (1592– 1635) nicht als der »Vater der Computerära« gelten. Um dazu zu werden, brauchte Schickard im Jahre 1623 im Wesentlichen lediglich diverse aus dem Uhrmacherhandwerk entliehene Zahnräder, die, intelligent ineinandergreifend, schließlich zu dem wurden, was als »rechnende Uhr« in die Geschichte technischer Erfindungen eingehen sollte. Die Maschine beherrschte zwar in der »Grundausstattung« nicht einmal das kleine Einmaleins, immerhin jedoch zwei Grundrechenarten, nämlich Addition und Subtraktion – und das sogar im sechsstelligen Bereich. Im Falle komplexerer Aufgaben wurde das Gerät um Napier‘sche Rechenstäbchen ergänzt (die Sie sich getrost als miniaturisierte Stäbe zur Anzeige des Wasserstandes vorstellen können). War plötzlich eine Glocke zu hören, dann weniger, um den Meister an den Mittagstisch zu locken, als um auf den Speicherüberlauf aufmerksam zu machen. Auch den gab es in der »rechnenden Uhr« bereits. Mehr über Schickard und sein beachtenswertes Werk erfahren Sie unter http://www.iser-uni-erlangen.de/aktuelles/ Schickard/index.html. Schauen Sie mal vorbei. Es würde ihn vielleicht gefreut haben. Ein Herr namens Kepler Schickards Erfindung gilt als der erste mechanische Rechner der Neuzeit – dem keineswegs die praktische Nutzanwendung versagt blieb. Ich bin zwar kein Wissenschaftshistoriker, allerdings physikalisch vorbelastet, weshalb ich gerne die Frage stelle, welche physikalischen Gesetze der (auch) Astronom Johannes Kepler (1571–1630) ohne die »Vier-Spezies-Maschine« (Gattungsbegriff für mechanische Rechner) hinterlassen hätte. Die hat er nämlich für seine astronomischen Berechnungen verwendet. Anscheinend mit Erfolg, können sich seither doch Generationen von Schülern der Oberstufe und Physikstudenten an den keplerschen Gesetzen (besonders dem zweiten und dritten) erfreuen – oder auch nicht ...
In den frühen 1980iger-Jahren – wir haben einen riesigen Schritt durch die Geschichte der rechnenden Maschinen gemacht – sah die Welt der Computer beträchtlich anders aus. Auch weil dank in Serie gefertigter (somit endlich erschwinglicher) Mikroprozessoren, vor allem aber durch die Arbeit des amerikanischen Elektronikingenieurs Ed Roberts (als Erfinder des ersten Tischrechners, des Altair 880; Roberts war zudem einer der ersten Arbeitgeber eines gewissen Bill Gates), der Computer zum beinahe profanen Allerweltsgerät mit eigenem Kürzel (PC), jedoch ohne integrierte Uhr geworden war. Das Manko mit der Uhr wurde im Jahre 1986 von Big Blue, sprich IBM (für International Business Machines), höchstselbst durch die Einführung des AT-Systems
43
3.1
3
Ausgeschlafen – das Wecker-Tool
korrigiert, wobei sich die Advanced Technology (AT) in weit mehr als dem Vorhandensein einer Uhr ausdrückte (es gab höhere Rechenleistung, höhere Festplattenund Diskettenkapazität etc.). Immerhin gibt es seitdem eine Zeitanzeige im PC, batteriegestützt und als Teil der Hauptplatine – ein überlebensfähiges Konzept, wie sich schnell zeigen sollte. Was es beim guten alten AT nicht gab, war ein grafisches Betriebssystem à la Windows (oder Linux oder OS2 oder, oder ...). Und damit setzen wir zum letzten Schritt hin zum modernen Personal Computer an. Erinnern Sie sich an den Begriff Echtzeit? Diese wurde im Umfeld des Garbage Collectors bereits erwähnt (als es darum ging, dass die Garbage Collection als automatische Speicherbereinigung für Echtzeitsysteme nicht unbedingt geeignet ist). Hier begegnet uns das Wort abermals, denn die in das Mainboard, also in die Hauptplatine des Personal Computers, integrierte Uhr (man spricht auch von einer Hardware-Uhr) firmiert unter dem Namen Echtzeit-Uhr, im Englischen Real Time Clock oder – bestens abgekürzt – RTC. Das »Echtzeitige« der Uhr drückt sich in dem aus, was sie misst: die physikalische Zeit nämlich – in strenger Abgrenzung zur logischen Zeit, bei der es vereinfacht gesagt um ein spezielles, auf beispielsweise verteilte Systeme abgestimmtes Zeitmodell geht, in dem stur (wissenschaftlich: monoton) »den Berg herauf« gezählt wird. Ganz gleich ob Rolex oder No-Name: Ein prinzipieller Unterschied zwischen einer digitalen Armbanduhr und einer Hardware-Uhr besteht nicht. Auch beim Timer-Chip der Hauptplatine existieren Taktgeber und Zähler, wobei der Zähler bei jedem Takt des Frequenzgebers erhöht wird. Die Bauart des Zählers ist nicht unabhängig von der des Taktgebers. Die einfache Formel könnte ungefähr so lauten: Je willkürlicher die Frequenz des Taktgebers gewählt ist, desto aufwendiger muss der Zähler gefertigt sein – was es selbstredend zu vermeiden gilt. Nach dem Anschalten des PCs übernimmt das BIOS (Basic Input/Output System), eine Art hardwarenahes Minimalprogramm, von der RTC die Uhrzeit (und das Datum). Beides wird dem Betriebssystem zur Verfügung gestellt, das seinerseits die Informationen selbstständig weiterschreibt. Ergo ist das, was Sie auf der Windows-Startleiste gelegentlich in Augenschein nehmen, nicht die von der RTC generierte Systemzeit, sondern eine kleine, wenngleich angenehme Zugabe des Betriebssystems. Besagte Zugabe wird als Software-Uhr bezeichnet, die lediglich aus einem Zähler besteht. Das Taktsignal erhält die Software-Uhr als Folge eines regelmäßigen Interrupts. Daraufhin wird vom Betriebssystem eine Interrupt-Routine ausgeführt,
44
Die Entwicklung der Bedienoberfläche
in der sich nicht nur das Weiterstellen der Software-Uhr erledigt, sondern beispielsweise auch das Prozess-Scheduling, also die Zeitplanerstellung für das Betriebssystem. Software-Uhr und Hardware-Uhr werden beim Booten des PCs über das bereits erwähnte BIOS abgeglichen, während beim Herunterfahren zuweilen ein Zurücksetzen der Hardware-Uhr auf den Wert der Software-Uhr erfolgt (übrigens mitunter auch dann, wenn es nicht unbedingt angebracht ist).
3.2
Die Entwicklung der Bedienoberfläche
Wie möchten Sie die praktische Arbeit beginnen? Mein Vorschlag: Beginnen wir mit der Festlegung der Anforderungen. Daraus soll aber kein langweiliges, die Aktualität rasch verlierendes Pflichtenheft resultieren, wohl aber Klarheit darüber, was realisiert werden soll (Abbildung 3.2 lässt ja bereits einiges vermuten). Folgendes müssen wir nun umsetzen: 왘
ein Hauptmenü (einziger Eintrag: Anwendung) 왘
왘
mit einem Submenü, dessen ebenfalls einziger Eintrag Beenden lautet
ein virtuelles Display (für das sich getrost auch »schwarzes Rechteck« schreiben ließe). Auf dem soll Folgendes angezeigt werden: 왘
Stunden, Minuten, Sekunden, im Format (Beispiel): 18:14:28.
왘
ein kurzer, prägnanter Satz, dessen Kernaussage die eingestellte Weckzeit ist. Beispiel: Sie werden um 18:30 geweckt! (siehe Abbildung 3.2).
Abbildung 3.2
Display-Anzeige, nachdem die Uhrzeit eingestellt wurde
45
3.2
3
Ausgeschlafen – das Wecker-Tool
왘
ein ebenso kurzer wie prägnanter Satz, dessen zentrale Aussage die ist, dass das Schläfchen für Sie vorbei ist; Beispiel: Die Pflicht ruft ... (siehe Abbildung 3.3).
Abbildung 3.3 왘
Display-Anzeige, nachdem die Weckzeit erreicht ist und das Wecksignal ertönt
einen Kalender, in dem Sie den Tag auswählen können, an dem Sie wünschen, geweckt zu werden. Der Kalender macht Sinn, denn stellen Sie sich vor, Sie überkommt um 23:55 Uhr die Müdigkeit (siehe Abbildung 3.4).
Abbildung 3.4
Aufgeklappter Kalender des Wecker-Tools
왘
einen Radiobutton. Genauer sind es deren zwei, gedacht zur Auswahl der Weckmelodie. Eine kleine Überschrift (Auswahl der Melodie) soll die Einstellungsmöglichkeiten einleiten.
왘
eine Schaltfläche (Stellen) zur Aktivierung des »Software-Weckers«. Das Einstellen der Weckzeit erfolgt getrennt nach Stunden und Minuten. Ein auf die Sekunde genaues Wecken erschien mir übertrieben. Ihnen steht es natürlich
46
Die Entwicklung der Bedienoberfläche
frei, das Wecker-Tool (auch) dahingehend zu erweitern. Auch hier soll eine kleine Überschrift (Einstellen der Weckzeit) die Möglichkeit zur Einstellung einleiten. 왘
eine Schaltfläche (Stop) zur Unterbrechung des Wecktons. Wird der Button betätigt, soll der Begleittext auf dem virtuellen Display ausgeblendet werden.
Abbildung 3.5 Warnmeldung in dem Fall, dass die eingestellte Weckzeit vor der Systemzeit liegt
Falls Sie versehentlich Ihr Wecker-Tool auf eine Zeit einstellen, die vor der aktuellen liegt, soll sich auch noch ein kleines Fenster mit der Meldung Möchten Sie tatsächlich in der Vergangenheit geweckt werden? öffnen. Zunächst allerdings gilt es etwas anderes einzustellen: ein neues Projekt nämlich. Spätestens an dieser Stelle sollten Sie die Entwicklungsumgebung Visual C# 2010 Express Edition geöffnet haben.
3.2.1
Anlegen eines neuen Projekts
Zu der in Abbildung 3.6 gezeigten Dialogmaske Neues Projekt gelangen Sie über Hauptmenü Datei 폷 Neues Projekt. Im Editorfeld Name geben Sie – wenn Sie mögen – Wecker_Tool ein. Wählen Sie aber vorher unter den verschiedenen Standardanwendungstypen Windows Forms-Anwendung aus! Nachdem Sie auf den Button Ok geklickt haben, wird einige Sekunden später (unter möglicherweise einigem Rumoren der Festplatte, je nachdem, wie viel Arbeitsspeicher es in Ihrem Computer gibt) das neue Projekt erstellt und unter anderem die Datei Form1.cs im Entwurf-Modus geöffnet.
47
3.2
3
Ausgeschlafen – das Wecker-Tool
Abbildung 3.6 Die Dialogmaske »Neues Projekt« zum Anlegen eines neuen Projekts in der Visual C# 2010 Express Edition
Auch wenn Ihnen das programmiertechnische Vorerzeugnis der Entwicklungsumgebung grundlegend bekannt ist, gestatten Sie mir dennoch einen »Abriss« dessen, was Sie beim Öffnen der Datei Form1.cs im Quelle-Modus erwartet. Gemessen an der Zahl der generierten Zeilen ist das zwar nicht viel, gleichwohl von elementarer Wichtigkeit. using using using using using using using using
System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms;
namespace Wecker_Tool { public partial class Form1 : Form { public Form1()
48
Die Entwicklung der Bedienoberfläche
{ InitializeComponent(); } } } Listing 3.1
Vorerzeugter Quellcode beim Erstellen des Projekts »Wecker_Tool«
Die ersten sechs Zeilen im obigen Listing bestehen aus using-Direktiven zur Einbindung grundlegender Namensräume (auf die ich später noch genauer eingehen werde). Im Block des Namensraums Wecker_Tool (der identisch mit dem Projekttitel ist) erwartet Sie sodann die öffentliche (public) Klasse Form1, ein Erbe der »Formular-Ur-Klasse« Form, als Grundlage jedweder WindowsForms-Anwendung. Eine Klasse für mehrere Teile Über das Schlüsselwort partial wurde die Deklaration der Klasse Form1 standardmäßig so realisiert, dass verschiedene Teile der Klassenimplementierung in verschiedenen Modulen oder Quelldateien der Anwendung untergebracht werden können. Warum ein solches Vorgehen mitunter sinnvoll ist? Die Erklärung würde – Sie lieben die Formulierung sicher genauso wie ich – »an dieser Stelle zu weit führen«. Wichtiger ist auch, wie es mit den partiellen Typdeklarationen weitergeht: Die einzelnen Dateien bzw. Module werden kompiliert, um die kombinierte Typimplementierung erstellen zu können. Das funktioniert wirklich, sogar mit Strukturen. Versuchen Sie dagegen, beispielsweise ein Interface mit partial zu deklarieren, ernten Sie böses »Compiler-Gegrummel«. An dem Punkt hört der Spaß nämlich auf – zumindest für den Compiler.
Weiter im Listing-Text! Als Nächstes sehen Sie die öffentliche Methode Form1(). Zu der gibt es kaum etwas anzumerken, zum Inhalt des Methodenrumpfes dagegen einiges. In ihm wird die Methode InitializeCompent() aufgerufen. Der Name ist bedingt selbsterklärend, verschweigt allerdings geflissentlich, was der Methode im Verlauf des Projekts aufgebürdet wird. Ein Einblick gefällig? Dann öffnen Sie die Datei Form1.Designer.cs durch einen Doppelklick auf den Dateinamen im Projektmappen-Explorer. Der folgende, zunächst recht übersichtliche Code dürfte Sie erwarten: private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Text = "Form1"; } Listing 3.2
Elementare Festlegungen in der Methode »InitializeComponent()«
49
3.2
3
Ausgeschlafen – das Wecker-Tool
Schnell ist es jedoch um die Übersichtlichkeit geschehen. Denn was immer Sie via Drag&Drop aus der Toolbox auf die durch Form1 dargestellte Oberfläche ziehen – initialisiert und von den Eigenschaften her belegt werden die Steuerelemente im Rumpf der Methode InitializeComponent(), was geradezu nach manuellen Eingriffen schreit. Bitte tun Sie das nicht! Wenn Sie die Initialisierungsmethode im Editor der Entwicklungsumgebung ändern, kann das gut gehen. Geht es schief, »verlieren« Sie im schlechtesten Falle sämtliche Controls – und womöglich die Nerven. Änderung des Klassennamens Bevor wir weitermachen: Ändern Sie bitte den Namen der Datei Form1.cs in cTime.cs. Ein Muss ist das zwar nicht, wohl aber ein sinnvolles Kann. Unser Formelement sollte zumindest auf das hinweisen, was später auf ihm dargestellt wird (es geht uns um Zeit). Wenn Ihnen der Name nicht gefällt, können Sie natürlich auch einen anderen wählen. Sie ändern ist den Dateinamen über 왘
einen Klick auf die Datei Form1.cs im Projektmappen-Explorer,
왘
das Drücken der rechten Maustaste und
왘
die anschließende Wahl der Option Umbenennen (im Kontextmenü).
Genauso wie beim »altehrwürdigen« Windows Explorer können Sie nun den neuen Namen editieren, womit es dann ... leider nicht getan wäre. Hat sich doch ein Fenster geöffnet, in dem Sie gefragt werden: »Möchten Sie auch alle Verweise auf das Codeelement Form1 umbenennen?« – Ja, das wollen Sie.
3.2.2
Die benötigten Steuerelemente
Abgesehen von der Nachrichtenbox, die mithilfe der Klasse MessageBox (Namensraum System.Windows.Form) realisiert wird (und die kein klassisches Steuerelement darstellt), brauchen Sie für die Realisation der Oberfläche zunächst eine Handvoll Controls. Diese werden Ihnen jetzt nicht in epischer Breite nahegebracht, wohl aber in Form von Tabelle 3.1. Bezeichnung des Steuerelements
Anzahl
Namen
MenuStrip
1
menuStrip1
Panel
3
displayPanel* panel1 panel2
Tabelle 3.1
50
Zur Entwicklung der Anzeige- und Bedienoberfläche erforderliche Steuerelemente
Die Entwicklung der Bedienoberfläche
Bezeichnung des Steuerelements
Anzahl
Namen
DateTimePicker
1
dateTimePicker1
Label
5
label1 label2 label3 message_label* static_clocklabel*
RadioButton
2
melodie1_radioButton* melodie2_radioButton*
Button
2
stellen_Button* stop_Button*
NumericUpDown
2
hours_numericUpDown* minutes_numericUpDown*
Tabelle 3.1
Zur Entwicklung der Anzeige- und Bedienoberfläche erforderliche Steuerelemente
Die Bezeichnung der Steuerelemente finden Sie in der Toolbox der Entwicklungsumgebung (Registerkarte Standard) wieder. Jedem im Projekt verwendeten Control wird vom Entwicklungssystem unter der Eigenschaft Name ein Default-Wert zugewiesen, der sich meist aus dem Namen des Controls, ergänzt um eine Ziffer, zusammensetzt. Beispielsweise heißen sie label1, label2, label3. Demnach geschieht hier nichts weiter als eine langweilige Nummerierung. Den Default-Wert können Sie beibehalten, Sie können ihn aber auch über das Eigenschaften-Fenster ändern. Das habe ich – teilweise – getan: In der Tabelle sind in der Spalte Namen jene Einträge »made by author«, die nicht mit einer Ziffer enden. Beispiel: minutes_numericUpDown. Zusätzlich wurden die Namen noch mit einem Sternchen markiert (das natürlich weder im Eigenschaften-Fenster noch im Programmcode auftauchen wird – eine Fehlermeldung weniger). Abbildung 3.7 zeigt Ihnen die Anordnung der benötigten Steuerelemente in der Entwurfsansicht. Zählen Sie die abgebildeten Controls ruhig ab, um sie mit der Summe der Steuerelemente in der Tabelle zu vergleichen. Sie stimmt eh nicht überein – aus folgendem Grund: Der Fokus in Abbildung 3.7 steht auf dem displayTime-Control. Hinter dem verbergen sich noch – unsichtbar – static_clocklabel und message_label. static_clocklabel realisiert auf GUI-Ebene (Graphical User Interface) die Darstellung der Uhrzeit, message_label die Anzeige der kleinen Begleittexte (Die Pflicht ruft!).
51
3.2
3
Ausgeschlafen – das Wecker-Tool
Abbildung 3.7
Oberfläche des Wecker-Tools in der Entwurfsansicht
Nachdem Sie die Controls auf dem Formular angeordnet und die Eigenschaften über das Eigenschaften-Fenster mit den entsprechenden Werten versehen haben, geht es ans Programmieren.
3.2.3
Ein EventHandler für das Beenden der Anwendung ...
... sollte wohl eher am Ende der Programmierung stehen. Das tut er aber nicht, weil das Theoriekapitel mit einigen einführenden Bemerkungen über Ereignisbehandlung abgeschlossen wurde. Zwischenzeitlich bei der Programmierung der Logik angekommen, machen wir beim Eventhandling einfach weiter. Abgesehen davon, brauchen wir bereits jetzt, wo noch keine einzige zeitliche Aspekte betreffende Codezeile geschrieben ist, eine Ereignisbehandlungsroutine. Die soll dafür sorgen, dass Ihre Anwendung »sauber«, also über den Submenüeintrag Beenden, zu schließen ist. Die Codedatei cTime.cs um eine solche Methode zu ergänzen, ist eine wahre Herkules-Aufgabe: Sie rufen cTime.cs im Entwurf-Modus auf, klicken zweimal im menuStrip1-Control auf den Eintrag Beenden und ... nichts weiter: Wie von Geisterhand öffnet sich cTime.cs, ergänzt um den gewünschten Eventhandler. In diesem entdecken Sie die Argumente e und sender wieder, leider jedoch keine Logik, die das eigentliche Schließen der Anwendung übernimmt. Was der Eventhandler darstellt, ist lediglich ein Rahmen, dessen Inhalt als Konsequenz eines Ereignisses auszuführen ist. Hier kommt uns Close() als Member der »Königsklasse« Form (durch die ein Formular als Grundlage einer Benutzeroberfläche erst dar-
52
Die Entwicklung der Bedienoberfläche
stellbar wird – nicht vergessen!) wie gerufen. Der Aufruf der Methode (im Eventhandler beendenToolStripMenuItem_Click()) bewirkt das Schließen des Formulars – hoffentlich. Nein, ganz gewiss, doch sollten wir uns durch ein Voranstellen des C#-Schlüsselwortes this absichern, verweist das beliebte Keyword doch auf die aktuell verwendete Instanz (der Klasse Form). Somit schreiben Sie: private void beendenToolStripMenuItem_ Click(object sender, EventArgs e) { this.Close(); }
3.2.4
Implementierung und Test der Zeitanzeige
Kein Wecker ohne Anzeige der Uhrzeit. Ihr Wecker-Tool bildet da natürlich keine Ausnahme. Wie das Betriebssystem an die Uhrzeit kommt, konnten Sie einige Seiten zuvor lesen. Nun sollte auch die Anwendung »Wecker-Tool« an Informationen zur Zeit kommen. Mit einer Klasse? Leider nicht. Eher eine Struktur führt uns zum Ziel, namentlich die DateTime-Struktur (im Namensraum System), die schon vom Namen her kein Geheimnis aus ihrer Aufgabe macht. Die Struktur selbst ist einigermaßen nutzlos, nicht aber ihre vornehmste Eigenschaft Now, die ein Objekt der Struktur abruft. Dieses Objekt ist auf Datum und Zeit des Betriebssystems, somit auf die Software-Uhr, festgelegt. Das ist gut, wenngleich für sich genommen zumindest hier nicht zielführend, fehlt doch eine weitere Eigenschaft, die beispielsweise die Stunde liefert. Die Eigenschaft heißt auch genauso wie das, was ermittelt werden soll: Hour. Eine Struktur und zwei Eigenschaften, verknüpft mit zwei Punktoperatoren, und das Ganze sieht dann folgendermaßen aus: DateTime.Now.Hour
Analog die Minuten: DateTime.Now.Minutes
Und schließlich die Sekunden: DateTime.Now.Second
Der Typ der von den Eigenschaften Hour, Minutes und Second bereitgestellten Informationen ist für die Übergabe an ein Label-Control eher ungeeignet. Hinzu kommt das für die Darstellung der Zeit definierte Format (siehe Abschnitt 3.2). Die Typ- und Formatfrage beantworten wir in einer längeren Codezeile: string dtime = Convert.ToString(DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second);
53
3.2
3
Ausgeschlafen – das Wecker-Tool
Was sich in diesem Einzeiler abspielt, dürfte Ihnen vermutlich klar sein. Falls nicht, ist hier die Erklärung: Die Klasse Convert (aus dem Namensraum System) konvertiert einen Basistyp in einen anderen Basistyp, der in unserem Falle string heißt. Dabei hilft der Klasse ihre Methode ToString(), deren Argument aus der Verknüpfung von Stunden-, Minuten- und Sekundenanzeige einschließlich zweier Doppelpunkte (das ist unser Zeitformat) besteht. Sie sehen das »Monster« von Argument hier nochmals explizit hingeschrieben: DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now. Second
Nun haben wir eine gebrauchsfähige Zeitanzeige – mit der wir nicht wissen, wohin. Die in dtime gespeicherte Zeitanzeige übergeben wir dem static_clocklabel genannten Label-Control zur Anzeige. Das funktioniert nur unter Einbeziehung der Label-Eigenschaft text, durch die die Text-Eigenschaft des LabelSteuerelements festgelegt oder abgerufen wird. Wie sieht das in strenger C#-Notation aus? So: static_clocklabel.Text = dtime;
Damit wäre die digitale Zeitanzeige implementiert. Hier sehen Sie noch einmal den vollständigen (Mini-)Code: string dtime = Convert.ToString(DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second); static_clocklabel.Text = dtime; Listing 3.3 Implementierung der digitalen Zeitanzeige über die »DateTime«-Struktur sowie das »static_clocklabel«
Testen können Sie die Zeitanzeige natürlich auch. Dazu gibt es zwei Möglichkeiten: 왘
Sie »packen« obigen Zweizeiler in den Rumpf der öffentlichen Methode cTime().
왘
Sie generieren den EventHandler cTime_Load_1().
Es wäre schön, wenn Sie sich für die zweite Möglichkeit entscheiden würden, cTime_Load() benötigen wir nämlich eh. Den Handler zu erzeugen ist darüber hi-
naus eine Angelegenheit von Sekunden: Klicken Sie zweimal im Entwurf-Modus der Datei cTime.cs auf das Formular (jedoch bitte nicht auf ein Control!), und die Methode wird erzeugt. In diese »packen« Sie bitte (vorübergehend!) den besprochenen Zweizeiler zur Zeitanzeige: private void cTime_Load(object sender, EventArgs e) {
54
Die Entwicklung der Bedienoberfläche
string dtime = Convert.ToString(DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second); static_clocklabel.Text = dtime; } Listing 3.4 Load()«
Bereitstellung der Zuweisungen zur Zeitanzeige im EventHandler »cTime_
Starten Sie die Anwendung mit (Shortcut (F5)) oder ohne Debugging ((Strg)+(F5)). Beim erstmaligen Laden wird zuerst alles ausgeführt, was sich im Rumpf des Eventhandlers cTime_Load() befindet. Versuchen Sie es! Das virtuelle Display des Wecker-Tools dürfte nicht mehr dunkel bleiben. Die Sache hat jedoch einen Haken.
3.2.5
Von der Zeitanzeige zur Implementierung einer Uhr
Berechtigterweise wird sich Ihre Begeisterung in Grenzen halten, hat sich die Zeitanzeige doch tatsächlich als leider einmalige Angelegenheit entpuppt. Man könnte auch schreiben: Die Uhr steht. Hier einen Ansatz zu finden, ist leichter als dessen Umsetzung in einem Programm. Sie müssen erreichen, dass das, was Sie zu Testzwecken im Rumpf des Eventhandlers cTime_Load()untergebracht haben, im Sekundentakt angestoßen, sprich, zur Ausführung gebracht wird. Dazu brauchen Sie eine Klasse, die den Takt angibt. Genauer gesagt muss diese Klasse einen Mechanismus bereitstellen, der eine Methode im Sekundenintervall ausführt. Ein Besuch im Namensraum System.Threading lässt Sie schnell fündig werden: Dort residiert die Klasse Timer, von der natürlich zunächst eine Instanz gebildet werden muss: static Timer static_clockTimer = new Timer();
In welchem Intervall (ausgedrückt in Millisekunden) die noch unbekannte Methode ausgeführt wird, legen Sie mit der Eigenschaft Interval der Klasse Timer fest: static_clockTimer.Interval = 1000;
Jetzt ist es Zeit für ein Ereignis, das dann eintritt, wenn die angegebene Anzahl von Millisekunden erreicht ist: static_clockTimer.Tick += new EventHandler(static_clockTimer_Check);
Danach schlägt die Sekunde der Methode static_clockTimer_Check(), die – das soll bereits hier verraten werden – nichts anderes enthält als das, was Sie wenige
55
3.2
3
Ausgeschlafen – das Wecker-Tool
Abschnitte zuvor zum Zwecke des Testens in den Rumpf des Eventhandlers cTime_Load()eingefügt haben: private void static_clockTimer_Check(object sender, EventArgs e) { string dtime = Convert.ToString(DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second); static_clocklabel.Text = dtime; } Listing 3.5 So stellen Sie die Zuweisungen zur Zeitanzeige im Eventhandler »static_ clockTimer_Check()« bereit.
Jetzt fehlt uns noch eine Art »Zündschlüssel« für den static_clockTimer, den wir in der Methode Start() der Timer-Klasse finden. Demnach: static_clockTimer.Start();
Somit haben wir die erforderlichen Codefragmente beisammen: static Timer static_clockTimer = new Timer(); private void cTime_Load(object sender, EventArgs e) { static_clockTimer.Interval = 1000; static_clockTimer.Tick += new EventHandler(static_clockTimer_Check); static_clockTimer.Start(); } private void static_clockTimer_Check(object sender, EventArgs e) { string dtime = Convert.ToString(DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second); static_clocklabel.Text = dtime; } Listing 3.6 Implementierung einer funktionsfähigen Uhr unter Einbeziehung einer Instanz der »Timer«-Klasse (»static_clockTimer«)
56
Die Entwicklung der Bedienoberfläche
3.2.6
Zahlen für das »NumericUpDown«-Control
Vom Auf-Ab-Steuerelement (auch mit Windows-Drehfeld ließe sich NumericUpDown-Control übersetzen – sogar laut Microsoft) existieren auf der Oberfläche zwei: eines für die Auswahl der Stunden (hours_numericUpDown), das andere für die Minuten (minutes_numericUpDown). Per Default ist der minimale Wert der Controls auf 0, der maximale auf 100 festgelegt. Das können Sie nicht nur ändern, das sollten Sie auch, denn der Tag hat bekanntlich 24 Stunden (was so nicht ganz stimmt) und die Minute bekanntlich 60 Sekunden. Zwei Möglichkeiten zur Änderung stehen zur Wahl: 왘
im Eigenschaften-Fenster über die Festlegung der Werte für die Eigenschaften Minimum und Maximum
왘
per Programmierung
Programmieren möchten Sie mithilfe dieses Buches, also werden wir die Werte besagter Eigenschaften »manuell« im Code festlegen. Die Eigenschaften Minimum und Maximum sind in erster Linie Member der Klasse NumericUpDown (Namensraum: Systems.Windows.Forms), was sogleich den Punkto-
perator als Verknüpfung zwischen Objektname und Eigenschaft auf den Plan ruft. Fangen wir mit hours_numericUpDown an. Die Stunden werden von 0 bis 23 heraufgezählt, und dekrementiert wird von 23 bis 0: hours_numericUpDown.Maximum = 23; hours_numericUpDown.Minimum = 0;
Für minutes_numericUpDown wird ein Wertebereich von 0 bis 59 gewählt: minutes_numericUpDown.Maximum = 59; minutes_numericUpDown.Minimum = 0;
Derart codiert, zeigen beide Windows-Drehfelder bei Ausführung des Programms 0 an. Obgleich es nicht zwingend ist (schließlich lassen sich nun die zur Weckzeitfestlegung nötigen Werte einstellen, ohne dass Sie sich durch unnötige klicken müssen), können wir das besser. Indem wir die Eigenschaft Value nutzen, wird im Drehfeld ein Wert abgerufen, der beim Start des Programms anstelle der unspektakulären 0 zu sehen ist. Es fragt sich bloß, welcher das ist! Genau genommen sind es zwei, nämlich: Stunde und Minute – im Sinne einer einmaligen Uhrzeit, die Ihnen in den Auf-Ab-Steuerelementen angezeigt wird. Gut, es war kein glücklicher, aber den Fall hatten wir bereits (siehe Abschnitt 3.2.4 – die »stehende Uhr«). Hier genügt uns DateTime.Now.Hour bzw. DateTime.Now.Minute für das, was der Eigenschaft Value zuzuweisen wäre:
57
3.2
3
Ausgeschlafen – das Wecker-Tool
hours_numericUpDown.Value = DateTime.Now.Hour; minutes_numericUpDown.Value = DateTime.Now.Minute;
Unterbringen ließen sich die sechs Zeilen Code in der Methode cTime(). Die Reihenfolge der Einzeiler können Sie frei wählen. Mein Vorschlag wäre: public cTime() { InitializeComponent(); hours_numericUpDown.Value = DateTime.Now.Hour; hours_numericUpDown.Maximum = 23; hours_numericUpDown.Minimum = 0; minutes_numericUpDown.Value = DateTime.Now.Minute; minutes_numericUpDown.Maximum = 59; minutes_numericUpDown.Minimum = 0; } Listing 3.7
3.2.7
Intervall- und Zeitbelegung der beiden Windows-Drehfelder
Mehr Lärm als Melodie – »tada« und »ir«
Nachdem ich die circa vierzig WAV-Dateien unter C:\WINDOWS\Media in Augenschein genommen hatte, verging mir schnell die Lust, nach zwei geeigneten Klängen für das Wecker-Tool zu suchen – möglichst schrecklich sollte es allerdings schon klingen. Auch deshalb wurde die Entscheidung schnell getroffen: tada.wav und ir_inter.wav. Das sind also die »Melodien«, die Ihnen zugemutet werden. In einer Endlosschleife abgespielt, hört sich tada.wav ein wenig an wie die musikalische Untermalung einer sich unaufhörlich auf die Erde zu bewegenden Sonne. Apokalypse in Zeiten von .NET. Und was ist mit ir_inter.wav? Solange sind die Zeiten noch nicht her, als sich am Ende des vinylhaltigen Tonträgers die Diamantnadel in einer der Rillen verfing. Sollte in dieser Rille ein pausbackiger Bläser eigentlich abschließend noch einmal in sein Horn stoßen, so tut er es bei ir_ inter.wav gleich mehrfach. Auch davon sollten Sie wach werden. Zu den Melodien gäbe es Alternativen, zum WAV-Format dagegen nicht, denn die für die Wiedergabe zuständige Klasse SoundPlayer versteht nichts anderes. Sie finden sie im Namensraum System.Media – den das Projekt noch nicht kennt. Exakter formuliert, geht es darum, die im Namensraum System.Media vorhandenen Typen (in unserem Falle eben jene Klasse SoundPlayer) für das Projekt Wecker-Tool zuzulassen. Anderenfalls »droht« Qualifizierung (an sich nichts Schlechtes), was kurz gesagt bedeutet, dass dem C#-Compiler deutlich gemacht werden muss, dass diese und keine andere Klasse gemeint ist.
58
Die Entwicklung der Bedienoberfläche
Als Sie das Projekt angelegt haben, konnten Sie im Kopf der Datei Form1.cs acht using-Direktiven entdecken, mit denen die Zulassung acht elementarer Namensräume bewirkt wird – zu denen System.Media leider nicht zählt. Das ist Pech, wenngleich kein Drama und darüber hinaus schnell behoben: Ergänzen Sie einfach in der Datei cTime.cs die Liste der using-Direktiven um eine weitere und letzte, nämlich: using System.Media;
Fertig! Übrigens: Im übertragenen Sinne fällt System.Media eher in die Kategorie »mittelprächtige Studentenbude«, existieren im Raum neben der SoundPlayerKlasse doch lediglich noch die Klassen SystemSound und System.Sounds. Zwei Instanzen der Klasse SoundPlayer werden benötigt: eine für Melodie 1, die andere für ... nein, das schreibe ich jetzt nicht. Stattdessen fügen Sie jetzt bitte in den Block der Klasse cTime den folgenden Zweizeiler ein: SoundPlayer sound1
= new SoundPayer(@"C:\Projekte\Wecker_Tool\ Sounds\tada.wav");
SoundPlayer sound2
= new SoundPlayer(@"C:\Projekte\Wecker_Tool\ Sounds\ir_inter.wav");
Listing 3.8
Zwei Objekte vom Typ »SoundPlayer« werden erzeugt.
Wie unschwer zu erkennen ist, handelt es sich bei dem, was den Konstruktoren an Wert übergeben wird, um die absoluten Pfade hin zu den Sounddateien tada.wav und ir_inter.wav. Wenn Sie das allzu wörtlich nehmen, womöglich noch ohne sich die beiden Initialisierungen genauer angesehen zu haben, ist Ihnen die nächste Beschwerde des Debuggers sicher. Bei der kann man nur schreiben: Willkommen zurück in der Welt von C. Und da Teile der Pfade nicht als Escape-Sequenzen interpretiert werden, muss ein spezielles Zeichen her. Erst dann ist für den Debugger klar: Es geht um Zeichenfolgenliterale (Anführungszeichen reichen in diesem Fall nicht). Dafür müssen Sie den Pfaden jeweils ein @-Zeichen voranstellen. Unter Laufzeitbedingungen zu testen gibt es hier nichts, fehlt doch die Implementierung für das Stellen des Weckers ebenso wie das den Weckton schlussendlich auslösende Ereignis, dessen Ursprung gleichwohl nicht der Button Stellen ist. Einen neuen Ordner im Projektverzeichnis anlegen Auch aus einem anderen Grunde können Sie nicht testen. Im Projektverzeichnis des Wecker-Tools existiert kein Ordner sounds. Vier Schritte schaffen Abhilfe:
59
3.2
3
Ausgeschlafen – das Wecker-Tool
1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Stammverzeichnis. 2. Wählen Sie im Kontextmenü zuerst Hinzufügen, anschließend Neuer Ordner. 3. Benennen Sie den neuen Ordner um. 4. Kopieren Sie die Dateien tada.wav und ir_inter.wav in den neu erstellten Ordner.
3.2.8
In einem Aufwasch – Einstellen der Weckzeit und Auslösen des Wecktons
Was sollte sich nach Betätigen des Buttons Stellen programmintern zunächst abspielen? Eine im Sekundenintervall durchgeführte Prüfung, ob es eine Identität zwischen Systemzeit (auch hinsichtlich des Datums) und eingestellter Weckzeit gibt (die es natürlich niemals geben würde, läge die eingestellte Weckzeit vor der Systemzeit). Mit anderen Worten: Sie brauchen noch einmal die Timer-Klasse. Fügen Sie bitte unterhalb der Zeile, in der die erste Instanz der Timer-Klasse erstellt wurde, folgenden Code zur erneuten Instanziierung der Klasse Timer ein: static Timer clockTimer = new Timer();
Einen Eventhandler für den Button Stellen gibt es in der Datei cTime.cs nicht. Noch nicht, denn nachdem Sie im Entwurf-Modus derselben Datei zweimal auf den Button Stellen geklickt haben, ist der Ereignishandler vorhanden: private void stellen_button_Click(object sender, EventArgs e){}
Weiter oben haben Sie sich zwecks Implementierung einer (zunächst »statischen«) Uhrzeit der Struktur DateTime bedient. Auch die brauchen wir ein zweites Mal, allerdings in erweiterter Form, geht es doch darum, aus dem Kalender (dateTimePicker1) das Datum zu holen sowie aus den beiden Windows-Drehfeldern hours_numericUpDown und minutes_numericUpDown die eingestellte Weckzeit abzurufen. So sieht der Code aus, den Sie dazu in den Rumpf des Eventhandlers stellen_ button_Click() einfügen: DateTime dt = new DateTime(dateTimePicker1.Value.Year, dateTimePicker1.Value.Month, dateTimePicker1.Value.Day, Convert.ToInt32(hours_numericUpDown.Value), Convert.ToInt32(minutes_numericUpDown.Value), Convert.ToInt32(DateTime.Now.Second)); Listing 3.9
60
Ein »Kessel Buntes« für den Konstruktor »DateTime()«
Die Entwicklung der Bedienoberfläche
An der Initialisierung ist nichts Weltbewegendes – bis auf eine Kleinigkeit: Wir müssen konvertieren. Ganz gleich, auf welcher Zeile der Überladungsliste Sie Ihren Finger stoppen, das Format der dem Konstruktor übergebenen Werte ist fast ausschließlich Int32 oder – the next generation of personal computers – Int64. Dumm gelaufen, denn bei diesen Werttypen spielt weder hous_numericUpDown noch minutes_numericUpDown so ohne Weiteres mit. Nun geht es ans Vergleichen, an ein if-else-Konstrukt und an das »In-SchwungBekommen« des zweiten Timers. Denn was wir wollen, ist klar: 왘
Wir wollen überprüfen, ob die eingestellte Weckzeit (inklusive Datum) kleiner gleich der Systemzeit ist.
왘
Ist die eingestellte Weckzeit größer als die Systemzeit, soll 왘
das Intervall des Timers festgelegt werden,
왘
der Timer auf einen Eventhandler bezogen werden und
왘
der Timer gestartet werden.
Übersetzt in C#-Notation, sehen obige Anforderungen wie folgt aus: if (dt