Sie sind auf Seite 1von 483

C#

SYBEX-WebBook

C#
Otmar Ganahl

Der Verlag hat alle Sorgfalt walten lassen, um vollstndige und akkurate Informationen in diesem Buch bzw. Programm und anderen evtl. beiliegenden Informationstrgern zu publizieren. SYBEX-Verlag GmbH, Dsseldorf, bernimmt weder die Garantie noch die juristische Verantwortung oder irgendeine Haftung fr die Nutzung dieser Informationen, fr deren Wirtschaftlichkeit oder fehlerfreie Funktion fr einen bestimmten Zweck. Ferner kann der Verlag fr Schden, die auf eine Fehlfunktion von Programmen, Schaltplnen o.. zurckzufhren sind, nicht haftbar gemacht werden, auch nicht fr die Verletzung von Patent- und anderen Rechten Dritter, die daraus resultiert. Projektmanagement: Anita Kucznierz DTP: Renate Felmet-Starke, Willich Endkontrolle: Mathias Kaiser Redaktionsbro, Dsseldorf Umschlaggestaltung: Guido Krsselsberg, Dsseldorf Farbreproduktionen: Fischer GmbH, Willich Belichtung, Druck und buchbinderische Verarbeitung: Bercker Graphischer Betrieb, Kevelaer

ISBN 3-8155-0125-3 1. Auflage 2002 Dieses Buch ist keine Original-Dokumentation zur Software der Firma Microsoft. Sollte Ihnen dieses Buch dennoch anstelle der Original-Dokumentation zusammen mit Disketten verkauft worden sein, welche die entsprechende Microsoft-Software enthalten, so handelt es sich wahrscheinlich um Raubkopien der Software. Benachrichtigen Sie in diesem Fall umgehend Microsoft GmbH, Edisonstr. 1, 85716 Unterschleiheim auch die Benutzung einer Raubkopie kann strafbar sein. Der Verlag und Microsoft GmbH. Alle Rechte vorbehalten. Kein Teil des Werkes darf in irgendeiner Form (Druck, Fotokopie, Mikrofilm oder in einem anderen Verfahren) ohne schriftliche Genehmigung des Verlages reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfltigt oder verbreitet werden. Printed in Germany Copyright 2002 by SYBEX-Verlag GmbH, Dsseldorf

Inhaltsverzeichnis
5

Inhaltsverzeichnis
Vorwort Widmung Danksagung Kapitel 1 Kapitel 2 Einleitung C# die neue Programmiersprache C#-Einstieg Einsprungsfunktion Namensraum Der Build-Prozess Schutzklassen-Modifizierer Zusammenfassung Klassen und Objekte unter C# Klassen Grunddatentypen Schlsselwort using System.Object Strukturen vs. Klassen Boxing und Unboxing Enumerationen Methoden Methode mit Bindung Statische Methode berladen von Methoden berladen von Operatoren ref- und out-Parameter Eigenschaften (Properties) Ausnahmeverarbeitung Exception Handling (EH) try catch throw finally Performanz 15 17 19 21 27 28 32 34 35 41 42 42 43 49 50 51 53 57 58 60 60 61 61 62 64 67 69 71 75 76

C#
6
Vererbung Virtuelle Methoden Abstrakte Basisklassen Schutzkonzept Interface (Schnittstellen) Zusammenfassung Felder und Collections Felder aus Wertinstanzen Felder aus Referenzinstanzen System.Array Indexer Die Schnittstelle IEnumerable und IEnumerate ArrayList Maps Kontrollstrukturen Verzweigungen Schleifen und Iterationen Sprungbefehle Delegates Multicast-Delegates Events System.EventHandler Delegate Attribute Zusammenfassung Kapitel 3 Baugruppen (Assemblies) Einleitung Grundlagen Singlefile- und Multifile-Assemblies Singlefile-Assembly Multifile-Assembly ILDASM (IL Disassembler) Assembly-Entwicklung unter Visual Studio.NET 77 82 84 85 86 88 89 89 91 92 93 94 96 97 98 98 100 100 101 104 106 110 112 117 119 120 121 123 123 125 128 131

Inhaltsverzeichnis
7
Shared Assembly Zusammenfassung Kapitel 4 XML-Einfhrung Einleitung XML-Grundlagen Das XML-Informationsmodell Verarbeitungsanweisungen Elemente Attribute Kommentare Zeichenreferenzen Zeichenfolgen (Character Data) Schemas XPATH Kurzformen XSLT Kapitel 5 XML-Klassen Einleitung XML-Dateien schreiben XML-Dateien lesen XmlTextReader XmlValidatingReader DOM-Objektmodell XmlNode XML-Dokumente verndern Suchen innerhalb von XML-Dokumenten Zusammenfassung XPATH XSLT (XSL-Transformationen) XML-Serialisierung von .NET-Objekten Serialisierung von einfachen Objekten 135 137 139 140 140 141 144 144 145 146 147 147 147 148 150 150 153 154 156 160 160 163 165 166 169 170 172 172 173 175 175

C#
8
Serialisieren von komplexeren Datentypen Serialisieren von Listen Namensgebung der Attribute Zusammenfassung Kapitel 6 Windows-Applikationen Einfhrung Grundlegende Architektur eines Win32-Windowsprogramms .NET-Programmiermodell Die Klasse Form Auf Fensterereignisse reagieren Steuerelement im Fenster Zusammenfassung Windows-Steuerelemente TextBox, TrackBar, RadioButton, Label Zusammenfassung Designer (Entwurfsansicht) Zusammenfassung Men Kontextmenu Werkzeugleiste Dialoge Kundenspezifische Steuerelemente Erweitern eines Steuerelements Entwicklung eines neuen Steuerelements Zusammenfassung GDI+ Pen und Brush Font Point, PointF Rectangle, RectangleF Size, SizeF Die Klasse Graphics 177 179 181 182 185 186 188 190 194 196 197 200 200 201 209 209 218 219 221 222 225 230 230 233 236 236 237 237 238 238 238 238

Inhaltsverzeichnis
9
OnPaint-Methode Koordinatensysteme Bilder, Bitmaps und Images Ressourcen Erstellung von Dateien vom Typ .resource Verwenden von Ressource-Dateien Ressource-Datei ber .resx-Dateien erstellen Zusammenfassung Untersttzung der Kulturen Zusammenfassung Kapitel 7 Konfigurationsdateien Konfigurationen .NET-Konfigurationsdateien Format der .NET-Konfigurationsdateien XML-Konfigurations-Bearbeiter machine.config appSettings Simple-Email-Beispiel Zusammenfassung Kapitel 8 ADO.NET Einleitung bersicht der Klassen Klassen im Namensraum System.OleDb Klassen fr den Zugriff auf SQL-Server von Microsoft Die Klasse DataSet Zugriff auf MS SQL Server Datenbank anlegen Daten in die Tabelle eintragen Eintrag lschen Update eines Eintrages Daten lesen Zusammenfassung 238 241 246 248 249 251 254 256 257 264 265 266 268 268 269 273 275 277 281 283 284 286 286 286 287 287 287 290 294 295 296 300

C#
10
DataSet Manipulation von Daten innerhalb eines DataSets DataSet und XML Datenbank aktualisieren mit Daten eines DataSets DataGrid Zusammenfassung Kapitel 9 ASP.NET Einleitung ASP-Active Server Pages Diskussion Von ASP zu ASP.NET Web-Steuerelemente Trennung zwischen Sicht und Funktion Verwendung von kompiliertem Code Zusammenfassung ASP.NET-Anwendungen mit VS Studio entwickeln Debugging von ASP.NET-Anwendungen Zusammenfassung Web-Steuerelemente Gemeinsame Eigenschaften HyperLink-Steuerelement Image-Steuerelement CheckBox-Steuerelement RadiobuttonList ListBox Zusammenfassung Kundenspezifische Steuerelemente Basisklasse Control Kundenspezifische Attribute Eingebettete Objekte Die Attribut-Klasse Style Basisklasse WebControl Composite Controls 300 303 306 309 313 315 317 318 319 324 325 326 328 331 332 333 341 341 341 341 343 343 344 345 347 348 348 348 350 354 356 357 358

Inhaltsverzeichnis
11
Statusverwaltung unter ASP.NET Application und Session Cache Zusammenfassung Kapitel 10 Web-Services Einleitung Ein einfaches Web-Service-Beispiel MyWebServices Web-Service im Web-Browser Client-Programme zu MathWebService Entwicklung und Anwendung von Web-Services unter Visual Studio.NET Web-Service-Anwendung Client-Anwendungen Zusammenfassung Kapitel 11 Remoting unter .NET Einleitung Remoting-Infrastruktur Client-aktivierte Objekte FolderFileEnum-Klasse Server Client Server-aktivierte Objekte Erzeugung von Remote-Objekten mit dem new-Operator Channel Lebensdauer (Leased Based Lifetime) Die Schnittstelle ILease LifetimeServices Sponsor Konfigurieren des Servers und des Clients ber Konfigurationsdateien 360 362 366 372 373 374 374 375 376 379 381 382 384 388 391 392 393 395 395 398 400 403 406 407 408 408 410 412 414

C#
12
Server-Konfiguration Client-Konfiguration IIS Hosting Asynchrone Aufrufe Events Remote-fhiger Objekte bergabe von Objekten in Remote-Methoden Zusammenfassung Kapitel 12 .NET-Klassen Einleitung Die Klasse System.Object ReferenceEquals Equals GetHashCode() ToString() GetType() Finalize() Die Klasse System.String Split Die Klasse System.IO.Path Die Klasse Path Die Klassen DirectoryInfo,FileInfo Die Klasse WebClient Kapitel 13 Ausgewhlte C#-Kapitel Interoperabilitt mit COM Einbinden von ActiveX-Steuerelementen Zusammenfassung Casting Explizites und implizites Casting Schlsselwort explicit Schlsselwort implicit C#-Prprozessor 414 416 418 421 426 426 427 429 430 431 431 432 433 434 434 434 435 435 436 437 439 440 443 444 444 448 448 448 451 451 452

Inhaltsverzeichnis
13
#define und #undef #if, #elif, #else, #endif #region, #endregion #warning, #error Kapitel 14 Fallbeispiel WWWPhotoPool 455 Beschreibung und Architektur der WWWPhotoPool-Applikation WWWPhotoPool-Server PhotoWebServer Publisher CD-Beispiel WWWPhotoPool-Server Publisher PhotoWeb Stichwortverzeichnis 456 456 458 459 459 459 460 460 461 452 452 453 454

15

Vorwort
Microsoft bietet mit .NET den Entwicklern eine Plattform an, in der sich die Grenzen zwischen World Wide Web und den Betriebssystemen verwischen. Die natrliche .NET-Sprache stellt C# (C Sharp) dar. Als ein Vertreter der C-Sprachenfamilie besticht C# durch ihre Einfachheit und Leistungsfhigkeit. Es war fr mich eine neue und schne Erfahrung, ein Fachbuch zu C# und .NET zu schreiben. Es wrde mich freuen, wenn Sie nach Lektre dieses Buches mit vielen neuen Ideen und hoch motiviert Ihre Projekte in C# und .NET umsetzen.

17

Widmung
Dieses Buch ist meiner Familie gewidmet.

19

Danksagung
Whrend der Erstellung des Buches haben mich viele Personen untersttzt. Ein herzliches Dankeschn mchte ich stellvertretend aussprechen: Dem gesamten SYBEX-Team, besonders den Projektmanagerinnen Anita Kucznierz und Katja Roth, dem Fachlektor Ron Nanko, der Sprachlektorin Brigitte Hamerski und Ute Dick. Meinem Bruder Claudio Ganahl, der mich in vielen fachlichen und inhaltlichen Fragen untersttzte. Meiner Frau Maria und meinen Kindern Janine und Manuel fr das Verstndnis, dass Papa in letzter Zeit weniger Zeit fr sie hatte. Blons, Mrz 2002 Otmar Ganahl

Einleitung

C#
22

1
Die Sprache C# (gesprochen c-sharp) kann nicht getrennt von der Laufzeitumgebung .NET betrachtet werden. Mit .NET ist Microsofts Vision einer umfassenden Plattform umschrieben. Die herkmmlichen Programmiermodelle von Microsoft konnten den Entwicklungen in der IT-Branche der letzten Jahren nur mehr bedingt Rechnung tragen. Neue Strmungen, vor allem im Bereich des Internets, wurden in bestehende Programmiermodelle hinein gepfercht. In Folge wurden diese immer komplexer und der Aufwand, diese zu beherrschen, wurde immer grer. Groartige Entwicklungswerkzeuge haben diese Problematik zwar entschrft, aber nicht gelst. Mit .NET versucht Microsoft sozusagen eine Bereinigung des Zustandes zu erreichen. .NET definiert ein gnzlich neues Programmiermodell und bercksichtigt hier smtliche Entwicklungen in der IT-Branche, angefangen von Internet, Sicherheit, Datenbankzugriffe, Webservices, XML, usw. bis hin zu den jngsten Entwicklungen im Bereich des Software Engineerings. Smtliche Erfahrungen der komponentenbasierenden Softwareentwicklung (COM) und der verteilten Programmierung (DCOM) sind in .NET eingeflossen. Konzepte, die mit der Zeit unbersichtlich und damit fehleranfllig wurden (Stichwort DLL-Hell, Registrierungsdatenbank, Installationsmechanismen usw.), sind unter .NET entsprechend neu berdacht worden und erzeugen nicht mehr diese Probleme, deren Behandlung in der Praxis viel Zeit kostete. Interessant ist auch der Ansatz, dass .NET als Softwareschicht zwischen Betriebssystem und den Applikationen fungiert, und damit ein Betriebssystem abstrahiert. Technisch spricht nichts dagegen, dass .NET-Runtimes auch fr andere Betriebssysteme entwickelt werden, was zur Folge htte, dass Programme auch ber verschiedene Plattformen hinweg lauffhig wren. Durch die Einfhrung einer Metasprache werden Applikationen sogar auch binr kompatibel. Das Ergebnis dieser Anstrengungen ist ein schlankes, einfaches, aber umso mchtigeres Programmiermodell, das leicht erlernbar und anwendbar ist. Software as a service ist ein vielfach verwendetes MarketingSchlagwort von Microsoft im Zusammenhang mit dieser Technologie.

Einleitung
1
Mit .NET sind aber Anpassungen der Programmiersprachen notwendig. Die meisten herkmmlichen Programmiersprachen (wie C, C++, VB) knnen oft syntaktisch die neuen Features des .NET Programmiermodells nicht untersttzen (z.B. Garbage Collecting). Da die C-Sprachen vor allem unter professionellen Entwicklern populr sind, hat Microsoft eine neue Sprache, C# (C-Sharp), entwickelt. Schon aus dem Namen der neuen Programmiersprache geht hervor, dass C# ein Vertreter der C-Sprachenfamilie darstellt. Und in der Tat sind viele Konzepte, allen voran aus C++ und Java (Java wird ebenfalls in die Kategorie C-Sprachen eingeordnet), in C# wiederzufinden. Die Mchtigkeit der C-Sprachen wurde mit der Einfachheit moderner Sprachanstze kombiniert, was auch sehr gut gelungen ist. Die hnlichkeit mit Java ist unverkennbar. Das vorliegende Buch ist vor allem fr Softwareentwickler und Programmierer unter Microsoft Betriebssystemen gedacht, die einen schnellen und kompakten Einstieg in die Thematik C# und .NET wnschen. Notwendig sind dazu Grundkenntnisse der objektorientierten Softwareentwicklung. Sollten Sie C++ oder Java beherrschen, dann werden Sie sich beim Erlernen der Grundlagen von C# sehr wohl fhlen. Aber auch Visual BasicProgrammierer sollten sich mit der vorliegenden Lektre nicht schwer tun. Es wird ebenfalls davon ausgegangen, dass Sie Visual Studio 6.0 kennen und Debugger fr Sie kein Fremdwort darstellt. An dieser Stelle wird auch explizit darauf hingewiesen, dass dieses Buch kein Referenzbuch der Sprache C# darstellt. Nach Durcharbeit dieser Lektre sollten Sie ein fundiertes C#- und .NET-Wissen besitzen, und damit die Voraussetzung geschaffen haben, um sich ber begleitende Technologiebeobachtung zum professionellen .NET-Programmierer zu entwickeln. Bestimmte Kapitel bentigen allerdings einen hheren Wissensstand, so z.B. Datenbankenprogrammierung, XML. Hier wird auf einschlgige Literatur verwiesen. Nachfolgend werden noch einige Hinweise zum Gebrauch dieses Buches gegeben. Ziel ist es, Sie in mglichst kurzer Zeit zu befhigen, professionellen .NET-Code unter C# zu produzieren. Dies geht natrlich nicht von heute auf morgen. Damit die

23

C#
24

1
Lernkurve aber trotzdem kurz und steil verluft, hier einige Tipps und Anmerkungen.
1

Lesen Sie ein Kapitel oder einen Abschnitt zuerst in Ruhe und gemtlicher Umgebung durch. Erst dann begeben Sie sich an die Tasten. Sie werden beim erstmaligen Durchlesen sicherlich nicht alles verstehen, aber Sie bekommen einen berblick, was Sie in diesem Kapitel erwartet. Kodieren Sie so gut wie mglich jedes Beispiel selbst aus. Sie werden vielleicht einwenden, dass Sie C# und .NET erlernen wollen und nicht Maschinenschreiben. Unterschtzen Sie aber nicht das daraus entstehende Lernpotenzial. Jeder Tippfehler hat einen KompilerFehler zur Folge, den Sie analysieren und erkennen mssen. Auerdem lernen Sie das Entwicklungssystem intuitiv, sozusagen nebenbei kennen. Das Beherrschen der Features des Entwicklungssystems ist eine wesentliche Voraussetzung fr die sptere Produktivitt beim Erzeugen von Code. Gerade beim Erlernen von Konzepten ist es besser kleine und berschaubare Beispiele zu verwenden, die den Blick speziell auf die gerade behandelte Thematik beschrnken. Oft wird der Fehler gemacht, in ein Beispielprogramm auch mehr oder weniger komplexe Algorithmen und spitzfindige Softwaremuster zu verpacken. Mit diesen knnen Sie sich dann beschftigen, wenn Sie die Sprache beherrschen. Daher werden vor allem die ersten Beispiele keinen Anspruch auf sinnvolle Verwendbarkeit erheben. Es sind einfache Beispiele, die sich in der Didaktik begrnden. Experimentieren Sie! Variieren Sie Codeabschnitte, erzeugen Sie knstlich Kompiler-Fehler und testen Sie stndig, ob Ihr Denkmodell schlssig ist. Abonnieren Sie eine Fachzeitschrift zum Thema und betreiben Sie aktiv Technologiebeobachtung!

Smtliche Beispiele basieren auf Visual Studio.NET (deutsche Ausgabe April 2002). Wenn Sie Visual Studio.NET auf Ihrem Rechner installieren, dann bentigen Sie ca. 2GByte Festplat-

Einleitung
1
tenspeicher. Auf Ihrem Entwicklungsrechner sollten Sie auch den IIS (Internet Information Server) und SQL-Server (oder aber MSDE) installiert haben. Wenn Sie die Installation noch nicht durchgefhrt haben, dann installieren Sie unbedingt zuerst den IIS und erst dann das .NET-Framework mit Visual Studio.NET. Es wird auch ausreichend RAM-Speicher (wenigstens 128, besser 256 MB) empfohlen, da ansonsten das Entwickeln zu einem Geduldsspiel wird. Ein Internetanschluss wird vorausgesetzt. Fr Kapitel 11: Remoting unter .NET ist das Vorhandensein eines kleinen TCP-Netzwerkes von Vorteil. Installieren Sie auch die aktuellste Version der MSDN (Microsoft developer network). Es wird davon ausgegangen, dass Sie diese umfangreiche Wissensdatenbank ausgiebig nutzen werden (und auch mssen). Auf der Begleit-CD zu diesem Buch sind smtliche Beispiele in auskodierter Form zu finden. Vielfach werden Sie eingedeutschte Begriffe (z.B. Assemblies) oder Mischwesen aus Deutsch und Englisch, wie MemberVariable finden. Um die Lesbarkeit zu frdern, sind diese Wrter im Buch kursiv formatiert. Fr konstruktive Kritik, Fehlerhinweise und Anregungen bin ich sehr dankbar. Zum Abschluss wnsche ich Ihnen viel Spa mit diesem Buch. Sollte es Ihnen gefallen, empfehlen Sie es weiter. Blons im Biosphrenpark Groes Walsertal/sterreich im Mrz 2002 Otmar Ganahl

25

C# die neue Programmiersprache

C#-Einstieg Klassen und Objekte unter C# Grunddatentypen Strukturen vs. Klassen Enumerationen Methoden Eigenschaften (Properties) Ausnahmeverarbeitung Exception Handling (EH) Vererbung Interface (Schnittstellen) Felder und Collections Kontrollstrukturen Delegates Events Attribute Zusammenfassung

28 42 49 53 58 60 67 69 77 86 89 98 101 106 112 117

C#
28

2
In diesem Kapitel werden Sie in kleinen, berschaubaren Beispielen die Konzepte der Sprache C# experimentell kennen lernen. All diese Beispiele dienen einem rein didaktischen Zweck und erheben keinen Anspruch auf eine andere sinnvolle Verwendbarkeit. Aus der langjhriger Seminarpraxis ist dem Autor bekannt, dass dies sehr sinnvoll ist und die Lernkurve betrchtlich verkrzt. Smtliche Beispiele werden Sie mit der Entwicklungsumgebung Visual Studio.NET durchfhren. Es wird angenommen, dass Sie mit einer der Entwicklungsumgebungen von Visual Studio 6 vertraut sind, und es wird daher so weit wie mglich auf redundante Screenshots verzichtet. Das ist mglich, da eine gewisse hnlichkeit zu Visual Studio 6 vorhanden ist, und viele Features intuitiv erlernbar sind. Es wird vorausgesetzt, dass Sie die wichtigsten Konzepte der Sprachen C und C++ beherrschen.

C#-Einstieg
In diesem ersten Beispiel wird eine ganz normale Textausgabe auf die Konsole erzeugt, wie es schon Kerninghan und Ritchie Mitte der 70er Jahren in Ihrem Klassiker C Programming getan haben. Es wird auf der Konsole derselben ehrwrdige Text ausgegeben, wie damals Kernighan und Ritchie, nmlich Hello World. Starten Sie das Entwicklungssystem Visual Studio.NET und legen Sie zunchst eine Projektmappe an. Eine Projektmappe ist vergleichbar mit einem Workspace unter Visual Studio 6, erlaubt also mehrere Projekte unter einem Namen zu organisieren. Nennen Sie diese Projektmappe DotNetExperiments. Eine leere Projektmappe legen Sie ber das Men Datei > Neu > Leere Projektmappe an. Ein Blick auf die erzeugte Dateistruktur im Datei-Explorer zeigt, dass im gewhlten Zielordner ein Ordner namens DotNetExperiments angelegt wurde. In der Entwicklungsumgebung sollten Sie im Projektmappen-Explorer nun die neu angelegte Projektmappe sehen (wenn dieser nicht sichtbar ist, dann knnen Sie diesen mittels Men Ansicht > Projektmappen-Explorer andocken).

C # die neue Programmiersprache


2
29

Projektmappe erzeugen Abb. 2.1

Fgen Sie nun dieser Projektmappe ein erstes Projekt hinzu. Der Menbefehl Datei > Neu > Projekt ffnet hierzu einen Dialog. Markieren Sie Visual C#-Projekte, whlen Sie vorerst aus didaktischen Grnden ein Leeres Projekt aus, und geben Sie dann dem Projekt den Namen HelloWorld. Vergessen Sie aber nicht, die Option Zu Projektmappe hinzufgen zu aktivieren, da ansonsten eine neue Projektmappe erzeugt wird, die den Namen des Projektes hat (Sie kennen sicherlich das hnliche Verhalten unter Visual Studio 6). Ein erneuter Blick auf die Dateistruktur zeigt, dass innerhalb des Projektmappen-Ordners ein neuer Ordner HelloWorld angelegt wurde. Der Projektmappen-Explorer zeigt nun auch das Projekt an. Da das Projekt noch leer ist, fgen Sie diesem eine C#-Quelldatei hinzu. Dies geschieht mit dem Menbefehl Projekt > Neues Element hinzufgen. Im nun erscheinenden Dialog whlen Sie den Typ C# Codedatei. Geben Sie der Datei einen selbstsprechenden Namen. Statt des vom Entwicklungssystem vordefinierten Namens CodeFile1.cs knnen Sie Application.cs nehmen. Geben Sie nun folgenden Code ein. Eine genaue Diskussion erfolgt spter, zuerst lernen Sie die Handhabung des Entwicklungssystems kennen.

C#
30

Projekt anlegen Abb. 2.2

Codedatei hinzufgen Abb. 2.3

CD-Beispiel HelloWorld1

class App //Definition einer neuen Klasse { //die Einstiegsfunktion public static void Main() {

C # die neue Programmiersprache


2
System.Console.WriteLine("Hello World"); } } /*Kein Strichpunkt notwendig*/ Nach einer bernahme des abgedruckten Quelltextsegments knnen Sie mittels S + % den Build-Prozess und die anschlieende Ausfhrung starten. Noch einige Bemerkungen zum Entwicklungssystem: Wenn Sie die Ordner-Struktur auf dem Dateisystem betrachten, werden Sie feststellen, dass im Projektmappen-Ordner fr das neue Projekt ein eigener Ordner angelegt wurde. Dieser enthlt unter anderem die Quelldatei Application.cs und einen weiteren Ordner bin. Hier befindet sich der Ordner Debug mit dem eigentlichen Programm HelloWorld.exe. Diese ausfhrbare Datei beinhaltet allerdings noch Debug-Informationen. Fr die Erzeugung einer Release-Version schalten Sie die aktive Konfiguration mit dem Menbefehl Erstellen > KonfigurationsManager auf Release um. Wenn Sie das Projekt nun neu entwickeln, entsteht im Ordner Release eine ausfhrbare Datei ohne Debug-Informationen. Diese Datei ist auch deutlich kleiner und schneller.

31

HINWEIS
Debug- und Releaseversionen
In der Regel wird es so sein, dass Sie bis zur Fertigstellung eines Projekts ausschlielich mit Debugversionen arbeiten, die weiterfhrende Informationen enthalten, um mithilfe des Visual Studio Debuggers Fehlererkennung, -suche und -behebung durchfhren zu knnen. Releaseversionen haben den Vorteil, diese fr den Endanwender uninteressanten Daten nicht zu enthalten, was sich in der Gre der entstehenden Dateien und nicht zuletzt auch der Abarbeitungsgeschwindigkeit niederschlgt.

Natrlich knnen Sie das Kompilieren auch explizit ber den Menpunkt Erstellen > Erstellen bzw. Erstellen > Alles neu erstellen durchfhren. Dasselbe gilt fr das Starten des Programms aus der Entwicklungsumgebung heraus. Im Menpunkt De-

C#
32

2
buggen sehen Sie die Mglichkeiten. Smtliche Funktionalitt wird auch direkt ber die Werkzeugleisten angeboten.

Einsprungsfunktion
Ein erfahrener C/C++-Programmierer wird bei diesem Beispiel nicht so schnell erschrecken. Was Kommentare anbelangt, so ist C- als auch C++-Stil mglich: //Definition einer neuen Klasse /*Kein Strichpunkt notwendig*/ Sie werden auch erkannt haben, dass mit dem Schlsselwort class ein neuer Typ mit dem Namen App definiert wird. Dies funktioniert hnlich wie bei C++, nur dass kein abschlieender Strichpunkt am Blockende notwendig ist. Die Klasse App implementiert in diesem Beispiel nur eine statische Methode Main(). Sie werden hier die Einsprungsfunktion vermuten, und damit haben Sie auch Recht. Das wirft aber die Frage auf, warum Main() die Einsprungsfunktion darstellt, ist sie doch unter dem Namensraum einer Klasse definiert? Die Erklrung ist, dass es unter C# keine globalen Funktionen (und auch keine globalen Variablen) gibt, wie Sie es aus C oder C++ kennen. Smtliche Funktionen mssen unter der Kontrolle einer Klasse sein. C++-Programmierer wissen auch, dass statische Methoden den globalen Funktionen aus C sehr hnlich sind. Fr den Aufruf von statischen Methoden sind unter C++ (wie auch unter C#) keine Instanzen der Klassen also Objekte notwendig, sondern nur die Angabe des Namensraumes. Wie findet aber der Programm-Loader nun diese Methode? In einem C#-Programm darf es nur genau eine Klasse mit einer statischen Methode Main() geben. Diese Methode gilt dann als Einstiegsfunktion. (Korrekterweise muss gesagt werden, dass sehr wohl mehrere statische Methoden mit dem Namen Main in unterschiedlichen Klassen vorkommen drfen. In diesem Fall allerdings muss dem Kompiler explizit diejenige Klasse angegeben werden, dessen Main()-Funktion als Einsprungsfunktion dienen soll. Mehr dazu finden Sie in der MSDN). Die .NET-Laufzeitschicht sucht dann diese Methode in den ihr bekannten Klassen und ruft sie auf. Wie dies im Detail funktioniert, werden Sie spter noch kennen lernen.

C # die neue Programmiersprache


2
Experimentieren Sie, indem Sie eine weitere Klasse (mit Namen App1) definieren, die ebenfalls eine Methode Main() implementiert. Beim Kompilieren werden Sie eine entsprechende Fehlermeldung erhalten. class App //Definition einer neuen Klasse { //die Einstiegsfunktion public static void Main() { System.Console.WriteLine("Hello World"); } } /*Kein Strichpunkt notwendig*/ class App1 { public static void Main() { System.Console.WriteLine("Hello World App1"); } } Benennen Sie nun die Methode Main() der Klasse App1 auf Main1() um der Kompiler arbeitet nun, ohne einen Fehler auszugeben. Sie knnen diese Methode sogar jederzeit explizit aufrufen. class App { public static void Main() { System.Console.WriteLine("Hello World"); //Aufruf der statischen Methode Main1 aus App1 App1.Main1(); } } class App1 { public static void Main1() { System.Console.WriteLine("Hello World App1"); } }
CD-Beispiel HelloWorld2

33

C#
34

2 Namensraum
Um die Methode Main1() aufzurufen, mssen Sie Namensraum angeben. Wenn Sie in C++ eine Klasse deklarieren, entsteht automatisch auch ein Namensraum. C++ verwendet den zweifachen Doppelpunkt (scope operator), um den Namensraum anzugeben. Unter C# wird der Namensraum mit dem Punktoperator eingeleitet. (Verwechseln Sie App1 nicht mit einem Objekt, App1 ist der Name der Klasse!) App1.Main1(); Ein Namensraum kann unter C# auch mit dem Schlsselwort namespace eingefhrt werden. Im folgenden Codebeispiel wird die Klasse App1 innerhalb eines Namensraumes definiert.
CD-Beispiel HelloWorld3

class App { public static void Main() { System.Console.WriteLine("Hello World"); //den Namespace angeben DotNetExperiments.App1.Main1(); } } //Erzeugung eines neuen Namensraumes namespace DotNetExperiments { class App1 { public static void Main1() { System.Console.WriteLine( "Hello World App1"); } } } Sie sehen, dass nun die Angabe des Namensraumes DotNetExperiments notwendig ist, um dann ber die Klasse App1 die statische Methode Main1() aufzurufen. DotNetExperiments.App1.Main1();

C # die neue Programmiersprache


2
Der Punktoperator wird unter C# verwendet, um Namensrume zu ffnen und auf Klassen-Members zu verweisen (wie auch bei C++). An diesem Punkt drfte es auch nicht schwer sein, die Programmzeile, die die Ausgabe auf die Konsole durchfhrt, zu verstehen. System.Console.WriteLine("Hello World"); System ist ein Namensraum in dem sich die Klasse Console befindet, die ihrerseits die statische Methode WriteLine() implementiert.

35

Der Build-Prozess
Sie stellen sich jetzt sicherlich die Frage, wo denn die Klasse Console mit der statischen Funktion WriteLine.) definiert ist? Unter C++ wrden Sie eine Bibliothek hinzulinken, die die Implementierungen der Funktionen der Klasse enthlt im Quellcode wrden durch einen Verweis auf eine Header-Datei diese Funktionsprototypen vereinbart werden. Eine vergleichbare Vereinbarung der Klasse System.Console fehlt aber im vorliegenden Beispiel! Um diesen Vorgang zu verstehen, werfen Sie erst einen Blick auf den C/C++-Mechanismus.

C/C++-Mechanismus Abb. 2.4

C#
36

2
Um eine Objektdatei zu generieren, gengen dem Kompiler die Prototypen der Funktionen und Datentypen. Die eigentliche Implementierung fgt dann der Linker in Form einer statischen Objekt-Bibliothek oder einer Import-Bibliothek (fr implizite Dll-Bindung) hinzu. Fr C++-Programmierer ergibt sich ein erhhter Aufwand dadurch, dass die konsistente Pflege von Header-Datei und Bibliothek notwendig ist. Im COM-Programmiermodell unter Windows (component object model) ist die Verwaltung auch nicht einfacher. Die Beschreibung von COM-Komponenten erfolgt in binrer Form in einer der Typbibliotheken, die bei modernen COM-Komponenten als Ressource direkt in den Komponenten integriert wird. Damit sind wenigstens sowohl die Beschreibungen als auch der Code in derselben Datei, was die Handhabung vereinfacht. Die Programmiersprache Visual Basic bedient sich intensiv dieser Typbibliotheken. In der Registrierung ist aber der Ort der Typbibliothek und der Ort des Binrcodes auf der Festplatte an unterschiedlichen Eintrgen zu verwalten. Die Konsistenz der Eintragungen ist fr das Funktionieren einer Komponente eine wesentliche Voraussetzung. Und genau dieser Umstand ist fehleranfllig. Unter Visual C++ knnen mittels Spracherweiterungen Header-Dateien aus den Typbibliotheken generiert werden. Visual Basic kann die Information direkt aus der Typbibliothek verwenden. .NET geht einen neuen und anderen Weg. Bei den herkmmlichen Programmiermodellen (Win32 und COM) sind Komponenten meistens in Dlls verpackt. Unter .NET wird eine Einheit, die Komponenten in binr ausfhrbaren Code enthlt, Assembly genannt. Eine Assembly ist ein wohldefinierter Verbund von Dateien. Im einfachsten Fall besteht eine Assembly aus genau einer Dll. Wesentlich ist nun, dass die Beschreibung der Komponente direkt im Assembly, in Form von so genannten Metadaten, untergebracht ist. Damit erffnen sich sehr viele Mglichkeiten. .NET-Sprachen und damit auch C# brauchen daher kein Header-Dateien, sondern nur die Angabe des Assemblies. In diesem findet sich alles: die Beschreibungen fr den Kompilier-Vorgang und die Implementierungen fr den Link-Vorgang.

C # die neue Programmiersprache


2
Damit ist auch eine logische Trennung des Build-Prozesses in einen Kompilier- und Link-Vorgang nicht mehr notwendig, da smtliche Informationen (Beschreibung und Implementierung) im Assembly zu finden sind. Der Aufruf aus der Kommandozeile wrde sich wie folgt gestalten: csc /out:HelloWorld.exe /r:mscorlib.dll Application.cs csc.exe ist der C#-Kompiler. ber die Option /out: kann der Name der entstehenden exe-Datei angegeben werden. Smtliche Assemblies, die fr die Applikation verwendet werden, sind mit der Option /r: aufzulisten. Zu guter Letzt folgt die Aufzhlung der Quellcode-Dateien. Mscorlib.dll ist die Kern-Assembly der Laufzeitschicht, und wird immer hinzugelinkt. Diese Kern-Assembly beinhaltet die Implementierungen der grundlegenden .NET-Klassen, unter anderem auch die Implementierung der Klasse Console. Bei der Verwendung des Entwicklungssystems wird dieser Aufruf natrlich implizit generiert. berprfen Sie diesen Sachverhalt. Erzeugen Sie in einem beliebigen Ordner mit einem beliebigen Editor (z.B. Notepad) eine Datei Application.cs mit obigem Code und geben Sie ber die Konsole die Anweisung csc /out:HelloWorld.exe /r:mscorlib.dll Application.cs ein. Nach fehlerfreier Ausfhrung befindet sich die ausfhrbare .exe-Datei in diesem Ordner. Verwenden Sie die Konsole von Visual Studio.NET! Diese finden Sie unter Start > Programme > Microsoft Visual Studio.NET 7.0 > Visual Studio.NET Tools > Visual Studio.NET Command Prompt. Hier ein anderes, interessantes Beispiel, das die Leistungsfhigkeit der mitgelieferten .NET-Klassen demonstriert, und auch gleichzeitig die Verwendung von Assemblies verdeutlicht. Das Beispielprogramm zeigt, wie eine E-Mail per Programm versendet werden kann. Fr eine ordnungsgeme Funktion dieses Programms ist es erforderlich, dass Ihr Rechner ber einen Internetanschluss verfgt. Ist dieses nicht der Fall, wrde die Programmausfhrung eine Exception auslsen berspringen Sie unter diesen Umstnden einfach das folgende Beispiel oder vollziehen es nur theoretisch nach.
CD-Beispiel HelloWorld4

37

C#
38

C#-Mechanismus Abb. 2.5

Erzeugen Sie ein neues C#-Projekt in der Projektmappe. Nennen Sie es SmartEmail (Datei > Neu > Projekt und vergessen Sie nicht die Option Zu Projektmappe hinzufgen zu aktivieren). Fgen Sie eine Quelldatei hinzu (App.cs) und tippen Sie den folgenden Code ein. Im Projektmappen-Explorer knnen Sie nun beide Projekte sehen. Das fett hervorgehobene Projekt ist das derzeit aktive. Sollte das neue erstellte Projekt nicht aktiv sein, dann holen Sie dies bitte nach, indem Sie das Projekt im Projektmappen-Explorer markieren und im Kontextmen (rechte Maustaste) Als Startprojekt festlegen auswhlen.

Im ProjektmappenExplorer knnen mehrere Projekte verwaltet werden Abb. 2.6

Im Menpunkt Erstellen erscheinen nun die zustzlichen Eintrge Projektmappe erstellen bzw. Projektmappe neu erstellen. Damit knnen alle Projekte einer Solution in einem kompiliert werden.

C # die neue Programmiersprache


2
class App { public static void Main() { string sAbsender; string sAdresse; string sBetreff; string sText; //Hier bitte Ihren(!) smtp Server URL eingeben System.Web.Mail.SmtpMail.SmtpServer = "smtp.provider.xx"; System.Console.WriteLine("Kleiner E-Mail Sender"); System.Console.Write("Absender: "); sAbsender = System.Console.ReadLine(); System.Console.Write("E-Mail Adresse: "); sAdresse = System.Console.ReadLine(); System.Console.Write("Betreff: "); sBetreff = System.Console.ReadLine(); System.Console.Write("Text: "); sText = System.Console.ReadLine(); System.Console.Write("E-Mail versenden? (j/n)"); if(System.Console.ReadLine()== "j") { System.Console.WriteLine( "E-Mail wird bertragen..."); System.Web.Mail.SmtpMail.Send(sAbsender, sAdresse, sBetreff, sText); System.Console.WriteLine( "E-Mail wurde bertragen"); } else { System.Console.WriteLine( "E-Mail wurde nicht bertragen"); } } Wenn Sie nun den Build-Prozess starten, wird der Kompiler mit einer Fehlermeldung reagieren. Der Grund liegt darin, dass in diesem Beispiel der Datentyp System.Web.Mail.SmtpMail verCD-Beispiel SmartEmail

39

C#
40

2
wendet wird, den der Kompiler aber nicht kennt, weil diese Klasse nicht in mscorlib.dll implementiert ist. Die Klasse findet sich im Assembly System.Web. Um dieses Programm aus der Konsole heraus zu entwickeln, mssen Sie folgende Kommandozeile eintippen. csc /out:smartemail.exe /r:mscorlib.dll /r:system.web.dll Application.cs Zustzlich zur Kern-Assembly wird ein weiteres Assembly (system.web.dll) angegeben. Im Entwicklungssystem fgen Sie diese Information im Projektmappen-Explorer durch. Expandieren Sie, wenn notwendig im Projektmappen-Explorer den Baum des Projektes SmartEmail, markieren Sie den Eintrag Verweise und bettigen Sie den Menbefehl Verweis hinzufgen im Kontextmen.

Hier werden smtliche verfgbaren Assemblies aufgelistet Abb. 2.7

Selektieren Sie das Assembly System.Web.dll im Reiter .NET und besttigen Sie. Nchfolgend sollte nun der Build-Prozess ohne Fehlermeldung des Kompilers ablaufen. Vergessen Sie nicht, einen gltigen DNS-Namen eines SMTPServers anzugeben am besten den von Ihrem Provider (bei Outlook finden Sie den Namen in den Konto-Einstellungen).

C # die neue Programmiersprache


2
Der in diesem Listing dargestellte Name ist natrlich nicht gltig. System.Web.Mail.SmtpMail.SmtpServer = "smtp.provider.xx"; Starten Sie nun das Programm, geben als Absender Ihren Namen an, eine gltige E-Mail-Adresse (nehmen Sie am besten Ihre eigene Adresse), einen Betreff (z.B. Hello World modern) und einen kleinen Text. Mit der Taste j wird nun eine E-Mail versendet. Das ist doch schon recht beeindruckend. Sie sehen, wie einfach diese Klasse zu verwenden ist. Sicherlich fallen Ihnen einige tolle Anwendungen ein, die E-Mails versenden (z.B. ein berwachungsprogramm einer Anlage, die im Bedarfsfall ein EMail versendet, usw.). In der .NET-Klassenbibliothek wimmelt es nur so von solchen leistungsfhigen Klassen, die nur darauf warten, angewendet zu werden. Aber erst sollen Sie C# sicher beherrschen.

41

Schutzklassen-Modifizierer
Unter C# ist bei der Definition jeder Methode und auch jeder Eigenschaft die explizite Angabe eines Schutzklassen-Modifizierers notwendig, der die jeweilige Schutzklasse angibt. Im Beispiel ist die Methode public definiert. Bei C++ wird mit dem Schlsselwort public fr einen Abschnitt in der Klassendeklaration die Schutzklasse definiert. Alle Methoden und Eigenschaften, die sich in diesem Abschnitt befinden, haben dann diese Schutzklasse. Die Schutzklassen werden in spteren Kapiteln noch genauer betrachtet. //Schutzklassenangabe unter C++ class MyClass { public: void Method1(); void Method2(); private: void PrivatMethod1(); };
Angabe der Schutzklasse bei C++

C#
42

2
//Schutzklassenangabe unter C# public static void Main() //explizite Angabe { ... }

Angabe der Schutzklasse unter C#

Zusammenfassung
In diesem Abschnitt haben Sie einen ersten Eindruck vom Programmiermodell .NET bekommen, die Handhabung des Entwicklungssystems kennen gelernt und erste Betrachtungen ber Konzepte wie Namensraum, Einsprungsfunktion, Assemblies und Metadaten durchgefhrt. Sie werden nun im nchsten Abschnitt die objektorientierten Features der Sprache C# kennen lernen.

Klassen und Objekte unter C#


Im vorherigen Kapitel haben Sie einen grundstzlichen Einstieg in die Sprache C#, der .NET-Laufzeitumgebung und in die Handhabung des Entwicklungssystems Visual Studio.NET gettigt. In diesem und den nchsten Abschnitten werden Sie sich auf die typisch objektorientierten Features der Sprache C# und der .NET-Laufzeitumgebung konzentrieren. Wieder in Form von kleinen, berschaubaren Beispielen werden Sie nacheinander diese Konzepte kennen lernen. Kleine Beispiele eignen sich auch hervorragend zum Experimentieren. Nutzen Sie diese Gelegenheiten, versuchen Sie mit Experimenten die Richtigkeit Ihres Denkmodells zu prfen und dieses gegebenenfalls zu korrigieren. Es wird davon ausgegangen, dass das Bruchrechnen fr die meisten Leser kein Problem darstellen wird. In den nchsten Kapiteln werden Sie anhand einer Klasse, die Bruchzahlen modelliert, die objektorientierten Features kennen lernen. Sukzessive wird diese Klasse in den nchsten Kapiteln ausgebaut und mit neuen Features versetzt. Bevor Sie aber damit beginnen, noch einige Begriffsdefinitionen zu den Bruchzahlen. Bei einer Bruchzahl (engl. fraction), wie z.B. , wird der Wert ber dem Bruchstrich Zhler (engl. numerator) und der Wert unter dem Bruchstrich Nenner (engl. denominator) genannt.

C # die neue Programmiersprache


2 Klassen
Fr die nchsten Beispiele erzeugen Sie am besten eine neue Projektmappe FractionExamples. Fgen Sie in gleicher Weise ein neues C#-Projekt hinzu, wie Sie es im Einfhrungsbeispiel kennen gelernt haben. Das Projekt soll den Namen Fraction erhalten. (Vergessen Sie nicht die Option Zu Projektmappe hinzufgen zu aktivieren.) Anschlieend erzeugen Sie eine C#Quelldatei (fraction.cs) und geben folgenden Code ein. namespace FractionExamples { class Fraction { public System.Int32 z; //Zhler public System.Int32 n; //Nenner public Fraction () { z = 0; n = 1; } public Fraction (System.Int32 z) { this.z = z; n = 1; } public Fraction ( System.Int32 z, System.Int32 n) { this.z = z; this. n = n; } public void WriteToConsole() { System.Console.WriteLine("{0}/{1}", z, n); } } }
CD-Beispiel Fraction1

43

C#
44

2
//Hauptprogramm fr Testzwecke class App { public static void Main() { FractionExamples.Fraction r1 = new FractionExamples. Fraction (); FractionExamples. Fraction r2 = new FractionExamples. Fraction (3); FractionExamples. Fraction r3 = new FractionExamples. Fraction (6,5); r1.WriteToConsole(); r2.WriteToConsole(); r3.WriteToConsole(); } } Ihnen ist bereits die einzig statische Methode mit dem Namen Main() bekannt, die die Einsprungsmethode der Applikation darstellt. Die Klasse, die diese Methode hlt, wurde hier beliebig App genannt. Der neue Datentyp wurde mit dem Schlsselwort class in einem eigenen Namensraum definiert (FractionExamples) erzeugt. Der Datentyp erhielt den Namen Fraction. Die Klasse Fraction modelliert eine Bruchzahl mit einem Zhler und einem Nenner ber die Member-Variablen z (Zhler) und n (Nenner). Hier wird fr den Klassennamen die englische Bezeichnung und fr die Member-Variablen die deutsche Bezeichnung verwendet. Dies ist zwar nicht konsequent, da aber im deutschen Sprachraum die Bezeichnungen numerator und denominaor vollkommen ungebruchlich sind, ist dies gerechtfertigt. Ihnen ist sicherlich die ungewohnte Definition der Member-Variablen z und n aufgefallen. Im Namensraum System existieren die Grunddatentypen (primitive types). System.Int32 ist ein solcher Typ. Unter .NET und den .NET-Sprachen, sind also auch die Grunddatentypen Objekte (dies gilt ja bekanntlich nicht bei C++). public System.Int32 z; public System.Int32 n;

C # die neue Programmiersprache


2
Weiter erkennt der gelernte C++-Programmierer drei Konstruktoren zur Initialisierung einer Variablen vom Typ Fraction einmal ohne, einmal mit einem und einmal mit zwei Initialisierungsparametern. Unter C++ knnte man mittels initialisierten Parametern den Codierungsaufwand verkrzen. Unter C# gibt es aber keine initialisierten Parameter und daher ist die explizite Codierung aller drei Konstruktoren notwendig. public Fraction(System.Int32 z, System.Int32 n) { this.z = z; this.n = n; } Interessant ist hier die Implementierung. Der Konstruktor verwendet als Parameternamen z und n, also dieselben Namen wie die der Member-Variablen. In der Implementierung berblenden diese lokalen Parametervariablen die Member-Variablen. Aber unter C# ist es mglich, mit dem Schlsselwort this auf die Member-Variablen zu verweisen. Unter C++ ist this ein Zeiger und daher die Verwendung des Pfeiloperators notwendig, aber unter C# gibt es keine Syntax fr Zeiger, daher kann und muss der Punktoperator verwendet werden. public void WriteToConsole() { System.Console.WriteLine("{0}/{1}",z,n); } Die Klasse Fraction implementiert auch eine Methode WriteToConsole() zur Ausgabe einer Bruchzahl. Eine Ausgabe auf die Konsole geschieht per WriteLine der Klasse Console im Namensraum System. Diese Methode kann hnlich printf(...) eine variable Anzahl von Parametern aufnehmen. hnlich den Formatelementen unter C (%d, %f, etc.) knnen im Formatstring Platzhalter fr die zu formatierenden Werte angegeben werden. Dies geschieht in Form eines nullbasierenden Index, der in geschweifter Klammer angegeben wird {0} bezieht sich demnach auf den ersten spezifizierten Parameter, {1} auf den zweiten und so weiter. Nun aber zum Hauptprogramm.

45

C#
46

2
public static void Main() { FractionExamples.Fraction r1 = new FractionExamples. Fraction (); FractionExamples. Fraction r2 = new FractionExamples. Fraction (3); FractionExamples. Fraction r3 = new FractionExample. Fraction (6,5); r1.WriteToConsole(); r2.WriteToConsole(); r3.WriteToConsole(); } Dieser Teil ist auch fr erfahrene C++-Programmierer erklrungsbedrftig, da hier doch neue, von C++ erheblich abweichende Konzepte verwendet werden. Sie mssen wissen, dass unter C# smtliche Datentypen, die mit dem Schlsselwort class definiert wurden, Referenzen darstellen. Mit der Anweisung FractionExamples.Fraction r1; wird nicht, wie Sie vielleicht vermuten, eine Variable angelegt, sondern nur Speicherplatz fr eine Referenz auf r1. Wenn Sie jetzt an einen Zeiger denken, dann liegen Sie absolut nicht falsch. Es wird Speicherplatz auf dem Stack (!) fr einen Zeiger auf ein Fraction-Element alloziert. Ein Fraction-Objekt existiert allerdings noch nicht! Ein Fraction-Element erzeugen Sie ber den new Operator. Durch die Angabe der Konstruktorparameter wird dann der entsprechende Initialisierungscode verwendet. (Beachten Sie, dass auch fr den Default-Konstruktor die Klammern verwendet werden mssen!). Das Objekt wird aber nicht auf dem Stack angelegt, sondern, wie auch bei C++ bei der Verwendung des new-Operators, auf dem Heap. Dieser Heap wird von der .NET-Umgebung verwaltet und ab sofort managed heap genannt. C++-Programmierer sind gewohnt, mit new allozierte Speicherbereiche wieder freizugeben. Dies ist unter .NET nicht notwendig, weil diese Arbeit die .NET-Laufzeitumgebung bernimmt.

C # die neue Programmiersprache


2
47

Referenzobjekt Abb. 2.8

Die .NET-Laufzeitumgebung hat einen Garbage Collector (GC) implementiert, zu deutsch Mllsammler, der diese Arbeit bernimmt. Sie fragen sich nun vielleicht, wann dies geschehen wird. Dies kann deterministisch nicht vorausgesagt werden. Prinzipiell gilt: Wenn keine Referenz mehr auf ein Objekt existiert, darf der GC den Speicherplatz wieder freigeben. Im Beispiel kann der GC das Objekt auflsen, wenn sich der betreffende Stack-Bereich auflst, oder im Programmierjargon, wenn der Scope (Gltigkeitsbereich) der Funktion (oder Methode) verlassen wird. Noch einmal zusammengefasst, und weil das fr Ihr Denkmodell wichtig ist: Alle Objekte, deren Klasse mit dem Schlsselwort class definiert wurde, sind auf dem managed heap angelegt. Der Programmierer hat den Zugriff auf das Objekt in Form einer Referenz (Zeiger). Da dies der Standard-Zugriff ist, ist auch keine explizite Syntax fr Zeiger notwendig. Es wird daher immer und berall der Punktoperator verwendet. r1.WriteToConsole(); r2.WriteToConsole(); r3.WriteToConsole(); Hier wird also die Methode WriteToConsole() ber die Objekte r1,r2 und r3 aufgerufen und die Werte auf dem Bildschirm ausgegeben.

C#
48

2
Ein kleines Experiment soll Ihr Verstndnis noch verbessern.
CD-Beispiel Fraction2

public static void Main() { FractionExamples.Fraction r1 = new FractionExamples.Fraction (); FractionExamples.Fraction r2; r1.WriteToConsole (); //Ausgabe von r1

r2 = r1; //r1 und r2 zeigen nun auf dasselbe Objekt r2.WriteToConsole (); //dieselbe Ausgabe wie r1 r2.z = 1; //wir ndern das Objekt ber r2 r1.WriteToConsole(); //Ausgabe des Objektes ber r1 } Bei diesem Beispiel wurde eine Referenz r2 definiert, aber diese Referenz zeigt auf kein Objekt. Der Kompiler wird hier einen Fehler ausgeben, wenn Sie versuchen wrden, auf r2 eine Aktion durchzufhren. Sie knnen aber r2 als Referenz eines bestehenden Objektes verwenden. Die Zeile r2 = r1; //r1 und r2 zeigen nun auf dasselbe Objekt

Zwei Referenzen auf dasselbe Objekt Abb. 2.9

C # die neue Programmiersprache


2
fhrt dies durch. Dass hier r2 und r1 tatschlich auf dasselbe Objekt verweisen, wird in diesem Beispiel gezeigt, indem das Objekt ber r2 verndert wird, die Ausgabe aber ber r1 erfolgt. Zur Verdeutlichung wird hier der Sachverhalt noch einmal grafisch dargestellt.

49

Grunddatentypen
Im Beispiel wurde der Datentyp System.Int32 verwendet. Das ist ein Grunddatentyp, den die .NET-Laufzeitumgebung im Namensraum System anbietet. C# erlaubt aber auch, statt System.Int32 das jedem C/C++-Programmierer bekannte Schlsselwort int zu verwenden. Das Schlsselwort int ist aber nichts anderes als ein Synonym der Sprache C# fr System.Int32. Weitere Grunddatentypen der .NET-Laufzeitumgebung und die entsprechenden C#-Synonyme sind in der folgenden Tabelle dargestellt.
.NET-Datentyp
System.Boolean System.Char System.Sbyte System.Int16 System.Int32 System.Int64 Sytem.Byte System.UInt16 System.UInt32 System.UInt64 System.Single System.Double System.Decimal System.String

C#-Schlsselwort
bool char sbyte short int long byte ushort uint ulong float double decimal string

Beschreibung
True oder False Unicode (2Byte) Zeichen ganzzahliger Typ (1 Byte) mit Vorzeichen -128 bis +127 ganzzahliger Typ (2 Byte) mit Vorzeichen ganzzahliger Typ (4 Byte) mit Vorzeichen ganzzahliger Typ (8 Byte) mit Vorzeichen ganzzahliger Typ (1 Byte) ohne V*orzeichen 0 255 ganzzahliger Typ (2 Byte) ohne Vorzeichen ganzzahliger Typ (4 Byte) ohne Vorzeichen ganzzahliger Typ (8 Byte) ohne Vorzeichen Fliekommazahl (32 Bit) Fliekommazahl(64Bit) Fliekommazahl (96Bit) Stringtyp (Unicode)

Tab. 2.1: C#-Schlsselwrter fr .NET-Grunddatentypen

C#
50

2
Verwenden Sie die C#-Schlsselwrter statt den generischen .NET-Typen. Die Lesbarkeit des Codes wird dadurch deutlich verbessert. .NET fasst diese Grunddatentypen unter dem Namen CTS (common type system) zusammen. Alle .NET-Sprachen (C#, VB.NET, MC++ etc.) mssen diese gemeinsamen Typen untersttzen. Die Syntax der speziellen Sprachen fr Grunddatentypen verweist immer auf einen der Typen im CTS. Dies ist eine wichtige Voraussetzung fr die Interoperabilitt zwischen .NET-Sprachen.

Schlsselwort using
Ebenfalls eine Verbesserung der Lesbarkeit ergibt die Verwendung des Schlsselwortes using. Damit kann ein Namensraum geffnet werden, sodass fr Klassen in diesem Namensraum die explizite Angabe des Namensraumes nicht mehr notwendig ist. Mit der Verwendung des Schlsselwortes using sowie der C#-Schlsselwrter fr die Grunddatentypen schaut der Code nun so aus:
CD-Beispiel Fraction3

using System; using FractionExamples; namespace FractionExamples { class Fraction { public int z; //Zhler public int n; //Nenner public Fraction () { z = 0; n = 1; } public Fraction (int z) { this.z = z; n = 1; } public Fraction (int z, int n)

C # die neue Programmiersprache


2
{ this.z = z; this. n = n; } public void WriteToConsole() { Console.WriteLine("{0}/{1}", z, n); } } } //Hauptprogramm fr Testzwecke class App { public static void Main() { Fraction r1 = new Fraction (); Fraction r2; r1.WriteToConsole(); //Ausgabe von r1 r2 = r1; //r1 und r2 zeigen nun auf dasselbe Objekt r2.WriteToConsole(); //dieselbe Ausgabe wie r1

51

r2.z = 1; //wir ndern das Objekt ber r2 r1.WriteToConsole();//Ausgabe des Objektes ber r1 } }

System.Object
Unter .NET mssen alle Klassen von System.Object abgeleitet sein. Unter C# ist dies implizit durch die Definition mit dem Schlsselwort class geschehen. Damit erbt jeder Datentyp schon eine Menge von diesem Datentyp. Interessant ist jetzt natrlich zu wissen, welche Methoden und Eigenschaften die Klasse System.Object implementiert.

C#
52

2
Boolean Equals(Object) Int32 GetHashCode() Testet auf Gleichheit und gibt True zurck, wenn gleich Die Methode GetHashCode gibt eine eindeutige, das Objekt bezeichnende ganzzahlige Zahl zurck. Wenn zwei Objekte desselben Typs gleich sind, dann haben diese auch denselben hash code. Kann vielfltig verwendet werden (z.B. Sortieralgorithmus etc.). Gibt ein Type-Objekt zurck. Damit kann dann direkt auf die Metadaten des Typs zurckgegriffen werden. Mehr dazu spter! Gibt den Namen der Klasse als String zurck. System.WriteLine verwendet brigens die Methode ToString() eines Objektes zur Ausgabe!

Type GetType()

String ToString()

Experimentieren Sie mit den Methoden der Basisklasse: Console.WriteLine(r1.GetHashCode()); Console.WriteLine(r1.ToString()); Console.WriteLine(r1.Equals(r2)); Console.WriteLine(r1.GetType()); Sie erhalten eine Ausgabe hnlich der folgenden: 4 DotNetSeminar.Fraction True DotNetSeminar.Fraction Bis auf die Methode GetType() knnen alle in der abgeleiteten Klasse berschrieben werden. Dies geschieht durch das Schlsselwort override. Dazu werden Sie spter noch einiges mehr hren. Im Beispiel ist es sinnvoll, die Methode ToString() zu berschreiben, anstatt eine Methode WriteToConsole() zu implementieren. Dies kann dann wie folgt geschehen:
CD-Beispiel Fraction4

override public String ToString() { string s;

C # die neue Programmiersprache


2
s = String.Format("{0}/{1}",z,n); return s; } Die Klasse Fraction kann nun ganz natrlich von der Console.WriteLine(...)-Methode verwendet werden, da WriteLine immer die ToString (...)-Methode auf die Objekte anwendet. Console.WriteLine(r1); Console.WriteLine( "Die rationale Zahl hat den Wert {0}",r1); Das berschreiben von virtuellen Methoden folgt spter noch im Detail.

53

Strukturen vs. Klassen


Im Einfhrungsbeispiel haben Sie einen ersten Eindruck bekommen, wie Objekte im Speicher verwaltet werden. Wesentlicher Kernpunkt der CLR (Common Language Runtime) ist der managed heap (zu deutsch verwaltete Halde). Sie haben auch festgestellt, dass smtliche Objekte vom Typ class im managed heap angelegt werden und dass auf dem Stack eine Referenzvariable entsteht. class FractionExamples { public int z; public int n; ... }

Referenzobjekt Abb. 2.10

C#
54

2
Dies gilt fr alle Typen, die von System.Object abgeleitet sind. Unter C# geschieht dies implizit durch das Schlsselwort class. Bezglich der Auflsung des Objektes muss sich der Programmierer keine Gedanken machen, dies wird vom Garbage Collector durchgefhrt (sobald im Stack keine Referenzvariablen existieren, darf der GC das Objekt im managed heap auflsen). Der Namensraum System besitzt aber auch noch eine andere Klasse, nmlich System.ValueType. Eine Ableitung von dieser Klasse hat ein anderes Verhalten zur Folge. Damit wird kein Objekt erzeugt, das sich auf dem managed heap befindet und eine Referenzvariable auf dem Stack hat, sondern das Objekt wird direkt im Stack angelegt. Unter C# werden solche Typen mit dem Schlsselwortes struct definiert. struct Fraction { public int z; public int n; ... }

Wertobjekt Abb. 2.11

Wie aus der Grafik ersichtlich, wird das Objekt nicht im managed heap angelegt, sondern direkt auf dem Stack. Das Objekt lst sich natrlich auf, wenn der Stack stirbt. Eine solches Objekt nennt sich Wertobjekt (valued object) im Gegensatz zum Referenzobjekt (referenced object). Unter C# mssen Sie

C # die neue Programmiersprache


2
also bei der Typdeklaration entscheiden, ob Objekte eines Typs als Referenzobjekte oder aber als Wertobjekte auftreten werden. Das ist unter C/C++ gnzlich anders. Dort entscheiden die Speicherklasse und der Ort der Variablendefinition, ob eine Variable auf dem Stack oder aber auf dem Heap angelegt wird. Dynamisch allozierte Variablen oder Objekte werden unter C++ immer auf dem Heap angelegt. Beachten Sie diesen Unterschied! Auerdem sollten Sie erkennen, dass das Schlsselwort struct unter C# eine vollkommen andere Bedeutung hat, als bei C und C++. Es stellt sich nun naturgem die Frage, wann soll ein Datentyp mittels class definiert werden bzw. wann mittels struct? Die Klasse System.ValueType berschreibt die virtuellen Methoden von System.Object in einer Form, die dem Verhalten von Wertvariablen entsprechen. Dies gilt im Besonderen auch fr die Zuweisung. Fhren Sie hierzu einige Experimente durch. using System; using FractionExamples; class Fraction { public int z; public int n; .. . } public static void Main() { Fraction r1 = new Fraction(3); Fraction r2; Console.WriteLine(r1); r2 = r1; r2.z = 5; //wir ndern das Objekt ber r2 Console.WriteLine(r1); //wir geben das Objekt ber r1 auf den //Bildschirm }

55

C#
56

2
Sie kennen das Verhalten des ursprnglichen Beispiels. Sie definieren nun aber die Klasse Fraction mit dem Schlsselwort struct. Der Kompiler meckert noch mit einer Fehlermeldung, dass bei Werttypen kein expliziter Default-Konstruktor implementiert werden kann. Sie entfernen diesen Code und starten einen neuen Kompiliervorgang.
CD-Beispiel Fraction5

struct Fraction { public int z; public int n; /*public Fraction () { z = 0; n = 1; }*/ ... } public static void Main() { Fraction r1 = new Fraction (3,2); Fraction r2; Console.WriteLine(r1); r2 = r1; //r2 wird mit den Werten von r1 belegt r2.z = 5; //wir ndern r2 Console.WriteLine(r1); //keine nderung von r1 feststellbar } Sie sehen, es handelt sich bei r1 und r2 tatschlich um voneinander unabhngige Objekte! Dieses Verhalten wird gerne bei primitiven Datentypen gesehen. Bei einer Zuweisung wird der Wert kopiert, und nicht die Referenz. Beim Datentyp Fraction wird man eher dieses Verhalten erwarten, und aus diesem Gesichtspunkt ist es sicherlich berlegenswert, Fraction mit struct denn mit class zu definieren. brigens sind die meisten Grunddatentypen der .NET-Laufzeitumgebung Werttypen.

C # die neue Programmiersprache


2 Boxing und Unboxing
In vielen Situationen kommt es vor, dass ein Werteobjekt das Verhalten eines Referenzobjektes haben sollte. Es ist mglich, ein Werteobjekt in eine Referenzinstanz zu konvertieren. Dieser Vorgang wird boxing genannt (der umgekehrte Vorgang wird unboxing genannt). Ein kleines Beispiel zum Experimentieren. Fraction sei mittels struct definiert worden und daher ein Werttyp. public static void Main() { Fraction r1 = new Fraction(1); //Wertobjekt (Stack) Fraction r2; //Wertobjekt (Stack) System.Object o1; //Referenz (Objekt //existiert noch nicht) object o2; //detto o1 = r1; o2 = o1; //boxing findet statt (Heap) //Referenzzuweisung //(auf dasselbe Objekt)
CD-Beispiel Fraction6

57

r2 = (Fraction)o2; //unboxing findet statt r1.z = 2; r2.z = 3; //Aenderung von r1 //Aenderung von r2

Console.WriteLine(r1); Console.WriteLine(o1); Console.WriteLine(o2); Console.WriteLine(r2); } r1 wird auf dem Stack angelegt und mit 1/1 vorbelegt. Auerdem wird auf dem Stack Speicherplatz fr r2 angelegt. Auf dem Stack werden dann zwei Referenzen (Zeigervariablen) vom Typ System.Object (object) angelegt. Bei der Zeile o1 = r1 findet nun dieses boxing statt. Syntaktisch weisen Sie hier einer Referenz einen Wert zu. Was passiert nun? Auf dem managed heap wird Speicherplatz angelegt und dieser wird mit den Daten des Speicherplatzes auf dem Stack (r1) belegt. o2 = o1 ist Ihnen aus dem Einfhrungsbeispiel bekannt, o2 zeigt auf dasselbe Objekt wie o1.

C#
58

2
Bei r2 = (fraction)o2 findet ein unboxing statt. Der Speicherplatz auf dem Stack (r2) wird nun mit den Daten des Objektes, das sich auf dem Heap befindet belegt. Die Ausgabe, die das Beispiel auf der Konsole erzeugt, demonstriert das Verhalten deutlich. Interessant ist auch die Zeile Console.WriteLine(o1). Es wird nmlich fr die Ausgabe die ToString(...)-Methode von Fraction verwendet. Ein deutlicher Hinweis, dass die Methode ToString() der Klasse System.Object eine virtuelle Methode darstellt. Mehr dazu aber spter. Boxing kann implizit und explizit geschehen. Explizites boxing ist syntaktisch dem Casting von C/C++ hnlich, aber natrlich viel typsicherer. Implizites (automatisches) boxing hat auch einen ganz besonderen syntaktischen Effekt, der nur mit dem boxing-Konzept erklrbar ist. 3.ToString(); 3 ist ganz klar ein Wertobjekt und muss daher ein boxing erfahren, wird somit zu einem Objekt erhoben, und dann wird die Methode ToString() ausgefhrt wird. Warum Console.WriteLine(3); funktioniert, sollte nun auch klar sein. In den weiteren Beispielen wird der Datentyp Fraction wieder mit dem Schlsselwort class definiert.

Enumerationen
Aufzhlungen (Enumerationen) kennen Sie sicherlich aus den Programmiersprachen C und C++. Es handelt sich dabei um ganzzahlige Typen, die vom Benutzer definiert werden knnen. Enumerationsinstanzen knnen dann nur Werte zugeordnet werden, die in der Definition der Enumeration festgelegt wurde. Darber hinaus knnen diesen ganzzahligen Werten auch noch anwendungsfreundliche Namen zugeordnet werden. Unter C/C++ konnten Enumerationsinstanzen auch direkt die ganzzahligen Werte zugeordnet werden. Der Kompiler hat dies akzeptiert, was oft zu Fehlern fhrte, weil unabsichtlich

C # die neue Programmiersprache


2
Werte zugeordnet werden konnten, die in der Enumerationsdefinition gar nicht vorgekommen sind. Unter C# ist dies nicht mehr mglich. Hier ein kleiner Codeausschnitt, der Ihnen die Funktionsweise von Enumerationen unter C# verdeutlichen soll. using System; public enum Week { Monday=0, Tuesday=1, Wednesday=2, Thursday=3, Friday=4, Saturday=5, Sunday=6 } class App { public static void Main() { Week Day = Week.Monday; //int iDay = Week.Monday; nicht erlaubt!!!! //Day = 5; ebenfalls nicht erlaubt!!!!! switch(Day) { case Week.Monday: //mach etwas break; case Week.Thursday: //mach etwas break; } } }
CD-Beispiel Enumeration1

59

C#
60

Methoden
Methode mit Bindung
Fgen Sie dem Beispiel eine Methode zur Addition von Bruchzahlen hinzu. (Fraction soll wieder als Referenztyp definiert werden). Aus der Grundschule kennen Sie sicherlich noch den Algorithmus, der in der Methode Add implementiert ist.
CD-Beispiel Fraction7

public Fraction Add(Fraction r) { Fraction erg = new Fraction(); erg.z = n*r.z + z*r.n; erg.n = n * r.n; return erg; } Im Gegensatz zu C++ ist die Angabe der Schutzklasse explizit bei jeder Methode notwendig (public). Rckgabewert der Methode ist vom Typ Fraction (Ergebnis der Addition). In der Implementierung wird eine Ergebnisvariable erzeugt, die das Additionsergebnis aufnimmt und zurckgibt. Der Aufruf der Methode kann dann wie folgt geschehen: class App { public static void Main() { Fraction r1 = new Fraction (3,2); Fraction r2 = new Fraction (5,2); Fraction r3; r3 = r1.Add(r2); Console.WriteLine(r3); } } Als Ergebnis der Addition sollte dann 16/4 am Bildschirm erscheinen (krzen kann die Applikation leider noch nicht).

C # die neue Programmiersprache


2 Statische Methode
Die Additionsmethode knnten Sie auch statisch implementieren. public static Fraction Add(Fraction r1,Fraction r2) { Fraction erg = new Fraction(); erg.z = r1.z*r2.n + r1.n*r2.z;; erg.n = r1.z * r2.n; return erg; } Sie wissen, eine statische Methode hat keine Bindung zu einem Objekt. Der Aufruf im Hauptprogramm: public static void Main() { Fraction r1 = new Fraction (3,2); Fraction r2 = new Fraction (5,2); Fraction r3; Fraction r4; r3 = r1.Add(r2); r4 = Fraction.Add(r1,r2); Console.WriteLine(r3); Console.WriteLine(r4); } Sie sehen, bei der statischen Methode ist natrlich die Angabe des Namensraumes notwendig. Es ist eine Geschmackssache, welche Variante lesbarer ist, in der numerischen Algebra mag die statische Variante syntaktisch vielleicht einen Vorteil haben. Aber wie Sie sehen, knnen auch beide Varianten gleichzeitig implementiert werden.
CD-Beispiel Fraction7

61

berladen von Methoden


Wie unter C++ knnen auch unter C# Methoden berladen werden, d.h. es knnen gleiche Methodennamen verwendet werden, diese mssen sich aber in der Anzahl bzw. Typen der Parameter unterscheiden. Genau dss ist im Beispiel bei der Implementierung der Additionsmethode passiert.

C#
62

2 berladen von Operatoren


hnlich C++ knnen auch unter C# hinter Operatoren Methoden definiert werden, die bei Verwendung des Operators im richtigen Kontext aufgerufen werden. Vor allem bei mathematischen Typen ist das oft sinnvoll. So wird im nchsten Beispiel der +-Operator mit der Additionsfunktionalitt berlagert.
CD Bespiel Fraction8

public static Fraction operator+( Fraction r1, Fraction r2) { Fraction erg = new Fraction (); erg.z = r1.z*r2.n + r1.n*r2.z; erg.n = r1.n * r2.n; return erg; } public static void Main() { Fraction r1 = new Fraction (3,2); Fraction r2 = new Fraction (5,2); Fraction r3; Fraction r4; Fraction r5; //Aufruf ber statische Methode r3 = Fraction.Add(r1,r2); //Aufruf ber Methode mit Bindung r4 = r1.Add(r2); //berladener Operator + r5 = r1+r2; Console.WriteLine(r3); Console.WriteLine(r4); Console.WriteLine(r5); Console.WriteLine(r1+r2); } Aber auch Operatoren knnen mehrfach berladen werden. Im folgenden Beispiel wird der +-Operator so berladen, dass ein Fracition-Typ und eine Int32-Typ miteinander addiert werden knnen. public static Fraction operator+( Fraction r1, int i2) {

C # die neue Programmiersprache


2
Fraction r2 = new Fraction (i2); return r1+r2; } Somit ist auch folgende Anweisung mglich: r6 = r1 + 5; Beachten Sie als C++-Programmierer aber, dass berlagerte Operatormethoden immer(!) static und public vereinbart sein mssen.

63

Vergleich von Objekten


Es ist auch mglich, die Operatoren == und != zu berladen. Hierzu aber erst einige Gedanken. Die Default-Implementierung von == hat folgendes Verhalten: Bei Referenzobjekten wird true zurckgegeben, wenn mit den Referenzen dasselbe Objekt verwiesen wird. Bei Wertobjekten wird true zurckgegeben, wenn die Werte bereinstimmen. Sie sehen daraus, dass ein Vergleich der Speicherbereiche auf dem Stack stattfindet. Dieses Verhalten knnen Sie natrlich berlagern. Im FractionBeispiel ist es sicherlich sinnvoll, bei Gleichheit des Inhaltes true zurckzugeben. Hierzu aber noch mehr unter bei der Basisklasse System.Object.

Nicht berladbare Operatoren


Beachten Sie, dass einige Operatoren, die unter C++ berladbar sind, unter C# nicht berladen werden knnen. Das sind: &&, || , (), [ ], = , -> , new Auffallend ist, dass auch die Zuweisung nicht berladbar ist, was aber nach genaueren berlegungen auch nicht notwendig ist (in C++ aber sehr wohl). Der [ ]-Operator (Indexoperator) kann ebenfalls nicht berladen werden, aber es gibt hier unter C# das Konzept des indexers, das Sie in einem spteren Kapitel kennen lernen werden. berlagern Sie nun die weiteren Operatoren, die binren Operatoren (diese brauchen zwei Operanden) +,-,*,/ sowie die

C#
64

2
unren Operatoren und ~, hinter denen Sie den Kehrwert implementieren. Auf der Begleit-CD dieses Buches finden Sie die Lsungen im Projekt Fraction8.

ref- und out- Parameter


Angenommen, Sie wollen in der Klasse Fraction eine Methode implementieren, die mit einem Aufruf die Werte der MemberVariablen z und n zurckgeben sollen. Dass eine Implementierung in der Form public void GetZN(int z, int n) { z = this.z; n = this.n; } mit folgendem Aufruf nicht zielfhrend ist, wird einem erfahrenen C/C++-Programmierer klar sein. public static void Main() { Fraction r1 = new Fraction(3,2); int z = 0; int n = 0; Console.WriteLine(z); Console.WriteLine(n); r1.GetND(z,z); Console.WriteLine(z); Console.WriteLine(n); } Da die Typen System.Int32 und damit int Wertobjekte darstellen (weil mittels struct definiert), ist dieses Verhalten erklrbar. Werte werden bei Funktionsbergabe kopiert. In der Implementierung werden also die Kopien manipuliert, was aber dann zur Folge hat, dass die eigentlich bergebenen Parameter keine nderung erfahren haben. In C/C++ kennen Sie die Lsung, Sie bergeben Adressen bzw. deklarieren den/die Parameter als C++-Referenzen. Unter C# gibt es einen hnli-

C # die neue Programmiersprache


2
chen Mechanismus. Obiges Beispiel mssten Sie wie folgt richtig implementieren: public void GetZN(ref int z,ref int n) { z = this.z; n = this.n; } public static void Main() { Fraction r1 = new Fraction(3,2); int z = 0; int n = 0; Console.WriteLine(z); Console.WriteLine(n); r1.GetZN(ref z, ref n); Console.WriteLine(z); Console.WriteLine(n); } Mit dem Schlsselwort ref geben Sie an, dass hier technisch eine Referenz und nicht der Wert bergeben werden sollte. Auffallend ist aber, dass auch beim Aufruf das Schlsselwort ref explizit angegeben werden muss! Was ist aber der Fall, wenn der Parameter, der in der Methode eine nderung erfahren sollte, ein Referenztyp ist? Dann ist das Schlsselwort ref nicht notwendig, weder bei der Implementierung noch beim Aufruf. Nachfolgend wird Ihnen das an einem zugegebenermaen akademischen Beispiel demonstriert. class App { public static void DoubleFraction(Fraction r) { r.n = r.n * 2; } public static void Main() {
CD-Beispiel Fraction9 CD-Beispiel Fraction9

65

C#
66

2
Fraction r1 = new Fraction(3,2); DoubleFraction(r1); Console.WriteLine(r1); ... } } Im Namensraum App wird eine statische Funktion DoubleFraction implementiert, die als bergabeparameter einen Typ Fraction (Referenztyp) hat und verdoppelt intern die MemberVariable z. Verifizieren Sie im Beispiel, dass r1 tatschlich verdoppelt! Vielleicht ist Ihnen aufgefallen, dass im obigen Beispiel die Variablen z und n mit 0 vorbelegt wurden. int z = 0; int n = 0; Geschieht dies nicht (keine Initialisierung), dann meckert der Kompiler mit einer entsprechenden Fehlermeldung. public static void Main() { Fraction r1 = new Fraction(3,2); int z; //nicht explizit vorbelegt int n; r1.GetZN(ref z, ref n); Console.WriteLine(z); Console.WriteLine(n); } Bei Wertetypen unangenehm, denn was ist, wenn Sie eine Methode implementieren wollen, dessen Aufgabe es eben ist, ein Objekt zu initialisieren. Dafr wurde das Schlsselwort out eingefhrt. public { z = n = } public void GetZN(out int z, out int n) this.z; this.n; static void Main()

C # die neue Programmiersprache


2
{ Fraction r1 = new Fraction(3,2); int z; int n; r1.GetZN(out z,out n); Console.WriteLine(z); Console.WriteLine(n); } Wenn die Parameter mit dem Schlsselwort out gekennzeichnet sind, dann akzeptiert der Kompiler dies, auch wenn die Variablen z und n nicht initialisiert wurden. Der Kompiler erkennt, dass dies beim Methodenaufruf GetZN(...) geschieht. Dass hier implizit eine Referenzbergabe erfolgt, ist selbstverstndlich.

67

Eigenschaften (Properties)
Im deutschsprachigen Raum versteht man unter den Eigenschaften von Klassen im Wesentlichen die Member-Variablen. Die Klasse Fraction besitzt zwei Eigenschaften vom Typ int, die auch public vereinbart sind, und somit jederzeit direkt von auen vernderbar sind. Das kann aber in vielen Fllen nicht gut sein, da damit ein Programmierer auch Werte zuordnen knnte, die logisch nicht erlaubt sind. So kann einem Programmierer nicht verboten werden, den Nenner eines Objektes vom Typ Fraction in seinem Programm explizit auf 0 zu stellen, was natrlich mathematisch Unfug ist. Die Lsung hierzu besteht darin, dass diese Eigenschaften private vereinbart werden, und damit ein direkter Zugriff nicht mehr mglich ist. class Fraction { private int z; private int n; ... } Nun wird der Kompiler mit einer Fehlermeldung aufwarten, sobald Sie versuchen werden, z oder n eines Objektes zu ndern. Sollten aber nderungen direkter Members mglich sein,

C#
68

2
dann mssen hier eben entsprechend Methoden angeboten werden, die z.B. so aussehen knnten: public int GetZ() { return z; } public int GetN() { return n; } public void SetZ(int z) { this.z = z; } public void SetN(int n) { //0 fr den Nenner ist verboten if(n == 0) n=1; this.den = n; } Damit kann nun innerhalb der Methoden reagiert werden, wenn es zu einer Zuweisung von ungltigen Werten kommt. (Zugegeben, die Implementierung von SetN(...) ist nicht zufriedenstellend. Die Verwendung des Exception-Mechanismus wrde sich hier anbieten. Dazu aber spter mehr.) Nachteilig an dieser Variante ist, dass die Lesbarkeit der Programme doch deutlich abnimmt. C# bietet aber hier einen neuen Mechanismus an, der nachfolgend vorgestellt wird:
CD-Beispiel Fraction10

private int z; private int n; public int N { get { return n; } set {

C # die neue Programmiersprache


2
n=value; if(n==0) n=1; } } Drei neue Schlsselwrter lernen Sie hier kennen. In den Blcken, die mit set bzw. get eingeleitet werden, wird die Funktionalitt programmiert, die durchgefhrt werden soll, wenn syntaktisch auf N zugegriffen wird. Im lesenden Zugriff (get) wird hier unmittelbar der Wert der privaten Member-Variable n zurckgegeben. Bei einem schreibenden Zugriff auf N (set) kann ber das Schlsselwort value auf den Wert zugegriffen werden, der bei einer Zuweisung auf die Member-Variable N verwendet wird. Fraction r = new Fraction(3,2); r.N = 0; //Schreibender Zugriff int n = r.N; //Lesender Zugriff Was nun ausschaut wie der Zugriff auf eine Member-Variable ist eigentlich ein Methodenaufruf. Wenn Sie auf den set-Block verzichten, dann kann nur lesend auf N zugegriffen werden, wenn Sie nur den set-Block implementieren, dann htten Sie nur schreibenden Zugriff auf N. Die Verwendung von Properties sind in der gezeigten Form sehr zu empfehlen, da diese die Lesbarkeit des Codes doch deutlich steigern. Im Deutschen wird der Ausdruck Eigenschaft gerne im Zusammenhang mit Member-Variablen verwendet. Deshalb wird hier der Terminus Eigenschaft vermieden, und das (deutschenglische) Kunstwort Member-Variable verwendet. Im Folgenden wird auch der Ausdruck Properties gebraucht.

69

Ausnahmeverarbeitung Exception Handling (EH)


Vielleicht kennen Sie den Exception-Handling (EH) Mechanismus von C++ oder auch SEH (Structured Exception Handling) des Betriebssystems Windows. Unter C++ kann das EH optional verwendet werden, da Exception-Handling unter C++ eine teure Sache in Bezug auf Laufzeit und Speicherbedarf dar-

C#
70

2
stellt. .NET und damit C# bietet ebenfalls einen Exception-Mechanismus an, der nachfolgend vorgestellt wird. In vielen Frameworks und APIs (Application Programming Interface) ist und war es sehr populr, Funktionen mit einem Rckgabewert zu versehen, der in irgendeiner Form den Erfolg des Aufrufes dokumentiert. Dies kann ein Boolscher Wert sein oder aber in Form eines Resultatwertes, der je nach Wert einen Fehler oder Status reprsentiert. C++-Programmierer, die auf dem COM-Programmiermodell entwickeln, kennen den Datentyp HRESULT (32 bit), der diese Funktion erfllt. Prinzipiell ist dieser Ansatz auch unter C# mglich, aber nicht zu empfehlen. bool f = Methode(); if(f== false) { //Fehlerbehandlung durchfhren } Es ist damit nmlich unter C/C++ und auch C# folgender Aufruf mglich. Methode(); Da der Programmierer in diesem Beispiel der Rckgabewert ignoriert, wird faktisch auf die Fehlerbehandlung gnzlich verzichtet. Und muss man sich jetzt wundern, dass Bugs in Programmen auftauchen? In der .NET-Laufzeitumgebung ist EH ein fundamentaler Bestandteil und es wird daher empfohlen, EH zu verwenden, statt Rckgabecodes, die still und heimlich auch ignoriert werden knnen. Wer den EH-Mechanismus von C++ kennt, wird alles sehr vertraut finden, trotzdem gibt es einige Unterschiede. Im Fraction-Beispiel werden Sie einen EH-Mechanismus bei der Division einfhren. Sie wissen, auch bei Bruchzahlen ist eine Division durch 0 nicht erlaubt. Ist die z-Komponente (Zhler) des Divisors 0, dann mssen Sie mit einer entsprechenden Fehlerbehandlung reagieren, da sonst ein nicht konsistentes Fraction-Objekt entsteht, das einen Nenner von 0 hat!

C # die neue Programmiersprache


2 try catch throw
public static Fraction operator/( Fraction r1, Fraction r2) { if(r2.z == 0) throw new Exception("Achtung Division durch 0"); return r1 * ~r2; //Multiplikation mit Kehrwert } Hier prft die Methode, ob der Zhler von r2 gleich 0 ist. Denn dann wrde die Bruchzahl den Wert 0 darstellen. Ist dies der Fall, dann werfen (throw) Sie eine Exception. Werfen mssen Sie ein Objekt vom Typ System.Exception oder aber davon abgeleitet. Dieses Objekt erzeugen Sie dynamisch (hierzu existiert eine grere Anzahl von Konstruktoren). public static void Main() { Fraction r1 = new Fraction (3,2); Fraction r2 = new Fraction (); // r2 hlt 0/1 Fraction r3; r3 = r1/r2; //hier tritt eine Exception auf Console.WriteLine(r3); } Wenn nun das Programm in dieser Form durchgefhrt wird, dann tritt eine Exception auf, d.h. das Programm beendet sich mit einem entsprechenden Hinweis. Nun das ist nicht gerade das Gelbe vom Ei, wenn sich das Programm, zwar mit einem Hinweis, aber endgltig verabschiedet. Sie wollen ja den Fehler behandeln bzw. auf einen Fehler entsprechend reagieren. public static void Main() { try { Fraction r1 = new Fraction (3,2); Fraction r2 = new Fraction(); // r2 hlt 0/1 Fraction r3;
CD-Beispiel Exception1

71

C#
72

2
r3 = r1/r2; //hier tritt eine Exception auf Console.WriteLine(r3); } catch(Exception e) { Console.WriteLine(e.Message); } Console.WriteLine("Hier ist das Programm fertig"); } Sie schtzen hier den Bereich im Hauptprogramm durch einen so genannten try-Block. Dieser Block wird auch guarded-Block genannt. Im Anschluss an einen try-Block folgen ein oder aber auch mehrere catch-Blcke (fangen), die eine solche Exception auffangen. D.h., wenn im guarded-Block eine Exception auftritt, dann wird unverzglich der catch-Block angesprungen und die entsprechenden Anweisungen durchgefhrt. Nach Ausfhrung dieser Anweisungen wird der Code hinter den catch-Blcken weitergefhrt. Es knnen auch mehrere catch-Blcke definiert werden, um auf unterschiedliche Arten von Exceptions auch unterschiedlich zu reagieren. Das nachfolgende Beispiel soll das verdeutlichen.
CD-Beispiel Exception2

class FractionException:Exception { string _message; int _ID; public string message { get{return _message;} } public int ID { get{return _ID;} } public FractionException(string message,int ID) { this._ID = ID; this._message = message; }

C # die neue Programmiersprache


2
} class Fraction { . . . } Hier wird eine neue Klasse FractionException definiert. Diese Klasse ist von Exception abgleitet (genaueres ber Vererbung folgt im Kapitel 3, Baugruppen (Assembly)), und kann daher auch im .NET-Exception-Mechanismus verwendet werden. Die Klasse implementiert zwei readonly-Properties (ID und message), die im Konstruktor belegt werden knnen. public static void Main() { try { Fraction r1 = new Fraction (3,2); Fraction r2 = new Fraction(); // r2 hlt 0/1 Fraction r3; r3 = r1/r2; //hier tritt eine Exception auf Console.WriteLine(r3); } catch(FractionException e) { Console.WriteLine("Custom EH: {0} ID: {1}", e.message,e.ID); } catch(Exception e) { Console.WriteLine(e.Message); } Console.WriteLine("Hier ist das Programm fertig"); } Sollte im guarded-Block eine Exeption auftreten, so ist diese vom Typ abhngig. Da die Methode Main() einen catch-Block fr eine Exception vom Typ FractionException implementiert, fllt das Programm bei Auftreten einer Fraction-Division durch 0 in diesen Block. Smtliche anderen Exception werden vom allgemeinen catch-Block abgehandelt.

73

C#
74

2
Exceptions knnen auch verschachtelt sein. Ein kleines Experiment soll auch dies wieder verdeutlichen:
CD-Beispiel Exception3

class App { public static void ExcTest(Fraction r2) { Console.WriteLine("ExcTest wird aufgerufen"); try { Fraction r1 = new Fraction(3,2); Fraction r3 = r1/r2; } catch(FractionException e) { Console.WriteLine("Custom EH: {0} ID: {1}", e.message,e.ID); } Console.WriteLine("ExcTest ist beendet"); } public static void Main() { try { ExcTest(new Fraction()); } catch(Exception e) { Console.WriteLine(e.Message); } Console.WriteLine("Hier ist das Programm fertig"); } } Die Klasse App implementiert hier die statische Methode ExcTest, die eine Division durchfhrt. Dieser Codeteil ist in der Methode selbst in einem guarded-Block geschtzt. Die Methode wird in Main(), ebenfalls in einem guarded-Block, ausgefhrt. Tritt nun eine Exception in der Methode ExcTest auf, so wird natrlich der catch-Block in der Methode selbst verwendet. Die Ausgabe auf der Konsole wird Folgende sein:

C # die neue Programmiersprache


2
ExcTest wird aufgerufen Custom EH: Achtung Division durch 0 ID: 5 ExcTest ist beendet Hier ist das Programm fertig Sie knnen allerdings in diesem catch-Block die Behandlung der Exception an den bergeordneten catch-Block weiterleiten. Mit dem Schlsselwort throw, ohne Angabe eines ExceptionObjektes, wird dann direkt auf den bergeordneten catchBlock gesprungen. catch(FractionException e) { Console.WriteLine("Custom EH: {0} ID: {1}", e.message,e.ID); throw; //Weiterreichen } Die Ausgabe auf der Konsole besttigt dies. ExcTest wird aufgerufen Custom EH: Achtung Division durch 0 ID: 5 Exception of type FractionExamples.FractionException was thrown. Hier ist das Programm fertig Beachten Sie, dass hier der Codeabschnitt in der Methode ExcTest nach dem catch-Block nicht durchgefhrt wurde, was Sie aber nicht berraschen sollte, da die Exception ja sofort weitergereicht wurde.
CD-Beispiel Exception4

75

finally
Eine immer wieder auftretende Schwierigkeit gibt es aber beim EH. Wie Sie festgestellt haben, fhrt das Programm seine Ttigkeit nach den catch-Blcken weiter. Dies kann insofern zu Schwierigkeiten fhren, wenn bestimmte Ressourcen geffnet sind (z.B. eine Datenbanksitzung), und dann wegen einer Exception nicht mehr freigegeben werden knnen. Fr solche Flle bietet C# eine Lsung mit dem Schlsselwort finally. Nachfolgendes Beispiel soll die Verwendung verdeutlichen:

C#
76

2
CD-Beispiel Exception4

public static void ExcTest(Fraction r2) { Console.WriteLine("ExcTest wird aufgerufen"); try { Fraction r1 = new Fraction(3,2); Fraction r3 = r1/r2; } catch(FractionException e) { Console.WriteLine("Custom EH: {0} ID: {1}", e.message,e.ID); throw; } finally { Console.WriteLine( "Diesen Codeabschnitt unbedingt durchfhen"); } Console.WriteLine("ExcTest ist beendet"); } Zu jedem guarded-Block kann ein finally-Block zugeordnet werden. Dieser wird aufgerufen, egal was im guarded-Block passiert. Die Ausgabe auf der Konsole besttigt dies: ExcTest wird aufgerufen Custom EH: Achtung Division durch 0 ID: 5 Diesen Codeabschnitt unbedingt durchfhren Exception of type FractionExamples.FractionException was thrown. Hier ist das Programm fertig

Performanz
Warum ist EH unter C++ aufwndig und hat groen Einfluss auf Codelnge und Laufzeit? EH unter C++ ist deshalb sehr aufwndig, da smtliche Objekte, die im guarded-Block angelegt wurden, entsprechend sicher auch aufgelst werden mssen, indem der Destruktor aufgerufen wird. Dies auch im Falle einer Exception. Wre dies nicht der Fall, dann knnten bse Speicherlecks auftreten.

C # die neue Programmiersprache


2
Ebenfalls mssen Objekte, die auf dem Stack angelegt sind, mit entsprechenden Destruktoraufrufen aufgelst werden. Stack-Unwinding wird das genannt und ist sehr aufwndig. Die Aufgabe des Kompilers ist es, hier entsprechende Vorsorge zu tragen. C++-Programme, die EH verwenden, sind meist sprbar langsamer. Das ist ein wesentlicher Grund, warum viele Programmierer auf C++ EH verzichten. Nun fragen Sie sich sicherlich, wie schaut dies unter C# aus. Nun hier ist fr die Auflsung der Objekte nicht der Kompiler zustndig, sondern der Garbage Collector (GC). Daher ist EH unter .NET um einiges schneller und der Overhead zur Laufzeit ist vernachlssigbar. Verwenden Sie deshalb unter C# EH fleiig, es wird zu deutlich stabileren Programmen fhren, ohne dass diese auch an Performanz verlieren.

77

Vererbung
Das Konzept der Vererbung (Wiederverwenden von Code ber Vererbung) ist eines der wichtigsten in der objektorientierten Softwareentwicklung berhaupt. Sie werden diese Konzepte, die C# natrlich untersttzt, anhand eines Beispiels kennen lernen, das im Buch Inside Visual C++ von David Kruglinski verwendet wurde. Dieses Beispiel eignet sich gut, um das Konzept der Vererbung zu verdeutlichen. In einem Weltraumsimulationsprogramm bewegen sich die unterschiedlichsten Massen im Raum (Sonnen, Planten, Monde, Raumschiffe etc.). Alle Massen gehorchen den physikalischen Gesetzen, unabhngig ihrer Erscheinungsform. Die gemeinsamen Eigenschaften knnten in einer eigenen Klasse definiert und implementiert werden, smtliche Erscheinungsformen werden dann diese Klasse als Basisklasse verwenden. using System; class Orbiter { protected decimal x; protected decimal y; protected decimal z; protected string name;
CD-Beispiel Orbiter1

C#
78

2
public Orbiter(string name, decimal x, decimal y, decimal z) { this.x = x; this.y = y; this.z = z; this.name = name; } public void Display() { Console.WriteLine("Orbiterobjekt {0}|{1}|{2}|{3})", name,x,y,z); } } class App { public static void Main() { Orbiter o = new Orbiter("o",100.0M,0.0M,0.0M); o.Display(); } } Erzeugen Sie ein neues Projekt, nennen Sie es Space und implementieren Sie die Klasse Orbiter. Natrlich werden Sie kein ausgewachsenes Simulationsprogramm erstellen, sondern beschrnken die Funktionalitt der Klasse auf den Namen eines Objektes sowie auf die Position des Objektes. Diese Eigenschaften (name,x,y,z) sollen Member-Variablen der Klasse Orbiter darstellen. In einem Simulationsprogramm sollten diese Krper natrlich dargestellt werden. Bezglich der Darstellung wird die Klasse auf eine Ausgabe auf die Konsole beschrnkt (DisplayMethode), die den Namen und die Position des Objektes ausgibt. (Zugegeben, Sie brauchen schon ein gutes Marketing, wenn Sie mit diesem Programm Geld verdienen wollten.) In einem ersten Versuch wird die Klasse gestestet. Beachten Sie, dass fr die Darstellung der Koordinaten der Typ decimal verwendet wird (Fliekommatyp mit 96 Bits). Konstante Werte vom Typ decimal verlangen den Buchstaben M (gro M ) im Anschluss an den Zahlenwert.

C # die neue Programmiersprache


2
Erweitern Sie das Simulationsprogramm, indem Sie zwei neue Klassen einfhren, eine fr Planeten und eine fr Raumschiffe. class Planet : Orbiter { private decimal radius; public Planet(string name, decimal x, decimal y, decimal z, decimal radius):base(name,x,y,z) { this.radius = radius; } new public void Display() { Console.WriteLine( "Planetobject {0} ({1}|{2}|{3}|r={4}km)", name,x,y,z,radius); } } class Spaceship : Orbiter { private decimal fuel; public Spaceship(string name, decimal x, decimal y, decimal z, decimal fuel):base(name,x,y,z) { this.fuel=fuel; } new public void Display() { Console.WriteLine( "Spacshipobject {0} ({1}|{2}|{3}|f={4}kg)", name,x,y,z,fuel); } }
CD-Beispiel Orbiter2

79

C#
80

2
Das Ableiten funktioniert hnlich wie in C++, indem Sie nach dem Namen der neuen Klasse einen Doppelpunkt gefolgt vom Namen der Klasse angeben, von der Sie die Eigenschaften und Funktionalitt erben wollen. class Planet : Orbiter Die zustzlichen Eigenschaften von Planet werden nun in bekannter Weise als Member-Variablen hinzugefgt. Die Klasse Planet erhlt eine zustzliche Member-Variable radius und einen eigenen Konstruktor fr die Belegung der Members. Es ist naheliegend, im Planet-Konstruktor fr die Belegung der Members, die von Orbiter geerbt wurde, den Konstruktor der Basisklasse zu verwenden. Das geschieht mit dieser Zeilen. public Planet(string name, decimal x, decimal y, decimal z, decimal radius):base(name,x,y,z) hnlich wie bei C++ wird der Basisklassenkonstruktor angegeben. Im Unterschied zu C++, wo der Name der Basisklasse angegeben wird, verwendet C# das Schlsselwort base. Die Implementierung der Methode Display() der Klasse Planet als auch der Klasse Spaceship werden nun aber entsprechend angepasst: new public void Display() { Console.WriteLine( "Planetobject {0} ({1}|{2}|{3}|r={4}km)", name,x,y,z,radius); } bzw. new public void Display() { Console.WriteLine( "Spacshipobject {0} ({1}|{2}|{3}|f={4}kg)", name,x,y,z,fuel); }

C # die neue Programmiersprache


2
Da auch schon die Basisklasse eine Methode Display() besitzt, mssen Sie das Schlsselwort new angeben, um dem Kompiler explizit zu zeigen, dass Sie die Methode berschreiben wollen. Weiterhin erwhnenswert ist die Verwendung der Schutzklasse protected fr die Members name,x,y,z in der Orbiter-Klasse. Wenn dies nicht gemacht worden wre, sondern Sie als Schutzklasse private angegeben htten, wrde sich der Kompiler bei der Methode Display der Klasse Planet mit einem Fehler melden, da auf die Members der Basisklasse direkt zugegriffen wird. Genauere Erluterungen zum Schutzkonzept folgen gleich. public static void Main() { Orbiter o = new Orbiter("o",100.0M,0.0M,0.0M); Planet p = new Planet("Jupiter", 1000.0M,200.3M,235.45M,353.2M); Spaceship s = new Spaceship("Enterprise", 20.3M,31.5M,83.8M,1000M); o.Display(); p.Display(); s.Display(); } Das Hauptprogramm zeigt die Verwendung der neuen Typen und auf der Konsole sollte sich folgende Ausgabe zeigen: Orbiterobjekt (o|100|0|0) Planetobject Jupiter (1000|200,3|235,45|r=353,2km) Spacshipobject Enterprise (20,3|31,5|83,8|f=1000kg) Sie sehen, die Display-Methode in den abgeleiteten Klassen wird berschrieben und kommt nicht zu Tage. Wollte man diese aufrufen (vorhanden ist sie ja), dann msste ein Casting auf die Basisklasse erfolgen, in der Form ((Orbiter)p).Display(); bzw. so Orbiter po = p; po.Display(); Beachten Sie bitte auch, dass im Gegensatz zu C++ unter C# eine Mehrfachvererbung nicht mglich ist!

81

C#
82

2 Virtuelle Methoden
Da sich immer wieder feststellen lsst, dass auch erfahrene C++-Programmierer sich mit dem Begriff virtuelle Methode schwer tun, wird dieses Feature an einem kleinen Beispiel demonstriert. public static void Main() { Orbiter o = new Orbiter("o",100.0M,0.0M,0.0M); Planet p = new Planet("Jupiter", 1000.0M,200.3M,235.45M,353.2M); Spaceship s = new Spaceship("Enterprise", 20.3M,31.5M,83.8M,1000M); Orbiter [] SpaceObjects; SpaceObjects = new Orbiter[3]; SpaceObjects[0] = o; SpaceObjects[1] = p; SpaceObjects[2] = s; for(int i = 0;i<3;i++) { SpaceObjects[i].Display(); } } SpaceObjects ist ein Feld (Array) aus Orbiter. Lassen Sie sich als C/C++-Programmierer nicht durch die ungewhnliche Syntax irritieren. Im nchsten Kapitel wird auf Felder nher eingegangen. Objekte lassen sich jederzeit zu Objekten der Basisklasse casten, genau so, wie Sie es schon aus C++ oder anderen objektorientierten Sprachen kennen. Die Ausgabe auf der Konsole ergibt Orbiterobjekt (o|100|0|0) Orbiterobjekt (Jupiter|1000|200,3|235,45) Orbiterobjekt (Enterprise|20,3|31,5|83,8) Sie sehen sehr schn, dass fr alle (!) Objekte die Display-Implementierung von Orbiter verwendet wird, was auch nicht verwunderlich ist, da ja im Feld Orbiter-Typen gehalten werden. Der Kompiler verwendet dann eben die Display-Methode

C # die neue Programmiersprache


2
von Orbiter. Wenn Sie aber in der Basisklasse die Methode mit dem Schlsselwort virtual deklarieren, und in den abgeleiteten Klassen das Schlsselwort override bei den betroffenen Methoden angeben, ndert sich das Verhalten. class Orbiter { . . . public virtual void Display() { Console.WriteLine(" Orbiterobject {0}({1}|{2}|{3})",name,x,y,z); } } class Planet : Orbiter { . . . public override void Display() { Console.WriteLine( "Planetobject {0} ({1}|{2}|{3}|r={4}km)", name,x,y,z,radius); } } Auf der Konsole erscheint nun: Orbiterobjekt (o|100|0|0) Planetobject Jupiter (1000|200,3|235,45|r=353,2km) Spacshipobject Enterprise (20,3|31,5|83,8|f=1000kg) Sie sehen, obwohl im Feld nur Orbiter-Typen verwaltet werden, wird zur Laufzeit die richtige Methode verwendet. Dies wird in der Theorie spte Bindung (nmlich whrend der Laufzeit) im Gegensatz zur frhen Bindung (zur Kompilierzeit) genannt. Technisch basiert das auf so genannte v-Tables (virtual tables). Eine Klasse mit virtuellen Methoden besitzt eine Tabelle mit den Funktionszeigern aller virtuell definierten Methode. Und jedes Objekt dieser Klasse hlt intern einen Zeiger auf diese Klasse. Die genaue technische Realisierung sollte mithilfe einschlgiger Literatur erfolgen.
CD-Beispiel Orbiter3

83

C#
84

2 Abstrakte Basisklassen
Im Beispiel knnten Sie einwenden, dass Instanzen vom Typ Orbiter gar nicht notwendig sind. Dann htten Sie einen Fall fr die Definition einer reinen Basisklasse. abstract class Orbiter { protected decimal x; protected decimal y; protected decimal z; protected string name; public Orbiter(string name, decimal x, decimal y, decimal z) { this.x = x; this.y = y; this.z = z; this.name = name; } abstract public } Dies knnen Sie durch das Schlsselwort abstract erreichen. Eine Klasse, die mit abstract gekennzeichnet ist, kann nur als Basisklasse dienen. Es knnen keine Objekte diese Typs direkt erzeugt werden. In einer abstrakten Klasse knnen auch Methoden als abstract definiert werden. Diese besitzen keine Implementierung, aber eine Implementierung ist in den abgeleiteten Klassen zwingend notwendig. Das Gegenteil einer abstrakten Klasse ist eine Klasse, die explizit als Basisklasse ausgeschlossen wird. Dies ist unter C# mglich, indem die Klasse mit dem Schlsselwort sealed definiert wird. sealed class Orbiter { protected decimal x; protected decimal y; ... } void Display();

C # die neue Programmiersprache


2
Wrde diese Klasse als Basisklasse verwendet, wrde der Kompiler mit einer entsprechenden Fehlermeldung reagieren.

85

Schutzkonzept
Die Sprache C++ kennt drei Schlsselwrter fr Schutzklassen: private public protected C# kennt darber hinaus noch: internal internal protected Alle diese Schlsselwrter knnen auf Members angewendet werden. Die Schlsselwrter public und internal sind darber hinaus auch als Klassen-Modifier mglich. internal abstract class Orbiter { ... } class Planet : Orbiter { ... } public bedeutet, dass jeder diese Klasse sehen kann. internal schrnkt die Sichtbarkeit auf ein Assembly ein. Wenn nichts angegeben wird, dann ist die Klasse nur in der Datei sichtbar, in der diese definiert ist. Die Angabe von Modifier ist fr Members zwingend und zwar explizit fr jede Vereinbarung notwendig. private decimal fuel; public Spaceship(string name,. . .){ . . .} private vereinbarte Eigenschaften und Methoden sind nur in der Klassenimplementierung sichtbar. protected Vereinbarungen ermglichen den Zugriff auf diese Elemente zustzlich in Implementierungen davon abgeleiteter Klassen. public erffnet den freien Zugriff von auen. Das Schlsselwort internal erffnet Zugriff auf die Members nur innerhalb eines Assembly.

C#
86

2
Eine Besonderheit stellt die Kombination der Schlsselwrter internal und protected dar. internal protected decimal fuel; Diese Members sind in diesem Fall innerhalb eines Assembly (internal) verwendbar oder auch ber Vererbung in abgeleiteten Klassen (protected). Die Kombination stellt also eine logische ODER-Verknpfung von internal und protected dar.

Interface (Schnittstellen)
Interfaces sind abstrakte Klassen, bei denen alle Members implizit abstract deklariert sind.
CD-Beispiel Orbiter4

interface IDescription { string GetText(); string GetXML(); } class Orbiter { . . . . . . Mit dem Schlsselwort interface ist die so definierte Klasse implizit abstract und es sind auch smtliche Members abstract definiert. Es ist gebruchlich, interface-Klassen mit dem Grobuchstaben I (fr Interface) einzuleiten. Im Beispiel haben Sie eine Schnittstelle erzeugt, die Methoden zur Beschreibung von Objekten anbietet. class Planet : Orbiter,IDescription { private decimal radius; public Planet(string name, decimal x, decimal y, decimal z, decimal radius):base(name,x,y,z) { this.radius = radius; } override public void Display()

C # die neue Programmiersprache


2
{ Console.WriteLine( "Planetobject {0} ({1}|{2}|{3}|r={4}km)", name,x,y,z,radius); } public string GetText() { return "Dies ist ein Planetobjekt mit dem Namen: " + name; } public string GetXML() { return "XML"; } } C# erlaubt keine Mehrfachableitung, Ausnahme sind aber Schnittstellen. Da die Klasse Planet auch von IDescription abgeleitet wurde, sind diese Methoden entsprechend auch zu implementieren. Dies ist beispielhaft geschehen. Schauen Sie sich nun die Verwendung von Interfaces an. public static void Main() { Planet p = new Planet("Jupiter", 1000.0M,200.3M,235.45M,353.2M); Spaceship s = new Spaceship("Enterprise", 20.3M,31.5M,83.8M,1000M); //Test, ob Schnittstelle vorhanden mit is //Operator if(p is IDescription) { //Verwendung des casting-Operators IDescription DescP = (IDescription)p; Console.WriteLine(DescP.GetText()); } //Verwendung des as-Operators IDescription DescPP = p as IDescription; if(DescPP != null) Console.WriteLine(DescPP.GetText());

87

C#
88

2
IDescription DescSS = p as IDescription; if(DescSS != null) Console.WriteLine(DescSS.GetText()); } Der Zugriff auf ein Planet-Objekt ber die Schnittstelle erfolgt per Casting. Erzeugen Sie zuerst eine Referenz auf die Klasse (Schnittstelle) IDescription DescP. Das Objekt (in diesem Fall p) casten Sie auf diese Basisklasse (Interface). Da smtliche Methoden der Basisklasse (Interface) virtuell vereinbart sind, werden auch ber den Zugriff in dieser Art die Methoden des Objektes aufgerufen. IDescription DescP = (IDescription)p; Wenn allerdings das Objekt keine Schnittstelle (Basisklasse) vom Typ IDescription besitzt, dann kommt es zu einer Exception beim Casten. Mit dem C#-Operator is knnen Sie vor dem Casting das Objekt anfragen, ob es eine Schnittstelle vom gewnschten Typ auch implementiert. Der Ausdruck p is IDescription hat den Wert true, wenn p die Schnittstelle IDescription implementiert. Eine andere Mglichkeit besteht in der Verwendung des Schlsselwortes as. IDescription DescPP = p as IDescription; Die Referenz vom Typ interface wird nicht per Casting, sondern wie oben gezeigt mit dem as-Operator initialisiert. Wrde die Schnittstelle nicht vom Objekt untersttzt werden, wre der Wert von DescPP nach Ausfhrung der Operation null. Eine Prfung auf null, bevor dann mit dem Objekt gearbeitet wird, ist natrlich sinnvoll, will man keine Exception verursachen.

Zusammenfassung
Schnittstellen sind ein sehr populrer Ansatz der modernen Softwareentwicklung. Wenn ein Clientprogramm fhig ist, eine Schnittstelle zu bedienen, dann kann das Programm alle Objekte verwenden, die diese Schnittstelle implementieren (da Schnittstellen ja virtuell sind). Es ist damit eine optimale Entkopplung zwischen einem Clientprogramm und einer Komponente zu erreichen. Kleine Kopplung (low coupling) ist ein wichtiges Kriterium beim Design von Klassen-(Objekt-)Modellen.

C # die neue Programmiersprache


2
Diesbezglich wird auf entsprechende Literatur aus dem Bereich Softwaredesign verwiesen.

89

Felder und Collections


Unter .NET sind Felder(Arrays) Objekte, die selbst von System.Array abgeleitet sind. Da System.Array ein Referenztyp ist, sind Felder immer Referenzobjekte und existieren daher auf dem Heap. Die Elemente eines Feldes werden abhngig von ihrem Typ (Referenztyp oder Wertetyp) als Referenzen oder direkt im Feld gehalten.

Felder aus Wertinstanzen


using System; class App { public static void Main() { int [] IntArr; //Eine Referenz auf ein Array //(Array existiert noch nicht!! IntArr = new int[5]; //Array wird nun erzeugt //Verwendung eines Arrays for(int i=0;i<5;i++) Console.WriteLine(IntArr[i]); for(int i=0;i<5;i++) IntArr[i] = i; for(int i=0;i<5;i++) Console.WriteLine(IntArr[i]); } } Die Zeile int [] IntArr; erzeugt auf dem Stack Speicherplatz fr eine Referenz auf ein Feld. (Beachten Sie, dass Felder selbst Referenztypen sind) und damit nicht das Feld selbst. Erst mit der Zeile
CD-Beispiel Collections1

C#
90

2
IntArr = new int[20]; wird nun ein Feld, in diesem Fall mit fnf Objekten, erzeugt. Achten Sie auf die Syntax beim Anlegen einer Referenz auf ein Feld und beim Erzeugen des Feldes. Obwohl int Wertetypen sind, befindet sich aber der Speicherplatz fr diese fnf Integerwerte auf dem Heap. Felder knnen auch unmittelbar initialisiert werden. int [] IntArr = {0,1,2,3,4}; Ansonsten unterscheiden sich Felder in ihrer Verwendung nur unwesentlich von Arrays in C/C++. Es ist auch mglich, mehrdimensionale Felder anzulegen:
CD-Beispiel Collections2

using System; class App { public static void Main() { int [,] matrix; matrix = new int[4,3]; matrix[0,0] = 1; matrix[3,2] = 9; for(int i=0;i<4;i++) { Console.WriteLine("{0} {1} {2}", matrix[i,0], matrix[i,1], matrix[i,2]); } } } Die Zeile int [,] matrix; erzeugt wiederum eine Referenz auf ein Feld und zwar eine Referenz auf ein zweidimensionales Feld. Die nchste Zeile matrix = new int[4,3];

C # die neue Programmiersprache


2
belegt nun Speicherplatz fr insgesamt 12 Elemente vom Typ Integer (4 Zeilen, 3 Spalten). Die Verwendung geschieht ber den [ ]-Operator hnlich C/C++.

91

Jagged Arrays
C# kennt auch so genannte jagged arrays. Dies sind vereinfacht gesagt, Felder aus Feldern. ber die Verwendung von Jagged arrays wird auf die MSDN verwiesen.

Felder aus Referenzinstanzen


Bei der Verwendung von Feldern mit Referenztypen als Elemente ist Vorsicht angebracht. Wenn ein Feld angelegt wird, dann wird fr jedes Objekt ein Speicherplatz fr eine Referenz (Zeiger) reserviert, der aber mit null belegt ist. class App { public static void Main() { Planet [] PlanetArr; PlanetArr = new Planet[3]; for(int i=0;i<3;i++) PlanetArr[i].Display(); } } Hier wird eine Exception auftreten, da zwar Referenzen fr Objekte vom Typ Planet angelegt wurden, aber keine Objekte selbst. Der Zugriff Planet[i].Display(); wird scheitern, da PlanetArr[i] den Wert null hat, also auf kein Objekt verweist. class App { public static void Main() { Planet [] PlanetArr; PlanetArr = new Planet[3];
CD-Beispiel Collection3

C#
92

2
PlanetArr[0] = new Planet("Merkur",0,0,0,0); PlanetArr[1] = new Planet("Venus",1M,1M,1M,1M); PlanetArr[2] = new Planet("Erde",2M,2M,2M,2M); for(int i=0;i<3;i++)PlanetArr[i].Display(); } } Dieses Beispiel ist von Erfolg gekrnt, da die erzeugten Referenzen ein Objekt zugewiesen bekommen. Sie sehen hier ganz deutlich, dass ein sauberes Verstndnis des Unterschieds zwischen Referenztypen und Werttypen und deren Handhabung unter C# von entscheidender Bedeutung ist!

System.Array
Wie schon erwhnt, sind unter .NET smtliche Felder von der Basisklasse System.Array abgeleitet, und erben daher eine Reihe von Methoden und Member-Variablen, die Sie als C++-Programmierer vielleicht aus der Standard Template Library (STL) kennen. public static void Main() { Planet [] PlanetArr; PlanetArr = new Planet[3]; PlanetArr[0] = new Planet("Merkur",0,0,0,0); PlanetArr[1] = new Planet("Venus",1M,1M,1M,1M); PlanetArr[2] = new Planet("Erde",2M,2M,2M,2M); for(int i=0;i<PlanetArr.Length;i++) PlanetArr[i].Display(); Array.Reverse(PlanetArr); for(int i=0;i<PlanetArr.Length;i++) PlanetArr[i].Display(); } Sehr angenehm erweist sich auch die Tatsache, dass Feld-Objekte eine Member-Variable Length besitzen. Beispielhaft fr eine Reihe von statischen Methoden sei im obigen Beispiel die

C # die neue Programmiersprache


2
Methode Array.Reverse gezeigt. Beachten Sie, dass die Methode Reverse eine statische Methode der Klasse Array darstellt! Erforschen Sie die Methoden und Members der Klasse System.Array!

93

Indexer
Hier und da mag es sinnvoll sein, einem Objekt die Mglichkeit der Indizierung zu geben, sodass ein Objekt sich hnlich einem Feld verhlt (smart array). Indexer werden unter C++ durch berladen des [ ]-Operators realisiert. Unter C# schaut die Syntax ein wenig anders aus. Die Klasse Orbiter aus dem Projekt Space soll mit einem Indexer ausgestattet werden, sodass die Koordinaten x, y und z ber den Index-Operator gelesen werden knnen. abstract class Orbiter { protected decimal x; protected decimal y; protected decimal z; protected string name; public decimal this[int ix] { get { if(ix == 0) return x; else if(ix == 1) return y; else if(ix == 2) return z; else throw new Exception("Unerlaubter Index"); } set { if(ix == 0) x = value; else if(ix == 1) y = value; else if(ix == 2) z = value; else throw new Exception("Unerlaubter Index"); } } ... }
CD-Beispiel Collections4

C#
94

2
In C++ wrden Sie den Operator [ ] berlagern unter C# wird der this-Operator fr die Implementierung verwendet. Falsche Indizes lsen bei dieser Implementierung eine Exception aus. public static void Main() { public static void Main() { Planet pl = new Planet("Merkur",1M,2M,3M,0); for(int i=0;i<3;i++) { Console.WriteLine(pl[i]); } } } Beachten Sie aber, dass Indexer nur dann verwendet werden sollten, wenn eine entsprechende Abstrahierung auch sinnvoll erscheint. Im gezeigten Beispiel ist dies allerdings mehr als fraglich!

Die Schnittstelle IEnumerable und IEnumerate


Die Schnittstelle IEnumerable besitzt eine Methode GetEnumerator(), die selbst ein IEnumerator-Schnittstelle zurckgibt. Die Schnittstelle IEnumerator besitzt folgende Members: object Current; gibt das gegenwrtige Objekt aus.

bool MoveNext(); positioniert Enumerator auf das nchste Element. void Reset(); positioniert Enumerator vor das erste Element.

Die Basisklasse aller Felder, System.Array implementiert die Schnittstelle IEnumerable. Hier ein Beispiel fr die Anwendung dieser Schnittstelle. Beachten Sie, dass Sie den Namensraum System.Collections freigeben.
CD-Beispiel Collections5

using System.Collections; class App { public static void Main() {

C # die neue Programmiersprache


2
Planet [] PlanetArr; PlanetArr = new Planet[3]; PlanetArr[0] = new Planet("Merkur",0,0,0,0); PlanetArr[1] = new Planet("Venus",1M,1M,1M,1M); PlanetArr[2] = new Planet("Erde",2M,2M,2M,2M);

95

IEnumerable iea = (IEnumerable) PlanetArr; IEnumerator ie = iea.GetEnumerator(); ie.Reset(); while(ie.MoveNext()==true) { Planet p = (Planet)ie.Current; p.Display(); } } } Im Beispiel wird zuerst die Schnittstelle IEnumerable vom Feldobjekt PlanetArr geholt (per Casting). ber diese Schnittstelle kann dann ein Enumerator erzeugt werden (GetEnumerator), der dann erlaubt, sich innerhalb des Feldes zu bewegen. Beachten Sie, dass das Property Current ein beliebiges Objekt zurckgeben kann, und daher gecastet werden muss. Speziell fr Objekte mit der Schnittstelle IEnumerable bietet C# die spezielle foreach-Syntax an. Dieser iteriert sich durch das Feld in gleicher Weise, wie im ersten Beispiel gezeigt. class App { public static void Main() { Planet [] PlanetArr; PlanetArr = new Planet[3]; PlanetArr[0] = new Planet("Merkur",0,0,0,0); PlanetArr[1] = new Planet("Venus",1M,1M,1M,1M); PlanetArr[2] = new Planet("Erde",2M,2M,2M,2M);
CD-Beispiel Collections6

foreach(Planet p in PlanetArr) { p.Display();

C#
96

2
} } }

ArrayList
Objekte vom Typ System.Array, also C#-Felder, sind nicht dynamisch, d.h. es knnen, nachdem das Feld erzeugt wurde, keine weiteren Feldelemente hinzugefgt werden. Eine Klasse, die dies erlaubt, ist die Klasse ArrayList im Namensraum System.Collections. ArrayList hlt die Objekte in Form von Referenzen auf die Basisklasse System.Object, und kann daher beliebige Objekte auch unterschiedlichen Typs verwalten.
CD-Beispiel Collections7

class App { public static void Main() { ArrayList pa = new ArrayList(); //Container fllen pa.Add(new Planet("Merkur",0,0,0,0)); pa.Add(new Planet("Venus",1M,1M,1M,1M)); pa.Add(new Planet("Erde",2M,2M,2M,2M)); for(int i=0;i<pa.Count;i++) { ((Planet)pa[i]).Display(); } pa.RemoveAt(2); //oder aber foreach(Planet p in pa) { p.Display(); } pa.Clear(); //lscht den Container } } Die Methode Add fgt der Liste ein neues Objekt hinzu. Auf die Liste kann auch mit dem Index-Operator zugegriffen werden.

C # die neue Programmiersprache


2
Allerdings erhalten Sie Objekte vom Typ System.Object zurck, die erst in den richtigen Typ gecasten mssen, wenn Sie diese sinnvoll verwenden wollen. ArrayList implementiert eine Reihe von ntzlichen Methoden und Properties. (Count, RemoveAt(...), Clear() etc.). Mehr dazu finden Sie in der MSDN unter dem Stichwort ArrayList.

97

Maps
Datenstrukturen, bestehend aus einem Schlssel (key) und einem Wert (value), werden Map (hier wird der englische Ausdruck beibehalten) genannt und haben im Softwaredesign eine wichtige Bedeutung. .NET bietet dem Entwickler die Klasse Hashtable an, die die Funktionalitt von Maps implementiert. Die Klasse Hashtable befindet sich ebenfalls im Namensraum System.Collections. Die Verwendung sehen Sie an diesem Beispiel: class App { public static void Main() { Hashtable PlanetSystem = new Hashtable(); PlanetSystem.Add("1", new Planet("Merkur",0,0,0,0)); PlanetSystem.Add("2", new Planet("Venus",1M,1M,1M,1M)); PlanetSystem.Add("3", new Planet("Erde",2M,2M,2M,2M)); Planet p; p = (Planet)PlanetSystem["1"]; p.Display(); } } Das Objekt vom Typ Hashtable bekommt im Beispiel den Namen PlanetSystem. Mit der Methode Add unter Angabe eines Schlssels (der brigens von einem beliebigen Typ sein kann, und im Beispiel ein String darstellt) kann ein Objekt in die Tabelle eingetragen werden. ber den Index-Operator kann nun das Objekt mit dem Schlssel als Index-Parameter aus der Tabelle gelesen werden. (Casting ist natrlich selbstverstndlich
CD-Beispiel Collections8

C#
98

2
notwendig!) Die Klasse Hashtable implementiert eine Menge von Methoden und es ist empfehlenswert, diese einmal zu erforschen. Hier gilt wiederum der Verweis auf die MSDN.

Kontrollstrukturen
C# als Vertreter der C-Sprachen besitzt im Wesentlichen dieselben effizienten Syntaxmglichkeiten, um den Programmfluss zu steuern. Allerdings unterscheiden sie sich in einigen Details. In diesem Kapitel werden Sie diese kleinen, aber doch wichtigen Unterschiede kennen lernen.

Verzweigungen
So wie C und C++ kennt auch C# grundstzlich zwei Arten von Verzweigungen (if-else, switch-case). Sie beginnen mit der ifelse-Verzweigung. Die Syntax in C lautet im allgemeinen Fall if( <Ausdruck> ) Anweisung bzw. Block else Anweisung bzw. Block Das Programm wertet den Ausdruck aus und testet diesen auf 0. Ergibt die Bewertung ungleich 0, dann wird der if-Block durchgefhrt, ergibt die Bewertung 0, dann wird der else-Block durchgefhrt. Dies hat einige groe Vorteile (Performanz), aber auch eine Reihe von Nachteilen. Sehr leicht schleichen sich Fehler ein (denken Sie z.B. nur an if(x=3) ...). Die if-Verzweigung wurde daher unter C# restriktiver gestaltet. Der Ausdruck einer if-Anweisung muss hier vom Typ bool sein, ansonsten meldet sich der Kompiler mit einer Fehlermeldung. Anweisungen wie z.B. int val = 5; if(val) { ... } sind nicht mglich, wohl aber

C # die neue Programmiersprache


2
int val = 5; if(val != 0) { ... } oder auch bool val = true; if(val) { } Das C#-switch-case-Konstrukt unterscheidet ebenfalls ein wenig vom C/C++-Verhalten. Unter C# verlangt jedes case definitiv ein break! Sicherlich haben Sie auch schon viel Zeit mit der Fehlersuche vergeudet, weil Sie ein break in einem switchcase-Konstrukt bersehen haben. Nun werden Sie einwenden, gerade dieses Feature war sehr angenehm, um Code krzer und performanter zu gestalten. Dies ist aber mit einem gotoBefehl auch unter C# sehr leicht zu bewerkstelligen. Ein Beispiel: int ix; switch (ix) { case 1: //Anweisungen break; case 2: goto case 3; break; case 3: //Anweisungen break; default: //Anweisungen break; } Sie sehen, jedes case-Label muss mit einem break abgeschlossen werden, ja auch das default-Label. case 2: und case 3: fhren denselben Code durch, ohne diesen mehrfach zu implementieren

99

C#
100

2 Schleifen und Iterationen


Die while- , und do-while-Schleifen funktionieren wie unter C, nur dass diese unter C# ebenfalls einen boolschen Ausdruck verlangen. Auch die for-Schleife verlangt unter C# in der Bedingungskomponente einen boolschen Ausdruck. Neu hinzu gekommen unter C# ist die foreach-Schleife, die Sie im Abschnitt Die Schnittstelle IEnumerable und IEnumerator schon kennen gelernt haben. foreach(Planet p in PlanetArr) { p.Display(); } Die forach-Schleife funktioniert allerdings nur auf Objekte, die die Schnittstelle IEnumerable implementieren. Dies ist bei der Klasse System.Array der Fall, und Sie wissen, dass C#-Felder Objekte sind, die von System.Array abgeleitet sind. Daher kann die foreach-Schleife auf jedes normale Feld angewendet werden.

Sprungbefehle
C# hat dieselben vier Sprungbefehle wie C und C++. break und continue Wie unter C/C++ kann break verwendet werden, um eine laufende Iteration oder switch-Anweisung zu unterbrechen und die Ausfhrungen nach der Anweisung weiterzufhren. goto Das viel diskutierte goto veranlasst das Programm, an ein bestimmtes Label zu springen. Unter C# ist goto ein wenig restriktiver als unter C/C++. So sind Sprnge nur innerhalb eines Bockes mglich. Eine sinnvolle Verwendung von goto haben Sie bei der Syntaxvorstellung der switch-case-Anweisung kennen gelernt. return Hier gibt es nicht viel zu erzhlen, die return-Anweisung veranlasst das Programm, die laufende Funktion zu verlassen und

C # die neue Programmiersprache


2
zur aufrufenden Funktion zurckzukehren. Optional kann ein Wert zurckgegeben werden.

101

Delegates
Delegates sind Vereinbarungen, die einen Prototyp einer Funktion spezifizieren. Sie kennen sicherlich das Konzept eines Funktionszeigers in C/C++. Das ist eine Variable (typisiert), die eine Funktion (bzw. einen Funktionszeiger) aufnehmen kann. Allerdings kann eine solche typisierte Variable nicht eine Adresse einer beliebigen Funktion aufnehmen, sondern nur von solchen Funktionen, die denselben Prototyp (Typ des Rckgabewertes und Anzahl und Typ der Parameter) besitzen. Delegates sind das Analogon dieser Funktionszeiger unter C#, mit einigen Verbesserungen, die eine fehlerhafte Verwendung von Funktionszeigern von vornherein verhindern sollten. Nachfolgend ein Beispiel zur Veranschaulichung: Sie werden nun einen kleinen Command Executer basteln, der auf bestimmte Befehle reagieren soll. using System; class Proc { static public string Do(string com) { switch(com) { case "cd": Console.WriteLine( "Befehl: cd wurde ausgefhrt"); break; case "copy": Console.WriteLine( "Befehl: copy wurde ausgefhrt"); break; default: Console.WriteLine("Befehl nicht erlaubt"); break; } return com; } }
CD-Beispiel Delegate1

C#
102

2
class App { public static void Main() { while(true) { Console.Write(">>"); //Ausgabe eines Prompt string com = Console.ReadLine(); //Befehl einlesen if(com == "ende") break; Proc.Do(com); } } } Die Klasse Proc stellt eine statische Methode mit dem Namen Do bereit. Die Methode Do hat einen String-bergabeparameter, der ein Kommando darstellt. In einer switch-case-Struktur wird die Methode auf die unterschiedlichen Kommandos reagieren (im Beispiel Ausgaben auf die Konsole). Die Methode gibt dann am Schluss den Kommandostring als Rckgabewert wieder zurck. In der Main()-Methode der Klasse App arbeitet eine Endlosschleife, die nach Ausgabe eines Prompts auf eine Eingabe wartet (Console.ReadLine). Der eingelesene String wird dann als Parameter fr den Aufruf Do der Klasse Proc verwendet. Bei einer Eingabe von ende wird die Schleife verlassen und das Programm terminiert. Diese Beispiel soll nun so abgendert werden, dass die Auswahl der Prozedurfunktion (Do) dynamisch gestaltet werden kann. Wie eingangs schon erwhnt, eignen sich hierzu Delegates. Die .NET-Klasse(n) System.Delegate (und System.MulticastDelegate) implementieren die Funktionalitt. Wenn Sie unter C# programmieren, werden Sie eher selten direkt mit diesen Klassen arbeiten, weil C# die Arbeit mit Delegates mit dem Schlsselwort delegate untersttzt. Das C#-Delegate-Modell hat sehr viel hnlichkeiten mit Klassen und ist mit diesem Denkmodell am einfachsten zu verstehen (und daher wird dieses Modell verwendet, wenngleich im Detail das Ganze ein wenig komplexer ist). public delegate string Procfunc(string s);

C # die neue Programmiersprache


2
Diese Zeile sieht einem Funktionsprototyp zum Verwechseln hnlich. Hier wird aber eine neue Klasse mit dem Namen Procfunc definiert. Wenn Procfunc eine Klasse darstellt, dann knnen auch Objekte vom Typ Procfunc angelegt werden. Procfunc f = new Procfunc(Proc.Do); Hier wird ein Objekt mit dem Namen f vom Typ Procfunc erzeugt. Dem Konstruktor der Klasse Procfunc kann ein Parameter in Form einer Methode (Funktionszeiger) mitgegeben werden. Damit verwaltet das Objekt f diese Methode. Beachten Sie allerdings, dass Sie dieser Klasse Procfunc als Konstruktionsparameter nur Methoden mitgeben drfen, die dem Prototyp bei der Definition der Klasse Procfunc entsprechen. Im Beispiel knnen konkret Objekte vom Typ Procfunc nur Methoden aufnehmen, die genau einen bergabeparameter vom Typ String und einen Rckgabewert String untersttzen. Mit diesem Objekt knnen nun Aufrufe der Methode gettigt werden, die vom Delegate verwaltet werden. f(com); Das Objekt f wird den Parameter der Methode weiterleiten (delegieren), die das Objekt auch verwaltet (im konkreten Fall der Methode Do). Erweitern Sie nun das Beispiel unter Verwendung eines Delegates fr die Prozedurfunktion! public delegate string Procfunc(string s); class App { static public string Work(string s) { Console.WriteLine( "Sie haben den Befehl {0} eingegeben",s); return s; } public static void Main() { //Objekt vom Typ Delegate erzeugen Procfunc f; f = new Procfunc(Work);
CD-Beispiel Delegate2

103

C#
104

2
//f = new Procfunc(Proc.Do); while(true) { Console.Write(">>"); //Ausgabe eines Prompt string com = Console.ReadLine();//Befehl einlesen if(com == "ende") break; f(com); } } } Der Aufruf der Prozedurfunktion erfolgt nun ber das Delegate-Objekt. Das Beispiel implementiert eine alternative Prozedurfunktion Work im Namensraum App, die dem Prototyp gehorcht, den das Delegate Procfunc vorschreibt. Die Methode Work kann daher vom Delegate Procfunc verwaltet werden. Procfunc f = new Procfunc(Work); Dies Zuordnung lsst sich natrlich ohne Weiteres dynamisch gestalten.

Multicast-Delegates
Im bisherigen Denkmodell verwaltet ein Delegate genau eine Methode (technisch in Form eines Funktionszeigers). Das Delegate delegiert dann Aufrufe an diese Methode weiter. Stellen Sie sich nun ein Delegate vor, das mehrere Methoden verwalten kann, und Aufrufe dann nacheinander an alle diese verwalteten Methoden weiterreicht. Solche Delegates existieren und werden Multicast-Delegates genannt.
CD-Beispiel Delegate3

using System; class Logger { public static void NotifyMessage(string mes) { Console.WriteLine( "Nachricht: {0} gespeichert",mes); } }

C # die neue Programmiersprache


2
class Alarm { public static void AlarmMessage(string mes) { Console.WriteLine("Alarm: {0} weitergegeben",mes); } } public delegate void Func(string s); class App { public static void Main() { Func fs; fs = new Func(Logger.NotifyMessage); fs += new Func(Alarm.AlarmMessage); fs("Mes1"); } } Die Zeile public delegate void Func(string s); definiert ein neues Delegate mit dem Namen Func, das Funktionszeiger des angegebenen Typs verwalten kann. Func fs; fs = new Func(Logger.NotifyMessage); fs += new Func(Alarm.AlarmMessage); fs("Mes1"); Einem Objekt fs vom Typ Func wird in bekannter Weise die statische Methode Logger.NotifyMessage zur Verwaltung bergeben. Neu ist nun aber, dass dem Delegate unter Verwendung des Operators += eine weitere Methode (Alarm.AlarmMessage) zur Verwaltung bergeben wird. Das Delegate wird nun Aufrufe an beide Methoden weiterleiten. Die Aufrufe erfolgen sequenziell in der Reihenfolge, wie die Methoden dem Delgate zur Verwaltung bertragen wurden.

105

C#
106

2
An der Konsolenausgabe werden Sie sehen, dass mit fs(Mes1); beide Methoden aufgerufen wurden, die das Delegate fs verwaltet. Nachricht: Mes1 gespeichert Alarm: Mes1 weitergegeben Bei Multicast-Delegates gibt es allerdings eine kleine Einschrnkung. Diese knnen nur Methoden verwalten, die keinen Rckgabewert besitzen. Multicast-Delegates werden intern nicht mit der Klasse System.Delegate implementiert, sondern mit der Klasse System.Multicast-Delegate. Der C#-Kompiler verwendet intern automatisch Multicast-Delegates, wenn die Vereinbarung mit dem Schlsselwort delegate eine Methode ohne Rckgabewert reprsentieren soll.

Events
Events werden verwendet, wenn ein Objekt ein anderes benachrichtigen mchte, dass ein bestimmtes Ereignis eingetroffen ist. Events werden oft bei grafischen User Interfaces verwendet. Als COM-Programmierer ist Ihnen sicherlich der Event-Mechanismus (Connection Points) bekannt. VB abstrahiert diese Technik im Besonderen, verwendet aber intern den Connection Point-Mechanismus. Es gibt einige Software-Patterns (Muster) fr Event-Mechanismen. Vielfach durchgesetzt und meist angewendet wird das so genannte Publish-Subcribe-Pattern. Ein Objekt, nennen Sie es Source-Objekt, publiziert Events. Anderen Objekten, nennen Sie sie Target-Objekte, ist es also bekannt, dass dieses Source-Objekt Events generieren kann. Diese Objekte knnen sich nun bei den Source-Objekten anmelden (subscribe), dass sie, wenn ein Ereignis auftritt, informiert werden. Technisch geschieht das Anmelden in der Form, dass die Target-Objekte beim Anmelden dem Source-Objekt einen Funktionszeiger mitgeben, ber den dann das Source-Objekt die Funktion aufruft, sobald ein Ereignis eingetreten ist. Nun knnen sich im Allgemeinen mehrere Target-Objekte bei einem Source-Objekt anmelden.

C # die neue Programmiersprache


2
using System; public delegate void SignalHandler(string s); class Source { public event SignalHandler Notify; public void DoSignal() { Notify ("Ein Event"); } } class Target { public void OnSignal(string s) { Console.WriteLine("Target: {0}",s); } } class OtherTarget { public void Notification(string s) { Console.WriteLine("OtherTarget: {0}",s); } } class App { public static void Main() { //Source Objekt erzeugen Source S = new Source(); //drei Targetobjekte erzeugen Target T1 = new Target(); Target T2 = new Target(); OtherTarget To = new OtherTarget(); //Targetmethoden im Sourceobjekt anmelden S. Notify += new SignalHandler(T1.OnSignal);
CD-Beispiel Event1

107

C#
108

2
S. Notify += new SignalHandler(To.Notification); S. Notify += new SignalHandler(T2.OnSignal); //Ein Ereignis im Sourceobjekt simulieren S.DoSignal(); } } Events sind unter C# nichts anderes als eine spezielle Form von Delegates. C# bietet ein eigenes Schlsselwort event an. Im Beispiel wird zuerst ein Delegate definiert. public delegate void SignalHandler(string s); Die Klasse Source definiert eine Member-Variable vom Typ SignalHandler mit dem Namen Notify. Da das Delegate SignalHandler Methoden ohne Rckgabewerte verwaltet, ist SignalHandler ein Multicast-Delegate (siehe vorheriges Kapitel). class Source { public event SignalHandler Notify; public void DoSignal() { Notify ("Ein Event"); } } Der Aufruf Notify(...) in der Methode DoSignal() der Klasse Source delegiert nun alles an die Methoden, die das Delegate Notify verwaltet. Im Hauptprogramm wird ein Objekt vom Typ Source mit dem Namen S angelegt. Anschlieend werden dem Delegate Notify der Klasse S in bekannter Weise drei Methoden zur Verwaltung bergeben. S.Notify += new SignalHandler(T1.OnSignal); S.Notify += new SignalHandler(To.Notification); S.Notify += new SignalHandler(T2.OnSignal); In diesem Beispiel werden die Methoden der Objekte T1,To und T2 dem Delegate zur Verwaltung bergeben.

C # die neue Programmiersprache


2
Auf der Konsole sollte dann folgende Ausgabe zu sehen sein: Target: Ein Event OtherTarget: Ein Event Target: Ein Event Sie werden sich vielleicht fragen, welche Bedeutung denn das Schlsselwort event bei der Definition der Member-Variable Notify hat? public event SignalHandler Notify; Und diese Frage ist auch berechtigt. Sie knnen nmlich das Schlsselwort event auch weglassen, und das Programm funktioniert in gleicher Weise. Die Verwendung des Schlsselwortes event hat nur informativen Charakter, die allerdings auch in den Metadaten des Assemblies eingetragen wird. Das Entwicklungssystem Visual Studio.NET wertet diese Information auch aus und zeigt dem Entwickler das Member Notify auch grafisch in Form eines gelben Blitzes an. Ein Target-Objekt kann sich auch wieder von einem Source-Objekt abmelden. public static void Main() { //Source Objekt erzeugen Source S = new Source(); //drei Targetobjekte erzeugen Target T1 = new Target(); Target T2 = new Target(); OtherTarget To = new OtherTarget(); SignalHandler del = new SignalHandler(T1.OnSignal); //Targetmethoden im Sourceobjekt anmelden S.Notify += del; S.Notify += new SignalHandler(To.Notification); S.Notify += new SignalHandler(T2.OnSignal); //Ein Ereignis im Sourceobjekt simulieren S.DoSignal(); //Abmelden S.Notify -= del;
CD-Beispiel Event2

109

C#
110

2
//Ein neues Ereignis simulieren S.DoSignal(); } Hier wird explizit eine Referenz del vom Typ Signalhandler mit Angabe der Methode T1.OnSignal erzeugt. SignalHandler del = new SignalHandler(T1.OnSignal); Diese Referenz knnen Sie nun sowohl zur Anmeldung als auch zur Abmeldung verwenden. //Anmelden S.Notify += del; //Abmelden S.Notify -= del;

System.EventHandler Delegate
Obwohl der Prototyp der Bearbeitungsfunktionen im Allgemeinen frei whlbar ist (siehe voriges Beispiel), ist es blich, diesen in folgender Form zu implementieren: public delegate void EventHandler(object o, EventArgs e); Das Target-Objekt implementiert eine Funktion mit diesem Prototyp und das Source-Objekt ruft diese Funktion auf. Im ersten Parameter o kommt die Quelle mit (also eine Referenz auf die Source), und im zweiten Parameter kommt die nhere Beschreibung des Events, in Form eines Objektes vom Typ EventArgs oder davon abgeleitet. EventArgs ist eine Klasse, die im Namensraum System definiert ist. Ebenfalls im Namensraum System ist das Delegate EventHandler definiert, sodass diese Definition nicht mehr explizit durchgefhrt werden muss.
CD-Beispiel Event3

using System; class MyEventArgs : EventArgs { public MyEventArgs(string mes) { Message=mes; }

C # die neue Programmiersprache


2
public string Message; } class Source { public event EventHandler Notify; public void DoSignal() { Notify(this,new MyEventArgs("Event ausgelst")); } } class Target { public void OnSignal(object o,EventArgs e) { MyEventArgs me = (MyEventArgs)e; Console.WriteLine("Target: {0}",me.Message); } } class App { public static void Main() { //Source Objekt erzeugen Source S = new Source(); //zwei Targetobjekte erzeugen Target T1 = new Target(); Target T2 = new Target(); //Anmelden S.Notify += new EventHandler(T1.OnSignal); S.Notify += new EventHandler(T2.OnSignal); //Ein Ereignis im Sourceobjekt simulieren S.DoSignal(); } }

111

C#
112

2
Dieses Beispiel verwendet eine eigene Klasse MyEventArgs, die von EventArgs abgeleitet ist. Die Member-Varialbe Notify in der Klasse Source wird nun vom Typ EventHandler (Namensraum System) definiert. Der Notifizierungsaufruf in der Source-Klasse sowie die Methode der Target-Klasse sind entsprechend zu modifizieren. Smtliche Klassen der .NET-Umgebung verwenden den hier vorgestellten Event-Mechanismus und es wird den Entwicklern empfohlen, Events ber das Delegate EventHandler zu realisieren. Da auch eigene EventArgs-Klassen verwendet werden knnen, ist das auch keine funktionale Einschrnkung.

Attribute
Die .NET-Laufzeitumgebung erlaubt Elementen, wie Assemblies, Klassen, Methoden Parameter usw. die Zuordnung von Attributen. Dieser Ansatz ist nicht ganz neu, so hat dies schon die IDL (Interface Definition Language) unter COM erlaubt, aber die Attribute sind dort fix festgelegt. Unter .NET knnen nun beliebige Attribute festgelegt und den Elementen zugeordnet werden. Die belegten Werte der Attribute sind in den Metadaten im Assembly untergebracht und knnen jederzeit programmtechnisch ber Klassen des Namensraumes System.Reflection erfragt werden, auch zur Laufzeit. Attribute werden einem Element in eckigen Klammern vorangestellt. Angenommen, Sie htten ein Attribut Programmer, das den Namen und das Datum eines Programmierers darstellt. Die Verwendung wrde dann so aussehen: [Programmer("George Bush","20.02.2002"), Programmer("Bill Clinton","1.1.2002")] public class Test { } Die Klasse Test erhlt also Informationen eines Attributs mit dem Namen Programmer. Attribute sind selbst Klassen, die von System.Attribute abgeleitet sind. Im folgenden Code sehen Sie, wie eine solche Attribut-Klasse definiert wird.

C # die neue Programmiersprache


2
using System; using System.Reflection; [AttributeUsage(AttributeTargets.Class, AllowMultiple=true)] public class ProgrammerAttribute : System.Attribute { string name; string date; public ProgrammerAttribute(string name,string date) { this.name = name; this.date = date; } public string Name{get {return name;}} public string Date{get {return date;}} } Auffallend ist, dass die Definition einer Attribut-Klasse selbst ein Attribut braucht (AttributeUsage). Die Bedeutung wird spter erklrt. Ansonsten sehen Sie eine normale Definition einer Klasse, die von der Basisklasse System.Attribute abgeleitet ist (Die Klasse System.Attribute ist im Namensraum System.Reflection untergebracht). Die Klasse wurde mit zwei Member-Variablen (im speziellen Fall die Strings name und date) und einem Konstruktor zur Belegung dieser Member-Variablen ausgestattet. Beachten Sie die Vereinbarung, dass bei der Klassendefinition eines Attributs nach dem Attributnamen das Wort Attribute angehngt wird. Die Klasse heit ProgrammerAttribute, aber bei der Verwendung der Klasse als Attribut reicht der Name Programmer aus! Sobald eine Attributklasse definiert ist, knnen Sie diese auch anwenden. [Programmer("George Bush","20.02.2002"), Programmer("Bill Clinton","1.1.2002")] public class Test { }

113

C#
114

2
In diesem Fall geben Sie der Klasse Test zwei Attribute mit. Da der Konstruktor der Klasse ProgrammerAttribute mit zwei Parametern ausgestattet ist, mssen auch zwei Parameter bei der Attributzuweisung angegeben werden. Beim Kompilieren werden diese Informationen nun in die Metadaten eingetragen, und knnen dann beliebig ausgelesen werden. Das folgende Codebeispiel soll Ihnen zeigen, wie dies programmtechnisch geschieht.
CD-Beispiel Attribute1

using System; using System.Reflection; [AttributeUsage(AttributeTargets.Class, AllowMultiple=true)] public class ProgrammerAttribute : System.Attribute { string name; string date; public ProgrammerAttribute(string name,string date) { this.name = name; this.date = date; } public string Name{get {return name;}} public string Date{get {return date;}} }

[Programmer("George Bush","20.02.2002"), Programmer("Bill Clinton","1.1.2002")] public class Test { } class App { public static void Main() { MemberInfo info; info = typeof(Test); object [] atts; atts = info.GetCustomAttributes(false);

C # die neue Programmiersprache


2
for(int i=0;i<atts.Length;i++) { if(atts[i].GetType()== typeof(ProgrammerAttribute)) { ProgrammerAttribute att = (ProgrammerAttribute) atts[i]; Console.WriteLine(att.Name); Console.WriteLine(att.Date); } } } } Die Methode GetCustomAttributes erlaubt Ihnen nun die Attribute auszulesen. Optional kann angegeben werden, welche Attribute gewnscht sind. Alle diese Attribute werden nun in einem Feld vom Typ object abgelegt. Diese knnen nun ausgelesen werden. Da im Allgemeinen auch unterschiedliche Attribute verwendet werden knnen, wird ein Feld zurckgegeben. Die Objekte im Feld werden nun im Bedarfsfall auf ProgrammerAttribute gecastet und dann entsprechend verarbeitet. Auf der Konsole sollte dann erscheinen: George Bush 20.02.2002 Bill Clinton 1.1.2002 Bei der Definition einer Attributklasse muss der Klasse selbst ein Attribut vom Typ AttributUsage angegeben werden. [AttributeUsage(AttributeTargets.Class, AllowMultiple=true)] public class ProgrammerAttribute : System.Attribute { ... AttributeUsage muss einen Eintrag AttributeTargets besitzen. Dafr sind folgende Eintrge (auch OR-Kombinationen) mglich.

115

C#
116

2
AttributeTargets.Module AttributeTargets.Class AttributeTargets.Struct AttributeTargets.Enum AttributeTargets.Constructor AttributeTargets.Method AttributeTargets.Property AttributeTargets.Field AttributeTargets.Event AttributeTargets.Interface AttributeTargets.Parameter AttributeTargets.Return AttributeTargets.Delegate AttributeTargets.All AttributeTargets.ClassMembers Sie definieren, bei welchen Elementen das Attribut verwendet werden darf. [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class), AllowMultiple = true)] class . . . Im gezeigten Beispiel wird ein Attribut definiert, das auf Methoden und Klassen verwendbar ist, und auch mehrfach angegeben werden darf. Attribute haben eine wesentliche Bedeutung unter .NET. Sie erlauben deklaratives Programmieren. Da diese zur Laufzeit ausgelesen werden knnen, kann die Laufzeitumgebung, aber auch eigene Klassen in Abhngigkeit von Attributen spezielle Behandlungen durchfhren. [WebMethod] public void Methode1() { . . . } Ein Beispiel dafr sind so genannte WebServices. WebServices sind Methoden, die ber das Protokoll http Remote-fhig sind. Das Marshalling (Verpacken von bergabeparameter in XML usw.) bernimmt hier der Internet Information Server (IIS). Es gengt eine Methode mit dem Attribut WebMethod auszustatten, alles andere bernimmt der IIS!

C # die neue Programmiersprache


2
117

Zusammenfassung
In diesem Kapitel haben Sie nun die ersten grundlegenden Konzepte der Sprache C# und .NET erarbeitet. Trennen Sie in Ihrem Denkmodell aber bitte streng C# von .NET. C# ist nicht .NET, sondern eine Sprache, die mit ihrer speziellen Syntax .NET-Konzepte abbildet. Andere .NET-Sprachen bilden die .NET-Konzepte mit der diesen Sprachen eigenen Syntax ab. Dies ist auch der Grund, warum in unterschiedlichen Sprachen geschriebene Codeteile untereinander kompatibel sind. Sie haben noch bei weitem nicht alles ber .NET und auch C# gehrt, aber mit diesem Wissen knnen Sie nun tiefer in die .NET-Welt eintauchen. Im nchsten Kapitel werden Sie kennen lernen, wie Softwarebaugruppen erstellt werden knnen.

Baugruppen (Assemblies)

Einleitung Grundlagen Singlefile- und Multifile-Assemblies ILDASM (IL Disassembler) Assembly-Entwicklung unter Visual Studio.NET Shared Assembly Zusammenfassung

120 121 123 128 131 135 137

C#
120

Einleitung
Seit den Anfngen der Softwareentwicklung sind Programmierer bestrebt, ihren Code so zu organisieren, dass eine Wiederverwendung in anderen Projekten mglich wird. So wird Funktionalitt in Baugruppen wie statische oder dynamische Bibliotheken gehalten, und ist somit zur Entwicklungszeit wiederverwendbar. Dynamische Bibliotheken (DLL-dynamic link library) erlauben darber hinaus noch das Verwalten der Funktionalitt in einer Datei, die dann zur Laufzeit von den unterschiedlichen Programmen verwendet wird. Neben der Einsparung von Speicherplatz auf der Festplatte, wurde damit auch die Verwaltung des Codes vereinfacht. Eine nderung im Code der Bibliothek wirkt sich sofort in allen Programmen aus, die diese Bibliothek verwenden. Im Fall der Berichtigung eines Fehlers ist dies sehr angenehm. Ein Quantensprung in der Wiederverwendbarkeit von Code war das Aufkommen von objektorientieren Konzepten, hier allen voran die Konzepte Vererbung, Virtualitt und Schnittstellen. Vor allem die Idee reiner virtueller Basisklassen (Schnittstellen) hat eine nachhaltige Entwicklung sowohl im Softwaredesign als auch bei den Betriebssystemen ausgelst. Objektorientierte, verteilte Komponentenmodelle sind entstanden und wurden auch Bestandteil der Betriebssysteme. Unter COM (component object model) von Microsoft werden nicht Funktionen ber DLLs bereitgestellt, sondern Klassen bzw. Objekte. Im Laufe der Zeit hat sich die COM-Technologie immens weiter entwickelt. Trotz all dieser Entwicklungen zeigten sich aber auch Schwierigkeiten und Unzulnglichkeiten ab. So ist vor allem die Abhngigkeit dieser Komponenten von den vielen Eintrgen in der Registrierungsdatenbank nur noch mit groem Aufwand zu verwalten. Auch ist die Wiederverwendbarkeit von COM-Objekten durch Vererben nicht in der Konsequenz mglich, wie dies die objektorientierten Theorien fordern. Vererbung fehlt ganz einfach bei COM (Aggregation konnte diesen Umstand ein wenig entschrfen). Mit .NET wurde hier mit all den Unzulnglichkeiten aufgerumt. .NET-Baugruppen (Assemblies) exportieren Funktionalitt ausschlielich in Form von Klassen, knnen am Konzept

Baugruppen (Assemblies)
3
der Vererbung teilnehmen und sind nicht mehr von der Registrierung abhngig. In diesem Kapitel werden Sie kennen lernen, wie Sie eigene Baugruppen erstellen, und diese auch organisieren knnen.

121

Grundlagen
Schon im Kapitel 2, C# Die neue Programmiersprache haben Sie einiges ber Assemblies gehrt. Im Folgenden wird aber statt des Ausdrucks Baugruppe der englische Ausdruck beibehalten. Ein Assembly ist eine logische Einheit von Dateien zu einem Ganzen. Im einfachsten Fall kann ein Assembly aus nur einer *.exe- bzw. einer *.dll-Datei bestehen. Ein Assembly beinhaltet Dateien, in denen sich Code befindet, kann aber auch andere Datei, wie z.B. Ressourcen, Bilder (.jpg, .bmp, .gif etc.), Videodateien, Audiodateien etc. beinhalten, die fr die Funktionalitt der Baugruppe notwendig sind. .NET verlangt, dass sich alle Dateien einer Baugruppe, sich im selben Ordner befinden mssen! Die .NET-Laufzeitumgebung betrachtet alle diese Dateien als eine Einheit. Wurden frher Komponenten in Form genau einer *.dll-Datei (z.B. als ActiveX -Komponente) verteilt, so werden Komponenten unter .NET in Form eines Assembly verteilt. Assemblies werden daher im Jargon auch logische Dlls genannt. Ein Assembly besteht grundstzlich aus folgenden Einheiten: Metadaten Typ-Metadaten Intermediate Language Code (IL) Ressourcen Im links dargestellten Assembly sind smtliche Elemente in einer Datei (Fraction.dll) untergebracht (single file assembly). Das rechts dargestellten Assembly besteht aus einer Datei Orbiter .dll, das die Assembly Metadaten, Typ-Metadaten und IL-Code enthlt, einer weiteren Datei Math.netmodule, das Typ-Metadaten und IL-Code enthlt sowie zweier Bilddateien (enterprise .jpg und jupiter.jpg), die vom Assembly bentigt werden. Alle diese Dateien bilden eine Einheit (multi file assembly).

C#
122

Assembly Aufbau: Alle diese Einheiten knnen in einer Datei vorkommen oder aber auf verschiedene Dateien aufgeteilt sein. Abb. 3.1

Ein ganz besonders wichtiger Teil der Metadaten wird Manifest genannt. Das Manifest beinhaltet smtliche Informationen ber das Assembly. Unter anderem beinhaltet das Manifest eine Liste aller Dateien, aus dem das Assembly besteht, aus eine Liste von allen abhngigen Assemblies, die von diesem Assembly bentigt werden, Informationen bezglich Version, Kultur und vieles mehr. In den Typ-Metadaten sind smtliche Beschreibungen der verwendeten Klassen und Strukturen untergebracht. ber den Reflection-Mechanismus (Sie erfahren Nheres dazu in spteren Kapiteln) kann jederzeit die Information der Klassen und Strukturen auch zur Laufzeit erhalten werden. Der eigentliche Code liegt im IL-Bereich. Dazu aber spter mehr. Eine Datei, die zwar Typ-Metadaten und IL-Code enthlt, aber keine Assembly-Metadaten, und somit auch keine Manifest, wird Modul (module) genannt. Obwohl ein Modul Code enthlt, ist diese Einheit von der .NET-Laufzeitumgebung nicht verwendbar. Ein Modul kann aber, wie Sie sehen, Bestandteil eines Assembly sein. Sie knnen damit Code, den Sie auch in mehreren Assemblies verwenden wollen, in einem Modul verwalten (ohne fr diesen Code ein eigenes Assembly zu erzeugen).

Baugruppen (Assemblies)
3
123

Singlefile- und Multifile-Assemblies


Zwei Beispiele sollen Ihnen die Zusammenhnge verdeutlichen. Im ersten Beispiel werden Sie ein Singlefile-Assembly erzeugen. Diese enthlt smtliche Informationen in einer Datei.

Singlefile-Assembly
Die Klasse Fraction aus Kapitel 2 soll in ein eigenes Assembly verpackt werden. Damit kann diese Klasse auch in anderen Programmen Verwendung finden. Aus didaktischen Grnden soll vorerst dieses Assembly nicht mit der Entwicklungsumgebung Visual Studio.NET erzeugt werden, weil hier einiges im Hintergrund abluft. Damit Ihnen die Zusammenhnge klar werden, soll dieses Beispiel aus der Konsole entwickelt werden. Starten Sie die .NET-Konsole mit Start > Programme > Microsoft Visual Studio.NET > Visual Studio.NET Tools > Visual Studio.NET Command Prompt. Erstellen Sie fr die folgenden Experimente einen eigenen Ordner TestAssemblySingleFile. In diesem Ordner erzeugen Sie eine Textdatei Fraction.cs, die ausschlielich die Implementierung der Klasse Fraction aus dem Kapitel 2: C# die neu Programmiersprache enthlt. using System; namespace FractionMath { public class Fraction { private int z; private int n; public Fraction () { z = 0; n = 1; } ... ... ... } }
CD Beispiel TestAssemblySingleFile

C#
124

3
Achten Sie darauf, dass die Klasse Fraction public vereinbart ist. Dies ist notwendig, wenn andere Assemblies diese Klasse verwenden mchten. Im Visual Studio.Net Command Prompt wechseln Sie in den Ordner TestAssemblySingleFile und geben dann folgende Kommandozeile ein (Sie sollten eine Batch-Datei make.bat erzeugen, damit knnen Sie sich einige Tipparbeit ersparen). csc /out:FractionMath.Fraction.dll /t:library Fraction.cs CSC.EXE ist der C#-Kompiler. Zuerst geben Sie den Namen des Assembly (/out:FractionMath.Fraction.dll) an, das entstehen soll, dann die Art (Target) des Assembly (/t:library-Bibliothek) und anschlieend die Assemblies und C#-Quellcodedateien, die verwendet werden. Stt nun der Kompiler bei seiner Arbeit auf einen Datentyp, der nicht direkt in der *.csDatei angelegt ist, dann sucht der Kompiler in den Manifesten und Typ-Metadaten der angegebenen Assemblies nach diesen Datentypen, und sollte diesen dort auch finden. Ansonsten gibt es einen Kompilierfehler. Nun legen Sie eine weitere Datei App.cs an und geben dieser einen kleinen Code fr den Test der Klasse Fraction ein. using System; using FractionMath; public class App { public static void Main() { Fraction f1 = new Fraction(3,2); Fraction f2 = new Fraction(4); Console.WriteLine(f1+f2); } } Dieses Konsolenprogramm wird also beim Linken einen Verweis (Referenz) auf die das Assembly FractionMath.Fraction.dll haben mssen. In der Kommandozeile geben Sie ein: csc /t:exe /out:TestApp.exe /r:FractionMath.Fraction.dll App.cs

Baugruppen (Assemblies)
3
Da Sie nun keine Bibliothek, sondern ein Konsolenprogramm erzeugen wollen, geben Sie die Option /t: (Target) exe an. Das Ergebnis sollte eine Datei mit dem Namen TestApp.exe sein. ber die Option /r: knnen Sie nun die notwendigen Assemblies dem Kompiler bekannt geben. Die Assemblies FractionMath.Fraction und TestApp.exe befinden sich schon im selben Ordner. Sie knnen das Programm aus der Konsole mit Eingabe von TestApp.exe starten.

125

Multifile-Assembly
Im nchsten Beispiel werden Sie ein multi file assembly erzeugen. Hierzu erweitern Sie das Beispiel in der Form, dass beim Start des Programms ein Bild des ehrwrdigen Naturwissenschaftlers und Mathematikers Isaac Newton erscheint. Die Datei newton.gif finden Sie auf der beiliegenden CD zum Buch (Es steht Ihnen natrlich frei, ein Foto von Ihnen zu verwenden). Hierzu legen Sie einen neuen Ordner TestAssemblyMultiFile an und kopieren die Datei FractionMath.Fraction.dll in diesen. Ebenfalls kopieren Sie in diesen Ordner die Datei newton.gif. Die Applikation programmieren Sie in der Datei App.cs in folgender Form: using System.Windows.Forms; public class NewtonPicture:Form { PictureBox pb; public NewtonPicture() { pb = new PictureBox(); pb.Click+= new EventHandler(OnClick); pb.Image = Image.FromFile("newton.gif"); pb.Size = ClientSize = pb.Image.Size; Controls.Add(pb); } void OnClick(object sender,EventArgs e) { this.Close(); } } public class App
CD-Beispiel TestAssemblyMultiFile

C#
126

3
{ public static void Main() { NewtonPicture p = new NewtonPicture(); p.ShowDialog(); Fraction f1 = new Fraction(3,2); Fraction f2 = new Fraction(4); Console.WriteLine(f1+f2); } } In der Main()-Methode wird erst ein Objekt der Klasse Form (NewtonPicture) angelegt und angezeigt. Die Klasse NewtonPicture ist von der Klasse Form abgeleitet, die ein Windowsfenster reprsentiert. Es macht nichts, wenn Sie den Code noch nicht verstehen, im Kapitel 6, Windows-Applikationen werden Sie in die Geheimnisse der Klasse Form eingeweiht. Vorerst interessiert hier die Zeile, die eine Datei ldt. pb.Image = Image.FromFile("newton.gif"); Da die Bilddatei newton.gif zur Applikation gehrt, ist es sinnvoll, diese in das Assembly aufzunehmen. Damit Sie nicht zu viel in der Kommandozeile tippen mssen, sollten Sie wieder mit einem Editor eine Batch-Datei anlegen. Nennen Sie diese Batch-Datei make.bat und geben Sie folgende Kommandos ein: csc /t:module /r:FractionMath.Fraction.dll /r:System.dll /r:System.Windows.Forms.dll /r:System.Drawing.dll App.cs al /t:exe /out:App.exe App.netmodule /main:App.Main /link:newton.gif Das erste Kommando kompiliert die Datei App.cs zu einem Modul (/t:module) mit dem Namen App.netmodule. Smtliche notwendigen Assemblies geben Sie hier mit. Mit dem Werkzeug Assembly Linker (AL) knnen Sie Assemblies (auch eine .exe-Datei ist ein Assembly) erzeugen. Der Aufruf in gezeigter Form erzeugt eine ausfhrbare .exe-Datei (/t:exe) mit dem Namen App.exe (/out:App.exe) unter Verwendung des Moduls App.netmodule. Sie mssen in diesem Fall auch die Einsprungsmethode dem Linker mitgeben

Baugruppen (Assemblies)
3
(/main:App.Main). Damit auch die Datei newton.gif in das Assembly mit aufgenommen wird, geben Sie dies mit der Option /link:newton.gif an. Das Assembly App.exe besteht nun aus folgenden Dateien: App.netmodul App.exe Newton.gif

127

Aufbau des Assembly App Abb. 3.2

Diese Dateien bilden nun einen Verbund. Wenn Sie das Programm App.exe starten, zeigt sich zuerst Newtons Portrt. Wenn Sie auf dieses klicken, schliet sich das Fenster und fhrt die Konsolenanweisungen durch. Wenn Sie nun die Datei Newton.gif umbenennen, dann wird schon beim Start der Applikation abgebrochen, weil die .NETLaufzeitumgebung das Assembly ber Prfziffern auf Vollstndigkeit prft. Fhren Sie dieses einmal durch! Wenn Sie nun das AL-Kommando so abndern, dass die Bilddatei Newton.gif gar nicht ins Assembly aufgenommen wird, dann erfolgt eine Fehlermeldung nicht bei Programmstart, sondern zur Laufzeit, wenn auf die Datei zugegriffen werden sollte. al /t:exe /out:App.exe App.netmodule /main:App.Main

Fhren Sie auch dieses Experiment durch! Wie Sie nun festgestellt haben, darf bei einem Assembly keine nderung durchgefhrt werden. Wenn eine Applikation ein Assembly ldt, berprft die Laufzeitumgebung aufgrund des Manifests smtliche am Assembly beteiligten Dateien. Wenn die Daten nicht konsistent sind, dann wird die Applikation gar nicht erst gestartet.

C#
128

ILDASM (IL Disassembler)


Mit der .NET-Framework-SDK wird ein Werkzeug mit dem Namen ILDASM ausgeliefert (IL Disassembler). Sie knnen dieses Programm aus der Kommandozeile starten. Tun Sie dies und ffnen anschlieend das Assembly FractionMath.Fraction.dll im Ordner TestAssembly-SingleFile.

FractionMath.Fraction.dll im ILDASM Abb. 3.3

ffnen Sie dann das Assembly App.exe im Ordner TestAssemblyMultiFile.

Assembly App Abb. 3.4

Baugruppen (Assemblies)
3
ILDASM zeigt nun in einer lesbaren Form das Manifest und gegebenenfalls Typ-Metadaten an. Ein Doppelklick auf den Eintrag Manifest ffnet ein neues Fenster mit den Daten des Manifests. Hier werden u.a. Eintrge der referenzierten Assemblies, die verwendeten Module und unter anderem auch die Datei newton.gif zu finden sein.

129

Manifesteintrag Abb. 3.5

Mit dem Werkzeug ILDASM knnen Sie aber auch Metadaten von Modulen betrachten. ffnen Sie mit ILDASM die Datei App.netmodule.

Typ-Metadate in einem Modul Abb. 3.6

C#
130

3
In diesem Modul erkennen Sie neben dem Manifest auch die Typ-Metadaten. Das Modul implementiert die Typen(Klassen) App und NewtonPicture. Mit einem Doppelklick auf die Methoden wird auch deren IL-Code (Intermediate Language Code) angezeigt.

IL-Code-Ausschnitt Abb. 3.7

In diesem Zusammenhang soll einiges ber die MSIL (Microsoft Intermediate Language) festgehalten werden. Unter .NET wird Programmcode nicht in Form von ladbarem Maschinencode in den Dateien gehalten, sondern in einer abstrahierten Maschinensprache. Beim Start einer Applikation findet dann die Umwandlung dieser abstrahierten Maschinensprache (IL) in die Maschinensprache der eingesetzten Plattform statt (native code). Diese Umwandlung fhrt ein just-in-time Kompiler (JIT-Kompiler) durch. Der JIT-Kompiler ist plattformabhngig. Damit knnen Applikationen theoretisch ber Plattformen hinweg verteilt werden. Ihnen fallen nun sicherlich Parallelen zu JAVA ein, und werden dann auch gleich an ein Performanzproblem denken. Auch JAVA verwendet eine Zwischensprache, einen Bytecode. Dieser wird allerdings von der plattformabhngigen JAVA-Engine interpretiert. .NET kompiliert den Zwischencode zu native code. Allerdings wird der IL-Code nur in Portionen kompiliert, nmlich dann, wenn er bentigt wird. Einmal kompiliert bleibt dieser im Speicher. Microsoft meint, dass diese Strategie besser ist, als wenn zu Programmstart smtliche Assemblies JIT- kompiliert werden. Die Wahrscheinlichkeit, dass groe Teile von Code bei der Ausfhrung gar nicht verwendet werden, sei gro.

Baugruppen (Assemblies)
3
Ihnen sind beim Betrachten der Assemblies mit ILDASM sicherlich die kryptischen Hash-Eintrge aufgefallen. Diese haben eine besondere und auch wichtige Bedeutung. Diese Hash-Eintrge werden beim Erstellen eines Assemblies erzeugt und ergeben sich im Wesentlichen aus den Prfsummen der beteiligten Dateien. Diese Prfsummen werden beim Start eines Programms berprft. Eine nderung einer Datei htte zur Folge, dass die Prfsummen nicht mehr bereinstimmen und die Laufzeitumgebung wrde einen Start verhindern. Vielleicht kann damit die Virenproblematik ein wenig entschrft werden. Dies hat nun aber zur Folge, dass im Multifile-Assembly App.exe die Datei newton.gif nicht einfach mit einer anderen Bilddatei ausgewechselt werden kann, da newton.gif ja Bestandteil des Assembly darstellt! Es ist auch nicht mglich, eine getestete und funktionierende Anwendung durch Kopieren einer neuen Version einer Baugruppe zu zerstren. Dies kam sehr oft bei COM vor. Eine Applikation wurde auf Basis einer COM-Komponente entwickelt. Eine nderung der Komponente hatte die Eigenschaft, dass alle Clients dieser Komponente nun mit der neuen gearbeitet haben. Es war nicht mglich, das zu beeinflussen. Dadurch konnte es dann zu den gefrchteten Nebeneffekten kommen. Ein Client, bei dem die modifizierte Komponente getestet wurde, funktioniert zwar, aber bei einem anderen Client traten dann pltzlich Fehler auf.

131

Assembly-Entwicklung unter Visual Studio.NET


Gleich vorweg, unter Visual Studio.NET lassen sich keine multifile assemblies erstellen. In der Praxis ist dieser Fall auch eher selten. Im Bedarfsfall mssen Sie auf die Konsole ausweichen. Im folgenden Beispiel erstellen Sie das Assembly FractionMath.Fraction.dll mit dem Entwicklungssystem. Legen Sie dazu erst eine neue Projektmappe mit dem Namen AssemblyTest an und fgen dieser ein neues C#-Projekt mit dem Namen FractionMath.Fraction an, aber whlen Sie nun die Vorlage Klassenbibliothek aus!

C#
132

Auswahl Klassenbibliothek Abb. 3.8

ndern Sie den Namen der Quellcodedatei in gewohnter Manier auf einen selbstsprechenden Namen (z.B. Fraction.cs) und kopieren dann den Code der Klasse Fraction in diese Datei.
CD Beispiel Assembly

using System; namespace FractionMath { public class Fraction { private int z; private int n; ... } Der Build-Prozess erzeugt Ihnen dann das Assembly FractionMath.Fraction.dll. Testen Sie dieses Assembly gleich mit einem kleinen Konsolenprogramm. Fgen Sie zur Projektmappe ein neues C#-Projekt mit der Projektvorlage Konsolenanwendung hinzu und geben Sie diesem Projekt den Namen TestFraction. Die Applikation soll ja das gerade erstellte Assembly verwenden, und Sie wissen ja, ein (privates) Assembly muss sich im selben Ordner befinden wie die ausfhrbare Datei. Sie knnen diesen Kopiervorgang vom Entwicklungssystem erledigen lassen.

Baugruppen (Assemblies)
3
Stellen Sie einen Verweis (Referenz) auf das Assembly FractionMath.Fraction.dll her. Im Projektmappen-Explorer knnen Sie ber das Kontextmen Verweise > Verweis hinzufgen dem Projekt ein Assembly zuordnen. Da es sich hier um ein privates Assembly handelt, schalten Sie in dem sich ffnenden Dialog auf den Reiter Projekte um. Hier werden smtliche Projekte der aktuellen Projektmappe aufgelistet. Whlen Sie das Projekt FractionMath.Fracion aus und fgen Sie dem aktuellen Projekt eine Referenz hinzu.

133

Verweis auf privates Assembly herstellen Abb. 3.9

Sie werden nun einwnden, dass das Assembly (FractionMath.Fraction.dll) sich gar nicht im Ordner von TestFraction.exe befindet. Wenn Sie aber in den Ordner von TestFraction/bin/debug navigieren, dann fllt Ihnen auf, dass das Entwicklungssystem genau diese DLL herkopiert hat. Sehr angenehm ist auch, dass das Entwicklungssystem eine nderung eines Projekt-Assembly registriert und den notwendigen Kopiervorgang dann auch durchfhrt. Nun knnen Sie den Applikationscode schreiben. using System; using FractionMath; public class App
CD Beispiel Assembly

C#
134

3
{ public static void Main() { Fraction f1 = new Fraction(3,2); Fraction f2 = new Fraction(1,2); Console.WriteLine(f1+f2); } } Das Beispiel sollte nun einwandfrei lauffhig sein. Beim Anlegen des Projektes FractionMath.Fraction ber den Projektassistenten wurde auch noch eine weitere Datei mit dem Namen AssemblyInfo.cs angelegt. Ein Blick in diese Datei zeigt ein groe Anzahl von Attribut-Eintrgen. ber diese Datei lassen sich nun spezifische Eintrge konfigurieren, die dann in den Metadaten des Assembly vorzufinden sind. Drei Abschnitte finden sich in dieser Datei. Im ersten Abschnitt knnen grundstzliche Informationen zum Assembly angegeben werden. [assembly: [assembly: [assembly: [assembly: [assembly: [assembly: [assembly: [assembly: AssemblyTitle("FractionMath")] AssemblyDescription("Bruchzahlen")] AssemblyConfiguration("Testversion")] AssemblyCompany("Privat")] AssemblyProduct("Testserie")] AssemblyCopyright("Copyright(C) 2002")] AssemblyTrademark("")] AssemblyCulture("")]

Im zweiten Abschnitt kann eine Versionsnummer definiert werden. Eine Versionsnummer unter .NET setzt sich wie folgt zusammen. <major version>.<minor version>.<build number>.<revision> In der Datei AssemblyInfo wird allerdings nur die major version und die minor version angegeben. Die build number und revision number vergibt das Entwicklungssystem automatisch. Die build number ergibt sich aus der Anzahl der Tage seit 1. Januar 2000 und die revision number entspricht den Sekunden seit Mitternacht (Lokalzeit) dividiert durch 2. (Passen Sie also auf mit Aussagen, wann Sie gearbeitet haben, ber die Versionsnummer ist dies nachvollziehbar.)

Baugruppen (Assemblies)
3
[assembly: AssemblyVersion("1.0.*")] Der dritte Abschnitt wird im nchsten Kapitel diskutiert, wo das Thema shared assemblies ist. [assembly: AssemblyDelaySign(false)] [assembly: AssemblyKeyFile("")] [assembly: AssemblyKeyName("")]

135

Shared Assembly
Bislang haben Sie private Assemblies erzeugt. Bei der Verwendung von privaten Assemblies sollte es zu keinen Namenskonflikten kommen, da Sie ja jederzeit die Kontrolle ber die Namensvergabe haben. Ein privates Assembly muss sich immer im selben Ordner bzw. in einem Unterordner der Applikation befinden. Ganz besonders wichtige Assemblies knnen aber auch shared erklrt werden, d.h. auf diese knnen dann smtliche .NET-Applikationen zugreifen. Diese Assemblies mssen sich nicht im Ordner bzw. Unterordner der Applikation befinden. Alle Assemblies der ausgelieferten .NET-Umgebung sind shared assemblies. Bei einem shared assembly kann natrlich nicht ausgeschlossen werden, dass ein Dritthersteller zufllig ein shared assembly mit demselben Namen erzeugt. Dies wrde nun zu Konflikten fhren. COM (Component Object Model) hat das Problem mit GUIDs (Global Unique Identifier) gelst. COM hat sich die Komponenten nicht ber den Namen, sondern ber die GUIDs gesucht. In der Registrierung stand das Mapping zwischen GUID und der DLL, die die Komponenten besitzt. Microsoft hat sich bei .NET etwas Neues einfallen lassen. Es ist prinzipiell mglich, im GAC (Global Assembly Cache) mehrere DLLs mit demselben Namen zu halten. Die .NET-Laufzeitumgebung sucht sich das richtige Assembly. Wie soll dies funktionieren? Shared assemblies verfgen ber ein kryptografisches Schlsselpaar in Form eines ffentlichen und privaten Schlssels. Beim Kompilieren einer Applikation holt sich der Kompiler den ffentlichen Schlssel des shared assembly und fgt diesen in die Metadaten ein. Wenn die Applikation nun startet, sucht sich die .NET-Laufzeitumgebung aus dem GAC das shared as-

C#
136

3
sembly aus, dessen Name und privater Schlssel mit dem ffentlichen Schlssel der Applikation bereinstimmt und ldt dann das Assembly. Private Assemblies haben diesen Mechanismus nicht! D.h., dass ein shared assembly mit einem Schlsselpaar ausgestattet und entsprechend installiert werden muss. Vielfach wird eine Firma dasselbe Schlsselpaar fr alle ihre shared assemblies verwenden. Wenn Sie ein Assembly in den GAC bringen wollen, dann mssen Sie dieses zuerst mit einem privaten Schlssel signieren. Dies garantiert die Identitt des Assembly. Das Entwicklungssystem signiert ein Assembly, wenn Sie folgendes Attribut setzen (in der Datei AssemblyInfo.cs): [assembly: AssemblyKeyFile("c:\\key.snk")] In diesem Attribut geben Sie den Namen einer binren Datei an, die ein Schlsselpaar enthlt. Wie kommen Sie aber zu einem Schlsselpaar? Hier bietet das Entwicklungssystem ein Tool sn.exe an. Ein Aufruf aus der Konsole sn -k key.snk erzeugt ein Schlsselpaar und legt dieses in einer binren Form in die angegebene Datei. Bei jedem Kompilierdurchgang wird das Assembly nun signiert. Damit ein Assembly auch tatschlich in den GAC kommt, mssen Sie das Werkzeug GACUTIL.EXE aus der Konsole starten. gacutil -i c:\\FractionMath.Fraction.dll Nun ist das shared assembly endlich verwendbar. Ein Blick mit dem Explorer in C:\WINNT\Assembly zeigt Ihnen das. Im Fachjargon hat nun dieses Assembly einen strong name erhalten und ist somit durch die Signierung ber ein Schlsselpaar eindeutig. Es ist unter .NET absolut kein Problem auch Assemblies mit dem gleichen Namen im GAC zu installieren. ndern Sie nun die Applikation so ab, dass diese auf das Assembly zugreift, das im GAC registriert ist. Vorerst aber entfernen Sie das Projekt FractionMath.Fraction aus dem Projekt-Explorer, damit es nicht bei jedem Lauf neu erstellt wird (Projekt im Projekt-Explorer markieren und _ drcken).

Baugruppen (Assemblies)
3
137

Blick in den GAC Abb. 3.10

Verweisen Sie nun auf die Datei FractionMath.Fraction.dll, wie Sie es auch fr das private Assembly getan haben, nur dass Sie im Dialog ber den Button Durchsuchen die DLL auswhlen. Sie werden sehen, dass das Assembly nicht kopiert wurde. Wenn Sie das Assembly mit GACUTIL.EXE im GAC registriert haben, dann drfen Sie es natrlich nicht mehr von der bestehenden Position im Dateisystem lschen oder verschieben. Sie knnen mit dem Werkzeug GACUTIL.EXE auch Assemblies von der GAC lschen. gacutil -u FractionMath.Fraction

Zusammenfassung
Waren bisher DLLs die Installationseinheiten unter Windows, so sind dies unter .NET die Assemblies. Assemblies knnen im Allgemeinen aus mehreren Dateien bestehen. Die Zusammengehrigkeit der Dateien ist im Manifest festgeschrieben. Eine nderung einer Datei eines Assemblies hat zur Folge, dass das Assembly unbrauchbar wird. Auerdem wird zwischen privaten und shared assemblies unterschieden. Private Assemblies mssen sich im Ordner der Applikation bzw. in einem Unterordner der Applikation befin-

C#
138

3
den. shared assemblies knnen sich auch an einem anderen Ort befinden, mssen dann allerdings im GAC (Global Assembly Cashe) registriert sein. Ein Groteil der Probleme, die sich im Laufe der Zeit mit den Dlls ergeben haben (Stichwort DLL-Hlle), wird dadurch drastisch entschrft. berlegen Sie sich gut, ob Sie Funktionalitt in shared assemblies unterbringen. Wenn Sie dies tun, dann haben Sie einen erhhten Installationsaufwand und auch die mglichen Fehlerquellen mehren sich. Aus diesem Grund wird die Entwicklung einer shared assembly eher die Ausnahme darstellen! Assemblies werden auch verwendet, um mehrere Sprachversionen zu untersttzen. Dazu aber mehr in einem spteren Kapitel.

XML-Einfhrung

Einleitung XML-Grundlagen Das XML-Informationsmodell Schemas XPATH XSLT

140 140 141 147 148 150

C#
140

Einleitung
In der Softwarebranche hat sich kaum eine Technologie jemals in einem derart hohen Tempo durchgesetzt, wie dies bei der Extensible Markup Language (XML) der Fall war. Im Februar 1998 fhrte das World Wide Web Konsortium (W3C) mit der XML 1.0 Recommendation ein Format ein, das beschreibt, wie Daten strukturiert in Dokumenten gehalten werden sollen. Durch eine einheitliche Form der Datenhaltung sollte eine Mglichkeit geschaffen werden, Informationen zwischen verschiedensten Applikationen, ber verschiedene Plattformen hinweg auszutauschen. Trotz der praktisch unbegrenzten Palette der Anwendungsmglichkeiten von XML wurde bei der Spezifikation auf eine mglichst einfache Syntax Wert gelegt. Diese Schlichtheit von XML fhrte zu einer noch nie da gewesenen, weltweiten Akzeptanz eines Formates, sodass XML heute als das Standardformat fr Daten gilt. In der Literatur ist auch der Begriff Weltsprache fr Daten blich. Praktisch in jedem Winkel der Softwareindustrie trifft man irgendwo auf XML. XML ist ein integraler Bestandteil unter .NET. Es ist daher fr jeden Softwareentwickler unter .NET unumgnglich, sich frher oder spter mit XML vertraut zu machen. Dieses Kapitel gibt einen kurzen und schnellen Einstieg in XML. Es geht hier vornehmlich um die Begriffsbestimmungen.

XML-Grundlagen
Bei der Speicherung von Informationen ist es sinnvoll, diese in einer strukturierten Form zu halten. Dadurch wird der eigentliche Dateninhalt bersichtlich gehalten. Der logische Aufbau bzw. Zusammenhang der Daten wird ber Zusatzinformationen innerhalb eines Dokuments festgelegt. Diese Zusatzinformationen werden als Markup bezeichnet. Auch bei XML-Dokumenten wird ganz gezielt zwischen den Dateninhalten und deren logischen Struktur getrennt. Die Trennung erfolgt hier in einer standardisierten Form. Die XMLSpezifikation beschreibt dazu eine Menge von Regeln, wie beliebige Daten in einer einheitlichen Form gespeichert werden knnen.

XML-Einfhrung
4
Das folgende XML-Fragment hlt beispielsweise Informationen ber eine Person in einer strukturierten Form: <Person> <Anrede>Herr</Anrede> <Nachname>Duck</Nachname> <Vorname>Donald</Vorname> </Person> Beim Betrachten dieses XML-Fragments sticht einem die auffallende hnlichkeit von XML mit HTML ins Auge. HTML und XML haben dieselben Zeichen, um Dateninhalt von Zusatzinformationen zu trennen. Beide Sprachen stammen von derselben bergeordneten Markup-Sprache SGML (Standard Generalized Markup Language) ab, haben aber ansonsten von Vornherein nichts miteinander zu tun. Sie verfolgen grundstzlich unterschiedliche Zielrichtungen. Whrend XML eine Mglichkeit fr die strukturierte Haltung von Daten darstellt, wird mittels HTML eine Datensicht beschrieben. Wichtig ist, dass XML weder als Ersatz noch als Weiterentwicklung von HTML zu verstehen ist. XML wurde fr einen gnzlich anderen Zweck entwickelt. Prinzipiell werden XML-Daten in Elementen gehalten. Der Aufbau von XML-Elementen ist im Wesentlichen derselbe, wie der von HTML-Elementen. Sie bestehen immer aus einem start-tag und einem end-tag. Dazwischen befindet sich der Dateninhalt. Der Dateninhalt kann auch aus weiteren XML-Elementen bestehen. Im Gegensatz zu HTML, wo ein fester Satz von erlaubten Elementnamen, zum Beispiel <H1> existiert, drfen in XML-Dokumenten beliebige Elemente eingefhrt werden. Hier wird deutlich, weshalb es sich um eine Extensible (erweiterbare) Markup Language handelt.

141

Das XML-Informationsmodell
Die schnelle, weltweite Verbreitung von XML machte es notwendig, die einzelnen Teile eines XML-Dokuments eindeutig zu benennen und zu definieren. Eine einheitliche Sprachregelung vereinfacht das Verstehen von XML-Spezifikationen und ist Voraussetzung fr die Kommunikation unter Softwareentwicklern. Diese Definition einer Menge von abstrakten Objek-

C#
142

4
ten, welche zusammen ein XML-Dokument ergeben, wurde von einer W3C-Arbeitsgruppe unter dem Namen XML Information Set (Infoset) verffentlicht. Eine komplette Abhandlung ber das Infoset wrde den Rahmen dieses Kapitels sprengen, sodass hier nur die wichtigsten Teile vorgestellt werden. Zu diesem Zweck wird ein vollstndiges XML-Dokument betrachtet. Nachfolgend werden die einzelnen Komponenten nher beschrieben.
CD-Beispiel Bcher.xml

<?xml version="1.0"?> <?Sybex-Lagerverwaltung Prioritt="hoch" ?> <!-- derzeit lieferbare Bcher --> <Bcher> <Buch isbn="I-4711-0815"> <Titel>C# WebBook</Titel> <Autor>Ganahl Otmar</Autor> <Copy-Right>&#169; Sybex Verlag</Copy-Right> <Beschreibung> Eine praxisnahe Einfhrung in C# </Beschreibung> </Buch> <Buch isbn="I-1234-5678"> <Titel>Meine Erfindungen</Titel> <Autor>Dsentrieb Daniel</Autor> <Copy-Right>&#169; Dsentrieb Daniel</Copy-Right> <Beschreibung> Innovationen fr Entenhausen</Beschreibung> </Buch> </Bcher> <!-- Aktuelles Datum: 1.3.2001 --> <!-- Sachbearbeiter: G.C. --> Das obige Dokument entspricht der grundlegenden XML-Syntax. Es wird deshalb als wohlgeformtes Dokument (well formed document) bezeichnet. Wohlgeformte Dokumente knnen in einem XML-fhigen Browser dargestellt werden.

XML-Einfhrung
4
143

Bcher.xml in der Browseransicht Abb. 4.1

Prinzipiell kann ein XML-Dokument aus maximal drei verschiedenen Abschnitten bestehen. Diese Abschnitte sind der optionale Prolog mit der XML-Deklaration, Verarbeitungsanweisungen (Processing Instructions, PI's) und Kommentaren, das eine und einzige XML-Element im Dokument, das so genannte Dokument-Element (document element), der optionale Epilog, welcher nur aus Kommentaren besteht. Wohlgeformte XML-Dokumente haben die hierarchische Form eines Baumes. Sie besitzen einen einzelnen Wurzelknoten (root node, document root). Dieser enthlt neben dem Prolog und dem Epilog auch das Dokument-Element. Dieses enthlt

C#
144

4
alle weiteren XML-Elemente. Wichtig ist die Tatsache, dass es sich beim Wurzelknoten nicht um das Dokument-Element handelt.

Hierarchie Abb. 4.2

Verarbeitungsanweisungen
Verarbeitungsanweisungen (Processing Instructions, PIs) erlauben es, innerhalb von Dokumenten Anweisungen fr Programme zu speichern, die das XML-Dokument verarbeiten. PIs beginnen mit einem Ziel (PI Target). Dieser Name ist weitestgehend beliebig und identifiziert die Anwendung, an die sich die Anweisung richtet. Lediglich die Zielnamen XML und xml sind fr die Standardverarbeitungsanweisungen reserviert. <?Sybex-Lagerverwaltung Prioritt="hoch" ?> <?xml-stylesheet type=text/xsl href=buch.xsl?> Das obige Beispiel zeigt eine Verarbeitungsanweisung fr die Applikation SYBEX-Lagervarwaltung. Wie und ob diese Applikation die Verarbeitungsanweisung interpretiert bleibt ihr berlassen. Die Standardverarbeitungsanweisungen xml-stylesheet enthalten Informationen darber, wie eine XML-Datei dargestellt werden soll. Ein XML-fhiger Browser wird diese PI auswerten und die Daten entsprechend darstellen.

Elemente
Elemente sind die grundlegenden Bausteine eines XML-Dokuments. Ein Element ist ein Container fr Dateninhalte. Es kann Daten in Form von Zeichen und weiteren Elementen enthalten. Jedes Element beginnt mit einem start-tag und muss mit einem zugehrigen end-tag enden. Ein tag besteht aus einem

XML-Einfhrung
4
Elementnamen, der in Spitzklammern eingeschlossen ist. Elementnamen knnen beliebig gewhlt werden. Ein komplettes Element knnte also folgendermaen aussehen: <Buchtitel>C# Web Book</Buchtitel> <BUCHTITEL>C# Web Book</BUCHTITEL> XML ist case-sensitive, weshalb die obigen Elemente nicht identisch sind. Besitzt ein Element keinen Dateninhalt, so kann das start-tag und das end-tag zu einem so genannten emptyelement-tag zusammengefasst werden. <Buchtitel/>

145

Untergeordnete Elemente, Einbettung


Ein XML-Dokument ist vereinfacht gesprochen ein einzelnes XML-Element, das Dokument Element. Jedes XML-Element kann jedoch beliebig viele andere Unterelemente (nested elements, child elements) beinhalten. Im folgenden Beispiel hat das Element Buch zwei Kindelemente. <Buch> <Titel>C# Web Book</Titel> <Autor>Ganahl Otmar</Autor> </Buch> Auer dem Dokument-Element sind alle Elemente eines XMLDokuments untergeordnete Elemente. Der hierarchische Elementbaum ist eine wichtige Eigenschaft von XML-Dateien. Jedes Element muss vollstndig in seinem bergeordneten Element (parent element) aufgenommen sein. Dies ist eine grundlegende Regel und gilt fr alle wohlgeformten XML-Dokumente.

Attribute
Werden die Elemente als Hauptwrter eines XML-Dokuments bezeichnet, so sind Attribute zugehrige Adjektive. Oft besteht der Wunsch, einem Element Zusatzinformationen hinzuzufgen. Genau dies kann mit Attributen erfolgen. Attribute knnen in start-tags oder in empty-element-tags angegeben werden. Pro Element darf ein Attributname nur ein-

C#
146

4
mal vorhanden sein. Die Attributwerte werden dabei entweder in einfachen oder in doppelten Anfhrungszeichen angegeben. <Buch isbn='I-4711-0815'></Buch> <Buch isbn="I-4711-0815"/> Es ist beim Design einer geeigneten XML-Struktur fr bestimmte Daten genau zu beachten, ob eine Information in einem Unterelement oder als Attribut gehalten wird. <Buch isbn="I-4711-0815"> <Titel>C# Web Book</Titel> <Autor>Ganahl Otmar</Autor> </Buch> Whrend hier der Buchtitel durchaus auch als Attribut abgelegt werden knnte, ist beim Autor Vorsicht geboten. Was, wenn ein Buch mehrere Autoren besitzt? In diesem Fall wre beispielsweise folgende XML-Struktur mglich: <Buch isbn="I-4711-0815" Titel="C# Web Book"> <Autoren> <Autor>Ganahl Otmar</Autor> <Autor>Dsentrieb Daniel</Autor> <Autoren> </Buch> Attribute fhren zwar zu bersichtlichen und leichter lesbaren XML-Dokumenten, die nachtrgliche Erweiterbarkeit der XMLStruktur wird jedoch eingeschrnkt.

Kommentare
Kommentare drfen innerhalb des Dokuments an beliebiger Stelle auerhalb des brigen Markup stehen. Ein XML-Prozessor kann, muss aber nicht, der Anwendung eine Mglichkeit einrumen, den Text eines Kommentars zu lesen. Die Zeichenkette -- (zwei Trennstriche) darf innerhalb eines Kommentars nicht erscheinen. <!-- Dies ist ein Kommentar -->

XML-Einfhrung
4 Zeichenreferenzen
Eine Zeichenreferenz verweist auf ein spezifisches Zeichen im Unicode Zeichensatz, etwa ein Zeichen, welches auf dem Eingabegert nicht direkt verfgbar ist. Eine solche Referenz wird durch die Symbole &# eingeleitet. Dieser Sequenz folgt der gewnschte Unicode in dezimaler oder hexadezimaler Form und ein abschlieendes Semikolon. Das XML-Element <Copy-Right>&#169; Sybex Verlag</Copy-Right> wird von einem XML Prozessor wie folgt interpretiert: <Copy-Right> Sybex Verlag</Copy-Right> In der XML 1.0-Spezifikation existieren zustzlich fnf standard entities. Mit ihnen ist es mglich, auf reservierte Zeichen zu verweisen. Das folgende Beispiel zeigt die Verwendung dieser standard entities: <Copy-Right>&#169; Sybex Verlag &lt; &quot; &apos; &amp; &gt;</Copy-Right> <Copy-Right> Sybex Verlag < " ' & ></Copy-Right>

147

Zeichenfolgen (Character Data)


Character Data ist der Text in einem XML-Dokument, der kein Markup Code darstellt. Alle Dateninhalte bzw. Attributwerte fallen darunter. Da mit den Zeichen & und < Markup Code beginnt, drfen diese Zeichen in ihrer literalen Form niemals in Zeichenfolgen aufscheinen. Werden sie bentigt, mssen sie durch die standard entities &amp; und &lt; ersetzt werden. Dies wird jedoch von einem XML-Editor automatisch erledigt.

Schemas
Bisher wurden die Grundlagen besprochen, um wohlgeformte XML-Dokumente zu erstellen. Solche Dokumente halten sich an die grundlegenden Syntaxregeln von XML. Es werden aber ansonsten keinerlei Anforderungen, weder an deren Dateninhalt noch an deren logische Struktur gestellt.

C#
148

4
Sollen nun Applikationen ber XML miteinander kommunizieren, so ist es notwendig, sich in irgendeiner Form auf eine XML-Struktur zu einigen. Schemas definieren solche Vokabulare. Sie stellen meta data (Daten ber Daten) dar. Neben der Definition der XML-Elemente und -Attribute knnen auch Bedingungen an die Dateninhalte gestellt werden. Ohne eine exakte Beschreibung der Struktur eines XML-Dokuments msste bei dessen Verarbeitung viel Fehlerbehandlungscode eingefgt werden. Mithilfe von Schemas kann die Fehlerberprfung an einer einzigen Stelle, noch vor der eigentlichen Verarbeitung erfolgen. Sie muss nicht einmal implementiert werden, da fertige Validierer zur Verfgung stehen. Diese berprfen, ob ein XML-Dokument einem Schema entspricht. Tut es dies, so wird es als gltiges Dokument bezeichnet. Bad Data kann von vornherein mit sehr kleinem Aufwand herausgefiltert werden. Vor allem bei Internet-Applikationen ist das von groer Bedeutung, da hier nicht angenommen werden kann, dass die empfangenen Daten einer gewissen Qualitt entsprechen. Der Applikationsentwickler kmmert sich in weiterer Folge lediglich um die eigentliche Programmlogik.

XPATH
In praktisch allen XML-verarbeitenden Applikationen ist das Herausfiltern von bestimmten Dokumentteilen erforderlich. Man mchte beispielsweise in einem XML-Dokument alle Elemente mit einem bestimmten Namen manipulieren. Die XPATH-Empfehlung der W3C definiert eine offizielle Syntax zur Adressierung von Infoset-Teilmengen. Mit einer solch einheitlichen Abfragesprache ist es mglich, allgemeine Komponenten zu implementieren, die einen eleganten Zugriff auf XML-Daten erlauben. Das folgende Beispiel zeigt einen einfachen XPATH-Ausdruck: child::Buch Ausgehend von einem Kontextknoten kann obige Ortsangabe bewertet werden. Sie liefert eine Knotenmenge, welche alle Buch-Knoten, die Kinder des Kontextknotens sind, enthlt.

XML-Einfhrung
4
Jede Ortsangabe hat den Typ einer Knotenmenge und besteht aus drei Teilen (Achsenbezeichnung, Knotentest, Prdikatsmenge). Achse::Knotentest[Prdikat1][Prdikat2] Die Achsenkennung identifiziert den Vorrat an Knoten, der im Zuge der Ortsangabe durchgefiltert wird. Der nchste Teil der Ortsangabe, der Knotentest, definiert das erste dieser Filter. Einige Achsenbezeichnungen sind: self child parent decendant decendant or selft attribute der Kontextknoten selbst alle Kindknoten der bergeordnete Knoten alle Kinder und Kindeskinder alle Kinder, Kindeskinder und der Kontextknoten selbst alle Attributknoten

149

Der Knotentest kann zum Beispiel nach dem Namen durchgefhrt werden, so liefert der folgende Ausdruck alle Kindknoten mit dem Namen Buch: child::Buch Zur weiteren Filterung sind Prdikatausdrcke hilfreich. Ein Prdikatausdruck ist selbst ein XPATH-Ausdruck, der aber immer einen boolschen Wert liefert. Er wird fr jeden Knoten in der aktuellen Knotenmenge ausgewertet. Ist das Ergebnis fr einen Knoten wahr, so bleibt er in der Menge, ansonsten wird er entfernt. child::Buch[attribute::isbn = 'I-4711-0815'] Der obige XPATH Ausdruck beschreibt alle Knoten mit dem Namen Buch, die Kindknoten vom Kontextknoten sind und ein Attribut isbn mit dem Wert I-4711-0815 besitzen.

C#
150

4 Kurzformen
Eines der wichtigsten Ziele von XPATH war es, die Syntax so kompakt wie mglich zu gestalten. Die XPATH-Spezifikation definiert eine Reihe von Abkrzungen, welche in Ortspfad Ausdrcken eingesetzt werden drfen. XPATH Ausdruck child::ChildName attribute::AttributName descendant-or-self::node() self::node parent::node Kurzform ChildName @AttributName // . ..

Somit sind die untenstehenden XPATH-Ausdrcke jeweils quivalent: child::Buch/child::Autor Buch/Autor child::Buch/attribute::isbn Buch/@isbn Beginnt eine XPATH-Ortsangabe mit einem Schrgstrich, so wird als Kontextknoten immer die document root verwendet. Man spricht in diesem Fall von einer absoluten Ortsangabe. /Bcher/Buch[2]

XSLT
Die generelle Akzeptanz von XML zur Datenhaltung fhrte dazu, dass sich viele Entwickler eigene Schemas zurechtlegten. Unterschiedliche Applikationen, auch wenn sie eigentlich dieselben Daten verarbeiten, verwenden daher unterschiedliche XML-Schemas. Sollen nun XML-Daten ausgetauscht werden, so muss eine geeignete Transformation stattfinden. Bei dieser Umwandlung werden meistens keine Dateninhalte gendert, lediglich deren logische Zusammenstellung ndert sich. Man knnte die Umformung traditionell in einer Programmiersprache implementieren. Dies erfllt zwar den Zweck, ist aber mit viel Codierungsaufwand verbunden. Vor allem wenn sich das Quell- oder Zielschema ndert, msste diese Software jedes Mal aufwndig nachgezogen werden.

XML-Einfhrung
4
Die Idee ist nun, einen einzigen Transformationsprozessor zu verwenden. Dieser Prozessor erhlt zustzlich zum Quelldokument Informationen, wie die Transformation durchzufhren ist. Die Extensible Stylesheet Language for Transformation (XSLT) definiert eine Sprache auf XML-Basis, mit der sich Transformationsregeln formulieren lassen. XSLT ist also eine Sprache, mit der sich die Umwandlung von XML-Dokumenten in beliebige Textdokumente beschreiben lsst. Als Zieldokument sind smtliche auf Text basierende Dokumente vorstellbar, einschlielich XML und HTML. XSLT eignet sich daher hervorragend fr die Abbildung einer XML-Reprsentation auf eine andere. XSLT geht davon aus, dass drei Dokumente zum Einsatz kommen: das Quelldokument die XSLT-Formatvorlage (Stylesheet) das resultierende Zieldokument

151

XSLT-Prozess Abb. 4.3

Beim Quelldokument und der Formatvorlage handelt es sich um wohlgeformte XML-Dokumente. XSLT-Formatvorlage werden allerdings in Dateien mit der Erweiterung .xsl abgespeichert.

XML-Klassen

Einleitung XML-Dateien schreiben XML-Dateien lesen DOM-Objektmodell XPATH XSLT (XSL-Transformationen) XML-Serialisierung von .NET-Objekten Zusammenfassung

154 156 160 165 172 173 175 182

C#
154

Einleitung
Wie schon mehrfach erwhnt, ist XML ein wesentlicher Bestandteil der .NET-Laufzeitumgebung. Nicht nur, dass .NET mchtige Klassen zur Verarbeitung und Erzeugung von XMLDateien anbietet, die .NET-Laufzeitumgebung verwendet XML selbst intensiv. In den weiteren Kapiteln wird darauf eingegangen. Die .NET-Laufzeitumgebung untersttzt hier folgende W3CStandards: XML 1.0 inklusive DTD-Untersttzung XML Namespaces XML Schemas XPath Ausdrcke XSL/T Transformationen DOM Level 2 Soap 1.1 In den nachfolgenden Betrachtungen wird die folgende XMLDatei Personen.xml immer wieder verwendet. Das ist eine einfache Aufzhlung von Personen, deren Vorname, Nachname, Anrede und E-Mail-Adresse festgehalten werden. Eine XSL-Datei Personen.xsl erlaubt eine Transformation nach HTML, damit diese Daten auch in einem Browser anwenderfreundlich betrachtet werden knnen.
CD-Beispiel Personen.xml

<?xml version="1.0"?> <!--Dies ist Entenhausen--> <?xml-stylesheet type='text/xsl' href='personen.xsl'?> <Personen> <Person> <Vorname>Donald</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr</Anrede> <eMail>Donald.duck@entenhausen.com</eMail> </Person> <Person> <Vorname>Daisy</Vorname>

XML-Klassen
5
<Nachname>Duck</Nachname> <Anrede>Frau</Anrede> <eMail>daisy.duck@entenhausen.com</eMail> </Person> <Person> <Vorname>Dagobert</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr $</Anrede> <eMail>dollar@entenhausen.com</eMail> </Person> </Personen> Visual Studio.NET hat auch einen XML-Editor integriert, der Sie bei der Erzeugung von XML-Dateien untersttzt. In der XML-Datei Personen.xml wird auf ein Stylesheet verwiesen. Sie finden dieses Stylesheet Personen.xsl ebenfalls auf der beigelegten CD dieses Buches. Microsoft Internet Explorer 6.0 fhrt eine Transformation der XML-Datei durch, wenn das referenzierte Stylesheet zur Verfgung steht. Ein Doppelklick auf die Datei Personen.xml ffnet den Internet Explorer und die transformierte Datei wird sich wie folgt darstellen.

155

personen.xml im Internet Explorer unter Verwendung des Stylesheets personen.xsl Abb. 5.1

C#
156

XML-Dateien schreiben
Fr das Erzeugen vom XML-Dateien bieten sich sowohl die Klasse XmlWriter und deren Ableitungen als auch die Klasse XmlDocument und deren Ableitungen an. Die Klasse XmlDocument implementiert das DOM-Modell, das Sie nachfolgend noch kennen lernen werden. Vorerst werden Sie sich mit der Klasse XmlTextWriter beschftigen, die von XmlWriter abgeleitet ist. Wiederum in kleinen berschaubaren Beispielen werden Sie die Mglichkeiten dieser Klasse kennen lernen. Legen Sie fr die Experimente am besten eine neue Projektmappe an und fgen Sie dieser gleich ein Projekt namens WriteXml hinzu.
CD-Beispiel WriteXml1

using System; using System.Xml; public class App { public static void Main() { string Filename = @"c:\Personen.xml"; XmlTextWriter tw = new XmlTextWriter(Filename,null); tw.Formatting = Formatting.Indented; tw.WriteStartDocument(); tw.WriteComment("Dies ist Entenhausen"); tw.WriteProcessingInstruction("xml-stylesheet", "type='text/xsl' href='personen.xsl'"); tw.WriteStartElement("Person"); tw.WriteElementString("Vorname","Donald"); tw.WriteElementString("Nachname","Duck"); tw.WriteElementString("Anrede","Herr"); tw.WriteElementString("eMail", "Donald.Duck@entenhausen.com"); tw.Flush(); tw.Close(); } }

XML-Klassen
5
Die Klasse XmlTextWriter befindet sich im Namensraum System.Xml. Diesen ffnen Sie am besten. Die Klasse selbst ist im Assembly System.Xml.dll implementiert. Daher mssen Sie dem Projekt einen Verweis auf dieses Assembly setzen (Kontextmen Verweise > Verweise hinzufgen). Nun aber zum Code selbst: Der Dateiname der gewnschten Datei wird im String Filename gehalten. Das @-Zeichen vor dem String ist ein C#-Feature. Smtliche Zeichen im folgenden hart kodierten String werden als normale Zeichen (allerdings, wie in C# blich, in Unicode) interpretiert. string Filename = @"c:\Personen.xml"; Ohne @-Zeichen mssten Sie die Zeile wie folgt schreiben: string Filename = "c:\\Personen.xml"; XmlTextWriter tw = new XmlTextWriter(Filename,null); In der Methode Main() wird ein Objekt tw vom Typ XmlTextWriter angelegt. Das Objekt bekommt einen Dateinamen (c:\Personen.xml) als bergabeparameter. XmlTextWriter verwaltet also intern eine Datei. tw.Formatting = Formatting.Indented; Damit formatiert XmlTextWriter die XML-Datei in einer besser lesbaren Form. Child-Tags werden nmlich eingerckt dargestellt. Sie erkennen nun im Code eine Reihe von Methoden der Klasse XmlTextWriter. Fr smtliche Elemente aus dem W3C-Infoset gibt es (meist mehrfach berlagerte) Methoden WriteXXX. Die Methode WriteElementString erlaubt Ihnen einfache XML-Elemente mit Start-Tag und End-Tag zu schreiben. Im Beispiel werden die Methoden WriteStartDocument, WriteComment und WriteProcessingInstruction fr die Erzeugung des XML-Prologs verwendet. tw.Flush(); tw.Close(); Die Datei wird erst beschrieben, wenn Sie die Methode Flush auf das Objekt tw ausfhren. Zu guter Letzt schlieen Sie dann noch den XmlTextWriter.

157

C#
158

5
Mit diesem Modell ist es nicht mglich, in eine bestehende Datei Elemente anzufgen! Es ist ein forward cursor-Modell, wie Sie sicherlich erkannt haben. Wenn Sie die Klasse XmlTextWriter weiter erforschen, dann finden Sie hier smtliche Methoden, um Elemente einer XML-Struktur zu erzeugen (so z.B. Kommentare, PI-Informationen, Attribute usw.). Es macht nicht viel Sinn, nun smtliche WriteXXX-Methoden aufzuzhlen, aber ein Blick in die MSDN ist diesbezglich empfehlenswert. Natrlich knnen Sie auch eine eigene XmlTextWriter-Klasse erzeugen. Dies wird im nchsten Beispiel demonstriert. Die Klasse XmlTextWriter soll um eine Methode WritePerson erweitert werden, und somit das Hinzufgen von Personen in eine XML-Datei auch programmtechnisch vereinfachen. Erweitern Sie das Projekt wie folgt:
CD-Beispiel WriteXml2

using System; using System.Xml; public class App { public class XmlPersonTextWriter:XmlTextWriter { public XmlPersonTextWriter( string Filename):base(Filename,null) { Formatting = Formatting.Indented; WriteStartDocument(); WriteComment("Dies ist Entenhausen"); WriteProcessingInstruction( "xml-stylesheet", "type='text/xsl' href='personen.xsl'"); WriteStartElement("Personen"); } public void WritePerson( string vn,string nn, string ar, string em) { WriteStartElement("Person"); WriteElementString("Vorname",vn); WriteElementString("Nachname",nn); WriteElementString("Anrede",ar);

XML-Klassen
5
WriteElementString("eMail",em); WriteEndElement(); } } public static void Main() { string Filename = @"c:\Personen.xml"; XmlPersonTextWriter tw = new XmlPersonTextWriter(Filename); tw.WritePerson("Donald", "Duck", "Herr", "Donald.Duck@entenhausen.com"); tw.WritePerson("Daisy", "Duck", "Frau", "Daisy.Duck@entenhausen.com"); tw.WritePerson("Dagobert", "Duck", "Herr ", "Dollar@entenhausen.com"); tw.Flush(); tw.Close(); } } Die neue Klasse XmlPersonTextWriter ist von XmlTextWriter abgeleitet. Der Konstruktor dieser Klasse fgt auch gleich den Prolog der XML-Datei hinzu. Die Implementierung der Methode WritePerson fgt XML-Elemente eines Personen-Eintrages hinzu. Damit vereinfacht sich im Hauptprogramm das Hinzufgen von Personen in die XML-Datei betrchtlich. tw.WritePerson("Donald", "Duck", "Herr", "Donald.Duck@entenhausen.com"); Die Klasse XmlDocument kann auch verwendet werden, um XML-Dateien zu erzeugen. Diese Klassen lernen Sie in Krze kennen.

159

C#
160

XML-Dateien lesen
Sie kennen sicherlich das DOM-Objektmodell (W3C) und das SAX-Programmiermodell. Das DOM-Objektmodell ldt das gesamte XML-Dokument in den Speicher und ermglich somit einen objektorientierten Zugriff auf die einzelnen Elemente einer XML-Datenstruktur. Eine Navigation innerhalb des Dokumentes ist damit sehr einfach mglich, da sich ja smtliche Daten im Speicher befinden. Dieser an und fr sich nicht spektakulre Umstand wird hier betont, weil das SAX-Programmiermodell ereignisgesteuert ist, und sich somit diametral vom DOM-Modell unterscheidet. Beide Modelle haben ihre Vor- und Nachteile. Das DOM-Modell wird tendenziell bei kleineren XML-Dateien verwendet, das SAX-Programmiermodell ist komplexer, dafr aber viel schneller und kann daher bei sehr groen Dateien von Vorteil sein. Das SAX-Programmiermodell ist ein Push-Modell, das heit, es wird die XML-Datei durchgescannt (forward cursor) und ein Ereignis in die Applikation gepushed, sobald ein Element erkannt wird. Der Programmierer reagiert also auf diese Ereignisse. Ein Navigieren innerhalb der Datei ist damit nicht mglich. Unter .NET werden die Klassen XmlReader und deren Ableitungen XmlTextReader, XmlNodeReader und XmlValidatingReader angeboten, die dem SAX-Programmiermodell sehr hnlich sind, aber ein Pull-Modell darstellen. Die Daten werden in eine Applikation gebracht, wenn es der Programmierer veranlasst. Ein weiterer Vorteil des Pull-Modells ist, dass der Programmierer selektiv Daten in die Applikation holen kann. Bei einem Push-Modell mssen alle Daten von der Applikation verarbeitet werden!

XmlTextReader
Die Klasse XmlReader implementiert Methoden, die ein einfaches Lesen von XML-Dateien ermglichen. Fgen Sie hierzu Ihrer Projektmappe eine neue C#-Konsolenanwendung mit dem Namen ReadXml hinzu. Verweisen Sie auf das Assembly System.Xml.dll und ffnen Sie im Quellcode den Namensraum System.xml.

XML-Klassen
5
using System; using System.Xml; public class App { public static void Main() { string Filename = @"c:\Personen.xml"; XmlTextReader tr = new XmlTextReader(Filename); while(tr.Read()) { if(tr.Name == "Vorname" && tr.NodeType == XmlNodeType.Element) { Console.WriteLine(tr.ReadElementString()); } } } } Es wird zuerst eine XmlTextReader-Instanz tr erzeugt. Auf diese Instanz wird dann wiederholt die Methode Read() aufgerufen, die true zurckgibt, wenn diese etwas lesen konnte. Bei jedem Lesevorgang ndert sich natrlich der Status der Instanz tr, dessen Status-Werte ber Eigenschaften von tr ausgelesen und verarbeitet werden knnen. Im Beispiel wird geprft, ob die Eigenschaft (Status) Name des tr-Objektes den Wert Vorname besitzt und ob die Eigenschaft XmlNode-Type den Enumerationswert Element besitzt. Wenn dies der Fall ist, wird die Methode ReadElementString aufgerufen, der in diesem Fall die Vornamen der Eintrge zurckgibt und diese auch am Bildschirm ausgibt. XmlNodeType ist eine Enumeration mit Werten, die dem XMLInfoset der W3C entsprechen. Hier sind einige Enumerationswerte aufgelistet: Attribute CDATA Comment Document
CD-Beispiel ReadXml1

161

C#
162

5
DocumentType Element EndElement Entity None ProcessingInstruction WhiteSpace Noch einmal: Bei jedem Read()-Vorgang springt der Cursor zum nchsten XmlNodeType und das XmlTextReader-Objekt ndert seinen Status. Der Typ kann ber die Eigenschaft NodeType ausgelesen werden. Es ist naheliegend, aber wichtig zu erkennen, dass z.B. die Eigenschaft Name eines XmlTextReader-Objekts je nach NodeType eine andere Bedeutung hat. Hier eine nicht vollstndige Auflistung der Bedeutung der Eigenschaft Namen fr die unterschiedlichen NodeTypes: Attribute DocumentType Element EntityReference ProcessingInstruction XmlDeclaration Name des Xml-Attributs Name des Dokumententyps Tag-Name Name des Ref-Entity Ziel #xml-Text

Die Methode ReadElement ist zwar eine Methode der Klasse XmlTextReader, darf aber nur dann aufgerufen werden, wenn der Status NodeType des XmlTextReader-Objektes den Wert XmlNodeType.Element besitzt und gibt dann den Wert innerhalb der Tags an. Wird diese Methode auch auf andere NodeTypes angewendet, wird eine Exception ausgelst. Einen weiteren wichtigen Aspekt soll hier erwhnt werden. Wrden Sie z.B. eine bestimmte Email-Adresse suchen, und mit dieser Information dann auf den Vor- und Nachnamen schlieen wollen, dann gestaltet sich das ziemlich aufwndig, da in dem Beispiel Vor- und Nachname vor dem Eintrag Email vorkommen. Da XmlTextWriter ein forward cursor-Modell

XML-Klassen
5
implementiert, knnen Sie nicht rckwrts navigieren. Hier mssten vorhergehende Elemente zwischengespeichert werden, um im Bedarfsfall darauf zurckzugreifen. Es wird Ihnen hier sicherlich klar werden, dass XmlTextReader sich sehr gut eignet, um sehr schnell ein oder mehrere bestimmte Informationen aus der XML-Datenstruktur zu extrahieren, aber dass bei komplexerer Funktionalitt der Programmieraufwand doch gehrig ansteigt.

163

XmlValidatingReader
Neben XmlTextReader existiert in der .NET-Laufzeitumgebung auch die Klasse XmlValidatingReader, die beim Lesen eben eine berprfung gegenber einem DTD (W3C), XSD-Schema(W3C) oder einem XDR-Schema (Microsoft) durchfhren kann. Die Klasse XmlNodeReader erhlt statt einer Datei ein Objekt vom Typ XmlNode. Dazu mehr im nchsten Kapitel. Im folgenden Beispiel werden Sie kennen lernen, wie mit der Klasse XmlValidatingReader eine Validierung gegenber eines W3C-Schemas gettigt werden kann. using System; using System.Xml; using System.Xml.Schema; public class App { static void OnValidationError( object sender,ValidationEventArgs args) { Console.WriteLine(args.Message); } public static void Main() { string Filename = @"c:\Personen.xml"; XmlTextReader tr = new XmlTextReader(Filename); XmlValidatingReader tvr = new XmlValidatingReader(tr); tvr.ValidationType = ValidationType.Schema; tvr.ValidationEventHandler += new ValidationEventHandler(OnValidationError);
CD-Beispiel ReadXml2

C#
164

5
while(tvr.Read()) { if(tvr.Name == "Vorname" && tvr.NodeType == XmlNodeType.Element) { Console.WriteLine(tvr.ReadElementString()); } } } } Sie sehen in der Main()-Methode wird in gewohnter Weise ein XmlTextReader-Objekt mit dem Namen tr angelegt. Dieses Objekt wird dann als Konstruktionsparameter der Klasse XmlValidatingReader tvr verwendet. Der ValidationType wird auf den Enumerationstyp ValidationType.Schema gestellt (mglich sind u.a. auch DTD und XDR). Die Klasse XmlValidatingReader lst ein Event aus, wenn beim Validieren eine Ungereimtheit auftritt. Im Beispiel wird diesem Event die Bearbeitungsmethode OnValidateError zugewiesen, die hier eine Ausgabe auf der Konsole erzeugt. Der Rest des Codes bleibt zum vorherigen Beispiel gleich. Die Datei Personen.xml muss nun natrlich einen Verweis auf das Schema besitzen. <Personen xmlns="urn:EntenhausenPersonenliste" "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:EntenhausenPersonenliste personen.xsd"> ... ... ... ... </Personen> Hier wird also auf die Datei Personen.xsd verwiesen, die sich im selben Ordner befinden muss wie Personen.xml. Diese Schema-Datei finden Sie auf der beiliegenden CD im Beispiel XmlRead2. Nach dieser Schema-Definition muss ein Element Person die Elemente Vorname, Nachname, Anrede und eMail besitzen und es ist gefordert, dass die Anrede entweder Herr oder aber Frau sein muss.

XML-Klassen
5
<Person> <Vorname>Dagobert</Vorname> <Anrede>Herr $</Anrede> <eMail>dollar@entenhausen.com</eMail> </Person> Dieses Beispiel wrde zwei Validierungsfehler generieren, weil das Element Nachname fehlt und die Anrede Herr $, statt nur Herr lautet.

165

DOM-Objektmodell
Die DOM-Implementierung (Document Object Model) in .NET erfllt die W3C-Spezifikationen Level 1 und Level 2. Das DOMModell wird ber dies Klasse XmlNode implementiert. XmlNode ist eine abstrakte Basisklasse und reprsentiert, wie der Name sagt, einen Knoten (nach der W3C-Infoset Spezifikation) im XML-Dokument. Fr jede Art von Knoten gibt es eine Klasse, die von XmlNode abgeleitet ist. Hier einige wichtige Nodes: XmlAttribute XmlElement XmlDeclaration XmlComment XmlEntity XmlNotation XmlWhitespace XmlText XmlDocument ... In einem Beispiel werden Sie die Funktionsweise und Zusammenarbeit dieser Klassen kennen lernen. using System; using System.Xml; public class App {
CD-Beispiel XmlDom1

C#
166

5
public static void Main() { string Filename = @"c:\Personen.xml"; XmlDocument doc = new XmlDocument(); doc.Load(Filename); XmlNodeList nl = doc.GetElementsByTagName("Vorname"); foreach(XmlNode n in nl) { Console.WriteLine(n.InnerText); } } } Hier wird eine Instanz doc vom Typ XmlDocument erzeugt und mit der XML-Datei geladen. Die Instanz doc hlt somit die gesamte XML-Datenstruktur. Aus der Instanz doc wird nun ber die Methode GetElementsByTagName(...) eine XmlNode-Liste gewonnen. Da Node-Listen sehr oft gebraucht werden, implementiert .NET eine eigene Klasse XmlNodeList. Die Liste nl im obigen Beispiel wird durch die Methode GetElementByTagName(...) gefllt. Im Beispiel also mit Nodes Vornamen, deren Inhalte nun z.B. mit der Eigenschaft InnerText gelesen, aber auch neu belegt werden kann. Erwhnenswert ist, dass XmlDocument von XmlNode abgeleitet ist, und somit selbst einen Node (Knoten) darstellt.

XmlNode

Die Klasse XmlNode Abb. 5.2

XmlNode ist die zentrale Klasse des .NET-DOM-Objektmodells. In der Abbildung sind die fr das Verstndnis wichtigsten

XML-Klassen
5
Member-Variablen aufgelistet. XmlNode reprsentiert einen Knoten nach den Spezifikationen des W3C-Infosets. Jeder XML-Knoten kann natrlich Kindknoten besitzen. Daher hat die Klasse XmlNode eine Member-Variable ChildNodes vom Typ XmlNode[]. Da die Klasse XmlDocument selbst von der Klasse XmlNode abgeleitet ist, besitzt diese natrlich ebenfalls die Member-Variable ChildNodes. Damit knnen Sie sich, ausgehend von einer Instanz XmlDocument, zu jedem beliebigen Knoten innerhalb des Dokumentes bewegen. Um auch programmtechnisch feststellen zu knnen, von welchem Typ ein Knoten ist, wurde die Klasse XmlNode auch mit einer Member-Variable NodeType ausgestattet. Diese MemberVariable ist vom Typ XmlNodeType, die Sie schon kennen gelernt haben. XmlNodeType ist ein Enumerationstyp, und die wichtigsten Aufzhlungswerte haben Sie schon kennen gelernt. Wenn Sie nun einen Knoten haben, dann knnen Sie auch auf den Inhalt des Knotens zugreifen. Hierzu dienen vier MemberVariablen, alle vom Typ string. Name Value InnerText InnerXmlText Die Bedeutung dieser Member-Variablen ist abhngig vom Node-Typ! Es ist empfehlenswert, dass Sie sichmittels kleiner Experimente die unterschiedliche Qualitt der Eigenschaft bewusst machen. Im Folgenden ist die Bedeutung dieser Eigenschaften fr die wichtigsten Knotentypen aufgelistet.

167

Die Bedeutung der Member-Variable Name


Attribute Comment Element Text Name des Attributs #Kommentar Tag-Name #Text

C#
168

5
Die Bedeutung der Member-Variable Value
Attribute Comment Element Text Der Wert des Attributs #Kommentar null-Referenz Inhalt des Textknotens

Die Bedeutung der Member-Variable InnerText


Die Eigenschaft InnerText eines Objekts vom Typ XmlNode fasst alle Inhalte des Knotens und auch dessen(!) Kindknoten. Nehmen Sie an, ein Objekt vom Typ XmlNode hlt folgenden Knoten (mit Kindknoten). <Person> <Vorname >Daisy</Vorname> <Nachname>Duck</Nachname> <Anrede>Frau</Anrede> <eMail>Daisy.Duck@entenhausen.com</eMail> </Person> Hier wrde die Member-Variable InnerText folgenden String beinhalten: DaisyDuckFrauDaisy.Duck@entenhausen.com Ein anderes Beispiel: Das Objekt vom Typ XmlNode enthlt folgenden Knoten: <Vorname>Donald</Vorname> Hier wrde die Member-Variable InnerText folgenden String halten: Donald

Die Bedeutung der Member-Variable InnerXmlText


Die Member-Variable InnerXmlText wrde bei obigem komplexen Knoten folgenden String halten. <Vorname>Daisy</Vorname><Nachname>Duck</Nachname><Anre de>Frau</Anrede><eMail>Daisy.Duck@entenhausen.com</eMa il>

XML-Klassen
5
Nach einigen Experimenten werden Sie das Verhalten dieser Eigenschaften kennen. Experimente vertiefen auch das Denkmodell des W3C-Infosets und daher kann man diese kleinen Experimentieraufgaben nur wrmstens empfehlen!

169

XML-Dokumente verndern
Mit dem DOM-Modell lsst sich eine XML-Datenstruktur auch verndern. Wenn Sie z.B. dem Element <Person> je ein Attribut type="animal hinzufgen, knnten Sie dies wie folgt realisieren: public class App { public static void Main() { string Filename = @"c:\Personen.xml"; XmlDocument doc = new XmlDocument(); doc.Load(Filename); XmlNodeList nl = doc.GetElementsByTagName("Person"); foreach(XmlNode n in nl) { XmlNode attr = doc.CreateNode(XmlNodeType.Attribute,"type",""); attr.Value = "animal"; n.Attributes.SetNamedItem(attr); } doc.Save(Filename); } } Das Ergebnis: <?xml version="1.0"?> <!--Dies ist Entenhausen--> <?xml-stylesheet type='text/xsl' href='personen.xsl'?> <Personen> <Person type="animal"> <Vorname>Donald</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr</Anrede>
CD-Beispiel XmlDom2

C#
170

5
<eMail>Donald.Duck@entenhausen.com</eMail> </Person> <Person type="animal"> <Vorname>Daisy</Vorname> <Nachname>Duck</Nachname> <Anrede>Frau</Anrede> <eMail>Daisy.Duck@entenhausen.com</eMail> </Person> <Person type="animal"> <Vorname>Dagobert</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr </Anrede> <eMail>Dollar@entenhausen.com</eMail> </Person> </Personen> Mit der Methode doc.CreateNode kann unter Angabe des NodeTypes ein neuer XmlNode erzeugt werden, in diesem Fall ein Attribut. Es wird der Wert des Attributes gesetzt und dann der Attribut-Liste des Knotens per SetNamedItem hinzugefgt. Sie sehen also, dass XmlNode eine Eigenschaft vom Typ Attributes besitzt. Natrlich hat diese Eigenschaft bei speziellen Knoten, wie z.B. Text keinen Sinn. Dass die Klasse XmlDocument natrlich eine Save-Methode besitzt ist anzunehmen.

Suchen innerhalb von XML-Dokumenten


CD-Beispiel XmlDom3

using System; using System.Xml; public class App { public static void Main() { string Filename = @"c:\Personen.xml"; XmlDocument doc = new XmlDocument(); doc.Load(Filename); string search = @"Personen/Person"; XmlNode node = doc.SelectSingleNode(search); if(node == null)

XML-Klassen
5
Console.WriteLine("Kein Node gefunden"); else Console.WriteLine(node.InnerXml); } } In diesem Code-Beispiel sehen Sie, dass die Klasse XmlDocument auch einen Suchalgorithmus innerhalb der XML-Datenstruktur anbietet. string search = @"Personen/Person"; Der String search muss den Spezifikationen von XPATH gehorchen. Die Methode SelectSingleNode(search) gibt den nchstgelegenen Knoten, der die XPath-Suchbedingung erfllt, zurck. ber die Methode SelectNodes(search) knnen alle Knoten in einem Schlag in eine Instanz vom Typ XmlNodeList gebracht werden. using System; using System.Xml; public class App { public static void Main() { string Filename = @"c:\Personen.xml"; XmlDocument doc = new XmlDocument(); doc.Load(Filename); string search = @"Personen/Person"; XmlNodeList nl = doc.SelectNodes(search); foreach(XmlNode n in nl) { foreach(XmlNode nc in n.ChildNodes) { if(nc.Name == "Anrede") Console.WriteLine(nc.InnerText); } } } }
CD-Beispiel XmlDom4

171

C#
172

5
Hier werden per XPATH-String alle relevanten Knoten zurckgegeben und ber die Member-Variable ChildNodes analysiert und entsprechend verarbeitet.

Zusammenfassung
Das DOM-Modell wirkt auf den ersten Blick kompliziert und komplex. Ist es aber nicht. Einmal durchschaut, ist es sehr leistungsfhig. Es wird noch einmal empfohlen: Spielen und experimentieren Sie mit diesen Beispielen. So lernen Sie den Umgang mit XmlNodes und auch das Prinzip der W3C-Infosets kennen. Ein sauberes Denkmodell erspart Ihnen dann sehr viel Zeit im Umgang mit diesen Klassen.

XPATH
Im obigen Beispiel haben Sie schon mit XPATH-Ausdrcken gearbeitet, um einen bestimmten Knoten in einer XML-Datenstruktur zu finden. XPATH ist eine W3C-Spezifikation, die sozusagen eine Adressierung eines Knotens innerhalb einer XML-Datenstruktur erlaubt. Die .NET-Laufzeitumgebung bietet eine Klasse XPathDocument an, die speziell fr XPATH (und auch XSLT) optimiert ist. Beachten Sie aber, dass XPathDocument nicht (!) von XmlDocument abgeleitet ist! Die weiteren notwendigen Klassen in diesem Zusammenhang sind XPathNavigator und XPathNodeIterator. Diese lernen Sie im nchsten Beispiel kennen. Beachten Sie auch, dass Sie den Namensraum System.Xml.XPath ffnen!
CD-Beispiel XmlXPath

using System; using System.Xml; using System.Xml.XPath; public class App { public static void Main() { string Filename = @"c:\Personen.xml"; XPathDocument doc = new XPathDocument(Filename);

XML-Klassen
5
XPathNavigator nav = ((IXPathNavigable)doc).CreateNavigator(); XPathNodeIterator iter = nav.Select(@"Personen/Person/Vorname"); while(iter.MoveNext()) { Console.WriteLine(iter.Current.Value); } } } Zuerst erzeugen Sie hier eine Instanz der Klasse XPathDocument mit dem Namen doc. Um in diesem Dokument navigieren zu knnen, brauchen Sie ein XpathNavigator-Objekt, das Sie bekommen, wenn Sie die Methode CreateNavigator(...) aufrufen. CreateNavigator ist eine Methode der Schnittstelle IXPathNavigator, und daher mssen Sie zuerst diese Schnittstelle vom XPathDocument-Objekt doc anfordern. In C# geschieht dies ja bekanntlich per Casting. Dieses Navigationsobjekt (nav) versorgen Sie nun mit XPATH-Ausdruck ber die Methode Select(...), die Ihnen ein neues Objekt vom Typ XPathNodeIterator (iter) zurckgibt. Diese Klasse implementiert ein Cursor-Modell und erlaubt Ihnen, sich innerhalb der Ergebnismenge zu bewegen. Sie werden sich nun vielleicht fragen, welchen Vorteil die Klasse XmlPathDocument gegenber der Klasse XmlDocument hat, da die Bedienung der Klasse XmlPathDocument doch aufwndiger und komplexer erscheint. Der Vorteil liegt in der Performanz und wirkt sich natrlich nur bei sehr groen XML-Dokumenten sprbar aus.

173

XSLT (XSL-Transformationen)
XPATH wird selten direkt verwendet. Der W3C-Standard XSL verwendet XPATH intensiv und es werden nun die Mglichkeiten betrachtet, die .NET bezglich XSLT bietet. XSL-Transformationen erlauben, eine bestehende XML-Datenstruktur in eine andere beliebige Datenstruktur umzuwandeln. Das Interessante an dieser Spezifikation ist, dass die Transformationsregeln oder die Transformationsinformation selbst in Form einer XML-Datei definiert werden kann. Es kann praktisch jede beliebige Datenstruktur damit erzeugt werden. Hauptschlich

C#
174

5
wird XSLT verwendet, um eine bestehende XML-Datenstruktur in eine andere XML-Datenstruktur umzuwandeln, oder aber ausgehend aus einer XML-Datenstruktur ein HTML-Datenstruktur zu erzeugen, die dann in einem Browser entsprechend dargestellt wird. Damit ist eine strikte Trennung zwischen Daten und Sicht gewhrleistet. Fr XSL-Transformationen bietet .NET die Klasse XslTransformation an. Diese Klasse finden Sie im Namensraum System.Xml.Xsl. Das dazugehrige Beispiel:
CD-Beispiel XmlXSLT

using using using using using

System; System.Xml; System.Xml.XPath; System.Xml.Xsl; System.IO;

public class App { public static void Main() { string Filename = @"c:\Personen.xml"; XPathDocument doc = new XPathDocument(Filename); XslTransform trans = new XslTransform(); trans.Load(@"c:\Personen.xsl"); FileStream fs = new FileStream( @"c:\Personen.html",FileMode.Create); XPathNavigator nav = ((IXPathNavigable)doc).CreateNavigator(); trans.Transform(nav,null,fs); } } Auch hier wird ein XPathDocument-Objekt doc erzeugt. Zustzlich wird dann ein XslTransform-Objekt angelegt und mit der entsprechenden .xsl-Datei geladen. Mit der Methode Transform der Klasse XslTransform kann nun eine XSL-Transformation durchgefhrt werden. Allerdings erwartet diese Methode einen XPathNavigator (was darauf hinweist, dass XSL auf XPATH basiert). Diesen erzeugen Sie in bekannter Weise.

XML-Klassen
5
Die Methode bekommt dann noch einen FileStream, damit sie auch wei, wo die transformierten Daten abzulegen sind. Die Datei Personen.xsl ist eine Vorlage zur Transformation der Personen.xml-Datei in eine HTML-Datei. Das Ergebnis Personen.html lsst sich nun problemlos im Browser ffnen.

175

XML-Serialisierung von .NET-Objekten


Unter dem Begriff der Serialisierung versteht man in der Softwareentwicklung im Wesentlichen das Abspeichern bzw. Laden von Objekten auf ein Speichermedium, wie z.B. einer Datei. Mchten Sie z.B. ein Objekt auf die Festplatte abspeichern, dann mssen Sie einen Byte-Stream erzeugen und diesen dann in einer Datei speichern. Das Initialisieren eines Objektes aus Daten aus einem Speichermedium geschieht durch den umgekehrten Vorgang. Es ist also Aufgabe des Entwicklers, einen Byte-Stream zu erzeugen. Gerne wurde ein Byte-Stream aus lesbaren Zeichen erzeugt, sodass mit einem handelsblichen Editor der Inhalt gelesen und auch unter Umstnden verndert werden konnte. Binre Formate haben den Vorteil, dass sie im Allgemeinen weniger Platz bentigen. Wenn nun ein Objekt in Textform abgespeichert wird, dann stellt sich heute natrlich gleich die Frage, warum nicht in XML-Syntax? Dss ist naheliegend, zumal man die Mglichkeit hat, solche Dateien jederzeit mit Texteditoren zu betrachten oder anderweitig zu manipulieren. Andererseits knnen XMLDaten, wie Sie gesehen haben, sehr leicht verarbeitet werden.

Serialisierung von einfachen Objekten


Unter .NET gibt es nun die Mglichkeit, die Serialisierung von Objekten der Laufzeitschicht zu berlassen, da diese Funktionalitt in dieser schon implementiert ist. using System; using System.Xml; using System.Xml.Serialization; public class Person { public string Vorname;
CD-Beispiel XmlObjSer1

C#
176

5
public string Nachname; public string Anrede; public string eMail; } public class App { public static void Main() { Person p = new Person(); p.Vorname = "Donald"; p.Nachname = "Duck"; p.Anrede = "Herr $"; p.eMail = "donald.duck@entenhausen.com"; XmlSerializer Sr = new XmlSerializer(typeof(Person)); string Filename = @"c:\PersonSer.xml"; XmlTextWriter tw = new XmlTextWriter(Filename,null); tw.Formatting = Formatting.Indented; Sr.Serialize(tw,p); tw.Flush(); tw.Close(); //Demonstration des Lesens XmlTextReader tr = new XmlTextReader(Filename); Person newp = (Person)Sr.Deserialize(tr); Console.WriteLine(newp.Vorname); } } Zuerst wird hier ein Objekt vom Typ Person angelegt und mit Werten belegt. Dann wird ein Objekt Sr der Klasse XmlSerializer fr den Typ Person angelegt. Die Klasse XmlSerializer befindet sich im Namensraum System.Xml.Serializer. Mit der Methode Serialize der Klasse XmlSerializer kann nun das Objekt in einem XML-Format gespeichert werden. Fr die Durchfh-

XML-Klassen
5
rung bentigt die Klasse XmlSerializer ein XmlTextWriter-Objekt, das hierzu zuerst angelegt wird. Das Ergebnis ist eine XML-Datei mit folgender Darstellung: <?xml version="1.0"?> <Person xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Vorname>Donald</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr $</Anrede> <eMail>donald.duck@entenhausen.com</eMail> </Person> Sie sehen also, die Implementierung verwendet den Klassennamen und die Member-Namen um Tags zu erzeugen (das geschieht ber den Reflection-Mechanismus). Beachten Sie, dass nur public vereinbarte Elemente ber diesen Mechanismus serialisiert werden knnen. Wenn Sie ein Objekt aus den Daten einer XML-Datei wieder rekonstruieren wollten, dann knnte das folgendermaen geschehen: XmlSerializer Sr = new XmlSerializer(typeof(Person)); XmlTextReader tr = new XmlTextReader(Filename); Person newp = (Person)Sr.Deserialize(tr);

177

Serialisieren von komplexeren Datentypen


Im ersten Moment sieht alles sehr einfach aus, aber die Voraussetzungen sind ebenfalls sehr einfach. Jetzt wird das Ganze ein wenig komplizierter. Wie sieht es aus, wenn die Klasse eine Basisklasse hat bzw. eingebettete Strukturen oder Klassen besitzt? public class Person { public string Vorname; public string Nachname; public string Anrede; public string eMail;
CD-Beispiel XmlObjSer2

C#
178

5
} public class Adresse { public string PLZ; public string Strasse; public string Ort; } public class PersonEx:Person { public DateTime Geburtstag; public Adresse Addr = new Adresse(); } Hier wurde eine neue Klasse PersonEx abgeleitet von Person erzeugt, die ihrerseits um ein Datum und Adresse erweitert wurde. public class App { public static void Main() { PersonEx p = new PersonEx(); p.Vorname = "Donald"; p.Nachname = "Duck"; p.Anrede = "Herr $"; p.eMail = "donald.duck@entenhausen.com"; p.Geburtstag = new DateTime(1927,12,1);

XmlSerializer Sr = new XmlSerializer(typeof(PersonEx)); string Filename = @"c:\PersonSer.xml"; XmlTextWriter tw = new XmlTextWriter(Filename,null); tw.Formatting = Formatting.Indented; Sr.Serialize(tw,p);

tw.Flush();

XML-Klassen
5
tw.Close(); //Deserialisation XmlTextReader tr =new XmlTextReader(Filename); PersonEx pex = (PersonEx) Sr.Deserialize(tr); } } Hier wurde folgende XML-Datei erzeugt: <?xml version="1.0"?> <PersonEx xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Vorname>Donald</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr $</Anrede> <eMail>donald.duck@entenhausen.com</eMail> <Geburtstag> 1927-12-01T00:00:00.0000000+01:00 </Geburtstag> <Addr> <PLZ>1234</PLZ> <Strasse>Dorfstrasse</Strasse> <Ort>Entenhausen</Ort> </Addr> </PersonEx> Ebenfalls problemlos funktioniert das Deserialisieren. XmlTextReader tr = new XmlTextReader(Filename); PersonEx pex = (PersonEx)Sr.Deserialize(tr);

179

Serialisieren von Listen


Toll, nicht wahr? Funktioniert das Ganze auch fr Listen? Versuchen Sie es: Im Folgenden ist nur die Main()-Methode dargestellt.

C#
180

5
public static void Main() { PersonEx p1 = new PersonEx(); p1.Vorname = "Donald"; p1.Nachname = "Duck"; p1.Anrede = "Herr"; p1.eMail = "donald.duck@entenhausen.com"; p1.Geburtstag = new DateTime(1927,12,1); p1.Addr.Ort = "Entenhausen"; p1.Addr.PLZ = "1234"; p1.Addr.Strasse = "Dorfstrasse"; //Ein zweites Objekt anlegen PersonEx p2 = new PersonEx(); p2.Vorname = "Donald"; p2.Nachname = "DAgobert"; ...... //ein Feld anlegen PersonEx [] pl = new PersonEx[2]{p1,p2}; XmlSerializer Sr = new XmlSerializer(typeof(PersonEx [])); string Filename = @"c:\PersonSer.xml"; XmlTextWriter tw = new XmlTextWriter(Filename,null); tw.Formatting = Formatting.Indented; Sr.Serialize(tw,pl); tw.Flush(); tw.Close(); } Zwei Objekte p1 und p2 vom Typ PersonEx werden in einem Feld pl abgespeichert. Beim Erzeugen von XmlSerializer muss der Array-Typ mitgegeben werden. XmlSerializer Sr = new XmlSerializer(typeof(PersonEx [])); Es entsteht damit folgende XML-Datei:

XML-Klassen
5
<?xml version="1.0"?> <ArrayOfPersonEx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <PersonEx> <Vorname>Donald</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr</Anrede> <eMail>donald.duck@entenhausen.com</eMail> <Geburtstag> 1927-12-01T00:00:00.0000000+01:00 </Geburtstag> <Addr> <PLZ>1234</PLZ> <Strasse>Dorfstrasse</Strasse> <Ort>Entenhausen</Ort> </Addr> </PersonEx> <PersonEx> <Vorname>Donald</Vorname> <Nachname>Dagobert</Nachname> <Anrede>Herr $</Anrede> <eMail>donald.duck@entenhausen.com</eMail> <Geburtstag> 1927-12-01T00:00:00.0000000+01:00 </Geburtstag> <Addr> <PLZ>1234</PLZ> <Strasse>Dorfstrasse</Strasse> <Ort>Entenhausen</Ort> </Addr> </PersonEx> </ArrayOfPersonEx> Fr das Feld wird ein eigenes Tag ArrayOfPersonEx erzeugt.

181

Namensgebung der Attribute


.NET bietet aber auch die Mglichkeit, per Attribute in die Namensgebung der Tags einzugreifen oder aber auch festzulegen, dass eine Variable nicht als eigenes Element, sondern als

C#
182

5
XML-Attribut wirksam wird. Auch Namensrume knnen Sie, wenn gewnscht, mit aufnehmen. [XmlRootAttribute("person")] public class Person { [XmlElementAttribute("FirstName")] public string Vorname; [XmlElementAttribute("LastName")] public string Nachname; [XmlAttributeAttribute("Title")] public string Anrede; public string eMail; } Nun wird das Objekt wie folgt abgespeichert: <?xml version="1.0"?> <person xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Title="Herr $"> <FirstName>Donald</FirstName> <LastName>Duck</LastName> <eMail>donald.duck@entenhausen.com</eMail> </person> Sie sehen, dass nun die entsprechenden Tags verwendet werden, die bei .NET-Attributen angegeben wurden. Auerdem sehen Sie hier auch, dass das Member Anrede nun als Attribut des Tags Person vorkommt. Diese Vorgehensweise ist natrlich nicht mglich, wenn Sie eine Klasse verwenden, deren Quellcode Sie nicht kennen bzw. die Klasse in einem Assembly zur Verfgung haben. .NET bietet auch hier Mglichkeiten an. Diese werden im Rahmen dieses Buches nicht behandelt und es wird auf die MSDN verwiesen.

Zusammenfassung
XML ist ein zentraler Bestandteil der .NET-Laufzeitumgebung. Dem Entwickler werden eine Reihe von Klassen bereitgestellt, die die Erzeugung und Manipulation mit XML-Daten und -Dateien vereinfachen.

XML-Klassen
5
Die .NET-Laufzeitumgebung selbst verwendet intern intensiv XML zur Implementierung unterschiedlichster Features. Im Rahmen dieses Buches werden Sie noch mehrfach Kontakt mit XML-Datenstrukturen haben.

183

Windows-Applikationen

Einfhrung Grundlegende Architektur eines Win32-Windowsprogramms .NET-Programmiermodell Windows-Steuerelemente Designer (Entwurfsansicht) Men Werkzeugleiste Dialoge Kundenspezifische Steuerelemente GDI+ Ressourcen Untersttzung der Kulturen

186 188 190 200 209 219 222 225 230 236 248 257

C#
186

Einfhrung
Bill Gates startete seine beispiellose Karriere Anfang der 80er mit dem Konsolen orientierten Betriebssystem MS DOS. Parallel dazu wurde von der Firma Apple ein Betriebssystem auf den Markt gebracht, das eine grafische Oberflche besa. Mitte der 80er Jahre startete auch Bill Gates mit Windows 1.0 den Einstieg in eine grafische Oberflche, die aber bei Weitem nicht an Apples Realisierung herangekommen ist. Es ist sicherlich auf die unglckliche Marktpolitik von Apple und der genialen Marketingstrategie Bill Gate's zurckzufhren, dass heute Windows das beherrschende Betriebssystem auf Personalcomputern ist. Aber was soll's. Wenn Sie als Softwareentwickler Ihr Brot verdienen mssen, dann kommen Sie nicht umhin, Ihre Software auch fr das Betriebssystem Windows anzupassen. Die meisten Betriebssysteme bieten den Programmierern eine Schnittstelle zum Betriebssystem in Form von C-Funktionen. Diese werden API-Funktionen (Application Programming Interface) genannt und erlauben Anwendungen zu schreiben, die auf diesem Betriebssystem laufen. Im Allgemeinen unterscheiden sich diese Funktionen bei unterschiedlichen Betriebssystemen, sodass ein Quellcode auf Basis der Win32-API nicht direkt auf einem anderen Betriebssystem kompiliert werden kann. Eine weitere Tatsache ist, dass die Win32-API nicht objektorientiert ist und daher wird bei komplexeren Anforderungen die Programmierung auf dieser API ziemlich unbersichtlich. Mit ANSI-C wurde eine Standard-Bibliothek definiert, die die unterschiedlichen Betriebssysteme abstrahieren sollte. Zwischen dem Anwendungsprogrammierer und den API-Funktionen wurde eine Schicht eingeschoben. Die Bibliothek ist natrlich plattformabhngig, aber es ist damit mglich, Programme zu schreiben, die wenigstens auf Quellcodebasis zwischen den Plattformen portabel sind. Mit Entwicklung von C++ hat sich auch diese Bibliothek weiter entwickelt, sie wurde objektorientiert und Portabilitt war im Wesentlichen gewhrleistet, solange der Programmierer nicht auf spezifische, plattformabhngige API-Funktionen zurckgriff.

Windows-Applikationen
6
Es ist interessant, dass sich fr die grafischen Betriebssysteme keine standardisierte Bibliothek herauskristallisierte, die es ermglichen wrde, auf Quellcodebasis Programme zu schreiben, die auf die unterschiedlichen Plattformen einfach zu portieren wren. Es gab sehr wohl Anstze dazu (beispielsweise wxWindows), aber so richtig durchstarten konnte keine dieser. Das Windows-Programmiermodell ist durch seine Historie und den immer neu hinzugekommenen Features ziemlich komplex geworden und in vielen Bereichen nicht mehr ganz schlssig. Mit .NET fhrt Microsoft auch ein vllig neues Programmiermodell fr die Windows-Programmierung ein. Mit einer Reihe von .NET-Klassen versucht nun Microsoft eine vom Betriebssystem unabhngige, objektorientierte Bibliothek auch fr grafische Oberflchen anzubieten. Vor allem im Namensraum System.Windows existieren eine Menge von Fensterklassen und erlauben so ein einfacheres Programmieren als das rohe Win32-Programmieren. Mit MFC (Microsoft Foundation Class) und ATL (Active Template Libraray) hat Microsoft zwar ebenfalls eine umfangreiche Bibliothek angeboten, die das Programmieren doch sehr erleichtert hat, aber die Bibliotheken von .NET gehen einen Schritt weiter. Die konsequente Anwendung dieser .NET-Klassen sollte theoretisch portable Programme hervorbringen, die auf unterschiedlichen Plattformen lauffhig sein sollten (sollte .NET auch auf andere Plattformen portiert werden). In diesem Kapitel werden Sie wieder anhand von Beispielen durch die verschiedensten Aspekte der Windows-Programmierung begleitet. Es ist klar, dass nicht alle Aspekte bis in die Tiefe behandelt werden knnen. Dies wrde den Rahmen des Buches bei Weitem sprengen. Sie eignen sich in diesem Kapitel das notwendige Wissen an, das Sie bentigen, um autodidaktisch vor allem mithilfe der MSDN anfallende Anforderungen und Probleme zu lsen. Hier wieder der Ratschlag: Experimentieren Sie mit den Beispielen und nehmen Sie auch die unerschpfliche Quelle MSDN in Anspruch!

187

C#
188

Grundlegende Architektur eines Win32Windowsprogramms


Bevor das .NET Programmiermodell betrachtet wird, wird fr diejenigen, die noch nie ein Windowsprogramm auf API-Basis erstellt haben, in Form eines relativ einfachen C-Beispiels ein Eindruck eines typischen Windowsprogramms vermittelt. Diejenigen, die diesbezglich Erfahrungen haben, knnen diesen Abschnitt einfach berspringen. Es hat sich gezeigt, dass grundlegende Kenntnisse der Win32Architektur das Verstndnis auch fr das .NET-Programmiermodell frdern und somit auch die Lernkurve verkrzen. Jeder Prozess unter den Win32-Betriebssystemen besitzt u.a. einen oder mehrere Threads. Threads, zu deutsch Ablaufpfade sind die Arbeitspferde, die Code durchfhren. Alle laufenden Threads eines Systems bekommen vom Betriebssystem eine bestimmte Zeit zur Ausfhrung des Codes (Thread Scheduling). Unter Win32 kann (muss nicht) jeder Thread eine so genannte Nachrichtenschleife besitzen. Es ist dies ein FIFO (First In-First Out), das vom Betriebssystem mit Nachrichten gefllt wird. Nachrichten sind z.B. Maus bewegen (WM_MOUSEMOVE), Linke Maustaste nach unten drcken (WM_LBUTTONDOWN), Tastaturtaste drcken (WM_KEYDOWN) usw. Nachrichten werden ber das Betriebssystem in die jeweilige Nachrichtenschlange des Threads gebracht. Auch Steuerelemente eines Fensters (Button, Editbox etc.) oder Men und Toolbars haben zur Folge, dass in die Nachrichtenschlagen des Threads Nachrichten gebracht werden, die es dann gilt, abzuarbeiten. Nachrichten knnen auch programmtechnisch durch die API-Funktion PostMessage(...) in die Nachrichtenschlange gebracht werden. Dies ist brigens eine oft verwendete Methode der Interprozesskommunikation auf Win32-Betriebssystemen. Ein Windowsprogrammierer hat nun sicherzustellen, dass die Nachrichten aus der Nachrichtenschlange abgeholt werden und wird dann auf die unterschiedlichen Nachrichten entsprechend der gewnschten Funktionalitt reagieren. Das Herz ei-

Windows-Applikationen
6
nes jeden Windowsprogramms ist daher folgende (Endlos)Schleife (diese befindet sich im Normalfall in der Hauptfunktion). while (GetMessage (&msg, NULL, 0, 0)) { DispatchMessage (&msg) ; } Die API-Funktion GetMessage(...) holt sich die nchste Nachricht in Form einer Struktur (msg) aus der Nachrichtenschlange und gibt diese dann der API-Funktion DispatchMessage(...) weiter. Wenn in der Nachrichtenschlange die Nachricht WM_QUIT liegt, dann gibt die Funktion GetMessage(...) den Wert 0 zurck und die Schleife wird abgebrochen. ber die Funktion DispatchMessage(...) wird dann im Allgemeinen eine entsprechende Prozedurfunktion aufgerufen, die z.B. folgendes Aussehen haben kann. LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (iMsg) { case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; DrawText (hdc,"Hello, Windows 95!",-1, rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_LBUTTONDOWN: Beep(1000,100); Return 0; case WM_KEYDOWN: //programmieren case WM_DESTROY : PostQuitMessage (0) ;

189

C#
190

6
return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; } Sie sehen, in dieser befindet sich im einfachsten Fall eine switch-case-Struktur, in der auf die unterschiedlichen Nachrichten mit Code reagiert werden kann. Im Beispielcode reagiert das Programm z.B. auf die Nachrichten WM_PAINT, WM_LBUTTONDOWN, WM_KEYDOWN und WM_DESTROY. Alle anderen Nachrichten werden der Funktion DefWindowProc weitergeleitet. Windowsprogrammieren heit also auf Nachrichten reagieren. Wie schon erwhnt, knnen Nachrichten auch von Steuerelementen, Mens, Werkzeugleisten etc. kommen. Konzentrieren Sie sich aber nun auf das .NET-Programmiermodell.

.NET-Programmiermodell
Mit .NET fhrt Microsoft auch ein vllig neues Programmiermodell fr die Programmierung von grafischen Oberflchen ein. Mit einer Reihe von .NET Klassen versucht Microsoft eine vom Betriebssystem unabhngige, objektorientierte Bibliothek auch fr grafische Oberflchen anzubieten (siehe einleitende Diskussion im vorigen Kapitel). Das Programmiermodell ist objektorientiert und prinzipiell portabel. Vor allem die Klassen des Namensraums System.Windows untersttzen eine Unzahl von Fensterklassen und erlauben ein einfacheres Programmieren als das rohe Win32-Programmieren. Die konsequente Anwendung dieser .NET Klassen sollte wenigstens theoretisch portable Programme hervorbringen, die auf unterschiedlichen CLRs (Common Language Runtime) lauffhig sein sollten. Am besten lernen Sie dieses Programmiermodell anhand eines kleinen Beispiels kennen. Aus didaktischen Grnden werden Sie das erste Beispiel ohne das Entwicklungssystem erzeugen. Legen Sie in einem eigenen Ordner SimpleWindow mit einem beliebigen Editor (z.B. Nodepad) eine Datei mit dem Namen SimpleWindow.cs und geben Sie folgenden C# -Quellcode ein:

Windows-Applikationen
6
using System; using System.Windows.Forms; public class App { static void Main() { Form win = new Form(); win.Text = "Klitzeklein"; Application.Run(win); } } Aus der Konsole geben Sie nun folgenden Befehl ein: csc /target:winexe /out:Winapp.exe /r:System.dll /r:System.Windows.Forms.dll SimpleWindow.cs Beachten Sie, dass Sie die .NET-Konsole verwenden! Sie finden diese unter Start > Programme > Microsoft Visual Studio.NET > Visual Studio .NET Tools > Visual Studio .NET Command Prompt. Wenn Sie keinen Tippfehler gemacht haben, dann wurde im Ordner eine ausfhrbare Datei Winapp.exe erzeugt. Wenn Sie diese Datei ausfhren, dann erscheint folgendes Fenster.
CD-Beispiel SimpleWindow

191

Minimale Windowsapplikation Abb. 6.1

Auch eine C#-Windows-Anwendung beginnt bei der Einsprungmethode Main() einer beliebigen Klasse. Im Beispiel wird erst ein Objekt vom Typ Form angelegt. Form ist eine

C#
192

6
Klasse im Namensraum System.Windows.Forms und implementiert ein typisches Fenster eines grafischen User Interface. Diese Fensterklasse besitzt natrlich eine Menge von MemberVariablen, im Beispiel wird das Member Text belegt. Der Eintrag erscheint im Fenster in der Kopfleiste. Die statische Methode Run der Klasse Application (ebenfalls im Namensraum System.Windows.Forms definiert), richtet nun eine Nachrichtenschleife im Thread ein und ffnet (optional) ein Fenster. Dieses Fenster wird der Methode Run als Parameter mitgegeben. Sehen Sie sich die Kommandozeile an: csc /target:winexe /out:Winapp.exe /r:System.dll /r:System.Windows.Forms.dll SimpleWindow.cs Neu ist die Angabe winexe statt exe bei der Option /target. Diese ist notwendig, wenn Sie ein Programm mit Fenster erzeugen wollen. In den Assemblies System.dll und System.Windows.Forms.dll befinden sich die in diesem kleinen Beispiel verwendeten Klassen und mssen daher angegeben werden. Erzeugen Sie nun dieses kleine Projekt mithilfe des Entwicklungssystems Visual Studio.NET. Fr die Experimente in diesem Kapitel legen Sie am besten eine neue Projektmappe mit dem Namen Winapp an. Das Entwicklungssystem bietet Ihnen eine Vorlage WindowsAnwendungen an. Bei den ersten Beispielen soll aber auf diese Vorlage noch verzichtet werden, und Sie erzeugen daher ein Leeres Projekt mit dem Namen SimpleWindow. Zuerst mssen Sie die Projekteigenschaften so ndern, dass auch eine Windowsapplikation erzeugt wird. Markieren Sie dazu das Projekt im Projektmappen-Explorer, ffnen Sie per Kontextmen Eigenschaften den Eigenschaftsordner des Projektes und ndern Sie den Ausgabetyp des Projektes von Konsole-Anwendung auf Windows-Anwendung. Damit weisen Sie den Kompiler an, die Applikation mit der Option /t:winexe zu kompilieren. Der Applikation fgen Sie nun eine C#-Codedatei mit dem Namen App.cs hinzu (Men Projekte > Neues Element hinzufgen... ) und geben den Code des Beispiels FirstWindow ein.

Windows-Applikationen
6
193

Leeres Projekt anlegen Abb. 6.2

Projekteigenschaft Windows-Anwendung Abb. 6.3

using System; using System.Windows.Forms; public class App { static void Main()

CD-Beispiel SimpleWindow

C#
194

6
{ Form win = new Form(); win.Text = "Klitzeklein"; Application.Run(win); } } Damit auch die notwendigen Assemblies vom Kompiler hinzugefgt werden, richten Sie noch Verweise auf System.dll und Sytem.Windows.Forms.dll ein (Kontextmen Verweise > Verweise hinzufgen... im Projektmappen-Explorer).

Die Klasse Form


Die Klasse Form bietet schon smtliche Features eines Fensters an. So z.B. die Fhigkeit, die Gre des Fensters zu verndern, das Fenster zu schlieen (und damit auch die Applikation) usw. Mchten Sie nun weitere Features dem Fenster hinzufgen, dann erzeugen Sie am besten eine neue Fensterklasse mit der Klasse Form als Basisklasse und fgen dieser neuen Klasse die entsprechenden Features hinzu. Erweitern Sie dazu das Beispiel:
CD-Beispiel Win1

using System; using System.Drawing; using System.Windows.Forms; public class MainWnd:Form { public MainWnd() { BackColor = Color.FromArgb(0,255,0); //alternative: BackColor = Color.Green; Text = "Einfaches Fenster"; } } class App { public static void Main() {

Windows-Applikationen
6
Application.Run(new MainWnd()); } } Hier haben Sie eine neue Klasse mit dem Namen MainWnd, abgeleitet von Form, erzeugt. Der Konstruktor der Klasse belegt die mitgeerbten Member-Variablen BackColor und Text spezifisch. Beachten Sie, dass fr die Farbe ein zustzlicher Namensraum (System.Drawing) geffnet werden muss, und dass Sie einen zustzlichen Verweis auf das Assembly System.Drawing.dll einrichten mssen! Die Methode Run der Klasse Application in Main() verwendet nun eine vom Typ MainWnd. Ein wichtiger Hinweis muss an dieser Stelle erfolgen: Werfen Sie einen Blick auf den Projektmappen-Explorer.

195

Blick auf den Projektmappen-Explorer Abb. 6.4

Sie sehen nun, dass im Projektmappen-Explorer die Datei App.cs mit einem neuen Icon versehen wurde, das ein Fenster andeuten soll. Ein Doppelklick auf App.cs im ProjektmappenExplorer ffnet Ihnen nun nicht mehr den Quellcode, sondern Sie kommen in die Entwurfsansicht des Fensters (Designer). Den Designer werden Sie spter noch kennen lernen. Die Quellcode-Darstellung erreichen Sie, indem Sie bei App.cs per Kontextmen Code anzeigen whlen. Eine weitere Mglichkeit, zwischen der Entwurfsansicht und der Quellcode-Ansicht umzuschalten, gibt es in der Werkzeugleiste des ProjektmappenExplorers. In der Abbildung 6.4 sind die Icons entsprechend markiert.

C#
196

6
Farbendarstellung
In der WIN32-Programmierung werden Farben als 32 Bit-Werte dargestellt. Aber nur die drei niederwertigen Bytes werden verwendet. Jedes Byte entspricht einer der Grundfarben Rot, Grn und Blau (RGB-Darstellung). .NET bietet im Assembly System.Drawing.dll eine Struktur mit dem Namen Color an. Diese Struktur hat eine statische Methode FromArgb, die es erlaubt, die Grundfarbenanteile anzugeben und damit eine Farbe zu erzeugen. BackColor = Color.FromArgb(0,255,0); Die Struktur definiert auch eine groe Zahl von Properties fr die wichtigsten Farben (z.B. Color.Green). Damit htte die Zeile BackColor = Color.Green; denselben Effekt.

Auf Fensterereignisse reagieren


In der Klasse Forms sind einige Events definiert. Im Wesentlichen sind dies smtliche Windowsnachrichten, die auf Fenster definiert sind. So auch das Event Click, das auftritt, wenn im Fenster eine Maustaste gedrckt wird. Wie Sie Eventhandler realisieren, haben Sie im Kapitel 2. C# die neue Programmiersprache kennen gelernt. Im folgenden Beispiel reagieren Sie in der Methode OnClick auf das entsprechende Event. Im Codebeispiel wird bei Auftreten des Events Click die Hintergrundfarbe umgeschaltet.
CD-Beispiel Win2

using System; using System.Drawing; using System.Windows.Forms; public class MainWnd:Form { public MainWnd() { this.BackColor = Color.Blue; this.Text = "SimpleWindow"; //Event anmelden this.Click += new EventHandler(OnClick);

Windows-Applikationen
6
} //Eventhandler implementieren protected void OnClick(object o, EventArgs e) { if(BackColor == Color.Blue) BackColor = Color.Red; else BackColor = Color.Blue; } } class App { public static void Main() { Application.Run(new MainWnd()); } } Die Klasse Form implementiert noch eine Reihe weiterer Ereignisse. Es gengt vorerst, wenn Sie den Mechanismus verstanden haben. Weitere Ereignisse werden Sie im Rahmen dieses Kapitels noch zu Genge kennen lernen. Wenn Sie bezglich des .NET-Event-Mechanismusses (Delegates) noch unsicher sind, dann sollten Sie die entsprechenden Abschnitte im Kapitel 2: C# die neue Programmiersprache auffrischen. Sie tun sich nachfolgend um einiges leichter.

197

Steuerelement im Fenster
Ein typisches Fenster unter Windows besitzt auch Steuerelemente. Im nchsten Beispiel werden Sie nun die prinzipielle Vorgehensweise beim Einbinden von Steuerelementen kennen lernen. Die Hintergrundfarbe des Fensters soll ber einen Button umgeschaltet werden knnen. using System; using System.Drawing; using System.Windows.Forms; public class MainWnd:Form { Button ButYellow; public MainWnd()
CD-Beispiel Win3

C#
198

6
{ BackColor = Color.Blue; Text = "SimpleWindow"; Click += new EventHandler(OnClick); ButYellow = new Button(); ButYellow.Location = new Point(0,0); ButYellow.Size = new Size(100,50); ButYellow.Text = "Farbe gelb"; ButYellow.BackColor = Color.Yellow; ButYellow.Click += new EventHandler(OnButYellow); Controls.Add(ButYellow); } protected void OnClick(object o,EventArgs e) { if(BackColor == Color.Blue) BackColor = Color.Red; else BackColor = Color.Blue; } protected void OnButYellow(object o,EventArgs e) { BackColor = Color.Yellow; } } class App { public static void Main() { Application.Run(new MainWnd()); } } Der Namensraum System.Windows.Forms bietet eine Menge Steuerelemente an, unter anderem auch das Steuerelement Button. Wie Sie aus dem Code ersehen, wurde der Klasse MainWnd eine zustzliche Member-Variable ButYellow vom Typ Button hinzugefgt. Smtliche Steuerelemente sind von System.ComponentModel.Component abgeleitet.

Windows-Applikationen
6
Im Konstruktor der Klasse MainWnd wird dieser Button erzeugt und mit Eigenschaften versehen. ButYellow = new Button(); ButYellow.Location = new Point(0,0); ButYellow.Size = new Size(100,50); ButYellow.Text = "Farbe gelb"; ButYellow.BackColor = Color.Yellow; ButYellow.Click += new EventHandler(OnButYellow); Die Eigenschaften Location und Size definieren die Position des Buttons, die anderen Eigenschaften sind selbsterklrend. Der Button implementiert auch ein Event Click. Diesem Event wird im Beispiel die Methode OnButYellow zugeordnet. Besonders wichtig ist, dass nun smtliche Steuerelemente, die im Fenster sichtbar sein sollten, dem dynamischen Feld Controls (geerbt von der Basisklasse Form) hinzugefgt werden. Controls.Add(ButYellow); Die Implementierung der Methode OnButYellow ist trivial.

199

Koordinatensystem
Im Beispiel wurde der Button innerhalb des Koordinatensystems des Fensters (Form) positioniert. Das Default-Koordinatensystem basiert auf der Einheit Pixel. Die Richtungen der Hauptachsen sehen Sie in der folgenden Abbildung. Ein Punkt im Koordinatensystem wird durch die Struktur Point des Namensraumes System.Drawing reprsentiert. Size, ebenfalls eine Struktur im Namensraum System.Drawing definiert eine Grenangabe eines Rechteckes (Width, Height). Sie werden im Rahmen diese Kapitels spter noch ausfhrlicher ber Koordinatensysteme und Klassen hren.

C#
200

Hauptachsen des Koordinatensystems Abb. 6.5

Zusammenfassung
In diesem Abschnitt haben Sie die grundlegenden Zusammenhnge des .NET-Windows-Programmiermodells kennen gelernt. Die Methode Run der Klasse Application startet eine Nachrichtenschlange und erzeugt das Hauptfenster ber eine Instanz der Klasse Form. Im Allgemeinen wird eine spezielle Fensterklasse auf Basis der Klasse Form erzeugt. Auf smtliche Eigenschaften und Fensternachrichten kann ber Member-Variablen und Events der Klasse Form zugegriffen werden. In ein Fenster knnen Steuerelemente platziert werden. Dies geschieht, indem Instanzen der Steuerelemente erzeugt werden, deren Eigenschaften belegt und dem dynamischen Feld Controls der Klasse Form zugewiesen werden. Steuerelemente implementieren ihrerseits Events, auf die programmtechnisch reagiert werden kann. In den nchsten Kapiteln werden Sie tiefer in die Materie einsteigen.

Windows-Steuerelemente
In diesem Kapitel werden Sie an einem Beispiel die Handhabung einiger wichtiger Steuerelemente kennen lernen. Die grundstzliche Vorgehensweise der Einbettung von Steuerelementen in eine Form haben Sie schon kennen gelernt.

Windows-Applikationen
6 TextBox, TrackBar, RadioButton, Label
201

Beispielapplikation Steuerelemente Abb. 6.6

Dieses kleine Beispielprogramm ermglicht dem Anwender die Hintergrundfarbe des Fenster auf verschiedene Arten zu setzen. Einmal in Form von TrackBars (Schieberegler). Jeder der Grundfarben Rot, Grn und Blau wird eine TrackBar zugeordnet. ber diese knnen dann die Intensittsanteile der Grundfarben zwischen 0 und 255 eingestellt werden. Die Hintergrundfarbe soll sich dann entsprechend ndern. Weiter ist jeder Grundfarbe eine TextBox zugeordnet, die die Werte auch digital darstellt. nderungen sind auch ber die TextBoxen mglich, was zur Folge hat, dass auch die Schieberegler der TrackBars die entsprechende Stellung einnehmen. ber eine Optionsgruppe knnen auch vordefinierte Farben direkt aktiviert werden. TextBoxen und TrackBars werden dann natrlich ebenfalls entsprechende Werte annehmen. Erzeugen Sie in der Projektmappe ein neues Leeres Projekt mit dem Namen Steuerelement. Richten Sie Verweise auf die notwendigen Assemblies System.dll, System.Drawing.dll und System.Windows.Forms.dll ein. Im Eigenschaftsdialog des Projektes schalten Sie dann den Ausgabetyp auf Windows-Anwendung um. In einer ersten Iteration werden die TrackBars mit den entsprechenden Labels realisiert.

C#
202

6
CD-Beispiel Steuerelemente1

using System; using System.Drawing; using System.Windows.Forms; public { Label Label Label class MainWnd:Form lRed; lGreen; lBlue;

TrackBar tbarRed; TrackBar tbarGreen; TrackBar tbarBlue; public MainWnd() { . . . . . . } } class App { public static void Main() { Application.Run(new MainWnd()); } } Die Fensterklasse MainWnd versehen Sie mit drei Labels (lRed, lGreen und lBlue) sowie drei TrackBars (tbarRed, tbarGreen und tbarBlue). Die Gre des Fensters sowie die Fensterberschrift werden im Konstruktor der Klasse MainWnd zuerst belegt. this.Text = "Farben"; this.ClientSize = new Size(400,300); Belegen Sie dann die Label-Steuerelemente. lRed = new Label(); lRed.Text = "Rotanteil"; lRed.Location = new Point(0,0); lRed.Size = new Size(80,30);

Windows-Applikationen
6
Das Label lRed bekommt den Text Rotanteil zugeordnet und wird an der Position (0,0) und mit einer Gre von 80 Pixel Breite und 30 Pixel Hhe ausgestattet. Bei den Labels lGreen und lBlue mssen Sie beachten, dass Sie die Koordinaten auch so whlen, dass diese Labels auch entsprechend Abbildung 6.6 positioniert werden. lGreen = new Label(); lGreen.Text = "Grnanteil"; lGreen.Location = new Point(0,40); lGreen.Size = new Size(80,30); lBlue = new Label(); lBlue.Text = "Blauanteil"; lBlue.Location = new Point(0,90); lBlue.Size = new Size(80,30); Dann initialisieren Sie die TrackBars. Der folgende Codeausschnitt zeigt, wie dies geschieht. tbarRed = new TrackBar(); tbarRed.Location = new Point(90,0); tbarRed.Size= new Size(200,30); tbarRed.Minimum = 0; tbarRed.Maximum = 255; tbarRed.TickFrequency = 25; tbarRed.Value = 128; tbarRed.ValueChanged += new EventHandler(OnSliderMoved); Nach der Positionierung der TrackBar wird der einstellbare Wertebereich ber die Members Minimum und Maximum eingestellt (im Beispiel zwischen 0 und 255). ber TickFrequency kann die Schrittteilung der TrackBar eingestellt werden. ber das Member Value knnen Sie jederzeit den Wert des Schiebereglers auslesen, und auch setzen. Im Beispiel wird diese auf den mittleren Wert 128 gesetzt. Wenn sich der Wert der TrackBar ndert, soll das Programm reagieren. Daher wird auf das Event ValueChanged der Klasse TrackBar eine Bearbeitungsfunktion zugeordnet (OnSliderMoved).

203

C#
204

6
In gleicher Weise initialisieren Sie die TrackBars tbarGreen und tbarBlue. tbarGreen = new TrackBar(); tbarGreen.Location = new Point(90,40); tbarGreen.Size= new Size(200,10); tbarGreen.Minimum = 0; tbarGreen.Maximum = 255; tbarGreen.TickFrequency = 25; tbarGreen.Value = 128; tbarGreen.ValueChanged += new EventHandler(OnSliderMoved);

tbarBlue = new TrackBar(); tbarBlue.Location = new Point(90,80); tbarBlue.Size= new Size(200,10); tbarBlue.Minimum = 0; tbarBlue.Maximum = 255; tbarBlue.TickFrequency = 25; tbarBlue.Value = 128; tbarBlue.ValueChanged += new EventHandler(OnSliderMoved); Vergessen Sie dann nicht, smtliche Steuerelemente in die Liste Controls mit aufzunehmen! Controls.Add(tbarRed); Controls.Add(tbarGreen); Controls.Add(tbarBlue); Controls.Add(lRed); Controls.Add(lGreen); Controls.Add(lBlue); Sie sehen, allen TrackBars wird dieselbe Bearbeitungsfunktion OnSliderMoved zugeordnet! Die Implementierung geschieht in Form einer Methode der Klasse MainWnd. protected void OnSliderMoved( object sender, EventArgs arg) {

Windows-Applikationen
6
this.BackColor = Color.FromArgb( tbarRed.Value,tbarGreen.Value,tbarBlue.Value); } In dieser Methode wird die Hintergrundfarbe des Fensters MainWnd mit der Farbe belegt, die sich aus den aktuellen Werten der drei TrackBars ergeben. Wie schon erwhnt, knnen diese Werte ber das Member Value der Klasse TrackBar ausgelesen werden. Kompilieren Sie das Projekt und dann knnen Sie schon die Hintergrundfarbe des Fensters ber die TrackBars einstellen. Nun soll das Projekt um die TextBoxen, die den digitalen Wert der Intensittswerte der Grundfarben anzeigen, erweitert werden. Fgen Sie der Klasse MainWnd zustzlich drei Texboxen hinzu: TextBox tbRed; TextBox tbGreen; TextBox tbBlue; Diese werden im Konstruktor entsprechend initialisiert. tbRed = new TextBox(); tbRed.Location = new Point(300,0); tbRed.Size = new Size(50,30); tbRed.TextChanged += new EventHandler(OnTBChanged); Eine Textnderung in der TextBox wird in der Bearbeitungsfunktion OnTBChanged verarbeitet. In hnlicher Weise werden auch die TextBoxen tbGreen und tbBlue initialisiert. tbGreen = new TextBox(); tbGreen.Location = new Point(300,40); tbGreen.Size = new Size(50,30); tbGreen.TextChanged += new EventHandler(OnTBChanged); tbBlue = new TextBox(); tbBlue.Location = new Point(300,80); tbBlue.Size = new Size(50,30); tbBlue.TextChanged += new EventHandler(OnTBChanged);
CD-Beispiel Steuerelemente2

205

C#
206

6
Vergessen Sie nicht, die neuen Steuerelemente auch der Liste Controls hinzuzufgen! Controls.Add(tbRed); Controls.Add(tbGreen); Controls.Add(tbBlue); Die Werte der Schiebereglerstellungen der TrackBars sollen in den TextBoxen dargestellt werden. Diese Funktionalitt fgen Sie am besten in der Bearbeitungsfunktion der TrackBars OnSliderMoved ein. protected void OnSliderMoved( object sender,EventArgs arg) { BackColor = Color.FromArgb( tbarRed.Value,tbarGreen.Value,tbarBlue.Value); tbRed.Text = tbarRed.Value.ToString(); tbGreen.Text = tbarGreen.Value.ToString(); tbBlue.Text = tbarBlue.Value.ToString(); } Fr jede TextBox wird die Member-Variable Text mit dem Wert Value der TrackBars belegt. Umgekehrt sollen bei einer nderung in den TextBoxen auch die Schieberegler die entsprechende Position einnehmen. Diese Funktionalitt programmieren Sie in die noch zu erstellende Methode OnTBChanged. protected void OnTBChanged( object sender,EventArgs arg) { try { tbarRed.Value = Int32.Parse(tbRed.Text); tbarGreen.Value = Int32.Parse(tbGreen.Text); tbarBlue.Value = Int32.Parse(tbBlue.Text); } catch(Exception e) { } }

Windows-Applikationen
6
Diese Methode wird aufgerufen, wenn sich der Inhalt einer TextBox ndert. In der Methode werden zuerst die Werte der TextBoxen ausgelesen und dann die TrackBars entsprechend gesetzt. Aber das geht nicht so einfach. TextBoxen halten ihre Werte in Form von Strings. Sie mssen daher zuerst den String in einen Integer-Wert umwandeln. Hierzu verwenden Sie die statische Methode Parse der Struktur Int32. Diese Methode fhrt die Umwandlung eines Ziffernstrings in einen Integerwert durch. (brigens: Smtliche nummerischen Typen besitzen diese angenehme Methode.) Die Eingabe eines Strings, der nicht einem Ziffernstring gehorcht, hat eine Exception zur Folge. Diese fangen Sie am besten ab, damit nicht das Programm wegen einer irrtmlich falschen Angabe terminiert. Zu guter Letzt erweitern Sie das Beispiel noch um eine Optionsgruppe gem Abbildung 6.6. Dazu erweitern Sie die Klasse um folgende Member-Variablen: GroupBox gbColor; RadioButton rbYellow; RadioButton rbGreen; RadioButton rbWhite; Initialisieren Sie im Konstruktor zuerst die RadioButtons. rbYellow = new RadioButton(); rbYellow.Location = new Point(20,130); rbYellow.Size = new Size(100,30); rbYellow.Text = "Gelb"; rbYellow.CheckedChanged += new EventHandler(OnRBChanged); Den RadionButtons wird dem Event CheckedChanged eine Bearbeitungsmethode zugewiesen. In gleicher Weise verfahren Sie nun mit den restlichen RadioButtons. rbGrenn = new RadioButton(); rbGreen.Location = new Point(20,170); rbGreen.Size = new Size(100,30); rbGreen.Text = "Grn";
CD-Beispiel Steuerelemente3

207

C#
208

6
rbGreen.CheckedChanged += new EventHandler(OnRBChanged); rbWhite = new RadioButton(); rbWhite.Location = new Point(20,210); rbWhite.Size = new Size(100,30); rbWhite.Text = "Weiss"; rbWhite.CheckedChanged += new EventHandler(OnRBChanged); RadioButtons werden meistens einer Gruppe (GroupBox) zugeordnet. Damit lsst sich auch nur ein Button aktivieren. Das Zuordnen zu einer Gruppe wird wie folgt durchgefhrt: gbColor =new GroupBox(); gbColor.Location = new Point(10,120); gbColor.Size = new Size(160,170); gbColor.Controls.Add(rbYellow); gbColor.Controls.Add(rbGreen); gbColor.Controls.Add(rbWhite); Nachdem die GroupBox positioniert ist, werden die RadioButtons der Liste Controls des Steuerelementes gbColor zugeordnet. Damit sind diese der GroupBox untergeordnet. Auch hier bitte wieder nicht vergessen, dass smtliche Steuerelemente in die Liste Controls der Klasse MainWnd aufgenommen werden mssen. Die Steuerelemente werden ansonsten nicht dargestellt! Controls.Add(rbYellow); Controls.Add(rbGreen); Controls.Add(rbWhite); Controls.Add(gbColor); Nun fehlt nur noch die Bearbeitungsmethode auf die Aktivierung der RadioButtons. protected void OnRBChanged( object sender,EventArgs arg) { Color col = new Color(); if(sender==rbYellow) col = Color.Yellow; if(sender==rbGreen) col = Color.Green; if(sender==rbWhite) col = Color.White;

Windows-Applikationen
6
209

tbRed.Text = col.R.ToString(); tbGreen.Text = col.G.ToString(); tbBlue.Text = col.B.ToString(); } Allen RadioButtons wurde dieselbe Bearbeitungsmethode zugeordnet. Sie knnen aber feststellen, von welchem RadioButton das Event ausgelst wurde. Entsprechend erzeugen Sie sich dann eine Farbe. Wenn Sie nun aus dieser die Farbanteile extrahieren (ber die Properties R, G und B), knnen Sie die TextBoxen direkt mit den entsprechenden Strings versorgen.

Zusammenfassung
Sie haben in diesem Beispiel nun die Verwendung von einigen Steuerelementen kennen gelernt. Die Vorgehensweise noch einmal zusammengefasst: Fensterklasse erzeugen (Basisklasse Form) Steuerelemente als Member-Variablen der Fensterklasse definieren Im Konstruktor der Fensterklasse diese initialisieren, positionieren und auf die gewnschten Events Behandlungsmethoden schreiben Die Steuerelemente der Liste Controls der Fensterklasse hinzufgen Ihnen ist sicherlich aufgefallen, welcher Aufwand das Positionieren von Steuerelementen ist. Stellen Sie sich nur vor, wenn Sie bei dem eben gezeigten Beispiel smtliche Steuerelemente verschieben mchten. Bei komplexeren Fensteransichten artet das Ganze auf ein lstiges und aufwndiges Pixelrechnen aus. Im nchsten Abschnitt werden Sie nun ein Werkzeug kennen lernen, das den Entwickler in dieser nicht gerade anspruchsvollen Arbeit entlastet.

Designer (Entwurfsansicht)
Hat ein Fenster viele Steuerelemente, dann wird die Verwaltung ziemlich aufwndig, denken Sie nur an die Positionierung

C#
210

6
der Steuerelemente, die in ein dummes Pixelrechnen ausartet. Wer schon Windowsprogramme unter Visual Studio 6.0 programmiert hat, kennt sicherlich den Ressourcen-Editor, der es ermglicht hat, die Steuerelemente grafisch in einem Dialog zu platzieren. Einen gnzlich neuen Weg geht Visual Studio.NET bezglich der grafischen Entwicklung von Layouts. Neu eingefhrt wurde der so genannte Designer. Dieser erlaubt hnlich, wie Sie es vom Ressourcen-Editor her kennen, Fenster grafisch zu entwickeln. Der Designer darf allerdings nicht mit dem Ressourcen-Editor verwechselt werden, da dieser einen gnzlich anderen technischen Hintergrund hat. Sie werden nun die Verwendung des Designers anhand eines Beispiels kennen lernen. Das Beispiel Simple Email aus dem Kapitel 2: C# die neue Programmiersprache soll nun als Windows-Programm ausgefhrt werden. In diesem Zusammenhang werden Sie auch weitere Steuerelemente kennen lernen. Auerdem werden Sie den Projektassistenten fr die Erzeugung eines Applikationsrumpfes verwenden.

Simple Email1-Beispiel Abb. 6.7

Legen Sie dazu ein neues Projekt an. Whlen Sie die Vorlage Windows-Anwendung und geben Sie dem Projekt den Namen Simple Email. Der Assistent erzeugt Ihnen das Projekt, fgt auch die fr ein Windows-Programm notwendigen Assemblies hinzu und bietet Ihnen dann auch gleich die Designer-Ansicht (zu deutsch Entwurfsansicht) an. Im Projektmappen-Explorer sehen Sie nun

Windows-Applikationen
6
211

Windows-Anwendung erstellen Abb. 6.8

das Projekt und eine Datei mit dem Namen Form1.cs. ndern Sie zuerst einmal diesen Namen mit dem Kontextmen auf App.cs. Fr die Datei App.cs existieren nun im Entwicklungssystem zwei Ansichten. Einmal die Entwurfsansicht (Designer) und einmal die Quellcodeansicht. Es wurde schon in Kapitel 5: XMLKlassen erklrt, wie Sie zwischen diesen Ansichten umschalten knnen. Lassen Sie sich nun die Datei einmal in der Quellcodeansicht darstellen. Sie sehen im Folgenden den Code ohne die Kommentare, die der Assistent eingefgt hat. using using using using using using System; System.Drawing; System.Collections; System.ComponentModel; System.Windows.Forms; System.Data;

namespace SimpleEmail { public class Form1 : System.Windows.Forms.Form { private System.ComponentModel.Container components

C#
212

6
=null; public Form1() { InitializeComponent(); } protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.Size = new System.Drawing.Size(300,300); this.Text = "Form1"; } #endregion static void Main() { Application.Run(new Form1()); } } } Im Namensraum SimpleEmail hat der Assistent eine Klasse Form1 angelegt, die von der Basisklasse Form abgeleitet ist, und somit eine Fensterklasse reprsentiert. Es ist empfehlenswert, den Namen der Klasse umzubenennen (z.B. MainWnd). Diese Klasse besitzt auch die statische Methode Main. Im Konstruktor dieser Fensterklasse, wo Sie bisher die Initialisierun-

Windows-Applikationen
6
gen durchgefhrt haben, befindet sich eine Methode InitializeComponent. Sie ahnen richtig, smtliche Initialisierungen werden in dieser Methode gekapselt. Die Methode InitializeComponent ist in einer Prprozessor-Anweisung #region#endregion eingebunden. Sie knnen mit dieser PrprozessorAnweisung Code-Blcke definieren, die sich im Quellcode-Editor von Visual Studio.NET wegblenden lassen. Die Anweisung hat ansonsten keine Bedeutung fr dieses Beispiel. Die Methode Dispose wird am Schluss aufgerufen und hier knnten noch Aufrumarbeiten durchgefhrt werden. Wechseln Sie nun in die Entwurfsansicht (Designer). In der Entwurfsansicht haben Sie eine Toolbox WindowsForms zur Verfgung. Sollte diese bei Ihnen nicht aktiviert sein, dann knnen Sie diese mit dem Menbefehl Ansicht > Toolbox aktivieren.

213

Windows Forms Toolbox Abb. 6.9

Sie knnen nun per Drag&Drop dem Fenster Steuerelemente hinzufgen. Fgen Sie nun ein Steuerelement vom Typ TextBox in den Fensterentwurf ein. Im Fenster Eigenschaften werden nun smtliche Eigenschaften des Steuerelementes aufgelistet. Im Fenster Eigenschaft befindet sich auch ein Eintrag Name. Geben Sie dem Steuerelement den sprechenden Namen tbAddress.

C#
214

Entwurfsansicht Abb. 6.10

Eigenschaftsfenster Abb. 6.11

Im Eigenschaftsfenster werden Sie sich oft aufhalten. Der Inhalt des Eigenschaftsfensters bezieht sich immer auf das gerade markierte Element im Designer. Es werden hier smtliche Members, Properties und Events eines Elements aufgelistet. Sie knnen diese nach Kategorien, Alphabetisch, ausschlielich nach den Eigenschaften (ohne Events) oder ausschlielich nach den Events (ohne Eigenschaften) sortieren. Die entsprechen-

Windows-Applikationen
6
den Icons finden Sie in der Werkzeugleiste des Eigenschaftsfensters. Im Eigenschaftsfenster knnen Sie darber hinaus einem Element auch einen Namen vergeben. Wechseln Sie nun wieder in die Quellcodeansicht der Datei App.cs. Es wird Ihnen auffallen, dass nun der Designer der Klasse eine neue Member-Variable vom Typ TextBox, mit dem von Ihnen im Eigenschaftsfenster gewhlten Namen, eingefgt hat. ndern Sie den Namen der Variable, die ein Steuerelement reprsentiert, immer ber den Designer! Wenn Sie nun einen Blick in die Methode InitializeComponent werfen, sehen Sie folgenden Code: this.tbAddress.Location = new System.Drawing.Point(48, 16); this.tbAddress.Name = "tbAddress"; this.tbAddress.Size = new System.Drawing.Size(200, 20); this.tbAddress.TabIndex = 0; this.tbAddress.Text = "textBox1"; Entsprechend den Angaben im Eigenschaftsfenster hat der Designer in der Methode InitializeComponent fr dieses Steuerelement Initialisierungscode eingefgt. Sollten Sie die Methode InitializeComponent im Quellcode nicht finden, dann suchen Sie den Eintrag Windows FormDesigner generated code und expandieren Sie diesen Eintrag. Der Quellcode-Editor erlaubt nmlich, Gliederungen wegzublenden. ber den Designer knnen Sie also smtliche Steuerelemente im Fenster platzieren und dessen Eigenschaften ber das Eigenschaftsfenster einstellen. Alle Aktionen im Designer haben eine unmittelbare Auswirkung im Code. Unterlassen Sie daher direkte Codenderungen in der Methode InitializeComponent, machen Sie alles ber den Designer! Es ist Ihnen sicherlich schon aufgefallen, dass Sie nicht nur die Eigenschaften der Steuerelemente, sondern auch die Eigenschaften der Form selbst im Eigenschaftsfenster festlegen knnen. Markieren Sie die Form und belegen Sie das Text-Property mit Einfaches E-Mail Programm.

215

C#
216

6
Fgen Sie nun dem Fenster einen Button hinzu. Geben Sie dem Button den Namen btSend und das Text-Property belegen Sie ebenfalls ber das Eigenschaftsfenster mit Senden. Diesem Button werden Sie nun eine Bearbeitungsmethode zuweisen, indem Sie eine Methode beim Event Click anmelden. Verwenden Sie hierzu ebenfalls den Designer:

Hinzufgen von EventBearbeitungsroutinen Abb. 6.12

Markieren Sie im Designer den Button, damit das Eigenschaftsfenster die Eigenschaften des Buttons auflistet und lassen nur die Events anzeigen. Suchen Sie das Event Click und fgen Sie per Doppelklick eine Behandlungsroutine hinzu. Der Designer erzeugt dann im Quellcode unverzglich den Rumpf einer Bearbeitungsroutine. Den Namen der Methode erstellt sich der Designer aus einer Kombination des Namens des Elementes und dem Namen des Events. private void btSend_Click( object sender, System.EventArgs e) { } In der Methode InitializeComponent wird die Anmeldung an das Ereignis durchgefhrt. this.btSend.Click += new System.EventHandler(this.btSend_Click);

Windows-Applikationen
6
Ein Doppelklick direkt auf ein Steuerelement in der Entwurfsansicht hat zur Folge, dass eine bevorzugte Bearbeitungsroutine des Steuerelementes erzeugt wird. Fr Buttons ist dies z.B. die Bearbeitungsroutine fr das Event Click. Wenn Sie eine Bearbeitungsroutine entfernen mchten, dann tun Sie dies bitte ausschlielich im Designer, in dem Sie die Methode im Eigenschaftsfenster lschen. Erweitern Sie nun das Beispiel mit den restlichen Elementen. Sie bentigen noch eine weitere TextBox fr den Betreff (Name tbSubject) und eine RichTextBox (Name rtbBody) fr den Inhalt der Nachricht. Fr statische Texte verwenden Sie das Steuerelement Label. In der Bearbeitungsmethode btSend_Click rufen Sie die noch zu erstellende Methode SendEmail() auf. private void btSend_Click( object sender, System.EventArgs e) { SendEmail(); } private void SendEmail() { if(this.tbAddress.Text == "") { this.tbAddress.Focus(); MessageBox.Show("Bitte Adresse eingeben"); return; } if(this.tbSubject.Text == "") { tbSubject.Focus(); MessageBox.Show("Betreff eingeben"); return; } if(this.rtbBody.Text=="") { rtbBody.Focus(); MessageBox.Show("Nachricht eingeben"); return; } System.Web.Mail.SmtpMail.SmtpServer =

217

C#
218

6
"smtp.Provider.com"; System.Web.Mail.SmtpMail.Send("George Bush", tbAddress.Text, tbSubject.Text, rtbBody.Text); tbAddress.Text=""; tbSubject.Text=""; rtbBody.Text = ""; } In der Methode SendEmail() prfen Sie erst die Eingabefelder. Wenn eines leer ist, wird eine entsprechende MessageBox erzeugt und dann der Eingabefokus auf das leere Steuerelement gelegt. Sind alle Eingabe vorhanden, wird die E-Mail abgeschickt (Sie kennen den Code wahrscheinlich aus Kapitel 2: C# die neue Programmiersprache). Vergessen Sie auch nicht, einen gltigen SMTP-Server in Ihrem Code anzugeben. Damit Ihr Projekt auch fehlerfrei kompiliert, mssen Sie auch einen Verweis auf das Assembly System.Web.dll herstellen!

Zusammenfassung
Nehmen Sie sich Zeit, den Designer gut kennen zu lernen. Wenn Sie diesen beherrschen, dann sind Sie uerst produktiv, wenn es um die Programmierung von Oberflchen geht. Machen Sie sich die Zusammenhnge zwischen Designer und Quellcode bewusst und ben Sie die wichtigen Schritte. Noch einmal die wesentlichen Features des Designers Der Designer manipuliert die Methode InitializeComponent einer Klasse abgeleitet vom Typ Form. Mit dem Designer knnen Steuerelemente, Mens, Werkzeugleisten usw. in grafischer Form einer Fensterklasse zugeordnet werden. Im Designermodus erscheint ein Eigenschaftsfenster, das die Eigenschaften des gerade markierten Elementes im Designer anzeigt. In diesem Eigenschaftsfenster knnen Sie einen Namen (Member) fr das Steuerelement festlegen sowie smtliche Properties und Members der Elemente belegen. Neben Properties

Windows-Applikationen
6
und Members lassen sich im Eigenschaftsfenster auch die Events der Steuerelemente anzeigen und entsprechende Behandlungsroutinen fr die jeweiligen Events erzeugen. In den folgenden Beispielen werden Sie die Arbeit mit dem Designer ben.

219

Men
Dem SimpleEmail-Beispiel werden Sie nun noch eine Menliste zufgen, nmlich einen Meneintrag Extra mit zwei Untermeneintrgen Senden und Alles Lschen. Mit dem Designer knnen Sie einer Fensterklasse eine Menleiste hinzufgen. In der Werkzeugleiste findet sich hierzu das Steuerelement MainMenu. Wechseln Sie zum Designer und fgen per Drag&Drop ein Hauptmen hinzu. Im Eigenschaftsfenster geben Sie diesem Men den Namen mMainMenu.

Meneintrge hinzufgen Abb. 6.13

Die Meneintrge knnen nun direkt in grafischer Form gettigt werden. Fgen Sie nun ein Hauptmen &Extras mit den Untermeneintrgen &Senden und &Alles Lschen hinzu.

C#
220

6
Markieren Sie dann den Meneintrag Alles Lschen. Im Eigenschaftsfenster geben Sie diesem Eintrag den Namen miClearAll (menu item), schalten dann die Ansicht des Eigenschaftsfensters auf Ereignisse (Events) um und fgen diesem Meneintrag (Item) eine Behandlungsmethode zum Event Click hinzu. In dieser Routine belegen Sie smtliche Text-Properties mit einem Leerstring.
CD-Beispiel SimpleEmail2

private void miClearAll_Click( object sender, System.EventArgs e) { tbAddress.Text=""; tbSubject.Text=""; rtbBody.Text = ""; } Markieren Sie nun den Meneintrag Senden und geben diesem Eintrag (Item) den Namen miSend. Fr diesen Eintrag erzeugen Sie sich aber nicht eine eigene Behandlungsroutine, sondern verwenden dieselbe, die auch der Button btSend verwendet. Sie knnen dies ber das Eigenschaftsfenster durchfhren, indem Sie die ComboBox beim Event Click ffnen. Diese listet smtliche, schon vorhandenen Behandlungsroutinen der Steuerelemente auf. Hier whlen Sie nun die Behandlungsroutine btSend_Click aus.

Eine vorhandene Routine zuordnen Abb. 6.14

Windows-Applikationen
6 Kontextmenu
Auch Kontextmens lassen sich mit dem Designer erzeugen. Per Drag&Drop holen Sie sich von der Werkzeugleiste ein Steuerelement vom Typ ContextMenu, geben diesem im Eigenschaftsfenster den Namen cmExtras und fgen dem Men im Designer ebenfalls die Meneintrge Senden und Alles Lschen hinzu. Diese Item-Eintrge erhalten die Namen cmiSend und cmiClearAll (context menu item). Schalten Sie dann das Eigenschaftsfenster auf die Ereignisansicht um und fgen Sie den Eintrgen Behandlungsroutinen hinzu. Sie knnen natrlich dieselben Routinen zuweisen, die Sie fr die Eintrge des Hauptmens verwendet haben. Das Kontextmen soll geffnet werden, wenn Sie im RichTextBox-Steuerelement rtbBody die rechte Maustaste bettigen. Daher im Designer das Steuerelement rtbBody markieren und im Eigenschaftsfenster eine Behandlungsroutine fr das Event MouseDown hinzufgen. Dieses fllen Sie dann mit folgendem Code: private void rtbBody_MouseDown( object sender, System.Windows.Forms.MouseEventArgs e) { if(e.Button == MouseButtons.Right) { this.cmExtras.Show(rtbBody,new Point(e.X,e.Y)); } } In der Methode haben Sie Zugriff auf eine Objekt der Klasse MouseEventArgs. Sie prfen erst, ob auch die rechte Maustaste bettigt wurde. Ist dies der Fall, dann rufen Sie die Methode Show der Klasse ContextMenu auf, mit Angabe des Steuerelementes, das mit dem Kontextmen verbunden sein soll und der Position des Kontextmens. Die momentane Cursorposition knnen Sie sich ber das X- und Y-Property des Objektes vom Typ MouseEventArgs holen.

221

C#
222

Werkzeugleiste
Dem Beispiel SimpleEmail fehlt noch eine Werkzeugleiste. Auch diese fgen Sie am besten ber den Designer ein. Wechseln Sie wieder zur Entwurfsansicht (Designer) und fgen Sie nun per Drag&Drop eine ToolBar aus der Werkzeugleiste hinzu. Die ToolBar wird dann sofort in das Fenster eingeblendet. Es kann sein, dass Sie die schon vorhandenen Steuerelemente umpositionieren mssen, weil die ToolBar doch einigen Platz beansprucht. Markieren Sie diese ToolBar und geben Sie dieser im Eigenschaftsfenster den Namen toolbMain. Dieser ToolBar mssen Sie nun Buttons mit einem Bitmap zuordnen. Die Bitmaps der Buttons werden in einem eigenen Objekt vom Typ ImageList gehalten. Dazu holen Sie sich von der Werkzeugleiste ein Steuerelement vom Typ ImageList und geben diesem auch den Namen ImageList. Im Eigenschaftsfenster sehen Sie dann, dass dieses Objekt eine Eigenschaft Images (in Form einer Auflistung) besitzt. Sie knnen hier einen Dialog ffnen, der Ihnen erlaubt, Bilder, Icons usw. dieser ImageList hinzuzufgen.

Bilder einer ImageList hinzufgen Abb. 6.15

Windows-Applikationen
6
Mit Add navigieren Sie dann zu einer entsprechenden Bilddatei. Sie knnen natrlich auch eine eigene Bilddateien erstellen. Brechen Sie also den Vorgang ab, und erstellen Sie zuerst zwei neue Bilddateien fr die Buttons der ToolBar. ber den Menbefehl Projekt > Neues Element hinzufgen > Bitmapdatei knnen Sie ein neues Bitmap erzeugen.

223

Bitmapdatei erzeugen Abb. 6.16

Geben Sie dem ersten Bitmap den Namen Send.bmp und lassen Sie dann Ihren knstlerischen Fhigkeiten freien Lauf. Nun generieren Sie in gleicher Weise noch ein Bitmap mit dem Namen ClearAll.bmp. Sie knnen nun diese beiden Bitmaps der ImageList im Eigenschaftsfenster des Designers hinzufgen. Erst jetzt sind Sie soweit, auch tatschlich Buttons der ToolBar zuzuweisen. Markieren Sie hierzu die ToolBar im Designer und ffnen Sie den Dialog der Eigenschaft Buttons, und fgen Sie per Add einen Button hinzu. Dieser bekommt einen Namen toobbSend und weisen Sie diesem auch gleich das entsprechende Image aus der ImageList zu. Auch einen Tooltip-Text knnen Sie zuweisen! Alsdann fgen Sie einen zweiten Button hinzu, geben diesem den Namen toobbClearAll und weisen diesem das entsprechende Bitmap zu.

C#
224

Bitmap-Editor Abb. 6.17

Nun brauchen Sie noch Code, um auf ein Button in der Werkzeugleiste zu reagieren. Erzeugen Sie eine Bearbeitungsroutine auf das Event ButtonClick und geben Sie dieser Routine folgende Funktionalitt:
CD-Beispiel SimpleEmail3

private void toolbMain_ButtonClick( object sender, ToolBarButtonClickEventArgs e) { if(e.Button == this.toobbSenden) SendEmail(); if(e.Button == this.toolbbClearAll) { tbAddress.Text=""; tbSubject.Text=""; rtbBody.Text = ""; } } Sie haben in der Methode Zugriff auf ein Objekt vom Typ ToolBarButtonClickEventArgs. In diesem steckt eine Referenz auf den aktivierten Button und knnen ber diese in gezeigter Form feststellen, welcher Button aktiviert wurde und reagieren dann entsprechend.

Windows-Applikationen
6
Hier noch einmal die Vorgehensweise zusammengefasst: Bitmaps fr die ToolBar-Buttons erstellen ber den Designer der Form eine ImageList hinzufgen und die Bitmaps in diese Liste mit aufnehmen (in der Member Images) Der Form eine ToolBar zuweisen. Dem Member Buttons ber den Designer Buttons hinzufgen und jedem dieser Buttons ein Image aus der ImageList zuweisen. Eventuell auch ein ToolTip vergeben. Eine Behandlungsroutine fr das Event ButtonClick der ToolBar per Designer erzeugen. In der Behandlungsroutine stellen Sie fest, von welchem ToolBar-Button die Nachricht kommt und reagieren entsprechend.

225

Dialoge
Dialoge sind in der Windows-Programmierung sehr wichtige Elemente, stellen sie doch die Schnittstelle zwischen dem Benutzer und der Anwendung dar. Im Win32-Programmiermodell sind Dialoge eine relativ komplexe Geschichte. Unter .NET ist das Modell schlssiger und damit einfacher geworden. Die Klasse Form (Basisklasse aller Fenster) implementiert zwei Methoden, die erlauben, ein Fenster darzustellen: Show() ShowDialog() Die Methode Show() ffnet ein Fenster normal. Das Fenster in unabhngig von anderen Fenstern. Die Methode ShowDialog() ffnet das Fenster als Dialog, d.h. die Anwendung ist blockiert, solange der Dialog geffnet ist. Im nchsten Beispiel werden Sie eine kleine Applikation schreiben, die es Ihnen erlaubt, Bilder unterschiedlichen Formats anzuzeigen. ber einen Datei-ffnen-Dialog kann eine Bilddatei ausgesucht werden, die dann im Fenster dargestellt wird. ber einen weiteren Dialog knnen Dateifilter (*.jpg, *.bmp, *.gif) eingestellt werden, damit dann im Datei-ffnen-Dialog nur die entsprechenden Dateien zur Auswahl bereit gestellt werden.

C#
226

6
Dazu legen Sie ein neues Windows-Projekt mit dem Namen ImageViewer an. Benennen Sie die vom Assistenten erzeugte Datei Form1.cs in App.cs um, und wechseln Sie in den DesignerModus. Markieren Sie das Hauptfenster und geben Sie diesem den Namen MainWnd statt den Namen Form1. Per Drag&Drop fgen Sie nun ein Steuerelement PictureBox hinzu und geben diesem im Eigenschaftsfenster den Namen Picture. Das Fenster erhlt dann noch eine Menleiste mit einem Eintrag Datei > ffnen (miOpen) und einem Eintrag Extras > Dateifilter (miFilter), mit entsprechenden Bearbeitungsroutinen. In der Bearbeitungsroutine fr miFilter soll folgender Dialog geffnet werden.

ImageViewer mit Filterdialog Abb. 6.18

Dazu fgen Sie dem Projekt eine neue Leere C#-Quellcodedatei hinzu (Projekt > Neues Element hinzufgen > Codedatei) und geben diesem den Namen FilterDialog.cs. In dieser Datei werden Sie eine neue Fensterklasse anlegen.
CD-Beispiel ImageViewer

using System; using System.Windows.Forms;

public class FilterDialog:Form { public FilterDialog() {

Windows-Applikationen
6
InitializeComponent(); } } Da die Datei nun eine Fensterklasse definiert, knnen Sie auch den Designer verwenden. Fgen Sie dem Fenster nun zwei Buttons btOK und btCancel hinzu (mit den Texten OK und Abbrechen) sowie drei CheckBoxen entsprechend Abbildung 6.18. Fr die Namen der CheckBoxen whlen Sie cbJPG, cbBMP und cbGIF. Wenn Sie nun wieder in die Quellcodeansicht wechseln, werden Sie feststellen, dass der Designer der Klasse FilterDialog die Methode InitializeComponent hinzugefgt hat. Die Klasse FilterDialog versehen Sie nun noch mit drei Properties. ber diese Properties knnen die Zustnde der CheckBoxen gesetzt, aber auch gelesen werden. public bool bJpg { set{cbJPG.Checked = value;} get{return cbJPG.Checked;} } public bool bBmp { set{cbBMP.Checked = value;} get{return cbBMP.Checked;} } public bool bGif { set{cbGIF.Checked = value;} get{return cbGIF.Checked;} } Nun bentigen Sie noch fr die beiden Buttons btOK und btCancel Bearbeitungsroutinen, die Sie natrlich mit dem Designer erstellen. private void btOK_Click( object sender, System.EventArgs e) { this.DialogResult = DialogResult.OK; }

227

C#
228

6
private void btCancel_Click( object sender, System.EventArgs e) { this.DialogResult = DialogResult.Cancel; } In diesen Behandlungsroutinen setzen Sie die Member-Variable DialogResult. Diese wurde von der Basisklasse Form geerbt. Es stehen Ihnen mehrere Enumerationswerte aus dem Namensraum DialogResult zur Verfgung, unter anderem DialogResult.OK und DialogResult.Cancel. Welche Bedeutung dieser Schritt hat, wird Ihnen im Folgenden deutlich werden. Damit haben Sie den Dialog fertig entwickelt. In der Klasse MainWindow fgen Sie nun drei Members ein: bool bJpgFilter; bool bGifFilter; bool bBmpFilter; Im Konstruktor belegen Sie diese Members alle mit true. Der Dialog sollte in der Behandlungsroutine des Meneintrages miFilter geffnet werden. Dies geschieht wie folgt: private void miFilter_Click(object sender, System.EventArgs e) { FilterDialog Dlg = new FilterDialog(); Dlg.bBmp = bBmpFilter; Dlg.bJpg = bJpgFilter; Dlg.bGif = bGifFilter; if(Dlg.ShowDialog()==DialogResult.OK) { bBmpFilter = Dlg.bBmp; bJpgFilter = Dlg.bJpg; bGifFilter = Dlg.bGif; } } In der Behandlungsroutine wird zuerst ein Objekt vom Typ FilterDialog mit dem Namen Dlg erzeugt. Anschlieend werden die Werte der CheckBoxen ber die Properties mit den booleschen Werten der Members belegt, die Sie gerade angelegt haben.

Windows-Applikationen
6
Erst jetzt rufen Sie die Methode ShowDialog auf. Damit wird das entsprechende Fenster im Dialogmodus geffnet. Wenn Sie nun eine CheckBox ndern und OK drcken, wird das Property DialogResult belegt, und dies hat zur Folge, dass der Dialog auch geschlossen wird. Die Methode ShowDialog kehrt zurck und gibt auch gleich den Wert von DialogResult als Rckgabewert mit. Im Code der aufrufenden Methode wird dieser Rckgabewert auf DialogResult.OK geprft und in diesem Fall werden nun die CheckBox-Werte ausgelesen und den Member-Variablen der Klasse MainWnd zugewiesen. In gleicher Weise knnten Sie nun einen Dialog schreiben, um eine Datei auszuwhlen. Aber einen solchen Dialog gibt es schon im Namensraum System.Windows.Forms in Form der Klasse OpenFileDialog. In der Behandlungsroutine fr miOpen wird ein Objekt vom Typ OpenFileDialog angelegt. private void miOpen_Click(object sender, System.EventArgs e) { string Filter = "Bilddateien |"; if(bJpgFilter) Filter = Filter + "*.jpg;"; if(bGifFilter) Filter = Filter + "*.gif;"; if(bBmpFilter) Filter = Filter + "*.bmp;"; OpenFileDialog Dlg = new OpenFileDialog(); Dlg.Filter = Filter; if(Dlg.ShowDialog()==DialogResult.OK) { try { Picture.Image = Image.FromFile(Dlg.FileName); } catch(Exception ex) { MessageBox.Show(ex.Message); } } } Diese Klasse OpenFileDialog besitzt ein Property Filter. Filter kann mit einem Formatstring versehen werden.

229

C#
230

6
Ein Beispiel: "Bilddateien | *.gif;*.bmp;" Dieser String als Filterstring wrde im Dialog nur die Dateien vom Typ *.gif und *.bmp auflisten. Zustzlich kme noch die Information Bilddateien im Dialog zu Tage. string Filter = "Bilddateien |"; if(bJpgFilter) Filter = Filter + "*.jpg;"; if(bGifFilter) Filter = Filter + "*.gif;"; if(bBmpFilter) Filter = Filter + "*.bmp;"; Der Filterstring wird also in Abhngigkeit der drei booleschen Member-Variablen zusammengestellt. Der Aufruf des Dialogs erfolgt, wie gehabt, mit der Methode ShowDialog(). Nach Rckkehr der Methode kann ber das Member Filename der Klasse OpenFileDialog auf den ausgewhlten Dateinamen zugegriffen werden. Mit der statischen Methode FromFile der Klasse Image, kann aus einem Dateinamen ein Objekt vom Typ Image gebildet werden, das dann dem Steuerelement vom Typ PictureBox im Property Image zugewiesen werden kann.
Tipp

Sie knnen eine neue Fensterklasse auch ber den Menpunkt Projekte > Neues Element hinzufgen > Windows Form erstellen lassen! Der Assistent erzeugt dann den Rahmencode fr eine Klasse, die von Form abgeleitet ist.

Kundenspezifische Steuerelemente
Wenn Sie eigene Steuerelemente entwickeln mchten, dann gibt es grundstzlich zwei Mglichkeiten: Erweitern Sie ein bestehendes Steuerelement oder aber entwickeln Sie ein vollkommen neues Steuerelement.

Erweitern eines Steuerelements


Stellen Sie sich vor, Sie brauchen fr Ihre Anwendung einen speziellen Button, der zwei Zustnde annehmen kann (hnlich einem Options-Button). Auerdem soll die Hintergrundfarbe vom Zustand des Buttons abhngig sein. Dann knnten Sie wie folgt vorgehen.

Windows-Applikationen
6
Erzeugen Sie sich eine neue Klasse in folgender Form: class SwitchButton:Button { public Color ColorOn; public Color ColorOff; bool _State; public bool State { set { _State = value; if(_State==true) base.BackColor = ColorOn; else base.BackColor = ColorOff; } get{return _State;} } void OnClick(object sender, EventArgs e) { if(_State==true) State=false; else State = true; } public SwitchButton() { ColorOn = Color.Yellow; ColorOff = Color.Blue; base.Click += new EventHandler(OnClick); State = false; } } Die neue Klasse SwitchButton wird von Button abgeleitet. Damit erben sie smtliche Funktionalitt. Zustzlich erhlt das Steuerelement zwei Member-Variablen ColorOn und ColorOff fr die zustandsabhngige Farbe und ein Property State, das die Hintergrundfarbe des Buttons abhngig vom Zustand steuert. Im Konstruktor werden Default-Farben fr die Zustnde zugeordnet und ein Grundzustand eingerichtet. Ebenfalls muss auf das Click-Ereignis reagiert werden. Bei jedem Click sollte sich der Zustand ndern (und damit auch die Farbe). Dies wird in der Methode OnClick durchgefhrt.

231

C#
232

6
Erstellen Sie ein Leeres Projekt, fgen diesem eine C#-Quellcode-Datei hinzu und geben Sie folgenden Code ein:
CD-Beispiel CustomControl1

using using using using using using

System; System.Drawing; System.Collections; System.ComponentModel; System.Windows.Forms; System.Data;

class SwitchButton:Button { // ... entsprechend obigem Code } class MainWnd:Form { void OnClick(object s, EventArgs e) { MessageBox.Show("Button gedrckt"); } public MainWnd() { SwitchButton but = new SwitchButton(); Controls.Add(but); but.Click+=new EventHandler(OnClick); } } class App { public static void Main() { Application.Run(new MainWnd()); } } Wenn Sie dieses Steuerelement auch in anderen Projekten bentigen, dann bietet es sich an, dieses in einer eigenen Assembly unterzubringen.

Windows-Applikationen
6 Entwicklung eines neuen Steuerelements
Visual Studio.NET bietet zur Entwicklung eigener Steuerelemente eine spezielle Projektvorlage Windows-Steuerelementbibliothek an. Diese Vorlage erzeugt ein Assembly! Im Beispiel werden Sie nun ein Steuerelement Photo erstellen. Diesem kann ein Bild, ein Titel und das Aufnahmedatum zugeordnet werden. Das Steuerelement stellt das Foto dann mit dem Titel dar. Ein Doppelklick auf das Foto ffnet einen Dialog, und gibt das Aufnahmedatum preis.

233

Kundenspezifisches Steuerelement-Photo Abb. 6.19

Erstellen Sie ein neues Projekt mit dieser Vorlage und nennen Sie das Projekt CustomControls. Geben Sie der Datei UserControl1 einen neuen Namen Photo.cs. ndern Sie im Quellcode auch den Klassennamen auf Photo ab. Im Designer fgen Sie nun eine PictureBox (Name: Pic) und ein Label (Name: lTitle) hinzu. Die Position dieser eingebetteten Steuerelemente ist nicht tragisch, da diese spter dynamisch gesetzt werden. Sie sehen, hier wird ein Steuerelement erzeugt, das selbst Steuerelemente einbettet. Fgen Sie nun mit dem Designer eine Behandlungsroutine fr das Ereignis(Event) SizeChanged hinzu (Achtung: nicht fr die eingebetteten Steuerelemente Pic und lTitle!). Ebenfalls fgen Sie eine Behandlungsroutine fr das Ereignis Paint hinzu. ndern Sie auch noch die Eigenschaft SizeMode des eingebetteten Steuerelements Pic auf StretchImage ab. Wenn der Anwender auf das Steuerelement Pic einen Doppelklick ausfhrt, dann sollte auf das Ereignis mit einer MessageBox reagiert werden. Also brauchen Sie noch eine Behandlungsroutine fr das Ereignis DoubleClick des eingebetteten Steuerelements Pic.

C#
234

6
Fgen Sie im Quellcode der Klasse Photo die Properties Picture, Date und Title hinzu. Picture und Title manipulieren die entsprechenden Steuerelemente, Date wird in der Member _Date abgespeichert.
CD-Beispiel CustomControl2

public string Title { set{lTitle.Text = value;} get{return lTitle.Text;} } public Image Picture { set{Pic.Image = value;} get{return Pic.Image;} } DateTime _Date; public DateTime Date { set{_Date = value;} get{return _Date;} } In der Behandlungsroutine SizeChanged wird die Gre der eingebetteten Steuerelemente in Abhngigkeit der Gre des Controls errechnet und zugeordnet. Im Member ClientSize haben Sie die Gre des Photo-Steuerelements zur Verfgung. private void Photo_SizeChanged(object sender, System.EventArgs e) { //Gre der PictureBox und Titellabel einstellen //Mit Bercksichtung eines Randes int H = ClientSize.Height; int W = ClientSize.Width; Pic.Location = new Point(3,3); Pic.Size = new Size(W-6,H*800/1000-6); lTitle.Location = new Point(3,H*800/1000-3); lTitle.Size = new Size(W-6,H*200/1000); } In Photo_Paint zeichnen Sie mit GDI+-Methoden einen Rand um das Steuerelement. Dazu mehr im nchsten Abschnitt.

Windows-Applikationen
6
private void Photo_Paint(object sender, PaintEventArgs e) { Pen p = new Pen(Color.Blue,100f); e.Graphics.DrawRectangle(p,10,10, ClientSize.Width,ClientSize.Height); } Bei einem Doppelklick auf das Foto (eingebettetes Steuerelement PictureBox) reagieren Sie mit einer MessageBox, die den Titel des Fotos sowie das Aufnahmedatum wiedergibt. private void Pic_DoubleClick(object sender, System.EventArgs e) { MessageBox.Show("Aufnahmezeit: "+ Date.ToLongDateString(),Title); }

235

Photo-Steuerelement im Eigenschaftsfenster des Designers Abb. 6.20

Wenn alles ohne Fehler kompiliert, wird ein neues Assembly mit diesem Steuerelement erzeugt. Testen Sie dieses nun in einem Windows-Programm. Dafr legen Sie ein neues Projekt PhotoTest (Windows-Anwendung) an. Wenn sich das Steuerelemente-Projekt und dieses Test-Projekt in derselben Projektmappe befinden, dann sehen Sie Ihr neu erstelltes Steuerelement sogar in der Werkzeugleiste unter Windows Forms. Sie knnen nun per Drag&Drop Steuerelemente vom Typ Photo der Form hinzufgen. Wenn Sie nun einen Blick auf das Eigenschaftsfenster des Photo-Steuerelements werfen, dann fllt

C#
236

6
Ihnen auf, dass smtliche Properties, die Sie dem Steuerelement vergeben haben, konfigurierbar sind.

Zusammenfassung
Unter .NET lassen sich sehr leicht kundenspezifische Steuerelemente erstellen. Die Mglichkeit der Vererbung erlaubt auch kundenspezifische Anpassungen und Erweiterungen von bestehenden Steuerelementen. Nutzen Sie dieses Feature gut, Sie knnen damit Ihre Programme deutlich aufwerten.

GDI+
Microsoft fasst alle C-API-Funktionen, die mit der grafischen Darstellung auf dem Bildschirm oder anderen Ausgabegerten (Drucker, Plotter ...) zu tun haben, unter dem Namen GDI (Graphic Device Interface) zusammen. Auch .NET bietet eine Schnittstelle zu den grafischen Ausgaberten an und nennt diese GDI+. Die GDI+-Klassen befinden sich in den Namensrumen System.Drawing und System.Drawing2D. Wenn Sie mit der GDI unter der Win32 API oder MFC/ATL schon programmiert haben, werden Sie feststellen, dass ein Groteil der .NET GDI+-Klassen die Win32-GDI-Befehle kapselt. Sie werden auch hier die Begriffe wie Pen, Brush, Bitmap, Size, Rectangle usw. wiederfinden. Sie finden in diesen Namensrumen Klassen fr Fonts, Bitmap-Manipulationen, Cursor und Icons, Klassen fr das Zeichnen von Objekten wie Linien, Ellipsen, Rechtecke, Klassen fr Punkte, Grenangaben, Farben sowie Klassen fr GDI-Objekte wie Stifte (Pen), Pinsel(Brush) usw. Darber hinaus wurden bei der Entwicklung dieser Klassen mit vielen, fr den Programmierer lstigen Unzulnglichkeiten aufgerumt. Nur ein Beispiel dazu: Ein Stift (Pen) unter Win32 ist ein GDI-Objekt, das eine Farbe und eine Strichstrke besitzt. Mit diesem Stift knnen Linien, Rechtecke usw. mit der entsprechenden Farbe und Strichstrke gezeichnet werden. Will man nun aber Linien mit anderen Farben und/oder anderer Strichstrke zeichnen, so muss ein neuer Stift mit diesen Eigenschaften erzeugt werden. Es ist nicht mglich, dem vorhandenen Stift eine neue Farbe zuzuordnen. Das Neuerzeugen

Windows-Applikationen
6
ist nichts Besonderes, aber lstig, und da die GDI-Objekte auch wieder aufgelst werden mussten, war die Verwaltung bei vielen GDI-Objekten uerst fehleranfllig. Unter .NET wird eine Klasse Pen (Stift) angeboten, die es erlaubt, bei Instanzen vom Typ Pen die Farbe bzw. Strichstrke jederzeit zu ndern, ohne eine neue Pen-Instanz zu erzeugen. Intern aber wird sehr wohl ein neuer Win32-Pen mit den neuen Eigenschaften erzeugt und der alte Pen aufgelst. Dies wird aber alles in der Klasse versteckt und vereinfacht die Handhabung enorm. Das ist nur ein Beispiel von vielen. Mit GDI+ wird hier eine Schnittstelle angeboten, die objektorientiert ist und weil sie logischer auch einfacher zu verwenden ist. Sie werden nun einen Teil der Funktionalitt der GDI+ kennen lernen, aber wenn Sie die Grundprinzipien kennen, ist es ein Leichtes, die weiteren Features intuitiv zu erfahren. Am besten Sie experimentieren unter Verwendung der Online Hilfe!

237

Pen und Brush


Viele Methoden, wie z.B. DrawLine (Zeichnen einer Linie) oder DrawEllipse (Zeichnen einer Ellipse) haben als Paramater auch die Angabe eines Stiftes. Ein Stift besteht aus einer Farbe und einer Strichstrke. Pen p = new Pen(Color.Beige,20); Einige Methoden, wie z.B. FillEllipse haben eine Fllfarbe (Brush, zu deutsch Pinsel) als bergabeparameter. Ein SolidBrush hat eine Farbe als Eigenschaft im Gegensatz zu einem mglichen Fllmuster. SolidBrush b = new SolidBrush(Color.Azure); Pen und SolidBrush sind Klassen im Namensraum System.Drawing und befinden sich im Assembly System.Drawing.dll.

Font
Die Klasse Font kapselt eine Schriftart. Sie ist ebenfalls im Namensraum System.Drawing zu finden. Zwei wichtige Eigen-

C#
238

6
schaften dieser Klasse sind die Schriftart selbst und die Schriftgre. Font f = new Font("Arial",20);

Point, PointF
Point bzw. PointF sind Strukturen, die die Koordinaten eines zweidimensionalen Punktes halten. Point hlt die Koordinaten in Int32-Typen, whrend PointF die Koordinaten als float-Typen hlt. ber die Eigenschaften X und Y kann auf die Koordinaten zugegriffen werden.

Rectangle, RectangleF
Strukturen, die die Daten eines Rechteckes in Int32 bzw. float halten. Die Eigenschaften sind: X Y Width Length x Wert des linken, oberen Punktes y Wert des linken, oberen Punktes Breite des Rechteckes Hhe des Rechteckes

Size, SizeF
Strukturen, hnlich den Rectangle bzw. RectangleF, haben aber nur die Eigenschaften Width und Length.

Die Klasse Graphics


Die Klasse Graphics aus dem Namensraum System.Drawing enthlt smtliche grafischen Methoden wie DrawLine, FillEllipse, DrawEllipse usw. Wenn Sie ein erfahrener Windows-Programmierer sind und hinter der Klasse Graphics die Kapselung des Gertekontextes sehen, so liegen Sie nicht falsch.

OnPaint-Methode
Die Klasse Form besitzt eine virtuelle Methode OnPaint(). Diese Methode wird aufgerufen, wenn sich ein Fenster neu zeichnen muss. Das Betriebssystem legt eine Nachricht WM_PAINT in

Windows-Applikationen
6
die Nachrichtenschlangen, wenn ein Fenster ungltig geworden ist. Dies ist immer dann der Fall, wenn ein berlappendes Fenster von einem Fenster weggeschoben wird, wenn ein Menbalken wieder verschwindet, wenn das Fenster erzeugt wird oder wenn die Gre verndert wird usw. Der Programmierer kann auch ein Fenster explizit fr ungltig erklren, was dann zur Folge hat, dass das Betriebssystem WM_PAINT in die Nachrichtenschlange legt und wenn ein Nachrichtenbearbeiter auf WM_PAINT existiert, wird dieser ausgefhrt. Die Methode OnPaint() der Klasse Form ist genau dieser Nachrichtenbearbeiter. In der Methode OnPaint() wird und muss die gesamte grafische Funktionalitt programmiert werden. Erstellen Sie dazu ein Leeres Projekt mit dem Namen GDI und fgen Sie diesem eine Leere C#-Quelldatei hinzu. In dieser wird eine Fensterklasse MainWnd mit der Methode OnPaint definiert. Die Klasse App implementiert die Methode Main, die ein Fenster MainWnd ffnet. Schalten Sie im Eigenschaftsdialog des Projektes den Ausgabetyp auf Windows-Anwendung um! In den folgenden Experimenten werden Sie sich hauptschlich in der Methode OnPaint aufhalten! using System; using System.Drawing; using System.Windows.Forms; public class MainWnd : Form { protected override void OnPaint(PaintEventArgs e) { . . . . } } class App { static void Main() { Application.Run(new MainWnd()); } } berschreiben Sie die Methode OnPaint der Basisklasse Form in folgender Form:

239

C#
240

6
CD-Beispiel GDI1

protected override void OnPaint(PaintEventArgs e) { Pen p = new Pen(Color.Red,3); SolidBrush b = new SolidBrush(Color.Beige); Font f = new Font("Arial",20); e.Graphics.DrawLine(p,0,0,100,100); e.Graphics.DrawEllipse(p,0,110,120,120); p.Color = Color.Blue; e.Graphics.DrawRectangle(p,210,110,120,120); e.Graphics.FillEllipse(b,0,240,120,120); e.Graphics.FillRectangle(b,210,240,120,120); b.Color = Color.Red; e.Graphics.DrawString("SimpleWindow",f,b,100,80); }

Zeichnen mit der GDI+ Abb. 6.21

In der Methode OnPaint werden erst einmal die GDI-Objekte, ein roter Stift (Pen) mit einer Strichstrke von 3, ein Pinsel (SolidBrush) mit der Farbe Beige und eine Schriftart (Font) vom Typ Arial mit einer Schriftgre von 20 erzeugt. Dann wird gezeichnet. Ein Objekt vom Typ Graphics erhalten Sie ber den

Windows-Applikationen
6
bergabeparameter e vom Typ PaintArgs und knnen damit smtliche Zeichenfunktionen aufrufen. Beachten Sie das unterschiedliche Verhalten der Methoden DrawEllipse und FillEllipse. DrawEllipse hat einen Pen als Parameter, FillEllipse einen Brush, der die Fllfarbe der Ellipse definiert. Zur Textausgabe verwenden Sie hier DrawString, das ebenfalls einen Brush als bergabeparameter erhlt, und damit die Farbe des Fonts bestimmt wird. Experimentieren Sie auch mit anderen Zeichenmethoden der Klasse Graphics!

241

Koordinatensysteme
Alle Koordinatenangaben sind bisher in Pixelkoordinaten angegeben worden. GDI+ kennt drei Koordinatensysteme: World Page Device Die Koordinatenangabe bei den Drawing-Methoden werden immer als Welt (World)-Koordinaten interpretiert. Bis diese aber tatschlich beim Bildschirm (Device) gezeichnet werden, erfahren diese zuerst eine Transformation in Page-Koordinaten und diese dann in die Gertekoordinaten (Device).

Achsen des Koordinatensystems Abb. 6.22

C#
242

6
Betrachten Sie zuerst die Gertekoordinaten anhand des Gertes Bildschirm. Der Bildschirm ist pixelorientiert, und daher ist die Einheit des Gertekoordinatensystems Pixel. Die Richtungen der x- und y-Achse ersehen Sie aus Abbildung 6.22. Auch World-Koordinaten sind nicht schwer zu verstehen. Das sind die tatschlichen, realen Abmessungen in einem bestimmten Lngenma z.B. m oder inch. Beim Page-Koordinatensystem denken Sie am besten an einen Plan (darum auch der Name Page) mit einem bestimmten Mastab. Wenn Sie also z.B. ein Gebude auf dem Bildschirm abbilden mchten, dann entscheiden Sie sich zuerst fr einen Mastab z.B. 1:100 (denn es ist offensichtlich, dass man ein Gebude nicht im Mastab 1:1 auf den Bildschirm bringen knnen). Die Weltkoordinaten werden dann zuerst auf 1:100 gestaucht, d.h. auf einem Blatt (Page) wird 1m genau 1cm entsprechen. Auf dem Bildschirm knnen allerdings nur Pixel gezeichnet werden, und diese knnen von Gert zu Gert verschieden sein. Bei einem Gert kann 1cm mehr Pixel bedeuten (z.B. 600dpi-Drucker), bei einem anderen Gert deutlich weniger (z.B. Bildschirm mit 800x600-Auflsung). Die Aufgabe der bersetzung von Page-Koordinaten in Gertekoordinaten ist also gerteabhngig und wird im Allgemeinen vom jeweiligen Gertetreiber bernommen. Es ist also nicht die Aufgabe des Programmierers, diese Transformation durchzufhren. Experimentieren Sie:
CD-Beispiel GDI2

protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; Pen p = new Pen(Color.Black,1); g.DrawLine(p,new Point(0,0),new Point(300,0)); g.DrawLine(p,new Point(0,0),new Point(0,300)); SolidBrush bx = new SolidBrush(Color.Red); SolidBrush by = new SolidBrush(Color.Blue); Font f = new Font("Arial",8); for(int i=0;i<300;i+=50) {

Windows-Applikationen
6
g.DrawLine(p,new Point(i,-5),new Point(i,5)); g.DrawString(i.ToString(),f,bx,i,10); g.DrawLine(p,new Point(-5,i),new Point(5,i)); g.DrawString(i.ToString(),f,by,10,i); } } Auf dem Bildschirm erscheinen nun die Achsen x und y mit einer 50er Teilung. Da das Transformationsverhltnis zwischen World und Page 1:1 und zwischen Page und Device ebenfalls 1:1 ist, entsprechen die angegebenen Koordinaten genau den Pixelkoordinaten. Das ist die Grundeinstellung. ndern Sie zuerst die Transformation zwischen Page und Pixel, indem Sie die Eigenschaft PageUnit des Graphics-Objektes auf den Wert GraphicsUnit.Millimeter setzen. Graphics g = e.Graphics; g.PageUnit = GraphicsUnit.Millimeter; Ab sofort werden smtliche Koordinatenangaben in den Methoden nicht mehr als Pixel interpretiert, sondern als Angaben in mm. Sie werden nun erkennen, dass smtliche Koordinatenangaben nun in mm interpretiert werden. Fr PageUnit sind auch noch folgende weitere Enumerationen mglich: GraphicsUnit.Inch GraphicsUnit.Pixel GraphicsUnit.Display GraphicsUnit.Point GraphicsUnit.Document inch Pixel (Grundeinstellung) (1/75 inch) (1/72 inch) (1/300 inch)
CD-Beispiel GDI3

243

Sie sehen auch, dass bei der Verwendung von mm bzw. inch die Verwendung von ganzzahligen Koordinatengren nicht mehr ausreichend ist, da ja ansonsten keine Gren kleiner als 1mm bzw. 1 inch gezeichnet werden knnen. Dies ist der Grund, dass GDI+ auch Koordinaten vom Typ float zulsst. Wenn Sie das durchfhren, dann schaut der Code nun wie folgt aus:

C#
244

6
CD-Beispiel GDI4

protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; g.PageUnit = GraphicsUnit.Millimeter; Pen p = new Pen(Color.Black,0.1F); g.DrawLine(p,new PointF(0,0),new PointF(300F,0F)); g.DrawLine(p,new PointF(0,0),new PointF(0F,300F)); SolidBrush bx = new SolidBrush(Color.Red); SolidBrush by = new SolidBrush(Color.Blue); Font f = new Font("Arial",8); for(float i=0;i<300.0F;i+=50.0F) { g.DrawLine(p,new PointF(i,-5F),new PointF(i,5F)); g.DrawString(i.ToString(),f,bx,i,10F); g.DrawLine(p,new PointF(-5F,i),new PointF(5F,i)); g.DrawString(i.ToString(),f,by,10F,i); } } Statt der Klasse Point wird die Klasse PointF verwendet. Die Zeichenmethoden besitzen alle berlagerte Varianten, die auch Fliekommatypen akzeptierten. Beachten Sie, dass bei der Angabe einer konstanten float-Zahl F bzw. f folgen muss! Die bersetzung von Weltkoordinaten in Page- Koordinaten wird durch eine (3x3)-Transformationsmatrix festgelegt. Damit erhalten Sie smtliche Mglichkeiten des Verschiebens, Stauchens und Streckens und auch Drehens des Koordinatensystems.

CD-Beispiel GDI5

protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; g.Transform = new Matrix(1.0f,-1.0f,1.0f,1.0f,10.0f,20.0f); . . . Die Klasse Matrix befindet sich im Namensraum System.Drawing.Drawing2D. Geben Sie diesen Namensraum frei, damit Sie den Typ verwenden knnen.

Windows-Applikationen
6
m12 m21 dx m12 m21 dy 0 0 1

245

Der Konstruktor von Matrix erlaubt die Belegung der Matrix in der Reihenfolge: m11,m12,m21,m22,dx, dy. Die dritte Spalte ist immer konstant (0,0,1). Im Folgenden sehen Sie einige ausgewhlte Beispiele fr Transformationsmatrizen: Einheitsmatrix 1 0 0 0 1 0 0 0 0

Ursprung wird auf der x-Achse um 10 und auf der yAchse um 20 verschoben 1 0 10 0 1 20 0 0 0

Ursprung wird auf der x-Achse um 10 und auf der yAchse um 20 verschoben. Die x-Achse als auch die yAchse werden um die Hlfte gestaucht. 0.5 0 10 0 0.5 20 0 0 0

Ursprung wird auf der x-Achse um 10 und auf der yAchse um 20 verschoben. Das Koordinatensystem wird auch noch um 45 entgegen dem Uhrzeigersinn gedreht. 1 -1 10 1 1 20 0 0 0

C#
246

6
Die Klasse Graphics besitzt auch Methode, ber die die Transformationsmatrix manipuliert werden kann. protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; g.TranslateTransform(10,20); g.RotateTransform(45.0f); g.ScaleTransform(0.5f,0.5f); g.PageUnit = GraphicsUnit.Millimeter; . . . Die Methode TranslateTransform belegt die Werte dx und dy der Transformationsmatrix und bestimmt somit die Verschiebung des Koordinatenursprungs. RotateTransform manipuliert die Werte m11, m12, m21, m22 um die entsprechende Rotation des Koordinatensystems um den angegebenen Winkel in Grad zu bewirken und ScaleTransform bernimmt die Skalierung (Stauchen bzw. Strecken) der x- als auch der y-Achse. Hier noch einmal die Vorgehensweise zusammengefasst: Definieren Sie eine Maeinheit auf dem Bildschirm (PageUnit). Damit wird Ihre Ausgabe unabhngig vom Ausgabegert. Definieren Sie einen Mastab ber die Transformationsmatrix. Sie knnen nun smtliche Koordinaten in Form von Welt-Koordinaten (tatschliche Gre) angeben. GDI+ wird zuerst eine Transformation durchfhren und dann auch noch dafr sorgen, dass die Ausgabe gerteunabhngig wird. Experimentieren Sie mit diesen Funktionen. So lernen Sie den Umgang am schnellsten.

Bilder, Bitmaps und Images


Die Handhabung von Bildern ist mit der Win32-API eine halbe Doktorarbeit. .NET bietet Klassen an, die die Verwendung von Bildern in unterschiedlichen Formaten deutlich vereinfacht. Die wichtigste Klasse ist die Klasse Image aus dem Namens-

Windows-Applikationen
6
raum System.Drawing. Eine statische Funktion FromFile erlaubt das Belegen eines Objektes. Es werden so ziemlich alle wichtigen Grafikformate akzeptiert. Image im = Image.FromFile(@"c:\Bild.jpg"); Mit der Methode DrawImage des Graphics-Objektes gestaltet sich das Zeichnen des Bildes auf dem Bildschirm sehr einfach. Sie geben die linke obere Ecke, die Breite und die Hhe als Parameter mit. Das Bild wird dann in diesem Bereich angepasst. Smtliche Transformationen, wie Sie es aus dem vorherigen Kapitel kennen, wirken sich auf die Bilder aus. g.DrawImage(im,0,0,100,100); public class MainWnd:Form { Image im; public MainWnd() { this.BackColor = Color.White; this.Text = "SimpleWindow"; this.ClientSize = new Size(400,400); im = Image.FromFile(@"c:\beispiel.jpg"); } protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; g.TranslateTransform(100,20); g.RotateTransform(45.0f); g.ScaleTransform(0.5f,0.5f); g.PageUnit = GraphicsUnit.Millimeter; Pen p = new Pen(Color.Black,0.1F); g.DrawLine(p,new PointF(0,0),new PointF(300F,0F)); g.DrawLine(p,new PointF(0,0),new PointF(0F,300F)); SolidBrush bx = new SolidBrush(Color.Red); SolidBrush by = new SolidBrush(Color.Blue); Font f = new Font("Arial",8); g.DrawImage(im,0,0,100,100); for(float i=0;i<300.0F;i+=50.0F) { g.DrawLine(p,new PointF(i,-5F),new PointF(i,5F));

247

C#
248

6
g.DrawString(i.ToString(),f,bx,i,10F); g.DrawLine(p,new PointF(-5F,i),new PointF(5F,i)); g.DrawString(i.ToString(),f,by,10F,i); } } } Eine Objektreferenz mit dem Name im vom Typ Image wird als Feld der Klasse MainWnd angelegt und im Konstruktor der Klasse durch die statische Methode FromFile und Angabe des Dateinamens initialisiert. In der virtuellen Methode OnPaint wird dann DrawImage mit als bergabeparameter aufgerufen. Achten Sie darauf, dass Sie die Datei beispiel.jpg auch existiert!

Bild ber GDI+ ausgegeben Abb. 6.23

Experimentieren Sie auch mit anderen Grafikformaten!

Ressourcen
Es macht viel Sinn, Daten, die nicht unmittelbar mit der Programmlogik zu tun haben, separat zu verwalten. Werden z.B. Texte einer Anwendung separat verwaltet, dann kann die Anwendung in eine andere Sprache portiert werden, ohne dass man den eigentlichen Code anrhren muss. Dies gilt aber nicht nur fr Texte, sondern kann auf Meneintrge, Erschei-

Windows-Applikationen
6
nungsbild von Dialogen, Cursor-Icons, Bitmap-Dateien und vieles mehr ausgeweitet werden. Daten dieser Art werden Ressourcen genannt. Zur Entwicklungszeit knnen Ressourcen in eigenen binren Dateien mit der Dateierweiterung .resources gehalten werden. Diese knnen ber AL .EXE (Assembly Linker) in ein Assembly eingebettet werden. Damit sind diese statischen Daten Bestandteil des Assembly. .NET bietet mchtige Mglichkeiten an, Ressourcen zu verwalten. Mit Visual Studio geht es sogar noch einfacher. Einem Projekt kann ber den Menpunkt Projekt > Vorhandenes Element hinzufgen... eine Datei mit der Dateierweiterung .resource hinzugefgt werden. Im Eigenschaftsfenster ndern Sie die Eigenschaft Buildaktion auf Eingebettete Ressource. Damit wird die Ressource zum Assembly hinzugelinkt.

249

Datei als Ressource einbinden Abb. 6.24

Erstellung von Dateien vom Typ .resource


Es gibt prinzipiell mehrere Mglichkeiten, Dateien vom Typ .resource zu erzeugen, die dann ber AL.EXE bzw. Visual Studio.NET in ein Assembly eingebunden werden knnen.

C#
250

6
ResGen.exe
In Form einer wohldefinierten XML-Textdatei knnen Daten, die in binrer Form in die .resource-Datei eingelagert werden sollten, beschrieben werden. Ein eigenes Programm Resgen .exe kann dann aus einer solchen XML-Datei eine RessourcenDatei erzeugen. Wenn Sie das Schema fr diese XML-Dateien (Dateierweiterung .resx) kennen, dann knnen Sie jederzeit selbst solche Ressourcen-Beschreibungen erstellen und dann mittels Resgen.exe daraus linkfhige .resource-Dateien erstellen. Natrlich knnen Sie sich nun spezielle, auch grafische Editoren vorstellen, die die Erzeugung solcher .resx-Dateien noch anwenderfreundlicher gestalten. (Der Designer ist brigens so ein Editor, der auch XML-Ressource-Dateien erzeugt.) .NET bietet fr diejenigen, die solche Editoren basteln mchten, folgende Klassen an: System.Resources.ResXResourceReader System.Resources.ResXResourceWriter Mit diesen Klassen lassen sich also .resx-Dateien erzeugen, aber auch lesen. Beachten Sie aber bitte ganz bewusst die Unterschiede zwischen einer .resx-Datei und einer .resource-Datei. Die .resx-Datei dient nur dazu, um die Daten in einer fr Menschen lesbaren Form bereitzustellen. Aus der .resx-Datei muss zuerst mit dem Hilfsprogramm Resgen.exe eine .resource-Datei erzeugt werden, die dann in ein Assembly eingebunden werden kann.

ResourceWriter und ResourceReader


.NET bietet auch Klassen an, die eine direkte Erzeugung von Ressource-Dateien erlauben System.Resources.ResourceWriter System.Resources.ResourceReader Diese Klassen werden im Allgemeinen nicht verwendet, um programmtechnisch Daten aus der Ressource zu lesen, sondern sind vornehmlich fr Entwicklungssystem-Hersteller gedacht, die Hilfsprogramme entwickeln mchten, um Ressource-Dateien direkt zu entwickeln. (Es ist anzunehmen, dass Resgen.exe diese Klassen bentzt.)

Windows-Applikationen
6 Verwenden von Ressource-Dateien
Wenn Sie programmtechnisch auf Daten innerhalb einer Ressource zugreifen mchten, dann bietet sich die Klasse ResourceManager an. Ein erstes Beispiel soll Ihnen das demonstrieren. Angenommen, in Ihrem Programm mchten Sie eine grere Bilddatei verwenden. Sie wollen diese Bilddatei aber nicht als eigenstndige, separate Datei der Anwendung beilegen, sondern die Daten der Bilddatei sollen direkt im .exe-Assembly als Ressource eingebettet sein. Dazu mssen Sie erst eine Ressource-Datei erstellen. Welche Mglichkeiten haben Sie dazu?

251

Erstellen einer .resx-Datei


Dazu mssen Sie das XML-Schema fr .resX-Dateien kennen und dann mit einem beliebigen Editor diese Datei schreiben. Dies ist relativ einfach, wenn die Daten Strings darstellen. Bei einer Bilddatei wird dies schwieriger, weil binre Daten in die XML-Datei (in base64-Codierung) gebracht werden mssen. Fr eine Bilddatei ist dies nicht anwendbar.

Verwendung eines Ressourcen-Editors


Ganz ntzlich wre ein Werkzeug, das eine Ressource-Datei mit eingebetteten Bilddaten unter Angabe einer Bilddatei erzeugen wrde. Visual Studio.NET bietet kein solches Werkzeug an. Aber wie schon erwhnt, bietet .NET Klassen dafr an. Schreiben Sie daher selbst ein solches Werkzeug! Legen Sie dazu ein neues Projekt (Vorlage Konsolen-Anwendung) mit dem Namen RW (ResourceWriter) an und geben Sie folgenden Code ein. Vergessen Sie auch nicht auf das Assembly System.Drawing.dll zu verweisen. using System; using System.Drawing; using System.Resources;
CD-Beispiel RW

class App {

C#
252

6
public static void Main() { ResourceWriter rw = new ResourceWriter(@"c:\newton.resource"); Image image = Image.FromFile(@"c:\newton.gif"); rw.AddResource("Bild",image); rw.AddResource("Bildname","Newton"); rw.Close(); } } Diese kleine (Quick&Dirty)-Applikation erzeugt eine .resource-Datei mit dem Namen newton.resource. Dazu wird ein Objekt vom Typ ResourceWriter mit Angabe des Dateinamens erzeugt. Diese Klasse besitzt eine dreifach berlagerte Methode AddRessource. void AddResource(string,string); void AddResource(string,byte[]); void AddResource(string,object); Damit knnen nun Daten in Form von Schlssel-Wert-Paaren hinzugefgt werden. Neben Strings knnen auch Byte-Streams und sogar beliebige Objekte (soweit diese serialisierbar sind) der Ressource-Datei hinzugefgt werden. Im Beispiel macht es Sinn, die Bilddatei nicht als Byte-Stream, sondern gleich als Objekt vom Typ Image hinzuzufgen. Daher wird zuerst ein Objekt vom Typ Image aus der Bilddatei erzeugt, und dann per AddResource mit dem Schlssel Bild der Datei zugewiesen. Weiter sehen Sie, dass auch ein String Newton (und dem Schlssel Bildname) in die RessourceDatei mit aufgenommen wird. Wenn Sie dieses Hilfsprogramm starten, erzeugt es die Ressource-Datei newton.resource, die Sie nun jederzeit in einem beliebigen Assembly einbetten knnen. (Die im Beispiel verwendete Bilddatei finden Sie auf der CD.) Dies wird im nchsten kleinen Beispiel demonstriert. Dazu legen Sie ein neues Projekt (Vorlage Leeres Projekt) mit dem Namen Newton an, fgen dieser eine C#-Codedatei mit dem Namen App.cs zu und ndern die Projekteinstellung auf Windows-Anwendung. Mit dem Menbefehl Projekt > Vorhan-

Windows-Applikationen
6
denes Element hinzufgen... navigieren Sie zur Ressource-Datei newton.resource und binden diese ein. Im Eigenschaftsfenster der Datei mssen Sie die Eigenschaft BuildAktion auf Eingebettete Ressource festlegen. Damit ist gewhrleistet, dass beim Build-Prozess diese Datei auch mitgelinkt wird.

253

Im Projekt zugefgte Ressource-Datei Abb. 6.25

Verweisen Sie dann noch auf die Assemblies System.dll, System.Windows.Forms.dll und System.Drawing.dll. using using using using using System; System.Windows.Forms; System.Reflection; System.Resources; System.Drawing;
CD-Beispiel Newton1

class MainWnd:Form { public MainWnd() { Assembly a = Assembly.GetExecutingAssembly(); ResourceManager rm = new ResourceManager("Newton.newton",a); Image im = (Image)rm.GetObject("Bild"); string text = rm.GetString("Bildname"); this.BackgroundImage = im; this.Text = text; } } class App {

C#
254

6
public static void Main() { Application.Run(new MainWnd()); } } Der in diesem Zusammenhang interessante Code befindet sich im Konstruktor. Sie erzeugen hier zuerst den so genannten ResourceManager mit Angabe der Ressource, sowie des Assembly, in der die Ressource eingebettet ist. Das Assembly entspricht der Anwendung selbst (daher GetExecutingAssembly) und der Name setzt sich aus dem Assembly-Namen und dem Namen der Ressource zusammen. Im speziellen Fall daher Newton.newton. Wenn das geklappt hat, dann knnen ber GetObject bzw. GetString der Klasse ResourceManager mit Angabe des Schlssels auf die Daten zugegriffen werden.

Newton.exe in Aktion Abb. 6.26

Ressource-Datei ber .resx-Dateien erstellen


Meistens brauchen Sie Ressourcen fr statische Texte. Hier untersttzt Sie das Entwicklungssystem besser, als bei der Einbindung von beliebigen Objekten. Fgen Sie der Applikation ber Projekt > Neues Element hinzufgen... eine Assembly-Ressourcen-Datei hinzu.

Windows-Applikationen
6
255

Ressourcen-Datei erzeugen Abb. 6.27

Taufen Sie diese Datei Strings.resx. Die Datei wird dann in einem eigenen Editor dargestellt.

Resx-Editor des Entwicklungssystems Abb. 6.28

C#
256

6
Sie knnen die Darstellung des Editors in eine reine XML-Ansicht oder in eine Tabellenansicht umschalten. Hier knnen Sie nun in eleganter Form Schlssel-Wert-Paare fr Strings hinzufgen (allerdings nur String-Ressourcen!). Fgen Sie entsprechend Abbildung 6.26 zwei String-Ressourcen Geburtsjahr und Sterbejahr hinzu. Den Quellcode erweitern Sie dann wie folgt:
CD-Beispiel Newton2

public MainWnd() { Assembly a = Assembly.GetExecutingAssembly(); ResourceManager rm = new ResourceManager("Newton2.newton",a); Image im = (Image)rm.GetObject("Bild"); string text = rm.GetString("Bildname"); this.BackgroundImage = im; this.Text = text; rm1 = new ResourceManager("Newton.Strings",a); string gj = rm1.GetString("Geburtsjahr"); string sj = rm1.GetString("Sterbejahr"); Text = Text + " " +gj + " " + sj; } Wenn Sie nun das Projekt kompilieren, dann erzeugt sich das Entwicklungssystem aus der Datei Strings.resx eine Datei (z.B. ber Resgen.exe) Newton.Strings.resources, die dann der Anwendung hinzugelinkt wird. (Sie finden diese temporre Dateien im Projektordner unter /obj/Debug bzw. /obj/Release.) Im Code wird ein zustzliches ResourcenManger-Objekt unter Angabe der neuen Ressource erzeugt und die Werte ber die Schlssel ausgelesen.

Zusammenfassung
Sie haben nun gelernt, wie Ressourcen in der .NET-Programmierung erzeugt und angewendet werden knnen. Ressourcen sind binre statische Daten, die in ein Assembly (.exe- oder .dll) eingebettet werden knnen. Es gibt mehrere Wege, Ressource-Dateien zu erzeugen. Es ist nun denkbar, Assemblies zu erzeugen, die ausschlielich Ressourcen beinhalten. Solche Assemblies werden auch Satelli-

Windows-Applikationen
6
ten-Assembly genannt. Vor allem, wenn mehrere Sprachen (Kulturen) untersttzt werden sollen, knnen die Texte, Icons usw. jeder Kultur in eine eigene Assembly untergebracht werden. Im nchsten Abschnitt wird dieser Aspekt beleuchtet.

257

Untersttzung der Kulturen


Schon frh hat Microsoft begonnen, Anwendungen und Betriebssysteme auch in unterschiedlichen Sprachen und fr unterschiedliche Kulturen anzupassen. Das gefllt den Menschen, und dieser Umstand ist sicherlich ein wesentlicher Grund fr Microsofts beherrschende Marktstellung in der ITBranche. Mit einer Sprachanpassung allein ist es nicht getan. Ein reines bersetzen ist meist zuwenig. Es sind wesentlich mehr Aspekte zu bercksichtigen, wie z.B. Formatangaben, Whrungen, Kuvertgren, Papiermaangaben, Zeitformate usw. Aber auch grafische Elemente werden unter Umstnden in unterschiedlichen Kulturen auch unterschiedlich interpretiert. Wenn Sie wissen, dass Ihre Software auch in unterschiedlichen Kulturen angewendet wird, dann sollten Sie schon im Design darauf Bedacht nehmen. Prinzipiell sollte eine strikte Trennung zwischen der Programmlogik und kulturabhngigen Teilen im Projekt durchgefhrt werden. Wie Sie schon in Kapitel 5: XML-Klassen erkannt haben, bieten sich Ressourcen ganz speziell an, um mehrere Kulturen zu untersttzen. .NET und im Besonderen Visual Studio.NET untersttzten den Entwickler in der Programmierung von multikultureller Software. Bevor Ihnen die Mglichkeiten an einem Beispiel demonstriert werden, noch ein wenig Theorie. In der Spezifikation RFC1766 sind Kulturnamen definiert. Ein Kulturname besteht aus einer Sprache (zwei Buchstaben) und einer Region(zwei Buchstaben). Hier einige Beispiele: en-US en-GB de-AT de-CH fr-CH englisch (Region USA) englisch (Region GB) deutsch (Region sterreich) deutsch (Region Schweiz) franzsisch (Region Schweiz)

C#
258

6
Ausgehend vom Code SimpleEmail soll das kleine E-Mail-Programm auf ein Englisch sprechendes Publikum angepasst werden. Sie knnen sich das Projekt von der CD holen und entsprechend erweitern. Werfen Sie nun kurz einen Blick in die Methode InitializeComponent. Sie wissen ja, dass hier der Designer Initialisierungscode fr Steuerelemente, Mens, Dialoge usw. einfgt. this.tbAddress.Location = new System.Drawing.Point(112, 72); this.tbAddress.Name = "tbAddress"; this.tbAddress.Size = new System.Drawing.Size(208, 20); this.tbAddress.TabIndex = 0; this.tbAddress.Text = ""; Hier z.B. der Initialisierungscode fr das Steuerelement tbAdress. Wechseln Sie nun zum Designer und markieren das HauptFenster. Im Eigenschaftsfenster werden die Eigenschaften der Form aufgelistet. Stellen Sie die Eigenschaft Localizable auf true.

Eigenschaft Localizable Abb. 6.29

Kompilieren Sie nun das Projekt neu und sehen Sie wieder in die Methode InitializeComponent. System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(MainWnd)); this.tbAddress.AccessibleDescription =

Windows-Applikationen
6
((string)(resources.GetObject( "tbAddress.AccessibleDescription"))); this.tbAddress.AccessibleName = ((string)(resources.GetObject( "tbAddress.AccessibleName"))); this.tbAddress.Anchor = . . . . . . Sie sehen, der Designer hat in der Methode ein ResourcenManager-Objekt eingerichtet, und smtliche Werte fr Steuerelemente sind nun in einer Ressource abgelegt. Im Ordner finden Sie die Datei App.resx. ffnen Sie diese und Sie sehen dort smtliche Eintrge! Wenn Sie nun wieder zum Designer wechseln, knnen Sie ber die Form-Eigenschaft Language (momentan eingestellt auf Default) eine Sprache aussuchen. Whlen Sie English.

259

Sprache aussuchen Abb. 6.30

Im Designer ndern Sie nun die Eintrge, auf Englisch. Das sind smtliche Text-Eintrge der Steuerelemente, der Mens und der Form. Vergessen Sie auch nicht die Tooltip-Eintrge der Werkzeugleiste. ndern Sie nun im Eigenschaftsfenster die Eigenschaft Language der Form auf Englisch U.S.A. Im Text-Property der Form geben Sie nun Simple Email U.S. ein.

C#
260

Simple Email im Designer (english) Abb. 6.31

Sie haben nun drei Ressourcen: Default (ursprnglich) English English U.S. Entwickeln Sie nun das Projekt, und werfen Sie einen Blick in den Ordner des Projekts. Dort existiert neben einer Datei App.resx (default) auch eine Datei App.en.resx (englisch) und eine Datei App.en-US.resx (englisch USA). Im Ausgabeverzeichnis (/bin/debug) wurde fr jede Sprache ein Ordner eingerichtet. In diesen findet sich nun je eine Datei mit dem Namen SimpleEmail.resources.dll. Das sind Satelliten-Dlls, die ausschlielich Ressourcen enthalten. Im Designer knnen Sie durch Angabe der Eigenschaft Language jederzeit zwischen den Sprachen umschalten. Der Designer zeigt dann auch die entsprechenden Texte wieder an. Wenn Sie nun das Programm starten, erscheint allerdings die Default-Variante. Was mssen Sie tun, damit eine andere Sprache dargestellt wird?

Windows-Applikationen
6
public MainWnd() { CultureInfo ci =new CultureInfo("en-us"); Thread.CurrentThread.CurrentCulture = ci; Thread.CurrentThread.CurrentUICulture = ci; InitializeComponent(); } Im Konstruktor fgen Sie vor (!) der Methode InitiallizeComponent obigen Code ein. Sie erzeugen ein Objekt vom Typ CultureInfo mit Angabe einer Kultur und weisen dieses Objekt dann dem aktuellen Thread in gezeigter Form zu. Damit der Code auch kompilierbar ist, mssen Sie noch folgende Namensrume freigeben: using System.Globalization; using System.Threading; Wenn Sie nun das Programm starten, erscheint die englische U.S. Variante.
CD-Beispiel SimpleEmail4

261

Simple Email in English U.S. Abb. 6.32

Experimentieren Sie. Whlen Sie jetzt einmal die Kultur en-gb. Fr diese Kultur haben Sie ja keine Ressource erstellt. CultureInfo ci =new CultureInfo("en-gb");

C#
262

6
Sie sehen, es wird die englische Variante (Sprache ohne Region) ausgewhlt. Was passiert, wenn Sie die Kultur fr-ch whlen? CultureInfo ci =new CultureInfo("fr-ch"); In diesem Fall wird die Default-Kultur ausgewhlt. Wenn der ResourcenManager einen Eintrag in der Ressource sucht, dann sucht dieser zuerst in der Satelliten-Dll der Kultur (Sprache und Region) des Threads. Ist keine Satelliten-Dll in dieser Kultur vorhanden, dann wird eine Satelliten-Dll der Sprache (ohne Region) gesucht. Gibt es diese Dll nicht, oder aber ist in dieser Dll der Wert nicht vorhanden, wird in der Default-Ressource nachgeschaut. Wenn Sie einen Blick in die .resx-Dateien werfen, dann fllt Ihnen auf, dass in den Sub-Dlls nur jene Eintrge gettigt sind, die von der bergeordneten Satelliten-Dll abweichen. Alle Eintrge, die ber Designer verwaltet werden, sind nun angepasst. Noch nicht bercksichtigt sind aber die hart kodierten Strings im Code. MessageBox.Show("Bitte Adresse eingeben"); MessageBox.Show("Betreff eingeben"); MessageBox.Show("Nachricht eingeben");

Eintrge in App.resx hinzufgen Abb. 6.33

Windows-Applikationen
6
Diese Texte mssen noch in die Ressource gebracht werden. Dazu ffnen Sie ber das Men Datei > ffnen > Datei die Datei App.resx. In dieser sind die Ressourcen-Eintrge fr die Default-Kultur im XML-Format untergebracht. Fgen Sie nun in der Tabellendarstellung drei String-Ressource-Eintrge hinzu. M_Address M_Subject M_Body Bitte Adresse eingeben Bitte Betreff eingeben Bitte Text eingeben

263

ffnen Sie nun in gleicher Weise die Datei App.en.resx. In dieser sind die Ressource-Eintrge der en-Kultur, die von der Default-Kultur abweichen, zu finden. Auch hier fgen Sie nun bitte die oben angefhrten Eintrge nach.

M_Address M_Subject M_Body

No adress No subject No Body

In der Datei App.en-US.resx brauchen Sie keine weiteren Eintrge durchzufhren, da die der en-Kultur verwendet werden knnen. ResourceManager rm = new ResourceManager(typeof(MainWnd)); if(this.tbAddress.Text == "") { this.tbAddress.Focus(); MessageBox.Show(rm.GetString("M_Address")); return; } if(this.tbSubject.Text == "") { tbSubject.Focus(); MessageBox.Show(rm.GetString("M_Subject")); return; } if(this.rtbBody.Text=="") {

C#
264

6
rtbBody.Focus(); MessageBox.Show(rm.GetString("M_Body")); return; } Die hart kodierten Eintrge werden nun ersetzt. Ein Objekt vom Typ ResourcenManager holt sich die Eintrge ber die Methode GetString unter Angabe des Schlssels aus der richtigen Satelliten-Dll.

Zusammenfassung
.NET untersttzt den Programmierer in der Entwicklung von internationalen Anwendungen mit einem sauberen und schlssigen Programmiermodell. Smtliche kulturabhngigen Ressourcen werden in eigene Satelliten-Dlls untergebracht, die sich in Unterordnern der Applikation befinden mssen. Die Default-Eintrge sind wie gehabt in der Haupt-Assembly untergebracht. Damit ist es auch mglich, Kulturen jederzeit auch nachtrglich zu untersttzen. Eine Unschnheit hat das Programm Simple Email noch. Die Auswahl der Kultur ist hart kodiert. Das ndern Sie in Kapitel 7: Konfigurationsdateien, in dem Sie einiges ber die Konfiguration von Anwendungen erfahren werden.

Konfigurationsdateien

Konfigurationen .NET-Konfigurationsdateien machine.config Zusammenfassung

266 268 273 281

C#
266

Konfigurationen
Kennen Sie die Datei config.sys? Diese Datei stammt aus der guten alten MS DOS-Zeit, und ist sogar noch gelegentlich unter Windows 2000 und hher zu finden. In dieser Textdatei werden unter MS DOS Betriebssystemeinstellungen, wie z.B. Treiber, Sprache usw., in Form von Text-Strings eingetragen. Dies kann mit jedem beliebigen Editor durchgefhrt werden. Natrlich mssen die Eintrge einem gewissen Schema gehorchen. Beim Start liest MS DOS dann diese Eintrge aus und verwertet diese entsprechend. Bestimmte Betriebssystemeinstellungen knnen somit deklarativ konfiguriert werden (ohne das Betriebssystem neu zu installieren). Unter Windows (mitgeerbt von Windows 3.xx) kennen Sie sicherlich die .ini-Dateien, in denen auch fr Applikationen Konfigurationseintrge gettigt werden knnen. Auch diese .iniDateien sind normale Textdateien und knnen daher mit einem handelsblichen Editor bearbeitet werden. Wie leicht einsehbar, mssen diese Konfigurationsdateien natrlich einer bestimmten Struktur gehorchen, damit die Applikation die Eintrge in der Datei auch findet. Meist sind die Eintrge von solchen Konfigurationsdateien Schlssel Wert Paare. [Path] ExecDir=C:\MyApp DataDir=C:\MyApp\data TempDir=C:\ MyApp\data\Temp Hier sehen Sie einen solchen typischen Eintrag einer .ini-Datei fr eine Applikation. [Path] stellt dabei eine Gruppe dar. In dieser Gruppe werden dann Eintrge definiert. Jeder dieser Eintrge besteht aus einem Schlssel (im Beispiel: ExecDir, DataDir, TempDir) und den zugehrigen Werten (nach dem Gleichheitszeichen). Der Programmierer der Applikation hat seinen Code so ausgestattet, dass bei Programmstart die Werte von der Applikation aus der .ini-Datei ausgelesen werden, und in diesem Fall die angegebenen Ordner fr die Arbeit verwendet verwendet. Mit der Einfhrung der Registrierungsdatenbank haben die .ini-Dateien nicht mehr diese Bedeutung, da Einstellungen

Konfigurationsdateien
7
programmtechnisch aus der Registrierung ausgelesen werden knnen. Die Registrierungseintrge werden meist bei der Installation der Applikationen gettigt und/oder die Applikation bietet Mglichkeiten ber Dialoge, Eintrge in die Registrierungsdatenbank durchzufhren. Wie Sie sicherlich schon mehrfach festgestellt haben, versucht .NET von der Registrierungsdatenbank weg zu kommen. Dies ist auch eine Voraussetzung fr XCOPY-Installing also Installation eines Programms durch einfaches Kopieren auf den eigenen Rechner. Und damit wird wieder die Auferstehung der Konfigurationstextdateien unter .NET gefeiert. Back to the roots werden sicherlich einige schon ltere Semester (und dazu gehrt man in unserer Branche schon ziemlich bald) mit Genugtuung feststellen. Textdateien im 3. Jahrtausend sind natrlich keine normalen Dateien, sondern, Sie haben richtig erraten, XML-Dateien. .NET bietet ein leistungsfhiges, erweiterbares und doch einfaches Modell zur Konfiguration von Applikationen ber XML-Dateien an. Wenn Sie dieses Modell einmal durchschauen, dann werden Sie es nicht mehr missen wollen. XML-Konfigurationsdateien fr ausfhrbare Applikationen (.exe) mssen sich im selben Ordner wie die Applikation selbst befinden und haben den Namen der Applikation mit der Dateierweiterung .config. Wenn die Applikation MyApplication.exe heit, so muss die zugehrige Konfigurationsdatei den Namen MyApplication.exe.config haben. Unter ASP.NET muss die Konfigurationsdatei einer ASP.NETApplikation den Namen Web.config tragen. Das Prinzip ist aber dasselbe. In diesem Kapitel werden Sie das Konzept wieder in Form von einfachen Beispielen kennen lernen.

267

C#
268

.NET-Konfigurationsdateien
Format der .NET-Konfigurationsdateien
<?xml version="1.0" encoding="UTF-8"?> <configuration> <configSections> <sectionGroup name="Sybex"> <section name="Path" type="SybexConfig.PathPropertiesHandler, SybexConfig" /> <section name="Email" type="SybexConfig.EmailPropertiesHandler, SybexConfig" /> </sectionGroup> </configSections> <Sybex> <Path ExecDir="C:\MyApp" DataDir="C\MyApp\Data" TempDir="C:\MyApp\Temp" /> <Email To="donald.duck@entenhausen.com" /> </Sybex> </configuration> Eine XML-Konfigurationsdatei beginnt mit der fr XML-Dateien typischen Einleitung. Das Root-Tag heit <configuration>. Bevor Eintrge verwendet werden knnen, mssen diese erst deklariert werden. Dies geschieht im Tag <configSection>. Im <configSection>-Tag knnen nun beliebig viele Gruppen definiert werden. Eine Gruppe bekommt einen bestimmten Namen, im Beispiel den Namen Sybex. Innerhalb einer Gruppe werden nun <section>-Tags definiert. Jedes <section>-Tag erhlt zwei Attribute, nmlich den Namen der Section (name=) und einen Typ (type=...). Bei der Typangabe wird die Klasse angegeben, ber welche diese Eintrge dann aus der Datei gelesen werden knnen, als auch das Assembly, in welchem sich die Klasse befindet.

Konfigurationsdateien
7
<section name="Path" type="SybexConfig.PathPropertiesHandler, SybexConfig" /> Dieses Tag weist darauf hin, dass Eintrge vom Typ <Path> spter mit der Klasse SybexConfig.PathPropertiesHandler, die sich im Assembly SybexConfig befindet, ausgelesen werden knnen. Dazu aber spter. Nachdem die Gruppen und Sektionen deklariert wurden, knnen nun Eintrge gettigt werden. <Sybex> <Path ExecDir="C:\MyApp" DataDir="C\MyApp\Data" TempDir="C:\MyApp\Temp" /> <Email To="donald.duck@entenhausen.com" /> <Sybex> In diesem Beispiel werden Eintrge fr die Gruppe <Sybex> gettigt, und zwar fr die deklarierte Sektion <Path> und <Email>. Die Werte werden in Form von XML-Tag-Attributen angegeben. Aber wie werden diese Werte programmtechnisch aus der Datei gelesen?

269

XML-Konfigurations-Bearbeiter
Laut den Eintragungen in der Konfigurationsdatei bentigen Sie allerdings auch noch die Klassen SybexConfig.PathPropertiesHandler und SybexConfig.EmailPropertiesHandler. Diese werden im Assembly SybexConfig erwartet. Fgen Sie daher der Projektmappe ein C#-Klassenbibliothek-Projekt mit dem Namen SybexConfig hinzu und nennen Sie in bewhrter Manier die Quellcodedatei in SybexConfig.cs (statt Class1.cs) um. Zuerst geben Sie in der Quellcodedatei die Namensrume System.Configuration und System.Xml frei. In diesen Namensrumen befinden sich nmlich smtliche notwendigen Klassen, die Sie nun bentigen werden. Jetzt definieren Sie die Klasse PathPropertiesHandler. Ein Konfigurations-Bearbeiter muss die Schnittstelle IConfigurationSectionHandler mit der einzigen Methode Create implementieren. Dieses sehen Sie im folgenden Codeabschnitt.

C#
270

7
CD-Beispiel SybexConfig

using System; using System.Configuration; using System.Xml; namespace SybexConfig { public class PathPropertiesHandler: IConfigurationSectionHandler { public virtual object Create(object parent, object context, XmlNode node) { PathProperties pp; pp = new PathProperties((PathProperties)parent); pp.LoadAttrFromXml(node); return pp; } } public class PathProperties { string _ExecDir; string _DataDir; string _TempDir; public string ExecDir{get{return _ExecDir;}} public string DataDir{get{return _DataDir;}} public string TempDir{get{return _TempDir;}} public PathProperties(PathProperties parent) { if(parent!=null) { _ExecDir = parent._ExecDir; _DataDir = parent._DataDir; _TempDir = parent._TempDir; } } internal void LoadAttrFromXml(XmlNode n) { XmlAttributeCollection ac = n.Attributes; _ExecDir = ac["ExecDir"].Value;

Konfigurationsdateien
7
_DataDir = ac["DataDir"].Value; _TempDir = ac["TempDir"].Value; } } } Die Methode Create hat drei bergabeparameter, wobei momentan nur der Parameter node vom Typ XmlNode interessiert. Sie liegen richtig, wenn Sie im Parameter node den XML-Eintrag vermuten, dessen Attribute Sie gerne auslesen wrden. In der Methode Create erzeugen Sie zuerst mit einem Konstruktor, dem Sie den Parameter parent mitgeben, ein Objekt vom Typ PathProperties. Dieses Objekt wird dann mit entsprechenden Werten belegt (dazu gleich mehr) und zurckgegeben. Untersuchen Sie nun die Anweisung pp.LoadAttrFromXml(node) nher. Sie holen sich zuerst ber die Eigenschaft Attributes von node die Attributen-Liste und lesen dann die einzelnen Werte der Attribute ber einen implementierten Indexer (mit Strings als Indexparameter) aus. Wenn sich nun alles fehlerfrei kompilieren lsst, legen Sie ein C#-Projekt vom Typ Konsolen-Anwendung an. Nennen Sie es ConfTest. Wieder nennen Sie die vom Assistenten erzeugte Datei Class1.cs auf App.cs und die in dieser Datei definierte Klasse Class1 auf App um. Damit Sie die gerade erzeugten Konfigurationsbearbeiter-Klassen verwenden knnen, mssen Sie natrlich einen Verweis auf Assembly SybexConfig herstellen. Geben Sie auch gleich die Namensrume SybexConfig und System.Configuration frei. Wenn Sie nun das Projekt kompilieren, dann wird die ausfhrbare Datei ConfTest.exe im Ordner /bin/Debug erzeugt. Nach Konvention muss sich nun die XML-Konfigurationsdatei der Applikation im selben Ordner wie die ausfhrbare Datei befinden. In diesem Ordner fgen Sie also eine neue Datei hinzu, am besten mit dem Men Datei > Neu > Datei > XML-Datei. Speichern Sie dann die Datei gleich mittels dem Menbefehl Datei > Speichern unter... im Projektordner unter /bin/Debug mit dem Namen ConfTest.exe.config ab. berprfen Sie mit dem Datei-Explorer, ob die Datei auch wirklich den Namen ConfTest.exe.config besitzt. Das ist sehr wichtig. In diese Datei fgen Sie nun die XML-Daten, die Sie am Beginn des Kapitels betrachtet haben, ein.

271

C#
272

7
CD-Beispiel ConfTest

using System; using SybexConfig; using System.Configuration; class App { static void Main(string[] args) { PathProperties pp = (PathProperties)ConfigurationSettings.GetConfig ("Sybex/Path"); Console.WriteLine(pp.ExecDir); Console.WriteLine(pp.DataDir); Console.WriteLine(pp.TempDir); } } Die Klasse ConfigurationSettings aus dem Namensraum System.Configuration knnen ber die statische Methode GetConfig(...) unter Angabe des XML-Path die Attribute ausgelesen werden. Die Methode GetConfig gibt Ihnen ein Objekt vom Typ object zurck. Dieses knnen Sie nun auf die entsprechende Konfigurationsklasse casten, in diesem Fall auf PathProperties, und anschlieend ber dieses Objekt auf die Konfigurationsdaten zugreifen. Die Methode GetConfig erzeugt sich aus der XML-Path-Angabe einen XmlNode, erzeugt intern ein Objekt PathPropertiesHandler (die Information holt sich die Implementierung ebenfalls aus der Konfigurationsdatei), ruft ber die Schnittstelle IConfigurationSectionHandler die Methode Create auf und reicht deren Rckgabeergebnis durch. Analog lsst sich eine Klasse EmailPropertiesHandler entwickeln, um das Tag <Email> aus der Konfigurationsdatei auszulesen.

CD-Beispiel SybexConfig

public class EmailPropertiesHandler: IConfigurationSectionHandler { public virtual object Create(object parent, object context, XmlNode node) { EmailProperties ep;

Konfigurationsdateien
7
ep = new EmailProperties((EmailProperties)parent); ep.LoadAttrFromXml(node); return ep; } } public class EmailProperties { string _To; public string To{get{return _To;}} public EmailProperties(EmailProperties parent) { if(parent!=null) { _To = parent.To; } } internal void LoadAttrFromXml(XmlNode n) { XmlAttributeCollection ac = n.Attributes; _To = ac["To"].Value; } } Das Auslesen im Hauptprogramm erfolgt dann in dieser Form: EmailProperties ep = (EmailProperties)ConfigurationSettings.GetConfig ("Sybex/Email"); Console.WriteLine(ep.To); Es macht viel Sinn, solche Konfigurations-Bearbeiter (Handler)Klassen in eigenen Assemblies unterzubringen. Damit knnen nmlich diese in anderen Applikationen ebenfalls verwendet werden.

273

machine.config
Im Beispiel haben Sie gesehen, dass Sie in der XML-Konfigurationsdatei zuerst die Struktur fr Eintrge definieren (im Tag <configSections>) und dann, mit Kenntnis dieser Struktur die eigentlichen Eintrge durchfhren.

C#
274

7
Ohne es zu wissen haben Sie aber in der Konfigurationsdatei smtliche Eintragungen einer anderen, maschinenglobalen Konfigurationsdatei sozusagen mitgeerbt. Diese Konfigurationsdatei befindet sich im Ordner WINNT\Microsoft.Net\Framework\v1.0.3705\CONFIG. Wenn Sie einen Blick in diese Datei werfen, sehen Sie eine Menge von <sectionGroup>- und <section>-Eintrgen. Unterlassen Sie es aber, hier nderungen durchzufhren. Alle diese Konfigurationsstrukturen drfen Sie in der Konfigurationsdatei der Applikation verwenden, ohne diese noch einmal zu deklarieren. Und das sind eine Menge, wie Sie sich berzeugen knnen. In dieser maschinenglobalen Konfigurationsdatei werden aber nicht nur Konfigurationsstrukturen deklariert, sondern auch Konfigurationseintrge! Ein Beispiel: <compilers> <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" warningLevel="1" /> <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.VisualBasic.VBCodeProvider, System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <compiler language="js;jscript;javascript" extension=".js" type="Microsoft.JScript.JScriptCodeProvider, Microsoft.JScript, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> </compilers>

Konfigurationsdateien
7
Dieser Eintrag konfiguriert z.B. die verfgbaren Kompiler der installierten .NET-Laufzeitumgebung und gibt deren Dateierweiterungen an. Werden Konfigurationsdaten, die in machine.config schon festgelegt sind, in der Konfigurationsdatei der Applikation noch einmal verwendet, werden die Eintrge der bergeordneten Konfigurationsdatei berblendet. Sie verstehen nun sicherlich auch den Parameter parent vom Typ object in der Methode Create der Schnittstelle IConfigurationSectionHandler. Bei der Suche nach Konfigurationsdaten werden bei Bedarf auch die bergeordneten Konfigurationseintrge ausgelesen. Viele der Konfigurationsstrukturen in der Datei machine.config sind in der Gruppe <System.Web> deklariert. Diese werden vornehmlich von ASP.NET verwendet. Im Kapitel9, ASP.NET werden Sie noch eingehend diese Konfigurationseintrge studieren.

275

appSettings
Interessant ist fr den Anwendungsprogrammierer die Konfigurationsstruktur appSettings in der Datei machine.config. <section name="appSettings" type= "System.Configuration.NameValueFileSectionHandler, System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> Diese <section> ist nicht innerhalb einer Gruppe deklariert, und somit sozusagen global verwendbar. Die Verwendung sei hier aufgezeigt. Nehmen Sie an, dass Ihre Applikation auf eine Tabelle in einer Datenbank alle Eintrge auslesen soll. Der Ort des Datenbankservers, der Datenbankname und die Tabelle sollen per Konfigurationsdatei einstellbar sein. Nun knnten Sie analog dem einfhrenden Beispiel eine eigene Konfigurationsstruktur deklarieren und entsprechende Handler dazu generieren. Mit der Verwendung des schon vordefinierten (in machine.config) Konfigurationsschemas geht es auch einfacher.

C#
276

7
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appSettings> <add key="DSN" value="server=bonsai;uid=sa;pwd=; database=pubs" /> <add key="QUERY_ALL" value="SELECT * FROM IMAGES" /> </appSettings> </configuration> Im Tag appSettings knnen Sie ber das Tag add und den Attributen key und value beliebig viele Schlssel/Werte-Paare zuordnen. Im Beispiel werden zwei Paare eingetragen, einmal ein Wert fr den Schlssel DSN und ein Wert fr den Schlssel QUERY_ALL. Aus der Definition der Konfigurationsstruktur entnehmen Sie, dass die Klasse System.Configuration.NameValueFileSectionHandler fr das Auslesen der Schlssel/Werte-Paare verwendet werden kann. Aber in der Klasse ConfigurationSettings existiert ein spezielle Indexer, der es Ihnen erlaubt, sehr einfach die Werte von Schlsseln auszulesen.
CD-Beispiel AppSetting

using System; using SybexConfig; using System.Configuration; namespace ConfTest { class App { static void Main(string[] args) { string dsn = ConfigurationSettings.AppSettings["DSN"]; string Query = ConfigurationSettings.AppSettings["QUERY_ALL"]; Console.WriteLine(dsn); Console.WriteLine(Query); } } }

Konfigurationsdateien
7
Den grten Teil des Konfigurationsbedarfs von Applikationen kann ber appSettings abgedeckt werden, sodass die explizite Entwicklung von Konfigurationsstrukturen eher die Ausnahme darstellen wird.

277

Simple-Email-Beispiel
Im Kapitel 6 haben Sie das Beispiel SimpleEmail programmiert. In diesem Beispiel haben Sie den smtp-Provider sowie den Absender hart kodiert. Diese beiden Eintrge sollen in die Konfigurationdatei untergebracht werden. Im Folgenden wird dieses Beispiel so erweitert, dass sowohl der smtp-Provider als auch der Absendername ber die Konfigurationsdatei belegt werden kann. Die Applikation wird noch mit einem Dialog erweitert, ber den diese Eintrge in der Konfigurationsdatei manipuliert werden knnen. Zustzlich soll ber die Konfigurationsdatei die verwendete Kultur eingestellt werden knnen. Erzeugen Sie im Ordner SimpleEmail\bin\debug eine XML-Datei mit dem Namen SimpleEmail.exe.config. <?xml version="1.0" encoding="UTF-8"?> <configuration> <appSettings> <add key="SMTP" value="email.myprovider.com" /> <add key="Absender" value="George Bush" /> <add key="Culture" value="en-US" /> </appSettings> </configuration> Zum Projekt fgen Sie einen neuen Dialog hinzu. Verwenden Sie dieses Mal den Assistenten, indem Sie den Menbefehl Projekt > Neues Element hinzufgen > Windows Form bettigen. Geben Sie der Datei den Namen ConfigureDialog. Im Designer fgen Sie dann entsprechend Abbildung 7.1 Steuerelemente hinzu (tbAbsender und tbSmtp).

C#
278

Konfigurationsdialog Abb. 7.1

In der Klasse ConfigureDialog fgen Sie folgende Member-Variablen und Properties hinzu: string ConfigFileName; //Name der XML-config Datei XmlDocument doc; //halt die XML-Struktur public string Absender //Texbox Absender auslesen { get{return tbAbsender.Text;} } public string SmtpProvider { get{return tbSmtp.Text;} } Hier der Konstruktorcode. public ConfigureDialog() { InitializeComponent(); try { //XML-Datei laden ConfigFileName = "SimpleEmail.exe.config"; doc = new XmlDocument(); doc.Load(ConfigFileName); //Knoten suchen XmlNode n = doc.SelectSingleNode( @"configuration/appSettings/add[@key='SMTP']"); XmlNode na =n.SelectSingleNode(@"@value"); this.tbSmtp.Text= na.InnerText; n = doc.SelectSingleNode(

Konfigurationsdateien
7
@"configuration/appSettings/add[@key='Absender']"); na = n.SelectSingleNode(@"@value"); this.tbAbsender.Text = na.InnerText; } catch(Exception ex) { MessageBox.Show( "Fehler in der Konfigurationsdatei"); DialogResult = DialogResult.Cancel; } } Vergessen Sie nicht den Namensraum System.Xml zu ffnen! Zuerst laden Sie die XML-Datei SimpleEmail.exe.configure in ein Objekt der Klasse XmlDocument. ber XPATH-Ausdrcke navigieren Sie dann zu den entsprechenden Nodes, lesen die Werte aus und belegen dann die Steuerelemente mit den Werten. Errichten Sie dann noch zwei Behandlungsroutinen fr btOK und btCancel. private void btOK_Click( object sender, System.EventArgs e) { XmlNode n = doc.SelectSingleNode (@"configuration/appSettings/add[@key='SMTP']"); XmlNode na =n.SelectSingleNode(@"@value"); na.Value = tbSmtp.Text; n.Attributes.SetNamedItem(na); n = doc.SelectSingleNode( @"configuration/appSettings/add[@key='Absender']"); na = n.SelectSingleNode(@"@value"); na.Value = tbAbsender.Text; n.Attributes.SetNamedItem(na); doc.Save(ConfigFileName); this.DialogResult = DialogResult.OK; } private void btCancel_Click(object sender, System.EventArgs e)

279

C#
280

7
{ DialogResult = DialogResult.Cancel; } Wieder navigieren Sie ber XPATH-Ausdrcke zu den jeweiligen Konten. Diese werden mit den neuen Werten aus den TextBoxen belegt und dann im Document abgespeichert. (Mehr zu XPATH finden Sie in Kapitel 4, XML-Einfhrung und Kapitel 5, XML-Klassen.) Nun mssen Sie noch die Applikation dazu bringen, dass diese die Werte aus der Konfigurationsdatei verwendet. Dazu statten Sie die Klasse MainWnd mit zwei neuen Members aus. string SmtpProvider; string Absender; Diese belegen Sie dann im Konstruktor mit den Daten aus der Konfigurationsdatei. public MainWnd() { SmtpProvider = ConfigurationSettings.AppSettings["SMTP"]; Absender = ConfigurationSettings.AppSettings["Absender"]; string sCulture = ConfigurationSettings.AppSettings["Culture"]; CultureInfo ci =new CultureInfo(sCulture); Thread.CurrentThread.CurrentCulture = ci; Thread.CurrentThread.CurrentUICulture = ci; InitializeComponent(); } In der Methode SendEmail() verwenden Sie dann diese Eintrge: System.Web.Mail.SmtpMail.SmtpServer = SmtpProvider; System.Web.Mail.SmtpMail.Send( Absender,

Konfigurationsdateien
7
tbAddress.Text, tbSubject.Text, rtbBody.Text); Die Applikation erhlt noch einen neuen Menpunkt Einstellungen... Dieser ffnet den Dialog und liest die neuen Werte aus. private void miConfigurations_Click( object sender, System.EventArgs e) { ConfigureDialog Dlg = new ConfigureDialog(); if(Dlg.ShowDialog()==DialogResult.OK) { Absender = Dlg.Absender; SmtpProvider = Dlg.SmtpProvider; } } Sie knnen nun den Smtp-Provider und den Absender ber diesen Dialog dauerhaft speichern. Um eine andere Kultur auszuwhlen, mssen Sie die Einstellungen in der Konfigurationsdatei hndisch durchfhren.

281

Zusammenfassung
.NET stellt ein einfaches, aber umso leistungsfhigeres Instrument Applikationen zu konfigurieren zur Verfgung. Dem Entwickler werden eine Reihe von Konfigurationsschemas ber eine bergeordnete Konfigurationsdatei angeboten, welche jederzeit benutzt werden knnen. Das Modell ist auch erweiterbar in der Form, dass eigene Konfigurationsschemas deklariert werden knnen. Nutzen Sie dieses Feature der Konfiguration so gut wie mglich. Bercksichtigen Sie in Ihrem Software-Design diese einfache Form des Konfigurierens von Applikationen.

ADO.NET

Einleitung bersicht der Klassen Zugriff auf MS SQL Server DataSet DataGrid Zusammenfassung

284 286 287 300 313 315

C#
284

Einleitung
ADO.NET ist ein neues Objektmodell fr den Zugriff auf Daten, die vornehmlich in Tabellen strukturiert sind, also auch auf Datenbanken. Nicht schon wieder ein neues Objektmodell, werden Sie vielleicht sagen, wenn Sie sich schon lnger mit der Windowsprogrammierung beschftigen. Microsoft hat schon mehrfach versucht, fr den Datenzugriff Standards zu setzen. Der Quasi-Industriestandard fr den Zugriff auf Datenbanken heit Structured Query Language oder abgekrzt SQL (von einigen auch Sehr qualvolle Language genannt ;-). Datenbankhersteller haben Bibliotheken zur Verfgung gestellt (meist in Form von C-Funktionen), damit Softwareentwickler auf ihre Datenbanken zugreifen konnten. Die Bibliotheken waren aber untereinander nicht austauschbar. Bei einer nachtrglichen Untersttzung oder einem Wechsel zu einer anderen Datenbank musste somit viel Code neu entwickelt werden, obwohl der Standard SQL verwendet wurde! Microsoft fhrte dann ODBC ein (Open Database Connectivity). ODBC sollte ein standardisierte C-API fr SQL-fhige Datenbanken darstellen. Wenn Programmierer Datenbankzugriffe auf dieser Schnittstelle programmieren, so ist auch ein Wechsel auf eine andere Datenbank relativ einfach, da der jeweilige ODBC-Treiber der Datenbank die Umsetzung auf die herstellerabhngige Schnittstelle durchfhrt. Praktisch alle fhrenden Datenbankhersteller stellen ODBC-Treiber fr die WindowsPlattformen bereit. ODBC definiert eine C-API, und ist somit auch nicht objektorientiert. Mit Aufkommen von COM wurden natrlich auch berlegungen gettigt, ein eigenes Objektmodell (ber COMSchnittstellen) bereitzustellen. Der erste Ansatz dazu war DAO (Data Access Objects). Vor allem unter MFC und Visual Basic war diese Schnittstelle sehr beliebt. Microsoft dachte natrlich auch daran, dass die Datenbankhersteller hnlich wie bei ODBC, generische DAO-Treiber bereitstellen. Dies war aber nicht der Fall. War auch nicht tragisch, da Microsoft einen DAO-Treiber fr ODBC bereitstellte, und so konnte man per DAO ber ODBC wieder praktisch auf alle Datenbanken zugreifen (natrlich mit einer kleinen Performanz-Einbue).

ADO.NET
8
Die weiteren Entwicklungen zeigten aber, dass der Zugriff auf Daten doch in einem weiteren Kontext zu sehen ist. Daten in einer Datenbank stellen einen Spezialfall dar. Es musste also ein Objektmodell (Schnittstellen) her, das den Zugriff auf beliebige Daten abstrahiert. Theoretisch sollte so Code erzeugt werden, der vollkommen unabhngig von der Datenquelle ist. Dieses Objektmodell wurde von Microsoft auch entwickelt und heit OLE-DB. Da zu dieser Zeit COM populr war, wurde OLE-DB auch ein COM-Objektmodell. Damit hat DAO natrlich ausgedient. Das OLE-DB Modell kann prinzipiell beliebige Datenquellen abstrahieren (sofern diese tabellarisch darstellbar sind) und spielt alle Stcke. Nachteilig ist aber, dass dadurch das Modell relativ komplex ist. Zudem werden viele Features nur sehr selten gebraucht. Um auch Visual Basic-Programmierer zu untersttzen, hat Microsoft eine Schicht eingefgt, die speziell fr VB-Programmierer gedacht ist (ist nicht bse gemeint ;-). Sie wurde einfacher gestaltet, spielt aber auch nicht alle Stcke. Diese Schicht erhielt dann den Namen ADO (ActiveX Data Objects). Bitte nicht verwechseln mit DAO! ADO ist also nichts anderes, als eine Schale um OLE-DB. Auch das ADO-Objektmodell ist ein COM-Objektmodell. Aber nicht alle Datenbankhersteller bieten auch generische OLE-DB-Treiber an. Fr diese Flle bietet Microsoft einen speziellen OLE-DB zu ODBC-Treiber. Damit ist es wieder mglich, auf praktisch alle Datenbanken zuzugreifen. Mit .NET hat COM ebenfalls ausgedient, und daher ist natrlich auch ein neues Objektmodell von Nten. Dieses neue Objektmodell erhielt den Namen ADO.NET. Obwohl die Namensgleichheit zu ADO besteht, so unterscheidet sich ADO.NET doch deutlich von ADO (alt). In diesem Kapitel werden Sie den Zugriff auf Datenbanken (und Daten allgemein) ber .NET kennen lernen. Dabei werden grundstzliche Kenntnisse ber relationale Datenbanken und SQL vorausgesetzt. Datenbanken sind zu einem zentralen Element in der Anwendungsprogrammierung und vor allem auch bei Web-Applikationen geworden!

285

C#
286

bersicht der Klassen


ADO.NET bietet Ihnen im Namensraum System.Data eine Menge von Klassen an. Implementiert sind diese Klassen alle im Assembly System.Data.dll. Einige Klassen in diesem Assembly bentigen auch Klassen, die im Assembly System.Xml.dll und System.dll implementiert sind. Verweisen Sie daher am besten auf alle diese Assemblies, wenn Sie mit ADO.NET arbeiten. Der Namensraum System.Data definiert intern u.a. die Namensrume System.OleDb und System.Sql.

Klassen im Namensraum System.OleDb


OleDbCommand OleDbConnection OleDbDataAdapter OleDbReader OleDbTransaction Diese Klassen sind unter dem Namensraum System.Data.OleDb definiert. ber diese Klassen knnen praktisch alle in Frage kommenden Datenbanken verwendet werden (siehe einleitende Diskussion).

Klassen fr den Zugriff auf SQL-Server von Microsoft


SqlCommand SqlConnection SqlDataAdapter SqlDataReader SqlTranaction Alle diese Klassen sind im Namensraum System.Data.Sql zu finden. Sie sind speziell fr den SQL Server von Microsoft gedacht. Ihnen ist sicherlich aufgefallen, dass diese Klassen mit denen aus dem Namensraum System.Data.OleDb korrespondieren. Bei der Verwendung des SQL Servers von Microsoft sind diese Klassen aber deutlich schneller. Das ist auch der Grund, warum Microsoft diese speziellen Klassen ausliefert. Die bei-

ADO.NET
8
den Objektmodelle sind aber faktisch gleich zu verwenden. Daher werden nachfolgend im Beispiel diese Klassen verwendet.

287

Die Klasse DataSet


Sollten Sie schon unter ADO (alt) programmiert haben, dann denken Sie bei der Klasse DataSet sicherlich gleich an das ADOObjekt RecordSet. Ganz falsch liegen Sie damit nicht, aber die Klasse DataSet geht weit ber die Funktionalitt von RecordSet hinaus. Wesentlich ist, dass ein DataSet-Objekt im verbindungslosen Zustand manipuliert werden kann. Dazu aber mehr in einem eigenen Unterkapitel.

Zugriff auf MS SQL Server


Fr die folgenden Betrachtungen bentigen Sie einen Zugriff auf einen SQL Server von Microsoft. Sollten Sie diesen nicht haben, dann ist es empfehlenswert, SQL Server oder die abgespeckte Version von MSDE (Microsoft Data Engine) auf Ihrem Rechner zu installieren. MSDE besitzt dieselben Schnittstellen wie SQL Server, ist aber nicht in dem Ma skalierbar (und kann vor allem lizenzfrei mit Ihrer Software vertrieben werden, wenn Sie mit Visual Studio.NET entwickeln). Ein spterer Wechsel auf die ausgewachsene Datenbank ist ohne Codenderung mglich!

Datenbank anlegen
An einem neuen Beispiel werden Sie nun den .NET-Zugriff auf Datenbanken kennen lernen. Denken Sie an eine Anwendung, die Fotos in Form von gngigen Bildformaten verwalten kann. Alle Fotos werden in einem bestimmten Ordner abgespeichert. Jedes Foto soll einen Titel haben und auch das Aufnahmedatum soll gespeichert werden. Sie knnen sich natrlich noch weitere Eigenschaften vorstellen. So knnte man jedem Foto einen kleinen Kommentar zuweisen oder aber auch eine Sprachdatei usw. Das ist aber Ihnen berlassen, weil im Moment die Konzentration auf ADO.NET liegt. In der Datenbank werden die Fotos aber nicht abgespeichert, sondern nur die zustzlichen Daten. Damit Sie mit den Experimenten starten

C#
288

8
knnen, erstellen Sie zuerst eine Datenbank. Sie ist sehr einfach gestaltet und besteht nur aus einer Tabelle tPhoto. Um Tabellen anzulegen, bietet SQL Server den so genannten Enterprise Manager an. MSDE, die abgespeckte Version, wird aber ohne diesen Enterprise Manager ausgeliefert. Macht aber nichts, da Visual Studio.NET diesen Enterprise Manager integriert. Sie knnen also aus dem Entwicklungssystem heraus auch die Datenbank erstellen. Dazu brauchen Sie das Fenster Server-Explorer. Wenn es nicht schon dargestellt wird, dann knnen Sie dieses Fenster ber den Menpunkt Ansicht > Server-Explorer aktivieren.

Server-Explorer Abb. 8.1

In Abbildung 8.1 sehen Sie die Darstellung auf dem Entwicklungsrechner des Autors. Der Server-Explorer zeigt die Server der Maschinen erle und esche an. Sie knnen mit dem ServerExplorer auch die Server anderer Maschinen darstellen lassen. Dies machen Sie ber das Kontextmen Server hinzufgen... ber den Server-Explorer knnen smtliche Server verwaltet werden, so unter anderem auch SQL Server. Dass Sie die notwendigen Rechte besitzen mssen, ist natrlich Voraussetzung. Auf dem Datenbank-Server legen Sie zuerst eine Datenbank mit dem Namen PhotoPool an. Hierzu markieren Sie die SQLServer-Instanz und erzeugen nun per Kontextmen Neue Da-

ADO.NET
8
tenbank eine neue Datenbank. Verwenden Sie Windows NT Security.

289

Neue Datenbank anlegen Abb. 8.2

Im Server-Explorer sollten Sie nun die Datenbank PhotoPool sehen. Expandieren Sie Eintrag im Server-Explorer, markieren Sie den Eintrag Tabellen und aktivieren Sie per Kontext den Meneintrag Neue Tabelle. Sie sehen nun die Entwurfsansicht der Tabelle. Fgen Sie der Tabelle Spalten hinzu, entsprechend der Abbildung 8.3.

Entwurfsansicht der Tabelle tPhoto Abb. 8.3

Achten Sie auch darauf, dass die Spalte ID Primrschlssel ist. Sie knnen der Spalte per Kontextmen Primrschlssel festlegen

C#
290

8
den Primrschlssel zuweisen. Im Eigenschaftsfenster der Spalte ID belegen Sie die Identitt (ID) auf Ja (siehe Abbildung 8.3). Wenn Sie diesen Entwurf zum ersten Mal abspeichern, werden Sie aufgefordert, der Tabelle einen Namen zu geben. Geben Sie der Tabelle den Namen tPhoto und schlieen Sie die Ansicht. Im Server-Explorer sollten Sie nun die Tabelle tPhoto sehen. Ein Doppelklick zeigt Ihnen den Inhalt dieser Tabelle an, die jetzt natrlich noch leer ist. Sie knnten hier auch direkt Eintragungen vornehmen. ber das Kontextmen Entwurf Tabelle knnen Sie aber auch jederzeit wieder in die Entwurfsansicht der Tabelle wechseln. Damit haben Sie die Voraussetzungen fr die folgenden Experimente geleistet. Sie werden nun die vier grundlegenden Operationen auf Tabellen kennen lernen. Das sind: Daten in die Tabelle eintragen Eintrag in der Tabelle lschen Updaten eines Eintrages Eintrge auslesen

Daten in die Tabelle eintragen


Fr die Experimente erzeugen Sie sich am besten eine neue Projektmappe mit dem Namen ADO und legen darin ein Projekt (Leeres Projekt) mit dem Namen PhotoAccess an. Fgen Sie dem Projekt eine leere C#-Quellcodedatei mit dem Namen App.cs hinzu. Damit Sie auch Zugriff auf die .NET-Klassen haben, mssen Sie einen Verweis auf das Assembly System.Data.dll und System.Dll herstellen.
CD-Beispiel PhotoAccess1

using System; using System.Data; using System.Data.SqlClient; public class PhotoAccessSql { string DSN; public PhotoAccessSql(string DSN) {

ADO.NET
8
this.DSN = DSN; } public void AddPhoto(string Title, DateTime Date, string Filename) { SqlConnection con = new SqlConnection(DSN); try { con.Open(); string sCmd = "INSERT INTO tPhoto VALUES('" + Title + "','" + Date.ToString() + "','" + Filename + "')"; SqlCommand cmd = new SqlCommand(sCmd,con); cmd.ExecuteNonQuery(); } finally { con.Close(); } } } class App { public static void Main() { //Achtung: Ihren spez. Servernamen verwenden string DSN = @"server=esche;uid=sa;pwd=;database=PhotoPool;"; PhotoAccessSql pa = new PhotoAccessSql(DSN); //3 Beispieleintrge ttigen DateTime dat = new DateTime(2001,8,3); pa.AddPhoto("Urlaub Korsika",dat,"Bild1.jpg"); pa.AddPhoto("Ankunft Koriska",dat,"Bild2.jpg"); pa.AddPhoto("Hotel Korsika",dat,"Bild3.jpg"); } }

291

C#
292

8
Smtliche Zugriffe auf die Datenbank werden Sie im Folgenden in der Klasse PhotoAccessSql implementieren. Diese Klasse besitzt eine Member-Variable DSN (Data Source Name). Diese Member-Variable wird im Konstruktor der Klasse belegt. Die Klasse PhotoAccessSql wird mit einer Methode AddPhoto ausgestattet. Als bergabeparameter verlangt diese Methode den Titel, das Aufnahmedatum und den Dateinamen des Fotos. Wie Ihnen sicherlich bekannt ist, brauchen Sie eine Datenbankverbindung, wenn Sie mit einer Datenbank kommunizieren wollen. Die Klasse SqlConnection abstrahiert eine solche Verbindung. Der Konstruktionsparameter dieser Klasse verlangt einen Connection-String. Der Connection-String fr die Datenbank PhotoPool schaut wie folgt aus. server=esche;uid=sa;pwd=;database=PhotoPool;; Im o.g. Fall des Autors heit der Server esche (hier setzen Sie natrlich Ihren Servernamen ein!), fr uid wird eine UserID angegeben. Im speziellen Fall ist dies der Systemadministrator (sa) und es wird kein Passwort angegeben (pwd). Die Datenbank heit PhotoPool. Wenn Sie ein initialisiertes SqlConnection-Objekt haben, dann knnen Sie ber die Methode Open eine tatschliche Verbindung ffnen. Vergessen Sie aber nicht, diese Verbindung auch wieder mit der Methode Close zu schlieen, wenn Sie Ihre Arbeit erledigt haben. Viele Datenbanken erlauben nmlich nur eine gewisse Anzahl von offenen Verbindungen, und Sie binden damit wertvolle Ressourcen. Damit dies auch sicher passiert, auch im Fall einer Exception, schtzen Sie den kritischen Code am besten mit einem try-finally-Block! Nchste Aufgabe besteht darin, ein SqlCommandObjekt zu erzeugen. Der Konstruktor des SqlCommand-Objektes verlangt zur Initialisierung eine SQL-Anweisung und ein SqlConnection-Objekt. Die SQL-Anweisung basteln Sie in Abhngigkeit der bergabeparameter zusammen. Ein Beispiel fr eine syntaktisch richtige SQL-Anweisung fr das Einfgen eines Eintrages sehen Sie hier. INSERT INTO tPhoto VALUES('Urlaubsfoto', '3.7.2001', 'Bild1.jpg')

ADO.NET
8
Die Klasse SqlCommand implementiert drei Methoden, die fr eine Ausfhrung der Anweisung verwendet werden knnen: ExecuteReader() ExecuteScaler() ExecuteNonQuery() Alle diese Methoden fhren die Anweisung die im SqlCommand-Objekt gekapselt ist, durch. ExecuteReader wird verwendet, wenn das Kommando Rckgabewerte besitzt (z.B. bei einer Abfrage). ExecuteScaler() wird gerne verwendet, wenn genau ein Rckgabewert zu erwarten ist (z.B. eine Abfrage bezglich Anzahl der Elemente). ExecuteNonQuery() kann angewendet werden, wenn das Kommando keinen Rckgabewert besitzt. Dies ist im Beispiel der Fall, da die INSERT-Anweisung einen Eintrag ttigt, und somit keine Abfrageergebnisse zurckgibt. In der Hauptfunktion wird ber den Connection-String ein Objekt vom Typ PhotoAccess erzeugt und dann die Methode AddPhoto aufgerufen. In der Tabelle tPhoto der Datenbank PhotoPool sollte nun ein Eintrag vorhanden sein. Fgen Sie einige Eintrge hinzu!
Eintrge in der Tabelle tPhoto der Datenbank PhotoPool Abb. 8.4

293

Hier die Schritte noch einmal zusammengefasst: SqlConnection-Objekt erzeugen (mit Angabe eines Connection-Strings) SqlConnection.Open aufrufen, um Verbindung zur Datenbank herzustellen SqlCommand-Objekt erzeugen, unter Angabe einer SQL-Anweisung und einer SqlConnection Aufruf einer der drei Methoden ExecuteNonQuery, Execute-Scalar, ExecuteReader SqlConnection.Close aufrufen, um die Datenbankverbindung wieder abzubauen

C#
294

8 Eintrag lschen
Die Klasse PhotoAccess erweitern Sie nun um die Methode DeletePhoto. ber Angabe der ID des Eintrages wird dann der gesamte Eintrag gelscht.
CD-Beispiel PhotoAccess2

public void DeletePhoto(int ID) { SqlConnection con = new SqlConnection(DSN); try { con.Open(); string sCmd = "DELETE FROM tPhoto WHERE "+ "ID ='"+ ID + "'"; SqlCommand cmd = new SqlCommand(sCmd,con); cmd.ExecuteNonQuery(); } finally { con.Close(); } } Bis auf die unterschiedliche SQL-Anweisung unterscheidet sich der Code nicht von der Implementierung der Methode AddPhoto. Ein Beispiel fr eine syntaktisch richtige SQL-Anweisung fr das Lschen eines Eintrages sehen Sie hier. DELETE FROM tPhotos WHERE ID='3' public static void Main() { string DSN = @"server=esche;uid=sa;pwd=;database=PhotoPool;"; //Beispielaufruf pa.DeletePhoto(3); } Mit diesem Aufruf wird der Eintrag mit der ID 3 gelscht.

ADO.NET
8 Update eines Eintrages
Die Methode UpdatePhoto soll Werte eines bestehenden Eintrages ndern. Eine SQL-Anweisung, die dieses durchfhrt, knnte wie folgt aussehen: UPDATE tPhoto SET Title='Urlaub Elba, Date='3.8.2001', Filename = 'Image1.gif' WHERE ID='2' In diesem Fall wrden die Eintrge Title, Date und Filename in der Tabelle tPhoto mit dem ID-Wert 4 entsprechend modifiziert. Auch diese Implementierung unterscheidet sich grundstzlich nicht von den vorhergehenden Implementierungen. public void UpdatePhoto( int ID,string Title,DateTime Date,string Filename) { SqlConnection con = new SqlConnection(DSN); try { con.Open(); string sCom = "UPDATE tPhoto SET Title = '" + Title + "', Date= '" + Date.ToString() + "', Filename= '" + Filename + "' WHERE ID= '"+ ID + "'"; SqlCommand cmd = new SqlCommand(sCom,con); cmd.ExecuteNonQuery(); } finally { con.Close(); } } Hier der Code zum Testen! public static void Main() { string DSN =
CD-Beispiel PhotoAccess3

295

C#
296

8
@"server=esche;uid=sa;pwd=;database=PhotoPool;"; PhotoAccessSql pa = new PhotoAccessSql(DSN); //Beispielaufruf DateTime dat = new DateTime(2000,7,2); pa.UpdatePhoto(2,"Urlaub Elba",dat,@"Image1.jpg"); }

Daten lesen
Nun fehlt noch eine Methode, um Daten aus der Datenbank zu lesen. Die Methode soll folgenden Prototyp besitzen: Photo [ ] GetPhotos( ); Diese gibt also ein Feld aus Objekten vom Typ Photo zurck. Die Klasse Photo mssen Sie natrlich zuerst noch anlegen.
CD-Beispiel PhotoAccess4

public class Photo { public int ID; public string Title; public DateTime Date; public string Filename; public Photo(int ID,string Title, DateTime Date, string Filename) { this.ID = ID; this.Title = Title; this.Date = Date; this. Filename = Filename; } } Damit knnen Sie nun die Methode GetPhotos wie folgt implementieren: public Photo[] GetPhotos() { SqlConnection con = new SqlConnection(DSN); try { con.Open(); string sCom = "SELECT * FROM tPhoto";

ADO.NET
8
SqlCommand cmd = new SqlCommand(sCom,con); SqlDataReader r = cmd.ExecuteReader(); ArrayList phs = new ArrayList(); while(r.Read()) { phs.Add(new Photo((int)r[0], (string)r["Title"], r.GetDateTime(2), (string)r[3])); } Photo [] ps = (Photo []) phs.ToArray(typeof(Photo)); return ps; } finally { con.Close(); } } Auch hier stellen Sie zuerst eine Verbindung zur Datenbank her, erzeugen dann ein SqlCommand-Objekt, verwenden aber hier die Methode ExecuteReader des SqlCommand-Objektes. Zurck erhalten Sie ein Objekt vom Typ SqlDataReader ( r ). Diese Klasse besitzt unter anderem eine Methode Read. ber diese Methode knnen Sie sich nun innerhalb der Ergebnismenge vorwrts iterieren (forward cursor). SqlDataReader fhrt intern einen Cursor, der bei jedem Read-Aufruf zum nchsten Ergebnis springt. Read() gibt false zurck, wenn kein weiteres Ergebnis mehr anliegt. Um auf die Daten zuzugreifen, haben Sie jetzt mehrere Mglichkeiten. Alle die nachfolgend vorgestellten Mglichkeiten wurden im obigen Beispielcode verwendet. Verwendung des Indexers der Klasse SqlDataReader Mit Angabe der Spaltennummer (Basis 0) wird der Spaltenwert zurckgegeben. Hier ein Beispiel: int ID = ((int)r[0];

297

Mit r[0] wird der Inhalt der ersten Spalte des Ergebnisses zurckgegeben, allerdings als object-Typ. Sie mssen daher den

C#
298

8
Wert noch entsprechend casten. Als Index knnen Sie aber auch direkt den Spaltennamen der Tabelle verwenden! string Name = (string)r["Title"]; Dies ist natrlich nicht so effizient, wie der Zugriff ber den Indexer, da hier noch eine String-Verarbeitung stattfinden muss. Auerdem drfen Sie in diesem Fall die Spaltennamen der Tabelle nicht mehr verndern. Verwendung der GetXXX-Methoden der Klasse SqlDataReader Die Klasse SqlDataReader implementiert fr jeden DatenbankDatentyp eine GetXXX-Methode. Im Beispiel wird der Datumswert mit der Methode GetDateTime ausgelesen. DateTime d =r.GetDateTime(2); Dabei wird der Spaltenindex der Methode als Parameter mitgegeben. Ein Vorteil der Verwendung der Methoden liegt darin, dass die Werte typisiert zurckkommen. Dies ist weniger fehleranfllig (zur Entwicklungszeit). Im Beispiel wird bei jedem erfolgreichen Read()-Vorgang ein Photo-Objekt erstellt und in ein dynamisches Feld gebracht. (ffnen Sie im Beispiel daher auch den Namensraum System.Collection.) Erst am Schluss wird dann der Inhalt des dynamischen Feldes noch in ein statisches Feld gebracht. Der Vorteil des statischen Feldes liegt wieder darin, dass dieses typisiert ist. Photo [] ps = (Photo []) phs.ToArray(typeof(Photo)); Hier noch ein kleiner Code fr einen Test! public static void Main() { string DSN = @"server=esche;uid=sa;pwd=;database=PhotoPool;"; PhotoAccessSql pa = new PhotoAccessSql(DSN); Photo [] ps = pa.GetPhotos(); for(int i=0;i<ps.Length;i++) {

ADO.NET
8
Console.WriteLine(ps[i].Title); } } Mit der Methode GetPhotos erhalten Sie smtliche Eintrge der Datenbank zurck. Wenn Sie aber nur einen bestimmten Eintrag aus der Datenbank mchten, dann mssen Sie noch eine weitere Methode implementieren. Photo GetPhoto(int ID); Diese Methode gibt genau ein Objekt vom Typ Photo zurck, das dem ID-Wert der Parameters entspricht. public Photo GetPhoto(int ID) { SqlConnection con = new SqlConnection(DSN); try { con.Open(); string sCom = "SELECT * FROM tPhoto " + "WHERE ID = '"+ ID +"'"; SqlCommand cmd = new SqlCommand(sCom,con); SqlDataReader r = cmd.ExecuteReader(); if(r.Read()) { return new Photo( (int)r[0], (string)r[1], r.GetDateTime(2), (string)r[3]); } else return null; } finally { con.Close(); } } Auch hier wird ein SqlCommand-Objekt mit der entsprechenden SQL-Anweisung erstellt, per ExecuteReader ein SqlDataReader-Objekt generiert und Read aufgerufen. Sollte kein Ergebnis vorliegen, dann wird ein null-Wert zurckgegeben.

299

C#
300

8 Zusammenfassung
Sie haben nun eine Klasse PhotoAccess entwickelt, die Methoden anbietet, um Manipulationen in der Datenbank durchzufhren. Smtliche SQL-spezifischen Zugriffe sind in den Methoden untergebracht. Im Hauptprogramm mssen Sie sich nicht mehr um die SQL-Details kmmern. Es macht natrlich viel Sinn, diese Klasse in einem eigenen Assembly unterzubringen.

DataSet
Mit .NET wurde diese neue, beraus interessante Klasse eingefhrt. Die Klasse DataSet hat viele Gemeinsamkeiten mit dem RecordSet aus ADO, geht aber in seiner Funktionalitt weit darber hinaus. Ein Objekt vom Typ DataSet kann intern mehrere Tabellen mit Spalten und Reihen beinhalten. Ja es knnen sogar die Beziehungen der Tabellen untereinander gehalten werden. Stellen Sie sich ein DataSet wie eine Mini-Datenbank im Speicher vor. Es muss aber betont werden, dass ein DataSet-Objekt absolut unabhngig von einer Datenbank ist. Sie knnen, wenn Sie wollen ein DataSet-Objekt erzeugen, in diesem dann Tabellen mit Spalten (und Namen), Beziehungen zwischen den Tabellen herstellen, und diese dann auch belegen, unabhngig von einer Datenbank. Es ist nun naheliegend, dass ein DataSet-Objekt auch mit Daten einer Datenbank oder mit Daten eines Abfrageergebnisses gefllt werden kann. Dies ist auch mglich, und .NET stellt dazu eine eigene Klasse SqlDataAdapter an, die Daten aus einer Datenbankverbindung auslesen und ein DataSet belegen kann. Dazu ein Beispiel (Legen Sie ein neues Projekt an!):
CD-Beispiel PhotoAccess5

using using using using

System; System.Data; System.Data.SqlClient; System.Collections;

class App

ADO.NET
8
{ public static void Main() { string DSN = @"server=esche;uid=sa;pwd=;database=PhotoPool;"; SqlConnection con = new SqlConnection(DSN); //DataAdapter ertellen SqlDataAdapter da = new SqlDataAdapter(); string sCom = "SELECT * FROM tPhoto"; da.SelectCommand = new SqlCommand(sCom,con); //DataSet erstellen und fllen DataSet ds = new DataSet(); da.Fill(ds,"Photos");

301

//Tabellen auslesen for(int i=0;i<ds.Tables.Count;i++) { DataTable t = ds.Tables[i]; Console.WriteLine("Tabellenname: {0}",t); //Spaltennamen der Tabelle auslesen for(int j=0;j<t.Columns.Count;j++) { DataColumn c = t.Columns[j]; Console.WriteLine("Spaltennamen: {0}",c); } //Reihen der Tabelle auslesen for(int j=0;j<t.Rows.Count;j++) { DataRow r = t.Rows[j]; Console.WriteLine("{0} {1} {2} {3}", (int)r[0],(string)r[1], (DateTime)r[2],(string)r[3]); } } } }

C#
302

8
Damit sich dieses Beispiel auch kompilieren lsst, mssen Sie die Assemblies System.dll, System.Data.dll und System.Xml.dll referenzieren. Nun aber zum Code: Zuerst wird in bekannter Weise SqlConnection-Objekt erzeugt. Anschlieend wird ein Objekt vom Typ SqlDataAdapter erstellt. SqlDataAdapter da = new SqlDataAdapter(); string sCom = "SELECT * FROM tPhoto"; da.SelectCommand = new SqlCommand(sCom,con); Diese Klasse besitzt ein Member SelectCommand vom Typ SqlCommand. Diese wird initialisiert (mit einer SQL-Anweisung und einem SqlConnection-Objekt. Spter ist diese Member noch einmal Thema! Nun wird ein (leeres) DataSet-Objekt erzeugt und dann mithilfe des SqlDataAdapter-Objektes gefllt. DataSet ds = new DataSet(); da.Fill(ds,"Photos"); Der Name Photos in der Methode Fill ist der Name der Tabelle innerhalb des DataSet-Objektes, und hier beliebig gewhlt und hat nichts mit dem Namen der Tabelle in der Datenbank zu tun. Innerhalb der Methode Fill wird die Datenbankverbindung hergestellt und dann auch wieder getrennt. Ein DataSetObjekt arbeitet also unabhngig von einer Datenbankverbindung! Im Beispiel wird nun mit dem Objekt ds vom Typ DataSet gearbeitet. In der folgenden Abbildung ist der grundstzliche Aufbau eines DataSet-Objektes dargestellt.

Interne Struktur eines DataSets Abb. 8.5

ADO.NET
8
Sie sehen, das DataSet kann aus mehreren Tabellen bestehen. Diese Tabellen werden im Member Tables der Klasse DataSet gehalten. Die einzelnen Tabellen haben den Typ DataTables. Ein Objekt vom Typ DataTables besitzt im Member Columns eine Collection aus DataColumn-Objekten (Spalten) und im Member Rows eine Collection aus DataRow-Objekten(Reihen). Im Codebeispiel wird sich im DataSet genau eine Tabelle befinden (mit dem Namen Photos). Diese besitzt vier Spalten (entsprechend der SQL-Abfrage) mit den Namen ID, Title, Date und Filename. ber die Objekte vom Typ DataRow knnen Sie nun auf die einzelnen Werte zugreifen. Da die Datenbankverbindung schon geschlossen wurde, sind Sie nun vollkommen unabhngig von einer Verbindung zur Datenbank.

303

Manipulation von Daten innerhalb eines DataSets


Im DataSet knnen Sie nun Daten ndern, Eintrge lschen oder aber neue Eintrge hinzufgen. Manipulationen finden natrlich nur im DataSet statt und nicht in der Datenbank! Auch hier haben Sie die vier grundstzliche Mglichkeiten zur Manipulation der Tabelle: Daten in die Tabelle eintragen Eintrag in der Tabelle lschen Updaten eines Eintrages Eintrge auslesen using using using using System; System.Data; System.Data.SqlClient; System.Collections;
CD-Beispiel PhotoAccess6

class App { public static void Display(DataSet ds) { for(int i=0;i<ds.Tables[0].Rows.Count;i++) { DataRow r = ds.Tables[0].Rows[i]; if(r.RowState!= DataRowState.Deleted) Console.WriteLine("{0} {1} {2} {3}",

C#
304

8
r[0], r[1],r[2],r[3]); } Console.WriteLine(); } public static void Main() { string DSN = @"server=esche;uid=sa;pwd=;database=PhotoPool;"; SqlConnection con = new SqlConnection(DSN); con.Open(); //DataAdapter ertellen SqlDataAdapter da = new SqlDataAdapter(); string sCom = "SELECT * FROM tPhoto"; da.SelectCommand = new SqlCommand(sCom,con); //DataSet erstellen und fllen DataSet ds = new DataSet(); da.Fill(ds,"Photos"); DataTable t = ds.Tables[0]; //Alle Eintrge darstellen lassen Display(ds); //Eintrag hinzufgen DateTime dat = new DateTime(2001,7,5); t.Rows.Add(new object [] {99,"Urlaub Elba",dat,"Image99.gif"}); t.Rows.Add(new object [] {100,"Ankunft Elba",dat,"Image100.gif"}); t.Rows.Add(new object [] {101,"Hotel Elba",dat,"Image101.gif"}); t.Rows.Add(new object [] {102,"Baden Elba",dat,"Image102.gif"}); Display(ds); //Datum des 1. Eintrages ndern DataRow r = t.Rows[0]; r[2] = new DateTime(2001,7,10); Display(ds);

ADO.NET
8
//Eintrag lschen t.Rows[1].Delete(); Display(ds); } } Hier wurde eine statische Methode Display eingefhrt, die smtliche Inhalte des DataSets ausgibt. Damit lassen sich die Manipulationen im DataSet dann auch gleich darstellen. Zuerst wird das DataSet in bekannter Weise mit dem Abfrageergebnis gefllt und dann wird die Datenbankverbindung geschlossen. Nachfolgend sehen Sie die Erklrungen der einzelnen Manipulationen.

305

Eintrag hinzufgen
DataTable t = ds.Tables[0]; DateTime dat = new DateTime(2001,7,5); t.Rows.Add(new object [] {99,"Urlaub Elba",dat,"Image99.gif"}); ber die Methode Add der Members Rows (in der Klasse DataTable) knnen jederzeit Eintrge hinzugefgt werden. Beachten Sie, dass Add ein Feld aus objects verlangt. Fr die Typsicherheit sind Sie als Programmierer verantwortlich!

Eintrag manipulieren
DataRow r = t.Rows[0]; r[2] = new DateTime(2001,7,10); In diesem Beispiel ndern Sie den Wert Date im ersten Eintrag. Auch hier sind Sie wieder fr die Typsicherheit verantwortlich.

Eintrag lschen
t.Rows[0].Delete(); Hier wrden Sie den ersten Eintrag (1. Reihe) lschen. Wenn Sie einen DataRow-Eintrag ber die Methode Delete lschen, wird eigentlich nur der Eintrag als gelscht markiert. Sie knnen den Zustand eines DataRows ber das Property RowState jederzeit abfragen. Im Beispiel wurde das in der Methode Display() durchgefhrt.

C#
306

8
public static void Display(DataSet ds) { for(int i=0;i<ds.Tables[0].Rows.Count;i++) { DataRow r = ds.Tables[0].Rows[i]; if(r.RowState!= DataRowState.Deleted) Console.WriteLine("{0} {1} {2} {3}", r[0], r[1],r[2],r[3]); } Console.WriteLine(); } } Ein DataRow kann folgende Enumerationswerte annehmen: DataRowState.Added DateRowState.Deleted DataRowState.Modified DataRowState.Unchanged Warum? Es wre doch sehr elegant, wenn Sie die Datenbankverbindung wieder aufnehmen knnten und smtliche genderte Daten im DataSet in der Datenbank nachgezogen wrden. Dss ist auch mglich. Dafr ist es aber notwendig, dass der SqlDataAdapter die Unterschiede kennt, die seit der letzten Verbindung zur Datenbank aufgetreten sind. Um die nderungen in der Datenbank durchfhren zu knnen, werden nmlich genau diese Zustandsdaten bentigt! Aber bevor Sie sich nher mit diesem Aspekt beschftigen, wird Ihnen noch ein weiteres, beraus interessantes Feature der Klasse DataSet vorgestellt.

DataSet und XML


Die Klasse DataSet implementiert die Methoden WriteXml und ReadXml. Sie knnen den Inhalt eines DataSets in einer XMLDatei abspeichern! Ja noch mehr, Sie knnen ein DataSet auch von einer XML-Datei fllen lassen!
CD-Beispiel PhotoAccess7

using using using using

System; System.Data; System.Data.SqlClient; System.Collections;

ADO.NET
8
307

class App { public static void Main() { string DSN = @"server=esche;uid=sa;pwd=;database=PhotoPool;"; SqlConnection con = new SqlConnection(DSN); //DataAdapter ertellen SqlDataAdapter da = new SqlDataAdapter(); string sCom = "SELECT * FROM tPhoto"; da.SelectCommand = new SqlCommand(sCom,con); //DataSet erstellen und fllen DataSet ds = new DataSet(); da.Fill(ds,"Photos"); DataTable t = ds.Tables[0]; //Eintrag hinzufgen DateTime dat = new DateTime(2001,7,5); t.Rows.Add(new object [] {99,"Urlaub Elba",dat,"Image99.gif"}); t.Rows.Add(new object [] {100,"Ankunft Elba",dat,"Image100.gif"}); //DataSet in der folgenden Datei abspeichern ds.WriteXml(@"c:\Fotos.xml"); } } Das DataSet wird im speziellen Fall in der Datei c:\Fotos.xml abgespeichert. <?xml version="1.0" standalone="yes"?> <NewDataSet> <Photos> <ID>1</ID> <Title> Urlaub Korsika </Title> <Date> 2001-08-03T00:00:00.0000000+02:00
Fotos.xml

C#
308

8
</Date> <Filename> Bild1.jpg </Filename> </Photos> <Photos> <ID>2</ID> <Title> Urlaub Elba </Title> <Date> 2000-07-02T00:00:00.0000000+02:00 </Date> <Filename> Image1.jpg </Filename> </Photos> ... ... </NewDataSet> Hier sehen Sie, wie Sie dieses DataSet ber die gerade erzeugte XML-Datei fllen knnen.
CD-Beispiel PhotoAccess8

class App { public static void Display(DataSet ds) { for(int i=0;i<ds.Tables[0].Rows.Count;i++) { DataRow r = ds.Tables[0].Rows[i]; if(r.RowState!= DataRowState.Deleted) Console.WriteLine("{0} {1} {2} {3}", r[0],r[1],r[2],r[3]); } Console.WriteLine(); } public static void Main() { //DataSet erstellen und fllen DataSet ds = new DataSet(); ds.ReadXml(@"c:\Fotos.xml");

ADO.NET
8
Display(ds); } } Erkennen Sie das Potenzial dieses Ansatzes? Statt einer Datenbank knnten Sie die Daten auch in einer XML-Datei persistent halten. Und dies ohne groen Aufwand. Vor allem fr Applikationen mit geringem Datenaufkommen eine beraus interessante Alternative. Oder aber Sie bieten in Ihrer Applikation beide Mglichkeiten an. Auch wenn unsichere Datenbankverbindungen bestehen (aufgrund unsicherer Verbindungen im Web), dann knnten Sie das DataSet als XML-Datei zwischenspeichern und bei Vorhandensein der Verbindung dann wieder ins DataSet laden und die Datenbank damit wieder aktualisieren.

309

Datenbank aktualisieren mit Daten eines DataSets


DataSets knnen ber DataAdapter-Objekte mit Daten von Datenbanken gefllt werden. Das haben Sie schon kennen gelernt. SqlDataAdapter da = new SqlDataAdapter(); string sCom = "SELECT * FROM tPhoto"; da.SelectCommand = new SqlCommand(sCom,con); Ein SqlDataAdapter-Objekt besitzt ein Member SelectCommand vom Typ SqlCommand. Hier kann die SQL-Anweisung und die Datenbankverbindung gesetzt werden, die der DataAdapter beim Belegen des DataSets (Fill) verwenden soll. Die Klasse SqlDataAdapter besitzt auch weitere Members fr das Einfgen, Lschen und Updaten. da.UpdateCommand = new SqlCommand(...); da.InsertCommand = new SqlCommand(...); da.DeleteCommand = new SqlCommand(...); Diese mssen in hnlicher Weise belegt werden, wie das Member SelectCommand, obgleich die SQL-Anweisungen und das Erzeugen der SqlCommand-Objektes deutlich komplizierter sind. Wenn Sie das geschafft haben, knnen Sie eine Datenbank ber das DataSet-Objekt auch aktualisieren. Sie mssen dann die Methode Update der Klasse SqlDataAdapter aufrufen.

C#
310

8
Diese verwendet die fr diese Ausfhrung notwendigen SqlCommands (fr Einfgen, Lschen und Updaten). Allerdings ist die Erstellung der SqlCommand-Objekte fr diese Members nicht trivial. Sie knnen aber den Designer dazu benutzen, um einen Groteil des Codes erzeugen zu lassen, wenn auch nur mit einigen Tricks. Legen Sie dazu ein neues (leeres) Projekt an, und fgen Sie eine Quellcodedatei hinzu. Der Designer wird im Entwicklungssystem nur angezeigt, wenn in der Datei eine Klasse abgeleitet von Form existiert. Sie brauchen nun zwar den Designer, nicht aber ein Fenster. Sie legen daher in der Datei eine Dummy-Klasse, abgleitet von Form an. using using using using using System; System.Data; System.Data.SqlClient; System.Collections; System.Windows.Forms;

class Wnd:Form { } Damit das auch funktioniert mssen Sie natrlich noch fr smtliche Assemblies Verweise einfhren. Wechseln Sie nun zum Designer und schieben Sie per Drag&Drop die Tabelle Photo im Server-Explorer in den Designer.

Connection und Adapter ber Designer erzeugen Abb. 8.6

ADO.NET
8
Wenn Sie nun einen Blick in den Code werfen, dann stellen Sie fest, dass der Designer eine Menge von Code erzeugt hat, nur knnen Sie diesen in dieser Form nicht brauchen! In der Klasse Wnd wurden vom Designer vier Elemente vom Typ SqlCommand angelgt (je eines fr Select, Delete, Update und Insert) sowie eine SqlConnection-Objekt und ein SqlDataAdapter-Objekt. ndern Sie zuerst den Namen der Klasse Wnd auf PhotoDataAdapterHelper. //class Wnd:Form class PhotoDataAdapterHelper { ... } Dann ndern Sie die Methode InitializeComponents auf //private void InitializeComponent() public SqlDataAdapter GetPhotoDataAdapter( SqlConnection con) { ... } An der Stelle, wo das sqlConnection1.ConnectionString hart kodiert belegt wird, belegen Sie diesen mit dem ConnectionString des bergabeparameters der Methode con. // // sqlConnection1 // /* this.sqlConnection1.ConnectionString = "data source=ESCHE;initial catalog=PhotoPool;" + "integrated security=SSPI;persist secu" + "rity info=True;workstation " + "id=ESCHE;packet size=4096"; */ this.sqlConnection1.ConnectionString = con.ConnectionString; Nun lschen Sie noch den Form-spezifischen Code und geben ein SqlDataAdapter-Objekt zurck, so wie es die Methode fordert.
CD-Beispiel PhotoAccess9

311

C#
312

8
// // Wnd // //this.AutoScaleBaseSize = new // System.Drawing.Size(5, 13); //this.ClientSize = new // System.Drawing.Size(292, 273); //this.Name = "Wnd"; return this.sqlDataAdapter1; Sie haben nun eine Helper-Klasse erzeugt, die ber die Methode GetPhotoDataAdapter einen fix und fertig initialisierten DataAdapter erzeugt, der auch fr Updates von Datenbanken verwendet werden kann. public static void Main() { string DSN = @"server=esche;uid=sa;pwd=;database=PhotoPool;"; SqlConnection con = new SqlConnection(DSN); //DataAdapter erstellen PhotoDataAdapterHelper pah = new PhotoDataAdapterHelper(); SqlDataAdapter da = pah.GetPhotoDataAdapter(con); //DataSet erstellen und fllen DataSet ds = new DataSet(); da.Fill(ds,"Photos"); Display(ds); DataTable t = ds.Tables[0]; //Eintrag ndern DataRow r = t.Rows[0]; r[2] = new DateTime(2001,7,11); Display(ds); //Eintrag hinzufgen DateTime dat = new DateTime(2001,7,5); t.Rows.Add(new object [] {99,"Urlaub Elba",dat,"Image99.gif"}); t.Rows.Add(new object []

ADO.NET
8
{100,"Ankunft Elba",dat,"Image100.gif"}); //Datenbank updaten da.Update(ds,"Photos"); } Beim Aufruf da.Update verwendet der DataAdapter die entsprechend definierten SQL-Anweisungen!

313

DataGrid
Zum Abschluss wird Ihnen noch ein Steuerelement vorgestellt, das ganz eng mit der Klasse DataSet zusammenarbeitet auch wieder anhand eines Beispiels. Legen Sie in der Projektmappe ein neues Projekt (WindowsAnwendung) mit dem Namen PhotoDataGrid an. Den Dateinamen Form1.cs nennen Sie in bewhrter Weise um auf App.cs. Im Designer ndern Sie den Namen der Fensterklasse auf MainWnd um. Von der ToolBox bringen Sie nun per Drag&Drop ein Steuerelement DataGrid und geben diesem den Namen PhotoGrid. Auerdem fgen Sie der Form noch einen Button mit dem Namen btUpdate hinzu. Das Text-Property belegen Sie mit in Datenbank bernehmen. Im Quellcode fgen Sie der Klasse MainWnd zwei Member-Variablen hinzu: DataSet ds; SqlDataAdapter da; Im Konstruktor der Klasse MainWnd fgen Sie nun folgenden Code hinzu: public MainWnd() { InitializeComponent(); string DSN = @"server=esche;uid=sa;pwd=;database=PhotoPool;"; SqlConnection con = new SqlConnection(DSN); //DataAdapter erstellen PhotoDataAdapterHelper pah = new PhotoDataAdapterHelper(); da = pah.GetPhotoDataAdapter(con);

C#
314

//DataSet erstellen und fllen ds = new DataSet(); da.Fill(ds,"Photos"); this.PhotosGrid.SetDataBinding(ds,"Photos"); } Nach dem Initialisieren der Steuerelemente erzeugen Sie einen SqlDataAdapter, fllen damit das DataSet-Objekt und binden dieses DataSet-Objekt an das DataGrid-Objekt an. this.PhotosGrid.SetDataBinding(ds,"Photos"); Da Sie in diesem Beispieil die PhotoDataAdapterHelper-Klasse aus dem vorherigen Beispiel verwenden, mssen Sie diese auch herkopieren. Geben Sie auch noch den Namensraum System.Data.SqlClient frei.

DataGrid in Aktion Abb. 8.7

Wenn sich das Projekt nun fehlerfrei kompilieren lsst, dann werden Ihnen die Daten der Tabelle im DataGrid dargestellt. Sie knnen im DataGrid sogar editieren, lschen und Eintrge hinzufgen! Was noch fehlt ist die Implementierung des Updatens beim Button btUpdate. Das gestaltet sich sehr einfach.

ADO.NET
8
private void btUpdate_Click(object sender, System.EventArgs e) { da.Update(ds,"Photos"); }

315

Zusammenfassung
In diesem Kapitel haben Sie nun einen berblick ber die Mchtigkeit der ADO.NET-Klassen kennen gelernt. Nicht behandelt wurden die Klassen im Namensraum System.Data.OleDb. Diese sind aber analog zu verwenden. Mit ADO.NET bietet Microsoft den Entwicklern extrem leistungsfhige Klassen an. Die Produktivitt sollte damit deutlich gesteigert werden. In den nchsten Kapiteln werden Sie noch einige Male auf ADO.NET stoen.

ASP.NET

Einleitung ASP-Active Server Pages Von ASP zu ASP.NET ASP.NET-Anwendungen mit VS Studio entwickeln Web-Steuerelemente Kundenspezifische Steuerelemente Composite Controls Statusverwaltung unter ASP.NET Zusammenfassung

318 319 325 333 341 348 358 360 372

C#
318

Einleitung
Ursprnglich war die Idee des Webs, u.a. Informationen in Form von Dateien einem greren Publikum bereitzustellen. Um diese Dateien herunterzuladen, wurde ein eigenes Protokoll entwickelt (http). Diese Information ist in der Datei in Form der Sprache HTML kodiert. Spezielle Browser knnen dann diese Information in einer fr den Menschen lesbaren Form darstellen. Die Dateien, die diesen HTML-Stream beschreiben, sind natrlich statisch. Bald schon kam Idee auf, diesen HTML-Stream dynamisch zu erzeugen. Die Web-Server-Hersteller boten dann den WebEntwicklern Programmierschnittstellen zu ihren Servern an. Microsofts Web-Server heit Internet Information Server (IIS). Die Programmierschnittstelle und das Programmiermodell fr den IIS heit ASP (Active Server Page). ASP stellt somit die Schnittstelle des Windows-Betriebssystems zum World-WideWeb dar. Fr die Erzeugung von HTML-Streams kann der IIS auf smtliche Ressourcen des Betriebssystems und des Netzwerks zugreifen. ASP basiert auf COM und erlaubt damit die Entwicklung von leistungsfhigen Web-Applikationen. Obwohl sehr leistungsfhig, haben sich im Laufe der Zeit aber auch die Grenzen des Programmiermodells gezeigt. Mit ASP.NET wurde das Programmiermodell gnzlich berarbeitet und ist praktisch nicht mehr mit dem klassischen Modell vergleichbar. Lassen Sie sich in diesem Kapitel berraschen!
Tipp

Auf Ihrem Entwicklungsrechner mssen Sie natrlich auch den IIS installiert haben. Wichtig ist, dass Sie diesen vor dem .NETFramework installieren, da ansonsten der IIS nicht die Features der .NET-Laufzeitumgebung verwenden kann. Mssen Sie den IIS erst noch installieren, dann deinstallieren Sie das .NET-Framework (und nur dieses, Visual Studio.NET mssen Sie nicht deinstallieren), installieren anschlieend den IIS und dann wieder das .NET-Framework!

ASP.NET
9
319

ASP-Active Server Pages


Vorerst wird aber das gute alte ASP betrachtet und diskutiert. Dazu legen Sie unter c:\inetpub\wwwroot einen Ordner mit dem Namen AspOld an. In diesen Ordner kopieren Sie eine Datei mit dem Namen Sphere.asp (oder deutsch: Kugel.asp) mit folgendem Inhalt: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <HTML> <HEAD> <h1>Kugelmathematik</h1> </HEAD> <BODY> <form method="post" action="Sphere.asp"> Radius: <input type="text" name="Radius" value=> <input type="submit" value="Berechnen"> <br> Berechnung am <%=Now%> </form>
CD-Beispiel AspOld1

<script language="VBScript" runat="server"> Dim r r = Request("Radius") If Len(r)>0 Then Dim V V=4.0*r*r*r*3.14/3.0 Dim A A=4.0*r*r*3.14 Response.Write("<h2>Volumen</h2> = " + _ FormatNumber(V)) Response.Write("<h2>Flche</h2> = " + _ FormatNumber(A)) End If </script> </BODY> </HTML>

C#
320

9
Wenn Sie dann mit einem Web-Browser (z.B. Internet Explorer) diese Seite aktivieren, (http://localhost/aspold/Spere.asp) sollten Sie folgendes oder ein hnliches Bild erhalten.

Sphere.asp in Aktion Abb. 9.1

Das Volumen und die Oberflche einer Kugel wird berechnet, wenn Sie in das Input-Feld den Radius eingeben und den Button Berechnen bettigen. Auerdem wird auch Datum und Zeit ausgegeben. Wenn Sie fr den Radius 4 eingeben und eine Berechnung durchfhren, erscheint im Internet Explorer folgendes Bild:

Sphere.asp hat berechnet Abb. 9.2

ASP.NET
9
In diesem kleinen typischen Beispiel sehen Sie die wichtigsten Prinzipien einer ASP-Seite. Eine ASP-Seite ist aufgebaut wie eine HTML-Seite, nur dass die Seite Elemente besitzt, die vom IIS (Internet Information Server) dynamisch interpretiert werden. Die Interpretation dieser Elemente ergibt HTML-Streams, die dann in den Text eingefgt werden. Da bei jeder Abfrage der Seite diese Erzeugung der Elemente stattfindet, sind diese HTML-Teile dynamisch. Wenn Sie im Internet Explorer den Menpunkt Ansicht > Quelltext anzeigen aktivieren, wird der HTML-Stream, den der WebBrowser sieht, dargestellt. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML> <HEAD> <h1>Kugelmathematik</h1> </HEAD> <BODY> <form method="post" action="Sphere.asp"> Radius: <input type="text" name="Radius" value=> <input type="submit" value="Berechnen"> <br> Berechnung am 12.03.2002 07:57:38 </form>
HTML Quellcode der Ansicht

321

</BODY> </HTML> <h2>Volumen</h2> = 267,95<h2>Flche</h2> = 200,96 Sie sehen hier deutlich: Der IIS (Internet Information Server) hat einige Elemente in der Seite dynamisch erstellt. Es gibt mehrere Mglichkeiten, Texte in einer ASP-Seite dynamisch zu gestalten. Die einfachste Mglichkeit liegt in der Verwendung des <% = %>-Elements. Der Wert nach dem Gleichheitszeichen wird in der HTML-Seite erscheinen. Dieses Element kann innerhalb des HTML-Textes jederzeit verwendet werden. Im Beispiel wird dieses Element verwendet, um Datum und Zeit zu errechnen und dynamisch in den HTML-Text einzublenden. Eine wei-

C#
322

9
tere Mglichkeit ergibt sich in der Verwendung der Methode Write des ASP-Objektes Response. Diese Methode kann innerhalb eines <% %>-Elements oder in einem Script-Block verwendet werden. Scripts sind Abschnitte in einer ASP-Seite, die mit dem Tag <script> eingeleitet werden. Hier wei der IIS, dass dieses Script durchgefhrt werden muss, um HTML-Stream zu erzeugen. Der HTML-Stream wird an der Stelle, wo sich das Script befindet, eingesetzt. ASP untersttzt die Scripting-Sprachen VBScript und JScript. Da diese Scripting-Sprachen auch auf die COM/COM+Laufzeitumgebung zugreifen knnen, ist es mglich, ASP-Seiten sehr leistungsfhig zu gestalten. Kompliziertere Funktionalitten, wie Berechnungen, knnen so auf COM-Komponenten ausgelagert werden, welche mit einer beliebigen Sprache entwickelt werden knnen. Sie knnen eine Web-Seite grundstzlich auf zwei Arten anfordern: http-GET http-POST Die POST-Anforderung unterscheidet sich von der GET-Anforderung darin, dass die Belegungen von Input-Steuerelementen mit bertragen werden. Bei einer GET-Anforderung ist das nicht der Fall. Nun aber zurck zum Beispiel: <form method="post" action="Sphere.asp"> Radius: <input type="text" name="Radius" value=> <input type="submit" value="Berechnen"> <br> Berechnung am <%=Now%> </form> Diese Zeilen definieren HTML-Steuerelemente in der Web-Seite. Konkret sind eine Eingabebox (type=text) und ein Button (type=submit) definiert. Beim Button wird auch das Attribut value belegt, das dem Inhalt, in diesem Fall der Beschriftung des Steuerelements entspricht. Das Attribut value ist beim Input-Steuerelement Radius nicht belegt, und das Steuerelement ist somit beim Start der Seite leer. Bei der Bettigung

ASP.NET
9
eines Steuerelements mit dem Attribut type="submit wird immer eine POST-Anforderung gettigt, d.h. es werden die Werte smtlicher Input-Steuerelemente in der Anforderung mit bertragen. Bei einem Submit wird diejenige Seite vom Server angefordert, die im Tag <form> unter dem Attribut action angegeben wird. Da im Beispiel unter dem Attribut action auf die eigene Seite verwiesen wird, kommt auch dieselbe Seite wieder zurck und vermittelt dem Anwender, dass sich die Seite gar nicht gendert hat. <script language="VBScript" runat="server"> Dim r r = Request("Radius") If Len(r)>0 Then Dim V V=4.0*r*r*r*3.14/3.0 Dim A A=4.0*r*r*3.14 Response.Write("<h2>Volumen</h2> = " + FormatNumber(V)) Response.Write("<h2>Flche</h2> = " + FormatNumber(A)) End If </script> Nun wird ein Script aktiv, das den Wert des Eingabefeldes Radius verarbeitet. In einer ASP-Seite kann ber das ASP-Objekt Request unter Angabe des Namens des Input-Steuerelements der Wert erfahren werden. Das sind die Werte, die per http-POST vom Browser mitkommen. Im Beispiel-Script wird zuerst getestet, ob das Input-Steuerelement eventuell leer ist (Len(r)>0). Wenn nicht, dann werden die mathematischen Operationen durchgefhrt und die Ergebnisse ber das Response-Objekt in den HTML-Stream eingefgt. Eines fllt an diesem Beispiel allerdings unangenehm auf. Der Wert des Eingabefeldes Radius ist nach dem http-GET Aufruf wieder leer. Warum ist dies so? Fr den Internet Information Server ist jede Anforderung vollkommen neu. Das Web ist zustandslos. Wenn Sie aber dennoch mchten, dass der Wert der vorherigen Seite dargestellt wird, dann mssen Sie einen kleinen Trick anwenden. Sie mssen das Attribut value des InputFeldes Radius dynamisch belegen.

323

C#
324

9
Dies kann wie folgt geschehen:
CD-Beispiel ASPOLD2

<input type="text" name="Radius" value="<%=(Request("Radius")%>"> Im Fall einer http-POST Anfrage haben Sie ja den Wert der Input-Steuerelemente und damit auch des Steuerelementes Radius im Request-Objekt zur Verfgung. Diesen Wert verwenden Sie zur Belegung des Attributs value des Steuerelements mit dem Namen Radius. Es sollte Ihnen aber klar sein, dass damit nur scheinbar ein Status zwischen den Anforderungen gehalten wird. Smtliche notwendigen Daten mssen vom Browser in der http-Anfrage mitgeliefert werden.

Kugelmathematik Abb. 9.3

Diskussion
Mit Scripting-Techniken wurden gnzlich neue Funktionalitten im Web ermglicht. Das Konzept ist bewhrt und schon lnger im Einsatz. Mit der Erfahrung haben sich aber auch einige Schwierigkeiten und Unzulnglichkeiten herauskristallisiert, die nun mit ASP.NET entschrft wurden. Vier Aspekte werden kurz vorgestellt.

ASP.NET
9
Typisierung Scripting-Sprachen verwenden keine strenge Typisierung wie z.B. C++ oder auch VB in gewissem Mae. Dies ist zwar u.U. angenehm, stellt aber ein nicht unbedeutendes PerformanceProblem und auch Fehlerquelle dar. ASP.NET kennt streng typisierte Datentypen. Interpreter ASP verwendet keinen kompilierten Code, sondern interpretiert die Scripting-Anweisungen. Durch die Verlagerung von umfangreichen Operationen auf COM-Komponenten knnen hier Verbesserungen erzielt werden. Die Installierung von COM-Komponenten ist allerdings fr viele meist eine Herausforderung und es kam hier immer wieder zu Irritationen. ASP.NET kennt kompilierten Code und ist daher bedeutend performanter. Trennung zwischen Sicht und Code ASP-Seiten kennen die Trennung zwischen Ansicht und Funktion nur bedingt. HTML und Scripting-Code findet sich vielfach auf derselben Seite. Dies erschwert die Wiederverwendung von Codeteilen. Unter ASP.NET werden Sie sehen, dass smtlicher funktionaler Code vom HTML-Code getrennt werden kann. Dies vereinfacht die Wartbarkeit von Web- Applikationen deutlich. Objektorientierung VBScript und damit auch das ASP-Programmiermodell sind nicht objektorientiert. Damit wird die Entwicklung und Pflege von komplexen Web-Anwendungen erschwert. Unter ASP.NET wird die objektorientierte Entwicklung von Web-Applikationen mglich. Im nchsten Kapitel wird genau dieses Beispiel in ASP.NET realisiert. Sie werden dann die Unterschiede deutlich erkennen.

325

Von ASP zu ASP.NET


Das ASP-Eingangsbeispiel soll auf das Programmiermodell ASP.NET portiert werden. Hierzu erzeugen Sie unter c:\inetpub\wwwroot einen neuen Ordner mit dem Namen ASPNET, wo Sie die Seiten unterbringen werden. Dann legen Sie mit ei-

C#
326

9
nem beliebigen Editor eine Datei mit dem Namen Shere.aspx an. An der Dateierweiterung .aspx erkennt der IIS, dass es sich um eine ASP.NET-Datei handelt.

Web-Steuerelemente
CD-Beispiel ASPNET1

<HTML> <HEAD> </HEAD> <BODY> <form runat="server"> <h1>KugelMathematik</h1> Radius: <asp:textbox id="Radius" runat="Server" /> <asp:button Text="Berechnen" OnClick="OnQuadrat" runat="Server" /><br> <%=DateTime.Now.ToString()%><br><h1> <asp:Label id="Volumen" runat="Server" /><br> <asp:Label id="Flche" runat="Server" /> </form> <script language="C#" runat="server"> void OnQuadrat(Object sender,EventArgs e) { string sr = Radius.Text; double r = Double.Parse(sr); double V = 4*r*r*r*3.14/3; double A = 4*r*r*3.14; Volumen.Text= "Volumen = " + V.ToString(); Flche.Text = "Flche =" + A.ToString(); } </script> </BODY> </HTML> Was ist hier neu? Die Dateierweiterung .aspx ist fr IIS die Information, dass es sich um eine ASP.NET-Seite handelt. ASP.NET-Elemente drfen in <%= %>-Elementen jederzeit C#Aufrufe beinhalten. Wichtig ist nur, dass das Assembly mit den

ASP.NET
9
verwendeten Typen sich im selben Ordner befindet oder aber, wie in diesem Fall, es sich um ein Shared Assemby handelt. <%=DateTime.Now.ToString()%> Es werden nicht mehr die HTML-Steuerelemente verwendet, sondern so genannte serverseitige Web-Steuerelemente. Diese werden mit dem Namensraum asp: eingeleitet. Das InputElement Radius aus dem ersten Beispiel wird nun ersetzt durch ein WebForm-Control, im Speziellen durch ein TextBox-Steuerelement. Die Schaltflche wird durch ein Button-WebFormControl ersetzt. Das Attribut RunAt=Server bedeutet, dass die Steuerelemente auf dem Server interpretiert werden. Das heit, dass ASP.NET hier letztendlich HTML-Code erzeugt, der dann zum Client geschickt wird. Im Script, das nun C#-Code darstellt, kann auf das Steuerelement mit dem Namen, der im Attribut id angegeben ist, zugegriffen werden. Das Auslesen aus dem Steuerelement erfolgt nicht mehr durch das ASP-Objekt Request, sondern mit Anweisungen wie: string sr = Radius.Text; Im Script-Code kann in einer objektorientierten Weise auf das Web-Steuerelement zugegriffen werden. Ebenfalls nicht verwendet wird das ASP-Objekt Response, sondern es wird direkt das Text-Property des Web-Steuerelements Label belegt. Volumen.Text= V.ToString(); Auch hier gilt wieder, dass der Ausdruck Volumen durch die id des Labeletiketts bei der Definition begrndet ist. <asp:Label id="Volumen" runat="Server" /> Es ist Ihnen sicherlich auch aufgefallen, dass sich die Seite die Zustnde ber die Rckmeldungen hinweg gemerkt hat. All die lstige Arbeit bernehmen die Web-Controls. Weiter stellen Sie fest, dass das Tag <form> keine weiteren Attribute besitzt, insbesondere kein active-Attribut und kein method-Attribut. Wenn Sie aber im Browser den HTML-Code betrachten, dann sehen Sie sehr wohl, dass diese Attribute im HTML-Stream letztendlich vorkommen.

327

C#
328

9
Eine wichtige Feststellung ist auch, dass smtliche Arbeit, die mit der Anzeige der UI-Schnittstelle zu tun hat, von den WebSteuerelementen abgedeckt ist (z.B. Zustnde der Steuerelemente erhalten). Die Ereignisse der Steuerelemente werden in Funktionen abstrahiert. Die Denkweise der Rckmeldungen wird mit dem ASPModell weg abstrahiert. Fr den Entwickler entsteht also ein Modell, das mit herkmmlichen Programmiermodellen vergleichbar ist, und daher natrlich auch strukturiertes Entwickeln besser untersttzt. Noch nicht gelst ist die Trennung zwischen Sicht und Funktion.

Trennung zwischen Sicht und Funktion


Im nchsten Beispiel trennen Sie die Funktionalitt von der Sicht. ndern Sie den Code in der Datei Sphere.aspx wie folgt:
CD-Beispiel ASPNET2

<%@Page Inherits="SphereMath" Src="Sphere.cs" %> <HTML> <HEAD> </HEAD> <BODY> <form runat="server"> <h1> <asp:Label id="lMainText" runat="server" /> </h1><br> <asp:Label id="lRadius" runat="server"/> <asp:Textbox id="tbRadius" runat="Server" /> <asp:Button id="btCompute" OnClick="OnCompute" runat="Server" /><br> <asp:Label id="lDatum" runat="server" /><h1> <asp:Label id="lVolumen" runat="Server" /><br> <asp:Label id="lFlche" runat="Server" /> </form> </BODY> </HTML> Neu ist das einleitende Direktive <%@Page..., auf das gleich eingegangen wird. Ansonsten finden sich im Code nur noch die Definitionen von Web-Controls. Auch sind smtlichen Ausgabeelementen Steuerelemente vom Typ Label zugeordnet. Alle Controls erhalten ber das Attribut id einen Namen. Eben-

ASP.NET
9
falls ist in allen Steuerelementen das Attribut runat="server gesetzt. Nun aber noch einmal auf das einleitende Direktive. <%@Page Inherits="SphereMath" Src="Sphere.cs" %> Diese Direktive bedeutet, dass diese Seite (Page) ihre Eigenschaften von einer Klasse SphereMath erbt (Inherits), was auch immer das bedeuten soll. Mit Src="Sphere.cs wird angegeben, wo diese Klasse definiert ist. In diesem Fall in der Datei Sphere.cs. Legen Sie daher im Ordner zustzlich mit einem beliebigen Editor eine Datei Sphere.cs an, und geben Sie folgenden C#Code ein. using using using using System; System.Web; System.Web.UI; System.Web.UI.WebControls;

329

public class SphereMath: Page { protected Label lMainText; protected Label lRadius; protected protected protected protected protected TextBox tbRadius; Button btCompute; Label lDatum; Label lVolumen; Label lFlche;

public void Page_Load(Object sender,EventArgs e) { lMainText.Text = "KugelMathematik"; lDatum.Text = DateTime.Now.ToString(); btCompute.Text = "Berechnen"; } public void OnCompute(Object sender,EventArgs e) { string sr = tbRadius.Text; double r = Double.Parse(sr); double V = 4*r*r*r*3.14/3; double A = 4*r*r*3.14; lVolumen.Text= "Volumen = " + V.ToString();

C#
330

9
lFlche.Text = "Flche =" + A.ToString(); } } In dieser Datei wird eine Klasse SphereMath definiert. Diese Klasse ist abgeleitet von der Klasse Page, die im Namensraum System.Web.UI definiert ist. Die Klasse Page reprsentiert eine Web-Seite. In der Klasse SphereMath sind nun Member-Variablen vom Typ Label, TextBox und Button vereinbart. Diese Typen sind im Namensraum System.Web.UI.WebControls zu finden. (Verwechseln Sie diese Steuerelemente nicht mit denen im Namensraum System.Windows.Forms!) Die Namen dieser Members korrespondieren mit den Namen der Elemente, die in der .aspx-Seite definiert wurden. Die Methode Page_Load wird, wie Sie richtig vermuten, beim Laden der Seite aufgerufen. In dieser Methode knnen Sie nun smtliche Steuerelement-Objekte initialisieren, indem Sie auf die Eigenschaften der Klassen zugreifen. Sie sehen, Sie knnen nun die Seite als ein Objekt mit Steuerelementen betrachten! Die C#-Quellcode-Datei, die mit er .aspx-Seite korrespondiert wird code-behind genannt. ffnen Sie nun die Seite SphereMath.aspx mit dem Browser! Eventuelle syntaktische Fehler werden im Browser dargestellt. Eine Unschnheit ist im Code noch vorhanden. In der .aspx-Seite ist ein Verweis auf die Methode OnCompute vorhanden. <asp:Button id="btCompute" OnClick="OnCompute" runat="Server" /> Enfernen Sie das Attribut OnClick in der aspx-Seite! In der Datei Sphere.cs fgen Sie nun dem Event Click die Bearbeitungsroutine hinzu. Sie sehen, es kann hier der bekannte EventMachanismus verwendet werden.
CD-Beispiel ASPNET3

public void Page_Load(Object sender,EventArgs e) { lMainText.Text = "KugelMathematik"; lDatum.Text = DateTime.Now.ToString(); btCompute.Text = "Berechnen"; btCompute.Click += new EventHandler(OnCompute); }

ASP.NET
9 Verwendung von kompiliertem Code
Beim erstmaligen Abrufen einer .aspx-Seite wird der IIS feststellen, dass der zu dieser Seite zugehrige Quellcode noch kompiliert werden muss. ASP.NET kompiliert den Quellcode und cached diesen. (ASP.NET erzeugt intern sogar noch eine eigene Klasse und verwendet die Klasse im Quellcode als Basisklasse!) Der Kompiliervorgang kann dauern (bei komplexen Seiten auch einige Sekunden), aber das findet nur beim ersten Aufruf statt. Bei allen weiteren Aufrufen wird dann die gecachte Version verwendet. Sie sehen also, Sie knnen gnzlich ohne Entwicklungssystem, nur allein mit einem Texteditor komplexe Web-Applikationen erstellen. Diesen anfnglichen Kompiliervorgang knnen Sie auch vermeiden, wenn Sie den code-behind explizit zu einem Assembly kompilieren und dieses dann in den Web-Ordner unterbringen. Sie mssen dabei allerdings einiges bercksichtigen. Im nchsten Beispiel wird die Web-Applikation Sphere in kompilierter Form untergebracht. Erzeugen Sie erst einmal aus Spere.cs ein Assembly. Dazu starten Sie den Visual Studio .NET Comman Prompt, wechseln in den Ordner der Applikation (/inetpub/wwwroot/aspnet) und geben folgendes Kommando ein: csc /t:library sphere.cs Im Ordner der Applikation (/inetpub/wwwroot/aspnet) richten Sie einen Unterordner /bin ein und kopieren in dieses das aus dem Kompiliervorgang entstandene Assembly sphere.dll. Nun fehlt noch eine kleine nderung in der Datei Sphere.aspx. Die Page-Direktive beschrnkt sich nun auf die Angabe der Klasse. <%@ Page Inherits="Sphere" %> ASP.NET sucht sich dann die Klasse in den Assemblies, die im Unterordner /bin zu finden sind. Leider funktioniert die Anwendung aber noch nicht, es wird eine Fehlermeldung auftreten. Sie mssen nmlich dem Ordner der Applikation einen so genannten Anwendungsnamen geben. Dazu ffnen Sie den Internet Informationsdieste Manager ber Start > Einstellungen > Systemsteuerung > Verwaltung.

331

C#
332

9
Im Navigationsbaum StandardWebsite finden Sie den Ordner der Applikation ASPNET. ffnen Sie fr diesen Ordner ber das Kontextmen den Eigenschaftsdialog. Erzeugen Sie dann mit dem Button Erstellen einen Anwendungsnamen und lschen Sie die Schreib- und Lesezugriffsberechtigungen.

ASPNET-Eigenschaften Abb. 9.4

Nun sollte es funktionieren!

Zusammenfassung
Das Web-Programmiermodell ASP.NET unterscheidet sich vollkommen vom herkmmlichen ASP-Programmiermodell. Ein Web-Seite unter ASP.NET besteht im Allgemeinen aus zwei Dateien, nmlich aus einer .aspx-Datei und einer Code-Datei (oder einer kompilierten Form). Diese beiden Dateien korrespondieren zueinander. Die .aspx-Datei enthlt vorteilhaft ausschlielich HTML-Eintrge und asp-Steuerelementeintrge. Die zugehrige Code-Datei definiert mindestens eine Klasse, die von Page abgeleitet ist. Diese Klasse reprsentiert die Web-Seite. Smtliche Steuer-

ASP.NET
9
elemente, die in der .aspx-Seite eingetragen sind, kommen in der Klasse als Member-Variablen vor. In der .aspx-Datei wird die Verbindung zur Code-Datei in Form einer Direktive (Page) hergestellt. Bevor Sie nun im Kapitel weitermachen, stellen Sie sicher, dass Sie die Zusammenhnge zwischen .aspx-Datei, .cs-Datei und Assembly .dll verstanden haben.

333

ASP.NET-Anwendungen mit VS Studio entwickeln


Ganz bewusst wurde das erste Beispiel ohne Verwendung des Entwicklungssystems erstellt: So sind die Zusammenhnge klarer erkennbar. In diesem Kapitel werden Sie kennen lernen, wie Sie Visual Studio.NET verwenden knnen, um effizient Web-Anwendungen zu produzieren. Erzeugen Sie zuerst eine neue Projektmappe mit dem Namen ASP. In dieser Projektmappe legen Sie ein C#-ASP.NET-Projekt an.

ASP.NET-Webanwendung erzeugen Abb. 9.5

Nennen Sie das Projekt MathServices. Es wird Ihnen auffallen, dass Sie als Ziel einen Webserver angeben (default localhost).

C#
334

9
Sie werden feststellen, dass der Wizard unter c:\inetpub\wwwroot einen neuen Ordner MathServices angelegt hat. In diesem Ordner befinden sich nun die Dateien zu diesem Projekt. Im personalisierten Ordner (Dokumente und /Einstellunge/UserXXX/\VSWebCashe) wird brigens das gesamte Projekt gecached. Werfen Sie mit dem Datei-Explorer einen Blick in den Ordner MathServices (dieser befindet sich im c:\inetpub\wwwroot).

Dateien eines Projektes vom Typ .NET-WebAnwendung Abb. 9.6

Neben den Projektdateien finden Sie hier die Datei WebForm1 .aspx mit der zugehrigen (code-behind) Datei WebForm1. aspx.cs. Auerdem hat der Assistent auch noch eine .resx-Datei fr diese Seite angelegt. Sie knnen unter ASP.NET Ressourcen in gleicher Weise verwenden, wie Sie das bei der WindowsProgrammierung kennen gelernt haben. Ebenfalls wurde vom Assistenten eine Datei Global.asax mit Global.asax.cs inklusiv einer .resx-Datei angelegt. Die Datei mit dem Namen Global.asax ist analog zur Datei Global.asa unter ASP zu verstehen. Dazu aber spter mehr im Detail. Die Datei Web.Config stellt die XML-Konfigurationsdatei bei ASP.NET-Anwendungen dar. Diese wurden schon im Kapitel 7: Konfigurationsdateien erwhnt. Kehren Sie nun zum Entwicklungssystem zurck. ndern Sie im Projektmappen-Explorer erst einmal den Namen der Datei WebForms1.aspx mit dem Kontextmen Umbenennen auf Sphere.aspx.

ASP.NET
9
Wenn Sie einen Doppelklick im Projektmappen-Explorer auf die Datei Sphere.aspx ttigen, dann stellt sich diese Seite im Designer dar (vgl. Designer bei Windows-Projekten). Links unten befindet sich ein Umschaltbutton auf die HTML-Sicht, die dann den Code der ASP-Seite darstellt. Wenn Sie im Projektmappen-Explorer die Seite Sphere.aspx markieren, knnen Sie mit der rechten Maustaste im Kontextmen Code anzeigen zur zugehrigen code-behind, in diesem Fall eine C#-Quelldatei Sphere.aspx.cs navigieren (Sie knnen auch ber die Werkzeugleiste des Projektmappen-Explorers die Sichten umschalten). Stellen Sie nun die code-behind Datei Sphere.aspx.cs dar! using using using using using using using using using using System; System.Collections; System.ComponentModel; System.Data; System.Drawing; System.Web; System.Web.SessionState; System.Web.UI; System.Web.UI.WebControls; System.Web.UI.HtmlControls;

335

namespace MathServices { public class SphereMath : System.Web.UI.Page { private void Page_Load( object sender, System.EventArgs e) { } #region Web Form Designer generated code override protected void OnInit(EventArgs e) { InitializeComponent(); base.OnInit(e); } private void InitializeComponent() {

C#
336

9
this.Load += new System.EventHandler(this.Page_Load); } #endregion } } Sie sehen hier, dass ein Namensraum MathServices (entsprechend dem Projektnamen) erstellt wurde. In diesem Namensraum befindet sich die Definition einer Klasse WebForm1, die von Page abgeleitet ist. Geben Sie dieser Klasse auch einen besseren Namen, nmlich SphereMath. Im Code finden Sie die schon bekannte Methode Page_Load und die Methode OnInit, die ihrerseits die Methode InitializeComponent aufruft. Wenn Sie hier Analogien zum .NET-Windows-Programmiermodell sehen, dann sind Sie vollkommen richtig. Navigieren Sie nun einmal in die HTML-Ansicht der Datei Sphere.aspx. In der einleitenden Direktive finden Sie den Eintrag Inherits="MathServices.SphereMath, also den Verweis auf die Klasse, die diese Seite reprsentieren soll (vgl. Diskussion in Kapitel 8: ADO.NET!). Fgen Sie ber den Designer folgende Web-Steuerelemente hinzu: Label Label TextBox Button Label Label Label lMainText lRadius tbRadius btCompute lDatum lVolumen lFlche

Weisen Sie allen Steuerelementen im Eigenschaftsfenster die oben angegebenen Namen zu (ID) und lschen Sie smtliche Text-Eigenschaften der verwendeten Steuerelemente.

ASP.NET
9
337

Anordnen von WebSteuerelementen im Designer Abb. 9.7

Schalten Sie einmal auf die HTML-Ansicht der Datei Sphere.aspx um.

HTML-Ansicht Abb. 9.8

Fr jedes Steuerelement wurde nun ein Eintrag gettigt, hnlich wie Sie es aus dem einfhrenden Beispiel her kennen. Jetzt werfen Sie einmal einen Blick in die Datei Sphere.aspx.cs (code-behind). protected protected protected protected Label lMainText; Label lRadius; TextBox btRadius; lDatum;

C#
338

9
protected Button btCompute; protected lVolumen; protected lFlche; In der Klasse SphereMath wurden Member-Variablen eingetragen, die zu den Steuerelementen der Seite Sphere.aspx korrespondieren. Sie knnen nun smtliche Funktionalitt hier in Form von C#-Code programmieren. Initialisieren Sie zuerst einmal die Steuerelemente in der Methode Load_Page. private void Page_Load(object sender, System.EventArgs e) { lMainText.Text = "KugelMathematik"; lMainText.Font.Bold = true; lMainText.Font.Size = FontUnit.Large; lRadius.Text = "Radius"; lDatum.Text = DateTime.Now.ToString(); btCompute.BackColor = Color.Red; } Belegen Sie die Text-Eigenschaften mit Werten. Damit die berschrift auch grer erscheint, wird die Eigenschaft Font des Labels entsprechend manipuliert. Im Label Datum soll die aktuelle Zeit erscheinen und der Button soll rot dargestellt werden, daher die Eigenschaft des Buttons BackColor auf die Farbe Color.Red stellen. Starten Sie einmal die Applikation mit S+%. Sie knnen die Applikation also auch aus dem Entwicklungssystem heraus starten. Oder aber ber die URL http://localhost/MathServices/ Sphere.aspx von einem Browser. Es fehlt noch die mathematische Berechnung der Kugeldaten. Diese soll beim Drcken des Buttons Compute stattfinden. Dazu mssen Sie sich beim Event Click des Buttons anmelden. Sie knnen dies aber auch ber den Designer durchfhren. Wechseln Sie dazu in die Designer-Ansicht der Datei Sphere.aspx und markieren Sie das Steuerelement btCompute und lassen Sie sich im Eigenschaftsfenster die Ereignisse des Steuerelements anzeigen. Ein Doppelklick auf das Ereignis Click erzeugt Ihnen im Code die entsprechende Behandlungs-

ASP.NET
9
routine und meldet diese auch beim Steuerelement an (Sie kennen diesen Vorgang aus dem Kapitel 6: Windows-Applikationen).

339

Hinzfgen von Ereignissen im Eigenschaftsfenster Abb. 9.9

Die Behandlungsroutine fllen Sie nun mit dem Ihnen schon bekannten Code. private void btCompute_Click(object sender, System.EventArgs e) { string sr = tbRadius.Text; double r = Double.Parse(sr); double V = 4*r*r*r*3.14/3; double A = 4*r*r*3.14; lVolumen.Text= "Volumen = " + V.ToString(); lFlche.Text = "Flche =" + A.ToString(); } Damit haben Sie es geschafft. Starten Sie nun die Applikation ber den Browser. Das sollte jetzt tadellos funktionieren. Schauen Sie sich einmal im Browser den HTML-Quellcode an (Menpunkt Ansicht > Quelltext anzeigen), den der IIS zum Browser gesandt hat. Sie sehen, es ist reiner HTML-Code, den ASP.NET erzeugt hat! Wenn Sie im Beispiel bei der Eingabe keine gltige Zahl eingeben, kommt es zu einem Fehler. ASP.NET gibt dann eine entsprechende Meldung zurck. Die Zeile double r = Double.Parse(sr);

C#
340

9
wird nmlich fehlschlagen und eine Exception auslsen. Die auftretende Fehlermeldung ist nicht gerade anwenderfreundlich. ndern Sie daher den Code wie folgt ab: private void btCompute_Click(object sender, System.EventArgs e) { try { string sr = tbRadius.Text; double r = Double.Parse(sr); double V = 4*r*r*r*3.14/3; double A = 4*r*r*3.14; lVolumen.Text= "Volumen = " + V.ToString(); lFlche.Text = "Flche =" + A.ToString(); } catch(Exception ex) { lVolumen.Text = ex.Message; } } Schtzen Sie den Code mit einem try-Block. Im Fall eines Fehlers verwenden Sie das Label lVolumen, um die Fehlermeldung auch auszugeben.

Ausgabe Exception Abb. 9.10

ASP.NET
9 Debugging von ASP.NET-Anwendungen
Mit ASP.NET lassen sich ASP-Applikationen nun auch Debuggen. Das ist erwhnenswert, weil dies unter ASP (alt) nicht mglich war. Smtliche Features des Debuggers sind nun anwendbar, wie das Setzen von Breakpoints, Auslesen von Werten aus Objekten usw.

341

Zusammenfassung
Mit Visual Studio.NET lassen sich schnell und effizient Web-Anwendungen entwickeln. Ein wenig Zeit braucht es, bis Sie die Bedienung des Entwicklungssystems beherrschen. ASP.NET-Anwendungen, die mit Visual Studio.NET erzeugt werden, verwenden das code-behind-Prinzip. Im Ordner befinden sich neben dem C#-Quellcode auch die kompilierten Versionen! Hinweis bei der Verwendung der CD-Beispiele: Wenn Sie die Beispiele von beiliegenden CD verwenden mchten, knnen Sie diese nicht direkt auf das Dateisystem kopieren und mit dem Entwicklungssystem ffnen. Folgende Vorgehensweise wird empfohlen: Legen Sie das Projekt mit Visual Studio.NET in eben gezeigter Form an, lschen Sie die Quellcode-Dateien im Projektordner und ersetzen Sie diese mit den Dateien auf der CD.

Web-Steuerelemente
Gemeinsame Eigenschaften
Die Web-Steuerelemente sind im Namensraum System.Web.UI.WebControls untergebracht. Sie sind alle von der Basisklasse WebControl abgleitet. Daher haben diese einige gemeinsame Eigenschaften. Alle diese Eigenschaften knnen Sie dynamisch verndern, indem Sie auf die entsprechenden Members der Objekte zugreifen. Hier sind einige Eigenschaften aufgelistet, die alle Web-Steuerelemente haben:

C#
342

9
BackColor BorderColor BorderStyle BorderWith Font ForeColor Height ID TabIndex ToolTip Visible Width Hintergrundfarbe Rahmenfarbe Rahmenart Rahmenbreite Schriftart Vordergrundfarbe Hhe des Steuerelements ID des Steuerlements Position der Tab-Reihenfolge Tooltip des Steuerlements Sichtbarkeit Breite

Im Folgenden soll eine Seite TestPage erzeugt werden, auf der sich einige Steuerelemente befinden. Die Absicht liegt darin, dass Sie die Verwendung einiger wichtiger Steuerelemente erlernen. Wenn Sie diese beherrschen, machen Ihnen die weiteren Steuerelemente keine Schwierigkeiten. Es wird daher auf eine Erklrung der weiteren Web-Steuerelemente verzichtet. Sollten Sie ganz spezielle Informationen bentigen, dann schlagen Sie in der MSDN nach.

Test-Steuerelemente Abb. 9.11

ASP.NET
9
Legen Sie eine neue ASP.NET-Web-Anwendung mit dem Namen TestPage an. Die vom Assistenten erzeugte Datei WebForm1.aspx nennen Sie in MainPage.aspx um. Die Klasse in der Datei MainPage.aspx.cs WebForm1 nennen Sie um auf MainPage.

343

HyperLink-Steuerelement
Fgen Sie ber den Designer der Seite ein Steuerelement vom Typ HyperLink hinzu und geben Sie diesem den ID-Wert HL. Im C#-Code wird dann das Steuerelement ebenfalls ersichtlich. protected System.Web.UI.WebControls.HyperLink HL; In der Methode Page_Load knnen Sie dieses Steuerelement nun initialisieren. private void Page_Load(object sender, System.EventArgs e) { HL.Text = "Ein interessanter Link"; HL.NavigateUrl = "http://www.teslab.com"; } Das Property NavigateUrl gibt dabei die Seite an, die bei einem Klick auf den Text geffnet wird.
CD-Beispiel TestPage

Image-Steuerelement
Interessant ist das Web-Steuerelement Image. Platzieren Sie ein Image-Steuerelement und weisen Sie einen ID-Namen zu. In der Klasse MainPage wurde folgendes Element hinzugefgt: protected System.Web.UI.WebControls.Image Im; Es gengt, wenn Sie die Eigenschaft ImageUrl belegen. Wenn Sie keinen Pfad angeben, dann sucht .NET im Pfad der ASP.NET-Applikation. Belegen Sie das Member ebenfalls in der Methode Page_Load. Stellen Sie auch sicher, dass sich auch eine Grafikdatei an der angegebenen Stelle befindet.
CD-Beispiel TestPage

C#
344

9
private void Page_Load(object sender, System.EventArgs e) { ... ... Im.ImageUrl = @"newton.gif"; }

CheckBox-Steuerelement
Fgen Sie ber den Designer der Web-Seite ein Label mit dem Namen lTime und eine CheckBox mit dem Namen cbLongView ein. Fgen Sie auch gleich mit dem Designer eine Bearbeitungsroutine fr das Ereignis CheckedChanged der CheckBox hinzu.

Event ber Designer hinzufgen Abb. 9.12

Die Bearbeitungsroutine implementieren Sie wie folgt:


CD-Beispiel TestPage

private void cbLongView_CheckedChanged( object sender, System.EventArgs e) { if(cbLongView.Checked == true) lTime.Text = DateTime.Now.ToLongTimeString(); else lTime.Text = DateTime.Now.ToShortTimeString(); } Bei Auftreten des Ereignisses stellen Sie den Zustand des Steuerelements fest und belegen in Abhngigkeit davon das Label lTime einmal mit einer langen bzw. kurzen Zeitdarstellung.

ASP.NET
9
Das Label lTime wird in Page_Load mit der kurzen Zeitdarstellung initialisiert. private void Page_Load(object sender, System.EventArgs e) { ... ... lTime.Text=DateTime.Now.ToShortTimeString(); cbLongView.Text = "Lange Anzeige"; cbLongView.AutoPostBack = true; } Sehr wichtig ist, dass Sie das Member AutoPostBack der Klasse CheckBox auf true setzen. Warum? Wenn Sie im Browser die CheckBox bettigen, dann muss eine http-POST Anforderung gestellt werden. Das machen die Web-Steuerelemente nicht standardmig (Ausnahme: Steuerelement Button). Beachten Sie das, man kann wegen dieses Umstandes schon stundenlang nach einem vermeintlichen Fehler suchen.

345

RadiobuttonList
Fgen Sie mit dem Designer der Web-Seite ein RadioButtonList-Steuerelement mit dem Namen rblTimeView hinzu und lassen Sie sich auch gleich eine Bearbeitungsroutine fr das Ereignis SelectedIndexChanged erzeugen. In der Methode Page_Load initialisieren Sie dieses Steuerelement wie folgt: private void Page_Load(object sender, System.EventArgs e) { ... ... if(!IsPostBack) { rblTimeView.Items.Add( new ListItem("Lange Anzeige")); rblTimeView.Items.Add( new ListItem("kurze Anzeige")); rblTimeView.AutoPostBack = true; } }
CD-Beispiel TestPage

C#
346

9
Sie fgen in der Initialisierung nun dieser RadioButtonList in gezeigter Form Items hinzu. Die Methode Page_Load wird bei jeder Seitenanforderung aufgerufen. D.h. bei einer GET- als auch bei einer POST-Anforderung. Die Items drfen aber nur beim erstmaligen Aufruf (GET-Aufruf) belegt werden (ansonsten wrden bei jedem Aufruf der RadioButtonList zwei neue Items zugeordnet werden). Die Klasse Page besitzt ein Member IsPostBack. Hier kann festgestellt werden, ob die Seite aufgrund einer POST- oder aber einer GET-Anforderung erzeugt wird. In der gezeigten Form findet eine Initialisierung nur bei einer GET-Anforderung statt. Vergessen Sie auch nicht, das Member AutoPostBack der Klasse RadioButtonList zu belegen! private void rblTimeView_SelectedIndexChanged( object sender, System.EventArgs e) { if(rblTimeView.SelectedIndex==0) { lTime.Text = DateTime.Now.ToLongTimeString(); cbLongView.Checked = true; } if(rblTimeView.SelectedIndex==1) { lTime.Text = DateTime.Now.ToShortTimeString(); cbLongView.Checked = false; } } Diese Behandlungsroutine wird aufgerufen, wenn Sie einen Button der RadioButtonList bettigen. Sie stellen in gezeigter Form fest, welcher Button aktiviert wurde und setzen das Label lTime entsprechend. Auerdem belegen sie auch die CheckBox cbLongView neu, da sich ja das Anzeigeformat des Labels lTime gendert hat. Richtigerweise mssen Sie auch noch die Behandlungsroutine auf das Ereignis CheckedChanged der CheckBox cbLongView entsprechend nachziehen. private void cbLongView_CheckedChanged( object sender, System.EventArgs e) { if(cbLongView.Checked == true) {

ASP.NET
9
lTime.Text = DateTime.Now.ToLongTimeString(); rblTimeView.SelectedIndex = 0; } else { lTime.Text = DateTime.Now.ToShortTimeString(); rblTimeView.SelectedIndex = 1; } }

347

ListBox
Fgen Sie nun der Web-Seite noch ein Steuerelement Listbox hinzu. Geben Sie diesem den Namen lbPhotos und erzeugen Sie auch gleich eine Behandlungsroutine zum Ereignis SelectedIndexChanged der Klasse ListBox. Dieses belegen Sie in der Methode Page_Load analog zum RadioButtonList. private void Page_Load(object sender, System.EventArgs e) { ... ... if(!IsPostBack) { lbPhotos.Items.Add(new ListItem("Newton")); lbPhotos.Items.Add(new ListItem("Einstein")); lbPhotos.Items.Add(new ListItem("Euler")); lbPhotos.AutoPostBack = true; } } ber diese ListBox soll das Image umgeschaltet werden knnen. Der Code der Behandlungsroutine muss daher wie folgt aussehen: private void lbPhotos_SelectedIndexChanged( object sender, System.EventArgs e) { if(lbPhotos.SelectedIndex==0) Im.ImageUrl = @"newton.gif"; if(lbPhotos.SelectedIndex==1) Im.ImageUrl = @"einstein.jpg";
CD-Beispiel TestPage

C#
348

9
if(lbPhotos.SelectedIndex==2) Im.ImageUrl = @"euler.jpg"; }

Zusammenfassung
Sie haben nun einige Steuerelemente kennen gelernt. ASP.NET bietet noch eine Reihe weiterer interessanter Steuerelemente an. Experimentieren Sie auch mit diesen Steuerelementen.

Kundenspezifische Steuerelemente
Die Tatsache, dass Web-Steuerelemente Objekte sind, erlaubt auch die Erzeugung von eigenen kundenspezifischen Steuerelementen. Sie werden nun in einem einfachen Beispiel die dahinter liegende Struktur kennen lernen. Sie ist relativ komplex, aber nicht kompliziert. Einmal erkannt, sollte diese Ihnen dann keine Schwierigkeiten mehr bereiten.

Basisklasse Control
Erzeugen Sie in der Projektmappe ein neues Projekt vom Typ Web-Steuerelementbibliothek, und geben Sie diesem den Namen MyWebControls. Dieses Projekt wird dann ein Assembly erzeugen, das smtliche eigens entwickelten serverseitigen Web-Steuerelemente beinhalten wird. Ein Blick auf den Projektmappen-Explorer zeigt, dass eine C#-Quellcodedatei mit dem Namen WebCustomControl1.cs angelegt wurde. Benennen Sie diese Quelldatei um auf MyWebControls.cs. In dieser Quellcodedatei wird ein Namensraum MyWebControls angelegt und gleich auch ein Rumpf-Code fr ein Web-Steuerelement erzeugt. Lschen Sie alles, denn aus didaktischen Grnden werden Sie ein Steuerelement von Grund auf erzeugen. Geben Sie dann folgenden Code ein:
CD-Beispiel MyWebControls1

using using using using

System; System.Web.UI; System.Web.UI.WebControls; System.ComponentModel;

namespace MyWebControls

ASP.NET
9
{ public class Time:Control { protected override void Render( HtmlTextWriter output) { output.Write("<h1>" + DateTime.Now+"</h1>"); } } } Damit haben Sie ein neues Web-Steuerelement erzeugt. Sie sehen hier eine neue Klasse Time, abgeleitet von Control (aus dem Namensraum System.Web.UI.WebControls) und berschreiben die virtuelle Methode Render. Das ist im Moment alles. Wenn das Steuerelement eine Ausgabe ttigen soll, wird ber das Objekt output vom Typ HtmlTextWriter ein HTML-Stream ausgegeben. Das geschieht programmtechnisch, wenn Sie die Methode Write des HtmlTextWriter-Objekts aufrufen. Dieser Code sollte kompilierfhig sein und ein Assembly mit dem Namen MyWebControls.dll erzeugen. In einem weiteren Projekt werden Sie nun das neue Steuerelement testen. Dazu erzeugen Sie in der Projektmappe eine neue ASP.NET-Webanwendung und geben diesem Projekt den Namen TestMyWebControls. Den Namen der Datei WebForm1.aspx benennen Sie auf MainForm.aspx um. Da Sie nun eigene Web-Steuerelemente verwenden wollen, die sich im Assembly MyWebControls befinden, mssen Sie einen Verweis zu diesem Assembly herstellen. In der HTML-Ansicht der Datei MainForm.aspx ist nun allerdings eine spezielle Register-Direktive notwendig. Fgen Sie diese gleich unterhalb der vorhandenen Page-Direktive hinzu. <%@ Page language="c#" ... %> <%@ Register TagPrefix="MyConts" Namespace="MyWebControls" Assembly="MyWebControls" %> Damit wird ASP.NET das Assembly und der Namensraum, in dem sich die Steuerelemente befinden, bekannt gegeben. Fr

349

C#
350

9
dieses Steuerelement wird auch ein TagPrefix definiert. Damit knnen Sie nun diese Steuerelemente der ASP-Seite hinzufgen. Leider knnen Sie das nicht ber den Designer erledigen. Sie mssen dies hndisch durchfhren.

CD-Beispiel TestMyWebControls1

<form id="Form1" method="post" runat="server"> <MyConts:Time id="myTime" runat="server" /> </form> Sie sehen, statt dem Prefix asp (das fr die ASP.NET-Steuerelemente verwendet wird) verwenden Sie hier das Prefix MyConts, das Sie in der Register-Direktive registriert haben. Dann geben Sie den Namen des Steuerelements an. Ein Aufruf dieser Seite ergibt folgende Ausgabe im Browser:

Kundenspezifisches Steuerelement im Browser Abb. 9.13

Kundenspezifische Attribute
Statten Sie nun das Steuerelement Time mit einer spezifische Eigenschaft TimeView aus. ber diese Eigenschaft soll die Formatierung der Zeit gesteuert werden knnen. Im Projekt MyWebControls definieren Sie zuerst eine Enumeration TimeView mit zwei Eintrgen LongTime und ShortTime.
CD-Beispiel MyWebControls2

public enum TimeView { LongTime,ShortTime }

ASP.NET
9
public class Time:Control { public TimeView View = TimeView.LongTime; protected { string Time; if(View == TimeView.ShortTime) Time=DateTime.Now.ToShortTimeString(); else Time = DateTime.Now.ToLongTimeString(); output.Write("<h1>" + Time +"</h1>"); } } Fgen Sie der Klasse Time die Member-Variable View hinzu und steuern Sie die Erzeugung des Ausgabe-Strings in der Methode Render ber den Status der Member-Variable View. In der HTML-Ansicht der .aspx-Seite knnen Sie nun unter Verwendung des Namens der Member-Variable View die Eigenschaft setzen. <form id="Form1" method="post" runat="server"> <MyConts:Time id="myTime" View="ShortTime" runat="server" /> </form> Wenn Sie die Applikation im Browser starten, dann erhalten in diesem Falle folgende Ansicht: Das ist schon bemerkenswert. In der .aspx-Seite definieren Sie in Form von HTML-Attribute die Eigenschaften des Steuerelements. Attribute sind aber Strings, und daher muss eine Konvertierung in intelligenter Form durchgefhrt werden. Ist die Member-Variable ein String, wird eine simple Zuweisung stattfinden, ist die Member-Variable aber ein integer oder float, hat hier eine Konvertierung string nach Zahl statt zu finden. Das funktioniert sogar fr Enumerationen, wie obiges Beispiel zeigt. Sollte eine Konvertierung aus irgendwelchen Grnden nicht mglich sein, dann kommt es zu einem ASP-Parse-Fehler.
CD-Beispiel TestMyWebControls2

351

override void Render( HtmlTextWriter output)

C#
352

Browseransicht nach Belegen eines Attributs Abb. 9.14

Fgen Sie eine weitere Eigenschaft hinzu. Es soll mglich sein, die Hintergrundfarbe des Steuerelements zuzuweisen. Dafr fhren Sie ein Property TimeColor ein.
CD-Beispiel MyWebControls3

using using using using using

System; System.Web.UI; System.Web.UI.WebControls; System.ComponentModel; System.Drawing;

namespace MyWebControls { public enum TimeView { LongTime,ShortTime } public class Time:Control { public TimeView View = TimeView.LongTime; private Color _TimeColor; public Color TimeColor { get{return _TimeColor;} set{_TimeColor = value;} } protected override void Render(

ASP.NET
9
HtmlTextWriter output) { string Time; if(View == TimeView.ShortTime) Time = DateTime.Now.ToShortTimeString(); else Time = DateTime.Now.ToLongTimeString(); output.Write("<h1 style='color:" + ColorTranslator.ToHtml(_TimeColor) + "'>" + Time + "</h1>"); } } } Beachten Sie aber die Ausgabe output.Write. Sie mssen dafr sorgen, dass ein entsprechender HTML-Stream erzeugt wird, der die Hintergrundfarbe auch wirklich im Browser setzt. Fr die Farbe Rot wrde die entsprechende HMTL-Anweisung wie folgt aussehen: <h1 style='color:Red'>17:03:58</h1> Da der Datentyp Color natrlich nicht mit der Farbencodierung unter HTML bereinstimmt, mssen Sie eine Konvertierung explizit durchfhren. Dies geschieht im Beispiel mit der statischen Methode ToHtml der Klasse ColorTranslator (Sie mssen fr Color den Namensraum System.Drawing freigeben!) In der ASP-Seite knnen Sie nun die Farbe wie folgt festlegen: <MyConts:Time runat="server" View="ShortTime" TimeColor="Red" /> Starten Sie die Applikation und betrachten Sie auch einmal den Quellcode der HTML-Datei, die dem Browser zugeschickt wurde. Es fllt Ihnen sicherlich auf, dass nun die Erzeugung des HTMLStreams ein wenig aufwndiger und vor allem auch fehleranflliger wird. Die Klasse HtmlTextWriter untersttzt Sie allerdings noch mit weiteren Methoden, die die Ausgabe ein wenig komplexer gestalten.
CD-Beispiel TestMyControls3

353

C#
354

9
CD-Beispiel MyWebControls4

protected {

override void Render( HtmlTextWriter output)

string Time; if(View == TimeView.ShortTime) Time = DateTime.Now.ToShortTimeString(); else Time = DateTime.Now.ToLongTimeString(); output.AddStyleAttribute("color", ColorTranslator.ToHtml(TimeColor)); output.RenderBeginTag("h1"); output.Write(Time); output.RenderEndTag(); } ber die Methode AddStyleAttribute knnen HTML-AttributEinstellungen fr den anschlieenden Tag angegeben werden. Die Methode RenderBeginTag ffnet einen Tag, der dann ber RenderEndTag() wieder geschlossen wird. Ein interner Stack bernimmt hier die Verwaltung der End-Tags. Haben Sie dieses Verfahren einmal verstanden, macht die Erzeugung von HTML-Streams deutlich weniger fehleranfllig.

Eingebettete Objekte
Verpacken Sie nun noch smtliche spezifische Eigenschaften in eine eigene Klasse TimeControlProperties.
CD-Beispiel MyWebControls5

using using using using using

System; System.Web.UI; System.Web.UI.WebControls; System.ComponentModel; System.Drawing;

namespace MyWebControls { public enum TimeView { LongTime,ShortTime } public class TimeControlProperties { public TimeView View = TimeView.LongTime;

ASP.NET
9
private Color _TimeColor; public Color TimeColor { get{return _TimeColor;} set{_TimeColor = value;} } } public class Time:Control { public TimeControlProperties Props = new TimeControlProperties(); protected { string Time; if(Props.View == TimeView.ShortTime) Time = DateTime.Now.ToShortTimeString(); else Time = DateTime.Now.ToLongTimeString(); output.AddStyleAttribute("color", ColorTranslator.ToHtml(Props.TimeColor)); output.RenderBeginTag("h1"); output.Write(Time); output.RenderEndTag(); } } } Hier wurde der Klasse eine Member-Variable des Typs TimeControlProperties zugefgt. Damit verwalten Sie also smtliche Eigenschaften in der Member-Variable Props. Wie wird nun aber in der ASP-Seite dieses eingebettete Objekt Props belegt? <MyConts:Time runat="server" Props-View="ShortTime" Props-TimeColor="Red" />
CD-Beispiel TestMyControls5

355

override void Render( HtmlTextWriter output)

Verwenden Sie den Bindestrich, um eine Eigenschaft eines eingebetteten Objektes zu belegen.

C#
356

9 Die Attribut-Klasse Style


ASP.NET bietet eine Klasse Style an, welche die Verwendung von immer wieder vorkommenden Eigenschaften wie Farben, Rnder, Fonts usw. deutlich vereinfacht. Die Verwendung ist im Folgenden gezeigt (es ist hier nur mehr der Code der Klasse dargestellt).
CD-Beispiel MyWebControls6

public class Time:Control { public TimeControlProperties Props = new TimeControlProperties(); public Style StockProps = new Style(); protected override void Render( HtmlTextWriter output)

{ string Time; if(Props.View == TimeView.ShortTime) Time = DateTime.Now.ToShortTimeString(); else Time = DateTime.Now.ToLongTimeString(); output.AddStyleAttribute("color", ColorTranslator.ToHtml(Props.TimeColor)); StockProps.AddAttributesToRender(output); output.RenderBeginTag("h1"); output.Write(Time); output.RenderEndTag(); } } Die Klasse hat eine zustzliche Member-Variable StockProps vom Typ Style erhalten. Mit der Methode AddAttributesToRender der Klasse Style wird nachfolgender Tag mit den entsprechenden Attributen belegt. Die Klasse Style kann folgende HTML-Attribute halten und ein Tag entsprechend belegen. BackColor BorderColor BorderStyle BorderWidth Font ForeColor

ASP.NET
9
CssClass Heigth Width Die entsprechenden Werte fr diese HTML-Attribute entnehmen Sie am besten aus der MSDN. Hier noch ein Beispiel fr die Zuweisung der Attribute im HTML-Code der .aspx-Datei: <MyConts:Timerunat=server Props-View="ShortTime" Props-TimeColor="Blue" StockProps-BackColor="Red" StockProps-BorderStyle="Solid" />
CD-Beispiel TestMyControls6

357

Basisklasse WebControl
Wenn Sie das Steuerelement Time statt von Control von WebControl ableiten, dann erben Sie ein Style-Objekt mit. Damit wird die Sache noch einfacher. public class Time:WebControl { public TimeControlProperties Props = new TimeControlProperties(); protected override void Render( HtmlTextWriter output) { string Time; if(Props.View == TimeView.ShortTime) Time = DateTime.Now.ToShortTimeString(); else Time = DateTime.Now.ToLongTimeString(); output.AddStyleAttribute("color", ColorTranslator.ToHtml(Props.TimeColor)); AddAttributesToRender(output); output.RenderBeginTag("h1"); output.Write(Time); output.RenderEndTag(); } } In der HTML-Darstellung der .aspx-Seite knnen Sie nun direkt auf die Eigenschaften der Style-Klasse zugreifen.
CD-Beispiel MyWebControls7

C#
358

9
CD-Beispiel TextMyControls7

<MyConts:Time runat=server Props-DisplayStyle=ShortTime BackColor=Red BorderStyle=Solid /> Fr sehr einfache Steuerelemente lsst sich dieser Code noch einmal vereinfachen, wenn man einige Features der Klasse WebControl ausnutzt.

CD-Beispiel MyWebControls8

public class Time:WebControl { public TimeControlProperties Props = new TimeControlProperties(); public Time():base("h1") { } protected override void RenderContents( HtmlTextWriter output) { string Time; if(Props.View == TimeView.ShortTime) Time = DateTime.Now.ToShortTimeString(); else Time = DateTime.Now.ToLongTimeString(); output.Write(Time); } } Dabei rufen Sie den Konstruktor der Basisklasse WebControl auf, und geben als Konstruktionsparameter einen HTML-Tag (im Beispiel h1) mit. Die Klasse erzeugt einen HTML-Stream mit diesem angegebenen Tag, versieht diesen mit den Attributen der Style-Klasse, ruft dann die Methode RenderContents auf und schliet zu guter Letzt den Stream mit dem entsprechenden End-Tag.

Composite Controls
Unter ASP.NET ist es auch mglich Steuerelemente zu erzeugen, die sich aus mehreren anderen Steuerelementen zusammensetzen (Composite Control). Im folgenden Beispiel wird Ihnen gezeigt, wie das prinzipiell funktioniert. Das zusammengesetzte Steuerelement soll sich aus dem kundenspezifischen

ASP.NET
9
Steuerelement Time und einer CheckBox zusammensetzen. ber die CheckBox kann dann die View des Steuerelements Time gesteuert werden. Dazu erzeugen Sie im bestehenden Projekt MyWebControls (und im Namensraum MyWebControls) eine neue Klasse TimeBox. Leiten Sie diese Klasse von WebControl und auch INamingContainer ab. public class OtherTime:WebControl,INamingContainer { CheckBox check; Time time; public bool Checked = false; protected override void CreateChildControls() { check = new CheckBox(); time = new Time(); check.Height = 50; check.Width = 200; check.Text = "Short Time"; check.Checked = Checked; Controls.Add(time); Controls.Add(check); check.AutoPostBack = true; check.CheckedChanged += new EventHandler(OnCheckedChanged); } private void OnCheckedChanged( object sender,EventArgs e) { if(check.Checked == true) Checked = true; else Checked = false; } protected override void Render( HtmlTextWriter output) { if(Checked) time.Props.View = TimeView.ShortTime; else time.Props.View = TimeView.LongTime;
CD-Beispiel MyWebControls9

359

C#
360

9
RenderChildren(output); } } In der Klasse wird ein Steuerelement vom Typ CheckBox und eines vom Typ Time in Form der Member-Variablen check und time angelegt. Eine public-Member-Variable (Checked) erlaubt auch einen Zugriff von auen. In der von WebControl virtuell vererbten Methode CreateChildControls werden diese eingebetteten Steuerelemente erzeugt und entsprechend mit Eigenschaften belegt. Die erzeugten Steuerelemente werden dann in der von WebControl geerbten Liste Controls hinzugefgt. In der .aspx-Seite knnen Sie nun dieses Steuerelement wie gewohnt anlegen.
CD-Beispiel TestMyControls9

<MyConts:TimeBox

runat=server Checked=true />

Wenn die ASP-Seite das Steuerelement TimeBox zeichnen muss, dann ruft ASP die Methode RenderChildren (eine Methode der Klasse WebControl) auf, diese durchluft die Liste Controls und ruft jeweils die Methode Render der eingebetteten Steuerelemente auf. Der CheckBox wird eine Bearbeitungsmethode auf das Ereignis CheckedChanged mit dem Namen OnCheckedChanged(...) hinzugefgt. Diese testet den Zustand der Checkbox und belegt das Member Checked. In der nun notwendigen Methode Render belegen sie zuerst das Attribut Props.DisplayStyle des eingebetteten Steuerelementes time und rufen dann explizit die Methode RenderChildren(...) auf. Vergessen Sie auf keinen Fall die Eigenschaft AutoPostBack der CheckBox auf true zu setzen. Nur dann wird eine neue Seite (http-POST) angefordert.

Statusverwaltung unter ASP.NET


ASP.NET bietet mehrere Mglichkeiten, um den Status einer Applikation zu verwalten. Session Application Cache Sie wissen, das Web-Protokoll ist ber die verschiedenen WebAnforderungen (Requests) statuslos. Um dennoch einen Sta-

ASP.NET
9
tus zwischen Benutzer und Server zu halten, muss der Client einen Schlssel anbieten, ber den dann der Server auf den Benutzer schlieen kann, und dies ber mehrere Anforderungen hinaus. D.h. bei jedem Aufruf hat der Client diesen Schlssel mitzugeben. Klassische Applikationen verwenden hierzu httpCookies. Nun gibt es aber immer mehr Benutzer, die httpCookies nicht akzeptieren. ASP.NET bietet ein Verfahren an, das nicht auf Cookies basiert, und somit auch auf Browsern funktioniert, die Cookies verweigern. ber das Session-Objekt lsst sich ein benutzerabhngiger Status verwalten. Damit knnten Daten in der Applikation verwaltet werden, die pro Benutzer gltig, und von anderen Benutzern strikt getrennt werden. Das Session-Objekt implementiert den Datenspeicher in Form einer Hash-Tabelle (Schlssel/Werte-Paar). Gespeichert werden Referenzen auf object und somit knnen beliebige Daten in diesem Datenspeicher gehalten werden, da ja unter .NET System.Object Basisklasse smtlicher Objekte darstellt. Session("[Key]") = [Value]; Das Session-Objekt berlagert den Indexer-Operator. Fr den Schlssel werden Strings verwendet. Session["Start"] = DateTime.Now; In diesem Beispiel wird der Schlssel Start verwendet. Mit diesem Schlssel wird dann die Zeit in Form eines DateTime-Objektes abgespeichert. Auf dieses Objekt kann nun jederzeit wieder ber den Schlssel zugegriffen werden. DateTime time = (DateTime)Session["Start"]; Da Objekte im Datenspeicher Session in Form von Referenzen auf object gehalten werden, ist natrlich ein casting auf den jeweiligen Typ notwendig. Im Wesentlichen gleich funktioniert das Application-Objekt. Auch dieses Objekt implementiert einen Datenspeicher in Form einer Hash-Tabelle, wobei auch hier der Schlssel in Form eines Strings definiert wird. Application["Start"] = DateTime.Now; DateTime timeApp = (DateTime)Application["Start"];

361

C#
362

9
Sie werden in einem einfachen Beispiel die Handhabung des Session- und Application-Objektes kennen lernen. Nehmen Sie an, Sie wollen den Zeitpunkt des Starts einer Applikation und des Starts einer Sitzung speichern und darstellen. So wird es nicht viel Sinn machen, den Zeitpunkt in Form einer MemberVariable in der Page-Klasse (bzw. von dieser abgeleitet) zu halten, da diese bei einer Anforderung der Seite jedes Mal neu erzeugt wird. Sie knnen aber Daten im Session- bzw. Application-Objekt, das jeder ASP.NET-Applikation zur Verfgung steht, abspeichern.

Application und Session


Legen Sie fr dieses Beispiel ein neues ASP.NET-Web Anwendung mit dem Namen State an. Geben Sie der vom Wizard erzeugten Datei WebForm1.aspx einen besseren Namen, z.B. StartPage.aspx. Dies machen Sie auch fr die Klasse WebForm1 (StartPage). Mit dem Designer fgen Sie nun Labels hinzu, damit Sie folgende Sicht erhalten.

Startzeiten Abb. 9.15

Die Namen der Labels entnehmen Sie aus folgendem Auszug aus der Quellcode-Datei StartPage.cs. protected protected protected protected System.Web.UI.WebControls.Label Label1; System.Web.UI.WebControls.Label Label2; System.Web.UI.WebControls.Label Label3; System.Web.UI.WebControls.Label lStartApplication;

ASP.NET
9
protected System.Web.UI.WebControls.Label lStartSession; protected System.Web.UI.WebControls.Label lStartPage; In dem vom Wizard erzeugten Projekt befindet sich auch eine ASP-Seite global.asax. Wer schon bisher ASP programmiert hat, tut sich leicht, denn hier hat global.asax dieselbe Funktion wie global.asa bei klassischen ASP-Projekten. Diese Datei implementiert die Klasse Global (abgeleitet von System.Web.HttpApplication) mit einer Anzahl von Methoden, die von der ASP.NET-Applikation zu gegebener Zeit aufgerufen werden. Die Namen der Methoden sind selbst erklrend. Unter anderem werden die folgenden Methoden implementiert, die sich fr das Beispiel bestens eignen, um die Startzeiten zu bekommen. protected void Application_Start( object sender, EventArgs e) { Application["Start"] = DateTime.Now; } protected void Session_Start( object sender, EventArgs e) { Session["Start"] = DateTime.Now; } In der Klasse StartPage (in der Datei Startpage.aspx) belegen Sie nun in der Methode Page_Load(...) die Steuerelemente u.a. mit den Daten aus den Datenspeichern Session und Application. private void Page_Load( object sender, System.EventArgs e) { lStartPage.Text = DateTime.Now.ToLongTimeString(); DateTime S_S = (DateTime)Session["Start"]; DateTime A_S = (DateTime)Application["Start"]; lStartSession.Text = S_S.ToLongTimeString(); lStartApplication.Text = A_S.ToLongTimeString(); }
CD-Beispiel State1

363

C#
364

9
Starten Sie nun mehrere Instanzen des Web-Browsers. Jede Instanz entspricht einer Session. Erweitern Sie diese Applikation mit einem Zhler auf die Seite. Jedesmal, wenn die Seite aufgerufen wird, soll ein Zhler auf der Ebene der Applikation um eins vergrert werden.
CD-Beispiel State2

protected void Application_Start(Object sender, EventArgs e) { Application["Start"] = DateTime.Now; Application["PageCounter"] = 0; } In der Datei global.asax initialisieren Sie einen Eintrag Application[-"PageCounter"] mit 0. In der Methode Page_Load(...) der Klasse StartPage in der Datei StartPage.aspx mssen Sie allerdings bei der Verwendung dieses Speicherplatzes aufpassen! Nachfolgender Code ist nmlich sehr fehleranfllig. int count = (int)Application["PageCounter"]; Application["PageCounter"] = count+1; lPageCounter.Text = count.ToString(); Warum? .NET-Komponenten sind free threaded, d.h. diese sind in mehreren Threads lauffhig. Wann das Betriebssystem zwischen den Threads (Ablaufpfaden) umschaltet, ist nicht vorhersehbar. Im schlimmsten Fall knnte das genau dann passieren, wenn ein Applikationsdatenspeicher gerade beschrieben wird. Wenn nun die Thread-Umschaltung auf halbem Wege stattfindet und ein Objekt im anderen Thread nun den erst halb belegten Speicherplatz ausliest, so ist es offenkundig, dass dann falsche Werte ausgelesen werden. Der Programmierer hat dafr zu sorgen, dass dieser Fall nicht eintreten kann. Zu diesem Zweck implementiert die Klasse Application die Methoden Lock() und Unlock(). Richtig sollte der Code so implementiert werden: private void Page_Load(object sender, System.EventArgs e) { lStartPage.Text = DateTime.Now.ToLongTimeString();

ASP.NET
9
lStartSession.Text = ((DateTime)Session["Start"]).ToLongTimeString(); lStartApplication.Text = ((DateTime)Application["Start"]).ToLongTimeString(); Application.Lock(); int count = (int)Application["PageCounter"]; Application["PageCounter"] = count+1; Application.UnLock(); lPageCounter.Text = count.ToString(); }

365

Seitenzhler Abb. 9.16

Wenn Sie allerdings die Zugriffe auf die Seite auch ber die Applikation speichern wollen, dann mssen diese natrlich irgendwo persistent gehalten werden. Das kann in einer Datenbank oder aber in einer normalen Datei (z.B. einer XMLDatei) sein. Bei Applikationsstart wrden hier die Werte ausgelesen. In der Methode Application_End(...) der Klasse Global ist der Ort, wo Sie die Werte persistent machen.

C#
366

9
protected void Application_End(Object sender, EventArgs e) { //speichere Info in Datenbank, XML-Datei etc. }

Cache
Im Folgenden werden Sie eine weitere Verwendung dieses globalen Speicherbereiches kennen lernen. Sie werden diese zuerst mit dem Application-Objekt lsen. Nachfolgend soll das Beispiel dann umgestellt werden auf das Objekt Cache, das bei bestimmten Anwendungen deutliche Vorteile gegenber dem Application-Objekt hat. Nehmen Sie an, Sie htten eine XML-Datei mit dem Namen Personen.xml. In dieser Datei sind Adressen in einem XML-Format gespeichert. Sie finden die Datei auf der beiliegenden CD.
XML-Datei Personen.xml

<?xml version="1.0"?> <!--Dies ist Entenhausen--> <?xml-stylesheet type='text/xsl' href='personen.xsl'?> <Personen> <Person> <Vorname>Donald</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr</Anrede> <eMail>Donald.Duck@entenhausen.com</eMail> </Person> <Person> <Vorname>Daisy</Vorname> <Nachname>Duck</Nachname> <Anrede>Frau</Anrede> <eMail>Daisy.Duck@entenhausen.com</eMail> </Person> <Person> <Vorname>Dagobert</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr </Anrede> <eMail>Dollar@entenhausen.com</eMail> </Person> </Personen>

ASP.NET
9
Sie sehen, die XML-Datei enthlt einen Verweis auf ein zugehriges Style-Sheet Personen.xsl. Dieses definiert die Transformation zu einer HTML-Seite. Auch die Datei finden Sie auf der beiliegenden CD. Das HTML-Transformationsergebnis der Datei Personen.xml mit der Datei Personen.xsl htte folgendes Erscheinungsbild im Browser:

367

XSLTransformationsergebnis Abb. 9.17

Erzeugen Sie nun ein neues Projekt vom Typ ASP.NET Webanwendung mit dem Namen TestCache. Geben Sie den Dateien in gewohnter Form bessere Namen (z.B. StartPage.aspx und StartPage.aspx.cs). Wechseln Sie zum C#-Code der Startseit (codebehind) und taufen Sie die Klasse WebForm1 auf StartPage um. Im HTMLCode der .aspx-Seite fgen Sie (am besten per Designer) ein Web-Steuerelement XML ein. Nennen Sie es ShowAddresses <form id=Form1 method=post runat="server"> <asp:Xml id=ShowAddresses runat="server"></asp:Xml> </form> In der Datei Global.asax werden Sie nun zwei Objekte in den globalen Datenspeicher Application bringen.

C#
368

9
CD-Beispiel TestCache

protected void Application_Start(Object sender, EventArgs e) { XmlDocument dom = new XmlDocument(); dom.Load(Server.MapPath("Personen.xml")); Application["Personen.xml"] = dom; XslTransform xsl = new XslTransform(); xsl.Load(Server.MapPath("Personen.xsl")); Application["Personen.xsl"] = xsl; } Zuerst wird ein Objekt vom Typ XmlDocument erzeugt und mit der entsprechenden Datei (Personen.xml) initialisiert. Da die Methode in der Root der Applikation erwartet wird, verwenden Sie vorteilhaft die Methode MapPath, um einen vollstndigen und vor allem richtigen Pfad zu erhalten. Weiter wird dann ein Objekt vom Typ XslTransform (xsl), initialisiert mit der XSL-Datei Personen.xsl in den Datenspeicher gebracht. Damit stehen diese Objekte fr die ganze Applikation zur Verfgung. In der Datei StartPage.aspx.cs implementieren Sie nun folgenden Code in der Methode Page_Load(...). protected System.Web.UI.WebControls. Xml.ShowAddresses; private void Page_Load(object sender, System.EventArgs e) { ShowAddresses.Document = (XmlDocument)Application["Personen.xml"]; ShowAddresses.Transform = (XslTransform)Application["Personen.xsl"]; } Hier belegen Sie die Eigenschaften Document und Transform des Web-Steuerelements XML mit den Daten aus dem Datenspeicher. Vergessen Sie auch nicht die folgenden Namensrume freizugeben (in StartPage.aspx.s und Global.asax.cs).

ASP.NET
9
using System.Xml; using System.Xml.Xsl; In der Methode Page_Load der Klasse StartPage fgen Sie nun folgenden Code ein: private void Page_Load( object sender, System.EventArgs e) { ShowAddresses.Document = (XmlDocument)Application["Personen.xml"]; ShowAddresses.Transform = (XslTransform)Application["Personen.xsl"]; } Die Transformation fhrt dann das Web-Steuerelement XML durch. Sie mssen nur die Properties Document und Transform mit den globalen Objekten belegen. Also werden die Dateien Personen.xml und Personen.xsl nur einmal (bei Applikationsstart) geladen, und stehen somit allen Sessions und Pages global zur Verfgung. Die Seiten werden somit blitzschnell aufgebaut! Tolle Sache, nicht wahr? Das Objekt Application bietet sich im ersten Moment gut fr Daten an, die ber alle Benutzer gehalten werden mssen. Nur wenn sich im Beispiel die Datei Personen.xml ndern wrde, weil z.B. ein neuer Adresseintrag in der XML-Datei durchgefhrt wurde, dann msste die Applikation neu gestartet werden, damit die Daten auch in der Applikation zur Verfgung stehen. Eine Lsung wre, wenn die Daten bei jedem Page_Load(...) von der Datei gelesen wrden. Dies ist aber aus Laufzeitgrnden nicht zu empfehlen! Um genau dieser Problematik zu entgegnen, wurde im ASP.NET-Programmiermodell der Typ System.Web.Cache eingefhrt. (Jede ASP.NET Applikation besitzt ein Objekt mit dem Namen Cache vom Typ System.Web.Cache.) Der Datenspeicher Cache ist dem Datenspeicher Application sehr hnlich, hat aber darber hinausgehende Funktionalitt. Daten im Objekt Cache knnen ablaufen. Wenn das der Fall ist, dann mssen diese neu geladen werden. Es werden von ASP.NET Ereignisse angeboten, die zur Folge haben, dass Daten aus dem Cache entfernt werden, und somit im Bedarfsfall neu geladen werden mssten.
CD-Beispiel TestCache

369

C#
370

9
nderung einer Datei Definierte Zeit ist abgelaufen ndern Sie das Beispiel nun so ab, dass statt des Datenspeichers Application der Datenspeicher Cache verwendet wird. Smtlichen Code in Application_Start knnen Sie entfernen. Der Klasse StartPage fgen Sie zwei neue Member-Variablen bei: XmlDocument dom = new XmlDocument(); XslTransform xsl = new XslTransform(); In Page_Load fgen Sie nun folgenden Code ein: private void Page_Load(object sender, System.EventArgs e) { if(Cache["Xml"]==null) { string path = Server.MapPath("Personen.xml"); dom.Load(path); CacheDependency d = new CacheDependency(path); Cache.Insert("Xml",dom,d); } if(Cache["Xsl"]==null) { string path = Server.MapPath("Personen.xsl"); xsl.Load(path); CacheDependency d = new CacheDependency(path); Cache.Insert("Xsl",xsl,d); } ShowAddresses.Document = (XmlDocument)Cache["Xml"]; ShowAddresses.Transform = (XslTransform)Cache["Xsl"]; } Vergessen Sie auch nicht, den Namensraum System.Web.Caching zu ffnen. In der Methode Page_Load(...) wird zuerst geprft, ob im Key Xml im Datenspeicher Cache ein Element abgespeichert ist. Ist das nicht der Fall, dann wird zuerst das XmlDocument-Objekt dom geladen und anschlieend in den Cache gebracht und

ASP.NET
9
zwar mit einer Abhngigkeit (CacheDependency). Die Abhngigkeit ist in diesem Fall eine Dateiabhngigkeit. D.h., wenn sich die Datei aus irgendeinem Grund ndern wrde, wird diese augenblicklich aus dem Cache entfernt (null) und wrde beim nchsten Aufruf der Datei neu geladen werden. Eine Abhngigkeit gegenber einer Datei (oder auch Ordner) wird ber ein Objekt der Klasse CacheDependency unter Angabe des Pfades der Datei (oder Ordner) erzeugt. In den Cache wird das Objekt mittels der Methode Insert unter Angabe eines Keys (String), dem Objekt und der Art der Abhngigkeit geladen. Dasselbe Prozedere fhren Sie mit der Datei Personen.xsl durch. Wenn Sie nun die Applikation starten und whrenddessen auch eine nderung in der Datei Personen.xml durchfhren, so wird die Applikation automatisch die neue Datei laden und in den Cache bringen. (Fgen Sie als Test ein Label in die ASP-Seite ein, das eine Ausgabe ttigt, wenn es zu einem Nachladen des Caches kommt.) In diesem Beispiel haben Sie eine Abhngigkeit bezglich einer Datei (Ordner ist ebenfalls mglich) verwendet. Sehr oft bietet sich auch eine Abhngigkeit zurzeit an, d.h. wenn eine bestimmte Zeit abgelaufen ist, dann wird der Eintrag im Cache gelscht und (wenn auch so programmiert) wieder nachgeladen. if(Cache["Xsl"]==null) { string path = Server.MapPath("Personen.xsl"); xsl.Load(path); Cache.Insert("Xsl",xsl,null, DateTime.Now.AddMinutes(1), TimeSpan.Zero, CacheItemPriority.High,null); } Hier wird der Eintrag nach einer Minute aus dem Cache entfernt und damit bei Bedarf wieder nachgeladen.

371

C#
372

Zusammenfassung
Sie haben in diesem Kapitel nun einen berblick auf das ASP.NET-Programmier-Modell erhalten. Web-Seiten lassen sich mit diesem Modell objektorientiert entwickeln. Die Steuerelemente reprsentieren serverseitig HTML-Code, den praktisch jeder Browser versteht. Smtliche Funktionalitt des Erzeugens von HTML-Code ist in diesen Klassen untergebracht. Die Erzeugung von kundenspezifischen Steuerelementen ermglicht die Unterbringung von immer wiederkehrender Funktionalitt in eine eigene Komponente. Smtliche Features der Sprache C# knnen verwendet werden. Die Analogie zum Windows-Programmiermodell ist unbersehbar. Damit werden sich auch klassische Windows-Programmierer leicht tun. Der Code einer Applikation wird zur Laufzeit kompiliert. Damit ist eine bedeutende Performanz-Steigerung gegenber dem klassischen ASP zu erwarten. Im nchsten Kapitel wenden Sie sich einem weiteren, beraus interessanten Feature zu: Web-Services.

Web-Services

Einleitung Ein einfaches Web-Service-Beispiel Entwicklung und Anwendung von Web-Services unter Visual Studio.NET Zusammenfassung

374 374 381 388

C#
374

10

Einleitung
In den Anfangszeiten des Internets waren Web-Seiten statische HTML-Seiten, die mit http-GET von einem Server heruntergeladen werden konnten. Prinzipiell konnten das beliebige Dateien sein, sie mussten nur existieren. Entsprechen die Inhalte dieser Dateien dem HTML-Standard, dann knnen spezielle Browser diese entsprechend darstellen. Mit ASP und anderen Script-Techniken werden HTML-Seiten auf dem Server nun dynamisch erzeugt und auf Anforderungen zurckgegeben. Damit wurde die Leistungsfhigkeit von Web-Seiten drastisch gesteigert (siehe Kapitel 9, ASP.NET). Eine weitere Steigerung der Leistungsfhigkeit erwartet man durch die WebServices. Hier bietet ein Server Funktionalitt hnlich einer Funktion an. D.h. mittels http-GET und httpPOST knnen statt Web-Seiten Funktionen aufgerufen werden, denen Parameter mitgegeben werden und die ein Ergebnis auch wieder zurckgeben. SOAP definiert ein solches Protokoll (Small Object Access Protocol). Der Server gibt hier als Antwort auf eine Anforderung das Ergebnis in Form einer XML-Datei zurck. Beliebige Clients knnen sich nun diesen Diensten (Service) bedienen, solange diese fhig sind http-Befehle abzusetzen und mit XML Daten umzugehen. WebServices sind relativ neu, und erste Anwendungen stehen in den Startlchern. In diesem Kapitel werden Sie lernen, wie die .NET-Laufzeitumgebung um den IIS (Internet Information Server) diese Technologie untersttzt.

Ein einfaches Web-Service-Beispiel


In einem ersten einfachen Beispiel werden Sie nun einen WebService auf dem IIS erzeugen. Ebenfalls beinhaltet dieses Beispiel auch eine kleine Client-Applikation (in Form einer Konsolenanwendung), die auf diesen Web-Service zugreift. Dieses erste Beispiel wird ohne Verwendung des Entwicklungssystems durchgefhrt, weil Sie hier die Zusammenhnge am besten erkennen werden. Der WebService wird die aus dem Kapitel 9, ASP.NET bekannten Funktionen fr die Berechnung des Volumens und der Flche einer Kugel implementieren.

Web-Services
10 MyWebServices
Erstellen Sie zuerst im Ordner c:\inetpub\wwwroot einen Unterordner mit dem Namen MyWebServices. In diesem Ordner erzeugen Sie mit einem beliebigen Texteditor eine Datei mit dem Namen MathServices.asmx und geben darin folgenden C#-Code: <%@ WebService language="C#" class="MathWebServices"%> using System; using System.Web.Services; public class MathWebServices:System.Web.Services.WebService { [WebMethod] public double SphereVolume(double r) { return 4.0*r*r*r*3.14/3.0; } [WebMethod] public double SphereArea(double r) { return 4.0*r*r*3.14; } } Damit haben Sie schon ein WebService erzeugt und installiert! Die Dateierweiterung .asmx ist fr den IIS die Information, dass diese Datei ein WebService beinhaltet. Diese Datei muss mit der Direktive WebService beginnen. <%@ WebService language="C#" class="MathWebServices"%> In dieser Datei wird nun in C# eine Klasse mit dem Namen MathWebServices erstellt, die von System.Web.Services.WebService abgeleitet ist. Die Klasse implementiert zwei Methoden, die mit dem Attribut [WebMethod] versehen sind. Damit haben Sie ein WebService erzeugt! Wenn Sie nun diese Seite aufrufen, dann kompiliert der IIS diese Datei in gleicher Weise, wie Sie es unter Kaptitel 9, ASP.NET kennen gelernt haCD-Beispiel MathWebServices

375

C#
376

10
ben, und stellt die Funktionalitt als WebService bereit. Unter .NET ein Web-Service anzulegen ist also eine triviale Sache, wenn Sie C# beherrschen. Sie brauchen nicht einmal ein Entwicklungssystem! Wie Sie nun auf diesen Web-Service zugreifen knnen, werden Sie gleich erfahren.

Web-Service im Web-Browser
Die Web-Service-Spezifikation schreibt vor, dass der Web-Server auch eine Mglichkeit anbieten muss, die Informationen ber den Web-Service zu erfragen. Das sind z.B. die Methodennamen, die das Service anbietet, die Parameter und Rckgabewerte der Methoden, usw. Diese Informationen werden in XML-Form zurckgegeben. Starten Sie den Browser und geben Sie folgende URL ein: http://localhost/MyWebServices/MathWebServices. asmx?wsdl (WSDL steht fr WebService Description Language). Sollten Sie einen syntaktischen Fehler in der .asmx-Datei gemacht haben, dann wird dem Browser eine Fehlermeldung bermittelt. Liegt kein Fehler vor, dann erhalten Sie eine XML-Information zurck. In Abbildung 10.1 sehen Sie einen Auszug aus dieser Datei. Sichten Sie diese Datei einmal grob, und es wird Ihnen auffallen, dass die Methodennamen, Parameternamen und Rckgabewerte im XML-Stream vorkommen. Achtung, das ist nicht ein Web-Service-Aufruf, sondern die Anforderung der Informationen zum Service. Wie wird nun ein WebService-Aufruf gettigt? Geben Sie Browser folgende URL ein: http://localhost/MyWebServices/MathWebServices.asmx/ SphereVolume?r=4.1 Sie geben die URL der .asmx-Seite mit Angabe der WebServiceMethode und der bergabeparameter ein. Die Reaktion des IIS ist wieder eine XML-Information mit dem Wert des Rckgabeergebnisses.

Web-Services
10
377

Informationen zum WebService Abb. 10.1

Rckgabeergebnis in XMLForm Abb. 10.2

Wenn Sie brigens im Web-Browser nur die URL der .asmx-Seite angeben, wird die Web-Service-Information ber ein Stylesheet sogar noch in eine fr Menschen besser lesbare Version transformiert. http://localhost/MyWebServices/MathWebServices.asmx Der Browser zeigt nun die beiden Methoden an. Die Beschreibung der Methoden kann genauer in Erfahrung gebracht werden, ja es knnen diese Methoden sogar aufgerufen werden.

C#
378

10

Web-Service-Information Abb. 10.3

Test der Web-ServiceFunktionen Abb. 10.4

Eine kurze Zusammenfassung: Ein Web-Service bietet Leistung an, die einer Funktion, wie sie Programmierer kennen, sehr hnlich sind. Der Aufruf gleicht einer Anforderung einer Web-Seite, das Ergebnis ist ein XML-Stream.

Web-Services
10
Die Web-Service-Spezifikation sieht vor, dass die Funktionalitt eines WebServices erfragt werden kann. Die Information wird ebenfalls in Form eines XMLStreams bereitgestellt. Der IIS in Zusammenarbeit mit der .NET-Laufzeitumgebung erlaubt die einfache Erzeugung eines WebServices. In einer .asmx-Seite mit einer einleitenden Direktive und Definition einer Klasse, die von System.Web.Services.WebService abgeleitet, reicht aus. Alle Methoden, die mit dem Attribut [WebMethod] gekennzeichnet sind, werden vom IIS als Web-ServiceFunktionen exportiert.

379

Client-Programme zu MathWebService
Wenn Sie auf ein Programm schreiben wollen, das auf WebServices zugreift, dann mssen Sie es dazu bringen, dass es einen http-Aufruf erzeugt, wie Sie es im einleitenden Beispiel gemacht haben. Auerdem wird das Programm dann das XML-Ergebnis entsprechend interpretieren mssen. Am besten, Sie htten als Client-Programmierer eine Methode mit folgendem Prototyp zur Verfgung: double SphereVolume(double r); Smtliche Arbeit (Aufruf gestalten und XML-Ergebnis interpretieren) soll in dieser Methode durchgefhrt werden. Das ist nichts anderes als ein Proxy (Stellvertreter). Das .NET-SDK stellt das Hilfsprogramm WSDL.EXE zur Verfgung. Mit diesem Werkzeug kann eine C#-Quellcode-Datei erzeugt werden, die den Proxy-Code enthlt.

Web-Service-Proxy
Legen Sie einen Ordner mit dem Namen TestMathWebService an und fhren Sie in diesem ber den Visual Studio.NET Command Prompt (Start > Programme > Microsoft Visual Studio.NET > Visual Studio.NET Tools > Visual Studio.NET Command Prompt) folgende Anweisung durch. WSDL http://localhost/MyWebServices/MathWebServices.asmx

C#
380

10
Das Hilfsprogramm holt sich die Beschreibung des Services vom Server (bekommt also die Beschreibung der Funktionen im XML-Format, und erzeugt damit einen Proxy (in Form einer C#-Datei). Im Ordner finden Sie nun die C#-Quellcode (MathWebServices.cs).

CD-Beispiel TestMathWebServices

public class MathWebServices: System.Web.Services.Protocols.SoapHttpClientProtocol { ... } Ein kurzer Blick in die Datei zeigt, dass in dieser eine Klasse mit dem Namen MathWebService definiert wurde. Erzeugen Sie nun im selben Ordner mit einem Texteditor die Datei App.cs und geben Sie folgenden Code ein: using System; class App { public static void Main() { MathWebServices Math = new MathWebServices(); Console.WriteLine(Math.SphereVolume(4.2)); } } Hier wird ein Objekt vom Typ MathWebServices angelegt und ber dieses Objekt die Methode SphereVolume aufgerufen. Das Rckgabeergebnis wird gleich auf die Konsole ausgegeben. Nun das Ganze noch kompilieren: csc /t:exe MathWebServices.cs App.cs Dann knnen Sie die Applikation App.exe starten. Es wird das Ergebnis ausgegeben. Werfen Sie noch einmal einen Blick in den Quellcode der Proxy-Klasse, und betrachten Sie den Konstruktor der Klasse: public MathWebServices() { this.Url= http://localhost _ /mywebservices/mathwebservices.asmx"; }

Web-Services
10
Der Konstruktor belegt das Member Url hart kodiert. Dies ist natrlich problematisch, da eine Verlagerung des Servers ein massives Problem darstellen wrde. Sie knnen aber jederzeit im Applikationscode diese Member berschreiben. Besser wre also: using System; class App { public static void Main() { MathWebServices Math = new MathWebServices(); Math.Url = "http://localhost/mywebservices/ _ mathwebservices.asmx"; Console.WriteLine(Math.SphereVolume(4.2)); } } Wenn nun noch die URL ber eine Konfigurationsdatei belegt wird, ist alles perfekt. Eine kurze Zusammenfassung: Da die Beschreibung eines Web-Services laut Spezifikation jederzeit vom Server erfragt werden kann, sollte die automatische Erzeugung eines Proxys fr die spezifische Entwicklungsumgebung kein Problem darstellen. Unter .NET heit dieses Tool WSDL.EXE und erzeugt einen C#-Quellcode fr einen Proxy dieser WebServices. ber diese Proxy-Klasse in dieser Quellcode-Datei kann in angenehmer Form programmtechnisch auf das Web-Service zugegriffen werden.

381

Entwicklung und Anwendung von WebServices unter Visual Studio.NET


Es ist schon bemerkenswert, wie einfach sich Web-Services (oder auch klassische Web-Anwendungen) auch ohne Hilfe des Entwicklungssystems erstellen lassen.

C#
382

10
Visual Studio.NET bietet aber mchtige Assistenten an, die die Erzeugung und vor allem die Anwendung von Web-Services noch mehr vereinfachen. Im Folgenden werden Sie einen kleinen Web-Service mit der Entwicklungsumgebung Visual Studio.NET entwickeln.

Web-Service-Anwendung
Das WebService wird eine Funktion GetDayOfDate implementieren. Sie werden dieser Funktion ein Datum in String-Form mitgeben knnen, und es wird der Wochentag zurckgegeben. Eine weitere Funktion GetDaySpan errechnet die Tage zwischen zwei Datumsangaben. Erzeugen Sie zuerst eine neue Projektmappe mit dem Namen WebService und erstellen Sie in dieser ein Projekt mit dem Namen DateServices. Verwenden Sie die Projektvorlage ASP.NETWebdienst. Im Projektmappen-Explorer sehen Sie, dass eine Datei Service1.asmx angelegt wurde. Nennen Sie diese um auf den Namen DateFunctions.asmx. Beachten Sie, dass dieser Name in der URL sichtbar wird. Fr die .asmx-Datei wird der Designer angezeigt, Sie knnen aber auf den code-behind wechseln, genau so, wie Sie es in Kapitel 9: ASP.NET kennen gelernt haben. (Visual Studio.NET bedient sich also auch der code-behind-Technik.) In der codebehind-Datei DateFunctions.asmx.cs findet sich nun eine Klasse Service1. Auch diese taufen Sie bitte um auf DateFunctions. (nicht vergessen, auch den Konstruktor auf diesen Namen umzunennen). Entfernen Sie auch den Namensraum DateServices er wird nicht bentigt.
CD-Beispiel DateServices

public class DateFunctions : System.Web.Services.WebService { [WebMethod] public string GetDayOfDate(string d) { try { DateTime dt = DateTime.Parse(d); return dt.DayOfWeek.ToString(); } catch(Exception ex)

Web-Services
10
{ return ex.Message; } } [WebMethod] public int GetDaySpan(string d1,string d2) { try { DateTime dt1 = DateTime.Parse(d1); DateTime dt2 = DateTime.Parse(d2); TimeSpan ts = dt2-dt1; if(ts.Days<0) return -ts.Days; else return ts.Days; } catch(Exception ex) { //im Fehlerfalle negativ return -1; } } public DateFunctions() { InitializeComponent(); } ... ... } Sie sehen, der bergebene String wird zuerst in einen Datumswert bersetzt. Tritt bei diesem Parsen ein Fehler auf, wird die auftretende Exception abgefangen und ein entsprechender Fehlerwert zurckgegeben. Starten Sie die Applikation im Browser, wird die Web-Service-Information der Applikation dargestellt. Testen Sie nun die Funktionen des Web-Services. Analog zu einem klassischen ASP.NET-Projekt hat der Assistent in c:\inetpub\wwwroot einen Ordner DateServices angelegt. Hier befinden sich smtliche Dateien, auch die Verwaltungsdateien fr das Entwicklungssystem Visual Studio.Net.

383

C#
384

10 Client-Anwendungen
Das eben angefertigte Web-Service soll nun getestet werden. Dazu ein kleines Konsolenprogramm: Legen Sie in der aktuellen Projektmappe ein neues Projekt (Vorlage Konsolenanwendung) an und geben Sie diesem den Namen TestServiceConsole. Nennen Sie die Datei Class1.cs auf App.cs um. Nun brauchen Sie zuerst einen Proxy. Diesen knnten Sie, wie im einleitenden Beispiel kennen gelernt, aus der Konsole ber das Hilfsprogramm WSDL.EXE erzeugen. Aber genau diese Aufgabe knnen Sie ber das Entwicklungssystem durchfhren. Markieren Sie im Projektmappen-Explorer den Eintrag Verweise des Projektes TestServiceConsole und bettigen Sie nun den Kontextmeneintrag Webverweis hinzufgen...

Webverweis hinzufgen Abb. 10.5

In dem nun erscheinenden Dialog geben Sie die URL-Adresse des Web-Services ein. Im speziellen Fall: http://localhost/DateServices/DateFunctions.asmx Wenn alles klappt, wird im Dialog die Information des WebServices angezeigt. Mit Verweis hinzufgen erstellt der Assistent dann den Proxy. Werfen Sie nun einen Blick auf den Projektmappen-Explorer. Damit Sie diese Darstellung sehen, mssen Sie allerdings in der Werkzeugleiste des Projektmappen-Explorers den Button Alle Dateien anzeigen aktivieren.

Web-Services
10
385

Web-Service Proxy im Projektmappen-Explorer Abb. 10.6

Im neuen Eintrag Webverweise finden Sie nun die Datei Reference.cs. Dies ist der erzeugte Proxy. Ein Blick in diese Datei zeigt, dass die Proxy-Klasse in einem eigenen Namensraum definiert ist (hier im Namensraum TestServiceConsole.localhost). Im Quellcode der Applikation ffnen Sie am besten diesen Namensraum. Damit knnen Sie dann den Code wie folgt realisieren: using System; using TestServiceConsole.localhost; class App { public static void Main() { DateFunctions df = new DateFunctions(); while(true) { Console.Write("Geben Sie ein Datum ein: "); string date = Console.ReadLine(); if(date=="!") break; string day = df.GetDayOfDate(date); Console.WriteLine("Dies ist ein {0}",day); } } }
CD-Beispiel TestServiceConsole

C#
386

10
Dieses Programm wird allerdings nur auf Ihrem Rechner funktionieren. Warum? Weil in der Proxy-Klasse die URL des WebServices hart kodiert eingegeben ist. Und dort steht localhost, d.h. das Programm wird den Web-Service immer auf der eigenen Maschine suchen. ndern Sie daher das Programm ab, dass es konfigurierbar ist. Erzeugen Sie eine XML-Datei mit dem Namen TestServiceConsole.exe.config und geben Sie folgende Konfigurationsdaten ein. (Mehr ber Konfigurationen lesen Sie im Kapitel 7: Konfigurationsdateien.) <?xml version="1.0" encoding="UTF-8"?> <configuration> <appSettings> <add key="URLDateServices" value="http://esche/DateServices/ DateFunctions.asmx" /> </appSettings> </configuration> ber den Key URLDateServices knnen Sie nun die URL dynamisch gestalten. using System; using TestServiceConsole.localhost; using System.Configuration; class App { public static void Main() { DateFunctions df = new DateFunctions(); string url = ConfigurationSettings.AppSettings ["URLDateServices"]; df.Url = url; while(true) { Console.Write("Geben Sie ein Datum ein: "); string date = Console.ReadLine(); if(date=="!") break; string day = df.GetDayOfDate(date); Console.WriteLine("Dies ist ein {0}",day);

Web-Services
10
} } } Testen Sie noch die zweite Funktion GetDaySpan, der bung halber in einem Windows-Programm. Erzeugen Sie also in der aktuellen Projektmappe ein neues Projekt (Projektvorlage Windows-Anwendung) mit dem Namen TestServiceWindows. Die Datei Form1.cs taufen Sie gleich um auf MainWnd.cs. Die Klasse Form1 im Quellcode benennen Sie ebenfalls auf MainWnd um. Wechseln Sie nun zum Designer und fgen Sie zwei DateTimePicker-Steuerelemente (mit den Namen dpDate1 und dpDate2), zwei Labels fr die Beschriftung, und ein Label fr die Ausgabe der Differenztage (Name lDifferenz) ein.

387

Window Web-ServiceClient in Aktion Abb. 10.7

Erzeugen Sie nun mit dem Designer fr das Ereignis (Event) ValueChanged des Steuerelements dpDate1 eine Bearbeitungsmethode. Dieselbe Bearbeitungsmethode weisen Sie auch dem Steuerelement dpDate2 zu. Wie im Projekt TestServiceConsole erzeugen Sie nun einen Proxy fr den Web-Service DateServices/Datefunction.asmx. In MainWnd.cs ffnen Sie den Namensraum TestServiceWindows.localhost und fgen der Klasse MainWnd ein Member vom Typ DateFunctions hinzu (DateFunctions ist die ProxyKlasse des Web-Services). DateFunctions df;
CD-Beispiel TestServiceWindows

C#
388

10
Im Konstruktor erzeugen Sie den Proxy, und belegen die URL des Web-Services dynamisch aus einem Eintrag in der Konfigurationsdatei. public MainWnd() { df = new DateFunctions(); string url = ConfigurationSettings.AppSettings ["URLDateServices"]; df.Url = url; InitializeComponent(); } private void dpDate1_ValueChanged(object sender, System.EventArgs e) { int diff = df.GetDaySpan(dpDate1.Text,dpDate2.Text); lDifferenz.Text = "Differenztage: " + diff; } In der Bearbeitungsroutine der DateTimePicker-Objekte rufen Sie die Web-Service-Funktion GetDaySpan auf und weisen dann das Rckgabeergebnis dem Ausgabe-Label zu. Damit das Ganze auch noch funktioniert, mssen Sie noch eine Konfigurationsdatei TestServiceWindows.exe.config erzeugen. Sie besitzt denselben Inhalt wie die Konfigurationsdatei des Beispiels TestServiceConsole.

Zusammenfassung
In diesem Kapitel haben Sie kennen gelernt, wie Sie mit Visual Studio.NET Web-Services erzeugen knnen. Auerdem wurde in zwei Beispielen gezeigt, wie auf diese Services programmtechnisch zugegriffen werden kann. Es soll hier noch einmal betont werden, dass Web-Services plattformunabhngig sind. D.h. Sie knnen in dieser Form auch auf Web-Services zugreifen, die auf einer Nicht-Windows-Plattform laufen, da ja die Schnittstelle zur Auenwelt http und XML darstellt. Ebenfalls knnen auch andere Plattformen auf die Web-Services zugreifen, die auf einer WindowsPlattform ausgefhrt werden.

Web-Services
10
Vielleicht haben Sie eine gute Idee, Funktionalitt in Form von Web-Services der Welt bereitzustellen. Die Werkzeuge sind vorhanden, es liegt nur noch an Ihnen, diese Ideen umzusetzen.

389

Remoting unter .NET

Einleitung Remoting-Infrastruktur Client-aktivierte Objekte Server-aktivierte Objekte Erzeugung von Remote-Objekten mit dem new-Operator Channel Lebensdauer (Leased Based Lifetime) Konfigurieren des Servers und des Clients ber Konfigurationsdateien IIS Hosting Asynchrone Aufrufe Events Remote-fhiger Objekte bergabe von Objekten in Remote-Methoden Zusammenfassung

392 393 395 403 406 407 408 414 418 421 426 426 427

C#
392

11

Einleitung
Remoting ist sicherlich einer der interessantesten Aspekte in der modernen Softwareentwicklung. Das Internet, das bisher Server mit einander verbindet und Clients erlaubt auf diese zuzugreifen, entwickelt sich immer mehr in Richtung vernetzter Computer allgemein. D.h. es existiert ein riesiges globales Netz, an dem jeder Computer, der auch einen Internetanschluss besitzt, eingebunden ist. Eine Kommunikation untereinander wird ermglicht. Die .NET-Architektur und .NETBasisklassen untersttzen dieses Vorhaben gnzlich. Die Kommunikation zwischen zwei Prozessen, die sich auch auf unterschiedlichen Rechnern befinden knnen, ist auch fr erfahrene Softwareentwickler immer wieder eine Herausforderung. Viele Aspekte sind hier zu beachten, man denke nur an Sicherheit, Verfgbarkeit, Verlsslichkeit und Fehlerbehandlung. Eine Kommunikation zwischen Prozessen auf unterschiedlichen Maschinen erfolgt ber ein Netzwerk. Unter Win32 sind es die WinSockets, die dem Programmierer den direkten Zugang zu Netzwerkdiensten erlauben. Auf Basis dieser Sockets wurden Softwareschichten aufgebaut, die es letztendlich einem Programmierer einfach machen, mit anderen Prozessen zu kommunizieren. Moderne Implementierungen sind sogar teilweise objektorientiert, wie z.B. CORBA oder COM/DCOM/COM+. Unter Win32 wurde sehr erfolgreich COM/DCOM/COM+ als verteiltes Objektmodell verwendet und es wurde viel Software auf dieser Basis entwickelt. Ursprnglich als Komponentenmodell wurde COM zu DCOM, einem verteilten Objektmodell erweitert, und erlaubte so auch entfernte Objekte zu erzeugen und deren Methoden aufzurufen. Die Nachteile wie Synchronismus der Aufrufe wurden mittels weiterer Konzepte wie z.B. Message Queue Server gelst. Mit COM+ wurden dann deutliche Verbesserungen eingefhrt, welche auch die Entwicklung von hoch skalierbaren Server-Anwendungen erlaubten, wie diese bei Web-Applikationen notwendig sind. Leider eigneten sich die Sprachelemente der herkmmlichen Programmiersprachen nur bedingt, dieser Entwicklung Rechnung zu tragen. Unter C++ konnte vor allem durch Templates eine gewisse bersichtlichkeit gewahrt werden, aber jeder er-

Remoting unter .NET


11
fahrene Programmierer wird besttigen, dass ein C++-Objekt sich von einem COM-Objekt deutlich unterscheidet und nur bedingt vergleichbar ist. Eine C++-Klasse auf eine Remote-fhige COM+-Klasse zu erweitern, ist auch fr erfahrene Entwickler eine Herausforderung. C# als neue C-Sprache und vor allem .NET trgt nun diesen Aspekten Rechnung. Sie werden sehen, dass die Entwicklung einer Remote-fhigen Klasse sich nicht sehr von der Entwicklung einer normalen C#-Klasse unterscheidet, und dass es vor allem keine Begrenzung bezglich der Datentypen gibt, wie dies bei COM der Fall war. Auch werden smtliche modernen Features der objektorientierten Sprachen, allen voran die Vererbung, in diesem Programmiermodell untersttzt.

393

Remoting-Infrastruktur

Remote Infrastruktur Abb. 11.1

Hier sehen Sie ein Modell fr die grundstzliche Architektur, die .NET fr Remote-Anwendungen verwendet. Vergleichen Sie diese mit DCOM, die Ihnen vermutlich vertraut ist. Auf der Client-Seite haben Sie ein Stckchen Code, welcher das Serverobjekt reprsentiert (proxy zu deutsch Stellvertreter). Dieser Proxy stellt bezglich der Schnittstelle ein Zwilling des eigentlichen Objektes dar. Die Implementierungen der Methoden des Proxys allerdings leiten die bergabeparameter der Methode an das Serverobjekt weiter. Unter der COM-Terminologie wurde das Kodieren dieser Information (binre) ebenfalls dem Proxy zugeordnet. Die .NET-Architektur fgt hier allerdings noch einen so genannten Formatter ein. Dieser Formatter hat die Aufgabe der Kodierung (auf der Clientseite) und der

C#
394

11
Dekodierung (auf der Serverseite). Prinzipiell knnen damit beliebige Methoden der Kodierung verwendet werden. Die ausgelieferte .NET-Umgebung erlaubt standardmig zwei Arten der Kodierung (binr und XML). Allerdings ist es jedem frei gestellt auch eigene Formatter zu schreiben. Dasselbe gilt auch fr den Channel (Kanal). Unter dem .NETRemoting-Modell sind Channels austauschbar (pluggable). Die ausgelieferte .NET-Umgebung bietet standardmig einen TCP-Kanal und einen http-Kanal an. Vor allem die Kombination http als Kanal und XML als Formatter eignet sich hervorragend fr die Kommunikation von Prozessen ber die Internetschiene, da fr smtliche Firewalls http und XML kein Problem darstellt (sehr wohl aber fr spezifische binre Formatter). Die Erzeugung von Proxys (Proxy-Code) geschieht unter CORBA, DCOM und anderen verteilten Programmiermodelle meist durch eine IDL (Interface Definition Language). In einer eigenen Sprache werden u.a. die Remote-fhigen Methoden und Parameter definiert, dieser Code wird dann einem IDL-Compiler gefttert, der dann daraus C- bzw. C++-Code fr den Proxy (inklusive Kodierung und Dekodierung) erzeugt hat. Unter .NET ist dies nicht mehr notwendig, da ja alle Klassen ausnahmslos ihre Beschreibung in Form von Metadaten im Code mitfhren, und diese Beschreibung sogar zur Laufzeit ausgelesen werden kann. Im Fall des Remotings wird der Proxy aus dieser Beschreibung zur Laufzeit erzeugt. Damit kann dieser Schritt entfallen und vereinfacht die Entwicklung deutlich. Da alle verwendeten Datentypen unter .NET auf die .NET-Typen zurckzufhren sind, gibt es auch keine diesbezglichen Einschrnkungen bei Datentypen fr Remote-fhige Klassen. Die .NET-Architektur bietet dem Entwickler gegenber COM mehrere Remoting-Mglichkeiten an. Diese sind hier als bersicht dargestellt, und Sie werden sie im Verlauf dieses Kapitels kennen lernen.

Remoting unter .NET


11
395

Client-aktivierte Objekte
FolderFileEnum-Klasse
Fr die nachfolgenden Experimente werden Sie eine Klasse entwickeln, die aus der Dateistruktur die Ordner bzw. auch Dateien ermitteln kann. Diese Klasse wird dann Remote-fhig gestaltet. Der Code dieser Klasse ist hier abgebildet. Die Klasse werden Sie in einem eigenen Dll-Assembly unterbringen. Legen Sie dazu eine neue Projektmappe mit dem Namen Remoting an. In dieser Projektmappe erzeugen Sie nun ein Projekt vom Typ C#Klassenbibliothek mit dem Namen FolderFileEnum. Der Quellcodedatei Class1.cs geben Sie den besseren Namen FolderFileEnum.cs, der Namensraum FolderFileEnum ist hier nicht sehr sinnvoll und daher lschen Sie diesen. Den Namen der Klasse Class1 ndern Sie auf FolderFileEnum und geben dann folgenden Code ein. using System; using System.IO; public class FolderFileEnum:MarshalByRefObject { public FolderFileEnum() { //zu Testzwecken Ausgabe beim Server Console.WriteLine( "Objekt {0} wird erzeugt",ToString()); } ~FolderFileEnum() { //zu Testzwecken Ausgabe beim Server Console.WriteLine( "Objekt {0} wird aufgelst",ToString()); } public string[] GetFolders( string root,string searchPattern) { try { //zu Testzwecken Ausgabe beim Server
CD-Beispiel FolderFileEnum

C#
396

11
Console.WriteLine( "GetFolders wurde aufgerufen"); DirectoryInfo di = new DirectoryInfo(root); DirectoryInfo [] DirList = di.GetDirectories(searchPattern); string [] Folders = new string[DirList.Length]; for(int i=0;i<Folders.Length;i++) { Folders[i] = DirList[i].Name; } return Folders; } catch(Exception e) { string [] Fail = {e.Message}; return Fail; } } public string[] GetFiles( string root,string searchPattern) { try { //zu Testzwecken Ausgabe beim Server Console.WriteLine("GetFiles wurde aufgerufen"); DirectoryInfo di = new DirectoryInfo(root); FileInfo [] FileList = di.GetFiles(searchPattern); string [] Files = new string[FileList.Length]; for(int i=0;i<Files.Length;i++) { Files[i] = FileList[i].Name; } return Files; } catch(Exception e) { string [] Fail = {e.Message}; return Fail; } } }

Remoting unter .NET


11
Die Klasse FolderFileEnum implementiert einen Konstruktor und den Destruktor, die rein zu Testzwecken eine Ausgabe auf der Konsole ttigen, damit Sie bei den nachfolgenden Experimenten den berblick bewahren, wo und wann Objekte angelegt bzw. vom Garbage Collector wieder entsorgt werden. An Funktionalitt implementiert die Klasse die Methoden GetFiles(...) und GetFolders(). Als bergabeparameter besitzen beide Methoden zwei Strings mit Angabe der Root (Ordner) und eines Suchstrings, der auch Wildcards beinhalten kann. Zurck geben diese Methoden ein Feld aus Strings, mit den Dateien bzw. Ordnern, die im angegebenen Root sind und den Suchstring erfllen. Fr Testzwecke erzeugen diese Methoden ebenfalls eine Ausgabe auf der Konsole, damit der Aufruf dieser Methoden auch berprft werden kann. Sollte innerhalb der Methoden etwas fehlschlagen, wird eine entsprechende Fehlermeldung im String Feld zurckgegeben. Sie sehen, dass im Wesentlichen dies eine ganz normale C#Klasse darstellt, nur dass die Klasse von MarshalByRefObject abgeleitet ist. Dazu aber spter! Um diese Klasse zu testen, legen Sie ein Konsolenprojekt mit dem Namen FolderFileEnumTest an, referenzieren auf obiges Assembly FolderFileEnum und geben folgenden Code ein. using System; class App { static void Main(string[] args) { FolderFileEnum obj = new FolderFileEnum(); while(true) { Console.Write( "\nWurzel angeben (! um zu beenden): "); string root = Console.ReadLine(); if(root=="!") break; string [] folders = obj.GetFolders(root,"*.*"); for(int i=0;i<folders.Length;i++) Console.WriteLine(folders[i]); } } }

397

C#
398

11
Zuerst wird ein Objekt obj vom Typ FolderFileEnum erzeugt. In einer Endlos-Schleife liest das Programm einen String von der Konsole ein. Mit Eingabe eines Rufezeichens wird die Schleife verlassen und das Programm beendet. Im anderen Fall wird dieser String der Methode GetFolders(...) im Parameter root bergeben. Das Ergebnis (alle Ordner in dieser Root) wird im Feld folders abgelegt und dann werden die Eintrge auf der Konsole ausgegeben. (Beachten Sie, dass Sie C:\ und nicht nur C: eingeben mssen, wenn Sie die Ordner in der Root der Festplatte C erhalten wollen!) Wenn die Klasse funktioniert, wird im nchsten Beispiel ein Remoting-Versuch gestartet.

Server
Sie werden nun einen Server schreiben, der Objekte dieser definierten Klasse halten kann. Hierzu erzeugen Sie in der Projektmappe ein neues Konsolenprojekt mit dem Namen RemServer, geben in bewhrter Weise der Datei Class1.cs den Namen App.cs. Stellen Sie dann eine Referenz zum Assembly FileFolderEnum.dll her.

Ausgabepfad ndern Abb. 11.2

Den Ausgabepfad-Pfad des Projektes ndern Sie auf c:\Remserver. Dazu ffnen Sie den Eigenschaftsdialog des Projektes Remserver (im Projektmappen-Explorer das Projekt markieren

Remoting unter .NET


11
und per Kontextmen den Menpunkt Eigenschaften aktivieren). Im Eigenschaftsdialog aktivieren Sie im Navigationsbaum den Eintrag Konfigurationseigenschaften > Erstellen und ndern den Ausgabepfad auf den oben erwhnten Ordner. Damit werden smtliche Aufgaben, insbesondere die ausfhrbare .exe-Datei in diesen Ordner geschrieben. Existiert der angegebene Ordner nicht, wird dieser beim ersten Mal angelegt. Diese Vorgehensweise wird Ihnen dann die Tests erleichtern. using using using using System; System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Http;
CD-Beispiel RemServer1

399

class Server { static void Main() { HttpChannel chan = new HttpChannel(8085); ChannelServices.RegisterChannel(chan); /*------------------------------------------*\ * Client Aktivierung \*------------------------------------------*/ RemotingConfiguration.ApplicationName = "TestServer"; RemotingConfiguration.RegisterActivatedServiceType (typeof(FolderFileEnum)); Console.WriteLine( "FolderFileServer gestartet..."); Console.WriteLine("<enter> um zu beenden..."); Console.ReadLine(); } } Vergessen Sie auch nicht eine Referenz auf das Assembly System.Runtime.Remoting.dll einzurichten. In der Main()-Methode wird zuerst ein Objekt chan vom Typ HttpChannel mit Port 8085 erzeugt und mit der Methode RegisterChannel der Klassen Remoting.Configuration registriert. Die Serverapplikation erhlt einen Namen mit (im Beispiel TestServer). ber diesen Namen knnen dann Instanzen von smtlichen Klassen, wel-

C#
400

11
che ber die Methode RegisterActivatedServiceType der Klasse RemotingConfiguration und Angabe der Klasse (typeof(FolderFileEnum) registriert wurden, erzeugt werden. Voraussetzung ist allerdings, dass diese Klassen von MarshalByRefObject abgeleitet wurden. Damit wre der Server fertig und Sie wenden sich der Programmierung eines Clients zu.

Client
Legen Sie in der Projektmappe ein neues Konsolenprojekt mit dem Namen TestClient an, ndern Sie den Ausgabepfad des Projektes auf c:\TestClient, damit das Testen einfacher wird. Eine Referenz zum Assembly FolderFileEnum.dll ist notwendig. Vergessen Sie auch nicht eine Referenz auf das Assembly System.Runtime.Remoting einzurichten.
CD-Beispiel TestClient1

using using using using using using using

System; System.IO; System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; System.Runtime.Remoting.Channels.Http; System.Runtime.Remoting.Activation;

public class Client { public static int Main(string [] args) { object[] attrs = {new UrlAttribute ("http://localhost:8085/TestServer") }; FolderFileEnum obj = (FolderFileEnum)Activator.CreateInstance( typeof(FolderFileEnum), null, attrs); if (obj == null) { Console.WriteLine("Kann Server nicht finden"); return 0; } while(true) { Console.Write(

Remoting unter .NET


11
"\nWurzel angeben (! um zu beenden): string root = Console.ReadLine(); if(root=="!") break; ");

401

string [] folders = obj.GetFolders(root,"*.*"); for(int i=0;i<folders.Length;i++) Console.WriteLine(folders[i]); } return 0; } } Starten Sie nun den Server, aber nicht aus der Entwicklungsumgebung heraus, sondern navigieren Sie mit dem Datei-Explorer in den Ordner c:\RemServer. Anschlieend starten Sie den Client (Sie knnen diesen auch vom Entwicklungssystem heraus starten). Auf der Konsole des Servers knnen Sie dann feststellen, dass serverseitig ein Objekt angelegt wurde (Ausgabe des Konstruktors). Geben Sie nun ber die Client-Konsole einen Pfad ein. Auf dem Server wird nun die Methode GetFolder(...) ausgefhrt und das Ergebnis auf der Client-Konsole ausgegeben.

Server-Applikation Abb. 11.3

Der Client-Code unterscheidet sich vom Projekt FolderFileEnumTest nur bezglich der Erzeugung des Objektes vom Typ FolderFileEnum. Sie erzeugen das Objekt ber die Methode CreateInstance der Klasse Activator.

C#
402

11

Client-Applikation Abb. 11.4

object[] attrs = {new UrlAttribute ("http://localhost:8085/TestServer") }; FolderFileEnum obj = (FolderFileEnum)Activator.CreateInstance( typeof(FolderFileEnum), null, attrs); Diese Methode bekommt in der Parameterliste u.a. den Typ einer Klasse, die der Server bereitstellt (FolderFileEnum) und eine Attributen-Liste, die im ersten Eintrag die URL des Servers beinhaltet. Dieser setzt sich zusammen aus dem Protokoll (http), der IP Adresse bzw. des Namens der Servermaschine (im speziellen Fall localhost) und dem Applikationsnamen, der im Serverprogramm der Applikation zugeordnet wurde (TestServer). Damit wird also auf dem Server das Objekt erzeugt. Fr die Auflsung des Objektes ist der Garbage Collector zustndig. Wenn Sie das Serverprogramm beenden, werden Sie die Ausgabe des Destruktors auf der Konsole des Servers erkennen. Auf der Clientseite wird das Objekt (obj) als Proxy erzeugt. Sie werden nun einwenden, dass auch auf der Clientseite das Assembly FolderFileEnum bentigt wird. Deshalb, weil die Beschreibung des Objektes bentigt wird, und diese befindet sich bekanntlich ja in den Metadaten des Assembly. Erzeugen Sie im Client auch ein zweites Objekt, und Sie werden sehen, dass im Server auch eine zweite Instanz entsteht. Experimentieren Sie nun noch mit einem tatschlichen Remote-Fall. Dafr installieren Sie auf einem beliebigen Rechner im Netzwerk Ihr Serverprogramm in einem eigenen Ordner. Sie

Remoting unter .NET


11
wissen, Sie brauchen dazu nur RemServer.exe und FolderFileEnum.dll zu kopieren, und starten dann auf dem anderen Rechner den Server. Beim Client geben Sie nun statt localhost den Rechnernamen an. ndern Sie dann noch den Client so ab, dass Sie den Host bei Start des Clientprogramms ber die Konsole eingeben knnen.

403

Server-aktivierte Objekte
Das Konzept der Server-aktivierten Objekte ist neu. ndern Sie zuerst den Server so ab, dass die Aktivierung serverseitig erfolgt: using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; class Server { static void Main() { HttpChannel chan = new HttpChannel(8085); ChannelServices.RegisterChannel(chan); RemotingConfiguration.RegisterWellKnownServiceType( typeof(FolderFileEnum), "FFE", WellKnownObjectMode.SingleCall);
CD-Beispiel RemServer2

Console.WriteLine("FolderFileServer gestartet..."); Console.WriteLine("<enter> um zu beenden..."); Console.ReadLine(); } } Sie registrieren nun die Klasse mittels der statischen Methode RegisterWellKnownServiceTyp(), geben hierzu die bereitzustellende Klasse an, einen Namen, ber den die Clients Verbindung aufnehmen knnen, und als letzten Parameter einen Typ (hier SingleCall, dazu aber spter). Auffallend ist, dass hier kein Applikationsname angegeben wird, sondern jede Klasse, die ber Remoting vom Server angeboten wird, einen eigenen Namen bekommt.

C#
404

11
Eine kleine nderung ist auch auf der Client-Seite durchzufhren:
CD-Beispiel TestClient2

public static int Main(string [] args) { FolderFileEnum obj = (FolderFileEnum)Activator.GetObject( typeof(FolderFileEnum), "http://localhost:8085/FFE"); if (obj == null) . . . } Sie erzeugen nun das Objekt mittels Activator.GetObject(...). Beachten Sie allerdings, dass in diesem Fall GetObject direkt eine URL als Parameter bekommt, die mit dem im Servercode vergebenen URL-Namen bereinstimmen muss. Das Verhalten ist allerdings ein gnzlich anderes. Starten Sie hierzu wieder den Server in C:\RemServer und dann das Clientprogramm (am besten ber das Entwicklungssystem). Es wird Ihnen sicherlich sofort auffallen, dass das Programm schneller startet. Fragen Sie nun mehrmals nach einem Pfad im Server an. Sie werden feststellen, dass fr jeden Aufruf speziell ein Objekt im Server angelegt wird. Dies ist das Prinzip von serverseitig aktivierten Objekten. Es ist klar, dass in solchen Objekten natrlich keinerlei Status ber die Aufrufe hinweg gehalten werden kann. Wenn Sie eine Anzahl von Aufrufen ttigen, dann wird nach einer gewissen Zeit, der Garbage Collector aktiv und entsorgt die Objekte. Bei diesem Programm macht es auch nichts aus, wenn der Server einmal beendet, und anschlieend wieder gestartet wird. Das Clientprogramm wird davon nichts merken. Das Serverprogramm erzeugt ja bei jedem Aufruf ein Objekt neu. Dieses Verhalten werden Sie bei Client-aktivierten Objekten nicht feststellen. Wenn hier der Server einmal beendet wird, dann wird der Client nicht weiterarbeiten knnen. Sie sehen, Remoting auf Basis von Server-aktivieren Objekten ist ein robuster Ansatz. Bei verteilten Anwendungen ber das Internet ist dies natrlich eine Voraussetzung, da hier stabile Verbindungen (noch) nicht den Standard darstellen. Das Ganze geht natrlich auf Kosten der Tatsachen, dass ein solches Objekt keinen Status zwischen den Aufrufen speichern kann.

Remoting unter .NET


11
405

Serveraktivierte Objekte (SingleCall) Abb. 11.5

Die Registrierung der Klasse FolderFileEnum mit dem URI-Namen FFE erfolgte hier im Modus SingleCall. In diesem Modus wird bei jedem Aufruf ein neues Objekt im Server erzeugt. RemotingConfiguration.RegisterWellKnownServiceType( typeof(FolderFileEnum), "FFE", WellKnownObjectMode.SingleCall); Alternativ kann auch der Modus Singleton verwendet werden. RemotingConfiguration.RegisterWellKnownServiceType( typeof(FolderFileEnum), "FFE", WellKnownObjectMode.Singleton);

Serveraktivierte Objekte (Singleton) Abb. 11.6

C#
406

11
Hier wird beim ersten Aufruf im Server ein Objekt angelegt. Allerdings wird bei nachfolgenden Aufrufen dieses Objekt fr die Abarbeitung der Anfrage verwendet. Damit existiert im Serverprogramm nur genau eine Instanz dieser Klasse. Dieses Objekt wird auch im Fall von mehreren unterschiedlichen Clients geteilt.

Erzeugung von Remote-Objekten mit dem new-Operator


Clientseitig wird ein Proxy (Stellvertreter) eines Objektes im Server ber die Methode CreateInstance (Client aktiviert) bzw. GetObject (Server aktiviert) der Klasse Activator erzeugt. Diese Methoden geben eine Referenz auf ein Proxy-Objekt zurck, das dann in die entsprechende Klasse gecastet werden muss. ber die Methode RegisterActivatedClientType (bei Server aktivierten Objekten) bzw. RegisterWellKnownClientType (bei Client-aktivierten Objekten) wird die .NET-Laufzeitumgebung informiert, dass beim Anlegen des Objektes ber den newOperator ein entsprechender Proxy des Typs erzeugt werden soll. Damit vereinfacht sich die Erzeugung eines Remote-Objektes deutlich.
CD-Beispiel TestClient4

public static int Main(string [] args) { RemotingConfiguration.RegisterActivatedClientType( typeof(FolderFileEnum), "http://localhost:8085/TestServer"); FolderFileEnum obj = new FolderFileEnum(); . . . } bzw.

CD-Beispiel TestClient5

public static int Main(string [] args) { RemotingConfiguration.RegisterWellKnownClientType( typeof(FolderFileEnum), "http://localhost:8085/FFE");

Remoting unter .NET


11
FolderFileEnum obj = new FolderFileEnum(); . . . } Damit reduziert sich das Programmieren auf der Clientseite auf die Registrierung der Remote-Klasse unter Angabe des Typs und der URI. Von der Handhabung unterscheidet sich die Verwendung von Remote-Objekten dann nicht mehr von normalen Objekten.

407

Channel
Bisher haben Sie Remoting ber einen http-Kanal durchgefhrt. Standardmig wird ein XML (SOAP)-Formatter fr die Encodierung verwendet. Wie schon in der Einfhrung erwhnt, ist die .NET-Remote-Architektur sehr anpassungsfhig, und es knnen beliebige Kanle, aber auch Formatter eingesetzt werden. Derzeit bietet die .NET-Umgebung einen tcp-Kanal an. Um ber den tcp-Kanal zu kommunizieren mssten folgende nderungen durchgefhrt werden: Auf der Serverseite: HttpChannel chan = new HttpChannel(8085); ndern auf: TcpChannel chan = new TcpChannel(8085); Auf der Clientseite: Die URI des Servers ndert sich aber nun auf: "tcp://localhost:8085/TestServer" HttpChannel und TcpChannel sind von der Klasse Channel abgeleitet. Sie knnen sich nun vorstellen, auch eigene Klassen zu erzeugen, die von Channel abgleitet sind, und dann diese verwenden. Denken Sie z.B. an einen Kanal ber die serielle Schnittstelle. Dasselbe gilt auch fr die Formatter. Die Erzeugung von kundenspezifischen Kanlen und Formattern wrde ber den Umfang des Buches hinausgehen. Hier wird wieder auf die MSDN verwiesen.

C#
408

11

Lebensdauer (Leased Based Lifetime)


Alle Objekte, die auerhalb einer Applikation Referenzen haben, wird eine Mietzeit (lease time) zugeordnet. Wenn diese Zeit abluft, wird das Objekt von der .NET-Remoting-Laufzeitschicht getrennt und beim nchsten Garbage Collector-Durchlauf entsorgt. Alle Objekte haben eine Default-lease period zugeordnet und es gibt nun mehrere Mglichkeiten diese Zeit zu manipulieren, um die Objekt-Lebensdauer zu verwalten. Diese Mglichkeiten umfassen: Das Serverobjekt kann seine lease time auf unendlich stellen, sodass der GC diese nie auflsen wird. Ein Client kann die lease time eines Objektes vom Server erfragen und diese auch bei Bedarf verlngern. Ein Client kann einen so genannten Sponsor registrieren. Wenn die Zeit eines Objektes abluft, wird der Sponsor kontaktiert, und dieser kann dann bei Bedarf die Zeit wieder verlngern. Wenn das Property ILease.RenewOnCallTime gesetzt ist, dann wird bei jedem Aufruf des Remote-Objektes die Ablaufzeit neu gesetzt.

Die Schnittstelle ILease


Experimentieren Sie vorerst im Client mit den Lebensdauerwerten. Bevor Sie mit den Experimenten starten, bringen Sie den RemServer dazu, dass die Objekte Client-aktiviert erzeugt werden. Im Client-Code fgen Sie, nachdem der Proxy erzeugt wurde, nachfolgenden Code hinzu. Die Schnittstelle ILease ist im Namensraum System.Runtime.Remoting.Lifetime definiert. Geben Sie daher diesen Namensraum frei.
CD-Beispiel TestClient6

FolderFileEnum obj = new FolderFileEnum(); ILease lease = (ILease) obj.GetLifetimeService();

Console.WriteLine("CurrentLeaseTime: {0}",lease.CurrentLeaseTime); Console.WriteLine("CurrentState: {0}",lease.CurrentState);

Remoting unter .NET


11
Console.WriteLine("RenewOnCallTime: {0}",lease.RenewOnCallTime); Console.WriteLine("SponsorshipTimeout: {0}",lease.SponsorshipTimeout); Console.WriteLine("InitialLeaseTime: {0}",lease.InitialLeaseTime); ... ber die Methode obj.GetLifetimeService() (von MarshalByRefObject geerbt) kann ein Lease-Objekt erzeugt werden und die Eigenschaften ber dieses Objekt ausgelesen werden. Auf dem Bildschirm haben Sie dann folgende Ausgabe:

409

Lease-Times Abb. 11.7

Sie sehen, dass die Ablaufzeit 5 Minuten betrgt. Die noch restliche Zeit betrgt ungefhr 4.59 Minuten. Der Status ist aktiv, d.h. die Zeit ist nicht abgelaufen. Bei jedem Aufruf verlngert sich die Ablaufzeit um 2 Minuten. Bezglich SponsorshipTimeout hren Sie spter mehr. Wenn Sie die Geduld haben, fnf Minuten abzuwarten, ohne allerdings einen Aufruf zu generieren, werden Sie bemerken, dass das Objekt im Server nun vom Client getrennt wurde, und ein Aufruf des Clients hat eine Exception zur Folge, da keine Verbindung zum Serverobjekt mehr besteht. Das gezeigte Modell der Verwaltung der Lebensdauer eines Objektes im Server ist eine bedeutende Verbesserung gegenber DCOM. Bricht einmal eine Verbindung ab (z.B. weil ein Client auf einem anderen Rechner abstrzt), werden eventuell gehaltene Ressourcen im Server wieder freigegeben.

C#
410

11 LifetimeServices
Durch die Klasse LifetimeServices im Namensraum System.Runtime.Remoting.Lifetime knnen fr einen Server die Zeiten global eingestellt werden. Rufen Sie in der Main()-Methode des Servers folgende Zeilen auf:
CD-Beispiel RemServer7

static void Main() { LifetimeServices.LeaseTime = TimeSpan.FromMinutes(20); LifetimeServices.RenewOnCallTime = TimeSpan.FromMinutes(10); LifetimeServices.SponsorshipTimeout = TimeSpan.FromMinutes(5); ... ... } Achtung, Sie mssen noch den Namensraum System.Runtime.Remoting.Lifetime ffnen! Sie erhalten nun in der Ausgabe des Clients die neuen Lease-Zeiten aufgelistet.

Neue Lease-Times Abb. 11.8

Es ist auch mglich, die Lease-Zeiten fr ein Objekt spezifisch zu definieren. Das muss allerdings in der Definition des Remote-fhigen Objektes geschehen. Durch das berschreiben der Methode InitializeLifetimeService der Basisklasse MarshalByRefObj knnen fr die Initialisierung der Lebensdauer spezifische Werte angegeben werden. (Achtung: Dies geschieht im Projekt

Remoting unter .NET


11
FolderFileEnum. Vergessen Sie nicht in der Quellcodedatei den Namensraum System.Runtime.Remoting.Lifetime freizugeben.) public class FolderFileEnum:MarshalByRefObject { public override Object InitializeLifetimeService() { ILease lease = (ILease)base.InitializeLifetimeService(); if (lease.CurrentState == LeaseState.Initial) { lease.InitialLeaseTime= TimeSpan.FromSeconds(10); lease.SponsorshipTimeout = TimeSpan.FromMinutes(2); lease.RenewOnCallTime = TimeSpan.FromSeconds(2); } return lease; } ... ... } In diesem Code-Beispiel wird die InitialLeaseTime auf 10 Sekunden gestellt. Hier wird die Verbindung schon nach 10 Sekunden abgebrochen.
CD-Beispiel FolderFileEnum8

411

Objekt-spezifische Lease-Times Abb. 11.9

Mit Ausnahme der Renew-Zeit knnen die Lease-Zeiten eines Objektes nur dann gendert werden, wenn sich das Objekt im Status LeaseState.Initial befindet. Wrde das nicht beachtet

C#
412

11
werden, so lst die .NET-Laufzeitumgebung eine Exception aus. Dies ist auch der Grund, warum in der obigen Implementierung auf diesen Status geprft wird. Die Lease-Zeit RenewOnCallTime kann aber jederzeit neu belegt werden, auch im Client. Allerdings darf diese Lease-Zeit nie kleiner gemacht werden. Dies kann dann in folgender Form geschehen: ILease lease = (ILease) obj.GetLifetimeService(); lease.Renew(TimeSpan.FromMinutes(20.0));

Sponsor
Ein Sponsor ist ein Objekt, das die Schnittstelle ISponsor implementiert. Hier ein Beispiel:
CD-Beispiel FolderFileEnum9

[Serializable] public class Sponsor: MarshalByRefObject,ISponsor { public TimeSpan Renewal(ILease l) { Console.WriteLine("Renewal wurde aufgerufen"); return new TimeSpan(0,0,0,5,0); } } Fgen Sie diese Klasse im Projekt FolderFileEnum in der Datei FolderFileEnum.cs hinzu. Die Schnittstelle ISponsor besitzt genau eine Methode Renewal. Wenn ein Objekt einen Sponsor registriert, dann wird die Methode Renewal der Schnittstelle ISponsor vom LeaseTime-Manager der .NET-Laufzeitumgebung aufgerufen, wenn die Lease-Zeit abgelaufen ist. Der Sponsor gibt ein TimeSpanObjekt mit der neuen Lease-Zeit zurck. Diese wird dann vom Lease-Time-Manager verwendet, um die neue Lease-Zeit des Objektes festzulegen. Im Beispiel wird zu Testzwecken noch eine Ausgabe auf die Konsole durchgefhrt. Dieses SponsorObjekt kann im Allgemeinen berall verteilt im Netzwerk sein. Allerdings wird verlangt, dass die Sponsor-Klasse Remote-fhig ist. Sollte das Sponsor-Objekt nicht antworten, wird das Remote-Objekt nach Ablauf der SponsorshipTimeout von der .NET-Remoting-Laufzeitschicht getrennt.

Remoting unter .NET


11
public override Object InitializeLifetimeService() { ILease lease = (ILease)base.InitializeLifetimeService(); if (lease.CurrentState == LeaseState.Initial) { lease.InitialLeaseTime = TimeSpan.FromSeconds(10); lease.SponsorshipTimeout = TimeSpan.FromMinutes(2); lease.RenewOnCallTime = TimeSpan.FromSeconds(2); } lease.Register(new Sponsor()); return lease; } In der virtuellen Methode InitializeLifetimeService setzen Sie in diesem Beispiel die InitialLeaseTime auf 10 Sekunden. Mit der Methode Register der Schnittstelle ILease wird nun ein Sponsor-Objekt zugeordnet.

413

Lease-Time Sponsoring Abb. 11.10

Nach Ablauf der Lease-Zeit ruft der Lease-Time-Manager der Laufzeitumgebung den Sponsor auf, um einen neue Lease-Zeit zu erfragen. Sie sehen im Experiment deutlich, wann diese Anfrage auftritt, da Sie eine Konsolenausgabe darauf programmiert haben. Zugegeben, die Implementierung des Sponsor-Objektes ist im Beispiel ziemlich einfallslos, da immer die gleiche Zeit zur Verfgung gestellt wird. Dies kann natrlich dynamisch und in Abhngigkeit von anderen Zustnden geschehen.

C#
414

11

Konfigurieren des Servers und des Clients ber Konfigurationsdateien


Smtliche Remote-Einstellungen vom Server als auch Client lassen sich ber Konfigurationsdateien deklarativ verwalten. Sollten Sie sich im Rahmen des Studiums diese Buches noch nicht mit Konfigurationsdateien beschftigt haben, sollten Sie zuerst das Kapitel 7: Konfigurationsdateien durchlesen. Beachten Sie im Folgenden, dass XML-Texte case-sensitiv sind!

Server-Konfiguration
Erstellen Sie im Ordner c:\RemServer (dies ist der AusgabepfadOrdner des Server-Beispiels) eine XML-Datei mit dem Namen Remserver.exe.config und geben Sie folgende Konfiguration in XML ein. Sie knnen die XML-Datei natrlich aus dem Entwicklungssystem heraus erstellen, und damit die sehr angenehmen XML-Features des Editors nutzen (ber Menpunkt Datei > Neu > Datei > XML-Datei).
Auf der CD zu finden im Projekt RemServer10

<?xml version="1.0"?> <configuration> <system.runtime.remoting> <application name="TestServer"> <channels> <channel ref="http" port="8085" /> <channel ref="tcp" port="8084" /> </channels> <service> <activated type="FolderFileEnum, FolderFileEnum" /> <wellknown mode="SingleCall" type="FolderFileEnum, FolderFileEnum" objectUri="FFE" /> </service> <lifetime leaseTime="20M" sponsorshipTimeout="10M" renewOnCallTime="3M" />

Remoting unter .NET


11
</application> </system.runtime.remoting> </configuration> Sie wissen, das Tag <configuration> ist das Root-Tag aller Konfigurationsdateien unter .NET. Innerhalb des Tags <system.runtime.remoting> knnen nun smtliche Remoting-spezifische Einstellungen deklarativ durchgefhrt werden. <application name="TestServer"> Dieser Name (der Applikation) ist vor allem bei Client-aktivierten Objekten sehr wichtig, weil er einen Bestandteil der URI darstellt. <channels> <channel ref="http" port="8085" /> <channel ref="tcp" port="8084" /> </channels> Innerhalb von <channels> knnen nun beliebig viele Kanle geffnet werden. Jeder Kanal wird in einem eigenen Tag <channel> definiert. Sie geben hier den Typ des Kanals an (ref="http bzw. ref="tcp) und den Port dieses Kanals. Im Beispiel wurden zwei Kanle geffnet. <service> <activated type="FolderFileEnum, FolderFileEnum" /> <wellknown mode="SingleCall" type="FolderFileEnum, FolderFileEnum" objectUri="FFE" /> </service> Innerhalb des Tags <service> lassen sich nun diejenigen Klassen angeben, die der Server per Remoting anbieten soll. In diesem Fall sollte der Server die FolderFileEnum-Objekte einmal als Client-aktivierte Objekte (<activated>) und einmal als Server-aktivierte Objekte <wellknown> bereitstellen. Lassen Sie sich nicht wegen des zweimaligen Vorkommens von FolderFileEnum irritieren. In den Tags wird der Name der Klasse, die der Server bereitstellt angegeben und anschlieend das Assembly, in welchem die Klasse definiert ist. Im speziellen Fall stimmen Assembly-Name und Klassennamen berein!

415

C#
416

11
Im Tag <wellknown> kann darber hinaus noch ein Modus angegeben werden (SingleCall bzw. Singleton) und die Eigenschaft objectUri. <lifetime leaseTime="20M" sponsorshipTimeout="10M" renewOnCallTime="3M" /> Im Tag <lifetime> knnen die spezifischen Werte der Lebensdauerverwaltung deklarativ eingestellt werden. Im speziellen Fall wurden alle Angaben in Minuten durchgefhrt. berraschend einfach gestaltet sich nun die Implementierung des Servers. Sie reduziert sich auf den Einzeiler RemotingConfiguration.Configure( "RemServer.exe.config"); in der Hauptmethode Main(). Der Rest des Codes ist dafr verantwortlich, dass das Serverprogramm weiterlebt bzw. definiert beendet werden kann.
CD-Beispiel RemServer10

using System; using System.Runtime.Remoting; class Server { static void Main() { RemotingConfiguration.Configure( "RemServer.exe.config"); Console.WriteLine( "FolderFileServer gestartet..."); Console.WriteLine("<enter> um zu beenden..."); Console.ReadLine(); } }

Client-Konfiguration
Betrachten Sie nun die clientseitigen Einstellungen ber die Konfigurationsdatei. Auch hier erzeugen Sie im Ordner der Client-Applikation (c:\TestClient) eine XML-Datei mit dem Namen TestClient.exe.config und fgen folgende XML-Elemente hinzu.

Remoting unter .NET


11
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.remoting> <application> <client url="http://localhost:8085/TestServer"> <wellknown type="FolderFileEnum,FolderFileEnum" url="http://localhost:8085/FFE" /> </client> </application> </system.runtime.remoting> </configuration> Neu ist hier das Tag <client>. In diesem werden im Wesentlichen den Ort des Servers (in Form einer url) als auch den Typ (Client-aktiviert oder Server-aktiviert). Hier die zwei Mglichkeiten: <client url="http://localhost:8085/TestServer"> <wellknown type="FolderFileEnum,FolderFileEnum" url="http://localhost:8085/FFE" /> </client> bzw. <client url="http://localhost:8085/TestServer"> <activated type="FolderFileEnum,FolderFileEnum" /> </client> Auch der Code im Clientprogramm reduziert sich auf einen Einzeiler: public static int Main(string [] args) { RemotingConfiguration.Configure( "TestClient.exe.config"); FolderFileEnum obj = new FolderFileEnum(); ... ... } Smtliche Objekte in der Konfigurationsdatei definierten Objekte knnen nun per new-Operator angelegt werden.

417

Auf der CD zu finden im Projekt TestClient10

C#
418

11
Achten Sie auch darauf, dass die XML-Konfigurationsdateien sich auch im selben Ordner befinden, wie die ausfhrbare exeDatei. (Beachten Sie dies insbesondere, wenn Sie die Beispiele von der CD herunterladen!)

IIS Hosting
Wenn Sie schon mit dem DCOM-Programmiermodell (Distributed Component Object Model) gearbeitet haben, dann vermissen Sie bisher sicherlich das Feature der Selbstaktivierung. Wenn unter DCOM eine Anfrage an ein Serverprogramm gettigt wird und das Serverprogramm nicht gestartet ist, dann wird das Programm von der COM-Laufzeitschicht gestartet. Dies ist bei den Beispielen nicht der Fall. Der Server muss explizit gestartet werden und gestartet sein, wenn Clients seine Dienste in Anspruch nehmen wollen. Es gibt auch keinen vergleichbaren Ansatz der Selbstaktivierung in der .NET-Laufzeitumgebung. Exe-Server in dieser Form werden self-hosted .NET servers genannt. Alle self-hosted Server mssen per Hand gestartet werden. .NET-Remote-Objekte knnen aber auch in anderen Applikationen leben. So z.B. in einem Windows-Service, welcher automatisch beim Booten des Systems startet. .NET Remote-Objekte knnen auch im IIS (Internet Information Server) installiert werden. In diesem Fall wird der Server automatisch gestartet. Zustzlich knnen Sie smtliche Sicherheitsmechanismen des IIS nutzen. Im Folgenden wird das Beispiel FolderFileEnum ber den IIS Client zur Verfgung gestellt. Voraussetzung fr dieses Beispiel ist natrlich, dass der IIS auf Ihrem System installiert ist. Legen Sie einen Ordner mit dem Namen FolderFileEnumIIS auf Ihrem Rechner an. In diesem Ordner erzeugen Sie dann einen Unterordner mit dem Namen \bin und kopieren in diesen Ordner das Assembly FolderFileEnum.dll. Im Ordner FolderFileEnumIIS erzeugen Sie eine Datei mit dem Namen Web.Config und geben folgende Konfigurationseinstellungen ein:
CD-Beispiel FolderFileEnumIIS

<configuration> <system.runtime.remoting> <application> <service>

Remoting unter .NET


11
<wellknown mode="SingleCall" objectUri="FFES.rem" type="FolderFileEnum, FolderFileEnum" /> </service> </application> </system.runtime.remoting> </configuration> Starten Sie nun den Internetdienste-Manager (ber Programme > Verwaltung bzw. Start > Einstellungen > Systemsteuerung > Verwaltung).

419

Internetdienste-Manager Abb. 11.11

Markieren Sie Standardwebsite und erstellen Sie im Kontextmen unter Neu > Virtuelles Verzeichnis eine neues virtuelles Verzeichnis. Geben Sie dem Verzeichnis den Alias-Namen FolderFileEnumServer und weisen Sie diesem das physische Verzeichnis FolderFileEnumIIS zu. Die Zugriffsberechtigung beschrnken Sie auf Ausfhren. Im Internetdienste-Manager erscheint nun zustzlich dieses virtuelle Verzeichnis. ber das Kontextmen Eigenschaften knnen Sie smtliche Eigenschaften jederzeit ndern, so auch die unterschiedlichsten Sicherheitsaspekte.

C#
420

11

Zugriffsberechtigungen einschrnken Abb. 11.12

Eigenschaften-Dialog eines Webfolders Abb. 11.13

Remoting unter .NET


11
Die neuen Einstellungen fr das Clientprogramm knnen Sie nun deklarativ ber die Datei TestClient.exe.config einstellen. Im Folgenden die Werte: <?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.remoting> <application> <client> <wellknown url="http://localhost /FolderFileEnumServer/FFES.rem" type="FolderFileEnum, FolderFileEnum" /> </client> </application> </system.runtime.remoting> </configuration> Der IIS horcht auf Port 80 auf Anfragen. Diese Portnummer mssen Sie nicht explizit angeben. Beachten Sie unbedingt, dass der Name fr die objectUri mit der Dateierweiterung .rem bzw. .soap ausgestattet wird. Ansonsten funktioniert das Beispiel nicht. Der IIS verlangt dies. Keine Ahnung warum, es ist einfach so! Das Ganze funktioniert natrlich auch ber das Netzwerk. Statt localhost geben Sie die IP-Adresse oder aber den DNS-Namen des Servers an. Der IIS kann nur Server-aktivierte Objekte bereitstellen.
Auf der CD zu finden im Projekt TestClient11

421

Asynchrone Aufrufe
Denken Sie an eine Client-Server-Applikation. Der Client ruft eine Methode eines Objektes auf, das im Server lebt. Weiterhin angenommen, dass die Durchfhrung der Methode eine lngere Zeit in Anspruch nimmt. Der Client-Thread ist nun solange gesperrt, bis das Rckgabeergebnis des Aufrufes ber den Remote-Kanal bertragen wurde. Diese Art von Aufrufen wird synchron genannt. Die Zeit zwischen Aufruf und Entgegennahme von Rckgabewerten des Aufrufes knnte vom Client aber fr andere sinnvolle Arbeit genutzt werden. In diesem Fall mssen Sie den Aufruf asynchron gestalten. Ein asynchroner

C#
422

11
Aufruf kann in zwei logische Teile aufgesplittert werden. Der erste Teil startet den asynchronen Aufruf, der zweite Teil verwaltet in irgendeiner Form das Ergebnis des Aufrufes. Am besten Sie experimentieren an einem Beispiel:
CD-Beispiel TestClient12

using using using using using using using using

System; System.IO; System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; System.Runtime.Remoting.Channels.Http; System.Runtime.Remoting.Activation; System.Runtime.Remoting.Lifetime;

public class Client { private delegate string [] GetFolderDel(string root,string search); public static int Main(string [] args) { RemotingConfiguration.Configure( "TestClient.exe.config"); FolderFileEnum obj = new FolderFileEnum(); GetFolderDel del = new GetFolderDel(obj.GetFolders); IAsyncResult ar = del.BeginInvoke("c:\\","*.*",null,null); //mach was sinnvolles ar.AsyncWaitHandle.WaitOne(10000,false); if(ar.IsCompleted) { string [] f = del.EndInvoke(ar); for(int i=0;i<f.Length;i++) Console.WriteLine(f[i]); } Console.Read();

Remoting unter .NET


11
return 0; } } Asynchrone Aufrufe lassen sich unter C# sehr einfach ber Delegates durchfhren. Fr die Methode, die Sie asynchron aufrufen wollen, wird zuerst ein Delegate erzeugt. (Sollten Sie mit Delegates noch nicht vertraut sein, dann lesen Sie das Kapitel 1: Einleitung nach.) private delegate string [] GetFolderDel(string root,string search); Im Hauptprogramm wird dann ein Objekt vom Typ FolderFileEnum erzeugt, ein Objekt vom Typ Delegate, und dieses wird dann mit der Methode des Objektes initialisiert. GetFolderDel del = new GetFolderDel(obj.GetFolders); ber dieses Delegate kann nun die Methode BeginInvoke aufgerufen werden. Beachten Sie, dass diese Methode dieselben Parameter aufweist wie die Delegate-Definition, zustzlich zwei Parameter, welche Sie in diesem Beispiel mit null belegen. Zu diesen Parametern aber spter. IAsyncResult ar = del.BeginInvoke("c:\\","*.*",null,null); Diese Methode gibt ein Objekt vom Typ IAsyncResult zurck. ber dieses Objekt knnen Sie spter das Ergebnis des Aufrufes erfahren. Damit ist ein Aufruf abgesetzt und der Client kann eine beliebige Arbeit fortsetzen. Nachdem diese Arbeit beendet ist, htte der Client nun Zeit, das Rckgabeergebnis auszuwerten. Aber vielleicht ist der Remote-Aufruf noch gar nicht zu Ende, und so macht es viel Sinn, eben zu warten, bis das Rckgabeergebnis auch tatschlich vorhanden ist. ar.AsyncWaitHandle.WaitOne(10000,false); WaitOne(), eine Methode des eingebetteten Objektes AsyncWaitHandel, stellt ein Synchronisationsobjekt dar. Durch diese Methode wird der aktuellen Thread in den Suspended Modus (verbraucht damit auch keine Rechenzeit) und wacht erst wieder auf, wenn das Ereignis (Rckgabeergebnis eingetrof-

423

C#
424

11
fen) oder aber das Timeout, welches in Form von ms bei der Methode WaitOne(...) mitgegeben wird, abgelaufen ist. ber ar.IsCompleted knnen Sie sich vergewissern, dass der Aufruf komplett durchgefhrt ist und knnen dann ber das Delegate die Methode EndInvoke(...) aufrufen. Diese gibt dann das Rckgabeergebnis preis. Eine Methode, mit einem void-Return Wert und mit ausschlielichen Input-Parametern kann mit dem [OneWay]-Attribut ausgestattet werden. Damit wird die Methode automatisch asynchron aufgerufen (fire and forget). [OneWay] public void Send(string mes) { //... } Damit das Attribut erkannt wird, mssen Sie den Namensraum System.Runtime.Remoting.Messaging ffnen. Der Aufruf BeginInvoke(...) kann auch in einer anderen Form gestaltet werden. Hier kommen nun die schon erwhnten zustzlichen Parameter ins Spiel. public class Client { private delegate string [] GetFolderDel(string root,string search); public static int Main(string [] args) { RemotingConfiguration.Configure( "TestClient.exe.config"); FolderFileEnum obj = new FolderFileEnum(); GetFolderDel del = new GetFolderDel(obj.GetFolders); AsyncCallback cb = new AsyncCallback(Result); IAsyncResult ar = del.BeginInvoke("c:\\","*.*", cb,null); //Mach was Console.ReadLine();

Remoting unter .NET


11
return 0; } [OneWay] static public void Result(IAsyncResult ar) { GetFolderDel cb = (GetFolderDel) ((AsyncResult)ar).AsyncDelegate; string [] f = (string[])cb.EndInvoke(ar); for(int i=0;i<f.Length;i++)Console.WriteLine(f[i]); } } Bevor Sie die Methode BeginInvoke(...) aufrufen, erzeugen Sie aber erst ein Objekt vom Typ AsyncCallback. Der Konstruktor dieser Klasse verlangt nach einer Funktion (in Form einer Methode eines Objektes oder aber auch in statische Ausfhrung, wie bei diesem Beispiel). Diese Funktion wird dann vom Server aufgerufen, sobald die Arbeit beim Server beendet ist. AsyncCallback cb = new AsyncCallback(Result); Diese Funktion (im Beispiel Result) muss allerdings einem gewissen Prototyp gehorchen. [OneWay] static public void Result(IAsyncResult ar) { GetFolderDel cb = (GetFolderDel) ((AsyncResult)ar).AsyncDelegate; string [] f = (string[])cb.EndInvoke(ar); for(int i=0;i<f.Length;i++)Console.WriteLine(f[i]); } Da diese Funktion kein Rckgabeergebnis hat, knnen Sie diese mit dem Attribut [OneWay] ausstatten. Damit ruft der Server diese Methode dann ebenfalls asynchron auf, was den Server natrlich entlastet. In der Parameterliste der Funktion bekommen Sie eine Schnittstelle IAsyncResult mit. Dieses casten Sie auf ein AsyncResult-Objekt und knnen dann ber die Eigenschaft AsyncDelegate, die genau das Delegate referenziert, das auch die Methode BeginInvoke(...) aufgerufen hat, die Methode EndInvoke(...) aufrufen.

425

C#
426

11

Events Remote-fhiger Objekte


Remote-Objekte knnen auch Events implementieren, und zwar in gleicher Weise wie Sie diese im Kapitel 1 kennen gelernt haben. Es ist nur zu beachten, dass das Remote-Objekt die Liste der angemeldeten Clients halten muss. Das ist auch der Grund, warum in diesem Fall die Objekte nicht Server-aktivierbar sein drfen, sondern nur Client-aktivierbar!

bergabe von Objekten in RemoteMethoden


Bisher wurden noch keine nheren berlegungen in Bezug der bergabe von Objekten in Remoting-Methoden-Aufrufen gettigt. Auch diese Objekte mssen ja ber den Kanal bertragen werden. Bisher hatten Sie vornehmlich C#-Grunddatentypen verwendet, aber es gibt hier keine Beschrnkung auf die Grunddatentypen. Das .NET-Remoting-Modell unterscheidet zwei Arten der bertragung von Objekten in der Parameterliste von Remoting-Methoden. marshal by referenz Nur Objekte von Klassen, die selbst von MarshalByRefObjekt abgeleitet sind, knnen in Form einer Referenz bertragen werden. Dabei wird nicht das Objekt bertragen (dieses lebt remote), sondern ein Proxy. marshal by value Damit Objekte einer Klasse auch als Teilnehmer von RemoteMethoden arbeiten knnen, mssen diese die Schnittstelle ISerialize implementieren oder aber mit dem Attribute [Serializable] gekennzeichnet sein. Objekte dieser Art haben leben nicht remote, sondern werden immer als ganze Objekte bertragen. Smtliche Objekte, die nicht serialisierbar sind bzw. nicht von MarshalByRefObjekt abgeleitet sind, knnen nicht als Parameter bzw. Rckgabewerte von Remote-Methoden arbeiten!

Remoting unter .NET


11
427

Zusammenfassung
In diesem Kapitel haben Sie die .NET-Remoting-Architektur kennen gelernt. Das ist eine robuste Architektur, die auch mit instabilen Verbindungen zurecht kommt. Vor allem aber werden Standard-Protokolle wie XML und http untersttzt. Damit knnen Win32-Prozesse auch ber die Internetschiene hinweg kommunizieren. Im Gegensatz zu den Web-Services mssen Sie aber alle Sicherheitsaspekte selbst verwalten! Seien sich dessen bewusst! Daher wird die Interprozesskommunikation auf Basis der .NETRemoting-Architektur sich eher im Intranet abspielen. Fr die anderen Flle sind Web-Services meist die bessere Lsung, auch wenn mit Web-Services nicht alles realisiert werden kann, was die .NET-Remoting-Architektur anbietet.

.NET-Klassen

Einleitung Die Klasse System.Object Die Klasse System.String Die Klasse System.IO.Path Die Klassen DirectoryInfo,FileInfo Die Klasse WebClient

430 431 435 436 439 440

C#
430

12

Einleitung

.NET-Klassenstruktur Abb. 12.1

In Abbildung 12.1 ist der prinzipielle Aufbau der .NET-Klassenstruktur abgebildet. Alle Klassen unter .NET basieren auf der Common Language Runtime (CTS). Fr Programmierer sind die Klassen im Common Type System (CTS) von zentraler Bedeutung. Hier sind alle Grunddatentypen definiert, allen voran System.Object. Alle weiteren Typen unter .NET basieren auf diesen Klassen. Smtliche .NET-Sprachen basieren auf diesem Typ-System, und das ist auch der Grund, warum Klassen ber die Sprachgrenzen hinweg verwendbar sind. Oberhalb der CLR sind die Basisklassen der .NET-Laufzeitumgebung angeordnet. Threading, Sicherheit und Ein/AusgabeFunktionalitt sind hier untergebracht. Aber Sie sehen auch, dass ADO.NET-Klassen, Webzugriffsklassen und XML hier implementiert sind. Diese gehren also zur Laufzeitumgebung. Hier finden sich also Klassen, die in einer Multi-Tier-Architektur typischerweise der Service-Schicht zugeordnet wrden. ber der Service-Schicht befinden sich Klassen zur Erzeugung von grafischen Oberflchen (ASP.NET und Window Form). Im Block Window Form sind eine Reihe von Klassendefinitionen zusammengefasst, die fr die klassische Fenster-Programmierung notwendig sind. Theoretisch ist auch eine Portierung die-

.NET-Klassen
12
ser Klassen auf ein anderes Betriebssystem denkbar. Die Klassenarchitektur von ASP.NET ist speziell fr die Zusammenarbeit mit dem IIS (Internet Information Server) gedacht. Es werden in diesem Kapitel auch noch einige interessante und wichtige .NET-Klassen nher vorgestellt. Die .NET-Laufzeitumgebung bietet eine Unzahl von Klassen und damit Funktionalitt an, alle diese vorzustellen wrde den Rahmen dieses Buches bei Weitem sprengen. Nehmen Sie sich aber hier und da die Zeit, diese Klassen zu erforschen. (nach dem Motto: Jede Woche eine neue Klasse J. Die MSDN stellt hier eine unerschpfliche Quelle dar.

431

Die Klasse System.Object


Im Kapitel 2: C# eine neue Programmiersprache haben Sie schon einiges ber diese Basisklasse gehrt. Hier wird aber auf einige Aspekte dieser Klasse eingegangen. Dass die Klasse System.Object die ultimative Basisklasse aller Objekte darstellt, sollte Ihnen nun bekannt sein. Eine Reihe von Methoden dieser Basisklasse sind virtuell ausgefhrt. In einer Referenz vom Typ System.Objekt kann daher auf jedes beliebige Objekt verwiesen werden. Eine Funktion mit einem bergabeparametertyp System.Object kann auch jeder beliebige Typ mitgegeben werden. (Hinweis: C/C++-Programmierer mussten hier immer auf void* ausweichen.)

ReferenceEquals
Die Methode ReferenceEquals ist eine statische Methode. Sie testet, ob zwei Referenzen auf dasselbe Objekt zeigen. Sie gibt true zurck, wenn die Referenzen denselben Speicherbereich adressieren, ansonsten false. Sie ist statisch und kann daher nicht berschrieben werden. using System; class App { static void Main() { string T1 = "Hallo"; string T2 = T1;
CD-Beispiel SO

C#
432

12

int i=4; int j=i; Console.WriteLine(object.ReferenceEquals(T1,T2)); Console.WriteLine(object.ReferenceEquals(i,j)); } } Der Vergleich von T1 und T2 ergibt den Wert true, weil die Klasse String ein Referenztyp darstellt. Der Vergleich von i und j gibt den Wert false. Es handelt sich um zwei unterschiedliche Speicherbereiche (der Grund liegt darin, dass der Datentyp int eine Wert-Typ darstellt).

Equals
System.Object implementiert Equals als statische als auch als nichtstatische Methode. public static bool Equals (object o1,object o2); public bool Equals (object o); Die nichtstatische Methode Equals ist virtuell berlagerbar. Die Default-Implementierung gibt den Wert true zurck, wenn die zu vergleichenden Referenenzen auf dasselbe Objekt verweisen. Mchten Sie ein anderes Verhalten haben, dann muss diese Methode berschrieben werden. Das ist bei der Klasse System.Value geschehen. Die Implementierung gibt dann true zurck, wenn die Objekte binr gleich sind. Im Kapitel 2: C# eine neue Programmiersprache haben Sie eine Klasse Fraction programmiert. Diese Klasse wurde mit dem C#-Schlsselwort class definiert und stellt somit eine Referenzklasse dar. Da Fraction einen mathematischen Datentyp modelliert, ist die Default-Implementierung von Equals ungnstig. Mathematische Gleichheit ist dann vorhanden, wenn zwei Objekte denselben Wert reprsentieren. Beim Typ Fraction gengt nicht einmal die Prfung auf binre Gleichheit, das die Bruchzahlen 4/2 und 8/4 mathematisch gleich sind. Daher knnte eine Implementierung wie folgt aussehen:

.NET-Klassen
12
public override bool Equals(object r) { Fraction t = this / (Fraction) r; if(t.z == t.n) return true; else return false; } Es werden die Objekte dividiert. Wenn das Ergebnis 1 (Zhler und Nenner sind gleich) ist, dann sind die Werte mathematisch gleich. Die statische Variante von Equals sollte dasselbe machen wie die nichtstatische. Warum ist aber dann eine statische Methode Equals berhaupt notwendig? Der Unterschied liegt darin, dass damit auch null-Referenzen verwendet werden knnen. Die Verwendung der statischen Methode ist somit um einiges sicherer! Die virtuelle Methode Equals wird sehr intensiv von anderen Klassen der .NET-Laufzeitumgebung verwendet. Vor allem, wenn eine Klasse als Schlssel (key) verwendet wird (z.B. in einer Hash-Tabelle), dann kommt es zu intensiven Aufrufen dieser Methode. Beachten Sie dies, da Sie hier einen groen Einfluss auf das Laufzeitverhalten ausben knnen. In welchem Zusammenhang steht die Methode Equals mit dem Vergleichsoperator ==? Sie mssen diesen Operator ebenfalls berlagern. Implementieren Sie den Vergleichsoperator einer Klasse aber nicht mit der nichtstatischen Methode Equals. if(null == obj)... Diese Zeile wrde dann nmlich eine Exception auslsen, weil auf null die Methode Equals aufgerufen wrde. Machen Sie es lieber umgekehrt. Implementieren Sie den Vergleichsoperator und verwenden den Vergleichsoperator zur Implementierung der Equals-Methode.

433

GetHashCode()
System.Object implementiert die Methode GetHashCode(). Ein Hash ist ein Integerschlssel (key) fr eine spezielle Instanz einer Klasse. Dieser Schlssel ist sozusagen eine auf einen Integerwert reduzierte Version des Inhalts des Objektes. Hashes

C#
434

12
sind fr viele Algorithmen, vornehmlich fr Sortier- und Suchalgorithmen von groem Vorteil. Damit knnen diese Ablufe entscheidend beschleunigt werden. Das .NET-Framework besitzt solche Hashtable-Klassen und diese verwenden intensiv die Methode GetHashCode(). Damit sind sehr schnelle Sortieralgorithmen mglich. Prinzipiell gilt folgende Regel: Wenn zwei Objekte bei einem Vergleich ber die Methode Equals als gleich gelten, dann sollten diese auch denselben Hashcode zurckgeben. Die Default-Implementierung gibt fr jedes Objekt einen unterschiedlichen Hashcode zurck. Es wird angeraten, auch die Methode GetHashcode zu berschreiben, wenn die Methode Equals berschrieben wird.

ToString()
Diese Methode ist virtuell ausgefhrt und sollte einer sinnvollen Text-Reprsentierung des Objektes entsprechen. Wenn Sie diese Methode nicht berschreiben, wird der Klassenname als String zurckgegeben.

GetType()
ber die Methode GetType kann jederzeit ein Objekt vom Typ System.Type erzeugt werden. ber dieses Objekt kann dann auf die Metadaten der Klasse zugegriffen werden. Die Methode GetType kann nicht berschrieben werden.

Finalize()
Die Methode Finalize wird vom Garbage Collector (GC) aufgerufen, kurz bevor es entsorgt wird. Sie ist mit dem Destruktor aus C++ vergleichbar. Um diese Methode zu berschreiben, mssen Sie sich unter C# der Destruktorsyntax bedienen (public ~Fraction()...). Sie haben keine Kontrolle darber, wann der Aufruf von Finalize erfolgt. Aus diesem Grund ist das Verlagern von Aufrumarbeiten, wie z.B. Schlieen von Datenbanken, Dateien etc. in die Methode Finalize nicht zu empfehlen. In dieser Hinsicht unterscheidet sich C# grundstzlich von C++. Besser ist, wenn die Arbeit in Methoden wie z.B. Close implementiert wird und diese dann explizit aufgerufen werden.

.NET-Klassen
12
435

Die Klasse System.String


Smtliche Strings unter .NET sind Instanzen der Klasse System.String (C#-Schlsselwort string). Strings werden unter .NET immer in Unicode gehalten. Das gilt brigens auch fr den Datentyp System.Char. Da unter C# char ein Synonym fr System.Char darstellt, mssen vor allem C/C++-Programmierer aufpassen. Eine Variable vom Typ char verbraucht zwei Bytes, da ein Unicode-Zeichen untergebracht werden muss. Wenn Sie ein Byte brauchen, dann verwenden Sie System.Byte bzw. das C#-Schlsselwort byte. Nun aber wieder zurck zu den Strings. Die Klasse String ist immutable, auf deutsch unabnderlich. D.h., dass smtliche Operationen auf ein String-Objekt ein modifiziertes Objekt zurckgeben, und nicht das Objekt selbst modifizieren. Um das zu verdeutlichen ein kleines Beispiel: using System; class App { static void Main() { string Text = "Hallo"; Console.WriteLine(Text); Text.ToUpper(); Console.WriteLine(Text); string NewText = Text.ToUpper(); Console.WriteLine(NewText); } } Sie sehen deutlich, dass der Methodenaufruf das Objekt selbst nicht ndert. Die Methode gibt einen modifizierten neuen String als Rckgabewert zurck.
CD-Beispiel String1

Split
Die Klasse implementiert einer Reihe von Methoden. Als sehr angenehm erweist sich die berlagerung des +-Operators. Es werden jetzt nicht alle Methoden der Klasse System.String aufgelistet. Sie sind selbsterklrend. Erwhnenswert ist allerdings

C#
436

12
die Methode Split, die dem Autor jedenfalls schon unzhlige Stunden an Arbeit erspart hat.
CD-Beispiel String2

using System; class App { static void Main() { string Text = "george.bush@washington.com"; char [] sep = new Char[]{'.','@'}; string [] parts = Text.Split(sep); for(int i=0;i<parts.Length;i++) Console.WriteLine(parts[i]); } } Die Methode Split verlangt als bergabeparameter ein Feld aus Separatoren. Die Anzahl ist beliebig. Der String wird dann in Einzelstrings zerlegt und in einem Feld untergebracht. Im Beispiel wird also die E-Mail-Adresse in einzelne Strings aufgesplittet (george, busch, washington, com). Dadurch, dass das System.String ein immutable-Typ darstellt, knnten intensive Stringverarbeitungen (vor allem dann, wenn Strings verndert werden mssen) ineffizient sein. Fr diesen Fall bietet die .NET die Klasse StringBuilder an. Hier knnen Sie einzelne Zeichen manipulieren. Mehr dazu erfahren Sie in der MSDN.

Die Klasse System.IO.Path


Haben Sie sich schon einmal mit Pfaden, Dateinamen, Dateinamenserweiterungen usw. herumgeschlagen? Eine mhsame Sache! Ein Beispiel gefllig? C:\Eigene Dateien\Bilder\Foto.jpg Hier haben Sie einen String, der eine .jpg-Datei in einem Ordner auf dem Datentrger C: bezeichnet. Wenn Sie nun den Dateinamen ohne Dateinamenserweiterung aus diesem String extrahieren wollen (Foto), mssen Sie in der Stringverarbeitung schn fit sein. Vermutlich werden Sie das Zeichen '.' (Punkt) im String suchen, dann das Zeichen '\' (Backslash)

.NET-Klassen
12
und die dazwischenliegenden Zeichen als den Dateinamen interpretieren. Wurde vielfach so programmiert und hat auch funktioniert, bis dann Microsoft das Zeichen '.' auch in Dateinamen erlaubte (z.B. Foto.Urlaub.jpg). Ab diesem Zeitpunkt funktionierten viele Programme nicht mehr, da, wie Sie sich denken knnen, mit obigem Algorithmus folgenschwere Nebeneffekte entstehen knnen. Hier einige andere, erlaubte Variationen: C:/Eigene Dateien/Bilder/Foto.jpg C:\\EigeneDateien\Bilder/Foto.Urlaub.jpg //Rechnername/c/Eigene Dateien/Foto.Urlaub.jpg Sie sehen, auch das Separator-Zeichen kann unterschiedlich gestaltet sein (Slash, Backslash). All diese Flle zu bercksichtigen ist eine aufwndige Sache, darber hinaus nicht einmal ohne Weiteres auf andere Plattformen portabel, denn wer sagt, dass auch auf anderen Plattformen fr die Separator-Zeichen Slash bzw. Backslash verwendet werden?

437

Die Klasse Path


Um all diesen Schwierigkeiten aus dem Weg zu gehen, bergeben Sie diese Arbeit am besten der Klasse Path aus dem Namensraum System.IO. Diese Klasse bietet eine Menge von ntzlichen statischen Methoden an, die das Programmiererleben verschnern. Die Klasse Path im Namensraum System.IO erlaubt Ihnen einfach und schnell Operationen wie das Extrahieren des Pfades, Dateinamens oder Dateinamenserweiterung sowie das Zusammenstellen von Pfadnamenstrings. Auerdem ist diese Klasse plattformunabhngig gestaltet, d.h. sie verwendet das/die dem Betriebssystem zugrunde liegende(n) Pfad-Separator-Zeichen. Alle Methoden der Path-Klasse sind statisch, und Sie bentigen daher keine Instanz dieser Klasse, um die Methoden zu verwenden. Werfen Sie einmal einen Blick auf einige dieser Methoden (am besten mit einem Beispiel!): using System; using System.IO; class App {
CD-Beispiel Path

C#
438

12
static void Main() { string path = @"c:\\Eigene" + @"Dateien\Bilder\Urlaubsfoto.jpg"; Console.WriteLine(Path.GetFileName(path)); Console.WriteLine(Path.GetExtension(path)); Console.WriteLine( Path.GetFileNameWithoutExtension(path)); Console.WriteLine(Path.GetDirectoryName(path)); Console.WriteLine(Path.GetFullPath(path)); Console.WriteLine(Path.GetPathRoot(path)); Console.WriteLine(Path.DirectorySeparatorChar); Console.WriteLine( Path.AltDirectorySeparatorChar); Console.WriteLine(Path.IsPathRooted(path)); string newPath = Path.Combine("Eigene Dateien","Bilder"); Console.WriteLine(newPath); } } Das Ergebnis auf dem Bildschirm ist selbsterklrend. Urlaubsfoto.jpg .jpg Urlaubsfoto c:\Eigene Dateien\Bilder c:\Eigene Dateien\Bilder\Urlaubsfoto.jpg c:\ \ / True Eigene Dateien\Bilder Es ist ratsam, ausschlielich diese Klasse fr Datei- und Pfadnamensmanipulationen zu verwenden. Neben der Arbeitsersparnis ist ihre Applikation auch plattformunabhngig und wenn vielleicht einmal eine Portierung auf Nicht-MicrosoftBetriebssysteme vorkommen soll, so bereitet ihre Applikation wenigstens bei den Dateisystemmanipulationen keine Probleme.

.NET-Klassen
12
439

Die Klassen DirectoryInfo,FileInfo


Die Klassen Directory, DirectoryInfo, File und FileInfo im Namensraum System.IO bieten Ihnen mchtige Methoden zum Erzeugen, Lschen, Verschieben, Kopieren und Umbenennen von Ordnern und Dateien. Die Klassen Directory und File beinhaltet im Wesentlichen dieselben Funktionalitten wie die Klassen DirectoryInfo und FileInfo, nur dass in den Klassen Directory und File die Methoden statisch sind, und daher keine Objektinstanz fr die Verwendung bentigen. Methoden der Klassen DirectoryInfo und FileInfo sind vorwiegend nicht statisch und knnen daher nur ber eine Objektinstanz aufgerufen werden. Lernen Sie die Verwendung der Klassen, indem Sie in einem kleinen Konsolenprogramm experimentieren. using System; using System.IO;
CD-Beispiel Filehandling

class App { static void Main(string[] args) { DirectoryInfo di = new DirectoryInfo("c:/winnt"); //gibt alle Unterordnernamen von c:/winnt aus DirectoryInfo [] subdis = di.GetDirectories(); for(int ix=0;ix<subdis.Length;ix++) { Console.WriteLine(subdis[ix].Name); } //gibt ebenfalls alle Unterordnernamen in c:/winnt //aus aber hier unter Verwendung der Klasse //Directory string [] strsub = Directory.GetDirectories("c://winnt"); for(int ix=0;ix< strsub.Length;ix++) { Console.WriteLine(strsub[ix]); }

C#
440

12
//gibt alle Filenamen in c:/winnt aus FileInfo [] fis = di.GetFiles(); for(int ix=0;ix<fis.Length;ix++) { Console.WriteLine(fis[ix].Name); } Console.WriteLine(di.CreationTime); Console.WriteLine(di.LastAccessTime); } Sie sehen auch, dass Sie ber Properties auch Zugriff auf Ordner- und Dateieigenschaften wie z.B. LastAcessTime haben. Auch das Erzeugen, Lschen, Kopieren und Verschieben von Ordnern oder Dateien ist mit diesen Klasse keine Hexerei. Hier Codefragmente, die die Verwendung verdeutlichen sollen: File.Copy(@"c:\Urlaubsfoto.jpg",@"c:\Fotos"); File.Delete(@"c:\Urlaubsfoto.jpg"); Experimentieren Sie mit diesen Klassen, damit Sie die Funktionsweise kennen lernen. Nehmen Sie auch bewusst den grundstzlichen Unterschied von Directory/File und DirectoryInfo/FileInfo wahr.

Die Klasse WebClient


Dass Sie irgendwann einmal in Ihrem Programm eine Datei vom Web herunterladen sollten, wird zuknftig immer wahrscheinlicher. .NET bietet dazu die interessante Klasse WebClient an. Die Klasse ist im Namensraum System.Net definiert und im Assembly System.dll implementiert. Nachfolgend wird Ihnen die Verwendung dieser Klasse an einem Beispiel demonstriert.
CD-Beispiel WebClient1

using System; using System.Net; using System.IO; class App { static void Main(string[] args) { WebClient Client = new WebClient();

.NET-Klassen
12
//Speicher Download-Datei ab Client.DownloadFile( @"http://www.teslab.com",@"c:\teslab.htm"); //Dowload-Datei als Stream Stream s = Client.OpenRead(@"http://www.teslab.com"); StreamReader r = new StreamReader(s); while(true) { string l = r.ReadLine(); if(l==null)break; Console.WriteLine(l); } } } Vergessen Sie nicht auf das Assembly System.dll zu verweisen! Daten knnen Sie mithilfe der Klasse WebClient auf zwei Arten programmtechnisch herunterladen. Die Methode DownloadFile speichert die Daten direkt in eine anzugebende Datei ab. Die Methode OpenRead gibt Ihnen einen Stream zurck, den Sie dann ber ein StreamReader-Objekt bearbeiten knnen. Experimentieren Sie mit diesem Beispiel!

441

Ausgewhlte C#-Kapitel

Interoperabilitt mit COM Casting C#-Prprozessor

444 448 452

C#
444

13

Interoperabilitt mit COM


COM (Component Object Model) war bisher das (zumindest teilweise) objektorientierte Programmiermodell auf den Win32-Plattformen. berall auf der Welt wurden Unmengen von Softwarekomponenten auf Basis von COM geschrieben. Microsoft hat groe Anstrengungen unternommen, dass COM-Componenten in der .NET-Umgebung verwendbar sind. Aber auch umgekehrt, .NET-Komponenten sind ebenfalls in einer klassischen COM-Umgebung einsetzbar. COM-Komponenten sind meist in Dlls untergebracht. Damit diese auf einem System verwendbar sind, mssen diese in der Registrierung untergebracht sein. Das geschieht beim Installieren. COMKomponenten exportieren ihre Funktionalitt in Form von Schnittstellen. Auerdem definiert COM auch einen Event-Mechanismus.

Einbinden von ActiveX-Steuerelementen


Die ActiveX-Steuerelemente sind ebenfalls COM-Komponenten. Viele Softwarehersteller haben groe Investitionen in die Programmierung von ActiveX-Steuerelementen gettigt. Wie Sie ActiveX-Steuerelemente in einem Windows-Programm verwenden soll folgendes Beispiel demonstrieren.

SuchmaschinenBrowser Abb. 13.1

Ausgewhlte C -Kapitel
13
Microsoft bietet Programmierern seinen Web-Browser in Form eines ActiveX-Steuerelements an. Dieses soll im Beispielprogramm verwendet werden. In einer TextBox wird ein Suchbegriff eingegeben. Auf Basis dieses Suchbegriffs werden dann die Suchmaschinen Altavista und Google umschaltbar in einem TabControl dargestellt (siehe Abb. 13.1). Legen Sie dazu ein neues Projekt an (Projektvorlage WindowsAnwendung). Nennen Sie das Projekt SearchBrowser. Nennen Sie dann auch die vom Assistenten erzeugten Datei- und Klassennamen um. Im Designer fgen Sie dann ein TabControl ein. Geben Sie dem TabControl den Namen tabBrowsers. Im Eigenschaftsfenster des tabBrowsers knnen Sie nun TabPages zuordnen. Fgen Sie zwei Sichten mit den Namen AltavistaPage und GooglePage hinzu. In diese Sichten mssen Sie nun jeweils das ActiveX-Steuerelement platzieren. Dazu wechseln Sie zur ToolBox und aktivieren im Eintrag Window Forms ber das Kontexmen den Eintrag Toolbox anpassen. Im folgenden Dialog werden alle registrierten ActiveXSteuerelemente aufgelistet.

445

ActiveX-Steuerelement aktivieren Abb. 13.2

Suchen Sie sich das ActiveX-Steuerelement Microsoft WebBrowser und aktivieren Sie dieses (Abb. 13.2). Das Steuerelement erscheint nun in der Toolbox. Sie knnen in gleicher Weise dieses Steuerelement verwenden wie .NET-Steuerele-

C#
446

13
mente. Bei diesem Vorgang erzeugt das Entwicklungssystem eine Wrapper-Klasse, die nach auen eine .NET-Schnittstelle anbietet. Intern greift diese Klasse ber COM-Mechanismen auf das tatschliche Objekt zu. Visual Stuido.NET verpackt diese Klasse dann auch gleich in ein Assembly, das direkt in den Ordner der ausfhrbaren Datei kopiert wird!

Toolbox mit ActiveXSteuerelement Abb. 13.3

Ziehen Sie nun je ein WebBrowser-Steuerelement in die Pages des TabControls. Geben Sie diesen den Namen WebBrowserGoogle und WebBrowserAltavista. Nun noch eine TextBox (tbSearchText) und einen Button (btSearch) einfgen. Mit dem Designer erzeugen Sie auch gleich eine Bearbeitungsroutine fr das Event Click. Die Position der Steuerelemente im Fenster soll von der Gre des Fensters abhngig gemacht werden. Fgen Sie daher der Form-Klasse eine Methode SetSizes hinzu, die dann smtliche Gren und Positionen der Steuerelemente berechnet und diese auch anordnet.
CD-Beispiel SearchBrowser

void SetSizes() { Size sTab=new Size(); sTab.Height = ClientSize.Height*3/4; sTab.Width = ClientSize.Width-20; tabBrowsers.Size = sTab; Size sBrowser = tabBrowsers.ClientSize; sBrowser.Width = tabBrowsers.Size.Width-20; WebBrowserAltavista.Size = sBrowser; WebBrowserGoogle.Size = sBrowser;

Ausgewhlte C -Kapitel
13
tbSearchText.Location=new Point(15,sTab.Height+20); btSearch.Location = new Point(15,sTab.Height+40); } Fgen Sie noch Bearbeitungsroutinen fr die Ereignisse LoadPage und SizeChanged des Hauptfensters hinzu (natrlich mit dem Designer) und rufen Sie in diesen die Methode SetSizes auf. In der Bearbeitungsroutine des Buttons btSearch sollen nun die beiden WebBrowser-Steuerelemente auf die entsprechenden Suchseiten navigieren. private void btSearch_Click( object sender, System.EventArgs e) { object o1 = ""; object o2 = ""; object o3 = ""; object o4 = ""; string AltaNav = "http://www.altavista.com/sites" + "/search/web?q="+ tbSearchText.Text; string GoogleNav = "http://www.google.com/" + "search?q="+tbSearchText.Text; WebBrowserAltavista.Navigate(AltaNav,ref o1,ref o2,ref o3,ref o4); WebBrowserGoogle.Navigate(GoogleNav, ref o1,ref o2,ref o3,ref o4); } Sie knnen nun smtliche Methoden und Properties des Steuerelements aufrufen, so auch die Methode Navigate. Unter COM werden der Methode Navigate Zeiger mitgegeben, damit Rckgabewerte belegt werden knnen. Der .NET-Wrapper verlangt hier nach Referenzen. Sie brauchen die Rckgabeergebnisse aber nicht, daher geben Sie Dummy-Objekte mit, die Sie natrlich auch erzeugen mssen und mit dem ref-Schlsselwort in der Parameterliste mitgeben. Die Zusammenstellung des Suchstrings ist natrlich von der Suchmaschine abhngig.

447

C#
448

13 Zusammenfassung
Unter .NET wird auf ActiveX-Steuerelemente ber Wrapper-Klassen zugegriffen. Diese Klassen implementieren die eigentlichen COM-Aufrufe. Visual Studio.NET kann diese Wrapper-Klassen automatisch aus den Informationen der Typbibliothek des Steuerelements erzeugen.

Casting
C-Programmierer knnen Castings (Typumwandlungen) durchfhren, wann immer und wo immer sie wollen. Diese Freiheit birgt allerdings auch Gefahren in sich, von der sicher jeder C-Programmierer sein Liedchen singen kann. Unter C++ ist es sogar mglich, jedem Datentyp Casting-Operatoren zu definieren, d.h. das Verhalten bei der Umwandlung von einem Datentyp zu einem anderen Datentyp zu kontrollieren.

Explizites und implizites Casting


Man unterscheidet explizites Casting und implizites Casting. byte b = 5; int i; i=b;//implizites Casting Beim impliziten Casting erfolgt die Typumwandlung sozusagen automatisch. byte b; int i=5; b=i;//implizites Casting Unter C sehr wohl mglich (maximal erscheint vielleicht eine Warnung), verweigert der C#-Kompiler dieses Casting, da es hier naturgem zu einem Datenverlust kommt. Der Programmierer muss den Kompiler explizit beauftragen, diesen Vorgang durchzufhren.

Ausgewhlte C -Kapitel
13
byte b; int i=5; b=(byte)i;//explizites Casting Es liegt nun in der Verantwortung und Beurteilung des Programmierers, ob dies auch Sinn macht. Unter C# wird Casting wesentlich restriktiver gehandhabt als unter C. Und das ist auch gut so. Wie auch unter C++ knnen aber unter C# spezifische Casting-Operatoren geschrieben werden. Somit kann das Verhalten bei einer Casting-Operation genau kontrolliert werden. Das Prinzip wird an einem kleinen Beispiel veranschaulicht: class Euro { public int Cent; public Euro() { Cent = 0; } public Euro(int E) { Cent = E*100; } public Euro(int E,int C) { Cent = E*100+C; } public override string ToString() { return Cent/100 + "E " + Cent%100; } } class CHF { public int Rappen; public CHF() { Rappen = 0; } public CHF(int F)
CD-Beispiel Casting1

449

C#
450

13
{ Rappen = F*100; } public CHF(int F,int R) { Rappen = F*100+R; } public override string ToString() { return Rappen/100 + "Fr " + Rappen%100; } } Diese beiden Klassen modellieren die Whrungen Euro und Schweizer Franken. Innerhalb der Klassen wird der Betrag in Cents bzw. Rappen gehalten. Drei Konstruktoren erlauben die Belegung. Die Methode ToString ist so berlagert, dass die Ausgabe des Betrages erfolgt (z.B: 20E 10 bzw. 10F 20). public class App { static void Main() { Euro ePreis = new Euro(5,20); Console.WriteLine(ePreis); CHF fPreis = new CHF(); fPreis = ePreis; //implicit nicht mglich fPreis = (CHF)ePreis; //explizit nicht mglich } } Hier sehen Sie diese beiden Klassen in der Anwendung. Ein Betrag von 5 Euro und 20 Cent wird im Objekt ePreis gehalten. Einem Objekt vom Typ CHF kann weder explizit noch implizit das Objekt ePreis vom Typ Euro zugeordnet werden. Der C#-Kompiler verweigert das (nicht nur aus geldmarktpolitischen Grnden :-). Fgen Sie nun der Klasse Euro einen Umwandlungsoperator hinzu.

Ausgewhlte C -Kapitel
13 Schlsselwort explicit
public static explicit operator CHF(Euro eu) { CHF t = new CHF(); t.Rappen = eu.Cent*146600/100000; return t; } Hier rechnet die Methode intern sogar noch den Kurs um. Umwandlungsoperatoren mssen immer in Form von statischen Methoden implementiert werden. Syntaktisch verwenden Sie hier das Schlsselwort operator, geben dann den Rckgabetyp der Operation an (CHF) und in der bergabeparameterliste das zu konvertierende Objekt (Euro eu). Damit ist diese Zeile mglich! fPreis = (CHF)ePreis; Allerdings ist die implizite Verwendung nicht mglich. fPreis = ePreis; Der Grund liegt darin, dass bei der Implementierung das Schlsselwort explicit verwendet wurde. Wenn Sie statt explicit das Schlsselwort implicit verwenden, kompiliert auch diese Zeile ohne Schwierigkeiten.
Definition eines expliziten Casting-Operators

451

Schlsselwort implicit
public static implicit operator CHF(Euro eu) { ... } Im Kapitel 2, C# eine neue Programmiersprache ist Ihnen beim Unterkapitel berlagern von Operatoren vielleicht aufgefallen, dass der Zuweisungsoperator unter C# nicht berlagerbar ist. Sie knnen dasselbe Verhalten aber meist ber die Definition eines impliziten Casting-Operators erreichen. ePreis = 3.92; Damit diese Zeile funktioniert, muss ein impliziter CastingOperator definiert werden. Dieser kann fr die Klasse Euro in folgender Form realisiert werden.
CD-Beispiel Casting2

C#
452

13
CD-Beispiel Casting3

public static implicit operator Euro(double val) { Euro t = new Euro(); t.Cent = (int)(val*100); return t; } Die Definition von Casting-Operatoren ermglicht einige interessante Aspekte, die Sie beim Klassendesign bercksichtigen sollten.

C#-Prprozessor
Wie auch C und C++ untersttzt C# das Konzept des Prprozessors. Der Prprozessor manipuliert in Abhngigkeit von Prprozessoranweisungen den Kompiliervorgang. Wie auch unter C/C++ werden Prprozessoranweisungen mit dem Zeichen # eingeleitet. Beachten Sie aber, dass C# nicht alle Prprozessoranweisungen, die C und C++ kennt, implementiert. So sind unter C# keine Makros mglich! Und dies ist doch eine wesentliche Einschrnkung, werden doch gerade Makros in C und C++ intensiv verwendet. Doch gerade diese Makros waren Grund fr viele irritierende Nebeneffekte.

#define und #undef


Die Anweisung #define erlaubt die Definition von Prprozessor-Symbolen. Diese Symbole knnen dann bei spteren Prprozessoranweisungen bercksichtigt werden. Diese Anweisungen drfen nur ganz am Beginn einer Quellcodedatei auftreten! #define TRACE Die Anweisung #undef kann ein Symbol wieder auflsen.

#if, #elif, #else, #endif


Diese Anweisungen werden verwendet, um den Kompiliervorgang in Abhngigkeit einer Bedingung zu prfen.

Ausgewhlte C -Kapitel
13
public static implicit operator Euro(double val) { Euro t = new Euro(); t.Cent = (int)(val*100); #if TRACE Console.WriteLine(t); #endif return t; } Die Zeile Console.WriteLine(t) wird nur dann kompiliert, wenn das Symbol TRACE definiert ist! Mit #else und #elif knnen noch komplexere Kontrollstrukturen fr den Kompiliervorgang definiert werden. #if TRACE==false ... #endif Der Code innerhalb der #if-#endif-Anweisung wrde nur dann kompiliert werden, wenn das Symbol TRACE nicht definiert wurde. Die unter C und C++ sehr beliebte Verwendung des #if-Statements, um ganze Codebereiche auszublenden (statt auszukommentieren), kann auch unter C# angewendet werden. #if false ... #endif Smtlicher Code innerhalb der #if-#endif-Anweisung wird vom Kompiler nicht bercksichtigt.

453

#region, #endregion
Diese Anweisungen haben keinen Einfluss auf den Kompiliervorgang. Diese dienen dazu, bestimmte Codeabschnitte zu kennzeichnen bzw. mit einem Namen zu versehen. Hersteller von Entwicklungssystemen knnten diese Anweisung bercksichtigen, und vor allem bei Editoren die Ansicht auf dem Bildschirm steuern (Visual Studio.NET bercksichtigt diese Anweisungen).

C#
454

13
#region Windows Form Designer generated code ... #endregion

#warning, #error
Mit diesen Anweisungen knnen Fehlermeldungen bzw. Warnungen im Ausgabefenster des Kompilers erzeugt werden. Hier ein Beispiel, das eine Warnung ausgibt, wenn ber das Symbol der TRACE-Modus zugeschaltet ist. #if TRACE #warning "Vor der Auslieferung TRACE entfernen" #endif Im Ausgabefenster des Kompilers wird dann folgende Warnung erscheinen, wenn das Symbol TRACE definiert ist. warning CS1030: #warning: '"Vor der Auslieferung TRACE entfernen"'

Fallbeispiel WWWPhotoPool

Beschreibung und Architektur der WWWPhotoPool-Applikation CD-Beispiel

456 459

C#
456

14

Beschreibung und Architektur der WWWPhotoPool-Applikation


WWWPhotoPool ist eine Applikation, die Fotos, Bilder etc. in unterschiedlichen Formaten verwaltet. Zentrales Element der Applikation ist der WWWPhotoPool-Server (Abb. 14.1). Dieser Server hlt in einer Datenbank die Informationen ber die Fotos und Bilder. Allerdings sind die Fotos nicht in der Datenbank oder auf dem WWWPhotoPool-Server gespeichert, sondern in der Datenbank steht nur die Information ber dieses Foto, unter anderem auch die tatschlichen Speicherorte (URL). Fotos knnen prinzipiell von jedermann (Publisher) ber diesen Server publiziert werden. Dafr muss sich der Publisher allerdings registrieren. Wenn er registriert ist, dann kann er ber ein Web-Service (PhotoPublishingService) seine Fotos in den WWWPhotoPool-Server eintragen lassen. Dazu muss der Publisher Speicherplatz zur Verfgung stellen, der ber Web erreichbar ist. Der Web-Service PhotoPublishingService ist ber ein Passwort geschtzt. Der WWWPhotoPool-Server bietet einen weiteren Web-Service (PhotoRechercheService) an. ber diesen Service knnen innerhalb der publizierten Fotos Recherchen durchgefhrt werden. Allerdings knnen ber diesen Service Fotos nicht heruntergeladen werden, aber die URL der Fotos kann erfahren werden. ber diese URL kann dann in direktem Kontakt (peer-to-peer) auf die Fotos zugegriffen werden. WWWPhotoPool-Server bietet seine Dienstleistungen also nur ber Web-Services an. Irgendjemand auf der Welt kommt nun auf die Idee, die Fotos auf diesem WWWPhotoServer ber eine eigene Web-Applikation zur Verfgung zu stellen. Dazu stellt er auf seinem Server eine Web-Seite (PhotoWeb) bereit, die ihrerseits ber das WebService PhotoRechercheService auf den WWWPhotoPool-Server zugreift. Beliebige Browser knnen ber diese Seite nun Recherchen durchfhren und beliebige Fotos herunterladen (hier wieder ber peer-to-peer). In Abbildung 14.1.sind die Zusammenhnge grafisch dargestellt.

WWWPhotoPool-Server
Die Daten auf dem PhotoPool-Server werden in einer SQL-Datenbank gehalten. Diese besteht aus zwei Tabellen.

Fallbeispiel WWWPhotoPool
14
457

System-Architektur Abb. 14.1

Tabelle tPublisher Abb. 14.2

Die Tabelle tPublisher speichert die Publisher, die ihre Fotos ber WWWPhotoPool bereitstellen mchten. Sie sehen, einem Publisher ist ein Benutzername (Account) und ein Passwort zugeordnet. Auerdem ist seine E-Mail-Adresse und die ServerURL eingetragen. Stellen Sie sich vor, ein angemeldeter Publisher kmmert sich nicht mehr um seine Eintrge, und stellt den Speicherbereich auf seiner Maschine nicht mehr bereit. Der Anwender wird es erst merken, wenn er direkt ber die peerto-peer-Verbindung diese Fotos herunterladen mchte. Um diesen Umstand zu entschrfen, implementiert der WWWPhotoPool-Server einen Prozess ReliabilityTest.exe (siehe Abbildung 14.1). Dieser prft von Zeit zu Zeit, ob der Publisher ber

C#
458

14
Web erreichbar ist. Die Anzahl dieser Versuche (AccessTry) und die Anzahl der erfolgreichen Versuche (AccessSuccess) werden ebenfalls in dieser Tabelle abgespeichert. Damit kann ein Verhltnis bezglich der Verfgbarkeit errechnet werden. Wird hier ein bestimmter Wert unterschritten, wirft der ReliabilityTest-Prozess den Publisher aus der Datenbank und lscht auch alle Foto-Eintrge in der Datenbank. Bevor dies eintritt wird er noch einmal ber E-Mail verwarnt.

Tabelle tPhoto Abb. 14.3

In der Tabelle tPhoto werden nun die Informationen der Fotos abgespeichert. Neben einem Titel, einem Eintragsdatum und einem beliebigen Kommentar ist hier natrlich die tatschliche URL des Fotos abgespeichert. Der Eintrag IDPublisher stellt eine Relation zur Tabelle tPublisher dar. Auf dem Server existiert auch ein Windowsprogramm fr den Administrator (PhotoPoolAdmin.exe). Damit kann die Datenbasis elegant verwaltet werden (z.B. Anlegen, Update usw. eines Publishers). Der WWWPhotoPool-Server bietet seine Dienste ber zwei WebService-Schnittstellen an. Die Web-Service-Schnittstelle PhotoPublisherService erlaubt das Registrieren bzw. Lschen von Foto-Eintrgen. Auch eine nderung der Stammdaten des Publishers ist ber diesen Web-Service mglich. Diese Schnittstelle ist also primr fr die Publisher gedacht und ist passwortgeschtzt. Die Web-Service-Schnittstelle PhotoRecherche Service ist fr die Anwender (Consumer) gedacht. ber diese Schnittstelle kann letztendlich die URL eines interessanten Fotos erfahren werden. Wenn diese URL bekannt ist, kann auf das Foto auch zugegriffen werden.

PhotoWebServer
Das ist eine normale ASP.NET-Anwendung. Diese Web-Seite bietet einem Internetbenutzer eine grafische Oberflche, um im Foto-Pool Recherchen durchzufhren. Die ASP.NET-Anwen-

Fallbeispiel WWWPhotoPool
14
dung selbst kommuniziert aber mit dem eigentlichen WWWPhotoPool-Server ber das Web-Service PhotoRechercheService (siehe Abb. 14.1). Normale Web-Clients knnen somit auf die Fotos im Foto-Pool zugreifen.

459

Publisher
Ein Publisher kann ber die Web-Service-Schnittstelle PhotoPublisherService seine Eintrge verwalten. Der Publisher muss auf seiner Maschine einen Speicherplatz freigeben, ber den dann bers Web zugegriffen werden kann. Die Maschinen mssen also auch bers Web erreichbar sein! Fr die Verwaltung der Fotos auf seiner Maschine bekommt der Publisher ein eigenes Windowsprogramm zur Verfgung. ber dieses Programm kann er Fotos in den Speicherbereich kopieren und gleichzeitig auch beim WWWPhotoPool-Server registrieren. Natrlich kann er diese auch lschen. Seine Stammdaten auf dem WWWPhotoPool-Server kann er ebenfalls ber dieses Programm verwalten.

CD-Beispiel
Smtlicher Code fr dieses Beispiel befindet sich auf der beiliegenden CD. Da die detaillierte Beschreibung den Seitenrahmen dieses Buches sprengen wrde, finden Sie diese ebenfalls auf der CD in Form eines Winword-Dokuments. Es werden dabei folgende Themen abgedeckt:

WWWPhotoPool-Server
Data-Access-Assembly (SQL-Datenbank, ADO.NET, DataSet, DataGrid, XML-Konfiguration) Zwei Web-Services (ASP.NET-WebService, Session-Verwaltung, Logon, XML-Konfiguration) PhotoAdmin.exe (Windowsprogramm) ReliabilityText.exe (Konsolenprogramm, E-Mail)

C#
460

14 Publisher
PublisherAdmin.exe (Windowsprogramm, kundenspezifische Windowssteuerelemente, XML-Konfiguration, Web-Service-Zugriffe , Webzugriffe, Dateiverarbeitung ...)

PhotoWeb
PhotoWeb.aspx (ASP.NET, kundenspezifische WebSteuerelemente, XML-Konfiguration, Session-Verwaltung, Web-Service-Zugriffe, peer-to-peer ...)

Stichwortverzeichnis
461

Stichwortverzeichnis
Symbols
!= . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 #define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452 #elif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452 #else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452 #endregion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453 #error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454 #if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452 #region . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213, 453 #undef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452 #warning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454 .NET-Konsole . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .191 .NET-Programmiermodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190 .resx-Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 /t:winexe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 /target . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 == . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

A
abstract . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84, 86 abstrakte Basisklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 Activator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 Activator.GetObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .404 active . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327 Active Server Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318 ActiveX Data Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 ActiveX-Steuerelementen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444 AddAttributesToRender . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356 AddResource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252 AddStyleAttribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354 ADO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 ADO.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283

C#
462
Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120 AL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70, 236 API-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .186 Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360 Application Programming Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 appSettings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276, 277 Architektur eines Win32-Windowsprogramms . . . . . . . . . . . . . . . .188 Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82, 89 ArrayList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 as . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 asmx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375, 376 ASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317 ASP-Seite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321 aspx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326 Assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36, 119, 120 Assembly Linker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .126 AssemblyInfo.cs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 asynchrone Aufrufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421 Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112, 145, 161 attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .149 AttributeUsage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 Aufbau des Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 Aufzhlungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .58 Ausgabepfad ndern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398 Ausnahmeverarbeitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 AutoPostBack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .345

B
BackColor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342 Bad Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .148 Basisklasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Basisklassenkonstruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 Baugruppen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120 BeginInvoke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423, 424

Stichwortverzeichnis
463
Bilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 Bindung, spte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Bitmaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223, 236, 246 bool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49, 98 Boolean Equals(Object) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 BorderColor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342 BorderStyle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342 BorderWith . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342 Boxing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99, 100 Bruchzahl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Brush . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236, 237 build number . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Buildaktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 Build-Prozess . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .31, 35 Button . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 byte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Byte-Stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252

C
C# Codedatei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 C# Mechanismus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 C/C++ Mechanismus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360, 366 CacheDependency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .371 case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Casting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81, 88, 173, 448 catch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .71 catch-Block . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 CDATA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161 Channel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394, 407 char . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Character Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 CheckBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344 child . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

C#
464
child elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 ChildNodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32, 44, 46 Clear . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Click . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446 client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417 Client-aktivierte Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395 ClientSize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234 CLR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53, 190 cmiClearAll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 cmiSend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 code-behind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330, 331, 334, 382 Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Color . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 ColorTranslator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .353 COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22, 36, 120, 392, 444 COM+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 Command Prompt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37, 123 Comment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 Common Language Runtime . . . . . . . . . . . . . . . . . . . . . . . . . . .53, 190, 430 Component Object Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .36, 120, 444 Composite Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .358 COM-Programmiermodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 config . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267 configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268, 415 ConfigurationSettings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .272 Connection Points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 Console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35, 45 Container . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .144 ContextMenu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348, 357 CORBA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 Count . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Create . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271

Stichwortverzeichnis
465
CreateInstance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 CreateNavigator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .173 CSC.EXE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 CTS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430 CultureInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 Current . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 CurrentCulture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 CurrentUICulture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261

D
DAO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284 Data Source Name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292 DataColumn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303 DataGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .313 DataRow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303 DataRowState . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .306 DataSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287, 300, 302 DataSet und XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .306 DataTables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303 Datei-ffnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225 Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 DateTime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298 DateTimePicker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387 DCOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22, 392 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341 Debug-Informationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .31 decendant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 decimal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .49, 78 default . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 delegate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 Delegates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .101 delegieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 DELETE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294 DeleteCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .309 denominator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

C#
466
Designer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 Device . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 Dialoge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .225 DialogResult . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228, 229 DispatchMessage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .189 Dispose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 DLL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120 DLL-Hlle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .22, 138 DNS-Name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 document root . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 DocumentType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .162 DOM-Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .156 DOM-Objektmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160, 165 double . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 DoubleClick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .233 do-while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 DrawEllipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .237 DrawImage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 DrawLine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .237 DrawString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 DTD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 dynamic link library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120 dynamische Bibliotheken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120

E
EH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Eigenschaftsfenster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 Einbinden von Steuerelementen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 eingebettete Ressource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 Einsprungsfunktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32 Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144, 162 else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 EndElement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .162 endif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452

Stichwortverzeichnis
467
EndInvoke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424 End-Tag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141, 145, 157 Entity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 Entwurfsansicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .209 Enumerationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 Equals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432 Erweitern eines Steuerelements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 EventArgs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 EventHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .110 Event-Mechanismus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106, 196, 214, 426 Exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69, 71, 383 Exception-Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 EXE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .192, 249 ExecuteNonQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 ExecuteReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 ExecuteScaler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 explicit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451 Extensible Markup Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140

F
Farbendarstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 Felder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Fensterereignisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 Fill . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302 FillEllipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 Finalize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434 finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Firewalls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394 float . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Font . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237, 342 for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 foreach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .95, 100 ForeColor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342

C#
468
Formatter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393, 407 FormDesigner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194, 196, 209 forward cursor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .158, 162 fraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .42 Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 FromArgb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 FromFile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 frhe Bindung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .83 Funktionszeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101, 103

G
GAC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 GACUTIL.EXE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 Garbage Collecting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .23 Garbage Collector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .47, 54, 77, 402 GC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .47 GDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 GDI+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 get . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 GetConfig . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .272 GetCustomAttributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 GetElementsByTagName . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 GetEnumerator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 GetExecutingAssembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 GetHashCode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .433 GetMessage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .189 GetObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .254, 404 GetString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 GetType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434 Global . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363 Global Assembly Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 global.asax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334, 364 goto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .99, 100 Graphic Device Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236

Stichwortverzeichnis
469
Graphics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 GraphicsUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 GroupBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 Grunddatentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 guarded-Block . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 GUID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .135 Gltigkeitsbereich . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

H
Hash-Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361 Hashtable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46, 55, 90 Height . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342 HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .321 HtmlTextWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 HttpChannel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399, 407 http-GET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322, 374 http-POST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322, 374 HyperLink . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343

I
IAsyncResult . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423 IConfigurationSectionHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 ID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342 IDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112, 394 IEnumerable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94, 100 IEnumerate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 IEnumerator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 IIS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318, 374 IIS Hosting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418 IL-Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 ILDASM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128, 129 ILDASM (IL Disassembler) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 ILease . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .408

C#
470
ImageList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222, 223 Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 Image-Steuerelement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .343 implicit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451 INamingContainer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359 Indexer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 inetpub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331 Infoset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 Inherits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 InitializeComponent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213, 215, 258, 336 InitializeLifetimeService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413 InitialLeaseTime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411 InnerText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .168 InnerXmlText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .168 InsertCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 Installationsmechanismen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .22 int . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Int32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 Int32 GetHashCode() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52 Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 Interface Definition Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394 Intermediate Language Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121, 130 internal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85 internal protected . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85 Internet Information Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .318, 374 Interoperabilitt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444 Interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .325 is . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 IsCompleted . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424 ISerialize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 ISponsor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412 IsPostBack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346 Item . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 Iterationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 IXPathNavigator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

Stichwortverzeichnis
471

J
Jagged Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 JAVA-Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 JIT-Kompiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 JScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322 just-in-time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130

K
key . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43, 53 Klassen und Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Kommentare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32, 146 Konfigurationsdateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265, 268 Konsole . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .123 Konsole-Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 Konstruktoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .45, 80 Kontextmenu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .221 Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 Koordinatensystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199, 241 Kulturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 kundenspezifische Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350 kundenspezifische Steuerelemente . . . . . . . . . . . . . . . . . . . . . . 230, 348

L
Label . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 lease time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .408 Leased Based Lifetime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .408 Length . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 LifetimeServices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410 ListBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 LoadPage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447 localhost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333 Localizable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258

C#
472
Location . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 long . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 low coupling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88

M
Machine.config . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .273 Main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32 major version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 managed heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46, 53, 57 Manifest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122, 129 Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Markup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 marshal by referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 marshal by value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 MarshalByRefObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395, 397 Matrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 Members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 Member-Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45, 80 Men . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .219 Menliste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .219 Message Queue Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 Metadaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36, 121, 122 method-Attribut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .327 Methode mit Bindung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 Microsoft Data Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 minor version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Modifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85 Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 MouseEventArgs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 MoveNext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 Mscorlib.dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 MSDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287, 288 MSDN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32

Stichwortverzeichnis
473
Multicast-Deletates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 multifile assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 Multifile-Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .125

N
Namensraum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 namespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Navigate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447 NavigateUrl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343 nested elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 new . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46, 81 None . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 null . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 numerator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

O
object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 Objektorientierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325 ODBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284 OLE-DB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 OleDbCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 OleDbConnection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 OleDbDataAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 OleDbReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 OleDbTransaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 OnClick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196, 231 OnPaint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 OpenFileDialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .62, 63 out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64, 66 output.Write . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 override . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52, 83

C#
474

P
Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241, 242, 329 Page_Load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330 PageUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243, 246 PaintArgs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 parent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .149 parent element . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 Parse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207, 339 PathProperties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 Pen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236, 237 Performanz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 PictureBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226, 233 PIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .144 Platzhalter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .45 Point . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 PointF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238, 244 Prprozessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452 primitive types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 private . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85 private Assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 Processing Instructions, PI's . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .143, 144 ProcessingInstruction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .162 Programm-Loader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32 Projektmappe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .28 Projektmappen-Explorer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29, 38 Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67, 214 protected . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .81, 85 Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379, 380, 393 public . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .41, 60, 67, 85 Publish-Subcribe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 Pull-Modells . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 Push-Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160

Stichwortverzeichnis
475

R
RadiobuttonList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345 RadionButtons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201, 207, 208 Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161, 297, 298 ReadElementString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161 ReadXml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306 RecordSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 Rectangle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .236, 238 RectangleF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 ref . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 ReferenceEquals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431 Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48, 124 Referenzobjekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 Referenztyp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 RegisterActivatedClientType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .406 RegisterChannel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399 RegisterWellKnownClientType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .406 RegisterWellKnownServiceTyp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403 Registrierungsdatenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Release-Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .31 Remoting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391 Remoting.Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399 RemoveAt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 RenderBeginTag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354 RenderChildren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360 RenderEndTag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354 Renew . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .411 RenewOnCallTime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412 Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323, 324 Reset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 ResGen.exe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 ResourceManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251, 254, 258 ResourceReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 ResourceWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 Response . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323, 327

C#
476
Ressource-Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .251, 254 Ressourcen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .121, 248 resx-Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 ResXResourceReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 ResXResourceWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 return . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 revision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 RichTextBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 root node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 RotateTransform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 runat= . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329

S
Satelliten-Dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262 SAX-Programmiermodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 sbyte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 ScaleTransform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 Schemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 Schieberegler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .201 Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Schlsselpaar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 Schnittstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .86, 444 Schutzklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85 Schutzklassen-Modifizierer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Schutzkonzept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85 Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .47 scope operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .34 Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .374 sealed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 section . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268 sectionGroup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274 SEH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 SELECT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296 SelectCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .302, 309 SelectNodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171

Stichwortverzeichnis
477
self . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 self-hosted . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418 Serialisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .175 Serializable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 Server aktivierte Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403 Server-Explorer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288, 290 Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360 set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 SGML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .141 Shared Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .135 short . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Show() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225 ShowDialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 ShowDialog() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225 SignalHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 single file assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 SingleCall . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403, 405 Singlefile-Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .123 Singleton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405 Size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199, 236, 238 SizeChanged . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 SizeF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 SizeMode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 Small Object Access Protocol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374 smart array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 SMTP-Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40, 218 sn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .374, 407 Soap 1.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 Sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 Software Engineering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 SolidBrush . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .237, 240 Speicherleck . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 Sponsor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412 SponsorshipTimeout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .409

C#
478
Sprungbefehle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284 SQL Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 SqlCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286, 292 SqlConnection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286, 292 SqlDataAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286, 300 SqlDataReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286, 297 SqlTranaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46, 54, 57, 89 Stack-Unwinding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 standard entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 Standard Generalized Markup Language . . . . . . . . . . . . . . . . . . . . . . 141 Start-Tag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141, 145, 157 statische Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 Statusverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360 Steuerelement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 StretchImage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .233 string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 String ToString() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52 strong name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 struct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Structured Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Structured Query Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284 Strukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356, 357 Stylesheet Language for Transformation . . . . . . . . . . . . . . . . . . . . . . 151 submit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .322 subscribe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 switch-case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44, 110 System.Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89, 92 System.Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 System.Boolean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 System.Char . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

Stichwortverzeichnis
479
System.Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 System.Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 System.Configuration.NameValueFileSectionHandler . . . . 275, 276 System.Data.OleDb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 System.Data.Sql . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 System.Delegate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 System.dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 System.Drawing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .195, 199, 236 System.Drawing2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 System.EventHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .110 System.Globalization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 System.Int16 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 System.Int32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 System.Int64 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 System.MulticastDelegate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 System.Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .51 System.Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 system.runtime.remoting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400, 415 System.Runtime.Remoting.Lifetime . . . . . . . . . . . . . . . . . . . . . . . 408, 411 System.Sbyte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 System.Threading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 System.UInt16 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 System.UInt32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 System.UInt64 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 System.ValueType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54, 55 System.Web.Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369 System.Web.dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 System.Web.HttpApplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363 System.Web.Services.WebService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375 System.Web.UI.WebControls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330, 349 System.Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 System.Windows.Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 System.xml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 System.Xml.dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157, 302 System.Xml.Serializer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176

C#
480
System.Xml.Xsl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 Sytem.Byte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

T
TabControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445 TabIndex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342 TabPages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445 TagPrefix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350 TcpChannel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407 tcp-Kanal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407 text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .322 TextBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201, 217 Text-Property . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .45 Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .188 throw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 ToolBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .222 ToolTip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223, 342 ToString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .434, 450 TrackBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .201 Transformationsmatrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 TranslateTransform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 try . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 try-Block . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .72 Type GetType() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52 typeof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400 Typisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .325 Typ-Metadaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

U
berladen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 berladen von Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 berladen von Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 uint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 ulong . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

Stichwortverzeichnis
481
Unboxing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 Unicode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 Update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295, 313 UpdateCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 Url . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381 User Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 ushort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 using . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

V
ValidationType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 Validierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 VBScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322 Verarbeitungsanweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Versionsnummer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 virtual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 virtual tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 virtuelle Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Visible . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342

W
W3C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .140, 160 Web.Config . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418 Web-Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445 WebControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .357, 360 WebForm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327 WebMethod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .116 WebServices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373, 374, 375 Web-Steuerelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326, 341 well formed document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 Werkzeugleiste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 Wertetyp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

C#
482
Wertobjekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .54 while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 WhiteSpace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .162 Width . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238, 342 Windows-Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .192 WindowsForms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 Windows-Steuerelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 winexe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .192 WinSockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 WM_KEYDOWN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .188 WM_LBUTTONDOWN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .188 WM_PAINT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 Wrapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446 WriteElementString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 WriteXml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306 WWWPhotoPool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456

X
XCOPY-Installing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267 XDR-Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139, 140 XML Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 XML Schemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 XmlAttribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .165 XmlComment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 XmlDeclaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .165 XmlDocument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156, 165, 368 XML-Dokument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 XmlElement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 XmlEntity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 XML-Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 XML-Informationsmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 XML-Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 XmlNode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165, 166

Stichwortverzeichnis
483
XmlNodeList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .166, 171 XmlNodeType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161 XmlNotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 XmlReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 XmlRootAttribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182 XML-Serialisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .175 XmlSerializer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 xml-stylesheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 XmlText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 XmlTextReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 XmlTextWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156, 157 XmlValidatingReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160, 163 XmlWhitespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 XmlWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 XPATH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148, 171, 172, 279 XPath-Ausdrcke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 XPathDocument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172, 173 XPathNodeIterator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .173 XSD-Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 XSL/T Transformationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 XSLT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150, 173 XSLT-Prozess . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 XslTransform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368 XSL-Transformationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .173, 174

Z
Zeichenreferenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147