483
C#

Otmar Ganahl - C Sharp

Embed Size (px)

Citation preview

Page 1: Otmar Ganahl - C Sharp

C#

Page 2: Otmar Ganahl - C Sharp
Page 3: Otmar Ganahl - C Sharp

SYBEX-WebBook®

C#

Otmar Ganahl

Z

Page 4: Otmar Ganahl - C Sharp

Der Verlag hat alle Sorgfalt walten lassen, um vollständige und akkurate Informationen in diesem Buch bzw. Pro-gramm und anderen evtl. beiliegenden Informationsträgern zu publizieren. SYBEX-Verlag GmbH, Düsseldorf,übernimmt weder die Garantie noch die juristische Verantwortung oder irgendeine Haftung für die Nutzung die-ser Informationen, für deren Wirtschaftlichkeit oder fehlerfreie Funktion für einen bestimmten Zweck. Fernerkann der Verlag für Schäden, die auf eine Fehlfunktion von Programmen, Schaltplänen o.Ä. zurückzuführen sind,nicht haftbar gemacht werden, auch nicht für die Verletzung von Patent- und anderen Rechten Dritter, die darausresultiert.

Projektmanagement: Anita KucznierzDTP: Renate Felmet-Starke, WillichEndkontrolle: Mathias Kaiser Redaktionsbüro, DüsseldorfUmschlaggestaltung: Guido Krüsselsberg, DüsseldorfFarbreproduktionen: Fischer GmbH, WillichBelichtung, Druck und buchbinderische Verarbeitung: Bercker Graphischer Betrieb, Kevelaer

ISBN 3-8155-0125-31. Auflage 2002

Dieses Buch ist keine Original-Dokumentation zur Software der Firma Microsoft. Sollte Ihnen dieses Buch den-noch anstelle der Original-Dokumentation zusammen mit Disketten verkauft worden sein, welche die entspre-chende Microsoft-Software enthalten, so handelt es sich wahrscheinlich um Raubkopien der Software.Benachrichtigen Sie in diesem Fall umgehend Microsoft GmbH, Edisonstr. 1, 85716 Unterschleißheim – auch dieBenutzung 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 einemanderen Verfahren) ohne schriftliche Genehmigung des Verlages reproduziert oder unter Verwendung elektroni-scher Systeme verarbeitet, vervielfältigt oder verbreitet werden.

Printed in GermanyCopyright © 2002 by SYBEX-Verlag GmbH, Düsseldorf

Page 5: Otmar Ganahl - C Sharp

Inhaltsverzeichnis5

Inhaltsverzeichnis

Vorwort 15Widmung 17Danksagung 19

Kapitel 1 Einleitung 21

Kapitel 2 C# – die neue Programmiersprache 27

C#-Einstieg 28Einsprungsfunktion 32Namensraum 34Der Build-Prozess 35Schutzklassen-Modifizierer 41Zusammenfassung 42

Klassen und Objekte unter C# 42Klassen 43

Grunddatentypen 49Schlüsselwort using 50System.Object 51

Strukturen vs. Klassen 53Boxing und Unboxing 57

Enumerationen 58

Methoden 60Methode mit Bindung 60Statische Methode 61Überladen von Methoden 61Überladen von Operatoren 62ref- und out-Parameter 64

Eigenschaften (Properties) 67

Ausnahmeverarbeitung – Exception Handling (EH) 69try – catch – throw 71finally 75Performanz 76

Page 6: Otmar Ganahl - C Sharp

C #

6

Vererbung 77Virtuelle Methoden 82Abstrakte Basisklassen 84Schutzkonzept 85

Interface (Schnittstellen) 86Zusammenfassung 88

Felder und Collections 89Felder aus Wertinstanzen 89Felder aus Referenzinstanzen 91System.Array 92Indexer 93Die Schnittstelle IEnumerable und IEnumerate 94ArrayList 96Maps 97

Kontrollstrukturen 98Verzweigungen 98Schleifen und Iterationen 100Sprungbefehle 100

Delegates 101Multicast-Delegates 104

Events 106System.EventHandler – Delegate 110

Attribute 112

Zusammenfassung 117

Kapitel 3 Baugruppen (Assemblies) 119

Einleitung 120

Grundlagen 121

Singlefile- und Multifile-Assemblies 123Singlefile-Assembly 123Multifile-Assembly 125

ILDASM (IL Disassembler) 128

Assembly-Entwicklung unter Visual Studio.NET 131

Page 7: Otmar Ganahl - C Sharp

Inhaltsverzeichnis7

Shared Assembly 135

Zusammenfassung 137

Kapitel 4 XML-Einführung 139

Einleitung 140

XML-Grundlagen 140

Das XML-Informationsmodell 141Verarbeitungsanweisungen 144Elemente 144Attribute 145Kommentare 146Zeichenreferenzen 147Zeichenfolgen (Character Data) 147

Schemas 147

XPATH 148Kurzformen 150

XSLT 150

Kapitel 5 XML-Klassen 153

Einleitung 154

XML-Dateien schreiben 156

XML-Dateien lesen 160XmlTextReader 160XmlValidatingReader 163

DOM-Objektmodell 165XmlNode 166XML-Dokumente verändern 169Suchen innerhalb von XML-Dokumenten 170Zusammenfassung 172

XPATH 172

XSLT (XSL-Transformationen) 173

XML-Serialisierung von .NET-Objekten 175Serialisierung von einfachen Objekten 175

Page 8: Otmar Ganahl - C Sharp

C #

8

Serialisieren von komplexeren Datentypen 177Serialisieren von Listen 179Namensgebung der Attribute 181

Zusammenfassung 182

Kapitel 6 Windows-Applikationen 185

Einführung 186

Grundlegende Architektur eines Win32-Windowsprogramms 188

.NET-Programmiermodell 190Die Klasse Form 194Auf Fensterereignisse reagieren 196Steuerelement im Fenster 197Zusammenfassung 200

Windows-Steuerelemente 200TextBox, TrackBar, RadioButton, Label 201Zusammenfassung 209

Designer (Entwurfsansicht) 209Zusammenfassung 218

Menü 219Kontextmenu 221

Werkzeugleiste 222

Dialoge 225

Kundenspezifische Steuerelemente 230Erweitern eines Steuerelements 230Entwicklung eines neuen Steuerelements 233Zusammenfassung 236

GDI+ 236Pen und Brush 237Font 237Point, PointF 238Rectangle, RectangleF 238Size, SizeF 238Die Klasse Graphics 238

Page 9: Otmar Ganahl - C Sharp

Inhaltsverzeichnis9

OnPaint-Methode 238Koordinatensysteme 241Bilder, Bitmaps und Images 246

Ressourcen 248Erstellung von Dateien vom Typ .resource 249Verwenden von Ressource-Dateien 251Ressource-Datei über .resx-Dateien erstellen 254Zusammenfassung 256

Unterstützung der Kulturen 257Zusammenfassung 264

Kapitel 7 Konfigurationsdateien 265

Konfigurationen 266

.NET-Konfigurationsdateien 268Format der .NET-Konfigurationsdateien 268XML-Konfigurations-Bearbeiter 269

machine.config 273appSettings 275Simple-Email-Beispiel 277

Zusammenfassung 281

Kapitel 8 ADO.NET 283

Einleitung 284

Übersicht der Klassen 286Klassen im Namensraum System.OleDb 286Klassen für den Zugriff auf SQL-Server von Microsoft 286Die Klasse DataSet 287

Zugriff auf MS SQL Server 287Datenbank anlegen 287Daten in die Tabelle eintragen 290Eintrag löschen 294Update eines Eintrages 295Daten lesen 296Zusammenfassung 300

Page 10: Otmar Ganahl - C Sharp

C #

10

DataSet 300Manipulation von Daten innerhalb eines DataSets 303DataSet und XML 306Datenbank aktualisieren mit Daten eines DataSets 309

DataGrid 313

Zusammenfassung 315

Kapitel 9 ASP.NET 317

Einleitung 318

ASP-Active Server Pages 319Diskussion 324

Von ASP zu ASP.NET 325Web-Steuerelemente 326Trennung zwischen Sicht und Funktion 328Verwendung von kompiliertem Code 331Zusammenfassung 332

ASP.NET-Anwendungen mit VS Studio entwickeln 333Debugging von ASP.NET-Anwendungen 341Zusammenfassung 341

Web-Steuerelemente 341Gemeinsame Eigenschaften 341HyperLink-Steuerelement 343Image-Steuerelement 343CheckBox-Steuerelement 344RadiobuttonList 345ListBox 347Zusammenfassung 348

Kundenspezifische Steuerelemente 348Basisklasse Control 348Kundenspezifische Attribute 350Eingebettete Objekte 354Die Attribut-Klasse Style 356Basisklasse WebControl 357

Composite Controls 358

Page 11: Otmar Ganahl - C Sharp

Inhaltsverzeichnis11

Statusverwaltung unter ASP.NET 360Application und Session 362Cache 366

Zusammenfassung 372

Kapitel 10 Web-Services 373

Einleitung 374

Ein einfaches Web-Service-Beispiel 374MyWebServices 375Web-Service im Web-Browser 376Client-Programme zu MathWebService 379

Entwicklung und Anwendung von Web-Services unter Visual Studio.NET 381

Web-Service-Anwendung 382Client-Anwendungen 384

Zusammenfassung 388

Kapitel 11 Remoting unter .NET 391

Einleitung 392

Remoting-Infrastruktur 393

Client-aktivierte Objekte 395FolderFileEnum-Klasse 395Server 398Client 400

Server-aktivierte Objekte 403

Erzeugung von Remote-Objekten mit dem new-Operator 406

Channel 407

Lebensdauer (Leased Based Lifetime) 408Die Schnittstelle ILease 408LifetimeServices 410Sponsor 412

Konfigurieren des Servers und des Clients über Konfigurationsdateien 414

Page 12: Otmar Ganahl - C Sharp

C #

12

Server-Konfiguration 414Client-Konfiguration 416

IIS Hosting 418

Asynchrone Aufrufe 421

Events Remote-fähiger Objekte 426

Übergabe von Objekten in Remote-Methoden 426

Zusammenfassung 427

Kapitel 12 .NET-Klassen 429

Einleitung 430

Die Klasse System.Object 431ReferenceEquals 431Equals 432GetHashCode() 433ToString() 434GetType() 434Finalize() 434

Die Klasse System.String 435Split 435

Die Klasse System.IO.Path 436Die Klasse Path 437

Die Klassen DirectoryInfo,FileInfo 439

Die Klasse WebClient 440

Kapitel 13 Ausgewählte C#-Kapitel 443

Interoperabilität mit COM 444Einbinden von ActiveX-Steuerelementen 444Zusammenfassung 448

Casting 448Explizites und implizites Casting 448Schlüsselwort explicit 451Schlüsselwort implicit 451

C#-Präprozessor 452

Page 13: Otmar Ganahl - C Sharp

Inhaltsverzeichnis13

#define und #undef 452#if, #elif, #else, #endif 452#region, #endregion 453#warning, #error 454

Kapitel 14 Fallbeispiel WWWPhotoPool 455

Beschreibung und Architektur der WWWPhotoPool-Applikation 456

WWWPhotoPool-Server 456PhotoWebServer 458Publisher 459

CD-Beispiel 459WWWPhotoPool-Server 459Publisher 460PhotoWeb 460

Stichwortverzeichnis 461

Page 14: Otmar Ganahl - C Sharp
Page 15: Otmar Ganahl - C Sharp

15

Vorwort

Microsoft bietet mit .NET den Entwicklern eine Plattform an, inder sich die Grenzen zwischen World Wide Web und den Be-triebssystemen verwischen. Die „natürliche“ .NET-Sprachestellt C# (C Sharp) dar. Als ein Vertreter der C-Sprachenfamiliebesticht C# durch ihre Einfachheit und Leistungsfähigkeit.Es war für mich eine neue und schöne Erfahrung, ein Fachbuchzu C# und .NET zu schreiben. Es würde mich freuen, wenn Sienach Lektüre dieses Buches mit vielen neuen Ideen und hochmotiviert Ihre Projekte in C# und .NET umsetzen.

Page 16: Otmar Ganahl - C Sharp
Page 17: Otmar Ganahl - C Sharp

17

WidmungDieses Buch ist meiner Familie gewidmet.

Page 18: Otmar Ganahl - C Sharp
Page 19: Otmar Ganahl - C Sharp

19

DanksagungWährend der Erstellung des Buches haben mich viele Personenunterstützt. Ein herzliches Dankeschön möchte ich stellvertre-tend aussprechen:Dem gesamten SYBEX-Team, besonders den Projektmanage-rinnen Anita Kucznierz und Katja Roth, dem Fachlektor RonNanko, der Sprachlektorin Brigitte Hamerski und Ute Dick.Meinem Bruder Claudio Ganahl, der mich in vielen fachlichenund inhaltlichen Fragen unterstützte.Meiner Frau Maria und meinen Kindern Janine und Manuel fürdas Verständnis, dass Papa in letzter Zeit weniger Zeit für siehatte.Blons, März 2002Otmar Ganahl

Page 20: Otmar Ganahl - C Sharp
Page 21: Otmar Ganahl - C Sharp

Einleitung

Page 22: Otmar Ganahl - C Sharp

C #

22 1

Die Sprache C# (gesprochen c-sharp) kann nicht getrennt vonder Laufzeitumgebung .NET betrachtet werden. Mit .NET istMicrosofts Vision einer umfassenden Plattform umschrieben.Die herkömmlichen Programmiermodelle von Microsoft konn-ten den Entwicklungen in der IT-Branche der letzten Jahrennur mehr bedingt Rechnung tragen. Neue Strömungen, vor al-lem im Bereich des Internets, wurden in bestehende Program-miermodelle hinein gepfercht. In Folge wurden diese immerkomplexer und der Aufwand, diese zu beherrschen, wurde im-mer größer. Großartige Entwicklungswerkzeuge haben dieseProblematik zwar entschärft, aber nicht gelöst. Mit .NET ver-sucht Microsoft sozusagen eine „Bereinigung“ des Zustandeszu erreichen. .NET definiert ein gänzlich neues Programmier-modell und berücksichtigt hier sämtliche Entwicklungen in derIT-Branche, angefangen von Internet, Sicherheit, Datenbank-zugriffe, Webservices, XML, usw. bis hin zu den jüngsten Ent-wicklungen im Bereich des Software Engineerings.Sämtliche Erfahrungen der komponentenbasierenden Soft-wareentwicklung (COM) und der verteilten Programmierung(DCOM) sind in .NET eingeflossen. Konzepte, die mit der Zeitunübersichtlich und damit fehleranfällig wurden (StichwortDLL-Hell, Registrierungsdatenbank, Installationsmechanismenusw.), sind unter .NET entsprechend neu überdacht wordenund erzeugen nicht mehr diese Probleme, deren Behandlung inder Praxis viel Zeit kostete.

Interessant ist auch der Ansatz, dass .NET als Softwareschichtzwischen Betriebssystem und den Applikationen fungiert, unddamit ein Betriebssystem abstrahiert. Technisch spricht nichtsdagegen, dass .NET-Runtimes auch für andere Betriebssyste-me entwickelt werden, was zur Folge hätte, dass Programmeauch über verschiedene Plattformen hinweg lauffähig wären.Durch die Einführung einer „Metasprache“ werden Applikatio-nen sogar auch binär kompatibel.

Das Ergebnis dieser Anstrengungen ist ein schlankes, einfa-ches, aber umso mächtigeres Programmiermodell, das leichterlernbar und anwendbar ist.

„Software as a service“ ist ein vielfach verwendetes Marketing-Schlagwort von Microsoft im Zusammenhang mit dieser Tech-nologie.

Page 23: Otmar Ganahl - C Sharp

Einleitung231

Mit .NET sind aber Anpassungen der Programmiersprachennotwendig. Die meisten „herkömmlichen“ Programmierspra-chen (wie C, C++, VB) können oft syntaktisch die neuen Fea-tures des .NET Programmiermodells nicht unterstützen (z.B.Garbage Collecting).

Da die C-Sprachen vor allem unter professionellen Entwicklernpopulär sind, hat Microsoft eine neue Sprache, C# (C-Sharp),entwickelt. Schon aus dem Namen der neuen Programmier-sprache geht hervor, dass C# ein Vertreter der C-Sprachenfa-milie darstellt. Und in der Tat sind viele Konzepte, allen voranaus C++ und Java (Java wird ebenfalls in die Kategorie C-Spra-chen eingeordnet), in C# wiederzufinden. Die Mächtigkeit derC-Sprachen wurde mit der Einfachheit moderner Sprachansät-ze kombiniert, was auch sehr gut gelungen ist. Die Ähnlichkeitmit Java ist unverkennbar.

Das vorliegende Buch ist vor allem für Softwareentwickler undProgrammierer unter Microsoft Betriebssystemen gedacht, dieeinen schnellen und kompakten Einstieg in die Thematik C#und .NET wünschen. Notwendig sind dazu Grundkenntnisseder objektorientierten Softwareentwicklung. Sollten Sie C++oder Java beherrschen, dann werden Sie sich beim Erlernen derGrundlagen von C# sehr wohl fühlen. Aber auch Visual Basic-Programmierer sollten sich mit der vorliegenden Lektüre nichtschwer tun. Es wird ebenfalls davon ausgegangen, dass Sie Vi-sual Studio 6.0 kennen und „Debugger“ für Sie kein Fremdwortdarstellt. An dieser Stelle wird auch explizit darauf hingewie-sen, dass dieses Buch kein Referenzbuch der Sprache C# dar-stellt. Nach Durcharbeit dieser Lektüre sollten Sie ein fundiertesC#- und .NET-Wissen besitzen, und damit die Voraussetzunggeschaffen haben, um sich über begleitende Technologiebe-obachtung zum professionellen .NET-Programmierer zu ent-wickeln.

Bestimmte Kapitel benötigen allerdings einen höheren Wis-sensstand, so z.B. Datenbankenprogrammierung, XML. Hierwird auf einschlägige Literatur verwiesen.

Nachfolgend werden noch einige Hinweise zum Gebrauch die-ses Buches gegeben. Ziel ist es, Sie in möglichst kurzer Zeit zubefähigen, professionellen .NET-Code unter C# zu produzie-ren. Dies geht natürlich nicht von heute auf morgen. Damit die

Page 24: Otmar Ganahl - C Sharp

C #

24 1

Lernkurve aber trotzdem kurz und steil verläuft, hier einigeTipps und Anmerkungen.

1 Lesen Sie ein Kapitel oder einen Abschnitt zuerst inRuhe und gemütlicher Umgebung durch. Erst dann be-geben Sie sich „an die Tasten“. Sie werden beim erst-maligen Durchlesen sicherlich nicht alles verstehen,aber Sie bekommen einen Überblick, was Sie in diesemKapitel erwartet.

2 Kodieren Sie so gut wie möglich jedes Beispiel selbstaus. Sie werden vielleicht einwenden, dass Sie C# und.NET erlernen wollen und nicht Maschinenschreiben.Unterschätzen Sie aber nicht das daraus entstehendeLernpotenzial. Jeder Tippfehler hat einen Kompiler-Fehler zur Folge, den Sie analysieren und erkennenmüssen. Außerdem lernen Sie das Entwicklungssys-tem intuitiv, sozusagen nebenbei kennen. Das Beherr-schen der Features des Entwicklungssystems ist einewesentliche Voraussetzung für die spätere Produktivi-tät beim Erzeugen von Code.

3 Gerade beim Erlernen von Konzepten ist es besser klei-ne und überschaubare Beispiele zu verwenden, dieden Blick speziell auf die gerade behandelte Thematikbeschränken. Oft wird der Fehler gemacht, in ein Bei-spielprogramm auch mehr oder weniger komplexeAlgorithmen und spitzfindige Softwaremuster zu ver-packen. Mit diesen können Sie sich dann beschäftigen,wenn Sie die Sprache beherrschen. Daher werden vorallem die ersten Beispiele keinen Anspruch auf sinn-volle Verwendbarkeit erheben. Es sind einfache Bei-spiele, die sich in der Didaktik begründen.

4 Experimentieren Sie! Variieren Sie Codeabschnitte, er-zeugen Sie künstlich Kompiler-Fehler und testen Sieständig, ob Ihr Denkmodell schlüssig ist.

5 Abonnieren Sie eine Fachzeitschrift zum Thema undbetreiben Sie aktiv Technologiebeobachtung!

Sämtliche Beispiele basieren auf Visual Studio.NET (deutscheAusgabe – April 2002). Wenn Sie Visual Studio.NET auf IhremRechner installieren, dann benötigen Sie ca. 2GByte Festplat-

Page 25: Otmar Ganahl - C Sharp

Einleitung251

tenspeicher. Auf Ihrem Entwicklungsrechner sollten Sie auchden IIS (Internet Information Server) und SQL-Server (oder aberMSDE) installiert haben. Wenn Sie die Installation noch nichtdurchgeführt haben, dann installieren Sie unbedingt zuerstden IIS und erst dann das .NET-Framework mit Visual Stu-dio.NET. Es wird auch ausreichend RAM-Speicher (wenigstens128, besser 256 MB) empfohlen, da ansonsten das Entwickelnzu einem Geduldsspiel wird. Ein Internetanschluss wird vo-rausgesetzt. Für Kapitel 11: Remoting unter .NET ist das Vorhan-densein eines kleinen TCP-Netzwerkes von Vorteil. InstallierenSie auch die aktuellste Version der MSDN (Microsoft developernetwork). Es wird davon ausgegangen, dass Sie diese umfang-reiche Wissensdatenbank ausgiebig nutzen werden (und auchmüssen). Auf der Begleit-CD zu diesem Buch sind sämtlicheBeispiele in auskodierter Form zu finden.

Vielfach werden Sie eingedeutschte Begriffe (z.B. Assemblies)oder „Mischwesen“ aus Deutsch und Englisch, wie „Member“-Variable finden. Um die Lesbarkeit zu fördern, sind diese Wör-ter im Buch kursiv formatiert.

Für konstruktive Kritik, Fehlerhinweise und Anregungen binich sehr dankbar. Zum Abschluss wünsche ich Ihnen viel Spaßmit diesem Buch. Sollte es Ihnen gefallen, empfehlen Sie esweiter.

Blons im Biosphärenpark Großes Walsertal/Österreich imMärz 2002

Otmar Ganahl

Page 26: Otmar Ganahl - C Sharp
Page 27: Otmar Ganahl - C Sharp

C# – die neue Programmiersprache

C#-Einstieg 28

Klassen und Objekte unter C# 42

Grunddatentypen 49

Strukturen vs. Klassen 53

Enumerationen 58

Methoden 60

Eigenschaften (Properties) 67

Ausnahmeverarbeitung – Exception Handling (EH) 69

Vererbung 77

Interface (Schnittstellen) 86

Felder und Collections 89

Kontrollstrukturen 98

Delegates 101

Events 106

Attribute 112

Zusammenfassung 117

Page 28: Otmar Ganahl - C Sharp

C #

28 2

In diesem Kapitel werden Sie in kleinen, überschaubaren Bei-spielen die Konzepte der Sprache C# experimentell kennen ler-nen. All diese Beispiele dienen einem rein didaktischen Zweckund erheben keinen Anspruch auf eine andere sinnvolle Ver-wendbarkeit.Aus der langjähriger Seminarpraxis ist dem Autor bekannt,dass dies sehr sinnvoll ist und die Lernkurve beträchtlich ver-kürzt. Sämtliche Beispiele werden Sie mit der Entwicklungsum-gebung Visual Studio.NET durchführen. Es wird angenommen,dass Sie mit einer der Entwicklungsumgebungen von VisualStudio 6 vertraut sind, und es wird daher so weit wie möglichauf redundante Screenshots verzichtet. Das ist möglich, da einegewisse Ähnlichkeit zu Visual Studio 6 vorhanden ist, und vieleFeatures intuitiv erlernbar sind.

Es wird vorausgesetzt, dass Sie die wichtigsten Konzepte derSprachen C und C++ beherrschen.

C#-EinstiegIn diesem ersten Beispiel wird eine ganz normale Textausgabeauf die Konsole erzeugt, wie es schon Kerninghan und RitchieMitte der 70er Jahren in Ihrem Klassiker C Programming getanhaben. Es wird auf der Konsole derselben „ehrwürdige“ Textausgegeben, wie damals Kernighan und Ritchie, nämlich „HelloWorld“.

Starten Sie das Entwicklungssystem Visual Studio.NET und le-gen Sie zunächst eine Projektmappe an. Eine Projektmappe istvergleichbar mit einem Workspace unter Visual Studio 6, er-laubt also mehrere Projekte unter einem Namen zu organisie-ren. 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-Explorerzeigt, dass im gewählten Zielordner ein Ordner namens Dot-NetExperiments angelegt wurde. In der Entwicklungsumge-bung sollten Sie im Projektmappen-Explorer nun die neuangelegte Projektmappe sehen (wenn dieser nicht sichtbar ist,dann können Sie diesen mittels Menü Ansicht > Projektmap-pen-Explorer andocken).

Page 29: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache292

Fügen Sie nun dieser Projektmappe ein erstes Projekt hinzu.Der Menübefehl Datei > Neu > Projekt öffnet hierzu einen Dia-log. Markieren Sie Visual C#-Projekte, wählen Sie vorerst ausdidaktischen Gründen ein Leeres Projekt aus, und geben Siedann dem Projekt den Namen „HelloWorld“. Vergessen Sieaber nicht, die Option Zu Projektmappe hinzufügen zu aktivie-ren, da ansonsten eine neue Projektmappe erzeugt wird, dieden Namen des Projektes hat (Sie kennen sicherlich das ähnli-che Verhalten unter Visual Studio 6). Ein erneuter Blick auf dieDateistruktur zeigt, dass innerhalb des Projektmappen-Ord-ners ein neuer Ordner „HelloWorld“ angelegt wurde. Der Pro-jektmappen-Explorer zeigt nun auch das Projekt an.

Da das Projekt noch leer ist, fügen Sie diesem eine C#-Quellda-tei hinzu. Dies geschieht mit dem Menübefehl Projekt > NeuesElement hinzufügen. Im nun erscheinenden Dialog wählen Sieden Typ C# Codedatei. Geben Sie der Datei einen selbstspre-chenden Namen. Statt des vom Entwicklungssystem vordefi-nierten Namens CodeFile1.cs können Sie Application.cs neh-men.

Geben Sie nun folgenden Code ein. Eine genaue Diskussion er-folgt später, zuerst lernen Sie die Handhabung des Entwick-lungssystems kennen.

Projektmappe erzeugen

Abb. 2.1

Page 30: Otmar Ganahl - C Sharp

C #

30 2

CD-BeispielHelloWorld1

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

Projekt anlegen

Codedatei hinzufügen

Abb. 2.2

Abb. 2.3

Page 31: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache312

System.Console.WriteLine("Hello World"); }} /*Kein Strichpunkt notwendig*/

Nach einer Übernahme des abgedruckten Quelltextsegmentskönnen Sie mittels S + % den Build-Prozess und die an-schließende Ausführung starten.

Noch einige Bemerkungen zum Entwicklungssystem: WennSie die Ordner-Struktur auf dem Dateisystem betrachten, wer-den Sie feststellen, dass im Projektmappen-Ordner für dasneue Projekt ein eigener Ordner angelegt wurde. Dieser ent-hält unter anderem die Quelldatei Application.cs und einenweiteren Ordner bin. Hier befindet sich der Ordner Debug mitdem eigentlichen Programm HelloWorld.exe. Diese ausführba-re Datei beinhaltet allerdings noch Debug-Informationen.

Für die Erzeugung einer Release-Version schalten Sie die aktiveKonfiguration mit dem Menübefehl Erstellen > Konfigurations-Manager auf Release um. Wenn Sie das Projekt nun neu entwi-ckeln, entsteht im Ordner Release eine ausführbare Datei ohneDebug-Informationen. Diese Datei ist auch deutlich kleinerund schneller.

HINWEIS

Natürlich können Sie das Kompilieren auch explizit über denMenüpunkt Erstellen > Erstellen bzw. Erstellen > Alles neu erstel-len durchführen. Dasselbe gilt für das Starten des Programmsaus der Entwicklungsumgebung heraus. Im Menüpunkt De-

Debug- und ReleaseversionenIn der Regel wird es so sein, dass Sie bis zur Fertigstellungeines Projekts ausschließlich mit Debugversionen arbeiten,die weiterführende Informationen enthalten, um mithilfedes Visual Studio Debuggers Fehlererkennung, -suche und-behebung durchführen zu können.

Releaseversionen haben den Vorteil, diese für den Endan-wender uninteressanten Daten nicht zu enthalten, was sichin der Größe der entstehenden Dateien und nicht zuletztauch der Abarbeitungsgeschwindigkeit niederschlägt.

Page 32: Otmar Ganahl - C Sharp

C #

32 2

buggen sehen Sie die Möglichkeiten. Sämtliche Funktionalitätwird auch direkt über die Werkzeugleisten angeboten.

EinsprungsfunktionEin erfahrener C/C++-Programmierer wird bei diesem Beispielnicht so schnell erschrecken. Was Kommentare anbelangt, soist C- als auch C++-Stil möglich:

//Definition einer neuen Klasse/*Kein Strichpunkt notwendig*/

Sie werden auch erkannt haben, dass mit dem Schlüsselwortclass ein neuer Typ mit dem Namen App definiert wird. Diesfunktioniert ähnlich wie bei C++, nur dass kein abschließenderStrichpunkt am Blockende notwendig ist.

Die Klasse App implementiert in diesem Beispiel nur eine stati-sche Methode Main(). Sie werden hier die Einsprungsfunktionvermuten, und damit haben Sie auch Recht. Das wirft aber dieFrage auf, warum Main() die Einsprungsfunktion darstellt, istsie doch unter dem Namensraum einer Klasse definiert?

Die Erklärung ist, dass es unter C# keine globalen Funktionen(und auch keine globalen Variablen) gibt, wie Sie es aus C oderC++ kennen. Sämtliche Funktionen müssen unter der Kontrolleeiner Klasse sein. C++-Programmierer wissen auch, dass stati-sche Methoden den globalen Funktionen aus C sehr ähnlichsind. Für 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? Ineinem C#-Programm darf es nur genau eine Klasse mit einerstatischen Methode Main() geben. Diese Methode gilt dann alsEinstiegsfunktion. (Korrekterweise muss gesagt werden, dasssehr wohl mehrere statische Methoden mit dem Namen Mainin unterschiedlichen Klassen vorkommen dürfen. In diesemFall allerdings muss dem Kompiler explizit diejenige Klasse an-gegeben werden, dessen Main()-Funktion als Einsprungsfunk-tion dienen soll. Mehr dazu finden Sie in der MSDN).

Die .NET-Laufzeitschicht sucht dann diese Methode in den ihrbekannten Klassen und ruft sie auf. Wie dies im Detail funktio-niert, werden Sie später noch kennen lernen.

Page 33: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache332

Experimentieren Sie, indem Sie eine weitere Klasse (mit Na-men App1) definieren, die ebenfalls eine Methode Main() im-plementiert. Beim Kompilieren werden Sie eine entsprechendeFehlermeldung 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 aufMain1() um – der Kompiler arbeitet nun, ohne einen Fehler aus-zugeben. Sie können diese Methode sogar jederzeit explizitaufrufen.

CD-Beispiel HelloWorld2

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"); }}

Page 34: Otmar Ganahl - C Sharp

C #

34 2

NamensraumUm die Methode Main1() aufzurufen, müssen Sie Namens-raum angeben. Wenn Sie in C++ eine Klasse deklarieren, ent-steht automatisch auch ein Namensraum. C++ verwendet denzweifachen Doppelpunkt (scope operator), um den Namens-raum anzugeben. Unter C# wird der Namensraum mit demPunktoperator eingeleitet. (Verwechseln Sie App1 nicht mit ei-nem Objekt, App1 ist der Name der Klasse!)

App1.Main1();

Ein Namensraum kann unter C# auch mit dem Schlüsselwortnamespace eingeführt werden. Im folgenden Codebeispielwird die Klasse App1 innerhalb eines Namensraumes definiert.

CD-BeispielHelloWorld3

class App{ public static void Main() { System.Console.WriteLine("Hello World"); //den Namespace angeben DotNetExperiments.App1.Main1(); }}//Erzeugung eines neuen Namensraumesnamespace DotNetExperiments{ class App1 { public static void Main1() {

System.Console.WriteLine( "Hello World – App1"); } }}

Sie sehen, dass nun die Angabe des Namensraumes DotNetEx-periments notwendig ist, um dann über die Klasse App1 die sta-tische Methode Main1() aufzurufen.

DotNetExperiments.App1.Main1();

Page 35: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache352

Der Punktoperator wird unter C# verwendet, um Namensräu-me zu öffnen und auf Klassen-Members zu verweisen (wieauch bei C++).

An diesem Punkt dürfte es auch nicht schwer sein, die Pro-grammzeile, die die Ausgabe auf die Konsole durchführt, zuverstehen.

System.Console.WriteLine("Hello World");

System ist ein Namensraum in dem sich die Klasse Console be-findet, die ihrerseits die statische Methode WriteLine() imple-mentiert.

Der Build-ProzessSie stellen sich jetzt sicherlich die Frage, wo denn die KlasseConsole mit der statischen Funktion WriteLine.) definiert ist?Unter C++ würden Sie eine Bibliothek hinzulinken, die die Im-plementierungen der Funktionen der Klasse enthält – imQuellcode würden durch einen Verweis auf eine Header-Dateidiese Funktionsprototypen vereinbart werden. Eine vergleich-bare Vereinbarung der Klasse System.Console fehlt aber imvorliegenden Beispiel!

Um diesen Vorgang zu verstehen, werfen Sie erst einen Blickauf den C/C++-Mechanismus.

C/C++-Mechanismus

Abb. 2.4

Page 36: Otmar Ganahl - C Sharp

C #

36 2

Um eine Objektdatei zu generieren, genügen dem Kompilerdie Prototypen der Funktionen und Datentypen. Die eigentli-che Implementierung fügt dann der Linker in Form einer stati-schen Objekt-Bibliothek oder einer Import-Bibliothek (fürimplizite Dll-Bindung) hinzu.

Für C++-Programmierer ergibt sich ein erhöhter Aufwand da-durch, dass die konsistente Pflege von Header-Datei und Bibli-othek notwendig ist.

Im COM-Programmiermodell unter Windows (component ob-ject model) ist die Verwaltung auch nicht einfacher. Die Be-schreibung von COM-Komponenten erfolgt in binärer Form ineiner der Typbibliotheken, die bei modernen COM-Komponen-ten als Ressource direkt in den Komponenten integriert wird.Damit sind wenigstens sowohl die Beschreibungen als auchder Code in derselben Datei, was die Handhabung vereinfacht.

Die Programmiersprache Visual Basic bedient sich intensiv die-ser Typbibliotheken. In der Registrierung ist aber der Ort derTypbibliothek und der Ort des Binärcodes auf der Festplatte anunterschiedlichen Einträgen zu verwalten. Die Konsistenz derEintragungen ist für das Funktionieren einer Komponente einewesentliche Voraussetzung. Und genau dieser Umstand istfehleranfällig. Unter Visual C++ können mittels Spracherwei-terungen Header-Dateien aus den Typbibliotheken generiertwerden. Visual Basic kann die Information direkt aus der Typ-bibliothek verwenden.

.NET geht einen neuen und anderen Weg. Bei den herkömmli-chen Programmiermodellen (Win32 und COM) sind Kompo-nenten meistens in Dlls verpackt.

Unter .NET wird eine Einheit, die Komponenten in binär aus-führbaren Code enthält, Assembly genannt. Eine Assembly istein wohldefinierter Verbund von Dateien. Im einfachsten Fallbesteht eine Assembly aus genau einer Dll. Wesentlich ist nun,dass die Beschreibung der Komponente direkt im Assembly, inForm von so genannten „Metadaten“, untergebracht ist. Da-mit eröffnen sich sehr viele Möglichkeiten. .NET-Sprachen unddamit auch C# brauchen daher kein Header-Dateien, sondernnur die Angabe des Assemblies. In diesem findet sich alles: dieBeschreibungen für den Kompilier-Vorgang und die Imple-mentierungen für den Link-Vorgang.

Page 37: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache372

Damit ist auch eine logische Trennung des Build-Prozesses ineinen Kompilier- und Link-Vorgang nicht mehr notwendig, dasämtliche Informationen (Beschreibung und Implementie-rung) im Assembly zu finden sind.Der Aufruf aus der Kommandozeile würde sich wie folgt ge-stalten:csc /out:HelloWorld.exe /r:mscorlib.dll Application.cs

csc.exe ist der C#-Kompiler. Über die Option /out: kann derName der entstehenden exe-Datei angegeben werden. Sämtli-che Assemblies, die für die Applikation verwendet werden, sindmit der Option /r: aufzulisten. Zu guter Letzt folgt die Aufzäh-lung der Quellcode-Dateien. Mscorlib.dll ist die Kern-Assemblyder Laufzeitschicht, und wird immer hinzugelinkt.Diese Kern-Assembly beinhaltet die Implementierungen dergrundlegenden .NET-Klassen, unter anderem auch die Imple-mentierung der Klasse Console. Bei der Verwendung des Ent-wicklungssystems wird dieser Aufruf natürlich implizitgeneriert. Überprüfen Sie diesen Sachverhalt. Erzeugen Sie ineinem beliebigen Ordner mit einem beliebigen Editor (z.B. No-tepad) eine Datei Application.cs mit obigem Code und gebenSie über die Konsole die Anweisungcsc /out:HelloWorld.exe /r:mscorlib.dll Application.cs

ein.CD-BeispielHelloWorld4

Nach fehlerfreier Ausführung befindet sich die ausführbare.exe-Datei in diesem Ordner. Verwenden Sie die Konsole vonVisual Studio.NET! Diese finden Sie unter Start > Programme >Microsoft Visual Studio.NET 7.0 > Visual Studio.NET Tools > Visu-al Studio.NET Command Prompt.Hier ein anderes, interessantes Beispiel, das die Leistungsfä-higkeit der „mitgelieferten“ .NET-Klassen demonstriert, undauch gleichzeitig die Verwendung von Assemblies verdeutlicht.Das Beispielprogramm zeigt, wie eine E-Mail per Programmversendet werden kann.Für eine ordnungsgemäße Funktion dieses Programms ist eserforderlich, dass Ihr Rechner über einen Internetanschlussverfügt. Ist dieses nicht der Fall, würde die Programmausfüh-rung eine Exception auslösen – überspringen Sie unter diesenUmständen einfach das folgende Beispiel oder vollziehen esnur theoretisch nach.

Page 38: Otmar Ganahl - C Sharp

C #

38 2

Erzeugen Sie ein neues C#-Projekt in der Projektmappe. Nen-nen Sie es „SmartEmail“ (Datei > Neu > Projekt – und vergessenSie nicht die Option Zu Projektmappe hinzufügen zu aktivie-ren). Fügen Sie eine Quelldatei hinzu (App.cs) und tippen Sieden folgenden Code ein. Im Projektmappen-Explorer könnenSie nun beide Projekte sehen.

Das fett hervorgehobene Projekt ist das derzeit aktive. Solltedas neue erstellte Projekt nicht aktiv sein, dann holen Sie diesbitte nach, indem Sie das Projekt im Projektmappen-Explorermarkieren und im Kontextmenü (rechte Maustaste) Als Start-projekt festlegen auswählen.

Im Menüpunkt Erstellen erscheinen nun die zusätzlichen Ein-träge Projektmappe erstellen bzw. Projektmappe neu erstellen.Damit können alle Projekte einer Solution in einem kompiliertwerden.

C#-Mechanismus

Abb. 2.5

Im Projektmappen-Explorer können mehrere

Projekte verwaltetwerden

Abb. 2.6

Page 39: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache392

CD-Beispiel SmartEmail

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 miteiner Fehlermeldung reagieren. Der Grund liegt darin, dass indiesem Beispiel der Datentyp System.Web.Mail.SmtpMail ver-

Page 40: Otmar Ganahl - C Sharp

C #

40 2

wendet wird, den der Kompiler aber nicht kennt, weil dieseKlasse nicht in mscorlib.dll implementiert ist.

Die Klasse findet sich im Assembly System.Web. Um dieses Pro-gramm aus der Konsole heraus zu entwickeln, müssen Sie fol-gende Kommandozeile eintippen.

csc /out:smartemail.exe /r:mscorlib.dll /r:system.web.dll Application.cs

Zusätzlich zur Kern-Assembly wird ein weiteres Assembly (sys-tem.web.dll) angegeben. Im Entwicklungssystem fügen Siediese Information im Projektmappen-Explorer durch. Expandie-ren Sie, wenn notwendig im Projektmappen-Explorer denBaum des Projektes SmartEmail, markieren Sie den EintragVerweise und betätigen Sie den Menübefehl Verweis hinzufü-gen im Kontextmenü.

Selektieren Sie das Assembly System.Web.dll im Reiter .NET undbestätigen Sie. Nchfolgend sollte nun der Build-Prozess ohneFehlermeldung des Kompilers ablaufen.

Vergessen Sie nicht, einen gültigen DNS-Namen eines SMTP-Servers anzugeben – am besten den von Ihrem Provider (beiOutlook finden Sie den Namen in den Konto-Einstellungen).

Hier werden sämtlicheverfügbaren Assemblies

aufgelistet

Abb. 2.7

Page 41: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache412

Der in diesem Listing dargestellte Name ist natürlich nicht gül-tig.

System.Web.Mail.SmtpMail.SmtpServer = "smtp.provider.xx";

Starten Sie nun das Programm, geben als Absender Ihren Na-men an, eine gültige E-Mail-Adresse (nehmen Sie am bestenIhre eigene Adresse), einen Betreff (z.B. „Hello World modern“)und einen kleinen Text. Mit der Taste j wird nun eine E-Mailversendet.

Das ist doch schon recht beeindruckend. Sie sehen, wie einfachdiese Klasse zu verwenden ist. Sicherlich fallen Ihnen einigetolle Anwendungen ein, die E-Mails versenden (z.B. ein Über-wachungsprogramm einer Anlage, die im Bedarfsfall ein E-Mail versendet, usw.).

In der .NET-Klassenbibliothek wimmelt es nur so von solchenleistungsfähigen Klassen, die nur darauf warten, angewendetzu werden. Aber erst sollen Sie C# sicher beherrschen.

Schutzklassen-ModifiziererUnter C# ist bei der Definition jeder Methode und auch jederEigenschaft die explizite Angabe eines Schutzklassen-Modifi-zierers notwendig, der die jeweilige Schutzklasse angibt. ImBeispiel ist die Methode public definiert. Bei C++ wird mit demSchlüsselwort public für einen Abschnitt in der Klassendeklara-tion die Schutzklasse definiert.

Alle Methoden und Eigenschaften, die sich in diesem Ab-schnitt befinden, haben dann diese Schutzklasse. Die Schutz-klassen werden in späteren Kapiteln noch genauer betrachtet.

Angabe der Schutzklasse bei C++

//Schutzklassenangabe unter C++class MyClass{ public:

void Method1(); void Method2();

private: void PrivatMethod1();

};

Page 42: Otmar Ganahl - C Sharp

C #

42 2

Angabe der Schutzklasseunter C#

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

ZusammenfassungIn diesem Abschnitt haben Sie einen ersten Eindruck vom Pro-grammiermodell .NET bekommen, die Handhabung des Ent-wicklungssystems kennen gelernt und erste Betrachtungenüber Konzepte wie Namensraum, Einsprungsfunktion, Assem-blies und Metadaten durchgeführt. Sie werden nun im nächs-ten Abschnitt die objektorientierten Features der Sprache C#kennen lernen.

Klassen und Objekte unter C#Im vorherigen Kapitel haben Sie einen grundsätzlichen Ein-stieg in die Sprache C#, der .NET-Laufzeitumgebung und in dieHandhabung des Entwicklungssystems Visual Studio.NET ge-tätigt. In diesem und den nächsten Abschnitten werden Siesich auf die typisch objektorientierten Features der Sprache C#und der .NET-Laufzeitumgebung konzentrieren.

Wieder in Form von kleinen, überschaubaren Beispielen wer-den Sie nacheinander diese Konzepte kennen lernen. KleineBeispiele eignen sich auch hervorragend zum Experimentie-ren. Nutzen Sie diese Gelegenheiten, versuchen Sie mit Experi-menten die Richtigkeit Ihres Denkmodells zu prüfen unddieses gegebenenfalls zu korrigieren.

Es wird davon ausgegangen, dass das Bruchrechnen für diemeisten Leser kein Problem darstellen wird. In den nächstenKapiteln werden Sie anhand einer Klasse, die Bruchzahlen mo-delliert, die objektorientierten Features kennen lernen. Sukzes-sive wird diese Klasse in den nächsten Kapiteln ausgebaut undmit 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 „Zähler“ (engl. numerator) und der Wertunter dem Bruchstrich „Nenner“ (engl. denominator) genannt.

Page 43: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache432

KlassenFür die nächsten Beispiele erzeugen Sie am besten eine neueProjektmappe FractionExamples. Fügen Sie in gleicher Weiseein neues C#-Projekt hinzu, wie Sie es im Einführungsbeispielkennen gelernt haben. Das Projekt soll den Namen Fraction er-halten. (Vergessen Sie nicht die Option Zu Projektmappe hinzu-fügen zu aktivieren.) Anschließend erzeugen Sie eine C#-Quelldatei (fraction.cs) und geben folgenden Code ein.

CD-BeispielFraction1

namespace FractionExamples{ class Fraction { public System.Int32 z; //Zähler 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); } }}

Page 44: Otmar Ganahl - C Sharp

C #

44 2

//Hauptprogramm für Testzweckeclass 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 NamenMain() bekannt, die die Einsprungsmethode der Applikationdarstellt. Die Klasse, die diese Methode hält, wurde hier belie-big App genannt.

Der neue Datentyp wurde mit dem Schlüsselwort class in ei-nem eigenen Namensraum definiert (FractionExamples) er-zeugt. Der Datentyp erhielt den Namen Fraction. Die KlasseFraction modelliert eine Bruchzahl mit einem Zähler und ei-nem Nenner über die Member-Variablen z (Zähler) und n (Nen-ner). Hier wird für den Klassennamen die englischeBezeichnung und für die Member-Variablen die deutsche Be-zeichnung verwendet. Dies ist zwar nicht konsequent, da aberim deutschen Sprachraum die Bezeichnungen numerator unddenominaor vollkommen ungebräuchlich sind, ist dies ge-rechtfertigt. Ihnen ist sicherlich die ungewohnte Definition derMember-Variablen z und n aufgefallen.

Im Namensraum System existieren die Grunddatentypen (pri-mitive types). System.Int32 ist ein solcher Typ. Unter .NET undden .NET-Sprachen, sind also auch die Grunddatentypen Ob-jekte (dies gilt ja bekanntlich nicht bei C++).

public System.Int32 z;public System.Int32 n;

Page 45: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache452

Weiter erkennt der gelernte C++-Programmierer drei Kon-struktoren zur Initialisierung einer Variablen vom Typ Fractioneinmal ohne, einmal mit einem und einmal mit zwei Initialisie-rungsparametern. Unter C++ könnte man mittels initialisier-ten Parametern den Codierungsaufwand verkürzen. Unter C#gibt es aber keine initialisierten Parameter und daher ist dieexplizite 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 ver-wendet als Parameternamen z und n, also dieselben Namenwie die der Member-Variablen. In der Implementierung über-blenden diese lokalen Parametervariablen die Member-Variab-len. Aber unter C# ist es möglich, mit dem Schlüsselwort thisauf die Member-Variablen zu verweisen. Unter C++ ist this einZeiger und daher die Verwendung des Pfeiloperators notwen-dig, aber unter C# gibt es keine Syntax für Zeiger, daher kannund muss der Punktoperator verwendet werden.

public void WriteToConsole(){

System.Console.WriteLine("{0}/{1}",z,n);}

Die Klasse Fraction implementiert auch eine Methode WriteTo-Console() zur Ausgabe einer Bruchzahl. Eine Ausgabe auf dieKonsole geschieht per WriteLine der Klasse Console im Na-mensraum System. Diese Methode kann ähnlich printf(...) einevariable Anzahl von Parametern aufnehmen. Ähnlich den For-matelementen unter C (%d, %f, etc.) können im FormatstringPlatzhalter für die zu formatierenden Werte angegeben wer-den.

Dies geschieht in Form eines nullbasierenden Index, der in ge-schweifter Klammer angegeben wird – {0} bezieht sich dem-nach auf den ersten spezifizierten Parameter, {1} auf denzweiten und so weiter.

Nun aber zum Hauptprogramm.

Page 46: Otmar Ganahl - C Sharp

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 für erfahrene C++-Programmierer erklä-rungsbedürftig, da hier doch neue, von C++ erheblich abwei-chende Konzepte verwendet werden. Sie müssen wissen, dassunter C# sämtliche Datentypen, die mit dem Schlüsselwortclass definiert wurden, Referenzen darstellen.

Mit der Anweisung

FractionExamples.Fraction r1;

wird nicht, wie Sie vielleicht vermuten, eine Variable angelegt,sondern nur Speicherplatz für eine Referenz auf r1. Wenn Siejetzt an einen Zeiger denken, dann liegen Sie absolut nichtfalsch.

Es wird Speicherplatz auf dem Stack (!) für einen Zeiger auf einFraction-Element alloziert. Ein Fraction-Objekt existiert aller-dings noch nicht! Ein Fraction-Element erzeugen Sie über dennew Operator.

Durch die Angabe der Konstruktorparameter wird dann derentsprechende Initialisierungscode verwendet. (Beachten Sie,dass auch für den Default-Konstruktor die Klammern verwen-det werden müssen!). Das Objekt wird aber nicht auf demStack angelegt, sondern, wie auch bei C++ bei der Verwendungdes new-Operators, auf dem Heap. Dieser Heap wird von der.NET-Umgebung verwaltet und ab sofort „managed heap“ ge-nannt.

C++-Programmierer sind gewohnt, mit new allozierteSpeicherbereiche wieder freizugeben. Dies ist unter .NET nichtnotwendig, weil diese Arbeit die .NET-Laufzeitumgebungübernimmt.

Page 47: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache472

Die .NET-Laufzeitumgebung hat einen „Garbage Collector“(GC) implementiert, zu deutsch „Müllsammler“, der diese Ar-beit übernimmt. Sie fragen sich nun vielleicht, wann dies ge-schehen wird. Dies kann deterministisch nicht vorausgesagtwerden. Prinzipiell gilt: Wenn keine Referenz mehr auf ein Ob-jekt existiert, darf der GC den Speicherplatz wieder freigeben.

Im Beispiel kann der GC das Objekt auflösen, wenn sich der be-treffende Stack-Bereich auflöst, oder im Programmierjargon,wenn der Scope (Gültigkeitsbereich) der Funktion (oder Metho-de) verlassen wird.

Noch einmal zusammengefasst, und weil das für Ihr Denkmo-dell wichtig ist: Alle Objekte, deren Klasse mit dem Schlüsselwort class defi-niert wurde, sind auf dem managed heap angelegt. Der Pro-grammierer hat den Zugriff auf das Objekt in Form einerReferenz (Zeiger). Da dies der Standard-Zugriff ist, ist auch kei-ne explizite Syntax für Zeiger notwendig. Es wird daher immerund überall der Punktoperator verwendet.

r1.WriteToConsole();r2.WriteToConsole();r3.WriteToConsole();

Hier wird also die Methode WriteToConsole() über die Objekter1,r2 und r3 aufgerufen und die Werte auf dem Bildschirm aus-gegeben.

Referenzobjekt

Abb. 2.8

Page 48: Otmar Ganahl - C Sharp

C #

48 2

Ein kleines Experiment soll Ihr Verständnis noch verbessern.

CD-BeispielFraction2

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 die-se Referenz zeigt auf kein Objekt. Der Kompiler wird hier einenFehler ausgeben, wenn Sie versuchen würden, auf r2 eine Akti-on durchzuführen. Sie können aber r2 als Referenz eines beste-henden Objektes verwenden. Die Zeile

r2 = r1; //r1 und r2 zeigen nun auf dasselbe Objekt

Zwei Referenzen aufdasselbe Objekt

Abb. 2.9

Page 49: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache492

führt dies durch. Dass hier r2 und r1 tatsächlich auf dasselbeObjekt verweisen, wird in diesem Beispiel gezeigt, indem dasObjekt über r2 verändert wird, die Ausgabe aber über r1 erfolgt.Zur Verdeutlichung wird hier der Sachverhalt noch einmal gra-fisch dargestellt.

GrunddatentypenIm Beispiel wurde der Datentyp System.Int32 verwendet. Das istein Grunddatentyp, den die .NET-Laufzeitumgebung im Na-mensraum System anbietet. C# erlaubt aber auch, statt Sys-tem.Int32 das jedem C/C++-Programmierer bekannteSchlüsselwort int zu verwenden. Das Schlüsselwort int ist abernichts anderes als ein Synonym der Sprache C# für Sys-tem.Int32. Weitere Grunddatentypen der .NET-Laufzeitumge-bung und die entsprechenden C#-Synonyme sind in derfolgenden Tabelle dargestellt.

.NET-Datentyp C#-Schlüsselwort BeschreibungSystem.Boolean bool True oder FalseSystem.Char char Unicode (2Byte) ZeichenSystem.Sbyte sbyte ganzzahliger Typ (1 Byte)

mit Vorzeichen -128 bis +127System.Int16 short ganzzahliger Typ (2 Byte)

mit VorzeichenSystem.Int32 int ganzzahliger Typ (4 Byte)

mit VorzeichenSystem.Int64 long ganzzahliger Typ (8 Byte)

mit VorzeichenSytem.Byte byte ganzzahliger Typ (1 Byte)

ohne V*orzeichen 0 – 255System.UInt16 ushort ganzzahliger Typ (2 Byte)

ohne VorzeichenSystem.UInt32 uint ganzzahliger Typ (4 Byte)

ohne VorzeichenSystem.UInt64 ulong ganzzahliger Typ (8 Byte)

ohne VorzeichenSystem.Single float Fließkommazahl (32 Bit)System.Double double Fließkommazahl(64Bit)System.Decimal decimal Fließkommazahl (96Bit)System.String string Stringtyp (Unicode)

Tab. 2.1:

C#-Schlüsselwörter für .NET-Grunddatentypen

Page 50: Otmar Ganahl - C Sharp

C #

50 2

Verwenden Sie die C#-Schlüsselwörter statt den generischen.NET-Typen. Die Lesbarkeit des Codes wird dadurch deutlichverbessert. .NET fasst diese Grunddatentypen unter dem Na-men CTS (common type system) zusammen. Alle .NET-Spra-chen (C#, VB.NET, MC++ etc.) müssen diese gemeinsamenTypen unterstützen. Die Syntax der speziellen Sprachen fürGrunddatentypen verweist immer auf einen der Typen im CTS.Dies ist eine wichtige Voraussetzung für die Interoperabilitätzwischen .NET-Sprachen.

Schlüsselwort usingEbenfalls eine Verbesserung der Lesbarkeit ergibt die Verwen-dung des Schlüsselwortes using. Damit kann ein Namensraum„geöffnet“ werden, sodass für Klassen in diesem Namens-raum die explizite Angabe des Namensraumes nicht mehr not-wendig ist. Mit der Verwendung des Schlüsselwortes usingsowie der C#-Schlüsselwörter für die Grunddatentypen schautder Code nun so aus:

CD-BeispielFraction3

using System;using FractionExamples;

namespace FractionExamples{ class Fraction { public int z; //Zähler 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)

Page 51: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache512

{ this.z = z; this. n = n; }

public void WriteToConsole() { Console.WriteLine("{0}/{1}", z, n); } }}

//Hauptprogramm für Testzweckeclass 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

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

}}

System.ObjectUnter .NET müssen alle Klassen von System.Object abgeleitetsein. Unter C# ist dies implizit durch die Definition mit demSchlüsselwort class geschehen. Damit erbt jeder Datentypschon eine Menge von diesem Datentyp. Interessant ist jetztnatürlich zu wissen, welche Methoden und Eigenschaften dieKlasse System.Object implementiert.

Page 52: Otmar Ganahl - C Sharp

C #

52 2

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:

4DotNetSeminar.FractionTrueDotNetSeminar.Fraction

Bis auf die Methode GetType() können alle in der abgeleitetenKlasse überschrieben werden. Dies geschieht durch dasSchlüsselwort override. Dazu werden Sie später noch einigesmehr hören.

Im Beispiel ist es sinnvoll, die Methode ToString() zu über-schreiben, anstatt eine Methode WriteToConsole() zu imple-mentieren. Dies kann dann wie folgt geschehen:

CD-BeispielFraction4

override public String ToString(){ string s;

Boolean Equals(Object) Testet auf Gleichheit und gibt True zurück, wenn gleich

Int32 GetHashCode() Die Methode GetHashCode gibt eine eindeutige, das Objekt bezeichnende ganzzahlige Zahl zurück. Wenn zwei Objekte desselben Typs gleich sind, dann haben diese auch denselben hash code. Kann vielfältig verwendet werden (z.B. Sortieralgorithmus etc.).

Type GetType() Gibt ein Type-Objekt zurück. Damit kann dann direkt auf die Metadaten des Typs zurückgegriffen werden. Mehr dazu später!

String ToString() Gibt den Namen der Klasse als String zurück.

System.WriteLine verwendet übrigens die Methode ToString() eines Objek-tes zur Ausgabe!

Page 53: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache532

s = String.Format("{0}/{1}",z,n); return s;}

Die Klasse Fraction kann nun ganz „natürlich“ von der Conso-le.WriteLine(...)-Methode verwendet werden, da WriteLine im-mer 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 später nochim Detail.

Strukturen vs. KlassenIm Einführungsbeispiel haben Sie einen ersten Eindruck be-kommen, wie Objekte im Speicher verwaltet werden. Wesent-licher Kernpunkt der CLR (Common Language Runtime) ist der„managed heap“ (zu deutsch „verwaltete Halde“). Sie habenauch festgestellt, dass sämtliche Objekte vom Typ class im„managed heap“ angelegt werden und dass auf dem Stackeine Referenzvariable entsteht.

class FractionExamples{

public int z;public int n;

...}

Referenzobjekt

Abb. 2.10

Page 54: Otmar Ganahl - C Sharp

C #

54 2

Dies gilt für alle Typen, die von System.Object abgeleitet sind.Unter C# geschieht dies implizit durch das Schlüsselwort class.Bezüglich der Auflösung des Objektes muss sich der Program-mierer keine Gedanken machen, dies wird vom Garbage Col-lector durchgeführt (sobald im Stack keine Referenzvariablenexistieren, darf der GC das Objekt im „managed heap“ auflö-sen).

Der Namensraum System besitzt aber auch noch eine andereKlasse, nämlich System.ValueType. Eine Ableitung von dieserKlasse hat ein anderes Verhalten zur Folge. Damit wird keinObjekt erzeugt, das sich auf dem „managed heap“ befindetund eine Referenzvariable auf dem Stack hat, sondern das Ob-jekt wird direkt im Stack angelegt. Unter C# werden solche Ty-pen mit dem Schlüsselwortes struct definiert.

struct Fraction{

public int z;public int n;

...}

Wie aus der Grafik ersichtlich, wird das Objekt nicht im „mana-ged heap“ angelegt, sondern direkt auf dem Stack. Das Objektlöst sich natürlich auf, wenn der Stack „stirbt“. Eine solchesObjekt nennt sich „Wertobjekt“ (valued object) im Gegensatzzum „Referenzobjekt“ (referenced object). Unter C# müssen Sie

Wertobjekt

Abb. 2.11

Page 55: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache552

also bei der Typdeklaration entscheiden, ob Objekte eines Typsals Referenzobjekte oder aber als Wertobjekte auftreten wer-den. Das ist unter C/C++ gänzlich anders. Dort entscheiden dieSpeicherklasse und der Ort der Variablendefinition, ob eine Va-riable auf dem Stack oder aber auf dem Heap angelegt wird.Dynamisch allozierte Variablen oder Objekte werden unterC++ immer auf dem Heap angelegt. Beachten Sie diesen Un-terschied!

Außerdem sollten Sie erkennen, dass das Schlüsselwort structunter C# eine vollkommen andere Bedeutung hat, als bei Cund C++.

Es stellt sich nun naturgemäß die Frage, wann soll ein Daten-typ mittels class definiert werden bzw. wann mittels struct?Die Klasse System.ValueType überschreibt die virtuellen Me-thoden von System.Object in einer Form, die dem Verhaltenvon Wertvariablen entsprechen. Dies gilt im Besonderen auchfür die Zuweisung. Führen Sie hierzu einige Experimentedurch.

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 r2Console.WriteLine(r1);

//wir geben das Objekt über r1 auf den //Bildschirm}

Page 56: Otmar Ganahl - C Sharp

C #

56 2

Sie kennen das Verhalten des ursprünglichen Beispiels. Sie de-finieren nun aber die Klasse Fraction mit dem Schlüsselwortstruct. Der Kompiler meckert noch mit einer Fehlermeldung,dass bei Werttypen kein expliziter Default-Konstruktor imple-mentiert werden kann. Sie entfernen diesen Code und starteneinen neuen Kompiliervorgang.

CD-BeispielFraction5

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 tatsächlich um vonei-nander unabhängige Objekte!

Dieses Verhalten wird gerne bei „primitiven“ Datentypen ge-sehen. Bei einer Zuweisung wird der Wert kopiert, und nichtdie Referenz. Beim Datentyp Fraction wird man eher diesesVerhalten erwarten, und aus diesem Gesichtspunkt ist es si-cherlich überlegenswert, Fraction mit struct denn mit class zudefinieren. Übrigens sind die meisten Grunddatentypen der.NET-Laufzeitumgebung Werttypen.

Page 57: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache572

Boxing und UnboxingIn vielen Situationen kommt es vor, dass ein Werteobjekt dasVerhalten eines Referenzobjektes haben sollte. Es ist möglich,ein Werteobjekt in eine Referenzinstanz zu konvertieren. Die-ser Vorgang wird „boxing“ genannt (der umgekehrte Vorgangwird „unboxing“ genannt). Ein kleines Beispiel zum Experi-mentieren. Fraction sei mittels struct definiert worden und da-her ein Werttyp.

CD-BeispielFraction6

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; //boxing findet statt (Heap) o2 = o1; //Referenzzuweisung //(auf dasselbe Objekt)

r2 = (Fraction)o2; //unboxing findet statt

r1.z = 2; //Aenderung von r1 r2.z = 3; //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. Außer-dem wird auf dem Stack Speicherplatz für r2 angelegt. Aufdem Stack werden dann zwei Referenzen (Zeigervariablen)vom Typ System.Object (object) angelegt. Bei der Zeile o1 = r1findet nun dieses boxing statt. Syntaktisch weisen Sie hier ei-ner Referenz einen Wert zu. Was passiert nun? Auf dem mana-ged heap wird Speicherplatz angelegt und dieser wird mit denDaten des Speicherplatzes auf dem Stack (r1) belegt. o2 = o1 istIhnen aus dem Einführungsbeispiel bekannt, o2 zeigt auf das-selbe Objekt wie o1.

Page 58: Otmar Ganahl - C Sharp

C #

58 2

Bei r2 = (fraction)o2 findet ein unboxing statt. Der Speicher-platz auf dem Stack (r2) wird nun mit den Daten des Objektes,das sich auf dem Heap befindet belegt. Die Ausgabe, die dasBeispiel auf der Konsole erzeugt, demonstriert das Verhaltendeutlich.

Interessant ist auch die Zeile Console.WriteLine(o1). Es wirdnämlich für die Ausgabe die ToString(...)-Methode von Fractionverwendet. Ein deutlicher Hinweis, dass die Methode To-String() der Klasse System.Object eine virtuelle Methode dar-stellt. Mehr dazu aber später.

Boxing kann implizit und explizit geschehen. Explizites boxingist syntaktisch dem Casting von C/C++ ähnlich, aber natürlichviel typsicherer. Implizites (automatisches) boxing hat auch ei-nen ganz besonderen syntaktischen Effekt, der nur mit demboxing-Konzept erklärbar ist.

3.ToString();

3 ist ganz klar ein Wertobjekt und muss daher ein boxing er-fahren, wird somit zu einem Objekt erhoben, und dann wirddie Methode ToString() ausgeführt wird. Warum

Console.WriteLine(3);

funktioniert, sollte nun auch klar sein.

In den weiteren Beispielen wird der Datentyp Fraction wiedermit dem Schlüsselwort class definiert.

EnumerationenAufzählungen (Enumerationen) kennen Sie sicherlich aus denProgrammiersprachen C und C++. Es handelt sich dabei umganzzahlige Typen, die vom Benutzer definiert werden kön-nen. Enumerationsinstanzen können dann nur Werte zugeord-net werden, die in der Definition der Enumeration festgelegtwurde. Darüber hinaus können diesen ganzzahligen Wertenauch noch anwendungsfreundliche Namen zugeordnet wer-den.

Unter C/C++ konnten Enumerationsinstanzen auch direkt dieganzzahligen Werte zugeordnet werden. Der Kompiler hatdies akzeptiert, was oft zu Fehlern führte, weil unabsichtlich

Page 59: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache592

Werte zugeordnet werden konnten, die in der Enumerations-definition gar nicht vorgekommen sind.

Unter C# ist dies nicht mehr möglich. Hier ein kleiner Codeaus-schnitt, der Ihnen die Funktionsweise von Enumerationen un-ter C# verdeutlichen soll.

CD-BeispielEnumeration1

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; } }}

Page 60: Otmar Ganahl - C Sharp

C #

60 2

Methoden

Methode mit BindungFügen Sie dem Beispiel eine Methode zur Addition von Bruch-zahlen hinzu. (Fraction soll wieder als Referenztyp definiertwerden). Aus der Grundschule kennen Sie sicherlich noch denAlgorithmus, der in der Methode Add implementiert ist.

CD-BeispielFraction7

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 explizitbei jeder Methode notwendig (public). Rückgabewert der Me-thode ist vom Typ Fraction (Ergebnis der Addition). In der Imp-lementierung wird eine Ergebnisvariable erzeugt, die dasAdditionsergebnis aufnimmt und zurückgibt.

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 er-scheinen (kürzen kann die Applikation leider noch nicht).

Page 61: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache612

Statische MethodeDie Additionsmethode könnten Sie auch statisch implemen-tieren.

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 ei-nem Objekt. Der Aufruf im Hauptprogramm:

CD-BeispielFraction7

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 natürlich die Angabedes Namensraumes notwendig. Es ist eine Geschmackssache,welche Variante lesbarer ist, in der numerischen Algebra magdie statische Variante syntaktisch vielleicht einen Vorteil ha-ben. Aber wie Sie sehen, können auch beide Varianten gleich-zeitig implementiert werden.

Überladen von MethodenWie unter C++ können auch unter C# Methoden überladenwerden, d.h. es können gleiche Methodennamen verwendetwerden, diese müssen sich aber in der Anzahl bzw. Typen derParameter unterscheiden. Genau dss ist im Beispiel bei der Im-plementierung der Additionsmethode passiert.

Page 62: Otmar Ganahl - C Sharp

C #

62 2

Überladen von OperatorenÄhnlich C++ können auch unter C# hinter Operatoren Metho-den definiert werden, die bei Verwendung des Operators imrichtigen Kontext aufgerufen werden. Vor allem bei mathema-tischen Typen ist das oft sinnvoll. So wird im nächsten Beispielder +-Operator mit der Additionsfunktionalität überlagert.

CD BespielFraction8

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 Methoder3 = Fraction.Add(r1,r2);

//Aufruf über Methode mit Bindungr4 = r1.Add(r2);

//überladener Operator + r5 = r1+r2; Console.WriteLine(r3);

Console.WriteLine(r4);Console.WriteLine(r5);Console.WriteLine(r1+r2);

}

Aber auch Operatoren können mehrfach überladen werden.Im folgenden Beispiel wird der +-Operator so überladen, dassein Fracition-Typ und eine Int32-Typ miteinander addiert wer-den können.

public static Fraction operator+( Fraction r1, int i2){

Page 63: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache632

Fraction r2 = new Fraction (i2);return r1+r2;

}

Somit ist auch folgende Anweisung möglich:

r6 = r1 + 5;

Beachten Sie als C++-Programmierer aber, dass überlagerteOperatormethoden immer(!) static und public vereinbart seinmüssen.

Vergleich von ObjektenEs ist auch möglich, die Operatoren == und != zu überladen.Hierzu aber erst einige Gedanken. Die Default-Implementie-rung von == hat folgendes Verhalten:

Bei Referenzobjekten wird true zurückgegeben, wenn mit denReferenzen dasselbe Objekt verwiesen wird. Bei Wertobjektenwird true zurückgegeben, wenn die Werte übereinstimmen.Sie sehen daraus, dass ein Vergleich der Speicherbereiche aufdem Stack stattfindet.

Dieses Verhalten können Sie natürlich überlagern. Im Fraction-Beispiel ist es sicherlich sinnvoll, bei Gleichheit des Inhaltestrue zurückzugeben. Hierzu aber noch mehr unter bei der Ba-sisklasse System.Object.

Nicht überladbare OperatorenBeachten Sie, dass einige Operatoren, die unter C++ überlad-bar sind, unter C# nicht überladen werden können.

Das sind:

&&, || , (), [ ], = , -> , new

Auffallend ist, dass auch die Zuweisung nicht überladbar ist,was aber nach genaueren Überlegungen auch nicht notwen-dig ist (in C++ aber sehr wohl).

Der [ ]-Operator (Indexoperator) kann ebenfalls nicht überla-den werden, aber es gibt hier unter C# das Konzept des „inde-xers“, das Sie in einem späteren Kapitel kennen lernen werden.

Überlagern Sie nun die weiteren Operatoren, die binären Ope-ratoren (diese brauchen zwei Operanden) +,-,*,/ sowie die

Page 64: Otmar Ganahl - C Sharp

C #

64 2

unären Operatoren – und ~, hinter denen Sie den Kehrwert im-plementieren. Auf der Begleit-CD dieses Buches finden Sie dieLösungen im Projekt Fraction8.

ref- und out- ParameterAngenommen, Sie wollen in der Klasse Fraction eine Methodeimplementieren, die mit einem Aufruf die Werte der Member-Variablen z und n zurückgeben sollen. Dass eine Implementie-rung in der Form

public void GetZN(int z, int n){ z = this.z; n = this.n;}

mit folgendem Aufruf nicht zielführend ist, wird einem erfah-renen 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 darstel-len (weil mittels struct definiert), ist dieses Verhalten erklär-bar. Werte werden bei Funktionsübergabe kopiert. In derImplementierung werden also die Kopien manipuliert, wasaber dann zur Folge hat, dass die eigentlich übergebenen Para-meter keine Änderung erfahren haben. In C/C++ kennen Siedie Lösung, Sie übergeben Adressen bzw. deklarieren den/dieParameter als C++-Referenzen. Unter C# gibt es einen ähnli-

Page 65: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache652

chen Mechanismus. Obiges Beispiel müssten Sie wie folgt rich-tig implementieren:

CD-BeispielFraction9

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 Schlüsselwort ref geben Sie an, dass hier technischeine Referenz und nicht der Wert übergeben werden sollte.Auffallend ist aber, dass auch beim Aufruf das Schlüsselwortref explizit angegeben werden muss!

Was ist aber der Fall, wenn der Parameter, der in der Methodeeine Änderung erfahren sollte, ein Referenztyp ist? Dann istdas Schlüsselwort ref nicht notwendig, weder bei der Imple-mentierung noch beim Aufruf. Nachfolgend wird Ihnen das aneinem zugegebenermaßen akademischen Beispiel demonst-riert.

CD-BeispielFraction9

class App{

public static void DoubleFraction(Fraction r){

r.n = r.n * 2;}public static void Main(){

Page 66: Otmar Ganahl - C Sharp

C #

66 2

Fraction r1 = new Fraction(3,2);DoubleFraction(r1);Console.WriteLine(r1);

... }}

Im Namensraum App wird eine statische Funktion DoubleFrac-tion implementiert, die als Übergabeparameter einen Typ Frac-tion (Referenztyp) hat und verdoppelt intern die Member-Variable z. Verifizieren Sie im Beispiel, dass r1 tatsächlich ver-doppelt!

Vielleicht ist Ihnen aufgefallen, dass im obigen Beispiel die Va-riablen z und n mit 0 vorbelegt wurden.

int z = 0;int n = 0;

Geschieht dies nicht (keine Initialisierung), dann meckert derKompiler mit einer entsprechenden Fehlermeldung.

public static void Main(){

Fraction r1 = new Fraction(3,2);

int z; //nicht explizit vorbelegtint n;

r1.GetZN(ref z, ref n);Console.WriteLine(z);Console.WriteLine(n);

}

Bei Wertetypen unangenehm, denn was ist, wenn Sie eine Me-thode implementieren wollen, dessen Aufgabe es eben ist, einObjekt zu initialisieren. Dafür wurde das Schlüsselwort outeingeführt.

public void GetZN(out int z, out int n){

z = this.z;n = this.n;

}public static void Main()

Page 67: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache672

{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 Schlüsselwort out gekennzeich-net sind, dann akzeptiert der Kompiler dies, auch wenn die Va-riablen z und n nicht initialisiert wurden. Der Kompilererkennt, dass dies beim Methodenaufruf GetZN(...) geschieht.Dass hier implizit eine Referenzübergabe erfolgt, ist selbstver-ständlich.

Eigenschaften (Properties)Im deutschsprachigen Raum versteht man unter den Eigen-schaften von Klassen im Wesentlichen die Member-Variablen.Die Klasse Fraction besitzt zwei Eigenschaften vom Typ int, dieauch public vereinbart sind, und somit jederzeit direkt von „au-ßen“ veränderbar sind. Das kann aber in vielen Fällen nicht gutsein, da damit ein Programmierer auch Werte zuordnen könn-te, die logisch nicht erlaubt sind. So kann einem Programmie-rer nicht verboten werden, den Nenner eines Objektes vomTyp Fraction in seinem Programm explizit auf 0 zu stellen, wasnatürlich mathematisch Unfug ist. Die Lösung hierzu bestehtdarin, dass diese Eigenschaften private vereinbart werden, unddamit ein direkter Zugriff nicht mehr möglich 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 än-dern. Sollten aber Änderungen direkter Members möglich sein,

Page 68: Otmar Ganahl - C Sharp

C #

68 2

dann müssen hier eben entsprechend Methoden angebotenwerden, die z.B. so aussehen könnten:

public int GetZ(){

return z;}

public int GetN(){

return n;}public void SetZ(int z){

this.z = z;}public void SetN(int n){ //0 für 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 ungültigen Werten kommt.(Zugegeben, die Implementierung von SetN(...) ist nicht zufrie-denstellend. Die Verwendung des Exception-Mechanismuswürde sich hier anbieten. Dazu aber später mehr.)

Nachteilig an dieser Variante ist, dass die Lesbarkeit der Pro-gramme doch deutlich abnimmt. C# bietet aber hier einenneuen Mechanismus an, der nachfolgend vorgestellt wird:

CD-BeispielFraction10

private int z;private int n;

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

Page 69: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache692

n=value; if(n==0) n=1; }}

Drei neue Schlüsselwörter lernen Sie hier kennen. In den Blö-cken, die mit set bzw. get eingeleitet werden, wird die Funktio-nalität programmiert, die durchgeführt werden soll, wennsyntaktisch auf N zugegriffen wird.

Im lesenden Zugriff (get) wird hier unmittelbar der Wert derprivaten Member-Variable n zurückgegeben. Bei einem schrei-benden Zugriff auf N (set) kann über das Schlüsselwort valueauf den Wert zugegriffen werden, der bei einer Zuweisung aufdie Member-Variable N verwendet wird.

Fraction r = new Fraction(3,2);r.N = 0; //Schreibender Zugriffint n = r.N; //Lesender Zugriff

Was nun ausschaut wie der Zugriff auf eine Member-Variableist eigentlich ein Methodenaufruf. Wenn Sie auf den set-Blockverzichten, dann kann nur lesend auf N zugegriffen werden,wenn Sie nur den set-Block implementieren, dann hätten Sienur schreibenden Zugriff auf N.

Die Verwendung von Properties sind in der gezeigten Formsehr zu empfehlen, da diese die Lesbarkeit des Codes dochdeutlich steigern.

Im Deutschen wird der Ausdruck „Eigenschaft“ gerne im Zu-sammenhang mit Member-Variablen verwendet. Deshalb wirdhier der Terminus „Eigenschaft“ vermieden, und das (deutsch-englische) Kunstwort Member-Variable verwendet. Im Folgen-den wird auch der Ausdruck Properties gebraucht.

Ausnahmeverarbeitung – Exception Handling (EH)Vielleicht kennen Sie den Exception-Handling (EH) Mechanis-mus von C++ oder auch SEH (Structured Exception Handling)des Betriebssystems Windows. Unter C++ kann das EH optio-nal verwendet werden, da Exception-Handling unter C++ eine„teure“ Sache in Bezug auf Laufzeit und Speicherbedarf dar-

Page 70: Otmar Ganahl - C Sharp

C #

70 2

stellt. .NET und damit C# bietet ebenfalls einen Exception-Me-chanismus an, der nachfolgend vorgestellt wird.

In vielen Frameworks und APIs (Application Programming Inter-face) ist und war es sehr populär, Funktionen mit einem Rück-gabewert zu versehen, der in irgendeiner Form den Erfolg desAufrufes dokumentiert. Dies kann ein Boolscher Wert sein oderaber in Form eines „Resultatwertes“, der je nach Wert einenFehler oder Status repräsentiert. C++-Programmierer, die aufdem COM-Programmiermodell entwickeln, kennen den Da-tentyp HRESULT (32 bit), der diese Funktion erfüllt. Prinzipiellist dieser Ansatz auch unter C# möglich, aber nicht zu empfeh-len.

bool f = Methode();if(f== false){

//Fehlerbehandlung durchführen}

Es ist damit nämlich unter C/C++ und auch C# folgender Auf-ruf möglich.

Methode();

Da der Programmierer in diesem Beispiel der Rückgabewert ig-noriert, wird faktisch auf die Fehlerbehandlung gänzlich ver-zichtet. Und muss man sich jetzt wundern, dass Bugs inProgrammen auftauchen?

In der .NET-Laufzeitumgebung ist EH ein fundamentaler Be-standteil und es wird daher empfohlen, EH zu verwenden,statt Rückgabecodes, die still und heimlich auch ignoriert wer-den können. Wer den EH-Mechanismus von C++ kennt, wirdalles sehr vertraut finden, trotzdem gibt es einige Unterschie-de.

Im Fraction-Beispiel werden Sie einen EH-Mechanismus beider Division einführen. Sie wissen, auch bei Bruchzahlen isteine Division durch 0 nicht erlaubt. Ist die z-Komponente (Zäh-ler) des Divisors 0, dann müssen Sie mit einer entsprechendenFehlerbehandlung reagieren, da sonst ein nicht konsistentesFraction-Objekt entsteht, das einen Nenner von 0 hat!

Page 71: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache712

try – catch – throwCD-BeispielException1

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 prüft die Methode, ob der Zähler von r2 gleich 0 ist. Denndann würde die Bruchzahl den Wert 0 darstellen. Ist dies derFall, dann „werfen“ (throw) Sie eine Exception. „Werfen“ müs-sen Sie ein Objekt vom Typ System.Exception oder aber davonabgeleitet. Dieses Objekt erzeugen Sie dynamisch (hierzu exis-tiert eine größere Anzahl von Konstruktoren).

public static void Main(){

Fraction r1 = new Fraction (3,2);Fraction r2 = new Fraction (); // r2 hält 0/1Fraction r3;

r3 = r1/r2; //hier tritt eine Exception aufConsole.WriteLine(r3);

}

Wenn nun das Programm in dieser Form durchgeführt wird,dann tritt eine Exception auf, d.h. das Programm beendet sichmit einem entsprechenden Hinweis. Nun das ist nicht geradedas „Gelbe vom Ei“, wenn sich das Programm, zwar mit einemHinweis, aber endgültig verabschiedet. Sie wollen ja den Feh-ler behandeln bzw. auf einen Fehler entsprechend reagieren.

public static void Main(){ try { Fraction r1 = new Fraction (3,2); Fraction r2 = new Fraction(); // r2 hält 0/1 Fraction r3;

Page 72: Otmar Ganahl - C Sharp

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 schützen hier den Bereich im Hauptprogramm durch einenso genannten try-Block. Dieser Block wird auch guarded-Blockgenannt. Im Anschluss an einen try-Block folgen ein oder aberauch mehrere catch-Blöcke („fangen“), die eine solche Excepti-on auffangen.

D.h., wenn im guarded-Block eine Exception auftritt, dann wirdunverzüglich der catch-Block angesprungen und die entspre-chenden Anweisungen durchgeführt. Nach Ausführung dieserAnweisungen wird der Code hinter den catch-Blöcken weiter-geführt.

Es können auch mehrere catch-Blöcke definiert werden, umauf unterschiedliche Arten von Exceptions auch unterschied-lich zu reagieren. Das nachfolgende Beispiel soll das verdeutli-chen.

CD-BeispielException2

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; }

Page 73: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache732

}

class Fraction{. . .}

Hier wird eine neue Klasse FractionException definiert. DieseKlasse ist von Exception abgleitet (genaueres über Vererbungfolgt im Kapitel 3, Baugruppen (Assembly)), und kann daherauch im .NET-Exception-Mechanismus verwendet werden. DieKlasse implementiert zwei readonly-Properties (ID und mes-sage), die im Konstruktor belegt werden können.

public static void Main(){ try { Fraction r1 = new Fraction (3,2); Fraction r2 = new Fraction(); // r2 hält 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 diesevom Typ abhängig. Da die Methode Main() einen catch-Blockfür eine Exception vom Typ FractionException implementiert,„fällt“ das Programm bei Auftreten einer Fraction-Divisiondurch 0 in diesen Block. Sämtliche anderen Exception werdenvom allgemeinen catch-Block abgehandelt.

Page 74: Otmar Ganahl - C Sharp

C #

74 2

Exceptions können auch verschachtelt sein. Ein kleines Experi-ment soll auch dies wieder verdeutlichen:

CD-BeispielException3

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 Exc-Test, die eine Division durchführt. Dieser Codeteil ist in der Me-thode selbst in einem guarded-Block geschützt. Die Methodewird in Main(), ebenfalls in einem guarded-Block, ausgeführt.Tritt nun eine Exception in der Methode ExcTest auf, so wirdnatürlich der catch-Block in der Methode selbst verwendet. DieAusgabe auf der Konsole wird Folgende sein:

Page 75: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache752

ExcTest wird aufgerufenCustom EH: Achtung Division durch 0 ID: 5ExcTest ist beendetHier ist das Programm fertig

Sie können allerdings in diesem catch-Block die Behandlungder Exception an den übergeordneten catch-Block weiterleiten.Mit dem Schlüsselwort throw, ohne Angabe eines Exception-Objektes, wird dann direkt auf den übergeordneten catch-Block gesprungen.

CD-BeispielException4

catch(FractionException e){ Console.WriteLine("Custom EH: {0} ID: {1}", e.message,e.ID); throw; //Weiterreichen}

Die Ausgabe auf der Konsole bestätigt dies.

ExcTest wird aufgerufenCustom EH: Achtung Division durch 0 ID: 5Exception of type FractionExamples.FractionException was thrown.Hier ist das Programm fertig

Beachten Sie, dass hier der Codeabschnitt in der Methode Exc-Test nach dem catch-Block nicht durchgeführt wurde, was Sieaber nicht überraschen sollte, da die Exception ja sofort weiter-gereicht wurde.

finallyEine immer wieder auftretende Schwierigkeit gibt es aberbeim EH. Wie Sie festgestellt haben, führt das Programm seineTätigkeit nach den catch-Blöcken weiter. Dies kann insofernzu Schwierigkeiten führen, wenn bestimmte Ressourcen ge-öffnet sind (z.B. eine Datenbanksitzung), und dann wegen ei-ner Exception nicht mehr freigegeben werden können. Fürsolche Fälle bietet C# eine Lösung mit dem Schlüsselwort fi-nally. Nachfolgendes Beispiel soll die Verwendung verdeutli-chen:

Page 76: Otmar Ganahl - C Sharp

C #

76 2

CD-BeispielException4

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 durchfühen"); } Console.WriteLine("ExcTest ist beendet");}

Zu jedem guarded-Block kann ein finally-Block zugeordnetwerden. Dieser wird aufgerufen, egal was im guarded-Blockpassiert. Die Ausgabe auf der Konsole bestätigt dies:

ExcTest wird aufgerufenCustom EH: Achtung Division durch 0 ID: 5Diesen Codeabschnitt unbedingt durchführenException of type FractionExamples.FractionException was thrown.Hier ist das Programm fertig

PerformanzWarum ist EH unter C++ aufwändig und hat großen Einflussauf Codelänge und Laufzeit? EH unter C++ ist deshalb sehr auf-wändig, da sämtliche Objekte, die im guarded-Block angelegtwurden, entsprechend sicher auch aufgelöst werden müssen,indem der Destruktor aufgerufen wird. Dies auch im Falle ei-ner Exception. Wäre dies nicht der Fall, dann könnten böseSpeicherlecks auftreten.

Page 77: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache772

Ebenfalls müssen Objekte, die auf dem Stack angelegt sind,mit entsprechenden Destruktoraufrufen aufgelöst werden.Stack-Unwinding wird das genannt und ist sehr aufwändig.Die Aufgabe des Kompilers ist es, hier entsprechende Vorsorgezu tragen. C++-Programme, die EH verwenden, sind meistspürbar langsamer. Das ist ein wesentlicher Grund, warumviele Programmierer auf C++ EH verzichten.

Nun fragen Sie sich sicherlich, wie schaut dies unter C# aus.Nun hier ist für die Auflösung der Objekte nicht der Kompilerzuständig, sondern der Garbage Collector (GC). Daher ist EHunter .NET um einiges schneller und der Overhead zur Laufzeitist vernachlässigbar. Verwenden Sie deshalb unter C# EH flei-ßig, es wird zu deutlich stabileren Programmen führen, ohnedass diese auch an Performanz verlieren.

VererbungDas Konzept der Vererbung (Wiederverwenden von Code überVererbung) ist eines der wichtigsten in der objektorientiertenSoftwareentwicklung überhaupt.

Sie werden diese Konzepte, die C# natürlich unterstützt, an-hand eines Beispiels kennen lernen, das im Buch „Inside VisualC++“ von David Kruglinski verwendet wurde. Dieses Beispieleignet sich gut, um das Konzept der Vererbung zu verdeutli-chen.

In einem Weltraumsimulationsprogramm bewegen sich dieunterschiedlichsten Massen im Raum (Sonnen, Planten, Mon-de, Raumschiffe etc.). Alle Massen gehorchen den physikali-schen Gesetzen, unabhängig ihrer Erscheinungsform. Diegemeinsamen Eigenschaften könnten in einer eigenen Klassedefiniert und implementiert werden, sämtliche Erscheinungs-formen werden dann diese Klasse als Basisklasse verwenden.

CD-BeispielOrbiter1

using System;

class Orbiter{ protected decimal x; protected decimal y; protected decimal z; protected string name;

Page 78: Otmar Ganahl - C Sharp

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 undimplementieren Sie die Klasse Orbiter. Natürlich werden Siekein ausgewachsenes Simulationsprogramm erstellen, sondernbeschränken die Funktionalität der Klasse auf den Namen einesObjektes sowie auf die Position des Objektes. Diese Eigenschaf-ten (name,x,y,z) sollen Member-Variablen der Klasse Orbiterdarstellen. In einem Simulationsprogramm sollten diese Körpernatürlich dargestellt werden. Bezüglich der Darstellung wird dieKlasse auf eine Ausgabe auf die Konsole beschränkt (Display-Methode), die den Namen und die Position des Objektes aus-gibt. (Zugegeben, Sie brauchen schon ein gutes Marketing,wenn Sie mit diesem Programm Geld verdienen wollten.)

In einem ersten Versuch wird die Klasse gestestet. BeachtenSie, dass für die Darstellung der Koordinaten der Typ decimalverwendet wird (Fließkommatyp mit 96 Bits). Konstante Wer-te vom Typ decimal verlangen den Buchstaben M (groß M ) imAnschluss an den Zahlenwert.

Page 79: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache792

Erweitern Sie das Simulationsprogramm, indem Sie zwei neueKlassen einführen, eine für Planeten und eine für Raumschiffe.

CD-BeispielOrbiter2

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); }}

Page 80: Otmar Ganahl - C Sharp

C #

80 2

Das Ableiten funktioniert ähnlich wie in C++, indem Sie nachdem Namen der neuen Klasse einen Doppelpunkt gefolgt vomNamen der Klasse angeben, von der Sie die Eigenschaften undFunktionalität erben wollen.

class Planet : Orbiter

Die zusätzlichen Eigenschaften von Planet werden nun in be-kannter Weise als Member-Variablen hinzugefügt. Die KlassePlanet erhält eine zusätzliche Member-Variable radius und ei-nen eigenen Konstruktor für die Belegung der Members. Es istnaheliegend, im Planet-Konstruktor für die Belegung derMembers, die von Orbiter geerbt wurde, den Konstruktor derBasisklasse 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 angege-ben. Im Unterschied zu C++, wo der Name der Basisklasse an-gegeben wird, verwendet C# das Schlüsselwort base.

Die Implementierung der Methode Display() der Klasse Planetals auch der Klasse Spaceship werden nun aber entsprechendangepasst:

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);}

Page 81: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache812

Da auch schon die Basisklasse eine Methode Display() besitzt,müssen Sie das Schlüsselwort new angeben, um dem Kompilerexplizit zu zeigen, dass Sie die Methode überschreiben wollen.

Weiterhin erwähnenswert ist die Verwendung der Schutzklas-se protected für die Members name,x,y,z in der Orbiter-Klasse.Wenn dies nicht gemacht worden wäre, sondern Sie alsSchutzklasse private angegeben hätten, würde sich der Kompi-ler bei der Methode Display der Klasse Planet mit einem Fehlermelden, da auf die Members der Basisklasse direkt zugegriffenwird. Genauere Erläuterungen zum Schutzkonzept folgengleich.

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 Typenund 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 Klassenwird überschrieben und kommt nicht zu Tage. Wollte man die-se aufrufen (vorhanden ist sie ja), dann müsste ein Casting aufdie 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 möglich ist!

Page 82: Otmar Ganahl - C Sharp

C #

82 2

Virtuelle MethodenDa sich immer wieder feststellen lässt, dass auch erfahreneC++-Programmierer sich mit dem Begriff virtuelle Methodeschwer tun, wird dieses Feature an einem kleinen Beispiel de-monstriert.

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 alsC/C++-Programmierer nicht durch die ungewöhnliche Syntaxirritieren. Im nächsten Kapitel wird auf Felder näher eingegan-gen. Objekte lassen sich jederzeit zu Objekten der Basisklassecasten, genau so, wie Sie es schon aus C++ oder anderen ob-jektorientierten 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 schön, dass für alle (!) Objekte die Display-Im-plementierung von Orbiter verwendet wird, was auch nichtverwunderlich ist, da ja im Feld Orbiter-Typen gehalten wer-den. Der Kompiler verwendet dann eben die Display-Methode

Page 83: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache832

von Orbiter. Wenn Sie aber in der Basisklasse die Methode mitdem Schlüsselwort virtual deklarieren, und in den abgeleitetenKlassen das Schlüsselwort override bei den betroffenen Me-thoden angeben, ändert sich das Verhalten.

CD-BeispielOrbiter3

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 wer-den, wird zur Laufzeit die „richtige“ Methode verwendet. Dieswird in der Theorie „späte Bindung“ (nämlich während derLaufzeit) im Gegensatz zur „frühen Bindung“ (zur Kompilier-zeit) genannt. Technisch basiert das auf so genannte v-Tables(virtual tables).

Eine Klasse mit virtuellen Methoden besitzt eine Tabelle mitden Funktionszeigern aller virtuell definierten Methode. Undjedes Objekt dieser Klasse hält intern einen Zeiger auf dieseKlasse. Die genaue technische Realisierung sollte mithilfe ein-schlägiger Literatur erfolgen.

Page 84: Otmar Ganahl - C Sharp

C #

84 2

Abstrakte BasisklassenIm Beispiel könnten Sie einwenden, dass Instanzen vom TypOrbiter gar nicht notwendig sind. Dann hätten Sie einen Fallfür 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 void Display();}

Dies können Sie durch das Schlüsselwort abstract erreichen.Eine Klasse, die mit abstract gekennzeichnet ist, kann nur alsBasisklasse dienen. Es können keine Objekte diese Typs direkterzeugt werden. In einer abstrakten Klasse können auch Me-thoden als abstract definiert werden. Diese besitzen keine Im-plementierung, aber eine Implementierung ist in denabgeleiteten Klassen zwingend notwendig.

Das Gegenteil einer abstrakten Klasse ist eine Klasse, die expli-zit als Basisklasse ausgeschlossen wird. Dies ist unter C# mög-lich, indem die Klasse mit dem Schlüsselwort sealed definiertwird.

sealed class Orbiter{

protected decimal x;protected decimal y;

...}

Page 85: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache852

Würde diese Klasse als Basisklasse verwendet, würde der Kom-piler mit einer entsprechenden Fehlermeldung reagieren.

SchutzkonzeptDie Sprache C++ kennt drei Schlüsselwörter für Schutzklassen:

privatepublicprotected

C# kennt darüber hinaus noch:

internalinternal protected

Alle diese Schlüsselwörter können auf Members angewendetwerden. Die Schlüsselwörter public und internal sind darüberhinaus auch als Klassen-Modifier möglich.

internal abstract class Orbiter{ ...}class Planet : Orbiter{...}

public bedeutet, dass jeder diese Klasse sehen kann. internalschränkt die Sichtbarkeit auf ein Assembly ein. Wenn nichtsangegeben wird, dann ist die Klasse nur in der Datei sichtbar,in der diese definiert ist.

Die Angabe von Modifier ist für Members zwingend und zwarexplizit für jede Vereinbarung notwendig.

private decimal fuel;public Spaceship(string name,. . .){ . . .}

private vereinbarte Eigenschaften und Methoden sind nur inder Klassenimplementierung sichtbar. protected Vereinbarun-gen ermöglichen den Zugriff auf diese Elemente zusätzlich inImplementierungen davon abgeleiteter Klassen. public eröff-net den freien Zugriff von außen. Das Schlüsselwort internal er-öffnet Zugriff auf die Members nur innerhalb eines Assembly.

Page 86: Otmar Ganahl - C Sharp

C #

86 2

Eine Besonderheit stellt die Kombination der Schlüsselwörterinternal und protected dar.

internal protected decimal fuel;

Diese Members sind in diesem Fall innerhalb eines Assembly(internal) verwendbar oder auch über Vererbung in abgeleite-ten Klassen (protected). Die Kombination stellt also eine logi-sche ODER-Verknüpfung von internal und protected dar.

Interface (Schnittstellen)Interfaces sind abstrakte Klassen, bei denen alle Members im-plizit abstract deklariert sind.

CD-BeispielOrbiter4

interface IDescription{

string GetText();string GetXML();

}

class Orbiter{ . . . . . .

Mit dem Schlüsselwort interface ist die so definierte Klasse im-plizit abstract und es sind auch sämtliche Members abstractdefiniert. Es ist gebräuchlich, interface-Klassen mit dem Groß-buchstaben I (für Interface) einzuleiten. Im Beispiel haben Sieeine Schnittstelle erzeugt, die Methoden zur Beschreibung vonObjekten 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()

Page 87: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache872

{ 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 aberSchnittstellen. Da die Klasse Planet auch von IDescription abge-leitet wurde, sind diese Methoden entsprechend auch zu im-plementieren. 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());

Page 88: Otmar Ganahl - C Sharp

C #

88 2

IDescription DescSS = p as IDescription; if(DescSS != null) Console.WriteLine(DescSS.GetText());}

Der Zugriff auf ein Planet-Objekt über die Schnittstelle erfolgtper 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 sämtliche Me-thoden der Basisklasse (Interface) virtuell vereinbart sind, wer-den auch über den Zugriff in dieser Art die Methoden desObjektes aufgerufen.

IDescription DescP = (IDescription)p;

Wenn allerdings das Objekt keine Schnittstelle (Basisklasse)vom Typ IDescription besitzt, dann kommt es zu einer Excepti-on beim Casten. Mit dem C#-Operator is können Sie vor demCasting das Objekt anfragen, ob es eine Schnittstelle vom ge-wünschten Typ auch implementiert. Der Ausdruck p is IDe-scription hat den Wert true, wenn p die SchnittstelleIDescription implementiert.

Eine andere Möglichkeit besteht in der Verwendung desSchlüsselwortes as.

IDescription DescPP = p as IDescription;

Die Referenz vom Typ interface wird nicht per Casting, sondernwie oben gezeigt mit dem as-Operator initialisiert. Würde dieSchnittstelle nicht vom Objekt unterstützt werden, wäre derWert von DescPP nach Ausführung der Operation null. EinePrüfung auf null, bevor dann mit dem Objekt gearbeitet wird,ist natürlich sinnvoll, will man keine Exception verursachen.

ZusammenfassungSchnittstellen sind ein sehr populärer Ansatz der modernenSoftwareentwicklung. Wenn ein Clientprogramm fähig ist,eine Schnittstelle zu bedienen, dann kann das Programm alleObjekte verwenden, die diese Schnittstelle implementieren(da Schnittstellen ja virtuell sind). Es ist damit eine optimaleEntkopplung zwischen einem Clientprogramm und einer Kom-ponente zu erreichen. Kleine Kopplung (low coupling) ist einwichtiges Kriterium beim Design von Klassen-(Objekt-)Model-len.

Page 89: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache892

Diesbezüglich wird auf entsprechende Literatur aus dem Be-reich Softwaredesign verwiesen.

Felder und CollectionsUnter .NET sind Felder(Arrays) Objekte, die selbst von Sys-tem.Array abgeleitet sind. Da System.Array ein Referenztyp ist,sind Felder immer Referenzobjekte und existieren daher aufdem Heap. Die Elemente eines Feldes werden abhängig von ih-rem Typ (Referenztyp oder Wertetyp) als Referenzen oder di-rekt im Feld gehalten.

Felder aus WertinstanzenCD-BeispielCollections1

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 für eine Referenz auf einFeld. (Beachten Sie, dass Felder selbst Referenztypen sind) unddamit nicht das Feld selbst. Erst mit der Zeile

Page 90: Otmar Ganahl - C Sharp

C #

90 2

IntArr = new int[20];

wird nun ein Feld, in diesem Fall mit fünf Objekten, erzeugt.Achten Sie auf die Syntax beim Anlegen einer Referenz auf einFeld und beim Erzeugen des Feldes. Obwohl int Wertetypensind, befindet sich aber der Speicherplatz für diese fünf Inte-gerwerte auf dem Heap.

Felder können auch unmittelbar initialisiert werden.

int [] IntArr = {0,1,2,3,4};

Ansonsten unterscheiden sich Felder in ihrer Verwendung nurunwesentlich von Arrays in C/C++.

Es ist auch möglich, mehrdimensionale Felder anzulegen:

CD-BeispielCollections2

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 eineReferenz auf ein zweidimensionales Feld. Die nächste Zeile

matrix = new int[4,3];

Page 91: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache912

belegt nun Speicherplatz für insgesamt 12 Elemente vom TypInteger (4 Zeilen, 3 Spalten).

Die Verwendung geschieht über den [ ]-Operator ähnlichC/C++.

Jagged ArraysC# kennt auch so genannte jagged arrays. Dies sind verein-facht gesagt, Felder aus Feldern. Über die Verwendung vonJagged arrays wird auf die MSDN verwiesen.

Felder aus ReferenzinstanzenBei der Verwendung von Feldern mit Referenztypen als Ele-mente ist Vorsicht angebracht. Wenn ein Feld angelegt wird,dann wird für jedes Objekt ein Speicherplatz für 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 für Ob-jekte vom Typ Planet angelegt wurden, aber keine Objekteselbst. Der Zugriff

Planet[i].Display();

wird scheitern, da PlanetArr[i] den Wert null hat, also auf keinObjekt verweist.

CD-BeispielCollection3

class App{ public static void Main() { Planet [] PlanetArr; PlanetArr = new Planet[3];

Page 92: Otmar Ganahl - C Sharp

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 gekrönt, da die erzeugten Refe-renzen ein Objekt zugewiesen bekommen.

Sie sehen hier ganz deutlich, dass ein sauberes Verständnisdes Unterschieds zwischen Referenztypen und Werttypen undderen Handhabung unter C# von entscheidender Bedeutungist!

System.ArrayWie schon erwähnt, sind unter .NET sämtliche Felder von derBasisklasse System.Array abgeleitet, und erben daher eine Rei-he von Methoden und Member-Variablen, die Sie als C++-Pro-grammierer 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-Ob-jekte eine Member-Variable Length besitzen. Beispielhaft füreine Reihe von statischen Methoden sei im obigen Beispiel die

Page 93: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache932

Methode Array.Reverse gezeigt. Beachten Sie, dass die Metho-de Reverse eine statische Methode der Klasse Array darstellt!Erforschen Sie die Methoden und Members der Klasse Sys-tem.Array!

IndexerHier und da mag es sinnvoll sein, einem Objekt die Möglichkeitder Indizierung zu geben, sodass ein Objekt sich ähnlich einemFeld verhält (smart array). Indexer werden unter C++ durchÜberladen des [ ]-Operators realisiert. Unter C# schaut dieSyntax ein wenig anders aus.

Die Klasse Orbiter aus dem Projekt Space soll mit einem Inde-xer ausgestattet werden, sodass die Koordinaten x, y und züber den Index-Operator gelesen werden können.

CD-BeispielCollections4

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"); } } ...}

Page 94: Otmar Ganahl - C Sharp

C #

94 2

In C++ würden Sie den Operator [ ] überlagern unter C# wirdder this-Operator für die Implementierung verwendet. FalscheIndizes lösen 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 werdensollten, wenn eine entsprechende Abstrahierung auch sinnvollerscheint. Im gezeigten Beispiel ist dies allerdings mehr alsfraglich!

Die Schnittstelle IEnumerable und IEnumerateDie Schnittstelle IEnumerable besitzt eine Methode GetEnume-rator(), die selbst ein IEnumerator-Schnittstelle zurückgibt. DieSchnittstelle IEnumerator besitzt folgende Members:

object Current; gibt das gegenwärtige Objekt aus.

bool MoveNext(); positioniert Enumerator auf das nächsteElement.

void Reset(); positioniert Enumerator vor das erste Ele-ment.

Die Basisklasse aller Felder, System.Array implementiert dieSchnittstelle IEnumerable. Hier ein Beispiel für die Anwendungdieser Schnittstelle. Beachten Sie, dass Sie den NamensraumSystem.Collections freigeben.

CD-BeispielCollections5

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

Page 95: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache952

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);

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 Feld-objekt PlanetArr geholt (per Casting). Über diese Schnittstellekann dann ein Enumerator erzeugt werden (GetEnumerator),der dann erlaubt, sich innerhalb des Feldes zu bewegen. Be-achten Sie, dass das Property Current ein beliebiges Objekt zu-rückgeben kann, und daher gecastet werden muss.

Speziell für Objekte mit der Schnittstelle IEnumerable bietetC# die spezielle foreach-Syntax an. Dieser iteriert sich durchdas Feld in gleicher Weise, wie im ersten Beispiel gezeigt.

CD-BeispielCollections6

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);

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

Page 96: Otmar Ganahl - C Sharp

C #

96 2

} }}

ArrayListObjekte vom Typ System.Array, also C#-Felder, sind nicht dyna-misch, d.h. es können, nachdem das Feld erzeugt wurde, keineweiteren Feldelemente hinzugefügt werden. Eine Klasse, diedies erlaubt, ist die Klasse ArrayList im Namensraum Sys-tem.Collections. ArrayList hält die Objekte in Form von Referen-zen auf die Basisklasse System.Object, und kann daherbeliebige Objekte auch unterschiedlichen Typs verwalten.

CD-BeispielCollections7

class App{ public static void Main() { ArrayList pa = new ArrayList();

//Container füllen 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(); //löscht den Container }}

Die Methode Add fügt der Liste ein neues Objekt hinzu. Auf dieListe kann auch mit dem Index-Operator zugegriffen werden.

Page 97: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache972

Allerdings erhalten Sie Objekte vom Typ System.Object zurück,die erst in den richtigen Typ gecasten müssen, wenn Sie diesesinnvoll verwenden wollen. ArrayList implementiert eine Reihevon nützlichen Methoden und Properties. (Count, Remo-veAt(...), Clear() etc.). Mehr dazu finden Sie in der MSDN unterdem Stichwort ArrayList.

MapsDatenstrukturen, bestehend aus einem Schlüssel (key) und ei-nem Wert (value), werden Map (hier wird der englische Aus-druck beibehalten) genannt und haben im Softwaredesigneine wichtige Bedeutung. .NET bietet dem Entwickler die Klas-se Hashtable an, die die Funktionalität von Maps implemen-tiert. Die Klasse Hashtable befindet sich ebenfalls imNamensraum System.Collections. Die Verwendung sehen Siean diesem Beispiel:

CD-BeispielCollections8

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 Na-men PlanetSystem. Mit der Methode Add unter Angabe einesSchlüssels (der übrigens von einem beliebigen Typ sein kann,und im Beispiel ein String darstellt) kann ein Objekt in die Ta-belle eingetragen werden. Über den Index-Operator kann nundas Objekt mit dem Schlüssel als Index-Parameter aus der Ta-belle gelesen werden. (Casting ist natürlich selbstverständlich

Page 98: Otmar Ganahl - C Sharp

C #

98 2

notwendig!) Die Klasse Hashtable implementiert eine Mengevon Methoden und es ist empfehlenswert, diese einmal zu er-forschen. Hier gilt wiederum der Verweis auf die MSDN.

KontrollstrukturenC# als Vertreter der C-Sprachen besitzt im Wesentlichen die-selben effizienten Syntaxmöglichkeiten, um den Programm-fluss zu steuern. Allerdings unterscheiden sie sich in einigenDetails. In diesem Kapitel werden Sie diese kleinen, aber dochwichtigen Unterschiede kennen lernen.

VerzweigungenSo wie C und C++ kennt auch C# grundsätzlich zwei Arten vonVerzweigungen (if-else, switch-case). Sie beginnen mit der if-else-Verzweigung. Die Syntax in C lautet im allgemeinen Fall

if( <Ausdruck> )Anweisung bzw. Block

elseAnweisung bzw. Block

Das Programm wertet den Ausdruck aus und testet diesen auf0. Ergibt die Bewertung ungleich 0, dann wird der if-Blockdurchgeführt, ergibt die Bewertung 0, dann wird der else-Blockdurchgeführt. Dies hat einige große Vorteile (Performanz),aber auch eine Reihe von Nachteilen. Sehr leicht schleichensich Fehler ein (denken Sie z.B. nur an if(x=3) ...). Die if-Ver-zweigung wurde daher unter C# restriktiver gestaltet. DerAusdruck einer if-Anweisung muss hier vom Typ bool sein, an-sonsten meldet sich der Kompiler mit einer Fehlermeldung.Anweisungen wie z.B.

int val = 5;if(val){

...}

sind nicht möglich, wohl aber

Page 99: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache992

int val = 5;if(val != 0){ ...}

oder auch

bool val = true;if(val){ …}

Das C#-switch-case-Konstrukt unterscheidet ebenfalls ein we-nig vom C/C++-Verhalten. Unter C# verlangt jedes case defini-tiv ein break! Sicherlich haben Sie auch schon viel Zeit mit derFehlersuche vergeudet, weil Sie ein break in einem switch-case-Konstrukt übersehen haben. Nun werden Sie einwenden,gerade dieses Feature war sehr angenehm, um Code kürzerund performanter zu gestalten. Dies ist aber mit einem goto-Befehl auch unter C# sehr leicht zu bewerkstelligen.

Ein Beispiel:

int ix;switch (ix){

case 1://Anweisungenbreak;

case 2:goto case 3;break;

case 3://Anweisungenbreak;

default://Anweisungenbreak;

}

Sie sehen, jedes case-Label muss mit einem break abgeschlos-sen werden, ja auch das default-Label. case 2: und case 3: führendenselben Code durch, ohne diesen mehrfach zu implementie-ren

Page 100: Otmar Ganahl - C Sharp

C #

100 2

Schleifen und IterationenDie while- , und do-while-Schleifen funktionieren wie unter C,nur dass diese unter C# ebenfalls einen boolschen Ausdruckverlangen. Auch die for-Schleife verlangt unter C# in der Be-dingungskomponente einen boolschen Ausdruck.

Neu hinzu gekommen unter C# ist die foreach-Schleife, die Sieim Abschnitt Die Schnittstelle IEnumerable und IEnumeratorschon kennen gelernt haben.

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

Die forach-Schleife funktioniert allerdings nur auf Objekte, diedie Schnittstelle IEnumerable implementieren. Dies ist bei derKlasse System.Array der Fall, und Sie wissen, dass C#-FelderObjekte sind, die von System.Array abgeleitet sind. Daher kanndie foreach-Schleife auf jedes „normale“ Feld angewendetwerden.

SprungbefehleC# hat dieselben vier Sprungbefehle wie C und C++.

break und continue

Wie unter C/C++ kann break verwendet werden, um eine lau-fende Iteration oder switch-Anweisung zu unterbrechen unddie Ausführungen nach der Anweisung weiterzuführen.

goto

Das viel diskutierte goto veranlasst das Programm, an ein be-stimmtes Label zu springen. Unter C# ist goto ein wenig rest-riktiver als unter C/C++. So sind Sprünge nur innerhalb einesBockes möglich. Eine sinnvolle Verwendung von goto habenSie bei der Syntaxvorstellung der switch-case-Anweisung ken-nen gelernt.

return

Hier gibt es nicht viel zu erzählen, die return-Anweisung veran-lasst das Programm, die laufende Funktion zu verlassen und

Page 101: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache1012

zur aufrufenden Funktion zurückzukehren. Optional kann einWert zurückgegeben werden.

DelegatesDelegates sind Vereinbarungen, die einen Prototyp einer Funk-tion spezifizieren. Sie kennen sicherlich das Konzept einesFunktionszeigers in C/C++. Das ist eine Variable (typisiert), dieeine Funktion (bzw. einen Funktionszeiger) aufnehmen kann.Allerdings kann eine solche typisierte Variable nicht eineAdresse einer beliebigen Funktion aufnehmen, sondern nurvon solchen Funktionen, die denselben Prototyp (Typ des Rück-gabewertes und Anzahl und Typ der Parameter) besitzen. Dele-gates sind das Analogon dieser Funktionszeiger unter C#, miteinigen Verbesserungen, die eine fehlerhafte Verwendung vonFunktionszeigern von vornherein verhindern sollten.

Nachfolgend ein Beispiel zur Veranschaulichung: Sie werdennun einen kleinen Command Executer basteln, der auf be-stimmte Befehle reagieren soll.

CD-BeispielDelegate1

using System;class Proc{ static public string Do(string com) { switch(com) { case "cd":

Console.WriteLine( "Befehl: cd wurde ausgeführt");

break; case "copy":

Console.WriteLine( "Befehl: copy wurde ausgeführt");

break; default:

Console.WriteLine("Befehl nicht erlaubt"); break; } return com; }}

Page 102: Otmar Ganahl - C Sharp

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 NamenDo bereit. Die Methode Do hat einen String-Übergabeparame-ter, der ein Kommando darstellt. In einer switch-case-Strukturwird die Methode auf die unterschiedlichen Kommandos rea-gieren (im Beispiel Ausgaben auf die Konsole). Die Methodegibt dann am Schluss den Kommandostring als Rückgabewertwieder zurück.

In der Main()-Methode der Klasse App arbeitet eine „Endlos-schleife“, die nach Ausgabe eines Prompts auf eine Eingabewartet (Console.ReadLine). Der eingelesene String wird dannals Parameter für den Aufruf Do der Klasse Proc verwendet. Beieiner Eingabe von „ende“ wird die Schleife verlassen und dasProgramm terminiert.

Diese Beispiel soll nun so abgeändert werden, dass die Aus-wahl der Prozedurfunktion (Do) dynamisch gestaltet werdenkann.

Wie eingangs schon erwähnt, eignen sich hierzu Delegates. Die.NET-Klasse(n) System.Delegate (und System.MulticastDelega-te) implementieren die Funktionalität. Wenn Sie unter C# pro-grammieren, werden Sie eher selten direkt mit diesen Klassenarbeiten, weil C# die Arbeit mit Delegates mit dem Schlüssel-wort delegate unterstützt. Das C#-Delegate-Modell hat sehrviel Ähnlichkeiten mit Klassen und ist mit diesem Denkmodellam einfachsten zu verstehen (und daher wird dieses Modellverwendet, wenngleich im Detail das Ganze ein wenig kom-plexer ist).

public delegate string Procfunc(string s);

Page 103: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache1032

Diese Zeile sieht einem Funktionsprototyp zum Verwechselnähnlich. Hier wird aber eine neue Klasse mit dem Namen Proc-func definiert. Wenn Procfunc eine Klasse darstellt, dann kön-nen auch Objekte vom Typ Procfunc angelegt werden.

Procfunc f = new Procfunc(Proc.Do);

Hier wird ein Objekt mit dem Namen f vom Typ Procfunc er-zeugt. Dem Konstruktor der Klasse Procfunc kann ein Parame-ter in Form einer Methode (Funktionszeiger) mitgegebenwerden. Damit verwaltet das Objekt f diese Methode. Beach-ten Sie allerdings, dass Sie dieser Klasse Procfunc als Konstruk-tionsparameter nur Methoden mitgeben dürfen, die demPrototyp bei der Definition der Klasse Procfunc entsprechen.Im Beispiel können konkret Objekte vom Typ Procfunc nur Me-thoden aufnehmen, die genau einen Übergabeparameter vomTyp String und einen Rückgabewert String unterstützen.

Mit diesem Objekt können nun Aufrufe der Methode getätigtwerden, die vom Delegate verwaltet werden.

f(com);

Das Objekt f wird den Parameter der Methode weiterleiten(delegieren), die das Objekt auch verwaltet (im konkreten Fallder Methode Do).

Erweitern Sie nun das Beispiel unter Verwendung eines Dele-gates für die Prozedurfunktion!

CD-BeispielDelegate2

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);

Page 104: Otmar Ganahl - C Sharp

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 Delega-te-Objekt. Das Beispiel implementiert eine alternative Proze-durfunktion Work im Namensraum App, die dem Prototypgehorcht, den das Delegate Procfunc vorschreibt. Die MethodeWork kann daher vom Delegate Procfunc verwaltet werden.

Procfunc f = new Procfunc(Work);

Dies Zuordnung lässt sich natürlich ohne Weiteres dynamischgestalten.

Multicast-DelegatesIm bisherigen Denkmodell verwaltet ein Delegate genau eineMethode (technisch in Form eines Funktionszeigers). Das Dele-gate „delegiert“ dann Aufrufe an diese Methode weiter.

Stellen Sie sich nun ein Delegate vor, das mehrere Methodenverwalten kann, und Aufrufe dann nacheinander an alle dieseverwalteten Methoden weiterreicht. Solche Delegates existie-ren und werden Multicast-Delegates genannt.

CD-BeispielDelegate3

using System;

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

Page 105: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache1052

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 Funkti-onszeiger 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 sta-tische Methode Logger.NotifyMessage zur Verwaltung überge-ben. Neu ist nun aber, dass dem Delegate unter Verwendungdes Operators += eine weitere Methode (Alarm.AlarmMessage)zur Verwaltung übergeben wird. Das Delegate wird nun Aufru-fe an beide Methoden weiterleiten. Die Aufrufe erfolgen se-quenziell in der Reihenfolge, wie die Methoden dem Delgatezur Verwaltung übertragen wurden.

Page 106: Otmar Ganahl - C Sharp

C #

106 2

An der Konsolenausgabe werden Sie sehen, dass mitfs(„Mes1“); beide Methoden aufgerufen wurden, die das Dele-gate fs verwaltet.

Nachricht: Mes1 gespeichertAlarm: Mes1 weitergegeben

Bei Multicast-Delegates gibt es allerdings eine kleine Ein-schränkung. Diese können nur Methoden verwalten, die kei-nen Rückgabewert besitzen.

Multicast-Delegates werden intern nicht mit der Klasse Sys-tem.Delegate implementiert, sondern mit der Klasse Sys-tem.Multicast-Delegate.

Der C#-Kompiler verwendet intern automatisch Multicast-De-legates, wenn die Vereinbarung mit dem Schlüsselwort dele-gate eine Methode ohne Rückgabewert repräsentieren soll.

EventsEvents werden verwendet, wenn ein Objekt ein anderes be-nachrichtigen möchte, dass ein bestimmtes Ereignis eingetrof-fen ist. Events werden oft bei grafischen User Interfacesverwendet. Als COM-Programmierer ist Ihnen sicherlich derEvent-Mechanismus (Connection Points) bekannt. VB abstra-hiert diese Technik im Besonderen, verwendet aber intern denConnection Point-Mechanismus.

Es gibt einige Software-Patterns (Muster) für Event-Mechanis-men. Vielfach durchgesetzt und meist angewendet wird dasso genannte „Publish-Subcribe“-Pattern.

Ein Objekt, nennen Sie es Source-Objekt, publiziert Events. An-deren Objekten, nennen Sie sie Target-Objekte, ist es also be-kannt, dass dieses Source-Objekt Events generieren kann. DieseObjekte können sich nun bei den Source-Objekten anmelden(subscribe), dass sie, wenn ein Ereignis auftritt, informiert wer-den. Technisch geschieht das Anmelden in der Form, dass dieTarget-Objekte beim Anmelden dem Source-Objekt einenFunktionszeiger mitgeben, über den dann das Source-Objektdie Funktion aufruft, sobald ein Ereignis eingetreten ist. Nunkönnen sich im Allgemeinen mehrere Target-Objekte bei ei-nem Source-Objekt anmelden.

Page 107: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache1072

CD-BeispielEvent1

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);

Page 108: Otmar Ganahl - C Sharp

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 vonDelegates. C# bietet ein eigenes Schlüsselwort event an.

Im Beispiel wird zuerst ein Delegate definiert.

public delegate void SignalHandler(string s);

Die Klasse Source definiert eine Member-Variable vom Typ Sig-nalHandler mit dem Namen Notify. Da das Delegate Signal-Handler Methoden ohne Rückgabewerte verwaltet, istSignalHandler ein Multicast-Delegate (siehe vorheriges Kapi-tel).

class Source{ public event SignalHandler Notify; public void DoSignal() { Notify ("Ein Event"); }}

Der Aufruf Notify(...) in der Methode DoSignal() der KlasseSource delegiert nun alles an die Methoden, die das DelegateNotify verwaltet.

Im Hauptprogramm wird ein Objekt vom Typ Source mit demNamen S angelegt. Anschließend werden dem Delegate Notifyder Klasse S in bekannter Weise drei Methoden zur Verwal-tung ü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,Tound T2 dem Delegate zur Verwaltung übergeben.

Page 109: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache1092

Auf der Konsole sollte dann folgende Ausgabe zu sehen sein:

Target: Ein EventOtherTarget: Ein EventTarget: Ein Event

Sie werden sich vielleicht fragen, welche Bedeutung denn dasSchlüsselwort event bei der Definition der Member-VariableNotify hat?

public event SignalHandler Notify;

Und diese Frage ist auch berechtigt. Sie können nämlich dasSchlüsselwort event auch weglassen, und das Programm funk-tioniert in gleicher Weise. Die Verwendung des Schlüsselwor-tes event hat nur informativen Charakter, die allerdings auchin den Metadaten des Assemblies eingetragen wird. Das Ent-wicklungssystem Visual Studio.NET wertet diese Informationauch aus und zeigt dem Entwickler das Member Notify auchgrafisch in Form eines „gelben Blitzes“ an.

Ein Target-Objekt kann sich auch wieder von einem Source-Ob-jekt abmelden.

CD-Beispiel Event2

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;

Page 110: Otmar Ganahl - C Sharp

C #

110 2

//Ein neues Ereignis simulieren S.DoSignal();}

Hier wird explizit eine Referenz del vom Typ Signalhandler mitAngabe der Methode T1.OnSignal erzeugt.

SignalHandler del = new SignalHandler(T1.OnSignal);

Diese Referenz können Sie nun sowohl zur Anmeldung alsauch zur Abmeldung verwenden.

//AnmeldenS.Notify += del;

//Abmelden S.Notify -= del;

System.EventHandler – DelegateObwohl der Prototyp der Bearbeitungsfunktionen im Allge-meinen frei wählbar 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 diesemPrototyp und das Source-Objekt ruft diese Funktion auf. Im ers-ten Parameter o kommt die Quelle mit (also eine Referenz aufdie Source), und im zweiten Parameter kommt die nähere Be-schreibung des Events, in Form eines Objektes vom Typ Event-Args oder davon abgeleitet. EventArgs ist eine Klasse, die imNamensraum System definiert ist. Ebenfalls im NamensraumSystem ist das Delegate EventHandler definiert, sodass dieseDefinition nicht mehr explizit durchgeführt werden muss.

CD-BeispielEvent3

using System;

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

Page 111: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache1112

public string Message;}

class Source{ public event EventHandler Notify;

public void DoSignal() { Notify(this,new MyEventArgs("Event ausgelöst")); }}

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(); }}

Page 112: Otmar Ganahl - C Sharp

C #

112 2

Dieses Beispiel verwendet eine eigene Klasse MyEventArgs, dievon EventArgs abgeleitet ist. Die Member-Varialbe Notify in derKlasse Source wird nun vom Typ EventHandler (NamensraumSystem) definiert. Der Notifizierungsaufruf in der Source-Klas-se sowie die Methode der Target-Klasse sind entsprechend zumodifizieren.

Sämtliche Klassen der .NET-Umgebung verwenden den hiervorgestellten Event-Mechanismus und es wird den Entwick-lern empfohlen, Events über das Delegate EventHandler zu rea-lisieren. Da auch eigene EventArgs-Klassen verwendet werdenkönnen, ist das auch keine funktionale Einschränkung.

AttributeDie .NET-Laufzeitumgebung erlaubt Elementen, wie Assemb-lies, Klassen, Methoden Parameter usw. die Zuordnung von At-tributen. Dieser Ansatz ist nicht ganz neu, so hat dies schon dieIDL (Interface Definition Language) unter COM erlaubt, aber dieAttribute sind dort fix festgelegt. Unter .NET können nun be-liebige Attribute festgelegt und den Elementen zugeordnetwerden. Die belegten Werte der Attribute sind in den Metada-ten im Assembly untergebracht und können jederzeit pro-grammtechnisch über Klassen des NamensraumesSystem.Reflection erfragt werden, auch zur Laufzeit.

Attribute werden einem Element in eckigen Klammern voran-gestellt. Angenommen, Sie hätten ein Attribut Programmer,das den Namen und das Datum eines Programmierers dar-stellt. Die Verwendung würde dann so aussehen:

[Programmer("George Bush","20.02.2002"), Programmer("Bill Clinton","1.1.2002")]public class Test{

}

Die Klasse Test erhält also Informationen eines Attributs mitdem Namen Programmer.

Attribute sind selbst Klassen, die von System.Attribute abgelei-tet sind. Im folgenden Code sehen Sie, wie eine solche Attri-but-Klasse definiert wird.

Page 113: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache1132

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 selbstein Attribut braucht (AttributeUsage). Die Bedeutung wird spä-ter erklärt. Ansonsten sehen Sie eine normale Definition einerKlasse, die von der Basisklasse System.Attribute abgeleitet ist(Die Klasse System.Attribute ist im Namensraum System.Reflec-tion untergebracht). Die Klasse wurde mit zwei Member-Vari-ablen (im speziellen Fall die Strings name und date) undeinem Konstruktor zur Belegung dieser Member-Variablenausgestattet.

Beachten Sie die Vereinbarung, dass bei der Klassendefinitioneines Attributs nach dem Attributnamen das Wort Attributeangehängt wird. Die Klasse heißt ProgrammerAttribute, aberbei der Verwendung der Klasse als Attribut reicht der NameProgrammer aus!

Sobald eine Attributklasse definiert ist, können Sie diese auchanwenden.

[Programmer("George Bush","20.02.2002"), Programmer("Bill Clinton","1.1.2002")]public class Test{

}

Page 114: Otmar Ganahl - C Sharp

C #

114 2

In diesem Fall geben Sie der Klasse Test zwei Attribute mit. Dader Konstruktor der Klasse ProgrammerAttribute mit zwei Pa-rametern ausgestattet ist, müssen auch zwei Parameter beider Attributzuweisung angegeben werden. Beim Kompilierenwerden diese Informationen nun in die Metadaten eingetra-gen, und können dann beliebig ausgelesen werden. Das fol-gende Codebeispiel soll Ihnen zeigen, wie dies programmtech-nisch geschieht.

CD-BeispielAttribute1

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);

Page 115: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache1152

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 Attri-bute auszulesen. Optional kann angegeben werden, welcheAttribute gewünscht sind. Alle diese Attribute werden nun ineinem Feld vom Typ object abgelegt. Diese können nun ausge-lesen werden. Da im Allgemeinen auch unterschiedliche Attri-bute verwendet werden können, wird ein Feld zurückgegeben.Die Objekte im Feld werden nun im Bedarfsfall auf Program-merAttribute gecastet und dann entsprechend verarbeitet.

Auf der Konsole sollte dann erscheinen:

George Bush20.02.2002Bill Clinton1.1.2002

Bei der Definition einer Attributklasse muss der Klasse selbstein Attribut vom Typ AttributUsage angegeben werden.

[AttributeUsage(AttributeTargets.Class, AllowMultiple=true)]public class ProgrammerAttribute : System.Attribute{ ...

AttributeUsage muss einen Eintrag AttributeTargets besitzen.Dafür sind folgende Einträge (auch OR-Kombinationen) mög-lich.

Page 116: Otmar Ganahl - C Sharp

C #

116 2

AttributeTargets.ModuleAttributeTargets.ClassAttributeTargets.StructAttributeTargets.EnumAttributeTargets.ConstructorAttributeTargets.MethodAttributeTargets.PropertyAttributeTargets.FieldAttributeTargets.EventAttributeTargets.InterfaceAttributeTargets.ParameterAttributeTargets.ReturnAttributeTargets.DelegateAttributeTargets.AllAttributeTargets.ClassMembers

Sie definieren, bei welchen Elementen das Attribut verwendetwerden darf.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class), AllowMultiple = true)]class . . .

Im gezeigten Beispiel wird ein Attribut definiert, das auf Me-thoden und Klassen verwendbar ist, und auch mehrfach ange-geben werden darf.

Attribute haben eine wesentliche Bedeutung unter .NET. Sieerlauben deklaratives Programmieren. Da diese zur Laufzeitausgelesen werden können, kann die Laufzeitumgebung, aberauch eigene Klassen in Abhängigkeit von Attributen spezielleBehandlungen durchführen.

[WebMethod]public void Methode1(){ . . .}

Ein Beispiel dafür sind so genannte WebServices. WebServicessind Methoden, die über das Protokoll http Remote-fähig sind.Das Marshalling (Verpacken von Übergabeparameter in XMLusw.) übernimmt hier der Internet Information Server (IIS). Esgenügt eine Methode mit dem Attribut WebMethod auszu-statten, alles andere übernimmt der IIS!

Page 117: Otmar Ganahl - C Sharp

C # – die neue Programmiersprache1172

ZusammenfassungIn diesem Kapitel haben Sie nun die ersten grundlegendenKonzepte der Sprache C# und .NET erarbeitet. Trennen Sie inIhrem 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 Sprachengeschriebene Codeteile untereinander kompatibel sind.

Sie haben noch bei weitem nicht alles über .NET und auch C#gehört, aber mit diesem Wissen können Sie nun tiefer in die.NET-Welt eintauchen. Im nächsten Kapitel werden Sie kennenlernen, wie Softwarebaugruppen erstellt werden können.

Page 118: Otmar Ganahl - C Sharp
Page 119: Otmar Ganahl - C Sharp

Baugruppen (Assemblies)

Einleitung 120

Grundlagen 121

Singlefile- und Multifile-Assemblies 123

ILDASM (IL Disassembler) 128

Assembly-Entwicklung unter VisualStudio.NET

131

Shared Assembly 135

Zusammenfassung 137

Page 120: Otmar Ganahl - C Sharp

C #

120 3

EinleitungSeit den Anfängen der Softwareentwicklung sind Program-mierer bestrebt, ihren Code so zu organisieren, dass eine Wie-derverwendung in anderen Projekten möglich wird. So wirdFunktionalität in Baugruppen wie statische oder dynamischeBibliotheken gehalten, und ist somit zur Entwicklungszeit wie-derverwendbar. Dynamische Bibliotheken (DLL-dynamic link li-brary) erlauben darüber hinaus noch das Verwalten derFunktionalität in einer Datei, die dann zur Laufzeit von den un-terschiedlichen Programmen verwendet wird. Neben der Ein-sparung von Speicherplatz auf der Festplatte, wurde damitauch die Verwaltung des Codes vereinfacht. Eine Änderung imCode der Bibliothek wirkt sich sofort in allen Programmen aus,die diese Bibliothek verwenden. Im Fall der Berichtigung einesFehlers ist dies sehr angenehm.

Ein Quantensprung in der Wiederverwendbarkeit von Codewar das Aufkommen von objektorientieren Konzepten, hierallen voran die Konzepte Vererbung, Virtualität und Schnitt-stellen. Vor allem die Idee reiner virtueller Basisklassen(Schnittstellen) hat eine nachhaltige Entwicklung sowohl imSoftwaredesign als auch bei den Betriebssystemen ausgelöst.Objektorientierte, verteilte Komponentenmodelle sind ent-standen und wurden auch Bestandteil der Betriebssysteme.Unter COM (component object model) von Microsoft werdennicht Funktionen über DLLs bereitgestellt, sondern Klassenbzw. Objekte.

Im Laufe der Zeit hat sich die COM-Technologie immens weiterentwickelt. Trotz all dieser Entwicklungen zeigten sich aberauch Schwierigkeiten und Unzulänglichkeiten ab. So ist vor al-lem die Abhängigkeit dieser Komponenten von den vielen Ein-trägen in der Registrierungsdatenbank nur noch mit großemAufwand zu verwalten. Auch ist die Wiederverwendbarkeitvon COM-Objekten durch Vererben nicht in der Konsequenzmöglich, wie dies die objektorientierten Theorien fordern. Ver-erbung fehlt ganz einfach bei COM (Aggregation konnte die-sen Umstand ein wenig entschärfen).

Mit .NET wurde hier mit all den Unzulänglichkeiten aufge-räumt. .NET-Baugruppen (Assemblies) exportieren Funktionali-tät ausschließlich in Form von Klassen, können am Konzept

Page 121: Otmar Ganahl - C Sharp

Baugruppen (Assemblies)1213

der Vererbung teilnehmen und sind nicht mehr von der Regist-rierung abhängig.

In diesem Kapitel werden Sie kennen lernen, wie Sie eigeneBaugruppen erstellen, und diese auch organisieren können.

GrundlagenSchon im Kapitel 2, C# – Die neue Programmiersprache habenSie einiges über Assemblies gehört. Im Folgenden wird aberstatt des Ausdrucks „Baugruppe“ der englische Ausdruck bei-behalten.

Ein Assembly ist eine logische Einheit von Dateien zu einemGanzen. Im einfachsten Fall kann ein Assembly aus nur einer*.exe- bzw. einer *.dll-Datei bestehen. Ein Assembly beinhaltetDateien, in denen sich Code befindet, kann aber auch andereDatei, wie z.B. Ressourcen, Bilder (.jpg, .bmp, .gif etc.), Videoda-teien, Audiodateien etc. beinhalten, die für die Funktionalitätder Baugruppe notwendig sind. .NET verlangt, dass sich alleDateien einer Baugruppe, sich im selben Ordner befinden müs-sen! Die .NET-Laufzeitumgebung betrachtet alle diese Dateienals eine Einheit.

Wurden früher Komponenten in Form genau einer *.dll-Datei(z.B. als ActiveX -Komponente) verteilt, so werden Komponen-ten unter .NET in Form eines Assembly verteilt. Assemblies wer-den daher im Jargon auch logische Dlls genannt.

Ein Assembly besteht grundsätzlich aus folgenden Einheiten:

MetadatenTyp-MetadatenIntermediate Language Code (IL)Ressourcen

Im links dargestellten Assembly sind sämtliche Elemente in ei-ner Datei (Fraction.dll) untergebracht (single file assembly). Dasrechts dargestellten Assembly besteht aus einer Datei Orbiter.dll, das die Assembly Metadaten, Typ-Metadaten und IL-Codeenthält, einer weiteren Datei Math.netmodule, das Typ-Meta-daten und IL-Code enthält sowie zweier Bilddateien (enterprise.jpg und jupiter.jpg), die vom Assembly benötigt werden. Allediese Dateien bilden eine Einheit (multi file assembly).

Page 122: Otmar Ganahl - C Sharp

C #

122 3

Ein ganz besonders wichtiger Teil der Metadaten wird Mani-fest genannt. Das Manifest beinhaltet sämtliche Informatio-nen über das Assembly. Unter anderem beinhaltet dasManifest eine Liste aller Dateien, aus dem das Assembly be-steht, aus eine Liste von allen abhängigen Assemblies, die vondiesem Assembly benötigt werden, Informationen bezüglichVersion, Kultur und vieles mehr.

In den Typ-Metadaten sind sämtliche Beschreibungen der ver-wendeten Klassen und Strukturen untergebracht. Über denReflection-Mechanismus (Sie erfahren Näheres dazu in späte-ren Kapiteln) kann jederzeit die Information der Klassen undStrukturen auch zur Laufzeit erhalten werden.

Der eigentliche Code liegt im IL-Bereich. Dazu aber spätermehr.

Eine Datei, die zwar Typ-Metadaten und IL-Code enthält, aberkeine Assembly-Metadaten, und somit auch keine Manifest,wird Modul (module) genannt. Obwohl ein Modul Code ent-hält, ist diese Einheit von der .NET-Laufzeitumgebung nichtverwendbar. Ein Modul kann aber, wie Sie sehen, Bestandteileines Assembly sein. Sie können damit Code, den Sie auch inmehreren Assemblies verwenden wollen, in einem Modul ver-walten (ohne für diesen Code ein eigenes Assembly zu erzeu-gen).

Assembly Aufbau:Alle diese Einheiten

können in einer Dateivorkommen oder aber auf

verschiedene Dateienaufgeteilt sein.

Abb. 3.1

Page 123: Otmar Ganahl - C Sharp

Baugruppen (Assemblies)1233

Singlefile- und Multifile-AssembliesZwei Beispiele sollen Ihnen die Zusammenhänge verdeutli-chen. Im ersten Beispiel werden Sie ein Singlefile-Assembly er-zeugen. Diese enthält sämtliche Informationen in einer Datei.

Singlefile-AssemblyDie Klasse Fraction aus Kapitel 2 soll in ein eigenes Assemblyverpackt werden. Damit kann diese Klasse auch in anderen Pro-grammen Verwendung finden. Aus didaktischen Gründen sollvorerst dieses Assembly nicht mit der EntwicklungsumgebungVisual Studio.NET erzeugt werden, weil hier einiges im Hinter-grund abläuft. Damit Ihnen die Zusammenhänge klar werden,soll dieses Beispiel aus der Konsole entwickelt werden. StartenSie die .NET-Konsole mit Start > Programme > Microsoft VisualStudio.NET > Visual Studio.NET Tools > Visual Studio.NET Com-mand Prompt.

Erstellen Sie für die folgenden Experimente einen eigenenOrdner TestAssemblySingleFile. In diesem Ordner erzeugen Sieeine Textdatei Fraction.cs, die ausschließlich die Implementie-rung der Klasse Fraction aus dem Kapitel 2: C# – die neu Pro-grammiersprache enthält.

CD BeispielTestAssembly-SingleFile

using System;namespace FractionMath{ public class Fraction { private int z; private int n;

public Fraction () { z = 0; n = 1; } ... ... ... }}

Page 124: Otmar Ganahl - C Sharp

C #

124 3

Achten Sie darauf, dass die Klasse Fraction public vereinbartist. Dies ist notwendig, wenn andere Assemblies diese Klasseverwenden möchten. Im Visual Studio.Net Command Promptwechseln Sie in den Ordner TestAssemblySingleFile und gebendann folgende Kommandozeile ein (Sie sollten eine Batch-Da-tei make.bat erzeugen, damit können Sie sich einige Tipparbeitersparen).

csc /out:FractionMath.Fraction.dll /t:library Fraction.cs

CSC.EXE ist der C#-Kompiler. Zuerst geben Sie den Namen desAssembly (/out:FractionMath.Fraction.dll) an, das entstehensoll, dann die Art (Target) des Assembly (/t:library-Biblio-thek) und anschließend die Assemblies und C#-Quellcodeda-teien, die verwendet werden. Stößt nun der Kompiler beiseiner Arbeit auf einen Datentyp, der nicht direkt in der *.cs-Datei angelegt ist, dann sucht der Kompiler in den Manifestenund Typ-Metadaten der angegebenen Assemblies nach diesenDatentypen, und sollte diesen dort auch finden. Ansonstengibt es einen Kompilierfehler.

Nun legen Sie eine weitere Datei App.cs an und geben diesereinen kleinen Code für 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 Ver-weis (Referenz) auf die das Assembly FractionMath.Fraction.dllhaben müssen. In der Kommandozeile geben Sie ein:

csc /t:exe /out:TestApp.exe /r:FractionMath.Fraction.dll App.cs

Page 125: Otmar Ganahl - C Sharp

Baugruppen (Assemblies)1253

Da Sie nun keine Bibliothek, sondern ein Konsolenprogrammerzeugen wollen, geben Sie die Option /t: (Target) exe an. DasErgebnis sollte eine Datei mit dem Namen TestApp.exe sein.Über die Option /r: können Sie nun die notwendigen Assem-blies dem Kompiler bekannt geben.

Die Assemblies FractionMath.Fraction und TestApp.exe befin-den sich schon im selben Ordner. Sie können das Programmaus der Konsole mit Eingabe von TestApp.exe starten.

Multifile-AssemblyIm nächsten Beispiel werden Sie ein multi file assembly erzeu-gen. Hierzu erweitern Sie das Beispiel in der Form, dass beimStart des Programms ein Bild des ehrwürdigen Naturwissen-schaftlers und Mathematikers Isaac Newton erscheint. Die Da-tei newton.gif finden Sie auf der beiliegenden CD zum Buch (Essteht Ihnen natürlich frei, ein Foto von Ihnen zu verwenden).

Hierzu legen Sie einen neuen Ordner TestAssemblyMultiFile anund 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 fol-gender Form:

CD-BeispielTestAssemblyMultiFile

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

Page 126: Otmar Ganahl - C Sharp

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 Newton-Picture ist von der Klasse Form abgeleitet, die ein Windows-fenster repräsentiert. Es macht nichts, wenn Sie den Codenoch nicht verstehen, im Kapitel 6, Windows-Applikationenwerden Sie in die „Geheimnisse“ der Klasse Form eingeweiht.Vorerst interessiert hier die Zeile, die eine Datei lädt.

pb.Image = Image.FromFile("newton.gif");

Da die Bilddatei newton.gif zur Applikation gehört, ist es sinn-voll, diese in das Assembly aufzunehmen.

Damit Sie nicht zu viel in der Kommandozeile tippen müssen,sollten Sie wieder mit einem Editor eine Batch-Datei anlegen.Nennen Sie diese Batch-Datei make.bat und geben Sie folgen-de Kommandos ein:

csc /t:module /r:FractionMath.Fraction.dll /r:System.dll /r:System.Windows.Forms.dll /r:System.Drawing.dll App.csal /t:exe /out:App.exe App.netmodule /main:App.Main /link:newton.gif

Das erste Kommando kompiliert die Datei App.cs zu einemModul (/t:module) mit dem Namen App.netmodule. Sämtlichenotwendigen Assemblies geben Sie hier mit.

Mit dem Werkzeug „Assembly Linker“ (AL) können Sie Assem-blies (auch eine .exe-Datei ist ein Assembly) erzeugen. Der Auf-ruf in gezeigter Form erzeugt eine ausführbare .exe-Datei(/t:exe) mit dem Namen App.exe (/out:App.exe) unter Verwen-dung des Moduls App.netmodule. Sie müssen in diesem Fallauch die Einsprungsmethode dem Linker mitgeben

Page 127: Otmar Ganahl - C Sharp

Baugruppen (Assemblies)1273

(/main:App.Main). Damit auch die Datei newton.gif in das As-sembly mit aufgenommen wird, geben Sie dies mit der Option/link:newton.gif an. Das Assembly App.exe besteht nun aus fol-genden Dateien:

App.netmodulApp.exeNewton.gif

Diese Dateien bilden nun einen Verbund. Wenn Sie das Pro-gramm App.exe starten, zeigt sich zuerst Newtons Porträt.Wenn Sie auf dieses klicken, schließt sich das Fenster und führtdie Konsolenanweisungen durch.

Wenn Sie nun die Datei Newton.gif umbenennen, dann wirdschon beim Start der Applikation abgebrochen, weil die .NET-Laufzeitumgebung das Assembly über Prüfziffern auf Vollstän-digkeit prüft. Führen Sie dieses einmal durch!

Wenn Sie nun das AL-Kommando so abändern, dass die Bild-datei Newton.gif gar nicht ins Assembly aufgenommen wird,dann erfolgt eine Fehlermeldung nicht bei Programmstart,sondern zur Laufzeit, wenn auf die Datei zugegriffen werdensollte.

al /t:exe /out:App.exe App.netmodule /main:App.Main

Führen Sie auch dieses Experiment durch!

Wie Sie nun festgestellt haben, darf bei einem Assembly keineÄnderung durchgeführt werden. Wenn eine Applikation einAssembly lädt, überprüft die Laufzeitumgebung aufgrund desManifests sämtliche am Assembly beteiligten Dateien. Wenndie Daten nicht konsistent sind, dann wird die Applikation garnicht erst gestartet.

Aufbau des Assembly App

Abb. 3.2

Page 128: Otmar Ganahl - C Sharp

C #

128 3

ILDASM (IL Disassembler)Mit der .NET-Framework-SDK wird ein Werkzeug mit dem Na-men ILDASM ausgeliefert (IL Disassembler). Sie können diesesProgramm aus der Kommandozeile starten. Tun Sie dies undöffnen anschließend das Assembly FractionMath.Fraction.dllim Ordner TestAssembly-SingleFile.

Öffnen Sie dann das Assembly App.exe im Ordner TestAssem-blyMultiFile.

FractionMath.Fraction.dllim ILDASM

Abb. 3.3

Assembly App

Abb. 3.4

Page 129: Otmar Ganahl - C Sharp

Baugruppen (Assemblies)1293

ILDASM zeigt nun in einer lesbaren Form das Manifest und ge-gebenenfalls Typ-Metadaten an. Ein Doppelklick auf den Ein-trag Manifest öffnet ein neues Fenster mit den Daten desManifests. Hier werden u.a. Einträge der referenzierten Assem-blies, die verwendeten Module und unter anderem auch dieDatei newton.gif zu finden sein.

Mit dem Werkzeug ILDASM können Sie aber auch Metadatenvon Modulen betrachten. Öffnen Sie mit ILDASM die DateiApp.netmodule.

Manifesteintrag

Abb. 3.5

Typ-Metadate in einem Modul

Abb. 3.6

Page 130: Otmar Ganahl - C Sharp

C #

130 3

In diesem Modul erkennen Sie neben dem Manifest auch dieTyp-Metadaten. Das Modul implementiert die Typen(Klassen)App und NewtonPicture. Mit einem Doppelklick auf die Metho-den wird auch deren IL-Code (Intermediate Language Code) an-gezeigt.

In diesem Zusammenhang soll einiges über die MSIL (Micro-soft Intermediate Language) festgehalten werden. Unter .NETwird Programmcode nicht in Form von ladbarem Maschinen-code in den Dateien gehalten, sondern in einer abstrahiertenMaschinensprache. Beim Start einer Applikation findet danndie Umwandlung dieser abstrahierten Maschinensprache (IL)in die Maschinensprache der eingesetzten Plattform statt (na-tive code). Diese Umwandlung führt ein just-in-time Kompiler(JIT-Kompiler) durch. Der JIT-Kompiler ist plattformabhängig.Damit können Applikationen theoretisch über Plattformenhinweg verteilt werden. Ihnen fallen nun sicherlich Parallelenzu JAVA ein, und werden dann auch gleich an ein Performanz-problem denken. Auch JAVA verwendet eine Zwischensprache,einen Bytecode. Dieser wird allerdings von der plattform-abhängigen JAVA-Engine interpretiert. .NET kompiliert denZwischencode zu native code. Allerdings wird der IL-Code nurin „Portionen“ kompiliert, nämlich dann, wenn er benötigtwird. Einmal kompiliert bleibt dieser im Speicher. Microsoftmeint, dass diese Strategie besser ist, als wenn zu Programm-start sämtliche Assemblies JIT- kompiliert werden. Die Wahr-scheinlichkeit, dass große Teile von Code bei der Ausführunggar nicht verwendet werden, sei groß.

IL-Code-Ausschnitt

Abb. 3.7

Page 131: Otmar Ganahl - C Sharp

Baugruppen (Assemblies)1313

Ihnen sind beim Betrachten der Assemblies mit ILDASM sicher-lich die kryptischen Hash-Einträge aufgefallen. Diese habeneine besondere und auch wichtige Bedeutung. Diese Hash-Ein-träge werden beim Erstellen eines Assemblies erzeugt und er-geben sich im Wesentlichen aus den Prüfsummen der beteilig-ten Dateien. Diese Prüfsummen werden beim Start einesProgramms überprüft. Eine Änderung einer Datei hätte zurFolge, dass die Prüfsummen nicht mehr übereinstimmen unddie Laufzeitumgebung würde einen Start verhindern. Viel-leicht kann damit die Virenproblematik ein wenig entschärftwerden.

Dies hat nun aber zur Folge, dass im Multifile-AssemblyApp.exe die Datei newton.gif nicht einfach mit einer anderenBilddatei ausgewechselt werden kann, da newton.gif ja Be-standteil des Assembly darstellt!

Es ist auch nicht möglich, eine getestete und funktionierendeAnwendung durch Kopieren einer neuen Version einer Bau-gruppe zu zerstören. Dies kam sehr oft bei COM vor. Eine Ap-plikation wurde auf Basis einer COM-Komponente entwickelt.Eine Änderung der Komponente hatte die Eigenschaft, dassalle Clients dieser Komponente nun mit der neuen gearbeitethaben. Es war nicht möglich, das zu beeinflussen. Dadurchkonnte es dann zu den gefürchteten Nebeneffekten kommen.Ein Client, bei dem die modifizierte Komponente getestet wur-de, funktioniert zwar, aber bei einem anderen Client tratendann plötzlich Fehler auf.

Assembly-Entwicklung unter Visual Studio.NETGleich vorweg, unter Visual Studio.NET lassen sich keine multi-file assemblies erstellen. In der Praxis ist dieser Fall auch eherselten. Im Bedarfsfall müssen Sie auf die Konsole ausweichen.Im folgenden Beispiel erstellen Sie das Assembly Frac-tionMath.Fraction.dll mit dem Entwicklungssystem. Legen Siedazu erst eine neue Projektmappe mit dem Namen Assembly-Test an und fügen dieser ein neues C#-Projekt mit dem NamenFractionMath.Fraction an, aber wählen Sie nun die VorlageKlassenbibliothek aus!

Page 132: Otmar Ganahl - C Sharp

C #

132 3

Ändern Sie den Namen der Quellcodedatei in gewohnter Ma-nier auf einen selbstsprechenden Namen (z.B. Fraction.cs) undkopieren dann den Code der Klasse Fraction in diese Datei.

CD BeispielAssembly

using System;namespace FractionMath{ public class Fraction { private int z; private int n; ...}

Der Build-Prozess erzeugt Ihnen dann das Assembly Frac-tionMath.Fraction.dll.Testen Sie dieses Assembly gleich mit einem kleinen Konsolen-programm. Fügen Sie zur Projektmappe ein neues C#-Projektmit der Projektvorlage Konsolenanwendung hinzu und gebenSie diesem Projekt den Namen TestFraction. Die Applikation soll ja das gerade erstellte Assembly verwenden,und Sie wissen ja, ein (privates) Assembly muss sich im selbenOrdner befinden wie die ausführbare Datei. Sie können diesenKopiervorgang vom Entwicklungssystem erledigen lassen.

AuswahlKlassenbibliothek

Abb. 3.8

Page 133: Otmar Ganahl - C Sharp

Baugruppen (Assemblies)1333

Stellen Sie einen Verweis (Referenz) auf das Assembly Frac-tionMath.Fraction.dll her. Im Projektmappen-Explorer könnenSie über das Kontextmenü Verweise > Verweis hinzufügen demProjekt ein Assembly zuordnen. Da es sich hier um ein privatesAssembly handelt, schalten Sie in dem sich öffnenden Dialogauf den Reiter Projekte um. Hier werden sämtliche Projekte deraktuellen Projektmappe aufgelistet. Wählen Sie das ProjektFractionMath.Fracion aus und fügen Sie dem aktuellen Pro-jekt eine Referenz hinzu.

Sie werden nun einwänden, dass das Assembly (Frac-tionMath.Fraction.dll) sich gar nicht im Ordner von Test-Fraction.exe befindet. Wenn Sie aber in den Ordner vonTestFraction/bin/debug navigieren, dann fällt Ihnen auf, dassdas Entwicklungssystem genau diese DLL herkopiert hat. Sehrangenehm ist auch, dass das Entwicklungssystem eine Ände-rung eines Projekt-Assembly registriert und den notwendigenKopiervorgang dann auch durchführt.

Nun können Sie den Applikationscode schreiben.

CD BeispielAssembly

using System;using FractionMath;public class App

Verweis auf privates Assembly herstellen

Abb. 3.9

Page 134: Otmar Ganahl - C Sharp

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 lauffähig sein.

Beim Anlegen des Projektes FractionMath.Fraction über denProjektassistenten wurde auch noch eine weitere Datei mitdem Namen AssemblyInfo.cs angelegt. Ein Blick in diese Dateizeigt ein große Anzahl von Attribut-Einträgen. Über diese Da-tei lassen sich nun spezifische Einträge konfigurieren, die dannin den Metadaten des Assembly vorzufinden sind.

Drei Abschnitte finden sich in dieser Datei. Im ersten Abschnittkönnen grundsätzliche Informationen zum Assembly angege-ben werden.

[assembly: AssemblyTitle("FractionMath")][assembly: AssemblyDescription("Bruchzahlen")][assembly: AssemblyConfiguration("Testversion")][assembly: AssemblyCompany("Privat")][assembly: AssemblyProduct("Testserie")][assembly: AssemblyCopyright("Copyright(C) 2002")][assembly: AssemblyTrademark("")][assembly: AssemblyCulture("")]

Im zweiten Abschnitt kann eine Versionsnummer definiertwerden. Eine Versionsnummer unter .NET setzt sich wie folgtzusammen.

<major version>.<minor version>.<build number>.<revision>

In der Datei AssemblyInfo wird allerdings nur die major versionund die minor version angegeben. Die build number und revisi-on number vergibt das Entwicklungssystem automatisch. Diebuild number ergibt sich aus der Anzahl der Tage seit 1. Januar2000 und die revision number entspricht den Sekunden seitMitternacht (Lokalzeit) dividiert durch 2. (Passen Sie also aufmit Aussagen, wann Sie gearbeitet haben, über die Versions-nummer ist dies nachvollziehbar.)

Page 135: Otmar Ganahl - C Sharp

Baugruppen (Assemblies)1353

[assembly: AssemblyVersion("1.0.*")]

Der dritte Abschnitt wird im nächsten Kapitel diskutiert, wodas Thema shared assemblies ist.

[assembly: AssemblyDelaySign(false)][assembly: AssemblyKeyFile("")][assembly: AssemblyKeyName("")]

Shared AssemblyBislang haben Sie private Assemblies erzeugt. Bei der Verwen-dung von privaten Assemblies sollte es zu keinen Namenskon-flikten kommen, da Sie ja jederzeit die Kontrolle über dieNamensvergabe haben. Ein privates Assembly muss sich im-mer im selben Ordner bzw. in einem Unterordner der Applika-tion befinden. Ganz besonders wichtige Assemblies könnenaber auch shared erklärt werden, d.h. auf diese können dannsämtliche .NET-Applikationen zugreifen. Diese Assembliesmüssen sich nicht im Ordner bzw. Unterordner der Applikationbefinden. Alle Assemblies der ausgelieferten .NET-Umgebungsind shared assemblies.

Bei einem shared assembly kann natürlich nicht ausgeschlos-sen werden, dass ein Dritthersteller zufällig ein shared assem-bly mit demselben Namen erzeugt. Dies würde nun zuKonflikten führen. COM (Component Object Model) hat dasProblem mit GUIDs (Global Unique Identifier) gelöst. COM hatsich die Komponenten nicht über den Namen, sondern überdie GUIDs gesucht. In der Registrierung stand das Mappingzwischen GUID und der DLL, die die Komponenten besitzt.

Microsoft hat sich bei .NET etwas Neues einfallen lassen. Es istprinzipiell möglich, im GAC (Global Assembly Cache) mehrereDLLs mit demselben Namen zu halten. Die .NET-Laufzeitumge-bung sucht sich das richtige Assembly. Wie soll dies funktionie-ren?

Shared assemblies verfügen über ein kryptografisches Schlüs-selpaar in Form eines öffentlichen und privaten Schlüssels.Beim Kompilieren einer Applikation holt sich der Kompiler denöffentlichen Schlüssel des shared assembly und fügt diesen indie Metadaten ein. Wenn die Applikation nun startet, suchtsich die .NET-Laufzeitumgebung aus dem GAC das shared as-

Page 136: Otmar Ganahl - C Sharp

C #

136 3

sembly aus, dessen Name und privater Schlüssel mit dem öf-fentlichen Schlüssel der Applikation übereinstimmt und lädtdann das Assembly. Private Assemblies haben diesen Mechanis-mus nicht!

D.h., dass ein shared assembly mit einem Schlüsselpaar ausge-stattet und entsprechend installiert werden muss. Vielfachwird eine Firma dasselbe Schlüsselpaar für alle ihre shared as-semblies verwenden.

Wenn Sie ein Assembly in den GAC bringen wollen, dann müs-sen Sie dieses zuerst mit einem privaten Schlüssel signieren.Dies garantiert die Identität des Assembly. Das Entwicklungs-system signiert ein Assembly, wenn Sie folgendes Attribut set-zen (in der Datei AssemblyInfo.cs):

[assembly: AssemblyKeyFile("c:\\key.snk")]

In diesem Attribut geben Sie den Namen einer binären Dateian, die ein Schlüsselpaar enthält. Wie kommen Sie aber zu ei-nem Schlüsselpaar? Hier bietet das Entwicklungssystem einTool sn.exe an. Ein Aufruf aus der Konsole

sn -k key.snk

erzeugt ein Schlüsselpaar und legt dieses in einer binärenForm in die angegebene Datei. Bei jedem Kompilierdurchgangwird das Assembly nun signiert.

Damit ein Assembly auch tatsächlich in den GAC kommt, müs-sen Sie das Werkzeug GACUTIL.EXE aus der Konsole starten.

gacutil -i c:\…\FractionMath.Fraction.dll

Nun ist das shared assembly endlich verwendbar. Ein Blick mitdem 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 Schlüssel-paar eindeutig. Es ist unter .NET absolut kein Problem auch As-semblies mit dem gleichen Namen im GAC zu installieren.

Ändern Sie nun die Applikation so ab, dass diese auf das Assem-bly zugreift, das im GAC registriert ist. Vorerst aber entfernenSie das Projekt FractionMath.Fraction aus dem Projekt-Explo-rer, damit es nicht bei jedem Lauf neu erstellt wird (Projekt imProjekt-Explorer markieren und _ drücken).

Page 137: Otmar Ganahl - C Sharp

Baugruppen (Assemblies)1373

Verweisen Sie nun auf die Datei FractionMath.Fraction.dll, wieSie es auch für das private Assembly getan haben, nur dass Sieim Dialog über den Button Durchsuchen die DLL auswählen. Siewerden sehen, dass das Assembly nicht kopiert wurde.

Wenn Sie das Assembly mit GACUTIL.EXE im GAC registriert ha-ben, dann dürfen Sie es natürlich nicht mehr von der beste-henden Position im Dateisystem löschen oder verschieben.

Sie können mit dem Werkzeug GACUTIL.EXE auch Assembliesvon der GAC löschen.

gacutil -u FractionMath.Fraction

ZusammenfassungWaren bisher DLLs die Installationseinheiten unter Windows,so sind dies unter .NET die Assemblies. Assemblies können imAllgemeinen aus mehreren Dateien bestehen. Die Zusammen-gehörigkeit der Dateien ist im Manifest festgeschrieben. EineÄnderung einer Datei eines Assemblies hat zur Folge, dass dasAssembly unbrauchbar wird.

Außerdem wird zwischen privaten und shared assemblies un-terschieden. Private Assemblies müssen sich im Ordner derApplikation bzw. in einem Unterordner der Applikation befin-

Blick in den GAC

Abb. 3.10

Page 138: Otmar Ganahl - C Sharp

C #

138 3

den. shared assemblies können sich auch an einem anderen Ortbefinden, müssen dann allerdings im GAC (Global AssemblyCashe) registriert sein.

Ein Großteil der Probleme, die sich im Laufe der Zeit mit denDlls ergeben haben (Stichwort DLL-Hölle), wird dadurch dras-tisch entschärft.

Überlegen Sie sich gut, ob Sie Funktionalität in shared assem-blies unterbringen. Wenn Sie dies tun, dann haben Sie einenerhöhten Installationsaufwand und auch die möglichen Feh-lerquellen mehren sich. Aus diesem Grund wird die Entwick-lung einer shared assembly eher die Ausnahme darstellen!

Assemblies werden auch verwendet, um mehrere Sprachversi-onen zu unterstützen. Dazu aber mehr in einem späteren Kapi-tel.

Page 139: Otmar Ganahl - C Sharp

XML-Einführung

Einleitung 140

XML-Grundlagen 140

Das XML-Informationsmodell 141

Schemas 147

XPATH 148

XSLT 150

Page 140: Otmar Ganahl - C Sharp

C #

140 4

EinleitungIn der Softwarebranche hat sich kaum eine Technologie jemalsin einem derart hohen Tempo durchgesetzt, wie dies bei derExtensible Markup Language (XML) der Fall war. Im Februar1998 führte das World Wide Web Konsortium (W3C) mit derXML 1.0 Recommendation ein Format ein, das beschreibt, wieDaten strukturiert in Dokumenten gehalten werden sollen.Durch eine einheitliche Form der Datenhaltung sollte eineMöglichkeit geschaffen werden, Informationen zwischen ver-schiedensten Applikationen, über verschiedene Plattformenhinweg auszutauschen.

Trotz der praktisch unbegrenzten Palette der Anwendungs-möglichkeiten von XML wurde bei der Spezifikation auf einemöglichst einfache Syntax Wert gelegt. Diese Schlichtheit vonXML führte zu einer noch nie da gewesenen, weltweiten Ak-zeptanz eines Formates, sodass XML heute als das Standard-format für Daten gilt. In der Literatur ist auch der Begriff„Weltsprache für Daten“ üblich. Praktisch in jedem Winkel derSoftwareindustrie trifft man irgendwo auf XML.

XML ist ein integraler Bestandteil unter .NET. Es ist daher fürjeden Softwareentwickler unter .NET unumgänglich, sich frü-her oder später mit XML vertraut zu machen.

Dieses Kapitel gibt einen kurzen und schnellen Einstieg inXML. Es geht hier vornehmlich um die Begriffsbestimmungen.

XML-GrundlagenBei der Speicherung von Informationen ist es sinnvoll, diese ineiner strukturierten Form zu halten. Dadurch wird der eigentli-che Dateninhalt übersichtlich gehalten. Der logische Aufbaubzw. Zusammenhang der Daten wird über Zusatzinformatio-nen innerhalb eines Dokuments festgelegt. Diese Zusatzinfor-mationen werden als Markup bezeichnet.

Auch bei XML-Dokumenten wird ganz gezielt zwischen denDateninhalten und deren logischen Struktur getrennt. DieTrennung erfolgt hier in einer standardisierten Form. Die XML-Spezifikation beschreibt dazu eine Menge von Regeln, wie be-liebige Daten in einer einheitlichen Form gespeichert werdenkönnen.

Page 141: Otmar Ganahl - C Sharp

XML-Einführung1414

Das folgende XML-Fragment hält beispielsweise Informatio-nen ü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 auf-fallende Ähnlichkeit von XML mit HTML ins Auge. HTML undXML haben dieselben Zeichen, um Dateninhalt von Zusatzin-formationen zu trennen. Beide Sprachen stammen von dersel-ben übergeordneten Markup-Sprache SGML (StandardGeneralized Markup Language) ab, haben aber ansonsten vonVornherein nichts miteinander zu tun. Sie verfolgen grund-sätzlich unterschiedliche Zielrichtungen. Während XML eineMöglichkeit für die strukturierte Haltung von Daten darstellt,wird mittels HTML eine Datensicht beschrieben. Wichtig ist,dass XML weder als Ersatz noch als Weiterentwicklung vonHTML zu verstehen ist. XML wurde für einen gänzlich anderenZweck entwickelt.

Prinzipiell werden XML-Daten in Elementen gehalten. Der Auf-bau von XML-Elementen ist im Wesentlichen derselbe, wie dervon HTML-Elementen. Sie bestehen immer aus einem start-tagund einem end-tag. Dazwischen befindet sich der Dateninhalt.Der Dateninhalt kann auch aus weiteren XML-Elementen be-stehen.

Im Gegensatz zu HTML, wo ein fester Satz von erlaubten Ele-mentnamen, zum Beispiel <H1> existiert, dürfen in XML-Doku-menten beliebige Elemente eingeführt werden. Hier wirddeutlich, weshalb es sich um eine Extensible (erweiterbare)Markup Language handelt.

Das XML-InformationsmodellDie schnelle, weltweite Verbreitung von XML machte es not-wendig, die einzelnen Teile eines XML-Dokuments eindeutigzu benennen und zu definieren. Eine einheitliche Sprachrege-lung vereinfacht das Verstehen von XML-Spezifikationen undist Voraussetzung für die Kommunikation unter Softwareent-wicklern. Diese Definition einer Menge von abstrakten Objek-

Page 142: Otmar Ganahl - C Sharp

C #

142 4

ten, welche zusammen ein XML-Dokument ergeben, wurdevon einer W3C-Arbeitsgruppe unter dem Namen XML Informa-tion Set (Infoset) veröffentlicht. Eine komplette Abhandlungüber das Infoset würde den Rahmen dieses Kapitels sprengen,sodass hier nur die wichtigsten Teile vorgestellt werden. Zudiesem Zweck wird ein vollständiges XML-Dokument betrach-tet. Nachfolgend werden die einzelnen Komponenten näherbeschrieben.

CD-BeispielBücher.xml

<?xml version="1.0"?><?Sybex-Lagerverwaltung Priorität="hoch" ?>

<!-- derzeit lieferbare Bücher --><Bücher> <Buch isbn="I-4711-0815"> <Titel>C# WebBook</Titel> <Autor>Ganahl Otmar</Autor> <Copy-Right>&#169; Sybex Verlag</Copy-Right> <Beschreibung> Eine praxisnahe Einführung in C# </Beschreibung> </Buch> <Buch isbn="I-1234-5678"> <Titel>Meine Erfindungen</Titel> <Autor>Düsentrieb Daniel</Autor> <Copy-Right>&#169; Düsentrieb Daniel</Copy-Right> <Beschreibung> Innovationen für Entenhausen</Beschreibung> </Buch></Bücher>

<!-- Aktuelles Datum: 1.3.2001 --><!-- Sachbearbeiter: G.C. -->

Das obige Dokument entspricht der grundlegenden XML-Syn-tax. Es wird deshalb als wohlgeformtes Dokument (well formeddocument) bezeichnet. Wohlgeformte Dokumente können ineinem XML-fähigen Browser dargestellt werden.

Page 143: Otmar Ganahl - C Sharp

XML-Einführung1434

Prinzipiell kann ein XML-Dokument aus maximal drei verschie-denen Abschnitten bestehen. Diese Abschnitte sind

der optionale Prolog mit der XML-Deklaration, Verar-beitungsanweisungen (Processing Instructions, PI's)und Kommentaren,das eine und einzige XML-Element im Dokument, dasso genannte Dokument-Element (document element),der optionale Epilog, welcher nur aus Kommentarenbesteht.

Wohlgeformte XML-Dokumente haben die hierarchische Formeines Baumes. Sie besitzen einen einzelnen Wurzelknoten(root node, document root). Dieser enthält neben dem Prologund dem Epilog auch das Dokument-Element. Dieses enthält

Bücher.xml in der Browseransicht

Abb. 4.1

Page 144: Otmar Ganahl - C Sharp

C #

144 4

alle weiteren XML-Elemente. Wichtig ist die Tatsache, dass essich beim Wurzelknoten nicht um das Dokument-Elementhandelt.

VerarbeitungsanweisungenVerarbeitungsanweisungen (Processing Instructions, PIs) erlau-ben es, innerhalb von Dokumenten Anweisungen für Program-me zu speichern, die das XML-Dokument verarbeiten. PIsbeginnen mit einem Ziel (PI Target). Dieser Name ist weitest-gehend beliebig und identifiziert die Anwendung, an die sichdie Anweisung richtet. Lediglich die Zielnamen XML und xmlsind für die Standardverarbeitungsanweisungen reserviert.

<?Sybex-Lagerverwaltung Priorität="hoch" ?><?xml-stylesheet type=”text/xsl” href=”buch.xsl”?>

Das obige Beispiel zeigt eine Verarbeitungsanweisung für dieApplikation „SYBEX-Lagervarwaltung“. Wie und ob diese Ap-plikation die Verarbeitungsanweisung interpretiert bleibt ihrüberlassen. Die Standardverarbeitungsanweisungen xml-sty-lesheet enthalten Informationen darüber, wie eine XML-Dateidargestellt werden soll. Ein XML-fähiger Browser wird diese PIauswerten und die Daten entsprechend darstellen.

ElementeElemente sind die grundlegenden Bausteine eines XML-Doku-ments. Ein Element ist ein Container für Dateninhalte. Es kannDaten in Form von Zeichen und weiteren Elementen enthal-ten.

Jedes Element beginnt mit einem start-tag und muss mit ei-nem zugehörigen end-tag enden. Ein tag besteht aus einem

Hierarchie

Abb. 4.2

Page 145: Otmar Ganahl - C Sharp

XML-Einführung1454

Elementnamen, der in Spitzklammern eingeschlossen ist. Ele-mentnamen können beliebig gewählt werden.

Ein komplettes Element könnte also folgendermaßen ausse-hen:

<Buchtitel>C# Web Book</Buchtitel><BUCHTITEL>C# Web Book</BUCHTITEL>

XML ist case-sensitive, weshalb die obigen Elemente nichtidentisch sind. Besitzt ein Element keinen Dateninhalt, so kanndas start-tag und das end-tag zu einem so genannten empty-element-tag zusammengefasst werden.

<Buchtitel/>

Untergeordnete Elemente, EinbettungEin XML-Dokument ist vereinfacht gesprochen ein einzelnesXML-Element, das Dokument Element. Jedes XML-Elementkann jedoch beliebig viele andere Unterelemente (nested ele-ments, child elements) beinhalten. Im folgenden Beispiel hatdas Element Buch zwei Kindelemente.

<Buch> <Titel>C# Web Book</Titel> <Autor>Ganahl Otmar</Autor></Buch>

Außer dem Dokument-Element sind alle Elemente eines XML-Dokuments untergeordnete Elemente. Der hierarchische Ele-mentbaum ist eine wichtige Eigenschaft von XML-Dateien. Je-des Element muss vollständig in seinem übergeordnetenElement (parent element) aufgenommen sein. Dies ist einegrundlegende Regel und gilt für alle wohlgeformten XML-Do-kumente.

AttributeWerden die Elemente als Hauptwörter eines XML-Dokumentsbezeichnet, so sind Attribute zugehörige Adjektive. Oft be-steht der Wunsch, einem Element Zusatzinformationen hinzu-zufügen. Genau dies kann mit Attributen erfolgen.

Attribute können in start-tags oder in empty-element-tags an-gegeben werden. Pro Element darf ein Attributname nur ein-

Page 146: Otmar Ganahl - C Sharp

C #

146 4

mal vorhanden sein. Die Attributwerte werden dabeientweder in einfachen oder in doppelten Anführungszeichenangegeben.

<Buch isbn='I-4711-0815'></Buch><Buch isbn="I-4711-0815"/>

Es ist beim Design einer geeigneten XML-Struktur für be-stimmte Daten genau zu beachten, ob eine Information in ei-nem Unterelement oder als Attribut gehalten wird.

<Buch isbn="I-4711-0815"> <Titel>C# Web Book</Titel> <Autor>Ganahl Otmar</Autor></Buch>

Während hier der Buchtitel durchaus auch als Attribut abge-legt werden könnte, ist beim Autor Vorsicht geboten. Was,wenn ein Buch mehrere Autoren besitzt? In diesem Fall wärebeispielsweise folgende XML-Struktur möglich:

<Buch isbn="I-4711-0815" Titel="C# Web Book"> <Autoren> <Autor>Ganahl Otmar</Autor> <Autor>Düsentrieb Daniel</Autor> <Autoren></Buch>

Attribute führen zwar zu übersichtlichen und leichter lesbarenXML-Dokumenten, die nachträgliche Erweiterbarkeit der XML-Struktur wird jedoch eingeschränkt.

KommentareKommentare dürfen innerhalb des Dokuments an beliebigerStelle außerhalb des übrigen Markup stehen. Ein XML-Prozes-sor kann, muss aber nicht, der Anwendung eine Möglichkeiteinräumen, den Text eines Kommentars zu lesen. Die Zeichen-kette „--“ (zwei Trennstriche) darf innerhalb eines Kommen-tars nicht erscheinen.

<!-- Dies ist ein Kommentar -->

Page 147: Otmar Ganahl - C Sharp

XML-Einführung1474

ZeichenreferenzenEine Zeichenreferenz verweist auf ein spezifisches Zeichen imUnicode Zeichensatz, etwa ein Zeichen, welches auf dem Ein-gabegerät nicht direkt verfügbar ist. Eine solche Referenz wirddurch die Symbole &# eingeleitet. Dieser Sequenz folgt dergewünschte Unicode in dezimaler oder hexadezimaler Formund ein abschließendes 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 zusätzlich fünf standardentities. Mit ihnen ist es möglich, auf reservierte Zeichen zuverweisen. Das folgende Beispiel zeigt die Verwendung dieserstandard entities:

<Copy-Right>&#169; Sybex Verlag &lt; &quot; &apos; &amp; &gt;</Copy-Right><Copy-Right>© Sybex Verlag < " ' & ></Copy-Right>

Zeichenfolgen (Character Data)Character Data ist der Text in einem XML-Dokument, der keinMarkup Code darstellt. Alle Dateninhalte bzw. Attributwertefallen darunter.

Da mit den Zeichen & und < Markup Code beginnt, dürfen die-se Zeichen in ihrer literalen Form niemals in Zeichenfolgenaufscheinen. Werden sie benötigt, müssen sie durch die stan-dard entities &amp; und &lt; ersetzt werden. Dies wird jedochvon einem XML-Editor automatisch erledigt.

SchemasBisher wurden die Grundlagen besprochen, um wohlgeformteXML-Dokumente zu erstellen. Solche Dokumente halten sichan die grundlegenden Syntaxregeln von XML. Es werden aberansonsten keinerlei Anforderungen, weder an deren Datenin-halt noch an deren logische Struktur gestellt.

Page 148: Otmar Ganahl - C Sharp

C #

148 4

Sollen nun Applikationen über XML miteinander kommunizie-ren, so ist es notwendig, sich in irgendeiner Form auf eineXML-Struktur zu einigen. Schemas definieren solche Vokabula-re. Sie stellen meta data (Daten über Daten) dar. Neben derDefinition der XML-Elemente und -Attribute können auch Be-dingungen an die Dateninhalte gestellt werden.

Ohne eine exakte Beschreibung der Struktur eines XML-Doku-ments müsste bei dessen Verarbeitung viel Fehlerbehand-lungscode eingefügt werden. Mithilfe von Schemas kann dieFehlerüberprüfung an einer einzigen Stelle, noch vor der ei-gentlichen Verarbeitung erfolgen. Sie muss nicht einmal imple-mentiert werden, da fertige Validierer zur Verfügung stehen.Diese überprüfen, ob ein XML-Dokument einem Schema ent-spricht. Tut es dies, so wird es als gültiges Dokument bezeich-net. Bad Data kann von vornherein mit sehr kleinem Aufwandherausgefiltert werden. Vor allem bei Internet-Applikationenist das von großer Bedeutung, da hier nicht angenommen wer-den kann, dass die empfangenen Daten einer gewissen Quali-tät entsprechen. Der Applikationsentwickler kümmert sich inweiterer Folge „lediglich“ um die eigentliche Programmlogik.

XPATHIn praktisch allen XML-verarbeitenden Applikationen ist dasHerausfiltern von bestimmten Dokumentteilen erforderlich.Man möchte beispielsweise in einem XML-Dokument alle Ele-mente mit einem bestimmten Namen manipulieren.

Die XPATH-Empfehlung der W3C definiert eine offizielle Syn-tax zur Adressierung von Infoset-Teilmengen. Mit einer solcheinheitlichen Abfragesprache ist es möglich, allgemeine Kom-ponenten zu implementieren, die einen eleganten Zugriff aufXML-Daten erlauben.

Das folgende Beispiel zeigt einen einfachen XPATH-Ausdruck:

child::Buch

Ausgehend von einem Kontextknoten kann obige Ortsangabebewertet werden. Sie liefert eine Knotenmenge, welche alleBuch-Knoten, die Kinder des Kontextknotens sind, enthält.

Page 149: Otmar Ganahl - C Sharp

XML-Einführung1494

Jede Ortsangabe hat den Typ einer Knotenmenge und bestehtaus drei Teilen (Achsenbezeichnung, Knotentest, Prädikats-menge).

Achse::Knotentest[Prädikat1][Prädikat2]…

Die Achsenkennung identifiziert den Vorrat an Knoten, der imZuge der Ortsangabe durchgefiltert wird. Der nächste Teil derOrtsangabe, der Knotentest, definiert das erste dieser Filter.

Einige Achsenbezeichnungen sind:

self der Kontextknoten selbst

child alle Kindknoten

parent der übergeordnete Knoten

decendant alle Kinder und Kindeskinder

decendant or selft alle Kinder, Kindeskinder und der Kon-textknoten selbst

attribute alle Attributknoten

Der Knotentest kann zum Beispiel nach dem Namen durchge-führt werden, so liefert der folgende Ausdruck alle Kindknotenmit dem Namen Buch:

child::Buch

Zur weiteren Filterung sind Prädikatausdrücke hilfreich. EinPrädikatausdruck ist selbst ein XPATH-Ausdruck, der aber im-mer einen boolschen Wert liefert. Er wird für jeden Knoten inder aktuellen Knotenmenge ausgewertet. Ist das Ergebnis füreinen Knoten wahr, so bleibt er in der Menge, ansonsten wirder entfernt.

child::Buch[attribute::isbn = 'I-4711-0815']

Der obige XPATH Ausdruck beschreibt alle Knoten mit dem Na-men Buch, die Kindknoten vom Kontextknoten sind und einAttribut isbn mit dem Wert „I-4711-0815“ besitzen.

Page 150: Otmar Ganahl - C Sharp

C #

150 4

KurzformenEines der wichtigsten Ziele von XPATH war es, die Syntax sokompakt wie möglich zu gestalten. Die XPATH-Spezifikationdefiniert eine Reihe von Abkürzungen, welche in Ortspfad Aus-drücken eingesetzt werden dürfen.

XPATH Ausdruck Kurzformchild::ChildName ChildNameattribute::AttributName @AttributNamedescendant-or-self::node() //self::node .parent::node ..

Somit sind die untenstehenden XPATH-Ausdrücke jeweilsäquivalent:

child::Buch/child::AutorBuch/Autor

child::Buch/attribute::isbnBuch/@isbn

Beginnt eine XPATH-Ortsangabe mit einem Schrägstrich, sowird als Kontextknoten immer die document root verwendet.Man spricht in diesem Fall von einer absoluten Ortsangabe.

/Bücher/Buch[2]

XSLTDie generelle Akzeptanz von XML zur Datenhaltung führte da-zu, dass sich viele Entwickler eigene Schemas zurechtlegten.Unterschiedliche Applikationen, auch wenn sie eigentlich die-selben Daten verarbeiten, verwenden daher unterschiedlicheXML-Schemas. Sollen nun XML-Daten ausgetauscht werden,so muss eine geeignete Transformation stattfinden. Bei dieserUmwandlung werden meistens keine Dateninhalte geändert,lediglich deren logische Zusammenstellung ändert sich.

Man könnte die Umformung traditionell in einer Program-miersprache implementieren. Dies erfüllt zwar den Zweck, istaber mit viel Codierungsaufwand verbunden. Vor allem wennsich das Quell- oder Zielschema ändert, müsste diese Softwarejedes Mal aufwändig nachgezogen werden.

Page 151: Otmar Ganahl - C Sharp

XML-Einführung1514

Die Idee ist nun, einen einzigen Transformationsprozessor zuverwenden. Dieser Prozessor erhält zusätzlich zum Quelldoku-ment Informationen, wie die Transformation durchzuführenist.

Die Extensible Stylesheet Language for Transformation (XSLT)definiert eine Sprache auf XML-Basis, mit der sich Transforma-tionsregeln formulieren lassen. XSLT ist also eine Sprache, mitder sich die Umwandlung von XML-Dokumenten in beliebigeTextdokumente beschreiben lässt. Als Zieldokument sindsämtliche auf Text basierende Dokumente vorstellbar, ein-schließlich XML und HTML. XSLT eignet sich daher hervorra-gend für die Abbildung einer XML-Repräsentation auf eineandere.

XSLT geht davon aus, dass drei Dokumente zum Einsatz kom-men:

das Quelldokumentdie XSLT-Formatvorlage (Stylesheet)das resultierende Zieldokument

Beim Quelldokument und der Formatvorlage handelt es sichum wohlgeformte XML-Dokumente. XSLT-Formatvorlage wer-den allerdings in Dateien mit der Erweiterung .xsl abgespei-chert.

XSLT-Prozess

Abb. 4.3

Page 152: Otmar Ganahl - C Sharp
Page 153: Otmar Ganahl - C Sharp

XML-Klassen

Einleitung 154

XML-Dateien schreiben 156

XML-Dateien lesen 160

DOM-Objektmodell 165

XPATH 172

XSLT (XSL-Transformationen) 173

XML-Serialisierung von.NET-Objekten

175

Zusammenfassung 182

Page 154: Otmar Ganahl - C Sharp

C #

154 5

EinleitungWie schon mehrfach erwähnt, ist XML ein wesentlicher Be-standteil der .NET-Laufzeitumgebung. Nicht nur, dass .NETmächtige Klassen zur Verarbeitung und Erzeugung von XML-Dateien anbietet, die .NET-Laufzeitumgebung verwendet XMLselbst intensiv. In den weiteren Kapiteln wird darauf einge-gangen.

Die .NET-Laufzeitumgebung unterstützt hier folgende W3C-Standards:

XML 1.0 inklusive DTD-UnterstützungXML NamespacesXML SchemasXPath AusdrückeXSL/T TransformationenDOM Level 2Soap 1.1

In den nachfolgenden Betrachtungen wird die folgende XML-Datei Personen.xml immer wieder verwendet. Das ist eine ein-fache Aufzählung von Personen, deren Vorname, Nachname,Anrede und E-Mail-Adresse festgehalten werden. Eine XSL-Da-tei Personen.xsl erlaubt eine Transformation nach HTML, damitdiese Daten auch in einem Browser anwenderfreundlich be-trachtet werden können.

CD-BeispielPersonen.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>[email protected]</eMail> </Person> <Person> <Vorname>Daisy</Vorname>

Page 155: Otmar Ganahl - C Sharp

XML-Klassen1555

<Nachname>Duck</Nachname> <Anrede>Frau</Anrede> <eMail>[email protected]</eMail> </Person> <Person> <Vorname>Dagobert</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr $</Anrede> <eMail>[email protected]</eMail> </Person></Personen>

Visual Studio.NET hat auch einen XML-Editor integriert, der Siebei der Erzeugung von XML-Dateien unterstützt.

In der XML-Datei Personen.xml wird auf ein Stylesheet verwie-sen. Sie finden dieses Stylesheet Personen.xsl ebenfalls auf derbeigelegten CD dieses Buches.

Microsoft Internet Explorer 6.0 führt eine Transformation derXML-Datei durch, wenn das referenzierte Stylesheet zur Verfü-gung steht. Ein Doppelklick auf die Datei Personen.xml öffnetden Internet Explorer und die transformierte Datei wird sichwie folgt darstellen.

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

Abb. 5.1

Page 156: Otmar Ganahl - C Sharp

C #

156 5

XML-Dateien schreibenFür das Erzeugen vom XML-Dateien bieten sich sowohl dieKlasse XmlWriter und deren Ableitungen als auch die KlasseXmlDocument und deren Ableitungen an. Die Klasse XmlDocu-ment implementiert das DOM-Modell, das Sie nachfolgendnoch kennen lernen werden. Vorerst werden Sie sich mit derKlasse XmlTextWriter beschäftigen, die von XmlWriter abgelei-tet ist. Wiederum in kleinen überschaubaren Beispielen wer-den Sie die Möglichkeiten dieser Klasse kennen lernen. LegenSie für die Experimente am besten eine neue Projektmappe anund fügen Sie dieser gleich ein Projekt namens WriteXml hin-zu.

CD-BeispielWriteXml1

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", "[email protected]");

tw.Flush(); tw.Close(); }}

Page 157: Otmar Ganahl - C Sharp

XML-Klassen1575

Die Klasse XmlTextWriter befindet sich im Namensraum Sys-tem.Xml. Diesen öffnen Sie am besten. Die Klasse selbst ist imAssembly System.Xml.dll implementiert. Daher müssen Siedem Projekt einen Verweis auf dieses Assembly setzen (Kon-textmenü Verweise > Verweise hinzufügen).

Nun aber zum Code selbst: Der Dateiname der gewünschtenDatei wird im String Filename gehalten. Das @-Zeichen vordem String ist ein C#-Feature. Sämtliche Zeichen im folgendenhart kodierten String werden als normale Zeichen (allerdings,wie in C# üblich, in Unicode) interpretiert.

string Filename = @"c:\Personen.xml";

Ohne @-Zeichen müssten 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 XmlText-Writer angelegt. Das Objekt bekommt einen Dateinamen(c:\Personen.xml) als Übergabeparameter. XmlTextWriter ver-waltet also intern eine Datei.

tw.Formatting = Formatting.Indented;

Damit formatiert XmlTextWriter die XML-Datei in einer besserlesbaren Form. Child-Tags werden nämlich eingerückt darge-stellt.

Sie erkennen nun im Code eine Reihe von Methoden der KlasseXmlTextWriter. Für sämtliche Elemente aus dem W3C-Infosetgibt es (meist mehrfach überlagerte) Methoden WriteXXX. DieMethode WriteElementString erlaubt Ihnen einfache XML-Ele-mente mit Start-Tag und End-Tag zu schreiben. Im Beispielwerden die Methoden WriteStartDocument, WriteCommentund WriteProcessingInstruction für die Erzeugung des XML-Pro-logs verwendet.

tw.Flush();tw.Close();

Die Datei wird erst beschrieben, wenn Sie die Methode Flushauf das Objekt tw ausführen. Zu guter Letzt schließen Sie dannnoch den XmlTextWriter.

Page 158: Otmar Ganahl - C Sharp

C #

158 5

Mit diesem Modell ist es nicht möglich, in eine bestehende Da-tei Elemente anzufügen! Es ist ein „forward cursor“-Modell,wie Sie sicherlich erkannt haben. Wenn Sie die Klasse XmlText-Writer weiter erforschen, dann finden Sie hier sämtliche Me-thoden, um Elemente einer XML-Struktur zu erzeugen (so z.B.Kommentare, PI-Informationen, Attribute usw.). Es machtnicht viel Sinn, nun sämtliche WriteXXX-Methoden aufzuzäh-len, aber ein Blick in die MSDN ist diesbezüglich empfehlens-wert.

Natürlich können Sie auch eine eigene XmlTextWriter-Klasseerzeugen. Dies wird im nächsten Beispiel demonstriert. DieKlasse XmlTextWriter soll um eine Methode WritePerson er-weitert werden, und somit das Hinzufügen von Personen ineine XML-Datei auch programmtechnisch vereinfachen. Erwei-tern Sie das Projekt wie folgt:

CD-BeispielWriteXml2

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);

Page 159: Otmar Ganahl - C Sharp

XML-Klassen1595

WriteElementString("eMail",em); WriteEndElement(); } } public static void Main() { string Filename = @"c:\Personen.xml"; XmlPersonTextWriter tw = new XmlPersonTextWriter(Filename);

tw.WritePerson("Donald", "Duck", "Herr", "[email protected]"); tw.WritePerson("Daisy", "Duck", "Frau", "[email protected]"); tw.WritePerson("Dagobert", "Duck", "Herr §", "[email protected]"); tw.Flush(); tw.Close(); }}

Die neue Klasse XmlPersonTextWriter ist von XmlTextWriterabgeleitet. Der Konstruktor dieser Klasse fügt auch gleich denProlog der XML-Datei hinzu. Die Implementierung der Metho-de WritePerson fügt XML-Elemente eines Personen-Eintrageshinzu. Damit vereinfacht sich im Hauptprogramm das Hinzu-fügen von Personen in die XML-Datei beträchtlich.

tw.WritePerson("Donald", "Duck", "Herr", "[email protected]");

Die Klasse XmlDocument kann auch verwendet werden, umXML-Dateien zu erzeugen. Diese Klassen lernen Sie in Kürzekennen.

Page 160: Otmar Ganahl - C Sharp

C #

160 5

XML-Dateien lesenSie kennen sicherlich das DOM-Objektmodell (W3C) und dasSAX-Programmiermodell. Das DOM-Objektmodell lädt das ge-samte XML-Dokument in den Speicher und ermöglich somit ei-nen objektorientierten Zugriff auf die einzelnen Elementeeiner XML-Datenstruktur. Eine Navigation innerhalb des Doku-mentes ist damit sehr einfach möglich, da sich ja sämtlicheDaten im Speicher befinden. Dieser an und für sich nicht spek-takuläre Umstand wird hier betont, weil das SAX-Program-miermodell ereignisgesteuert ist, und sich somit diametralvom DOM-Modell unterscheidet.

Beide Modelle haben ihre Vor- und Nachteile. Das DOM-Mo-dell wird tendenziell bei kleineren XML-Dateien verwendet,das SAX-Programmiermodell ist komplexer, dafür aber vielschneller und kann daher bei sehr großen Dateien von Vorteilsein. Das SAX-Programmiermodell ist ein Push-Modell, dasheißt, es wird die XML-Datei „durchgescannt“ (forward cursor)und ein Ereignis in die Applikation „gepushed“, sobald ein Ele-ment erkannt wird. Der Programmierer reagiert also auf dieseEreignisse. Ein Navigieren innerhalb der Datei ist damit nichtmöglich.

Unter .NET werden die Klassen XmlReader und deren Ableitun-gen XmlTextReader, XmlNodeReader und XmlValidatingReaderangeboten, die dem SAX-Programmiermodell sehr ähnlichsind, aber ein Pull-Modell darstellen. Die Daten werden in eineApplikation gebracht, wenn es der Programmierer veranlasst.Ein weiterer Vorteil des Pull-Modells ist, dass der Programmie-rer selektiv Daten in die Applikation holen kann. Bei einemPush-Modell müssen alle Daten von der Applikation verarbei-tet werden!

XmlTextReaderDie Klasse XmlReader implementiert Methoden, die ein einfa-ches Lesen von XML-Dateien ermöglichen. Fügen Sie hierzu Ih-rer Projektmappe eine neue C#-Konsolenanwendung mit demNamen ReadXml hinzu. Verweisen Sie auf das Assembly Sys-tem.Xml.dll und öffnen Sie im Quellcode den NamensraumSystem.xml.

Page 161: Otmar Ganahl - C Sharp

XML-Klassen1615

CD-BeispielReadXml1

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 die-se Instanz wird dann wiederholt die Methode Read() aufgeru-fen, die true zurückgibt, wenn diese etwas lesen konnte. Beijedem Lesevorgang ändert sich natürlich der Status der In-stanz tr, dessen Status-Werte über Eigenschaften von tr aus-gelesen und verarbeitet werden können.

Im Beispiel wird geprüft, ob die Eigenschaft (Status) Name destr-Objektes den Wert Vorname besitzt und ob die EigenschaftXmlNode-Type den Enumerationswert Element besitzt. Wenndies der Fall ist, wird die Methode ReadElementString aufgeru-fen, der in diesem Fall die Vornamen der Einträge zurückgibtund diese auch am Bildschirm ausgibt.

XmlNodeType ist eine Enumeration mit Werten, die dem XML-Infoset der W3C entsprechen. Hier sind einige Enumerations-werte aufgelistet:

AttributeCDATACommentDocument

Page 162: Otmar Ganahl - C Sharp

C #

162 5

DocumentTypeElementEndElementEntityNoneProcessingInstructionWhiteSpace…

Noch einmal: Bei jedem Read()-Vorgang springt der Cursorzum nächsten XmlNodeType und das XmlTextReader-Objektändert seinen Status. Der Typ kann über die Eigenschaft Node-Type ausgelesen werden.

Es ist naheliegend, aber wichtig zu erkennen, dass z.B. die Ei-genschaft Name eines XmlTextReader-Objekts je nach NodeTy-pe eine andere Bedeutung hat. Hier eine nicht vollständigeAuflistung der Bedeutung der Eigenschaft Namen für die unter-schiedlichen NodeTypes:

Attribute Name des Xml-AttributsDocumentType Name des DokumententypsElement Tag-NameEntityReference Name des Ref-EntityProcessingInstruction ZielXmlDeclaration #xml-Text

Die Methode ReadElement ist zwar eine Methode der KlasseXmlTextReader, darf aber nur dann aufgerufen werden, wennder Status NodeType des XmlTextReader-Objektes den WertXmlNodeType.Element besitzt und gibt dann den Wert inner-halb der Tags an. Wird diese Methode auch auf andere Node-Types angewendet, wird eine Exception ausgelöst.

Einen weiteren wichtigen Aspekt soll hier erwähnt werden.Würden Sie z.B. eine bestimmte Email-Adresse suchen, undmit dieser Information dann auf den Vor- und Nachnamenschließen wollen, dann gestaltet sich das ziemlich aufwändig,da in dem Beispiel Vor- und Nachname vor dem Eintrag Emailvorkommen. Da XmlTextWriter ein „forward cursor“-Modell

Page 163: Otmar Ganahl - C Sharp

XML-Klassen1635

implementiert, können Sie nicht rückwärts navigieren. Hiermüssten vorhergehende Elemente zwischengespeichert wer-den, um im Bedarfsfall darauf zurückzugreifen.

Es wird Ihnen hier sicherlich klar werden, dass XmlTextReadersich sehr gut eignet, um sehr schnell ein oder mehrere be-stimmte Informationen aus der XML-Datenstruktur zu extra-hieren, aber dass bei komplexerer Funktionalität derProgrammieraufwand doch gehörig ansteigt.

XmlValidatingReaderNeben XmlTextReader existiert in der .NET-Laufzeitumgebungauch die Klasse XmlValidatingReader, die beim Lesen eben eineÜberprüfung gegenüber einem DTD (W3C), XSD-Schema(W3C)oder einem XDR-Schema (Microsoft) durchführen kann. DieKlasse XmlNodeReader erhält statt einer Datei ein Objekt vomTyp XmlNode. Dazu mehr im nächsten Kapitel.

Im folgenden Beispiel werden Sie kennen lernen, wie mit derKlasse XmlValidatingReader eine Validierung gegenüber einesW3C-Schemas getätigt werden kann.

CD-BeispielReadXml2

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);

Page 164: Otmar Ganahl - C Sharp

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 einXmlTextReader-Objekt mit dem Namen tr angelegt. Dieses Ob-jekt wird dann als Konstruktionsparameter der Klasse XmlVali-datingReader tvr verwendet. Der ValidationType wird auf denEnumerationstyp ValidationType.Schema gestellt (möglichsind u.a. auch DTD und XDR). Die Klasse XmlValidatingReaderlöst ein Event aus, wenn beim Validieren eine Ungereimtheitauftritt. Im Beispiel wird diesem Event die Bearbeitungsme-thode OnValidateError zugewiesen, die hier eine Ausgabe aufder Konsole erzeugt. Der Rest des Codes bleibt zum vorherigenBeispiel gleich.

Die Datei Personen.xml muss nun natürlich einen Verweis aufdas 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 imselben Ordner befinden muss wie Personen.xml. Diese Sche-ma-Datei finden Sie auf der beiliegenden CD im BeispielXmlRead2. Nach dieser Schema-Definition muss ein ElementPerson die Elemente Vorname, Nachname, Anrede und eMailbesitzen und es ist gefordert, dass die Anrede entweder „Herr“oder aber „Frau“ sein muss.

Page 165: Otmar Ganahl - C Sharp

XML-Klassen1655

<Person> <Vorname>Dagobert</Vorname> <Anrede>Herr $</Anrede> <eMail>[email protected]</eMail> </Person>

Dieses Beispiel würde zwei Validierungsfehler generieren, weildas Element Nachname fehlt und die Anrede „Herr $“, statt nur„Herr“ lautet.

DOM-ObjektmodellDie DOM-Implementierung (Document Object Model) in .NETerfüllt die W3C-Spezifikationen Level 1 und Level 2. Das DOM-Modell wird über dies Klasse XmlNode implementiert. XmlNo-de ist eine abstrakte Basisklasse und repräsentiert, wie derName sagt, einen Knoten (nach der W3C-Infoset Spezifikation)im XML-Dokument. Für jede Art von Knoten gibt es eine Klas-se, die von XmlNode abgeleitet ist.

Hier einige wichtige Nodes:

XmlAttributeXmlElementXmlDeclarationXmlCommentXmlEntityXmlNotationXmlWhitespaceXmlTextXmlDocument...

In einem Beispiel werden Sie die Funktionsweise und Zusam-menarbeit dieser Klassen kennen lernen.

CD-BeispielXmlDom1

using System;using System.Xml;

public class App{

Page 166: Otmar Ganahl - C Sharp

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 undmit der XML-Datei geladen. Die Instanz doc hält somit die ge-samte XML-Datenstruktur. Aus der Instanz doc wird nun überdie Methode GetElementsByTagName(...) eine XmlNode-Listegewonnen.

Da Node-Listen sehr oft gebraucht werden, implementiert.NET eine eigene Klasse XmlNodeList. Die Liste nl im obigenBeispiel wird durch die Methode GetElementByTagName(...)gefüllt. Im Beispiel also mit Nodes Vornamen, deren Inhalte nunz.B. mit der Eigenschaft InnerText gelesen, aber auch neu be-legt werden kann.

Erwähnenswert ist, dass XmlDocument von XmlNode abgelei-tet ist, und somit selbst einen Node (Knoten) darstellt.

XmlNode

XmlNode ist die zentrale Klasse des .NET-DOM-Objektmodells.In der Abbildung sind die für das Verständnis wichtigsten

Die Klasse XmlNode

Abb. 5.2

Page 167: Otmar Ganahl - C Sharp

XML-Klassen1675

Member-Variablen aufgelistet. XmlNode repräsentiert einenKnoten nach den Spezifikationen des W3C-Infosets. JederXML-Knoten kann natürlich Kindknoten besitzen. Daher hatdie Klasse XmlNode eine Member-Variable ChildNodes vom TypXmlNode[]. Da die Klasse XmlDocument selbst von der KlasseXmlNode abgeleitet ist, besitzt diese natürlich ebenfalls dieMember-Variable ChildNodes. Damit können Sie sich, ausge-hend von einer Instanz XmlDocument, zu jedem beliebigenKnoten innerhalb des Dokumentes bewegen.

Um auch programmtechnisch feststellen zu können, von wel-chem Typ ein Knoten ist, wurde die Klasse XmlNode auch miteiner Member-Variable NodeType ausgestattet. Diese Member-Variable ist vom Typ XmlNodeType, die Sie schon kennen ge-lernt haben. XmlNodeType ist ein Enumerationstyp, und diewichtigsten Aufzählungswerte haben Sie schon kennen ge-lernt.

Wenn Sie nun einen Knoten haben, dann können Sie auch aufden Inhalt des Knotens zugreifen. Hierzu dienen vier Member-Variablen, alle vom Typ string.

NameValueInnerTextInnerXmlText

Die Bedeutung dieser Member-Variablen ist abhängig vomNode-Typ! Es ist empfehlenswert, dass Sie sichmittels kleinerExperimente die unterschiedliche Qualität der Eigenschaft be-wusst machen. Im Folgenden ist die Bedeutung dieser Eigen-schaften für die wichtigsten Knotentypen aufgelistet.

Die Bedeutung der Member-Variable NameAttribute Name des AttributsComment #KommentarElement Tag-NameText #Text

Page 168: Otmar Ganahl - C Sharp

C #

168 5

Die Bedeutung der Member-Variable ValueAttribute Der Wert des AttributsComment #KommentarElement null-ReferenzText Inhalt des Textknotens

Die Bedeutung der Member-Variable InnerTextDie Eigenschaft InnerText eines Objekts vom Typ XmlNodefasst alle Inhalte des Knotens und auch dessen(!) Kindknoten.Nehmen Sie an, ein Objekt vom Typ XmlNode hält folgendenKnoten (mit Kindknoten).

<Person> <Vorname >Daisy</Vorname> <Nachname>Duck</Nachname> <Anrede>Frau</Anrede> <eMail>[email protected]</eMail> </Person>

Hier würde die Member-Variable InnerText folgenden Stringbeinhalten:

[email protected]

Ein anderes Beispiel:

Das Objekt vom Typ XmlNode enthält folgenden Knoten:

<Vorname>Donald</Vorname>

Hier würde die Member-Variable InnerText folgenden Stringhalten:

Donald

Die Bedeutung der Member-Variable InnerXmlTextDie Member-Variable InnerXmlText würde bei obigem komple-xen Knoten folgenden String halten.

<Vorname>Daisy</Vorname><Nachname>Duck</Nachname><Anrede>Frau</Anrede><eMail>[email protected]</eMail>

Page 169: Otmar Ganahl - C Sharp

XML-Klassen1695

Nach einigen Experimenten werden Sie das Verhalten dieserEigenschaften kennen. Experimente vertiefen auch das Denk-modell des W3C-Infosets und daher kann man diese kleinenExperimentieraufgaben nur wärmstens empfehlen!

XML-Dokumente verändernMit dem DOM-Modell lässt sich eine XML-Datenstruktur auchverändern. Wenn Sie z.B. dem Element <Person> je ein Attributtype="animal“ hinzufügen, könnten Sie dies wie folgt realisie-ren:

CD-BeispielXmlDom2

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>

Page 170: Otmar Ganahl - C Sharp

C #

170 5

<eMail>[email protected]</eMail> </Person> <Person type="animal"> <Vorname>Daisy</Vorname> <Nachname>Duck</Nachname> <Anrede>Frau</Anrede> <eMail>[email protected]</eMail> </Person> <Person type="animal"> <Vorname>Dagobert</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr §</Anrede> <eMail>[email protected]</eMail> </Person></Personen>

Mit der Methode doc.CreateNode kann unter Angabe des No-deTypes ein neuer XmlNode erzeugt werden, in diesem Fall einAttribut. Es wird der Wert des Attributes gesetzt und dann derAttribut-Liste des Knotens per SetNamedItem hinzugefügt. Siesehen also, dass XmlNode eine Eigenschaft vom Typ Attributesbesitzt. Natürlich hat diese Eigenschaft bei speziellen Knoten,wie z.B. Text keinen Sinn.

Dass die Klasse XmlDocument natürlich eine Save-Methodebesitzt ist anzunehmen.

Suchen innerhalb von XML-DokumentenCD-Beispiel

XmlDom3using 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)

Page 171: Otmar Ganahl - C Sharp

XML-Klassen1715

Console.WriteLine("Kein Node gefunden"); else Console.WriteLine(node.InnerXml); }}

In diesem Code-Beispiel sehen Sie, dass die Klasse XmlDocu-ment auch einen Suchalgorithmus innerhalb der XML-Daten-struktur anbietet.

string search = @"Personen/Person";

Der String search muss den Spezifikationen von XPATH gehor-chen. Die Methode SelectSingleNode(search) gibt den nächst-gelegenen Knoten, der die XPath-Suchbedingung erfüllt,zurück.

Über die Methode SelectNodes(search) können alle Knoten ineinem Schlag in eine Instanz vom Typ XmlNodeList gebrachtwerden.

CD-BeispielXmlDom4

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); } } }}

Page 172: Otmar Ganahl - C Sharp

C #

172 5

Hier werden per XPATH-String alle relevanten Knoten zurück-gegeben und über die Member-Variable ChildNodes analysiertund entsprechend verarbeitet.

ZusammenfassungDas DOM-Modell wirkt auf den ersten Blick kompliziert undkomplex. Ist es aber nicht. Einmal durchschaut, ist es sehr leis-tungsfähig. Es wird noch einmal empfohlen: Spielen und expe-rimentieren Sie mit diesen Beispielen. So lernen Sie denUmgang mit XmlNodes und auch das Prinzip der W3C-Infosetskennen. Ein sauberes Denkmodell erspart Ihnen dann sehr vielZeit im Umgang mit diesen Klassen.

XPATHIm obigen Beispiel haben Sie schon mit XPATH-Ausdrücken ge-arbeitet, um einen bestimmten Knoten in einer XML-Daten-struktur zu finden. XPATH ist eine W3C-Spezifikation, diesozusagen eine Adressierung eines Knotens innerhalb einerXML-Datenstruktur erlaubt.

Die .NET-Laufzeitumgebung bietet eine Klasse XPathDocu-ment an, die speziell für XPATH (und auch XSLT) optimiert ist.Beachten Sie aber, dass XPathDocument nicht (!) von XmlDocu-ment abgeleitet ist!

Die weiteren notwendigen Klassen in diesem Zusammenhangsind XPathNavigator und XPathNodeIterator. Diese lernen Sieim nächsten Beispiel kennen. Beachten Sie auch, dass Sie denNamensraum System.Xml.XPath öffnen!

CD-BeispielXmlXPath

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);

Page 173: Otmar Ganahl - C Sharp

XML-Klassen1735

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 XPathDocu-ment mit dem Namen doc. Um in diesem Dokument navigie-ren zu können, brauchen Sie ein XpathNavigator-Objekt, dasSie bekommen, wenn Sie die Methode CreateNavigator(...)aufrufen. CreateNavigator ist eine Methode der SchnittstelleIXPathNavigator, und daher müssen Sie zuerst diese Schnitt-stelle vom XPathDocument-Objekt doc anfordern. In C# ge-schieht dies ja bekanntlich per Casting. Dieses Navigationsob-jekt (nav) versorgen Sie nun mit XPATH-Ausdruck über dieMethode Select(...), die Ihnen ein neues Objekt vom Typ XPath-NodeIterator (iter) zurückgibt. Diese Klasse implementiert einCursor-Modell und erlaubt Ihnen, sich innerhalb der Ergebnis-menge zu bewegen. Sie werden sich nun vielleicht fragen, wel-chen Vorteil die Klasse XmlPathDocument gegenüber der Klas-se XmlDocument hat, da die „Bedienung“ der KlasseXmlPathDocument doch aufwändiger und komplexer er-scheint. Der Vorteil liegt in der Performanz und wirkt sich na-türlich nur bei sehr großen XML-Dokumenten spürbar aus.

XSLT (XSL-Transformationen)XPATH wird selten direkt verwendet. Der W3C-Standard XSLverwendet XPATH intensiv und es werden nun die Möglichkei-ten betrachtet, die .NET bezüglich XSLT bietet. XSL-Transfor-mationen erlauben, eine bestehende XML-Datenstruktur ineine andere beliebige Datenstruktur umzuwandeln. Das Inter-essante an dieser Spezifikation ist, dass die Transformations-regeln oder die Transformationsinformation selbst in Formeiner XML-Datei definiert werden kann. Es kann praktisch jedebeliebige Datenstruktur damit erzeugt werden. Hauptsächlich

Page 174: Otmar Ganahl - C Sharp

C #

174 5

wird XSLT verwendet, um eine bestehende XML-Datenstrukturin eine andere XML-Datenstruktur umzuwandeln, oder aberausgehend aus einer XML-Datenstruktur ein HTML-Daten-struktur zu erzeugen, die dann in einem Browser entsprechenddargestellt wird. Damit ist eine strikte Trennung zwischen Da-ten und Sicht gewährleistet.

Für XSL-Transformationen bietet .NET die Klasse XslTransfor-mation an. Diese Klasse finden Sie im Namensraum Sys-tem.Xml.Xsl. Das dazugehörige Beispiel:

CD-BeispielXmlXSLT

using System;using System.Xml;using System.Xml.XPath;using System.Xml.Xsl;using 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. Zu-sätzlich wird dann ein XslTransform-Objekt angelegt und mitder entsprechenden .xsl-Datei geladen. Mit der MethodeTransform der Klasse XslTransform kann nun eine XSL-Trans-formation durchgeführt werden. Allerdings erwartet dieseMethode einen XPathNavigator (was darauf hinweist, dass XSLauf XPATH basiert). Diesen erzeugen Sie in bekannter Weise.

Page 175: Otmar Ganahl - C Sharp

XML-Klassen1755

Die Methode bekommt dann noch einen FileStream, damit sieauch weiß, wo die transformierten Daten abzulegen sind.

Die Datei Personen.xsl ist eine Vorlage zur Transformation derPersonen.xml-Datei in eine HTML-Datei. Das Ergebnis Perso-nen.html lässt sich nun problemlos im Browser öffnen.

XML-Serialisierung von .NET-ObjektenUnter dem Begriff der Serialisierung versteht man in der Soft-wareentwicklung im Wesentlichen das Abspeichern bzw. La-den von Objekten auf ein Speichermedium, wie z.B. einerDatei. Möchten Sie z.B. ein Objekt auf die Festplatte abspei-chern, dann müssen Sie einen Byte-Stream erzeugen und die-sen dann in einer Datei speichern. Das Initialisieren einesObjektes aus Daten aus einem Speichermedium geschiehtdurch den umgekehrten Vorgang. Es ist also Aufgabe des Ent-wicklers, einen Byte-Stream zu erzeugen. Gerne wurde einByte-Stream aus lesbaren Zeichen erzeugt, sodass mit einemhandelsüblichen Editor der Inhalt gelesen und auch unter Um-ständen verändert werden konnte. Binäre Formate haben denVorteil, dass sie im Allgemeinen weniger Platz benötigen.

Wenn nun ein Objekt in Textform abgespeichert wird, dannstellt sich heute natürlich gleich die Frage, warum nicht inXML-Syntax? Dss ist naheliegend, zumal man die Möglichkeithat, solche Dateien jederzeit mit Texteditoren zu betrachtenoder anderweitig zu manipulieren. Andererseits können XML-Daten, wie Sie gesehen haben, sehr leicht verarbeitet werden.

Serialisierung von einfachen ObjektenUnter .NET gibt es nun die Möglichkeit, die Serialisierung vonObjekten der Laufzeitschicht zu überlassen, da diese Funktio-nalität in dieser schon implementiert ist.

CD-BeispielXmlObjSer1

using System;using System.Xml;using System.Xml.Serialization;

public class Person{ public string Vorname;

Page 176: Otmar Ganahl - C Sharp

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 = "[email protected]";

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 mitWerten belegt. Dann wird ein Objekt Sr der Klasse XmlSeriali-zer für den Typ Person angelegt. Die Klasse XmlSerializer befin-det sich im Namensraum System.Xml.Serializer. Mit derMethode Serialize der Klasse XmlSerializer kann nun das Objektin einem XML-Format gespeichert werden. Für die Durchfüh-

Page 177: Otmar Ganahl - C Sharp

XML-Klassen1775

rung benötigt die Klasse XmlSerializer ein XmlTextWriter-Ob-jekt, 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/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Vorname>Donald</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr $</Anrede> <eMail>[email protected]</eMail></Person>

Sie sehen also, die Implementierung verwendet den Klassen-namen und die Member-Namen um Tags zu erzeugen (das ge-schieht über den Reflection-Mechanismus). Beachten Sie, dassnur public vereinbarte Elemente über diesen Mechanismus se-rialisiert werden können.

Wenn Sie ein Objekt aus den Daten einer XML-Datei wieder re-konstruieren wollten, dann könnte das folgendermaßen ge-schehen:

XmlSerializer Sr = new XmlSerializer(typeof(Person));XmlTextReader tr = new XmlTextReader(Filename);Person newp = (Person)Sr.Deserialize(tr);

Serialisieren von komplexeren DatentypenIm ersten Moment sieht alles sehr einfach aus, aber die Voraus-setzungen sind ebenfalls sehr einfach. Jetzt wird das Ganze einwenig komplizierter.

Wie sieht es aus, wenn die Klasse eine Basisklasse hat bzw.eingebettete Strukturen oder Klassen besitzt?

CD-BeispielXmlObjSer2

public class Person{ public string Vorname; public string Nachname; public string Anrede; public string eMail;

Page 178: Otmar Ganahl - C Sharp

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 er-zeugt, die ihrerseits um ein Datum und Adresse erweitert wur-de.

public class App{ public static void Main() { PersonEx p = new PersonEx(); p.Vorname = "Donald"; p.Nachname = "Duck"; p.Anrede = "Herr $"; p.eMail = "[email protected]"; 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();

Page 179: Otmar Ganahl - C Sharp

XML-Klassen1795

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/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Vorname>Donald</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr $</Anrede> <eMail>[email protected]</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);

Serialisieren von ListenToll, nicht wahr? Funktioniert das Ganze auch für Listen? Ver-suchen Sie es: Im Folgenden ist nur die Main()-Methode dar-gestellt.

Page 180: Otmar Ganahl - C Sharp

C #

180 5

public static void Main(){ PersonEx p1 = new PersonEx(); p1.Vorname = "Donald"; p1.Nachname = "Duck"; p1.Anrede = "Herr"; p1.eMail = "[email protected]"; 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 einemFeld pl abgespeichert. Beim Erzeugen von XmlSerializer mussder Array-Typ mitgegeben werden.

XmlSerializer Sr = new XmlSerializer(typeof(PersonEx []));

Es entsteht damit folgende XML-Datei:

Page 181: Otmar Ganahl - C Sharp

XML-Klassen1815

<?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>[email protected]</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>[email protected]</eMail> <Geburtstag> 1927-12-01T00:00:00.0000000+01:00 </Geburtstag> <Addr> <PLZ>1234</PLZ> <Strasse>Dorfstrasse</Strasse> <Ort>Entenhausen</Ort> </Addr> </PersonEx></ArrayOfPersonEx>

Für das Feld wird ein eigenes Tag ArrayOfPersonEx erzeugt.

Namensgebung der Attribute.NET bietet aber auch die Möglichkeit, per Attribute in die Na-mensgebung der Tags einzugreifen oder aber auch festzule-gen, dass eine Variable nicht als eigenes Element, sondern als

Page 182: Otmar Ganahl - C Sharp

C #

182 5

XML-Attribut wirksam wird. Auch Namensräume können Sie,wenn gewünscht, 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/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Title="Herr $"> <FirstName>Donald</FirstName> <LastName>Duck</LastName> <eMail>[email protected]</eMail></person>

Sie sehen, dass nun die entsprechenden Tags verwendet wer-den, die bei .NET-Attributen angegeben wurden. Außerdemsehen Sie hier auch, dass das Member Anrede nun als Attributdes Tags Person vorkommt.

Diese Vorgehensweise ist natürlich nicht möglich, wenn Sieeine Klasse verwenden, deren Quellcode Sie nicht kennen bzw.die Klasse in einem Assembly zur Verfügung haben. .NET bietetauch hier Möglichkeiten an. Diese werden im Rahmen diesesBuches nicht behandelt und es wird auf die MSDN verwiesen.

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

Page 183: Otmar Ganahl - C Sharp

XML-Klassen1835

Die .NET-Laufzeitumgebung selbst verwendet intern intensivXML zur Implementierung unterschiedlichster Features. ImRahmen dieses Buches werden Sie noch mehrfach Kontakt mitXML-Datenstrukturen haben.

Page 184: Otmar Ganahl - C Sharp
Page 185: Otmar Ganahl - C Sharp

Windows-Applikationen

Einführung 186

Grundlegende Architektur einesWin32-Windowsprogramms

188

.NET-Programmiermodell 190

Windows-Steuerelemente 200

Designer (Entwurfsansicht) 209

Menü 219

Werkzeugleiste 222

Dialoge 225

Kundenspezifische Steuerelemente 230

GDI+ 236

Ressourcen 248

Unterstützung der Kulturen 257

Page 186: Otmar Ganahl - C Sharp

C #

186 6

EinführungBill Gates startete seine beispiellose Karriere Anfang der 80ermit dem Konsolen orientierten Betriebssystem MS DOS. Paral-lel dazu wurde von der Firma Apple ein Betriebssystem auf denMarkt gebracht, das eine grafische Oberfläche besaß.

Mitte der 80er Jahre startete auch Bill Gates mit Windows 1.0den Einstieg in eine grafische Oberfläche, die aber bei Weitemnicht an Apples Realisierung herangekommen ist. Es ist sicher-lich auf die unglückliche Marktpolitik von Apple und der genia-len Marketingstrategie Bill Gate's zurückzuführen, dass heuteWindows das beherrschende Betriebssystem auf Personal-computern ist.

Aber was soll's. Wenn Sie als Softwareentwickler Ihr Brot ver-dienen müssen, dann kommen Sie nicht umhin, Ihre Softwareauch für das Betriebssystem Windows anzupassen.

Die meisten Betriebssysteme bieten den Programmierern eineSchnittstelle zum Betriebssystem in Form von C-Funktionen.Diese werden API-Funktionen (Application Programming Inter-face) genannt und erlauben Anwendungen zu schreiben, dieauf diesem Betriebssystem laufen. Im Allgemeinen unterschei-den sich diese Funktionen bei unterschiedlichen Betriebssyste-men, sodass ein Quellcode auf Basis der Win32-API nicht direktauf einem anderen Betriebssystem kompiliert werden kann.Eine weitere Tatsache ist, dass die Win32-API nicht objektori-entiert ist und daher wird bei komplexeren Anforderungen dieProgrammierung auf dieser API ziemlich unübersichtlich.

Mit ANSI-C wurde eine Standard-Bibliothek definiert, die dieunterschiedlichen Betriebssysteme abstrahieren sollte. Zwi-schen dem Anwendungsprogrammierer und den API-Funktio-nen wurde eine Schicht eingeschoben. Die Bibliothek istnatürlich plattformabhängig, aber es ist damit möglich, Pro-gramme zu schreiben, die wenigstens auf Quellcodebasis zwi-schen den Plattformen portabel sind. Mit Entwicklung von C++hat sich auch diese Bibliothek weiter entwickelt, sie wurde ob-jektorientiert und Portabilität war im Wesentlichen gewähr-leistet, solange der Programmierer nicht auf spezifische,plattformabhängige API-Funktionen zurückgriff.

Page 187: Otmar Ganahl - C Sharp

Windows-Applikationen1876

Es ist interessant, dass sich für die grafischen Betriebssystemekeine standardisierte Bibliothek herauskristallisierte, die es er-möglichen würde, auf Quellcodebasis Programme zu schrei-ben, die auf die unterschiedlichen Plattformen einfach zuportieren wären. Es gab sehr wohl Ansätze dazu (beispielswei-se wxWindows), aber so richtig durchstarten konnte keine die-ser.

Das Windows-Programmiermodell ist durch seine Historie undden immer neu hinzugekommenen Features ziemlich komplexgeworden und in vielen Bereichen nicht mehr ganz schlüssig.

Mit .NET führt Microsoft auch ein völlig neues Programmier-modell für die Windows-Programmierung ein.

Mit einer Reihe von .NET-Klassen versucht nun Microsoft einevom Betriebssystem unabhängige, objektorientierte Bibliothekauch für grafische Oberflächen anzubieten. Vor allem imNamensraum System.Windows existieren eine Menge vonFensterklassen und erlauben so ein einfacheres Programmie-ren als das rohe Win32-Programmieren. Mit MFC (MicrosoftFoundation Class) und ATL (Active Template Libraray) hat Micro-soft zwar ebenfalls eine umfangreiche Bibliothek angeboten,die das Programmieren doch sehr erleichtert hat, aber die Bibli-otheken von .NET gehen einen Schritt weiter. Die konsequenteAnwendung dieser .NET-Klassen sollte theoretisch portableProgramme hervorbringen, die auf unterschiedlichen Plattfor-men lauffähig sein sollten (sollte .NET auch auf andere Plattfor-men portiert werden).

In diesem Kapitel werden Sie wieder anhand von Beispielendurch die verschiedensten Aspekte der Windows-Programmie-rung begleitet. Es ist klar, dass nicht alle Aspekte bis in die Tie-fe behandelt werden können. Dies würde den Rahmen desBuches bei Weitem sprengen. Sie eignen sich in diesem Kapiteldas notwendige Wissen an, das Sie benötigen, um autodidak-tisch vor allem mithilfe der MSDN anfallende Anforderungenund Probleme zu lösen. Hier wieder der Ratschlag: Experimen-tieren Sie mit den Beispielen und nehmen Sie auch die uner-schöpfliche Quelle MSDN in Anspruch!

Page 188: Otmar Ganahl - C Sharp

C #

188 6

Grundlegende Architektur eines Win32-WindowsprogrammsBevor das .NET Programmiermodell betrachtet wird, wird fürdiejenigen, die noch nie ein Windowsprogramm auf API-Basiserstellt haben, in Form eines relativ einfachen C-Beispiels einEindruck eines typischen Windowsprogramms vermittelt. Die-jenigen, die diesbezüglich Erfahrungen haben, können diesenAbschnitt einfach überspringen.

Es hat sich gezeigt, dass grundlegende Kenntnisse der Win32-Architektur das Verständnis auch für das .NET-Programmier-modell fördern und somit auch die Lernkurve verkürzen.

Jeder Prozess unter den Win32-Betriebssystemen besitzt u.a. ei-nen oder mehrere Threads. Threads, zu deutsch Ablaufpfadesind die „Arbeitspferde“, die Code durchführen. Alle laufendenThreads eines Systems bekommen vom Betriebssystem eine be-stimmte Zeit zur Ausführung des Codes (Thread Scheduling).Unter Win32 kann (muss nicht) jeder Thread eine so genannteNachrichtenschleife besitzen. Es ist dies ein FIFO (First In-FirstOut), das vom Betriebssystem mit Nachrichten gefüllt wird.Nachrichten sind z.B. „Maus bewegen“ (WM_MOUSEMOVE),„Linke Maustaste nach unten drücken“ (WM_LBUTTONDOWN),„Tastaturtaste drücken“ (WM_KEYDOWN) usw.

Nachrichten werden über das Betriebssystem in die jeweiligeNachrichtenschlange des Threads gebracht. Auch Steuerele-mente eines Fensters (Button, Editbox etc.) oder Menü undToolbars haben zur Folge, dass in die Nachrichtenschlagen desThreads Nachrichten gebracht werden, die es dann gilt, abzu-arbeiten. Nachrichten können auch programmtechnisch durchdie API-Funktion PostMessage(...) in die Nachrichtenschlangegebracht werden. Dies ist übrigens eine oft verwendete Me-thode der Interprozesskommunikation auf Win32-Betriebssys-temen.

Ein Windowsprogrammierer hat nun sicherzustellen, dass dieNachrichten aus der Nachrichtenschlange abgeholt werdenund wird dann auf die unterschiedlichen Nachrichten entspre-chend der gewünschten Funktionalität reagieren. Das Herz ei-

Page 189: Otmar Ganahl - C Sharp

Windows-Applikationen1896

nes jeden Windowsprogramms ist daher folgende (Endlos)-Schleife (diese befindet sich im Normalfall in der Hauptfunkti-on).

while (GetMessage (&msg, NULL, 0, 0)){ DispatchMessage (&msg) ;}

Die API-Funktion GetMessage(...) holt sich die nächste Nach-richt in Form einer Struktur (msg) aus der Nachrichtenschlangeund gibt diese dann der API-Funktion DispatchMessage(...) wei-ter. Wenn in der Nachrichtenschlange die Nachricht WM_QUITliegt, dann gibt die Funktion GetMessage(...) den Wert 0 zurückund die Schleife wird abgebrochen. Über die Funktion Dis-patchMessage(...) wird dann im Allgemeinen eine entsprechen-de Prozedurfunktion aufgerufen, die z.B. folgendes Aussehenhaben 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) ;

Page 190: Otmar Ganahl - C Sharp

C #

190 6

return 0 ; }

return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Sie sehen, in dieser befindet sich im einfachsten Fall eineswitch-case-Struktur, in der auf die unterschiedlichen Nach-richten mit Code reagiert werden kann. Im Beispielcode rea-giert das Programm z.B. auf die Nachrichten WM_PAINT,WM_LBUTTONDOWN, WM_KEYDOWN und WM_DESTROY. Alleanderen Nachrichten werden der Funktion DefWindowProcweitergeleitet.

Windowsprogrammieren heißt also „auf Nachrichten reagie-ren“. Wie schon erwähnt, können Nachrichten auch vonSteuerelementen, Menüs, Werkzeugleisten etc. kommen. Kon-zentrieren Sie sich aber nun auf das .NET-Programmiermodell.

.NET-ProgrammiermodellMit .NET führt Microsoft auch ein völlig neues Programmier-modell für die Programmierung von grafischen Oberflächenein. Mit einer Reihe von .NET Klassen versucht Microsoft einevom Betriebssystem unabhängige, objektorientierte Biblio-thek auch für grafische Oberflächen anzubieten (siehe einlei-tende Diskussion im vorigen Kapitel). Das Programmiermodellist objektorientiert und prinzipiell portabel.

Vor allem die Klassen des Namensraums System.Windows un-terstützen eine Unzahl von Fensterklassen und erlauben eineinfacheres Programmieren als das „rohe“ Win32-Programmie-ren. Die konsequente Anwendung dieser .NET Klassen solltewenigstens theoretisch portable Programme hervorbringen,die auf unterschiedlichen CLRs (Common Language Runtime)lauffähig sein sollten.

Am besten lernen Sie dieses Programmiermodell anhand eineskleinen Beispiels kennen. Aus didaktischen Gründen werdenSie das erste Beispiel ohne das Entwicklungssystem erzeugen.Legen Sie in einem eigenen Ordner SimpleWindow mit einembeliebigen Editor (z.B. Nodepad) eine Datei mit dem NamenSimpleWindow.cs und geben Sie folgenden C# -Quellcode ein:

Page 191: Otmar Ganahl - C Sharp

Windows-Applikationen1916

CD-BeispielSimpleWindow

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 findendiese unter Start > Programme > Microsoft Visual Studio.NET >Visual Studio .NET Tools > Visual Studio .NET Command Prompt.

Wenn Sie keinen Tippfehler gemacht haben, dann wurde imOrdner eine ausführbare Datei Winapp.exe erzeugt. Wenn Siediese Datei ausführen, dann erscheint folgendes Fenster.

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

Minimale Windowsapplikation

Abb. 6.1

Page 192: Otmar Ganahl - C Sharp

C #

192 6

Klasse im Namensraum System.Windows.Forms und imple-mentiert ein typisches Fenster eines grafischen User Interface.Diese Fensterklasse besitzt natürlich eine Menge von Member-Variablen, im Beispiel wird das Member Text belegt. Der Ein-trag erscheint im Fenster in der Kopfleiste.

Die statische Methode Run der Klasse Application (ebenfalls imNamensraum System.Windows.Forms definiert), richtet nuneine Nachrichtenschleife im Thread ein und öffnet (optional)ein Fenster. Dieses Fenster wird der Methode Run als Parame-ter 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 er-zeugen wollen. In den Assemblies System.dll und System.Win-dows.Forms.dll befinden sich die in diesem kleinen Beispielverwendeten Klassen und müssen daher angegeben werden.

Erzeugen Sie nun dieses kleine Projekt mithilfe des Entwick-lungssystems Visual Studio.NET. Für die Experimente in diesemKapitel legen Sie am besten eine neue Projektmappe mit demNamen Winapp an.

Das Entwicklungssystem bietet Ihnen eine Vorlage Windows-Anwendungen an. Bei den ersten Beispielen soll aber auf dieseVorlage noch verzichtet werden, und Sie erzeugen daher einLeeres Projekt mit dem Namen SimpleWindow.

Zuerst müssen Sie die Projekteigenschaften so ändern, dassauch eine Windowsapplikation erzeugt wird. Markieren Siedazu das Projekt im Projektmappen-Explorer, öffnen Sie perKontextmenü Eigenschaften den Eigenschaftsordner des Pro-jektes und ändern Sie den Ausgabetyp des Projektes von Kon-sole-Anwendung auf Windows-Anwendung.

Damit weisen Sie den Kompiler an, die Applikation mit der Op-tion /t:winexe zu kompilieren. Der Applikation fügen Sie nuneine C#-Codedatei mit dem Namen App.cs hinzu (Menü Projek-te > Neues Element hinzufügen... ) und geben den Code des Bei-spiels FirstWindow ein.

Page 193: Otmar Ganahl - C Sharp

Windows-Applikationen1936

CD-BeispielSimpleWindow

using System;using System.Windows.Forms;

public class App{ static void Main()

Leeres Projekt anlegen

Projekteigenschaft Windows-Anwendung

Abb. 6.2

Abb. 6.3

Page 194: Otmar Ganahl - C Sharp

C #

194 6

{ Form win = new Form(); win.Text = "Klitzeklein"; Application.Run(win); }}

Damit auch die notwendigen Assemblies vom Kompiler hinzu-gefügt werden, richten Sie noch Verweise auf System.dll undSytem.Windows.Forms.dll ein (Kontextmenü Verweise > Ver-weise hinzufügen... im Projektmappen-Explorer).

Die Klasse FormDie Klasse Form bietet schon sämtliche Features eines Fenstersan. So z.B. die Fähigkeit, die Größe des Fensters zu verändern,das Fenster zu schließen (und damit auch die Applikation)usw. Möchten Sie nun weitere Features dem Fenster hinzufü-gen, dann erzeugen Sie am besten eine neue Fensterklasse mitder Klasse Form als Basisklasse und fügen dieser neuen Klassedie entsprechenden Features hinzu. Erweitern Sie dazu dasBeispiel:

CD-BeispielWin1

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() {

Page 195: Otmar Ganahl - C Sharp

Windows-Applikationen1956

Application.Run(new MainWnd()); }}

Hier haben Sie eine neue Klasse mit dem Namen MainWnd,abgeleitet von Form, erzeugt. Der Konstruktor der Klasse be-legt die mitgeerbten Member-Variablen BackColor und Textspezifisch. Beachten Sie, dass für die Farbe ein zusätzlicher Na-mensraum (System.Drawing) geöffnet werden muss, und dassSie einen zusätzlichen Verweis auf das Assembly System.Dra-wing.dll einrichten müssen!

Die Methode Run der Klasse Application in Main() verwendetnun eine vom Typ MainWnd.

Ein wichtiger Hinweis muss an dieser Stelle erfolgen: WerfenSie einen Blick auf den Projektmappen-Explorer.

Sie sehen nun, dass im Projektmappen-Explorer die DateiApp.cs mit einem neuen Icon versehen wurde, das ein Fensterandeuten soll. Ein Doppelklick auf App.cs im Projektmappen-Explorer öffnet Ihnen nun nicht mehr den Quellcode, sondernSie kommen in die Entwurfsansicht des Fensters (Designer).Den Designer werden Sie später noch kennen lernen. Die Quell-code-Darstellung erreichen Sie, indem Sie bei App.cs per Kon-textmenü Code anzeigen wählen. Eine weitere Möglichkeit,zwischen der Entwurfsansicht und der Quellcode-Ansicht um-zuschalten, gibt es in der Werkzeugleiste des Projektmappen-Explorers. In der Abbildung 6.4 sind die Icons entsprechendmarkiert.

Blick auf den Projektmappen-Explorer

Abb. 6.4

Page 196: Otmar Ganahl - C Sharp

C #

196 6

FarbendarstellungIn der WIN32-Programmierung werden Farben als 32 Bit-Wertedargestellt. Aber nur die drei niederwertigen Bytes werdenverwendet. Jedes Byte entspricht einer der Grundfarben Rot,Grün und Blau (RGB-Darstellung).

.NET bietet im Assembly System.Drawing.dll eine Struktur mitdem Namen Color an. Diese Struktur hat eine statische Metho-de FromArgb, die es erlaubt, die Grundfarbenanteile anzuge-ben und damit eine Farbe zu erzeugen.

BackColor = Color.FromArgb(0,255,0);

Die Struktur definiert auch eine große Zahl von Properties fürdie wichtigsten Farben (z.B. Color.Green). Damit hätte die Zeile

BackColor = Color.Green;

denselben Effekt.

Auf Fensterereignisse reagierenIn der Klasse Forms sind einige Events definiert. Im Wesentli-chen sind dies sämtliche Windowsnachrichten, die auf Fensterdefiniert sind. So auch das Event Click, das auftritt, wenn imFenster eine Maustaste gedrückt wird. Wie Sie Eventhandlerrealisieren, haben Sie im Kapitel 2. C# – die neue Programmier-sprache kennen gelernt. Im folgenden Beispiel reagieren Sie inder Methode OnClick auf das entsprechende Event. Im Code-beispiel wird bei Auftreten des Events Click die Hintergrundfar-be umgeschaltet.

CD-BeispielWin2

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);

Page 197: Otmar Ganahl - C Sharp

Windows-Applikationen1976

} //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 Ereig-nisse. Es genügt vorerst, wenn Sie den Mechanismus verstan-den haben. Weitere Ereignisse werden Sie im Rahmen diesesKapitels noch zu Genüge kennen lernen. Wenn Sie bezüglichdes .NET-Event-Mechanismusses (Delegates) noch unsichersind, dann sollten Sie die entsprechenden Abschnitte im Kapi-tel 2: C# – die neue Programmiersprache auffrischen. Sie tunsich nachfolgend um einiges leichter.

Steuerelement im FensterEin typisches Fenster unter Windows besitzt auch Steuerele-mente. Im nächsten Beispiel werden Sie nun die prinzipielleVorgehensweise beim Einbinden von Steuerelementen ken-nen lernen.

Die Hintergrundfarbe des Fensters soll über einen Button um-geschaltet werden können.

CD-BeispielWin3

using System;using System.Drawing;using System.Windows.Forms;

public class MainWnd:Form{ Button ButYellow; public MainWnd()

Page 198: Otmar Ganahl - C Sharp

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 MengeSteuerelemente an, unter anderem auch das SteuerelementButton. Wie Sie aus dem Code ersehen, wurde der Klasse Main-Wnd eine zusätzliche Member-Variable ButYellow vom TypButton hinzugefügt. Sämtliche Steuerelemente sind von Sys-tem.ComponentModel.Component abgeleitet.

Page 199: Otmar Ganahl - C Sharp

Windows-Applikationen1996

Im Konstruktor der Klasse MainWnd wird dieser Button er-zeugt 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 desButtons, die anderen Eigenschaften sind selbsterklärend. DerButton implementiert auch ein Event Click. Diesem Event wirdim Beispiel die Methode OnButYellow zugeordnet.

Besonders wichtig ist, dass nun sämtliche Steuerelemente, dieim Fenster sichtbar sein sollten, dem dynamischen Feld Con-trols (geerbt von der Basisklasse Form) hinzugefügt werden.

Controls.Add(ButYellow);

Die Implementierung der Methode OnButYellow ist trivial.

KoordinatensystemIm Beispiel wurde der Button innerhalb des Koordinatensys-tems des Fensters (Form) positioniert. Das Default-Koordina-tensystem basiert auf der Einheit Pixel. Die Richtungen derHauptachsen sehen Sie in der folgenden Abbildung. Ein Punktim Koordinatensystem wird durch die Struktur Point des Na-mensraumes System.Drawing repräsentiert. Size, ebenfallseine Struktur im Namensraum System.Drawing definiert eineGrößenangabe eines Rechteckes (Width, Height). Sie werdenim Rahmen diese Kapitels später noch ausführlicher über Ko-ordinatensysteme und Klassen hören.

Page 200: Otmar Ganahl - C Sharp

C #

200 6

ZusammenfassungIn diesem Abschnitt haben Sie die grundlegenden Zusammen-hänge des .NET-Windows-Programmiermodells kennen ge-lernt. Die Methode Run der Klasse Application startet eineNachrichtenschlange und erzeugt das Hauptfenster über eineInstanz der Klasse Form. Im Allgemeinen wird eine spezielleFensterklasse auf Basis der Klasse Form erzeugt. Auf sämtlicheEigenschaften und Fensternachrichten kann über Member-Va-riablen und Events der Klasse Form zugegriffen werden. In einFenster können Steuerelemente platziert werden. Dies ge-schieht, indem Instanzen der Steuerelemente erzeugt werden,deren Eigenschaften belegt und dem dynamischen Feld Con-trols der Klasse Form zugewiesen werden. Steuerelemente im-plementieren ihrerseits Events, auf die programmtechnischreagiert werden kann.

In den nächsten Kapiteln werden Sie tiefer in die Materie ein-steigen.

Windows-SteuerelementeIn diesem Kapitel werden Sie an einem Beispiel die Handha-bung einiger wichtiger Steuerelemente kennen lernen. Diegrundsätzliche Vorgehensweise der Einbettung von Steuerele-menten in eine Form haben Sie schon kennen gelernt.

Hauptachsen desKoordinatensystems

Abb. 6.5

Page 201: Otmar Ganahl - C Sharp

Windows-Applikationen2016

TextBox, TrackBar, RadioButton, Label

Dieses kleine Beispielprogramm ermöglicht dem Anwenderdie Hintergrundfarbe des Fenster auf verschiedene Arten zusetzen. Einmal in Form von TrackBars (Schieberegler). Jeder derGrundfarben Rot, Grün und Blau wird eine TrackBar zugeord-net. Über diese können dann die Intensitätsanteile der Grund-farben zwischen 0 und 255 eingestellt werden. DieHintergrundfarbe soll sich dann entsprechend ändern. Weiterist jeder Grundfarbe eine TextBox zugeordnet, die die Werteauch „digital“ darstellt. Änderungen sind auch über die Text-Boxen möglich, was zur Folge hat, dass auch die Schiebereglerder TrackBars die entsprechende Stellung einnehmen. Übereine Optionsgruppe können auch vordefinierte Farben direktaktiviert werden. TextBoxen und TrackBars werden dann na-türlich ebenfalls entsprechende Werte annehmen.

Erzeugen Sie in der Projektmappe ein neues Leeres Projekt mitdem Namen Steuerelement. Richten Sie Verweise auf die not-wendigen Assemblies System.dll, System.Drawing.dll und Sys-tem.Windows.Forms.dll ein. Im Eigenschaftsdialog desProjektes schalten Sie dann den Ausgabetyp auf Windows-An-wendung um.

In einer ersten Iteration werden die TrackBars mit den entspre-chenden Labels realisiert.

Beispielapplikation Steuerelemente

Abb. 6.6

Page 202: Otmar Ganahl - C Sharp

C #

202 6

CD-BeispielSteuerelemente1

using System;using System.Drawing;using System.Windows.Forms;

public class MainWnd:Form{ Label lRed; Label lGreen; Label 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 undtbarBlue). Die Größe des Fensters sowie die Fensterüberschriftwerden 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);

Page 203: Otmar Ganahl - C Sharp

Windows-Applikationen2036

Das Label lRed bekommt den Text „Rotanteil“ zugeordnet undwird an der Position (0,0) und mit einer Größe von 80 PixelBreite und 30 Pixel Höhe ausgestattet.

Bei den Labels lGreen und lBlue müssen Sie beachten, dass Siedie Koordinaten auch so wählen, dass diese Labels auch ent-sprechend Abbildung 6.6 positioniert werden.

lGreen = new Label();lGreen.Text = "Grünanteil";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 Codeaus-schnitt 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 einstellbareWertebereich über die Members Minimum und Maximum ein-gestellt (im Beispiel zwischen 0 und 255). Über TickFrequencykann die Schrittteilung der TrackBar eingestellt werden. Überdas Member Value können Sie jederzeit den Wert des Schie-bereglers auslesen, und auch setzen. Im Beispiel wird diese aufden mittleren Wert 128 gesetzt.

Wenn sich der Wert der TrackBar ändert, soll das Programm re-agieren. Daher wird auf das Event ValueChanged der KlasseTrackBar eine Bearbeitungsfunktion zugeordnet (OnSliderMo-ved).

Page 204: Otmar Ganahl - C Sharp

C #

204 6

In gleicher Weise initialisieren Sie die TrackBars tbarGreen undtbarBlue.

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, sämtliche Steuerelemente in die Lis-te 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 BearbeitungsfunktionOnSliderMoved zugeordnet! Die Implementierung geschieht inForm einer Methode der Klasse MainWnd.

protected void OnSliderMoved( object sender, EventArgs arg){

Page 205: Otmar Ganahl - C Sharp

Windows-Applikationen2056

this.BackColor = Color.FromArgb( tbarRed.Value,tbarGreen.Value,tbarBlue.Value);}

In dieser Methode wird die Hintergrundfarbe des FenstersMainWnd mit der Farbe belegt, die sich aus den aktuellen Wer-ten der drei TrackBars ergeben. Wie schon erwähnt, könnendiese Werte über das Member Value der Klasse TrackBar aus-gelesen werden.

Kompilieren Sie das Projekt und dann können Sie schon dieHintergrundfarbe des Fensters über die TrackBars einstellen.Nun soll das Projekt um die TextBoxen, die den digitalen Wertder Intensitätswerte der Grundfarben anzeigen, erweitertwerden.

Fügen Sie der Klasse MainWnd zusätzlich drei Texboxen hinzu:

CD-BeispielSteuerelemente2

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 Textänderung in der TextBox wird in der Bearbeitungs-funktion OnTBChanged verarbeitet.

In ähnlicher Weise werden auch die TextBoxen tbGreen undtbBlue 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);

Page 206: Otmar Ganahl - C Sharp

C #

206 6

Vergessen Sie nicht, die neuen Steuerelemente auch der ListeControls hinzuzufügen!

Controls.Add(tbRed);Controls.Add(tbGreen);Controls.Add(tbBlue);

Die Werte der Schiebereglerstellungen der TrackBars sollen inden TextBoxen dargestellt werden. Diese Funktionalität fügenSie am besten in der Bearbeitungsfunktion der TrackBars On-SliderMoved 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();}

Für jede TextBox wird die Member-Variable Text mit dem WertValue der TrackBars belegt.

Umgekehrt sollen bei einer Änderung in den TextBoxen auchdie Schieberegler die entsprechende Position einnehmen. Die-se Funktionalität programmieren Sie in die noch zu erstellendeMethode 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) { }}

Page 207: Otmar Ganahl - C Sharp

Windows-Applikationen2076

Diese Methode wird aufgerufen, wenn sich der Inhalt einerTextBox ändert. In der Methode werden zuerst die Werte derTextBoxen ausgelesen und dann die TrackBars entsprechendgesetzt. Aber das geht nicht so einfach.

TextBoxen halten ihre Werte in Form von Strings. Sie müssendaher zuerst den String in einen Integer-Wert umwandeln.Hierzu verwenden Sie die statische Methode Parse der StrukturInt32. Diese Methode führt die Umwandlung eines Ziffern-strings in einen Integerwert durch. (Übrigens: Sämtliche num-merischen Typen besitzen diese angenehme Methode.) DieEingabe eines Strings, der nicht einem Ziffernstring gehorcht,hat eine Exception zur Folge. Diese fangen Sie am besten ab,damit nicht das Programm wegen einer irrtümlich falschenAngabe terminiert.

Zu guter Letzt erweitern Sie das Beispiel noch um eine Opti-onsgruppe gemäß Abbildung 6.6. Dazu erweitern Sie die Klas-se um folgende Member-Variablen:

CD-BeispielSteuerelemente3

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 Be-arbeitungsmethode zugewiesen.

In gleicher Weise verfahren Sie nun mit den restlichen Radio-Buttons.

rbGrenn = new RadioButton();rbGreen.Location = new Point(20,170);rbGreen.Size = new Size(100,30);rbGreen.Text = "Grün";

Page 208: Otmar Ganahl - C Sharp

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) zuge-ordnet. Damit lässt sich auch nur ein Button aktivieren. DasZuordnen zu einer Gruppe wird wie folgt durchgeführt:

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 RadioBut-tons der Liste Controls des Steuerelementes gbColor zugeord-net. Damit sind diese der GroupBox untergeordnet.

Auch hier bitte wieder nicht vergessen, dass sämtliche Steuer-elemente in die Liste Controls der Klasse MainWnd aufgenom-men werden müssen. Die Steuerelemente werden ansonstennicht dargestellt!

Controls.Add(rbYellow);Controls.Add(rbGreen);Controls.Add(rbWhite);Controls.Add(gbColor);

Nun fehlt nur noch die Bearbeitungsmethode auf die Aktivie-rung 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;

Page 209: Otmar Ganahl - C Sharp

Windows-Applikationen2096

tbRed.Text = col.R.ToString(); tbGreen.Text = col.G.ToString(); tbBlue.Text = col.B.ToString();}

Allen RadioButtons wurde dieselbe Bearbeitungsmethode zu-geordnet. Sie können aber feststellen, von welchem RadioBut-ton das Event ausgelöst wurde. Entsprechend erzeugen Siesich dann eine Farbe. Wenn Sie nun aus dieser die Farbanteileextrahieren (über die Properties R, G und B), können Sie dieTextBoxen direkt mit den entsprechenden Strings versorgen.

ZusammenfassungSie haben in diesem Beispiel nun die Verwendung von einigenSteuerelementen kennen gelernt. Die Vorgehensweise nocheinmal zusammengefasst:

Fensterklasse erzeugen (Basisklasse Form)Steuerelemente als Member-Variablen der Fenster-klasse definierenIm Konstruktor der Fensterklasse diese initialisieren,positionieren und auf die gewünschten Events Be-handlungsmethoden schreibenDie Steuerelemente der Liste Controls der Fensterklas-se hinzufügen

Ihnen ist sicherlich aufgefallen, welcher Aufwand das Positio-nieren von Steuerelementen ist. Stellen Sie sich nur vor, wennSie bei dem eben gezeigten Beispiel sämtliche Steuerelementeverschieben möchten. Bei komplexeren Fensteransichten artetdas Ganze auf ein lästiges und aufwändiges „Pixelrechnen“aus.

Im nächsten Abschnitt werden Sie nun ein Werkzeug kennenlernen, das den Entwickler in dieser nicht gerade anspruchs-vollen Arbeit entlastet.

Designer (Entwurfsansicht)Hat ein Fenster viele Steuerelemente, dann wird die Verwal-tung ziemlich aufwändig, denken Sie nur an die Positionierung

Page 210: Otmar Ganahl - C Sharp

C #

210 6

der Steuerelemente, die in ein „dummes“ Pixelrechnen ausar-tet. Wer schon Windowsprogramme unter Visual Studio 6.0programmiert hat, kennt sicherlich den Ressourcen-Editor, deres ermöglicht hat, die Steuerelemente grafisch in einem Dia-log zu platzieren.

Einen gänzlich neuen Weg geht Visual Studio.NET bezüglichder grafischen Entwicklung von Layouts. Neu eingeführt wur-de der so genannte Designer. Dieser erlaubt ähnlich, wie Sie esvom Ressourcen-Editor her kennen, Fenster grafisch zu entwi-ckeln.

Der Designer darf allerdings nicht mit dem Ressourcen-Editorverwechselt werden, da dieser einen gänzlich anderen techni-schen Hintergrund hat. Sie werden nun die Verwendung desDesigners anhand eines Beispiels kennen lernen. Das BeispielSimple Email aus dem Kapitel 2: C# – die neue Programmierspra-che soll nun als Windows-Programm ausgeführt werden. In die-sem Zusammenhang werden Sie auch weitere Steuerelementekennen lernen. Außerdem werden Sie den Projektassistentenfür die Erzeugung eines Applikationsrumpfes verwenden.

Legen Sie dazu ein neues Projekt an. Wählen Sie die VorlageWindows-Anwendung und geben Sie dem Projekt den NamenSimple Email.

Der Assistent erzeugt Ihnen das Projekt, fügt auch die für einWindows-Programm notwendigen Assemblies hinzu und bie-tet Ihnen dann auch gleich die Designer-Ansicht (zu deutschEntwurfsansicht) an. Im Projektmappen-Explorer sehen Sie nun

Simple Email1-Beispiel

Abb. 6.7

Page 211: Otmar Ganahl - C Sharp

Windows-Applikationen2116

das Projekt und eine Datei mit dem Namen Form1.cs. ÄndernSie zuerst einmal diesen Namen mit dem Kontextmenü aufApp.cs.

Für die Datei App.cs existieren nun im Entwicklungssystemzwei Ansichten. Einmal die Entwurfsansicht (Designer) undeinmal die Quellcodeansicht. Es wurde schon in Kapitel 5: XML-Klassen erklärt, wie Sie zwischen diesen Ansichten umschaltenkönnen. Lassen Sie sich nun die Datei einmal in der Quellcode-ansicht darstellen. Sie sehen im Folgenden den Code ohne dieKommentare, die der Assistent eingefügt hat.

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

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

Windows-Anwendung erstellen

Abb. 6.8

Page 212: Otmar Ganahl - C Sharp

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 KlasseForm1 angelegt, die von der Basisklasse Form abgeleitet ist,und somit eine Fensterklasse repräsentiert. Es ist empfehlens-wert, den Namen der Klasse umzubenennen (z.B. MainWnd).

Diese Klasse besitzt auch die statische Methode Main. Im Kon-struktor dieser Fensterklasse, wo Sie bisher die Initialisierun-

Page 213: Otmar Ganahl - C Sharp

Windows-Applikationen2136

gen durchgeführt haben, befindet sich eine MethodeInitializeComponent. Sie ahnen richtig, sämtliche Initialisierun-gen werden in dieser Methode gekapselt. Die Methode Initiali-zeComponent ist in einer Präprozessor-Anweisung #region-#endregion eingebunden. Sie können mit dieser Präprozessor-Anweisung Code-Blöcke definieren, die sich im Quellcode-Edi-tor von Visual Studio.NET wegblenden lassen. Die Anweisunghat ansonsten keine Bedeutung für dieses Beispiel.

Die Methode Dispose wird am Schluss aufgerufen und hierkönnten noch Aufräumarbeiten durchgeführt werden.

Wechseln Sie nun in die Entwurfsansicht (Designer). In der Ent-wurfsansicht haben Sie eine Toolbox WindowsForms zur Ver-fügung. Sollte diese bei Ihnen nicht aktiviert sein, dannkönnen Sie diese mit dem Menübefehl Ansicht > Toolbox akti-vieren.

Sie können nun per Drag&Drop dem Fenster Steuerelementehinzufügen. Fügen Sie nun ein Steuerelement vom Typ Text-Box in den Fensterentwurf ein.

Im Fenster Eigenschaften werden nun sämtliche Eigenschaftendes Steuerelementes aufgelistet. Im Fenster Eigenschaft befin-det sich auch ein Eintrag Name. Geben Sie dem Steuerelementden sprechenden Namen tbAddress.

Windows Forms Toolbox

Abb. 6.9

Page 214: Otmar Ganahl - C Sharp

C #

214 6

Im Eigenschaftsfenster werden Sie sich oft aufhalten. Der In-halt des Eigenschaftsfensters bezieht sich immer auf das gera-de markierte Element im Designer. Es werden hier sämtlicheMembers, Properties und Events eines Elements aufgelistet. Siekönnen diese nach Kategorien, Alphabetisch, ausschließlichnach den Eigenschaften (ohne Events) oder ausschließlich nachden Events (ohne Eigenschaften) sortieren. Die entsprechen-

Entwurfsansicht

Eigenschaftsfenster

Abb. 6.10

Abb. 6.11

Page 215: Otmar Ganahl - C Sharp

Windows-Applikationen2156

den Icons finden Sie in der Werkzeugleiste des Eigenschafts-fensters. Im Eigenschaftsfenster können Sie darüber hinauseinem Element auch einen Namen vergeben.

Wechseln Sie nun wieder in die Quellcodeansicht der DateiApp.cs. Es wird Ihnen auffallen, dass nun der Designer der Klas-se eine neue Member-Variable vom Typ TextBox, mit dem vonIhnen im Eigenschaftsfenster gewählten Namen, eingefügthat. Ändern Sie den Namen der Variable, die ein Steuerele-ment repräsentiert, immer über den Designer!

Wenn Sie nun einen Blick in die Methode InitializeComponentwerfen, 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 De-signer in der Methode InitializeComponent für dieses Steuere-lement Initialisierungscode eingefügt.

Sollten Sie die Methode InitializeComponent im Quellcode nichtfinden, dann suchen Sie den Eintrag Windows FormDesignergenerated code und expandieren Sie diesen Eintrag. Der Quell-code-Editor erlaubt nämlich, Gliederungen wegzublenden.

Über den Designer können Sie also sämtliche Steuerelementeim Fenster platzieren und dessen Eigenschaften über das Ei-genschaftsfenster einstellen. Alle Aktionen im Designer habeneine unmittelbare Auswirkung im Code. Unterlassen Sie daherdirekte Codeänderungen in der Methode InitializeComponent,machen Sie alles über den Designer! Es ist Ihnen sicherlichschon aufgefallen, dass Sie nicht nur die Eigenschaften derSteuerelemente, sondern auch die Eigenschaften der Formselbst im Eigenschaftsfenster festlegen können. Markieren Siedie Form und belegen Sie das Text-Property mit „EinfachesE-Mail Programm“.

Page 216: Otmar Ganahl - C Sharp

C #

216 6

Fügen Sie nun dem Fenster einen Button hinzu. Geben Sie demButton den Namen btSend und das Text-Property belegen Sieebenfalls über das Eigenschaftsfenster mit „Senden“.

Diesem Button werden Sie nun eine Bearbeitungsmethode zu-weisen, indem Sie eine Methode beim Event Click anmelden.Verwenden Sie hierzu ebenfalls den Designer:

Markieren Sie im Designer den Button, damit das Eigen-schaftsfenster die Eigenschaften des Buttons auflistet und las-sen nur die Events anzeigen. Suchen Sie das Event Click undfügen Sie per Doppelklick eine Behandlungsroutine hinzu. DerDesigner erzeugt dann im Quellcode unverzüglich den Rumpfeiner Bearbeitungsroutine. Den Namen der Methode erstelltsich der Designer aus einer Kombination des Namens des Ele-mentes und dem Namen des Events.

private void btSend_Click( object sender, System.EventArgs e){

}

In der Methode InitializeComponent wird die Anmeldung andas Ereignis durchgeführt.

this.btSend.Click += new System.EventHandler(this.btSend_Click);

Hinzufügen von Event-Bearbeitungsroutinen

Abb. 6.12

Page 217: Otmar Ganahl - C Sharp

Windows-Applikationen2176

Ein Doppelklick direkt auf ein Steuerelement in der Entwurfs-ansicht hat zur Folge, dass eine bevorzugte Bearbeitungsrouti-ne des Steuerelementes erzeugt wird. Für Buttons ist dies z.B.die Bearbeitungsroutine für das Event Click. Wenn Sie eine Be-arbeitungsroutine entfernen möchten, dann tun Sie dies bitteausschließlich im Designer, in dem Sie die Methode im Eigen-schaftsfenster löschen.

Erweitern Sie nun das Beispiel mit den restlichen Elementen.Sie benötigen noch eine weitere TextBox für den Betreff (Na-me tbSubject) und eine RichTextBox (Name rtbBody) für den In-halt der Nachricht. Für statische Texte verwenden Sie dasSteuerelement Label.

In der Bearbeitungsmethode btSend_Click rufen Sie die nochzu 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 =

Page 218: Otmar Ganahl - C Sharp

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() prüfen Sie erst die Eingabefelder.Wenn eines leer ist, wird eine entsprechende MessageBox er-zeugt und dann der Eingabefokus auf das leere Steuerelementgelegt.

Sind alle Eingabe vorhanden, wird die E-Mail abgeschickt (Siekennen den Code wahrscheinlich aus Kapitel 2: C# – die neueProgrammiersprache). Vergessen Sie auch nicht, einen gültigenSMTP-Server in Ihrem Code anzugeben. Damit Ihr Projekt auchfehlerfrei kompiliert, müssen Sie auch einen Verweis auf dasAssembly System.Web.dll herstellen!

ZusammenfassungNehmen Sie sich Zeit, den Designer gut kennen zu lernen.Wenn Sie diesen beherrschen, dann sind Sie äußerst produktiv,wenn es um die Programmierung von Oberflächen geht. Ma-chen Sie sich die Zusammenhänge zwischen Designer undQuellcode bewusst und üben Sie die wichtigen Schritte. Nocheinmal die wesentlichen Features des Designers

Der Designer manipuliert die Methode InitializeCom-ponent einer Klasse abgeleitet vom Typ Form.Mit dem Designer können Steuerelemente, Menüs,Werkzeugleisten usw. in grafischer Form einer Fens-terklasse zugeordnet werden.Im Designermodus erscheint ein Eigenschaftsfenster,das die Eigenschaften des gerade markierten Elemen-tes im Designer anzeigt. In diesem Eigenschaftsfens-ter können Sie einen Namen (Member) für dasSteuerelement festlegen sowie sämtliche Propertiesund Members der Elemente belegen. Neben Properties

Page 219: Otmar Ganahl - C Sharp

Windows-Applikationen2196

und Members lassen sich im Eigenschaftsfenster auchdie Events der Steuerelemente anzeigen und entspre-chende Behandlungsroutinen für die jeweiligen Eventserzeugen.

In den folgenden Beispielen werden Sie die Arbeit mit dem De-signer üben.

MenüDem SimpleEmail-Beispiel werden Sie nun noch eine Menülistezufügen, nämlich einen Menüeintrag Extra mit zwei Unter-menüeinträgen Senden und Alles Löschen.

Mit dem Designer können Sie einer Fensterklasse eine Menü-leiste hinzufügen. In der Werkzeugleiste findet sich hierzu dasSteuerelement MainMenu. Wechseln Sie zum Designer und fü-gen per Drag&Drop ein Hauptmenü hinzu. Im Eigenschafts-fenster geben Sie diesem Menü den Namen mMainMenu.

Die Menüeinträge können nun direkt in grafischer Form getä-tigt werden. Fügen Sie nun ein Hauptmenü &Extras mit denUntermenüeinträgen &Senden und &Alles Löschen hinzu.

Menüeinträge hinzufügen

Abb. 6.13

Page 220: Otmar Ganahl - C Sharp

C #

220 6

Markieren Sie dann den Menüeintrag Alles Löschen. Im Eigen-schaftsfenster geben Sie diesem Eintrag den Namen miClear-All (menu item), schalten dann die Ansicht des Eigenschafts-fensters auf Ereignisse (Events) um und fügen diesemMenüeintrag (Item) eine Behandlungsmethode zum EventClick hinzu. In dieser Routine belegen Sie sämtliche Text-Pro-perties mit einem Leerstring.

CD-BeispielSimpleEmail2

private void miClearAll_Click( object sender, System.EventArgs e){ tbAddress.Text=""; tbSubject.Text=""; rtbBody.Text = "";}

Markieren Sie nun den Menüeintrag Senden und geben diesemEintrag (Item) den Namen miSend. Für diesen Eintrag erzeugenSie sich aber nicht eine eigene Behandlungsroutine, sondernverwenden dieselbe, die auch der Button btSend verwendet.Sie können dies über das Eigenschaftsfenster durchführen, in-dem Sie die ComboBox beim Event Click öffnen. Diese listetsämtliche, schon vorhandenen Behandlungsroutinen der Steu-erelemente auf. Hier wählen Sie nun die BehandlungsroutinebtSend_Click aus.

Eine vorhandene Routinezuordnen

Abb. 6.14

Page 221: Otmar Ganahl - C Sharp

Windows-Applikationen2216

KontextmenuAuch Kontextmenüs lassen sich mit dem Designer erzeugen.Per Drag&Drop holen Sie sich von der Werkzeugleiste ein Steu-erelement vom Typ ContextMenu, geben diesem im Eigen-schaftsfenster den Namen cmExtras und fügen dem Menü imDesigner ebenfalls die Menüeinträge Senden und Alles Löschenhinzu. Diese Item-Einträge erhalten die Namen cmiSend undcmiClearAll (context menu item). Schalten Sie dann das Eigen-schaftsfenster auf die Ereignisansicht um und fügen Sie denEinträgen Behandlungsroutinen hinzu. Sie können natürlichdieselben Routinen zuweisen, die Sie für die Einträge desHauptmenüs verwendet haben.

Das Kontextmenü soll geöffnet werden, wenn Sie im RichText-Box-Steuerelement rtbBody die rechte Maustaste betätigen.Daher im Designer das Steuerelement rtbBody markieren undim Eigenschaftsfenster eine Behandlungsroutine für das EventMouseDown hinzufügen. Dieses füllen Sie dann mit folgen-dem 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 KlasseMouseEventArgs. Sie prüfen erst, ob auch die rechte Maustastebetätigt wurde. Ist dies der Fall, dann rufen Sie die MethodeShow der Klasse ContextMenu auf, mit Angabe des Steuerele-mentes, das mit dem Kontextmenü verbunden sein soll undder Position des Kontextmenüs. Die momentane Cursorpositi-on können Sie sich über das X- und Y-Property des Objektesvom Typ MouseEventArgs holen.

Page 222: Otmar Ganahl - C Sharp

C #

222 6

WerkzeugleisteDem Beispiel SimpleEmail fehlt noch eine Werkzeugleiste.Auch diese fügen Sie am besten über den Designer ein. Wech-seln Sie wieder zur Entwurfsansicht (Designer) und fügen Sienun per Drag&Drop eine ToolBar aus der Werkzeugleiste hin-zu. Die ToolBar wird dann sofort in das Fenster eingeblendet.Es kann sein, dass Sie die schon vorhandenen Steuerelementeumpositionieren müssen, weil die ToolBar doch einigen Platzbeansprucht. Markieren Sie diese ToolBar und geben Sie dieserim Eigenschaftsfenster den Namen toolbMain. Dieser ToolBarmüssen Sie nun Buttons mit einem Bitmap zuordnen.

Die Bitmaps der Buttons werden in einem eigenen Objekt vomTyp ImageList gehalten. Dazu holen Sie sich von der Werk-zeugleiste ein Steuerelement vom Typ ImageList und gebendiesem auch den Namen ImageList. Im Eigenschaftsfenster se-hen Sie dann, dass dieses Objekt eine Eigenschaft Images (inForm einer Auflistung) besitzt. Sie können hier einen Dialogöffnen, der Ihnen erlaubt, Bilder, Icons usw. dieser ImageListhinzuzufügen.

Bilder einer ImageListhinzufügen

Abb. 6.15

Page 223: Otmar Ganahl - C Sharp

Windows-Applikationen2236

Mit Add navigieren Sie dann zu einer entsprechenden Bildda-tei. Sie können natürlich auch eine eigene Bilddateien erstel-len. Brechen Sie also den Vorgang ab, und erstellen Sie zuerstzwei neue Bilddateien für die Buttons der ToolBar. Über denMenübefehl Projekt > Neues Element hinzufügen > Bitmapdateikönnen Sie ein neues Bitmap erzeugen.

Geben Sie dem ersten Bitmap den Namen Send.bmp und las-sen Sie dann Ihren künstlerischen Fähigkeiten freien Lauf.

Nun generieren Sie in gleicher Weise noch ein Bitmap mit demNamen ClearAll.bmp. Sie können nun diese beiden Bitmaps derImageList im Eigenschaftsfenster des Designers hinzufügen.

Erst jetzt sind Sie soweit, auch tatsächlich Buttons der ToolBarzuzuweisen. Markieren Sie hierzu die ToolBar im Designer undöffnen Sie den Dialog der Eigenschaft Buttons, und fügen Sieper Add einen Button hinzu. Dieser bekommt einen NamentoobbSend und weisen Sie diesem auch gleich das entspre-chende Image aus der ImageList zu. Auch einen Tooltip-Textkönnen Sie zuweisen!

Alsdann fügen Sie einen zweiten Button hinzu, geben diesemden Namen toobbClearAll und weisen diesem das entspre-chende Bitmap zu.

Bitmapdatei erzeugen

Abb. 6.16

Page 224: Otmar Ganahl - C Sharp

C #

224 6

Nun brauchen Sie noch Code, um auf ein Button in der Werk-zeugleiste zu reagieren. Erzeugen Sie eine Bearbeitungsrouti-ne auf das Event ButtonClick und geben Sie dieser Routinefolgende Funktionalität:

CD-BeispielSimpleEmail3

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 Tool-BarButtonClickEventArgs. In diesem steckt eine Referenz aufden aktivierten Button und können über diese in gezeigterForm feststellen, welcher Button aktiviert wurde und reagie-ren dann entsprechend.

Bitmap-Editor

Abb. 6.17

Page 225: Otmar Ganahl - C Sharp

Windows-Applikationen2256

Hier noch einmal die Vorgehensweise zusammengefasst:

Bitmaps für die ToolBar-Buttons erstellenÜber den Designer der Form eine ImageList hinzufü-gen und die Bitmaps in diese Liste mit aufnehmen (inder Member Images)Der Form eine ToolBar zuweisen. Dem Member But-tons über den Designer Buttons hinzufügen und jedemdieser Buttons ein Image aus der ImageList zuweisen.Eventuell auch ein ToolTip vergeben.Eine Behandlungsroutine für das Event ButtonClick derToolBar per Designer erzeugen. In der Behandlungs-routine stellen Sie fest, von welchem ToolBar-Buttondie Nachricht kommt und reagieren entsprechend.

DialogeDialoge sind in der Windows-Programmierung sehr wichtigeElemente, stellen sie doch die Schnittstelle zwischen dem Be-nutzer und der Anwendung dar. Im Win32-Programmiermo-dell sind Dialoge eine relativ komplexe Geschichte. Unter .NETist das Modell schlüssiger und damit einfacher geworden.

Die Klasse Form (Basisklasse aller Fenster) implementiert zweiMethoden, die erlauben, ein Fenster darzustellen:

Show()ShowDialog()

Die Methode Show() öffnet ein Fenster „normal“. Das Fensterin unabhängig von anderen Fenstern. Die Methode ShowDia-log() öffnet das Fenster als Dialog, d.h. die Anwendung ist blo-ckiert, solange der Dialog geöffnet ist.

Im nächsten Beispiel werden Sie eine kleine Applikation schrei-ben, die es Ihnen erlaubt, Bilder unterschiedlichen Formats an-zuzeigen. Über einen Datei-Öffnen-Dialog kann eine Bilddateiausgesucht werden, die dann im Fenster dargestellt wird. Übereinen weiteren Dialog können Dateifilter (*.jpg, *.bmp, *.gif)eingestellt werden, damit dann im Datei-Öffnen-Dialog nurdie entsprechenden Dateien zur Auswahl bereit gestellt wer-den.

Page 226: Otmar Ganahl - C Sharp

C #

226 6

Dazu legen Sie ein neues Windows-Projekt mit dem NamenImageViewer an. Benennen Sie die vom Assistenten erzeugteDatei Form1.cs in App.cs um, und wechseln Sie in den Designer-Modus. Markieren Sie das Hauptfenster und geben Sie diesemden Namen MainWnd statt den Namen Form1. Per Drag&Dropfügen Sie nun ein Steuerelement PictureBox hinzu und gebendiesem im Eigenschaftsfenster den Namen Picture. Das Fens-ter erhält dann noch eine Menüleiste mit einem Eintrag Datei> Öffnen (miOpen) und einem Eintrag Extras > Dateifilter (miFil-ter), mit entsprechenden Bearbeitungsroutinen. In der Bear-beitungsroutine für miFilter soll folgender Dialog geöffnetwerden.

Dazu fügen Sie dem Projekt eine neue Leere C#-Quellcodeda-tei hinzu (Projekt > Neues Element hinzufügen > Codedatei) undgeben diesem den Namen FilterDialog.cs. In dieser Datei wer-den Sie eine neue Fensterklasse anlegen.

CD-BeispielImageViewer

using System;using System.Windows.Forms;

public class FilterDialog:Form{

public FilterDialog(){

ImageViewer mitFilterdialog

Abb. 6.18

Page 227: Otmar Ganahl - C Sharp

Windows-Applikationen2276

InitializeComponent();}

}

Da die Datei nun eine Fensterklasse definiert, können Sie auchden Designer verwenden. Fügen Sie dem Fenster nun zweiButtons btOK und btCancel hinzu (mit den Texten „OK“ und„Abbrechen“) sowie drei CheckBoxen entsprechend Abbildung6.18. Für die Namen der CheckBoxen wählen Sie cbJPG, cbBMPund cbGIF.

Wenn Sie nun wieder in die Quellcodeansicht wechseln, wer-den Sie feststellen, dass der Designer der Klasse FilterDialogdie Methode InitializeComponent hinzugefügt hat.

Die Klasse FilterDialog versehen Sie nun noch mit drei Proper-ties. Über diese Properties können die Zustände der CheckBo-xen 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 benötigen Sie noch für die beiden Buttons btOK und bt-Cancel Bearbeitungsroutinen, die Sie natürlich mit dem Desig-ner erstellen.

private void btOK_Click( object sender, System.EventArgs e){ this.DialogResult = DialogResult.OK;}

Page 228: Otmar Ganahl - C Sharp

C #

228 6

private void btCancel_Click( object sender, System.EventArgs e){ this.DialogResult = DialogResult.Cancel;}

In diesen Behandlungsroutinen setzen Sie die Member-Variab-le DialogResult. Diese wurde von der Basisklasse Form geerbt.Es stehen Ihnen mehrere Enumerationswerte aus dem Na-mensraum DialogResult zur Verfügung, unter anderem Dialog-Result.OK und DialogResult.Cancel. Welche Bedeutung dieserSchritt hat, wird Ihnen im Folgenden deutlich werden. Damithaben Sie den Dialog fertig entwickelt.

In der Klasse MainWindow fügen 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 Menüeintra-ges miFilter geöffnet 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 Fil-terDialog mit dem Namen Dlg erzeugt. Anschließend werdendie Werte der CheckBoxen über die Properties mit den boole-schen Werten der Members belegt, die Sie gerade angelegt ha-ben.

Page 229: Otmar Ganahl - C Sharp

Windows-Applikationen2296

Erst jetzt rufen Sie die Methode ShowDialog auf. Damit wirddas entsprechende Fenster im Dialogmodus geöffnet. WennSie nun eine CheckBox ändern und OK drücken, wird das Pro-perty DialogResult belegt, und dies hat zur Folge, dass der Dia-log auch geschlossen wird. Die Methode ShowDialog kehrtzurück und gibt auch gleich den Wert von DialogResult alsRückgabewert mit. Im Code der aufrufenden Methode wirddieser Rückgabewert auf DialogResult.OK geprüft und in die-sem Fall werden nun die CheckBox-Werte ausgelesen und denMember-Variablen der Klasse MainWnd zugewiesen.

In gleicher Weise könnten Sie nun einen Dialog schreiben, umeine Datei auszuwählen. Aber einen solchen Dialog gibt esschon im Namensraum System.Windows.Forms in Form derKlasse OpenFileDialog. In der Behandlungsroutine für miOpenwird 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. Filterkann mit einem Formatstring versehen werden.

Page 230: Otmar Ganahl - C Sharp

C #

230 6

Ein Beispiel:

"Bilddateien | *.gif;*.bmp;"

Dieser String als Filterstring würde im Dialog nur die Dateienvom Typ *.gif und *.bmp auflisten. Zusätzlich käme noch die In-formation 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 Abhängigkeit der drei booleschenMember-Variablen zusammengestellt. Der Aufruf des Dialogserfolgt, wie gehabt, mit der Methode ShowDialog(). NachRückkehr der Methode kann über das Member Filename derKlasse OpenFileDialog auf den ausgewählten Dateinamen zu-gegriffen werden.

Mit der statischen Methode FromFile der Klasse Image, kannaus einem Dateinamen ein Objekt vom Typ Image gebildetwerden, das dann dem Steuerelement vom Typ PictureBox imProperty Image zugewiesen werden kann.

Tipp Sie können eine neue Fensterklasse auch über den MenüpunktProjekte > Neues Element hinzufügen > Windows Form erstellenlassen! Der Assistent erzeugt dann den Rahmencode für eineKlasse, die von Form abgeleitet ist.

Kundenspezifische SteuerelementeWenn Sie eigene Steuerelemente entwickeln möchten, danngibt es grundsätzlich zwei Möglichkeiten: Erweitern Sie ein be-stehendes Steuerelement oder aber entwickeln Sie ein voll-kommen neues Steuerelement.

Erweitern eines SteuerelementsStellen Sie sich vor, Sie brauchen für Ihre Anwendung einenspeziellen Button, der zwei Zustände annehmen kann (ähnlicheinem Options-Button). Außerdem soll die Hintergrundfarbevom Zustand des Buttons abhängig sein. Dann könnten Siewie folgt vorgehen.

Page 231: Otmar Ganahl - C Sharp

Windows-Applikationen2316

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. Da-mit erben sie sämtliche Funktionalität. Zusätzlich erhält dasSteuerelement zwei Member-Variablen ColorOn und ColorOfffür die zustandsabhängige Farbe und ein Property State, dasdie Hintergrundfarbe des Buttons abhängig vom Zustandsteuert. Im Konstruktor werden Default-Farben für die Zustän-de zugeordnet und ein Grundzustand eingerichtet. Ebenfallsmuss auf das Click-Ereignis reagiert werden. Bei jedem Clicksollte sich der Zustand ändern (und damit auch die Farbe). Dieswird in der Methode OnClick durchgeführt.

Page 232: Otmar Ganahl - C Sharp

C #

232 6

Erstellen Sie ein Leeres Projekt, fügen diesem eine C#-Quell-code-Datei hinzu und geben Sie folgenden Code ein:

CD-BeispielCustomControl1

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

class SwitchButton:Button{ // ... entsprechend obigem Code}

class MainWnd:Form{ void OnClick(object s, EventArgs e) { MessageBox.Show("Button gedrückt"); } 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 be-nötigen, dann bietet es sich an, dieses in einer eigenen Assem-bly unterzubringen.

Page 233: Otmar Ganahl - C Sharp

Windows-Applikationen2336

Entwicklung eines neuen SteuerelementsVisual Studio.NET bietet zur Entwicklung eigener Steuerele-mente eine spezielle Projektvorlage Windows-Steuerelement-bibliothek an. Diese Vorlage erzeugt ein Assembly! Im Beispielwerden Sie nun ein Steuerelement Photo erstellen. Diesemkann ein Bild, ein Titel und das Aufnahmedatum zugeordnetwerden. Das Steuerelement stellt das Foto dann mit dem Titeldar. Ein Doppelklick auf das Foto öffnet einen Dialog, und gibtdas Aufnahmedatum preis.

Erstellen Sie ein neues Projekt mit dieser Vorlage und nennenSie das Projekt CustomControls. Geben Sie der DateiUserControl1 einen neuen Namen Photo.cs. Ändern Sie imQuellcode auch den Klassennamen auf Photo ab. Im Designerfügen Sie nun eine PictureBox (Name: Pic) und ein Label (Na-me: lTitle) hinzu. Die Position dieser eingebetteten Steuerele-mente ist nicht tragisch, da diese später dynamisch gesetztwerden. Sie sehen, hier wird ein Steuerelement erzeugt, dasselbst Steuerelemente einbettet.

Fügen Sie nun mit dem Designer eine Behandlungsroutine fürdas Ereignis(Event) SizeChanged hinzu (Achtung: nicht für dieeingebetteten Steuerelemente Pic und lTitle!). Ebenfalls fügenSie eine Behandlungsroutine für das Ereignis Paint hinzu.

Ändern Sie auch noch die Eigenschaft SizeMode des eingebet-teten Steuerelements Pic auf StretchImage ab. Wenn der An-wender auf das Steuerelement Pic einen Doppelklick ausführt,dann sollte auf das Ereignis mit einer MessageBox reagiertwerden. Also brauchen Sie noch eine Behandlungsroutine fürdas Ereignis DoubleClick des eingebetteten SteuerelementsPic.

Kundenspezifisches Steuerelement-Photo

Abb. 6.19

Page 234: Otmar Ganahl - C Sharp

C #

234 6

Fügen Sie im Quellcode der Klasse Photo die Properties Picture,Date und Title hinzu. Picture und Title manipulieren die ent-sprechenden Steuerelemente, Date wird in der Member _Dateabgespeichert.

CD-BeispielCustomControl2

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 Größe dereingebetteten Steuerelemente in Abhängigkeit der Größe desControls errechnet und zugeordnet. Im Member ClientSize ha-ben Sie die Größe des Photo-Steuerelements zur Verfügung.

private void Photo_SizeChanged(object sender, System.EventArgs e){ //Größe der PictureBox und Titellabel einstellen //Mit Berücksichtung 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 Randum das Steuerelement. Dazu mehr im nächsten Abschnitt.

Page 235: Otmar Ganahl - C Sharp

Windows-Applikationen2356

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 Steuerele-ment PictureBox) reagieren Sie mit einer MessageBox, die denTitel des Fotos sowie das Aufnahmedatum wiedergibt.

private void Pic_DoubleClick(object sender, System.EventArgs e){ MessageBox.Show("Aufnahmezeit: "+ Date.ToLongDateString(),Title);}

Wenn alles ohne Fehler kompiliert, wird ein neues Assemblymit diesem Steuerelement erzeugt. Testen Sie dieses nun in ei-nem Windows-Programm. Dafür legen Sie ein neues ProjektPhotoTest (Windows-Anwendung) an. Wenn sich das Steuere-lemente-Projekt und dieses Test-Projekt in derselben Projekt-mappe befinden, dann sehen Sie Ihr neu erstelltes Steuerele-ment sogar in der Werkzeugleiste unter Windows Forms. Siekönnen nun per Drag&Drop Steuerelemente vom Typ Photoder Form hinzufügen. Wenn Sie nun einen Blick auf das Eigen-schaftsfenster des Photo-Steuerelements werfen, dann fällt

Photo-Steuerelement im Eigenschaftsfenster des Designers

Abb. 6.20

Page 236: Otmar Ganahl - C Sharp

C #

236 6

Ihnen auf, dass sämtliche Properties, die Sie dem Steuerele-ment vergeben haben, konfigurierbar sind.

ZusammenfassungUnter .NET lassen sich sehr leicht kundenspezifische Steuerele-mente erstellen. Die Möglichkeit der Vererbung erlaubt auchkundenspezifische Anpassungen und Erweiterungen von be-stehenden Steuerelementen. Nutzen Sie dieses Feature gut,Sie können damit Ihre Programme deutlich aufwerten.

GDI+Microsoft fasst alle C-API-Funktionen, die mit der grafischenDarstellung auf dem Bildschirm oder anderen Ausgabegeräten(Drucker, Plotter ...) zu tun haben, unter dem Namen GDI (Gra-phic Device Interface) zusammen. Auch .NET bietet eineSchnittstelle zu den grafischen Ausgaberäten an und nenntdiese GDI+. Die GDI+-Klassen befinden sich in den Namensräu-men System.Drawing und System.Drawing2D. Wenn Sie mitder GDI unter der Win32 API oder MFC/ATL schon program-miert haben, werden Sie feststellen, dass ein Großteil der .NETGDI+-Klassen die Win32-GDI-Befehle kapselt. Sie werden auchhier die Begriffe wie Pen, Brush, Bitmap, Size, Rectangle usw.wiederfinden.

Sie finden in diesen Namensräumen Klassen für Fonts,Bitmap-Manipulationen, Cursor und Icons, Klassen für dasZeichnen von Objekten wie Linien, Ellipsen, Rechtecke, Klassenfür Punkte, Größenangaben, Farben sowie Klassen für GDI-Ob-jekte wie Stifte (Pen), Pinsel(Brush) usw.

Darüber hinaus wurden bei der Entwicklung dieser Klassen mitvielen, für den Programmierer lästigen Unzulänglichkeitenaufgeräumt. Nur ein Beispiel dazu: Ein Stift (Pen) unter Win32ist ein GDI-Objekt, das eine Farbe und eine Strichstärke besitzt.Mit diesem Stift können Linien, Rechtecke usw. mit der ent-sprechenden Farbe und Strichstärke gezeichnet werden. Willman nun aber Linien mit anderen Farben und/oder andererStrichstärke zeichnen, so muss ein neuer Stift mit diesen Ei-genschaften erzeugt werden. Es ist nicht möglich, dem vor-handenen Stift eine neue Farbe zuzuordnen. Das Neuerzeugen

Page 237: Otmar Ganahl - C Sharp

Windows-Applikationen2376

ist nichts Besonderes, aber lästig, und da die GDI-Objekte auchwieder aufgelöst werden mussten, war die Verwaltung bei vie-len GDI-Objekten äußerst fehleranfällig.

Unter .NET wird eine Klasse Pen (Stift) angeboten, die es er-laubt, bei Instanzen vom Typ Pen die Farbe bzw. Strichstärkejederzeit zu ändern, ohne eine neue Pen-Instanz zu erzeugen.Intern aber wird sehr wohl ein „neuer“ Win32-Pen mit denneuen Eigenschaften erzeugt und der „alte“ Pen aufgelöst.Dies wird aber alles in der Klasse versteckt und vereinfacht dieHandhabung enorm.

Das ist nur ein Beispiel von vielen. Mit GDI+ wird hier eineSchnittstelle angeboten, die objektorientiert ist und weil sielogischer auch einfacher zu verwenden ist.

Sie werden nun einen Teil der Funktionalität der GDI+ kennenlernen, aber wenn Sie die Grundprinzipien kennen, ist es einLeichtes, die weiteren Features intuitiv zu erfahren. Am bestenSie experimentieren unter Verwendung der Online Hilfe!

Pen und BrushViele Methoden, wie z.B. DrawLine (Zeichnen einer Linie) oderDrawEllipse (Zeichnen einer Ellipse) haben als Paramater auchdie Angabe eines Stiftes. Ein Stift besteht aus einer Farbe undeiner Strichstärke.

Pen p = new Pen(Color.Beige,20);

Einige Methoden, wie z.B. FillEllipse haben eine Füllfarbe(Brush, zu deutsch Pinsel) als Übergabeparameter. Ein Solid-Brush hat eine Farbe als Eigenschaft – im Gegensatz zu einemmöglichen Füllmuster.

SolidBrush b = new SolidBrush(Color.Azure);

Pen und SolidBrush sind Klassen im Namensraum System.Dra-wing und befinden sich im Assembly System.Drawing.dll.

FontDie Klasse Font kapselt eine Schriftart. Sie ist ebenfalls im Na-mensraum System.Drawing zu finden. Zwei wichtige Eigen-

Page 238: Otmar Ganahl - C Sharp

C #

238 6

schaften dieser Klasse sind die Schriftart selbst und dieSchriftgröße.

Font f = new Font("Arial",20);

Point, PointFPoint bzw. PointF sind Strukturen, die die Koordinaten eineszweidimensionalen Punktes halten. Point hält die Koordinatenin Int32-Typen, während PointF die Koordinaten als float-Ty-pen hält. Über die Eigenschaften X und Y kann auf die Koordi-naten zugegriffen werden.

Rectangle, RectangleFStrukturen, die die Daten eines Rechteckes in Int32 bzw. floathalten. Die Eigenschaften sind:

X x Wert des linken, oberen Punktes

Y y Wert des linken, oberen Punktes

Width Breite des Rechteckes

Length Höhe des Rechteckes

Size, SizeFStrukturen, ähnlich den Rectangle bzw. RectangleF, haben abernur die Eigenschaften Width und Length.

Die Klasse GraphicsDie Klasse Graphics aus dem Namensraum System.Drawingenthält sämtliche grafischen Methoden wie DrawLine, FillEllip-se, DrawEllipse usw. Wenn Sie ein erfahrener Windows-Pro-grammierer sind und hinter der Klasse Graphics die Kapselungdes Gerätekontextes sehen, so liegen Sie nicht falsch.

OnPaint-MethodeDie Klasse Form besitzt eine virtuelle Methode OnPaint(). DieseMethode wird aufgerufen, wenn sich ein Fenster neu zeichnenmuss. Das Betriebssystem legt eine Nachricht WM_PAINT in

Page 239: Otmar Ganahl - C Sharp

Windows-Applikationen2396

die Nachrichtenschlangen, wenn ein Fenster ungültig gewor-den ist. Dies ist immer dann der Fall, wenn ein überlappendesFenster von einem Fenster weggeschoben wird, wenn einMenübalken wieder verschwindet, wenn das Fenster erzeugtwird oder wenn die Größe verändert wird usw. Der Program-mierer kann auch ein Fenster explizit für ungültig erklären,was dann zur Folge hat, dass das Betriebssystem WM_PAINT indie Nachrichtenschlange legt und wenn ein Nachrichtenbear-beiter auf WM_PAINT existiert, wird dieser ausgeführt.

Die Methode OnPaint() der Klasse Form ist genau dieser Nach-richtenbearbeiter. In der Methode OnPaint() wird und muss diegesamte grafische Funktionalität programmiert werden. Er-stellen Sie dazu ein Leeres Projekt mit dem Namen GDI und fü-gen Sie diesem eine Leere C#-Quelldatei hinzu. In dieser wirdeine Fensterklasse MainWnd mit der Methode OnPaint defi-niert. Die Klasse App implementiert die Methode Main, die einFenster MainWnd öffnet. Schalten Sie im Eigenschaftsdialogdes Projektes den Ausgabetyp auf Windows-Anwendung um!In den folgenden Experimenten werden Sie sich hauptsächlichin 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 Formin folgender Form:

Page 240: Otmar Ganahl - C Sharp

C #

240 6

CD-BeispielGDI1

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); }

In der Methode OnPaint werden erst einmal die GDI-Objekte,ein roter Stift (Pen) mit einer Strichstärke von 3, ein Pinsel (So-lidBrush) mit der Farbe Beige und eine Schriftart (Font) vomTyp Arial mit einer Schriftgröße von 20 erzeugt. Dann wird ge-zeichnet. Ein Objekt vom Typ Graphics erhalten Sie über den

Zeichnen mit der GDI+

Abb. 6.21

Page 241: Otmar Ganahl - C Sharp

Windows-Applikationen2416

Übergabeparameter e vom Typ PaintArgs und können damitsämtliche Zeichenfunktionen aufrufen. Beachten Sie das un-terschiedliche Verhalten der Methoden DrawEllipse und FillEl-lipse. DrawEllipse hat einen Pen als Parameter, FillEllipse einenBrush, der die Füllfarbe der Ellipse definiert. Zur Textausgabeverwenden Sie hier DrawString, das ebenfalls einen Brush alsÜbergabeparameter erhält, und damit die Farbe des Fonts be-stimmt wird.

Experimentieren Sie auch mit anderen Zeichenmethoden derKlasse Graphics!

KoordinatensystemeAlle Koordinatenangaben sind bisher in Pixelkoordinaten an-gegeben worden. GDI+ kennt drei Koordinatensysteme:

WorldPageDevice

Die Koordinatenangabe bei den Drawing-Methoden werdenimmer als Welt (World)-Koordinaten interpretiert. Bis dieseaber tatsächlich beim Bildschirm (Device) gezeichnet werden,erfahren diese zuerst eine Transformation in Page-Koordina-ten und diese dann in die Gerätekoordinaten (Device).

Achsen des Koordinatensystems

Abb. 6.22

Page 242: Otmar Ganahl - C Sharp

C #

242 6

Betrachten Sie zuerst die Gerätekoordinaten anhand des „Ge-rätes“ Bildschirm. Der Bildschirm ist pixelorientiert, und daherist die Einheit des Gerätekoordinatensystems Pixel. Die Rich-tungen der x- und y-Achse ersehen Sie aus Abbildung 6.22.

Auch World-Koordinaten sind nicht schwer zu verstehen. Dassind die tatsächlichen, realen Abmessungen in einem be-stimmten Längenmaß z.B. m oder inch.

Beim Page-Koordinatensystem denken Sie am besten an einenPlan (darum auch der Name Page) mit einem bestimmtenMaßstab. Wenn Sie also z.B. ein Gebäude auf dem Bildschirmabbilden möchten, dann entscheiden Sie sich zuerst für einenMaßstab z.B. 1:100 (denn es ist offensichtlich, dass man ein Ge-bäude nicht im Maßstab 1:1 auf den Bildschirm bringen kön-nen). Die Weltkoordinaten werden dann zuerst auf 1:100gestaucht, d.h. auf einem Blatt (Page) wird 1m genau 1cm ent-sprechen. Auf dem Bildschirm können allerdings nur Pixel ge-zeichnet werden, und diese können von Gerät zu Gerätverschieden sein. Bei einem Gerät kann 1cm mehr Pixel bedeu-ten (z.B. 600dpi-Drucker), bei einem anderen Gerät deutlichweniger (z.B. Bildschirm mit 800x600-Auflösung). Die Aufga-be der Übersetzung von Page-Koordinaten in Gerätekoordina-ten ist also geräteabhängig und wird im Allgemeinen vomjeweiligen Gerätetreiber übernommen. Es ist also nicht dieAufgabe des Programmierers, diese Transformation durchzu-führen.

Experimentieren Sie:

CD-BeispielGDI2

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) {

Page 243: Otmar Ganahl - C Sharp

Windows-Applikationen2436

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 ei-ner 50er Teilung. Da das Transformationsverhältnis zwischenWorld und Page 1:1 und zwischen Page und Device ebenfalls 1:1ist, entsprechen die angegebenen Koordinaten genau den Pi-xelkoordinaten. Das ist die Grundeinstellung.

Ändern Sie zuerst die Transformation zwischen Page und Pixel,indem Sie die Eigenschaft PageUnit des Graphics-Objektes aufden Wert GraphicsUnit.Millimeter setzen.

CD-BeispielGDI3

Graphics g = e.Graphics;g.PageUnit = GraphicsUnit.Millimeter;

Ab sofort werden sämtliche Koordinatenangaben in den Me-thoden nicht mehr als Pixel interpretiert, sondern als Angabenin mm.

Sie werden nun erkennen, dass sämtliche Koordinatenanga-ben nun in mm interpretiert werden. Für PageUnit sind auchnoch folgende weitere Enumerationen möglich:

GraphicsUnit.Inch inchGraphicsUnit.Pixel Pixel (Grundeinstellung)GraphicsUnit.Display (1/75 inch)GraphicsUnit.Point (1/72 inch)GraphicsUnit.Document (1/300 inch)

Sie sehen auch, dass bei der Verwendung von mm bzw. inchdie Verwendung von ganzzahligen Koordinatengrößen nichtmehr ausreichend ist, da ja ansonsten keine Größen kleiner als1mm bzw. 1 inch gezeichnet werden können. Dies ist derGrund, dass GDI+ auch Koordinaten vom Typ float zulässt.Wenn Sie das durchführen, dann schaut der Code nun wiefolgt aus:

Page 244: Otmar Ganahl - C Sharp

C #

244 6

CD-BeispielGDI4

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. DieZeichenmethoden besitzen alle überlagerte Varianten, dieauch Fließkommatypen akzeptierten. Beachten Sie, dass beider Angabe einer konstanten float-Zahl F bzw. f folgen muss!

Die Übersetzung von Weltkoordinaten in Page- Koordinatenwird durch eine (3x3)-Transformationsmatrix festgelegt. Damiterhalten Sie sämtliche Möglichkeiten des Verschiebens, Stau-chens und Streckens und auch Drehens des Koordinatensys-tems.

CD-BeispielGDI5

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.Dra-wing.Drawing2D. Geben Sie diesen Namensraum frei, damitSie den Typ verwenden können.

Page 245: Otmar Ganahl - C Sharp

Windows-Applikationen2456

m12 m12 0

m21 m21 0

dx dy 1

Der Konstruktor von Matrix erlaubt die Belegung der Matrix inder Reihenfolge: m11,m12,m21,m22,dx, dy. Die dritte Spalte istimmer konstant (0,0,1). Im Folgenden sehen Sie einige ausge-wählte Beispiele für Transformationsmatrizen:

Einheitsmatrix1 0 0

0 1 0

0 0 0

Ursprung wird auf der x-Achse um 10 und auf der y-Achse um 20 verschoben1 0 0

0 1 0

10 20 0

Ursprung wird auf der x-Achse um 10 und auf der y-Achse um 20 verschoben. Die x-Achse als auch die y-Achse werden um die Hälfte gestaucht.0.5 0 0

0 0.5 0

10 20 0

Ursprung wird auf der x-Achse um 10 und auf der y-Achse um 20 verschoben. Das Koordinatensystemwird auch noch um 45° entgegen dem Uhrzeigersinngedreht.1 1 0

-1 1 0

10 20 0

Page 246: Otmar Ganahl - C Sharp

C #

246 6

Die Klasse Graphics besitzt auch Methode, über die die Trans-formationsmatrix 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 dyder Transformationsmatrix und bestimmt somit die Verschie-bung des Koordinatenursprungs.

RotateTransform manipuliert die Werte m11, m12, m21, m22 umdie entsprechende Rotation des Koordinatensystems um denangegebenen Winkel in Grad zu bewirken und ScaleTransformübernimmt die Skalierung (Stauchen bzw. Strecken) der x- alsauch der y-Achse.

Hier noch einmal die Vorgehensweise zusammengefasst:

Definieren Sie eine Maßeinheit auf dem Bildschirm(PageUnit). Damit wird Ihre Ausgabe unabhängig vomAusgabegerät.Definieren Sie einen Maßstab über die Transformati-onsmatrix.Sie können nun sämtliche Koordinaten in Form vonWelt-Koordinaten (tatsächliche Größe) angeben. GDI+wird zuerst eine Transformation durchführen unddann auch noch dafür sorgen, dass die Ausgabe gerä-teunabhängig wird.

Experimentieren Sie mit diesen Funktionen. So lernen Sie denUmgang am schnellsten.

Bilder, Bitmaps und ImagesDie Handhabung von Bildern ist mit der Win32-API eine halbeDoktorarbeit. .NET bietet Klassen an, die die Verwendung vonBildern in unterschiedlichen Formaten deutlich vereinfacht.Die wichtigste Klasse ist die Klasse Image aus dem Namens-

Page 247: Otmar Ganahl - C Sharp

Windows-Applikationen2476

raum System.Drawing. Eine statische Funktion FromFile er-laubt das Belegen eines Objektes. Es werden so ziemlich allewichtigen Grafikformate akzeptiert.

Image im = Image.FromFile(@"c:\Bild.jpg");

Mit der Methode DrawImage des Graphics-Objektes gestaltetsich das Zeichnen des Bildes auf dem Bildschirm sehr einfach.Sie geben die linke obere Ecke, die Breite und die Höhe als Pa-rameter mit. Das Bild wird dann in diesem Bereich angepasst.Sämtliche Transformationen, wie Sie es aus dem vorherigenKapitel 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));

Page 248: Otmar Ganahl - C Sharp

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 alsFeld der Klasse MainWnd angelegt und im Konstruktor derKlasse durch die statische Methode FromFile und Angabe desDateinamens initialisiert. In der virtuellen Methode OnPaintwird dann DrawImage mit als Übergabeparameter aufgeru-fen. Achten Sie darauf, dass Sie die Datei beispiel.jpg auch exis-tiert!

Experimentieren Sie auch mit anderen Grafikformaten!

RessourcenEs macht viel Sinn, Daten, die nicht unmittelbar mit der Pro-grammlogik zu tun haben, separat zu verwalten. Werden z.B.Texte einer Anwendung separat verwaltet, dann kann die An-wendung in eine andere Sprache portiert werden, ohne dassman den eigentlichen Code anrühren muss. Dies gilt abernicht nur für Texte, sondern kann auf Menüeinträge, Erschei-

Bild über GDI+ ausgegeben

Abb. 6.23

Page 249: Otmar Ganahl - C Sharp

Windows-Applikationen2496

nungsbild von Dialogen, Cursor-Icons, Bitmap-Dateien undvieles mehr ausgeweitet werden. Daten dieser Art werden Res-sourcen genannt.

Zur Entwicklungszeit können Ressourcen in eigenen binärenDateien mit der Dateierweiterung .resources gehalten werden.Diese können über AL .EXE (Assembly Linker) in ein Assemblyeingebettet werden. Damit sind diese statischen Daten Be-standteil des Assembly.

.NET bietet mächtige Möglichkeiten an, Ressourcen zu verwal-ten. Mit Visual Studio geht es sogar noch einfacher. Einem Pro-jekt kann über den Menüpunkt Projekt > Vorhandenes Elementhinzufügen... eine Datei mit der Dateierweiterung .resourcehinzugefügt werden. Im Eigenschaftsfenster ändern Sie die Ei-genschaft Buildaktion auf Eingebettete Ressource. Damit wirddie Ressource zum Assembly hinzugelinkt.

Erstellung von Dateien vom Typ .resourceEs gibt prinzipiell mehrere Möglichkeiten, Dateien vom Typ .re-source zu erzeugen, die dann über AL.EXE bzw. Visual Stu-dio.NET in ein Assembly eingebunden werden können.

Datei als Ressource einbinden

Abb. 6.24

Page 250: Otmar Ganahl - C Sharp

C #

250 6

ResGen.exeIn Form einer wohldefinierten XML-Textdatei können Daten,die in binärer Form in die .resource-Datei eingelagert werdensollten, beschrieben werden. Ein eigenes Programm Resgen.exe kann dann aus einer solchen XML-Datei eine Ressourcen-Datei erzeugen.

Wenn Sie das Schema für diese XML-Dateien (Dateierweite-rung .resx) kennen, dann können Sie jederzeit selbst solcheRessourcen-Beschreibungen erstellen und dann mittels Res-gen.exe daraus linkfähige .resource-Dateien erstellen.

Natürlich können Sie sich nun spezielle, auch grafische Edito-ren vorstellen, die die Erzeugung solcher .resx-Dateien nochanwenderfreundlicher gestalten. (Der Designer ist übrigens soein Editor, der auch XML-Ressource-Dateien erzeugt.)

.NET bietet für diejenigen, die solche Editoren basteln möch-ten, folgende Klassen an:

System.Resources.ResXResourceReaderSystem.Resources.ResXResourceWriter

Mit diesen Klassen lassen sich also .resx-Dateien erzeugen,aber auch lesen. Beachten Sie aber bitte ganz bewusst die Un-terschiede zwischen einer .resx-Datei und einer .resource-Da-tei. Die .resx-Datei dient nur dazu, um die Daten in einer fürMenschen lesbaren Form bereitzustellen. Aus der .resx-Dateimuss zuerst mit dem Hilfsprogramm Resgen.exe eine .resour-ce-Datei erzeugt werden, die dann in ein Assembly eingebun-den werden kann.

ResourceWriter und ResourceReader.NET bietet auch Klassen an, die eine direkte Erzeugung vonRessource-Dateien erlauben

System.Resources.ResourceWriterSystem.Resources.ResourceReader

Diese Klassen werden im Allgemeinen nicht verwendet, umprogrammtechnisch Daten aus der Ressource zu lesen, son-dern sind vornehmlich für Entwicklungssystem-Hersteller ge-dacht, die Hilfsprogramme entwickeln möchten, um Ressour-ce-Dateien direkt zu entwickeln. (Es ist anzunehmen, dassResgen.exe diese Klassen benützt.)

Page 251: Otmar Ganahl - C Sharp

Windows-Applikationen2516

Verwenden von Ressource-DateienWenn Sie programmtechnisch auf Daten innerhalb einer Res-source zugreifen möchten, dann bietet sich die Klasse Resour-ceManager an.

Ein erstes Beispiel soll Ihnen das demonstrieren. Angenom-men, in Ihrem Programm möchten Sie eine größere Bilddateiverwenden. Sie wollen diese Bilddatei aber nicht als eigenstän-dige, separate Datei der Anwendung beilegen, sondern die Da-ten der Bilddatei sollen direkt im .exe-Assembly als Ressourceeingebettet sein.

Dazu müssen Sie erst eine Ressource-Datei erstellen. WelcheMöglichkeiten haben Sie dazu?

Erstellen einer .resx-DateiDazu müssen Sie das XML-Schema für .resX-Dateien kennenund dann mit einem beliebigen Editor diese Datei schreiben.Dies ist relativ einfach, wenn die Daten Strings darstellen. Beieiner Bilddatei wird dies schwieriger, weil binäre Daten in dieXML-Datei (in base64-Codierung) gebracht werden müssen.Für eine Bilddatei ist dies nicht anwendbar.

Verwendung eines Ressourcen-EditorsGanz nützlich wäre ein Werkzeug, das eine Ressource-Dateimit eingebetteten Bilddaten unter Angabe einer Bilddatei er-zeugen würde. Visual Studio.NET bietet kein solches Werkzeugan. Aber wie schon erwähnt, bietet .NET Klassen dafür an.Schreiben Sie daher selbst ein solches Werkzeug!

Legen Sie dazu ein neues Projekt (Vorlage Konsolen-Anwen-dung) mit dem Namen RW (ResourceWriter) an und geben Siefolgenden Code ein. Vergessen Sie auch nicht auf das AssemblySystem.Drawing.dll zu verweisen.

CD-BeispielRW

using System;using System.Drawing;using System.Resources;

class App{

Page 252: Otmar Ganahl - C Sharp

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 .resour-ce-Datei mit dem Namen newton.resource. Dazu wird ein Ob-jekt vom Typ ResourceWriter mit Angabe des Dateinamenserzeugt. Diese Klasse besitzt eine dreifach überlagerte Metho-de AddRessource.

void AddResource(string,string);void AddResource(string,byte[]);void AddResource(string,object);

Damit können nun Daten in Form von Schlüssel-Wert-Paarenhinzugefügt werden. Neben Strings können auch Byte-Streamsund sogar beliebige Objekte (soweit diese serialisierbar sind)der Ressource-Datei hinzugefügt werden.

Im Beispiel macht es Sinn, die Bilddatei nicht als Byte-Stream,sondern gleich als Objekt vom Typ Image hinzuzufügen. Daherwird zuerst ein Objekt vom Typ Image aus der Bilddatei er-zeugt, und dann per AddResource mit dem Schlüssel „Bild“ derDatei zugewiesen. Weiter sehen Sie, dass auch ein String„Newton“ (und dem Schlüssel „Bildname“) in die Ressource-Datei mit aufgenommen wird.

Wenn Sie dieses Hilfsprogramm starten, erzeugt es die Res-source-Datei newton.resource, die Sie nun jederzeit in einembeliebigen Assembly einbetten können. (Die im Beispiel ver-wendete Bilddatei finden Sie auf der CD.) Dies wird im nächs-ten kleinen Beispiel demonstriert.

Dazu legen Sie ein neues Projekt (Vorlage Leeres Projekt) mitdem Namen Newton an, fügen dieser eine C#-Codedatei mitdem Namen App.cs zu und ändern die Projekteinstellung aufWindows-Anwendung. Mit dem Menübefehl Projekt > Vorhan-

Page 253: Otmar Ganahl - C Sharp

Windows-Applikationen2536

denes Element hinzufügen... navigieren Sie zur Ressource-Dateinewton.resource und binden diese ein. Im Eigenschaftsfensterder Datei müssen Sie die Eigenschaft BuildAktion auf Eingebet-tete Ressource festlegen. Damit ist gewährleistet, dass beimBuild-Prozess diese Datei auch mitgelinkt wird.

Verweisen Sie dann noch auf die Assemblies System.dll, Sys-tem.Windows.Forms.dll und System.Drawing.dll.

CD-BeispielNewton1

using System;using System.Windows.Forms;using System.Reflection;using System.Resources;using System.Drawing;

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{

Im Projekt zugefügte Ressource-Datei

Abb. 6.25

Page 254: Otmar Ganahl - C Sharp

C #

254 6

public static void Main() { Application.Run(new MainWnd()); }}

Der in diesem Zusammenhang interessante Code befindet sichim Konstruktor. Sie erzeugen hier zuerst den so genannten Re-sourceManager mit Angabe der Ressource, sowie des Assem-bly, in der die Ressource eingebettet ist. Das Assemblyentspricht der Anwendung selbst (daher GetExecutingAssem-bly) und der Name setzt sich aus dem Assembly-Namen unddem Namen der Ressource zusammen. Im speziellen Fall daherNewton.newton. Wenn das geklappt hat, dann können überGetObject bzw. GetString der Klasse ResourceManager mit An-gabe des Schlüssels auf die Daten zugegriffen werden.

Ressource-Datei über .resx-Dateien erstellenMeistens brauchen Sie Ressourcen für statische Texte. Hier un-terstützt Sie das Entwicklungssystem besser, als bei der Ein-bindung von beliebigen Objekten. Fügen Sie der Applikationüber Projekt > Neues Element hinzufügen... eine Assembly-Res-sourcen-Datei hinzu.

Newton.exe in Aktion

Abb. 6.26

Page 255: Otmar Ganahl - C Sharp

Windows-Applikationen2556

Taufen Sie diese Datei Strings.resx. Die Datei wird dann in ei-nem eigenen Editor dargestellt.

Ressourcen-Datei erzeugen

Abb. 6.27

Resx-Editor des Entwicklungssystems

Abb. 6.28

Page 256: Otmar Ganahl - C Sharp

C #

256 6

Sie können die Darstellung des Editors in eine reine XML-An-sicht oder in eine Tabellenansicht umschalten. Hier können Sienun in eleganter Form Schlüssel-Wert-Paare für Strings hinzu-fügen (allerdings nur String-Ressourcen!). Fügen Sie entspre-chend Abbildung 6.26 zwei String-Ressourcen Geburtsjahr undSterbejahr hinzu.

Den Quellcode erweitern Sie dann wie folgt:

CD-BeispielNewton2

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 dasEntwicklungssystem aus der Datei Strings.resx eine Datei (z.B.über Resgen.exe) Newton.Strings.resources, die dann der An-wendung hinzugelinkt wird. (Sie finden diese temporäre Da-teien im Projektordner unter /obj/Debug bzw. /obj/Release.)

Im Code wird ein zusätzliches ResourcenManger-Objekt unterAngabe der neuen Ressource erzeugt und die Werte über dieSchlüssel ausgelesen.

ZusammenfassungSie haben nun gelernt, wie Ressourcen in der .NET-Program-mierung erzeugt und angewendet werden können. Ressour-cen sind binäre statische Daten, die in ein Assembly (.exe- oder.dll) eingebettet werden können. Es gibt mehrere Wege, Res-source-Dateien zu erzeugen.

Es ist nun denkbar, Assemblies zu erzeugen, die ausschließlichRessourcen beinhalten. Solche Assemblies werden auch Satelli-

Page 257: Otmar Ganahl - C Sharp

Windows-Applikationen2576

ten-Assembly genannt. Vor allem, wenn mehrere Sprachen(Kulturen) unterstützt werden sollen, können die Texte, Iconsusw. jeder Kultur in eine eigene Assembly untergebracht wer-den. Im nächsten Abschnitt wird dieser Aspekt beleuchtet.

Unterstützung der KulturenSchon früh hat Microsoft begonnen, Anwendungen und Be-triebssysteme auch in unterschiedlichen Sprachen und für un-terschiedliche Kulturen anzupassen. Das gefällt denMenschen, und dieser Umstand ist sicherlich ein wesentlicherGrund für Microsofts beherrschende Marktstellung in der IT-Branche.

Mit einer Sprachanpassung allein ist es nicht getan. Ein reinesÜbersetzen ist meist zuwenig. Es sind wesentlich mehr Aspek-te zu berücksichtigen, wie z.B. Formatangaben, Währungen,Kuvertgrößen, Papiermaßangaben, Zeitformate usw. Aberauch grafische Elemente werden unter Umständen in unter-schiedlichen Kulturen auch unterschiedlich interpretiert.

Wenn Sie wissen, dass Ihre Software auch in unterschiedlichenKulturen angewendet wird, dann sollten Sie schon im Designdarauf Bedacht nehmen. Prinzipiell sollte eine strikte Tren-nung zwischen der Programmlogik und kulturabhängigen Tei-len im Projekt durchgeführt werden. Wie Sie schon in Kapitel 5:XML-Klassen erkannt haben, bieten sich Ressourcen ganz spe-ziell an, um mehrere Kulturen zu unterstützen.

.NET und im Besonderen Visual Studio.NET unterstützten denEntwickler in der Programmierung von multikultureller Soft-ware. Bevor Ihnen die Möglichkeiten an einem Beispiel de-monstriert werden, noch ein wenig Theorie.

In der Spezifikation RFC1766 sind Kulturnamen definiert. EinKulturname besteht aus einer Sprache (zwei Buchstaben) undeiner Region(zwei Buchstaben). Hier einige Beispiele:

en-US englisch (Region USA)en-GB englisch (Region GB)de-AT deutsch (Region Österreich)de-CH deutsch (Region Schweiz)fr-CH französisch (Region Schweiz)

Page 258: Otmar Ganahl - C Sharp

C #

258 6

Ausgehend vom Code SimpleEmail soll das kleine E-Mail-Pro-gramm auf ein Englisch sprechendes Publikum angepasst wer-den. Sie können sich das Projekt von der CD holen undentsprechend erweitern.

Werfen Sie nun kurz einen Blick in die Methode InitializeCom-ponent. Sie wissen ja, dass hier der Designer Initialisierungs-code für Steuerelemente, Menüs, Dialoge usw. einfügt.

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 für das Steuerelement tb-Adress.

Wechseln Sie nun zum Designer und markieren das Haupt-Fenster. Im Eigenschaftsfenster werden die Eigenschaften derForm aufgelistet. Stellen Sie die Eigenschaft Localizable auftrue.

Kompilieren Sie nun das Projekt neu und sehen Sie wieder indie Methode InitializeComponent.

System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(MainWnd));this.tbAddress.AccessibleDescription =

Eigenschaft Localizable

Abb. 6.29

Page 259: Otmar Ganahl - C Sharp

Windows-Applikationen2596

((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 ResourcenMa-nager-Objekt eingerichtet, und sämtliche Werte für Steuerele-mente sind nun in einer Ressource abgelegt.

Im Ordner finden Sie die Datei App.resx. Öffnen Sie diese undSie sehen dort sämtliche Einträge!

Wenn Sie nun wieder zum Designer wechseln, können Sie überdie Form-Eigenschaft Language (momentan eingestellt aufDefault) eine Sprache aussuchen. Wählen Sie English.

Im Designer ändern Sie nun die Einträge, auf Englisch. Das sindsämtliche Text-Einträge der Steuerelemente, der Menüs undder Form. Vergessen Sie auch nicht die Tooltip-Einträge derWerkzeugleiste.

Ändern Sie nun im Eigenschaftsfenster die Eigenschaft Langu-age der Form auf Englisch U.S.A. Im Text-Property der Form ge-ben Sie nun Simple Email U.S. ein.

Sprache aussuchen

Abb. 6.30

Page 260: Otmar Ganahl - C Sharp

C #

260 6

Sie haben nun drei Ressourcen:

Default (ursprünglich)EnglishEnglish U.S.

Entwickeln Sie nun das Projekt, und werfen Sie einen Blick inden Ordner des Projekts. Dort existiert neben einer DateiApp.resx (default) auch eine Datei App.en.resx (englisch) undeine Datei App.en-US.resx (englisch USA). Im Ausgabeverzeich-nis (/bin/debug) wurde für jede Sprache ein Ordner eingerich-tet. In diesen findet sich nun je eine Datei mit dem NamenSimpleEmail.resources.dll. Das sind Satelliten-Dlls, die aus-schließlich Ressourcen enthalten.

Im Designer können Sie durch Angabe der Eigenschaft Langua-ge jederzeit zwischen den Sprachen umschalten. Der Designerzeigt dann auch die entsprechenden Texte wieder an.

Wenn Sie nun das Programm starten, erscheint allerdings dieDefault-Variante. Was müssen Sie tun, damit eine andereSprache dargestellt wird?

Simple Email im Designer(english)

Abb. 6.31

Page 261: Otmar Ganahl - C Sharp

Windows-Applikationen2616

CD-BeispielSimpleEmail4

public MainWnd(){ CultureInfo ci =new CultureInfo("en-us"); Thread.CurrentThread.CurrentCulture = ci; Thread.CurrentThread.CurrentUICulture = ci;

InitializeComponent();}

Im Konstruktor fügen Sie vor (!) der Methode InitiallizeCompo-nent obigen Code ein. Sie erzeugen ein Objekt vom Typ Culture-Info mit Angabe einer Kultur und weisen dieses Objekt danndem aktuellen Thread in gezeigter Form zu.

Damit der Code auch kompilierbar ist, müssen Sie noch folgen-de Namensräume freigeben:

using System.Globalization;using System.Threading;

Wenn Sie nun das Programm starten, erscheint die englischeU.S. Variante.

Experimentieren Sie. Wählen Sie jetzt einmal die Kultur en-gb.Für diese Kultur haben Sie ja keine Ressource erstellt.

CultureInfo ci =new CultureInfo("en-gb");

Simple Email in English U.S.

Abb. 6.32

Page 262: Otmar Ganahl - C Sharp

C #

262 6

Sie sehen, es wird die englische Variante (Sprache ohne Regi-on) ausgewählt. Was passiert, wenn Sie die Kultur fr-ch wäh-len?

CultureInfo ci =new CultureInfo("fr-ch");

In diesem Fall wird die Default-Kultur ausgewählt.

Wenn der ResourcenManager einen Eintrag in der Ressourcesucht, dann sucht dieser zuerst in der Satelliten-Dll der Kultur(Sprache und Region) des Threads. Ist keine Satelliten-Dll indieser Kultur vorhanden, dann wird eine Satelliten-Dll derSprache (ohne Region) gesucht. Gibt es diese Dll nicht, oderaber ist in dieser Dll der Wert nicht vorhanden, wird in der De-fault-Ressource nachgeschaut.

Wenn Sie einen Blick in die .resx-Dateien werfen, dann fällt Ih-nen auf, dass in den Sub-Dlls nur jene Einträge getätigt sind,die von der übergeordneten Satelliten-Dll abweichen. Alle Ein-träge, die über Designer verwaltet werden, sind nun ange-passt. Noch nicht berücksichtigt sind aber die hart kodiertenStrings im Code.

MessageBox.Show("Bitte Adresse eingeben");MessageBox.Show("Betreff eingeben");MessageBox.Show("Nachricht eingeben");

Einträge in App.resxhinzufügen

Abb. 6.33

Page 263: Otmar Ganahl - C Sharp

Windows-Applikationen2636

Diese Texte müssen noch in die Ressource gebracht werden.Dazu öffnen Sie über das Menü Datei > Öffnen > Datei die Da-tei App.resx. In dieser sind die Ressourcen-Einträge für die De-fault-Kultur im XML-Format untergebracht.

Fügen Sie nun in der Tabellendarstellung drei String-Ressour-ce-Einträge hinzu.

M_Address „Bitte Adresse eingeben“

M_Subject „Bitte Betreff eingeben“

M_Body „Bitte Text eingeben“

Öffnen Sie nun in gleicher Weise die Datei App.en.resx. In die-ser sind die Ressource-Einträge der en-Kultur, die von der De-fault-Kultur abweichen, zu finden. Auch hier fügen Sie nunbitte die oben angeführten Einträge nach.

M_Address „No adress“

M_Subject „No subject“

M_Body „No Body“

In der Datei App.en-US.resx brauchen Sie keine weiteren Ein-träge durchzuführen, da die der en-Kultur verwendet werdenkönnen.

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==""){

Page 264: Otmar Ganahl - C Sharp

C #

264 6

rtbBody.Focus(); MessageBox.Show(rm.GetString("M_Body")); return;}

Die hart kodierten Einträge werden nun ersetzt. Ein Objektvom Typ ResourcenManager holt sich die Einträge über die Me-thode GetString unter Angabe des Schlüssels aus der richtigenSatelliten-Dll.

Zusammenfassung.NET unterstützt den Programmierer in der Entwicklung voninternationalen Anwendungen mit einem sauberen undschlüssigen Programmiermodell. Sämtliche kulturabhängigenRessourcen werden in eigene Satelliten-Dlls untergebracht,die sich in Unterordnern der Applikation befinden müssen. DieDefault-Einträge sind wie gehabt in der „Haupt“-Assembly un-tergebracht. Damit ist es auch möglich, Kulturen jederzeitauch nachträglich zu unterstützen.

Eine Unschönheit hat das Programm Simple Email noch. DieAuswahl der Kultur ist hart kodiert. Das ändern Sie in Kapitel 7:Konfigurationsdateien, in dem Sie einiges über die Konfigurati-on von Anwendungen erfahren werden.

Page 265: Otmar Ganahl - C Sharp

Konfigurationsdateien

Konfigurationen 266

.NET-Konfigurationsdateien 268

machine.config 273

Zusammenfassung 281

Page 266: Otmar Ganahl - C Sharp

C #

266 7

KonfigurationenKennen Sie die Datei config.sys? Diese Datei stammt aus derguten alten MS DOS-Zeit, und ist sogar noch gelegentlich unterWindows 2000 und höher zu finden. In dieser Textdatei wer-den unter MS DOS Betriebssystemeinstellungen, wie z.B. Trei-ber, Sprache usw., in Form von Text-Strings eingetragen. Dieskann mit jedem beliebigen Editor durchgeführt werden. Natür-lich müssen die Einträge einem gewissen Schema gehorchen.Beim Start liest MS DOS dann diese Einträge aus und verwertetdiese entsprechend. Bestimmte Betriebssystemeinstellungenkönnen somit „deklarativ“ konfiguriert werden (ohne das Be-triebssystem neu zu installieren).

Unter Windows (mitgeerbt von Windows 3.xx) kennen Sie si-cherlich die .ini-Dateien, in denen auch für Applikationen Kon-figurationseinträge getätigt werden können. Auch diese .ini-Dateien sind normale Textdateien und können daher mit ei-nem handelsüblichen Editor bearbeitet werden.

Wie leicht einsehbar, müssen diese Konfigurationsdateien na-türlich einer bestimmten Struktur gehorchen, damit die Appli-kation die Einträge in der Datei auch findet. Meist sind dieEinträge von solchen Konfigurationsdateien Schlüssel – Wert –Paare.

[Path]ExecDir=C:\MyAppDataDir=C:\MyApp\dataTempDir=C:\ MyApp\data\Temp

Hier sehen Sie einen solchen typischen Eintrag einer .ini-Dateifür eine Applikation. [Path] stellt dabei eine Gruppe dar. In die-ser Gruppe werden dann Einträge definiert. Jeder dieser Ein-träge besteht aus einem Schlüssel (im Beispiel: ExecDir,DataDir, TempDir) und den zugehörigen Werten (nach demGleichheitszeichen). Der Programmierer der Applikation hatseinen Code so ausgestattet, dass bei Programmstart die Wer-te von der Applikation aus der .ini-Datei ausgelesen werden,und in diesem Fall die angegebenen Ordner für die Arbeit ver-wendet verwendet.

Mit der Einführung der Registrierungsdatenbank haben die.ini-Dateien nicht mehr diese Bedeutung, da Einstellungen

Page 267: Otmar Ganahl - C Sharp

Konfigurationsdateien2677

programmtechnisch aus der Registrierung ausgelesen werdenkönnen. Die Registrierungseinträge werden meist bei der In-stallation der Applikationen getätigt und/oder die Applikationbietet Möglichkeiten über Dialoge, Einträge in die Registrie-rungsdatenbank durchzuführen.

Wie Sie sicherlich schon mehrfach festgestellt haben, versucht.NET von der Registrierungsdatenbank weg zu kommen. Diesist auch eine Voraussetzung für XCOPY-Installing – also Instal-lation eines Programms durch einfaches Kopieren auf den ei-genen Rechner.

Und damit wird wieder die Auferstehung der Konfigurati-onstextdateien unter .NET gefeiert. „Back to the roots“ werdensicherlich einige schon ältere Semester (und dazu gehört manin unserer Branche schon ziemlich bald) mit Genugtuung fest-stellen.

Textdateien im 3. Jahrtausend sind natürlich keine „normalen“Dateien, sondern, Sie haben richtig erraten, XML-Dateien. .NETbietet ein leistungsfähiges, erweiterbares und doch einfachesModell zur Konfiguration von Applikationen über XML-Datei-en an. Wenn Sie dieses Modell einmal durchschauen, dannwerden Sie es nicht mehr missen wollen.

XML-Konfigurationsdateien für ausführbare Applikationen(.exe) müssen sich im selben Ordner wie die Applikation selbstbefinden und haben den Namen der Applikation mit der Datei-erweiterung .config. Wenn die Applikation MyApplication.exeheißt, so muss die zugehörige Konfigurationsdatei den NamenMyApplication.exe.config haben.

Unter ASP.NET muss die Konfigurationsdatei einer ASP.NET-Applikation den Namen Web.config tragen. Das Prinzip ist aberdasselbe. In diesem Kapitel werden Sie das Konzept wieder inForm von einfachen Beispielen kennen lernen.

Page 268: Otmar Ganahl - C Sharp

C #

268 7

.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="[email protected]" /> </Sybex></configuration>

Eine XML-Konfigurationsdatei beginnt mit der für XML-Datei-en typischen Einleitung.

Das Root-Tag heißt <configuration>. Bevor Einträge verwendetwerden können, müssen diese erst deklariert werden. Dies ge-schieht im Tag <configSection>. Im <configSection>-Tag kön-nen nun beliebig viele Gruppen definiert werden. Eine Gruppebekommt einen bestimmten Namen, im Beispiel den NamenSybex. Innerhalb einer Gruppe werden nun <section>-Tags de-finiert. Jedes <section>-Tag erhält zwei Attribute, nämlich denNamen der Section (name=) und einen Typ (type=...). Bei derTypangabe wird die Klasse angegeben, über welche diese Ein-träge dann aus der Datei gelesen werden können, als auch dasAssembly, in welchem sich die Klasse befindet.

Page 269: Otmar Ganahl - C Sharp

Konfigurationsdateien2697

<section name="Path" type="SybexConfig.PathPropertiesHandler, SybexConfig" />

Dieses Tag weist darauf hin, dass Einträge vom Typ <Path>später mit der Klasse SybexConfig.PathPropertiesHandler, diesich im Assembly SybexConfig befindet, ausgelesen werdenkönnen. Dazu aber später.

Nachdem die Gruppen und Sektionen deklariert wurden, kön-nen nun Einträge getätigt werden.

<Sybex> <Path ExecDir="C:\MyApp" DataDir="C\MyApp\Data" TempDir="C:\MyApp\Temp" /> <Email To="[email protected]" /> <Sybex>

In diesem Beispiel werden Einträge für die Gruppe <Sybex> ge-tätigt, und zwar für die deklarierte Sektion <Path> und<Email>. Die Werte werden in Form von XML-Tag-Attributenangegeben.

Aber wie werden diese Werte programmtechnisch aus der Da-tei gelesen?

XML-Konfigurations-BearbeiterLaut den Eintragungen in der Konfigurationsdatei benötigenSie allerdings auch noch die Klassen SybexConfig.PathProper-tiesHandler und SybexConfig.EmailPropertiesHandler. Diesewerden im Assembly SybexConfig erwartet. Fügen Sie daherder Projektmappe ein C#-Klassenbibliothek-Projekt mit demNamen SybexConfig hinzu und nennen Sie in bewährter Ma-nier die Quellcodedatei in SybexConfig.cs (statt Class1.cs) um.Zuerst geben Sie in der Quellcodedatei die Namensräume Sys-tem.Configuration und System.Xml frei. In diesen Namensräu-men befinden sich nämlich sämtliche notwendigen Klassen,die Sie nun benötigen werden.

Jetzt definieren Sie die Klasse PathPropertiesHandler. Ein Konfi-gurations-Bearbeiter muss die Schnittstelle IConfigurationSec-tionHandler mit der einzigen Methode Create implementieren.Dieses sehen Sie im folgenden Codeabschnitt.

Page 270: Otmar Ganahl - C Sharp

C #

270 7

CD-BeispielSybexConfig

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;

Page 271: Otmar Ganahl - C Sharp

Konfigurationsdateien2717

_DataDir = ac["DataDir"].Value; _TempDir = ac["TempDir"].Value; } }}

Die Methode Create hat drei Übergabeparameter, wobei mo-mentan nur der Parameter node vom Typ XmlNode interessiert.Sie liegen richtig, wenn Sie im Parameter node den XML-Eintragvermuten, dessen Attribute Sie gerne auslesen würden. In derMethode Create erzeugen Sie zuerst mit einem Konstruktor,dem Sie den Parameter parent mitgeben, ein Objekt vom TypPathProperties. Dieses Objekt wird dann mit entsprechendenWerten belegt (dazu gleich mehr) und zurückgegeben. Unter-suchen Sie nun die Anweisung pp.LoadAttrFromXml(node)näher. Sie holen sich zuerst über die Eigenschaft Attributesvon node die Attributen-Liste und lesen dann die einzelnenWerte der Attribute über einen implementierten Indexer (mitStrings als Indexparameter) aus.

Wenn sich nun alles fehlerfrei kompilieren lässt, legen Sie einC#-Projekt vom Typ Konsolen-Anwendung an. Nennen Sie esConfTest. Wieder nennen Sie die vom Assistenten erzeugte Da-tei Class1.cs auf App.cs und die in dieser Datei definierte KlasseClass1 auf App um. Damit Sie die gerade erzeugten Konfigura-tionsbearbeiter-Klassen verwenden können, müssen Sie na-türlich einen Verweis auf Assembly SybexConfig herstellen.Geben Sie auch gleich die Namensräume SybexConfig und Sys-tem.Configuration frei.

Wenn Sie nun das Projekt kompilieren, dann wird die ausführ-bare Datei ConfTest.exe im Ordner /bin/Debug erzeugt. NachKonvention muss sich nun die XML-Konfigurationsdatei derApplikation im selben Ordner wie die ausführbare Datei befin-den. In diesem Ordner fügen Sie also eine neue Datei hinzu,am besten mit dem Menü Datei > Neu > Datei > XML-Datei.Speichern Sie dann die Datei gleich mittels dem MenübefehlDatei > Speichern unter... im Projektordner unter /bin/Debugmit dem Namen ConfTest.exe.config ab. Überprüfen Sie mitdem Datei-Explorer, ob die Datei auch wirklich den NamenConfTest.exe.config besitzt. Das ist sehr wichtig.

In diese Datei fügen Sie nun die XML-Daten, die Sie am Beginndes Kapitels betrachtet haben, ein.

Page 272: Otmar Ganahl - C Sharp

C #

272 7

CD-BeispielConfTest

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 Sys-tem.Configuration können über die statische Methode Get-Config(...) unter Angabe des XML-Path die Attributeausgelesen werden. Die Methode GetConfig gibt Ihnen ein Ob-jekt vom Typ object zurück. Dieses können Sie nun auf die ent-sprechende Konfigurationsklasse casten, in diesem Fall aufPathProperties, und anschließend über dieses Objekt auf dieKonfigurationsdaten zugreifen. Die Methode GetConfig er-zeugt sich aus der XML-Path-Angabe einen XmlNode, erzeugtintern ein Objekt PathPropertiesHandler (die Information holtsich die Implementierung ebenfalls aus der Konfigurationsda-tei), ruft über die Schnittstelle IConfigurationSectionHandlerdie Methode Create auf und reicht deren Rückgabeergebnisdurch.

Analog lässt sich eine Klasse EmailPropertiesHandler entwi-ckeln, um das Tag <Email> aus der Konfigurationsdatei auszu-lesen.

CD-BeispielSybexConfig

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

Page 273: Otmar Ganahl - C Sharp

Konfigurationsdateien2737

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 könnennämlich diese in anderen Applikationen ebenfalls verwendetwerden.

machine.configIm Beispiel haben Sie gesehen, dass Sie in der XML-Konfigura-tionsdatei zuerst die Struktur für Einträge definieren (im Tag<configSections>) und dann, mit Kenntnis dieser Struktur dieeigentlichen Einträge durchführen.

Page 274: Otmar Ganahl - C Sharp

C #

274 7

Ohne es zu wissen haben Sie aber in der Konfigurationsdateisämtliche Eintragungen einer anderen, maschinenglobalenKonfigurationsdatei sozusagen mitgeerbt. Diese Konfigurati-onsdatei befindet sich im Ordner WINNT\Microsoft.Net\Frame-work\v1.0.3705\CONFIG. Wenn Sie einen Blick in diese Dateiwerfen, sehen Sie eine Menge von <sectionGroup>- und <sec-tion>-Einträgen. Unterlassen Sie es aber, hier Änderungendurchzuführen. Alle diese Konfigurationsstrukturen dürfen Siein der Konfigurationsdatei der Applikation verwenden, ohnediese noch einmal zu deklarieren. Und das sind eine Menge,wie Sie sich überzeugen können. In dieser maschinenglobalenKonfigurationsdatei werden aber nicht nur Konfigurations-strukturen deklariert, sondern auch Konfigurationseinträge!

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>

Page 275: Otmar Ganahl - C Sharp

Konfigurationsdateien2757

Dieser Eintrag konfiguriert z.B. die verfügbaren Kompiler derinstallierten .NET-Laufzeitumgebung und gibt deren Dateier-weiterungen an.

Werden Konfigurationsdaten, die in machine.config schonfestgelegt sind, in der Konfigurationsdatei der Applikationnoch einmal verwendet, werden die Einträge der übergeord-neten Konfigurationsdatei überblendet. Sie verstehen nun si-cherlich auch den Parameter parent vom Typ object in derMethode Create der Schnittstelle IConfigurationSectionHand-ler. Bei der Suche nach Konfigurationsdaten werden bei Bedarfauch die übergeordneten Konfigurationseinträge ausgelesen.

Viele der Konfigurationsstrukturen in der Datei machine.configsind in der Gruppe <System.Web> deklariert. Diese werdenvornehmlich von ASP.NET verwendet. Im Kapitel9, ASP.NETwerden Sie noch eingehend diese Konfigurationseinträge stu-dieren.

appSettingsInteressant ist für den Anwendungsprogrammierer die Konfi-gurationsstruktur 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, undsomit sozusagen „global“ verwendbar. Die Verwendung seihier aufgezeigt. Nehmen Sie an, dass Ihre Applikation auf eineTabelle in einer Datenbank alle Einträge auslesen soll. Der Ortdes Datenbankservers, der Datenbankname und die Tabellesollen per Konfigurationsdatei einstellbar sein. Nun könntenSie analog dem einführenden Beispiel eine eigene Konfigurati-onsstruktur deklarieren und entsprechende Handler dazu ge-nerieren. Mit der Verwendung des schon vordefinierten (inmachine.config) Konfigurationsschemas geht es auch einfa-cher.

Page 276: Otmar Ganahl - C Sharp

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 können Sie über das Tag add und den Attri-buten key und value beliebig viele Schlüssel/Werte-Paare zu-ordnen. Im Beispiel werden zwei Paare eingetragen, einmalein Wert für den Schlüssel DSN und ein Wert für den SchlüsselQUERY_ALL. Aus der Definition der Konfigurationsstruktur ent-nehmen Sie, dass die Klasse System.Configuration.NameValue-FileSectionHandler für das Auslesen der Schlüssel/Werte-Paareverwendet werden kann. Aber in der Klasse ConfigurationSet-tings existiert ein spezielle Indexer, der es Ihnen erlaubt, sehreinfach die Werte von Schlüsseln auszulesen.

CD-BeispielAppSetting

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); } }}

Page 277: Otmar Ganahl - C Sharp

Konfigurationsdateien2777

Den größten Teil des Konfigurationsbedarfs von Applikationenkann über appSettings abgedeckt werden, sodass die expliziteEntwicklung von Konfigurationsstrukturen eher die Ausnah-me darstellen wird.

Simple-Email-BeispielIm Kapitel 6 haben Sie das Beispiel SimpleEmail programmiert.In diesem Beispiel haben Sie den smtp-Provider sowie den Ab-sender hart kodiert. Diese beiden Einträge sollen in die Konfi-gurationdatei untergebracht werden. Im Folgenden wirddieses Beispiel so erweitert, dass sowohl der smtp-Provider alsauch der Absendername über die Konfigurationsdatei belegtwerden kann. Die Applikation wird noch mit einem Dialog er-weitert, über den diese Einträge in der Konfigurationsdateimanipuliert werden können. Zusätzlich soll über die Konfigu-rationsdatei die verwendete Kultur eingestellt werden kön-nen.

Erzeugen Sie im Ordner SimpleEmail\bin\debug eine XML-Da-tei 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 fügen Sie einen neuen Dialog hinzu. VerwendenSie dieses Mal den Assistenten, indem Sie den Menübefehl Pro-jekt > Neues Element hinzufügen > Windows Form betätigen.Geben Sie der Datei den Namen ConfigureDialog. Im Designerfügen Sie dann entsprechend Abbildung 7.1 Steuerelementehinzu (tbAbsender und tbSmtp).

Page 278: Otmar Ganahl - C Sharp

C #

278 7

In der Klasse ConfigureDialog fügen Sie folgende Member-Vari-ablen und Properties hinzu:

string ConfigFileName; //Name der XML-config DateiXmlDocument 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(

Konfigurationsdialog

Abb. 7.1

Page 279: Otmar Ganahl - C Sharp

Konfigurationsdateien2797

@"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 inein Objekt der Klasse XmlDocument. Über XPATH-Ausdrückenavigieren Sie dann zu den entsprechenden Nodes, lesen dieWerte aus und belegen dann die Steuerelemente mit den Wer-ten.

Errichten Sie dann noch zwei Behandlungsroutinen für btOKund 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)

Page 280: Otmar Ganahl - C Sharp

C #

280 7

{ DialogResult = DialogResult.Cancel;}

Wieder navigieren Sie über XPATH-Ausdrücke zu den jeweili-gen Konten. Diese werden mit den neuen Werten aus denTextBoxen belegt und dann im Document abgespeichert.(Mehr zu XPATH finden Sie in Kapitel 4, XML-Einführung undKapitel 5, XML-Klassen.)

Nun müssen Sie noch die Applikation dazu bringen, dass diesedie Werte aus der Konfigurationsdatei verwendet. Dazu stat-ten Sie die Klasse MainWnd mit zwei neuen Members aus.

string SmtpProvider;string Absender;

Diese belegen Sie dann im Konstruktor mit den Daten aus derKonfigurationsdatei.

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 Einträ-ge:

System.Web.Mail.SmtpMail.SmtpServer =SmtpProvider;

System.Web.Mail.SmtpMail.Send( Absender,

Page 281: Otmar Ganahl - C Sharp

Konfigurationsdateien2817

tbAddress.Text,tbSubject.Text,rtbBody.Text);

Die Applikation erhält noch einen neuen Menüpunkt Einstel-lungen... Dieser öffnet den Dialog und liest die neuen Werteaus.

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 können nun den Smtp-Provider und den Absender überdiesen Dialog dauerhaft speichern. Um eine andere Kultur aus-zuwählen, müssen Sie die Einstellungen in der Konfigurations-datei händisch durchführen.

Zusammenfassung.NET stellt ein einfaches, aber umso leistungsfähigeres Instru-ment Applikationen zu konfigurieren zur Verfügung. Dem Ent-wickler werden eine Reihe von Konfigurationsschemas übereine übergeordnete Konfigurationsdatei angeboten, welchejederzeit benutzt werden können. Das Modell ist auch erwei-terbar in der Form, dass eigene Konfigurationsschemas dekla-riert werden können.

Nutzen Sie dieses Feature der Konfiguration so gut wie mög-lich. Berücksichtigen Sie in Ihrem Software-Design diese einfa-che Form des Konfigurierens von Applikationen.

Page 282: Otmar Ganahl - C Sharp
Page 283: Otmar Ganahl - C Sharp

ADO.NET

Einleitung 284

Übersicht der Klassen 286

Zugriff auf MS SQL Server 287

DataSet 300

DataGrid 313

Zusammenfassung 315

Page 284: Otmar Ganahl - C Sharp

C #

284 8

EinleitungADO.NET ist ein neues Objektmodell für den Zugriff auf Daten,die vornehmlich in Tabellen strukturiert sind, also auch auf Da-tenbanken. „Nicht schon wieder ein neues Objektmodell“,werden Sie vielleicht sagen, wenn Sie sich schon länger mit derWindowsprogrammierung beschäftigen.

Microsoft hat schon mehrfach versucht, für den DatenzugriffStandards zu setzen. Der Quasi-Industriestandard für den Zu-griff auf Datenbanken heißt Structured Query Language oderabgekürzt SQL (von einigen auch Sehr qualvolle Language ge-nannt ;-).

Datenbankhersteller haben Bibliotheken zur Verfügung ge-stellt (meist in Form von C-Funktionen), damit Softwareent-wickler auf ihre Datenbanken zugreifen konnten. Die Bibliothe-ken waren aber untereinander nicht austauschbar. Bei einernachträglichen Unterstützung oder einem Wechsel zu eineranderen Datenbank musste somit viel Code neu entwickeltwerden, obwohl der „Standard“ SQL verwendet wurde!

Microsoft führte dann ODBC ein (Open Database Connectivity).ODBC sollte ein standardisierte C-API für SQL-fähige Datenban-ken darstellen. Wenn Programmierer Datenbankzugriffe aufdieser Schnittstelle programmieren, so ist auch ein Wechselauf eine andere Datenbank relativ einfach, da der jeweiligeODBC-Treiber der Datenbank die Umsetzung auf die hersteller-abhängige Schnittstelle durchführt. Praktisch alle führendenDatenbankhersteller stellen ODBC-Treiber für die Windows-Plattformen bereit.

ODBC definiert eine C-API, und ist somit auch nicht objektori-entiert. Mit Aufkommen von COM wurden natürlich auchÜberlegungen getätigt, ein eigenes Objektmodell (über COM-Schnittstellen) bereitzustellen. Der erste Ansatz dazu war DAO(Data Access Objects). Vor allem unter MFC und Visual Basicwar diese Schnittstelle sehr beliebt. Microsoft dachte natürlichauch daran, dass die Datenbankhersteller ähnlich wie bei OD-BC, generische DAO-Treiber bereitstellen. Dies war aber nichtder Fall. War auch nicht tragisch, da Microsoft einen DAO-Trei-ber für ODBC bereitstellte, und so konnte man per DAO überODBC wieder praktisch auf alle Datenbanken zugreifen (natür-lich mit einer kleinen Performanz-Einbuße).

Page 285: Otmar Ganahl - C Sharp

ADO.NET2858

Die weiteren Entwicklungen zeigten aber, dass der Zugriff aufDaten doch in einem weiteren Kontext zu sehen ist. Daten ineiner Datenbank stellen einen Spezialfall dar. Es musste alsoein Objektmodell (Schnittstellen) her, das den Zugriff auf be-liebige Daten abstrahiert. Theoretisch sollte so Code erzeugtwerden, der vollkommen unabhängig von der Datenquelle ist.Dieses Objektmodell wurde von Microsoft auch entwickeltund heißt OLE-DB. Da zu dieser Zeit COM populär war, wurdeOLE-DB auch ein COM-Objektmodell. Damit hat DAO natürlichausgedient.

Das OLE-DB Modell kann prinzipiell beliebige Datenquellen ab-strahieren (sofern diese tabellarisch darstellbar sind) undspielt alle Stücke. Nachteilig ist aber, dass dadurch das Modellrelativ komplex ist. Zudem werden viele Features nur sehr sel-ten gebraucht. Um auch Visual Basic-Programmierer zu unter-stützen, hat Microsoft eine Schicht eingefügt, die speziell fürVB-Programmierer gedacht ist (ist nicht böse gemeint ;-). Siewurde einfacher gestaltet, spielt aber auch nicht alle Stücke.Diese Schicht erhielt dann den Namen ADO (ActiveX Data Ob-jects). Bitte nicht verwechseln mit DAO! ADO ist also nichts an-deres, als eine Schale um OLE-DB. Auch das ADO-Objektmodellist ein COM-Objektmodell. Aber nicht alle Datenbankherstellerbieten auch generische OLE-DB-Treiber an. Für diese Fälle bie-tet Microsoft einen speziellen OLE-DB zu ODBC-Treiber. Damitist es wieder möglich, auf praktisch alle Datenbanken zuzu-greifen.

Mit .NET hat COM ebenfalls ausgedient, und daher ist natür-lich auch ein neues Objektmodell von Nöten. Dieses neue Ob-jektmodell erhielt den Namen ADO.NET. Obwohl dieNamensgleichheit zu ADO besteht, so unterscheidet sichADO.NET doch deutlich von ADO (alt).

In diesem Kapitel werden Sie den Zugriff auf Datenbanken(und Daten allgemein) über .NET kennen lernen. Dabei werdengrundsätzliche Kenntnisse über relationale Datenbanken undSQL vorausgesetzt. Datenbanken sind zu einem zentralen Ele-ment in der Anwendungsprogrammierung und vor allem auchbei Web-Applikationen geworden!

Page 286: Otmar Ganahl - C Sharp

C #

286 8

Übersicht der KlassenADO.NET bietet Ihnen im Namensraum System.Data eine Men-ge von Klassen an. Implementiert sind diese Klassen alle im As-sembly System.Data.dll. Einige Klassen in diesem Assemblybenötigen auch Klassen, die im Assembly System.Xml.dll undSystem.dll implementiert sind. Verweisen Sie daher am bestenauf alle diese Assemblies, wenn Sie mit ADO.NET arbeiten. DerNamensraum System.Data definiert intern u.a. die Namens-räume System.OleDb und System.Sql.

Klassen im Namensraum System.OleDbOleDbCommandOleDbConnectionOleDbDataAdapterOleDbReaderOleDbTransaction

Diese Klassen sind unter dem Namensraum System.Da-ta.OleDb definiert. Über diese Klassen können praktisch alle inFrage kommenden Datenbanken verwendet werden (sieheeinleitende Diskussion).

Klassen für den Zugriff auf SQL-Server von Microsoft

SqlCommandSqlConnectionSqlDataAdapterSqlDataReaderSqlTranaction

Alle diese Klassen sind im Namensraum System.Data.Sql zufinden. Sie sind speziell für den SQL Server von Microsoft ge-dacht. Ihnen ist sicherlich aufgefallen, dass diese Klassen mitdenen aus dem Namensraum System.Data.OleDb korrespon-dieren. Bei der Verwendung des SQL Servers von Microsoft sinddiese Klassen aber deutlich schneller. Das ist auch der Grund,warum Microsoft diese speziellen Klassen ausliefert. Die bei-

Page 287: Otmar Ganahl - C Sharp

ADO.NET2878

den Objektmodelle sind aber faktisch gleich zu verwenden.Daher werden nachfolgend im Beispiel diese Klassen verwen-det.

Die Klasse DataSetSollten Sie schon unter ADO (alt) programmiert haben, danndenken Sie bei der Klasse DataSet sicherlich gleich an das ADO-Objekt RecordSet. Ganz falsch liegen Sie damit nicht, aber dieKlasse DataSet geht weit über die Funktionalität von RecordSethinaus. Wesentlich ist, dass ein DataSet-Objekt im verbin-dungslosen Zustand manipuliert werden kann. Dazu abermehr in einem eigenen Unterkapitel.

Zugriff auf MS SQL ServerFür die folgenden Betrachtungen benötigen Sie einen Zugriffauf einen SQL Server von Microsoft. Sollten Sie diesen nicht ha-ben, dann ist es empfehlenswert, SQL Server oder die abge-speckte Version von MSDE (Microsoft Data Engine) auf IhremRechner zu installieren. MSDE besitzt dieselben Schnittstellenwie SQL Server, ist aber nicht in dem Maß skalierbar (und kannvor allem lizenzfrei mit Ihrer Software vertrieben werden,wenn Sie mit Visual Studio.NET entwickeln). Ein spätererWechsel auf die ausgewachsene Datenbank ist ohne Codeän-derung möglich!

Datenbank anlegenAn einem neuen Beispiel werden Sie nun den .NET-Zugriff aufDatenbanken kennen lernen. Denken Sie an eine Anwendung,die Fotos in Form von gängigen Bildformaten verwalten kann.Alle Fotos werden in einem bestimmten Ordner abgespeichert.Jedes Foto soll einen Titel haben und auch das Aufnahmeda-tum soll gespeichert werden. Sie können sich natürlich nochweitere Eigenschaften vorstellen. So könnte man jedem Fotoeinen kleinen Kommentar zuweisen oder aber auch eineSprachdatei usw. Das ist aber Ihnen überlassen, weil im Mo-ment die Konzentration auf ADO.NET liegt. In der Datenbankwerden die Fotos aber nicht abgespeichert, sondern nur die zu-sätzlichen Daten. Damit Sie mit den Experimenten starten

Page 288: Otmar Ganahl - C Sharp

C #

288 8

können, erstellen Sie zuerst eine Datenbank. Sie ist sehr ein-fach gestaltet und besteht nur aus einer Tabelle tPhoto.

Um Tabellen anzulegen, bietet SQL Server den so genanntenEnterprise Manager an. MSDE, die abgespeckte Version, wirdaber ohne diesen Enterprise Manager ausgeliefert. Macht abernichts, da Visual Studio.NET diesen Enterprise Manager inte-griert. Sie können also aus dem Entwicklungssystem herausauch die Datenbank erstellen.

Dazu brauchen Sie das Fenster Server-Explorer. Wenn es nichtschon dargestellt wird, dann können Sie dieses Fenster überden Menüpunkt Ansicht > Server-Explorer aktivieren.

In Abbildung 8.1 sehen Sie die Darstellung auf dem Entwick-lungsrechner des Autors. Der Server-Explorer zeigt die Serverder Maschinen erle und esche an. Sie können mit dem Server-Explorer auch die Server anderer Maschinen darstellen lassen.Dies machen Sie über das Kontextmenü Server hinzufügen...Über den Server-Explorer können sämtliche Server verwaltetwerden, so unter anderem auch SQL Server. Dass Sie die not-wendigen Rechte besitzen müssen, ist natürlich Vorausset-zung.

Auf dem Datenbank-Server legen Sie zuerst eine Datenbankmit dem Namen PhotoPool an. Hierzu markieren Sie die SQL-Server-Instanz und erzeugen nun per Kontextmenü Neue Da-

Server-Explorer

Abb. 8.1

Page 289: Otmar Ganahl - C Sharp

ADO.NET2898

tenbank eine neue Datenbank. Verwenden Sie Windows NT Se-curity.

Im Server-Explorer sollten Sie nun die Datenbank PhotoPool se-hen. Expandieren Sie Eintrag im Server-Explorer, markieren Sieden Eintrag Tabellen und aktivieren Sie per Kontext den Menü-eintrag Neue Tabelle. Sie sehen nun die Entwurfsansicht derTabelle. Fügen Sie der Tabelle Spalten hinzu, entsprechend derAbbildung 8.3.

Achten Sie auch darauf, dass die Spalte ID Primärschlüssel ist. Siekönnen der Spalte per Kontextmenü Primärschlüssel festlegen

Neue Datenbank anlegen

Abb. 8.2

Entwurfsansicht der Tabelle tPhoto

Abb. 8.3

Page 290: Otmar Ganahl - C Sharp

C #

290 8

den Primärschlüssel zuweisen. Im Eigenschaftsfenster der Spal-te ID belegen Sie die Identität (ID) auf Ja (siehe Abbildung 8.3).

Wenn Sie diesen Entwurf zum ersten Mal abspeichern, werdenSie aufgefordert, der Tabelle einen Namen zu geben. GebenSie der Tabelle den Namen tPhoto und schließen Sie die An-sicht. Im Server-Explorer sollten Sie nun die Tabelle tPhoto se-hen. Ein Doppelklick zeigt Ihnen den Inhalt dieser Tabelle an,die jetzt natürlich noch leer ist. Sie könnten hier auch direktEintragungen vornehmen. Über das Kontextmenü Entwurf Ta-belle können Sie aber auch jederzeit wieder in die Entwurfsan-sicht der Tabelle wechseln.

Damit haben Sie die Voraussetzungen für die folgenden Expe-rimente geleistet. Sie werden nun die vier grundlegendenOperationen auf Tabellen kennen lernen. Das sind:

Daten in die Tabelle eintragenEintrag in der Tabelle löschenUpdaten eines EintragesEinträge auslesen

Daten in die Tabelle eintragenFür die Experimente erzeugen Sie sich am besten eine neueProjektmappe mit dem Namen ADO und legen darin ein Pro-jekt (Leeres Projekt) mit dem Namen PhotoAccess an. Fügen Siedem Projekt eine leere C#-Quellcodedatei mit dem NamenApp.cs hinzu.

Damit Sie auch Zugriff auf die .NET-Klassen haben, müssen Sieeinen Verweis auf das Assembly System.Data.dll und System.Dllherstellen.

CD-BeispielPhotoAccess1

using System;using System.Data;using System.Data.SqlClient;

public class PhotoAccessSql{ string DSN; public PhotoAccessSql(string DSN) {

Page 291: Otmar Ganahl - C Sharp

ADO.NET2918

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 Beispieleinträge tätigen 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"); }}

Page 292: Otmar Ganahl - C Sharp

C #

292 8

Sämtliche Zugriffe auf die Datenbank werden Sie im Folgen-den in der Klasse PhotoAccessSql implementieren. Diese Klassebesitzt eine Member-Variable DSN (Data Source Name). DieseMember-Variable wird im Konstruktor der Klasse belegt. DieKlasse PhotoAccessSql wird mit einer Methode AddPhoto aus-gestattet. Als Übergabeparameter verlangt diese Methodeden Titel, das Aufnahmedatum und den Dateinamen des Fo-tos.

Wie Ihnen sicherlich bekannt ist, brauchen Sie eine Daten-bankverbindung, wenn Sie mit einer Datenbank kommunizie-ren wollen. Die Klasse SqlConnection abstrahiert eine solcheVerbindung. Der Konstruktionsparameter dieser Klasse ver-langt einen Connection-String. Der Connection-String für dieDatenbank PhotoPool schaut wie folgt aus.

server=esche;uid=sa;pwd=;database=PhotoPool;“;

Im o.g. Fall des Autors heißt der Server esche (hier setzen Sienatürlich Ihren Servernamen ein!), für uid wird eine UserID an-gegeben. Im speziellen Fall ist dies der Systemadministrator(sa) und es wird kein Passwort angegeben (pwd). Die Daten-bank heißt PhotoPool.

Wenn Sie ein initialisiertes SqlConnection-Objekt haben, dannkönnen Sie über die Methode Open eine tatsächliche Verbin-dung öffnen. Vergessen Sie aber nicht, diese Verbindung auchwieder mit der Methode Close zu schließen, wenn Sie Ihre Ar-beit erledigt haben. Viele Datenbanken erlauben nämlich nureine gewisse Anzahl von offenen Verbindungen, und Sie bin-den damit wertvolle Ressourcen.

Damit dies auch sicher passiert, auch im Fall einer Exception,schützen Sie den kritischen Code am besten mit einem try-fi-nally-Block! Nächste Aufgabe besteht darin, ein SqlCommand-Objekt zu erzeugen. Der Konstruktor des SqlCommand-Objek-tes verlangt zur Initialisierung eine SQL-Anweisung und einSqlConnection-Objekt. Die SQL-Anweisung basteln Sie in Ab-hängigkeit der Übergabeparameter zusammen. Ein Beispielfür eine syntaktisch richtige SQL-Anweisung für das Einfügeneines Eintrages sehen Sie hier.

INSERT INTO tPhoto VALUES('Urlaubsfoto', '3.7.2001', 'Bild1.jpg')

Page 293: Otmar Ganahl - C Sharp

ADO.NET2938

Die Klasse SqlCommand implementiert drei Methoden, die füreine Ausführung der Anweisung verwendet werden können:

ExecuteReader()ExecuteScaler()ExecuteNonQuery()

Alle diese Methoden führen die Anweisung die im SqlCom-mand-Objekt gekapselt ist, durch. ExecuteReader wird verwen-det, wenn das Kommando Rückgabewerte besitzt (z.B. beieiner Abfrage). ExecuteScaler() wird gerne verwendet, wenngenau ein Rückgabewert zu erwarten ist (z.B. eine Abfrage be-züglich Anzahl der Elemente). ExecuteNonQuery() kann ange-wendet werden, wenn das Kommando keinen Rückgabewertbesitzt. Dies ist im Beispiel der Fall, da die INSERT-Anweisungeinen Eintrag tätigt, und somit keine Abfrageergebnisse zu-rückgibt.

In der Hauptfunktion wird über den Connection-String ein Ob-jekt vom Typ PhotoAccess erzeugt und dann die Methode Add-Photo aufgerufen. In der Tabelle tPhoto der DatenbankPhotoPool sollte nun ein Eintrag vorhanden sein. Fügen Sie ei-nige Einträge hinzu!

Hier die Schritte noch einmal zusammengefasst:

SqlConnection-Objekt erzeugen (mit Angabe einesConnection-Strings)SqlConnection.Open aufrufen, um Verbindung zur Da-tenbank herzustellenSqlCommand-Objekt erzeugen, unter Angabe einerSQL-Anweisung und einer SqlConnectionAufruf einer der drei Methoden ExecuteNonQuery,Execute-Scalar, ExecuteReaderSqlConnection.Close aufrufen, um die Datenbankver-bindung wieder abzubauen

Einträge in der Tabelle tPhoto der Datenbank PhotoPool

Abb. 8.4

Page 294: Otmar Ganahl - C Sharp

C #

294 8

Eintrag löschenDie Klasse PhotoAccess erweitern Sie nun um die Methode De-letePhoto. Über Angabe der ID des Eintrages wird dann der ge-samte Eintrag gelöscht.

CD-BeispielPhotoAccess2

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 sichder Code nicht von der Implementierung der Methode Add-Photo. Ein Beispiel für eine syntaktisch richtige SQL-Anwei-sung für das Löschen 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 gelöscht.

Page 295: Otmar Ganahl - C Sharp

ADO.NET2958

Update eines EintragesDie Methode UpdatePhoto soll Werte eines bestehenden Ein-trages ändern. Eine SQL-Anweisung, die dieses durchführt,könnte wie folgt aussehen:

UPDATE tPhoto SET Title='Urlaub Elba, Date='3.8.2001', Filena-me = 'Image1.gif' WHERE ID='2'

In diesem Fall würden die Einträge Title, Date und Filename inder Tabelle tPhoto mit dem ID-Wert 4 entsprechend modifi-ziert. Auch diese Implementierung unterscheidet sich grund-sätzlich nicht von den vorhergehenden Implementierungen.

CD-BeispielPhotoAccess3

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 =

Page 296: Otmar Ganahl - C Sharp

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 lesenNun fehlt noch eine Methode, um Daten aus der Datenbank zulesen. Die Methode soll folgenden Prototyp besitzen:

Photo [ ] GetPhotos( );

Diese gibt also ein Feld aus Objekten vom Typ Photo zurück.Die Klasse Photo müssen Sie natürlich zuerst noch anlegen.

CD-BeispielPhotoAccess4

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 können Sie nun die Methode GetPhotos wie folgt imple-mentieren:

public Photo[] GetPhotos(){ SqlConnection con = new SqlConnection(DSN); try { con.Open(); string sCom = "SELECT * FROM tPhoto";

Page 297: Otmar Ganahl - C Sharp

ADO.NET2978

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 Datenbankher, erzeugen dann ein SqlCommand-Objekt, verwenden aberhier die Methode ExecuteReader des SqlCommand-Objektes.Zurück erhalten Sie ein Objekt vom Typ SqlDataReader ( r ).Diese Klasse besitzt unter anderem eine Methode Read. Überdiese Methode können Sie sich nun innerhalb der Ergebnis-menge vorwärts iterieren (forward cursor). SqlDataReader führtintern einen Cursor, der bei jedem Read-Aufruf zum nächstenErgebnis springt. Read() gibt false zurück, wenn kein weiteresErgebnis mehr anliegt. Um auf die Daten zuzugreifen, habenSie jetzt mehrere Möglichkeiten. Alle die nachfolgend vorge-stellten Möglichkeiten wurden im obigen Beispielcode ver-wendet.

Verwendung des Indexers der Klasse SqlDataReaderMit Angabe der Spaltennummer (Basis 0) wird der Spalten-wert zurückgegeben. Hier ein Beispiel:

int ID = ((int)r[0];

Mit r[0] wird der Inhalt der ersten Spalte des Ergebnisses zu-rückgegeben, allerdings als object-Typ. Sie müssen daher den

Page 298: Otmar Ganahl - C Sharp

C #

298 8

Wert noch entsprechend casten. Als Index können Sie aberauch direkt den Spaltennamen der Tabelle verwenden!

string Name = (string)r["Title"];

Dies ist natürlich nicht so effizient, wie der Zugriff über den In-dexer, da hier noch eine String-Verarbeitung stattfinden muss.Außerdem dürfen Sie in diesem Fall die Spaltennamen der Ta-belle nicht mehr verändern.

Verwendung der GetXXX-Methoden der Klasse SqlDa-taReader

Die Klasse SqlDataReader implementiert für jeden Datenbank-Datentyp eine GetXXX-Methode. Im Beispiel wird der Datums-wert mit der Methode GetDateTime ausgelesen.

DateTime d =r.GetDateTime(2);

Dabei wird der Spaltenindex der Methode als Parameter mit-gegeben. Ein Vorteil der Verwendung der Methoden liegt da-rin, dass die Werte typisiert zurückkommen. Dies ist wenigerfehleranfällig (zur Entwicklungszeit).

Im Beispiel wird bei jedem erfolgreichen Read()-Vorgang einPhoto-Objekt erstellt und in ein dynamisches Feld gebracht.(Öffnen Sie im Beispiel daher auch den Namensraum Sys-tem.Collection.) Erst am Schluss wird dann der Inhalt des dyna-mischen Feldes noch in ein statisches Feld gebracht. DerVorteil des statischen Feldes liegt wieder darin, dass dieses ty-pisiert ist.

Photo [] ps = (Photo []) phs.ToArray(typeof(Photo));

Hier noch ein kleiner Code für 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++) {

Page 299: Otmar Ganahl - C Sharp

ADO.NET2998

Console.WriteLine(ps[i].Title); }}

Mit der Methode GetPhotos erhalten Sie sämtliche Einträgeder Datenbank zurück. Wenn Sie aber nur einen bestimmtenEintrag aus der Datenbank möchten, dann müssen Sie nocheine weitere Methode implementieren.

Photo GetPhoto(int ID);

Diese Methode gibt genau ein Objekt vom Typ Photo zurück,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 entsprechen-den SQL-Anweisung erstellt, per ExecuteReader ein SqlData-Reader-Objekt generiert und Read aufgerufen. Sollte keinErgebnis vorliegen, dann wird ein null-Wert zurückgegeben.

Page 300: Otmar Ganahl - C Sharp

C #

300 8

ZusammenfassungSie haben nun eine Klasse PhotoAccess entwickelt, die Metho-den anbietet, um Manipulationen in der Datenbank durchzu-führen. Sämtliche SQL-spezifischen Zugriffe sind in denMethoden untergebracht. Im Hauptprogramm müssen Siesich nicht mehr um die SQL-Details kümmern. Es macht natür-lich viel Sinn, diese Klasse in einem eigenen Assembly unterzu-bringen.

DataSetMit .NET wurde diese neue, überaus interessante Klasse einge-führt. Die Klasse DataSet hat viele Gemeinsamkeiten mit demRecordSet aus ADO, geht aber in seiner Funktionalität weit da-rüber hinaus.

Ein Objekt vom Typ DataSet kann intern mehrere Tabellen mitSpalten und Reihen beinhalten. Ja es können sogar die Bezie-hungen der Tabellen untereinander gehalten werden. StellenSie sich ein DataSet wie eine Mini-Datenbank im Speicher vor.Es muss aber betont werden, dass ein DataSet-Objekt absolutunabhängig von einer Datenbank ist. Sie können, wenn Siewollen ein DataSet-Objekt erzeugen, in diesem dann Tabellenmit Spalten (und Namen), Beziehungen zwischen den Tabellenherstellen, und diese dann auch belegen, unabhängig von ei-ner Datenbank.

Es ist nun naheliegend, dass ein DataSet-Objekt auch mit Da-ten einer Datenbank oder mit Daten eines Abfrageergebnisses„gefüllt“ werden kann. Dies ist auch möglich, und .NET stelltdazu eine eigene Klasse SqlDataAdapter an, die Daten aus ei-ner Datenbankverbindung auslesen und ein DataSet belegenkann.

Dazu ein Beispiel (Legen Sie ein neues Projekt an!):

CD-BeispielPhotoAccess5

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

class App

Page 301: Otmar Ganahl - C Sharp

ADO.NET3018

{ 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 füllen DataSet ds = new DataSet(); da.Fill(ds,"Photos");

//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]); } } }}

Page 302: Otmar Ganahl - C Sharp

C #

302 8

Damit sich dieses Beispiel auch kompilieren lässt, müssen Siedie Assemblies System.dll, System.Data.dll und System.Xml.dllreferenzieren. Nun aber zum Code:

Zuerst wird in bekannter Weise SqlConnection-Objekt erzeugt.Anschließend 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 Sql-Command. Diese wird initialisiert (mit einer SQL-Anweisungund einem SqlConnection-Objekt. Später ist diese Membernoch einmal Thema!

Nun wird ein (leeres) DataSet-Objekt erzeugt und dann mithil-fe des SqlDataAdapter-Objektes „gefüllt“.

DataSet ds = new DataSet();da.Fill(ds,"Photos");

Der Name „Photos“ in der Methode Fill ist der Name der Tabel-le innerhalb des DataSet-Objektes, und hier beliebig gewähltund hat nichts mit dem Namen der Tabelle in der Datenbankzu tun. Innerhalb der Methode Fill wird die Datenbankverbin-dung hergestellt und dann auch wieder getrennt. Ein DataSet-Objekt arbeitet also unabhängig von einer Datenbankverbin-dung!

Im Beispiel wird nun mit dem Objekt ds vom Typ DataSet gear-beitet. In der folgenden Abbildung ist der grundsätzliche Auf-bau eines DataSet-Objektes dargestellt.

Interne Struktureines DataSets

Abb. 8.5

Page 303: Otmar Ganahl - C Sharp

ADO.NET3038

Sie sehen, das DataSet kann aus mehreren Tabellen bestehen.Diese Tabellen werden im Member Tables der Klasse DataSetgehalten. Die einzelnen Tabellen haben den Typ DataTables.Ein Objekt vom Typ DataTables besitzt im Member Columnseine Collection aus DataColumn-Objekten (Spalten) und imMember Rows eine Collection aus DataRow-Objekten(Reihen).

Im Codebeispiel wird sich im DataSet genau eine Tabelle befin-den (mit dem Namen Photos). Diese besitzt vier Spalten (ent-sprechend der SQL-Abfrage) mit den Namen ID, Title, Date undFilename. Über die Objekte vom Typ DataRow können Sie nunauf die einzelnen Werte zugreifen. Da die Datenbankverbin-dung schon geschlossen wurde, sind Sie nun vollkommen un-abhängig von einer Verbindung zur Datenbank.

Manipulation von Daten innerhalb eines DataSetsIm DataSet können Sie nun Daten ändern, Einträge löschenoder aber neue Einträge hinzufügen. Manipulationen findennatürlich nur im DataSet statt und nicht in der Datenbank!Auch hier haben Sie die vier grundsätzliche Möglichkeiten zurManipulation der Tabelle:

Daten in die Tabelle eintragenEintrag in der Tabelle löschenUpdaten eines EintragesEinträge auslesen

CD-BeispielPhotoAccess6

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

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}",

Page 304: Otmar Ganahl - C Sharp

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 füllen DataSet ds = new DataSet(); da.Fill(ds,"Photos");

DataTable t = ds.Tables[0]; //Alle Einträge darstellen lassen Display(ds);

//Eintrag hinzufügen 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);

Page 305: Otmar Ganahl - C Sharp

ADO.NET3058

//Eintrag löschen t.Rows[1].Delete(); Display(ds);

}}

Hier wurde eine statische Methode Display eingeführt, diesämtliche Inhalte des DataSets ausgibt. Damit lassen sich dieManipulationen im DataSet dann auch gleich darstellen. Zu-erst wird das DataSet in bekannter Weise mit dem Abfrageer-gebnis gefüllt und dann wird die Datenbankverbindunggeschlossen. Nachfolgend sehen Sie die Erklärungen der ein-zelnen Manipulationen.

Eintrag hinzufügenDataTable 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 Data-Table) können jederzeit Einträge hinzugefügt werden. Beach-ten Sie, dass Add ein Feld aus objects verlangt. Für dieTypsicherheit sind Sie als Programmierer verantwortlich!

Eintrag manipulierenDataRow 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 für die Typsicherheit verantwortlich.

Eintrag löschent.Rows[0].Delete();

Hier würden Sie den ersten Eintrag (1. Reihe) löschen.

Wenn Sie einen DataRow-Eintrag über die Methode Delete lö-schen, wird eigentlich nur der Eintrag als gelöscht markiert. Siekönnen den Zustand eines DataRows über das Property Row-State jederzeit abfragen. Im Beispiel wurde das in der MethodeDisplay() durchgeführt.

Page 306: Otmar Ganahl - C Sharp

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.AddedDateRowState.DeletedDataRowState.ModifiedDataRowState.Unchanged

Warum? Es wäre doch sehr elegant, wenn Sie die Datenbank-verbindung wieder aufnehmen könnten und sämtliche geän-derte Daten im DataSet in der Datenbank nachgezogenwürden. Dss ist auch möglich. Dafür ist es aber notwendig,dass der SqlDataAdapter die Unterschiede kennt, die seit derletzten Verbindung zur Datenbank aufgetreten sind. Um dieÄnderungen in der Datenbank durchführen zu können, wer-den nämlich genau diese Zustandsdaten benötigt! Aber bevorSie sich näher mit diesem Aspekt beschäftigen, wird Ihnennoch ein weiteres, überaus interessantes Feature der KlasseDataSet vorgestellt.

DataSet und XMLDie Klasse DataSet implementiert die Methoden WriteXml undReadXml. Sie können den Inhalt eines DataSets in einer XML-Datei abspeichern! Ja noch mehr, Sie können ein DataSet auchvon einer XML-Datei „füllen“ lassen!

CD-BeispielPhotoAccess7

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

Page 307: Otmar Ganahl - C Sharp

ADO.NET3078

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 füllen DataSet ds = new DataSet(); da.Fill(ds,"Photos");

DataTable t = ds.Tables[0]; //Eintrag hinzufügen 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.xmlabgespeichert.

Fotos.xml<?xml version="1.0" standalone="yes"?><NewDataSet> <Photos> <ID>1</ID> <Title> Urlaub Korsika </Title> <Date> 2001-08-03T00:00:00.0000000+02:00

Page 308: Otmar Ganahl - C Sharp

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 erzeugteXML-Datei füllen können.

CD-BeispielPhotoAccess8

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 füllen DataSet ds = new DataSet(); ds.ReadXml(@"c:\Fotos.xml");

Page 309: Otmar Ganahl - C Sharp

ADO.NET3098

Display(ds); }}

Erkennen Sie das Potenzial dieses Ansatzes? Statt einer Daten-bank könnten Sie die Daten auch in einer XML-Datei persistenthalten. Und dies ohne großen Aufwand. Vor allem für Applika-tionen mit geringem Datenaufkommen eine überaus interes-sante Alternative. Oder aber Sie bieten in Ihrer Applikationbeide Möglichkeiten an. Auch wenn unsichere Datenbankver-bindungen bestehen (aufgrund unsicherer Verbindungen imWeb), dann könnten Sie das DataSet als XML-Datei zwischen-speichern und bei Vorhandensein der Verbindung dann wiederins DataSet laden und die Datenbank damit wieder aktualisie-ren.

Datenbank aktualisieren mit Daten eines DataSetsDataSets können über DataAdapter-Objekte mit Daten von Da-tenbanken gefüllt werden. Das haben Sie schon kennen ge-lernt.

SqlDataAdapter da = new SqlDataAdapter();string sCom = "SELECT * FROM tPhoto";da.SelectCommand = new SqlCommand(sCom,con);

Ein SqlDataAdapter-Objekt besitzt ein Member SelectCom-mand vom Typ SqlCommand. Hier kann die SQL-Anweisungund die Datenbankverbindung gesetzt werden, die der Data-Adapter beim Belegen des DataSets (Fill) verwenden soll.

Die Klasse SqlDataAdapter besitzt auch weitere Members fürdas Einfügen, Löschen und Updaten.

da.UpdateCommand = new SqlCommand(...);da.InsertCommand = new SqlCommand(...);da.DeleteCommand = new SqlCommand(...);

Diese müssen in ähnlicher Weise belegt werden, wie das Mem-ber SelectCommand, obgleich die SQL-Anweisungen und dasErzeugen der SqlCommand-Objektes deutlich kompliziertersind. Wenn Sie das geschafft haben, können Sie eine Daten-bank über das DataSet-Objekt auch aktualisieren. Sie müssendann die Methode Update der Klasse SqlDataAdapter aufrufen.

Page 310: Otmar Ganahl - C Sharp

C #

310 8

Diese verwendet die für diese Ausführung notwendigen Sql-Commands (für Einfügen, Löschen und Updaten).

Allerdings ist die Erstellung der SqlCommand-Objekte für dieseMembers nicht trivial. Sie können aber den Designer dazu be-nutzen, um einen Großteil des Codes erzeugen zu lassen,wenn auch nur mit einigen Tricks. Legen Sie dazu ein neues(leeres) Projekt an, und fügen Sie eine Quellcodedatei hinzu.Der Designer wird im Entwicklungssystem nur angezeigt,wenn in der Datei eine Klasse abgeleitet von Form existiert. Siebrauchen nun zwar den Designer, nicht aber ein Fenster. Sie le-gen daher in der Datei eine Dummy-Klasse, abgleitet von Forman.

using System;using System.Data;using System.Data.SqlClient;using System.Collections;using System.Windows.Forms;

class Wnd:Form{}

Damit das auch funktioniert müssen Sie natürlich noch fürsämtliche Assemblies Verweise einführen. Wechseln Sie nunzum Designer und schieben Sie per Drag&Drop die TabellePhoto im Server-Explorer in den Designer.

Connection und Adapterüber Designer erzeugen

Abb. 8.6

Page 311: Otmar Ganahl - C Sharp

ADO.NET3118

Wenn Sie nun einen Blick in den Code werfen, dann stellen Siefest, dass der Designer eine Menge von Code erzeugt hat, nurkönnen Sie diesen in dieser Form nicht brauchen! In der KlasseWnd wurden vom Designer vier Elemente vom Typ SqlCom-mand angelgt (je eines für Select, Delete, Update und Insert) so-wie eine SqlConnection-Objekt und ein SqlDataAdapter-Objekt.

Ändern Sie zuerst den Namen der Klasse Wnd auf PhotoData-AdapterHelper.

CD-BeispielPhotoAccess9

//class Wnd:Formclass PhotoDataAdapterHelper{ ...}

Dann ändern Sie die Methode InitializeComponents auf

//private void InitializeComponent()public SqlDataAdapter GetPhotoDataAdapter( SqlConnection con){...}

An der Stelle, wo das sqlConnection1.ConnectionString hart ko-diert belegt wird, belegen Sie diesen mit dem ConnectionStringdes Ü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 löschen Sie noch den Form-spezifischen Code und gebenein SqlDataAdapter-Objekt zurück, so wie es die Methode for-dert.

Page 312: Otmar Ganahl - C Sharp

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 Metho-de GetPhotoDataAdapter einen fix und fertig initialisiertenDataAdapter erzeugt, der auch für Updates von Datenbankenverwendet 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 füllen 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 hinzufügen DateTime dat = new DateTime(2001,7,5); t.Rows.Add(new object [] {99,"Urlaub Elba",dat,"Image99.gif"}); t.Rows.Add(new object []

Page 313: Otmar Ganahl - C Sharp

ADO.NET3138

{100,"Ankunft Elba",dat,"Image100.gif"});

//Datenbank updaten da.Update(ds,"Photos");}

Beim Aufruf da.Update verwendet der DataAdapter die ent-sprechend definierten SQL-Anweisungen!

DataGridZum Abschluss wird Ihnen noch ein Steuerelement vorgestellt,das ganz eng mit der Klasse DataSet zusammenarbeitet – auchwieder anhand eines Beispiels.

Legen Sie in der Projektmappe ein neues Projekt (Windows-Anwendung) mit dem Namen PhotoDataGrid an. Den Dateina-men Form1.cs nennen Sie in bewährter Weise um auf App.cs.Im Designer ändern Sie den Namen der Fensterklasse aufMainWnd um. Von der ToolBox bringen Sie nun perDrag&Drop ein Steuerelement DataGrid und geben diesemden Namen PhotoGrid. Außerdem fügen Sie der Form noch ei-nen Button mit dem Namen btUpdate hinzu. Das Text-Propertybelegen Sie mit „in Datenbank übernehmen“.

Im Quellcode fügen Sie der Klasse MainWnd zwei Member-Va-riablen hinzu:

DataSet ds;SqlDataAdapter da;

Im Konstruktor der Klasse MainWnd fügen Sie nun folgendenCode 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);

Page 314: Otmar Ganahl - C Sharp

C #

314 8

//DataSet erstellen und füllen ds = new DataSet(); da.Fill(ds,"Photos"); this.PhotosGrid.SetDataBinding(ds,"Photos");}

Nach dem Initialisieren der Steuerelemente erzeugen Sie ei-nen SqlDataAdapter, füllen damit das DataSet-Objekt und bin-den dieses DataSet-Objekt an das DataGrid-Objekt an.

this.PhotosGrid.SetDataBinding(ds,"Photos");

Da Sie in diesem Beispieil die PhotoDataAdapterHelper-Klasseaus dem vorherigen Beispiel verwenden, müssen Sie dieseauch herkopieren. Geben Sie auch noch den Namensraum Sys-tem.Data.SqlClient frei.

Wenn sich das Projekt nun fehlerfrei kompilieren lässt, dannwerden Ihnen die Daten der Tabelle im DataGrid dargestellt.Sie können im DataGrid sogar editieren, löschen und Einträgehinzufügen!

Was noch fehlt ist die Implementierung des Updatens beimButton btUpdate. Das gestaltet sich sehr einfach.

DataGrid in Aktion

Abb. 8.7

Page 315: Otmar Ganahl - C Sharp

ADO.NET3158

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

ZusammenfassungIn diesem Kapitel haben Sie nun einen Überblick über dieMächtigkeit der ADO.NET-Klassen kennen gelernt. Nicht be-handelt wurden die Klassen im Namensraum System.Da-ta.OleDb. Diese sind aber analog zu verwenden.

Mit ADO.NET bietet Microsoft den Entwicklern extrem leis-tungsfähige Klassen an. Die Produktivität sollte damit deutlichgesteigert werden. In den nächsten Kapiteln werden Sie nocheinige Male auf ADO.NET stoßen.

Page 316: Otmar Ganahl - C Sharp
Page 317: Otmar Ganahl - C Sharp

ASP.NET

Einleitung 318

ASP-Active Server Pages 319

Von ASP zu ASP.NET 325

ASP.NET-Anwendungen mitVS Studio entwickeln

333

Web-Steuerelemente 341

Kundenspezifische Steuerelemente 348

Composite Controls 358

Statusverwaltung unter ASP.NET 360

Zusammenfassung 372

Page 318: Otmar Ganahl - C Sharp

C #

318 9

EinleitungUrsprünglich war die Idee des Webs, u.a. Informationen inForm von Dateien einem größeren Publikum bereitzustellen.Um diese Dateien herunterzuladen, wurde ein eigenes Proto-koll entwickelt (http). Diese Information ist in der Datei inForm der Sprache HTML kodiert. Spezielle Browser könnendann diese Information in einer für den Menschen lesbarenForm darstellen. Die Dateien, die diesen HTML-Stream be-schreiben, sind natürlich statisch.

Bald schon kam Idee auf, diesen HTML-Stream dynamisch zuerzeugen. Die Web-Server-Hersteller boten dann den Web-Entwicklern Programmierschnittstellen zu ihren Servern an.

Microsofts Web-Server heißt Internet Information Server (IIS).Die Programmierschnittstelle und das Programmiermodell fürden IIS heißt ASP (Active Server Page). ASP stellt somit dieSchnittstelle des Windows-Betriebssystems zum World-Wide-Web dar. Für die Erzeugung von HTML-Streams kann der IIS aufsämtliche Ressourcen des Betriebssystems und des Netzwerkszugreifen.

ASP basiert auf COM und erlaubt damit die Entwicklung vonleistungsfähigen Web-Applikationen. Obwohl sehr leistungs-fähig, haben sich im Laufe der Zeit aber auch die Grenzen desProgrammiermodells gezeigt. Mit ASP.NET wurde das Pro-grammiermodell gänzlich überarbeitet und ist praktisch nichtmehr mit dem klassischen Modell vergleichbar. Lassen Sie sichin diesem Kapitel überraschen!

Tipp Auf Ihrem Entwicklungsrechner müssen Sie natürlich auch denIIS installiert haben. Wichtig ist, dass Sie diesen vor dem .NET-Framework installieren, da ansonsten der IIS nicht die Featuresder .NET-Laufzeitumgebung verwenden kann. Müssen Sie denIIS erst noch installieren, dann deinstallieren Sie das .NET-Fra-mework (und nur dieses, Visual Studio.NET müssen Sie nichtdeinstallieren), installieren anschließend den IIS und dannwieder das .NET-Framework!

Page 319: Otmar Ganahl - C Sharp

ASP.NET3199

ASP-Active Server PagesVorerst wird aber das gute alte ASP betrachtet und diskutiert.Dazu legen Sie unter c:\inetpub\wwwroot einen Ordner mitdem Namen AspOld an. In diesen Ordner kopieren Sie eine Da-tei mit dem Namen Sphere.asp (oder deutsch: Kugel.asp) mitfolgendem Inhalt:

CD-BeispielAspOld1

<!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>

<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>Fläche</h2> = " + _ FormatNumber(A)) End If </script> </BODY></HTML>

Page 320: Otmar Ganahl - C Sharp

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.

Das Volumen und die Oberfläche einer Kugel wird berechnet,wenn Sie in das Input-Feld den Radius eingeben und den But-ton Berechnen betätigen. Außerdem wird auch Datum undZeit ausgegeben. Wenn Sie für den Radius 4 eingeben und eineBerechnung durchführen, erscheint im Internet Explorer fol-gendes Bild:

Sphere.asp in Aktion

Abb. 9.1

Sphere.asp hat berechnet

Abb. 9.2

Page 321: Otmar Ganahl - C Sharp

ASP.NET3219

In diesem kleinen typischen Beispiel sehen Sie die wichtigstenPrinzipien einer ASP-Seite. Eine ASP-Seite ist aufgebaut wieeine HTML-Seite, nur dass die Seite Elemente besitzt, die vomIIS (Internet Information Server) dynamisch interpretiert wer-den. Die Interpretation dieser Elemente ergibt HTML-Streams,die dann in den Text eingefügt werden. Da bei jeder Abfrageder Seite diese Erzeugung der Elemente stattfindet, sind dieseHTML-Teile dynamisch.

Wenn Sie im Internet Explorer den Menüpunkt Ansicht > Quell-text anzeigen aktivieren, wird der HTML-Stream, den der Web-Browser „sieht“, dargestellt.

HTML Quellcode der Ansicht

<!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>

</BODY></HTML><h2>Volumen</h2> = 267,95<h2>Fläche</h2> = 200,96

Sie sehen hier deutlich: Der IIS (Internet Information Server) hateinige Elemente in der Seite dynamisch erstellt. Es gibt mehre-re Möglichkeiten, Texte in einer ASP-Seite dynamisch zu ge-stalten. Die einfachste Möglichkeit liegt in der Verwendungdes <% = %>-Elements. Der Wert nach dem Gleichheitszeichenwird in der HTML-Seite erscheinen. Dieses Element kann inner-halb des HTML-Textes jederzeit verwendet werden. Im Beispielwird dieses Element verwendet, um Datum und Zeit zu errech-nen und dynamisch in den HTML-Text einzublenden. Eine wei-

Page 322: Otmar Ganahl - C Sharp

C #

322 9

tere Möglichkeit ergibt sich in der Verwendung der MethodeWrite des ASP-Objektes Response. Diese Methode kann inner-halb eines <% %>-Elements oder in einem Script-Block verwen-det werden.

Scripts sind Abschnitte in einer ASP-Seite, die mit dem Tag<script> eingeleitet werden. Hier weiß der IIS, dass dieses Scriptdurchgeführt werden muss, um HTML-Stream zu erzeugen. DerHTML-Stream wird an der Stelle, wo sich das Script befindet,eingesetzt. ASP unterstützt die Scripting-Sprachen VBScript undJScript. Da diese Scripting-Sprachen auch auf die COM/COM+-Laufzeitumgebung zugreifen können, ist es möglich, ASP-Sei-ten sehr leistungsfähig zu gestalten. Kompliziertere Funktiona-litäten, wie Berechnungen, können so auf COM-Komponentenausgelagert werden, welche mit einer beliebigen Sprache ent-wickelt werden können.

Sie können eine Web-Seite grundsätzlich auf zwei Arten anfor-dern:

http-GEThttp-POST

Die POST-Anforderung unterscheidet sich von der GET-Anfor-derung darin, dass die Belegungen von Input-Steuerelemen-ten mit übertragen werden. Bei einer GET-Anforderung ist dasnicht der Fall.

Nun aber zurück 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-Sei-te. Konkret sind eine Eingabebox (type=”text“) und ein Button(type=”submit“) definiert. Beim Button wird auch das Attributvalue belegt, das dem Inhalt, in diesem Fall der Beschriftungdes Steuerelements entspricht. Das Attribut value ist beim In-put-Steuerelement Radius nicht belegt, und das Steuerele-ment ist somit beim Start der Seite leer. Bei der Betätigung

Page 323: Otmar Ganahl - C Sharp

ASP.NET3239

eines Steuerelements mit dem Attribut type="submit“ wirdimmer eine POST-Anforderung getätigt, d.h. es werden dieWerte sämtlicher Input-Steuerelemente in der Anforderungmit übertragen. Bei einem Submit wird diejenige Seite vomServer angefordert, die im Tag <form> unter dem Attribut ac-tion angegeben wird. Da im Beispiel unter dem Attribut actionauf die eigene Seite verwiesen wird, kommt auch dieselbe Sei-te wieder zurück und vermittelt dem Anwender, dass sich dieSeite gar nicht geändert 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>Fläche</h2> = " + FormatNumber(A)) End If </script>

Nun wird ein Script aktiv, das den Wert des Eingabefeldes Radi-us verarbeitet. In einer ASP-Seite kann über das ASP-Objekt Re-quest unter Angabe des Namens des Input-Steuerelements derWert erfahren werden. Das sind die Werte, die per http-POSTvom Browser mitkommen. Im Beispiel-Script wird zuerst ge-testet, ob das Input-Steuerelement eventuell leer ist (Len(r)>0).Wenn nicht, dann werden die mathematischen Operationendurchgeführt und die Ergebnisse über das Response-Objekt inden HTML-Stream eingefügt.

Eines fällt an diesem Beispiel allerdings unangenehm auf. DerWert des Eingabefeldes Radius ist nach dem http-GET Aufrufwieder leer. Warum ist dies so? Für den Internet InformationServer ist jede Anforderung vollkommen neu. Das Web ist zu-standslos. Wenn Sie aber dennoch möchten, dass der Wert dervorherigen Seite dargestellt wird, dann müssen Sie einen klei-nen Trick anwenden. Sie müssen das Attribut value des Input-Feldes Radius dynamisch belegen.

Page 324: Otmar Ganahl - C Sharp

C #

324 9

Dies kann wie folgt geschehen:

CD-BeispielASPOLD2

<input type="text" name="Radius" value="<%=(Request("Radius")%>">

Im Fall einer http-POST Anfrage haben Sie ja den Wert der In-put-Steuerelemente und damit auch des Steuerelementes Ra-dius im Request-Objekt zur Verfügung. Diesen Wertverwenden Sie zur Belegung des Attributs value des Steuerele-ments mit dem Namen Radius.

Es sollte Ihnen aber klar sein, dass damit nur scheinbar ein Sta-tus zwischen den Anforderungen gehalten wird. Sämtlichenotwendigen Daten müssen vom Browser in der http-Anfragemitgeliefert werden.

DiskussionMit Scripting-Techniken wurden gänzlich neue Funktionalitä-ten im Web ermöglicht. Das Konzept ist bewährt und schonlänger im Einsatz. Mit der Erfahrung haben sich aber auch eini-ge Schwierigkeiten und Unzulänglichkeiten herauskristalli-siert, die nun mit ASP.NET entschärft wurden. Vier Aspektewerden kurz vorgestellt.

Kugelmathematik

Abb. 9.3

Page 325: Otmar Ganahl - C Sharp

ASP.NET3259

TypisierungScripting-Sprachen verwenden keine strenge Typisierung wiez.B. C++ oder auch VB in gewissem Maße. Dies ist zwar u.U. an-genehm, stellt aber ein nicht unbedeutendes Performance-Problem und auch Fehlerquelle dar. ASP.NET kennt streng typi-sierte Datentypen.

InterpreterASP verwendet keinen kompilierten Code, sondern interpre-tiert die Scripting-Anweisungen. Durch die Verlagerung vonumfangreichen Operationen auf COM-Komponenten könnenhier Verbesserungen erzielt werden. Die Installierung vonCOM-Komponenten ist allerdings für viele meist eine Heraus-forderung und es kam hier immer wieder zu Irritationen.ASP.NET kennt kompilierten Code und ist daher bedeutendperformanter.

Trennung zwischen Sicht und CodeASP-Seiten kennen die Trennung zwischen Ansicht und Funkti-on nur bedingt. HTML und Scripting-Code findet sich vielfachauf derselben Seite. Dies erschwert die Wiederverwendungvon Codeteilen. Unter ASP.NET werden Sie sehen, dass sämtli-cher funktionaler Code vom HTML-Code getrennt werdenkann. Dies vereinfacht die Wartbarkeit von Web- Applikatio-nen deutlich.

ObjektorientierungVBScript und damit auch das ASP-Programmiermodell sindnicht objektorientiert. Damit wird die Entwicklung und Pflegevon komplexen Web-Anwendungen erschwert. Unter ASP.NETwird die objektorientierte Entwicklung von Web-Applikatio-nen möglich.

Im nächsten Kapitel wird genau dieses Beispiel in ASP.NET rea-lisiert. Sie werden dann die Unterschiede deutlich erkennen.

Von ASP zu ASP.NETDas ASP-Eingangsbeispiel soll auf das ProgrammiermodellASP.NET portiert werden. Hierzu erzeugen Sie unter c:\inet-pub\wwwroot einen neuen Ordner mit dem Namen ASPNET,wo Sie die Seiten unterbringen werden. Dann legen Sie mit ei-

Page 326: Otmar Ganahl - C Sharp

C #

326 9

nem beliebigen Editor eine Datei mit dem Namen Shere.aspxan. An der Dateierweiterung .aspx erkennt der IIS, dass es sichum eine ASP.NET-Datei handelt.

Web-SteuerelementeCD-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="Fläche" 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(); Fläche.Text = "Fläche =" + A.ToString(); } </script> </BODY></HTML>

Was ist hier neu? Die Dateierweiterung .aspx ist für IIS die In-formation, dass es sich um eine ASP.NET-Seite handelt.ASP.NET-Elemente dürfen in <%= %>-Elementen jederzeit C#-Aufrufe beinhalten. Wichtig ist nur, dass das Assembly mit den

Page 327: Otmar Ganahl - C Sharp

ASP.NET3279

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. Die-se werden mit dem Namensraum asp: eingeleitet. Das Input-Element Radius aus dem ersten Beispiel wird nun ersetzt durchein WebForm-Control, im Speziellen durch ein TextBox-Steuer-element. Die Schaltfläche wird durch ein Button-WebForm-Control ersetzt. Das Attribut RunAt=”Server“ bedeutet, dass dieSteuerelemente auf dem Server interpretiert werden. Dasheißt, dass ASP.NET hier letztendlich HTML-Code erzeugt, derdann zum Client geschickt wird.

Im Script, das nun C#-Code darstellt, kann auf das Steuerele-ment mit dem Namen, der im Attribut id angegeben ist, zuge-griffen werden. Das Auslesen aus dem Steuerelement erfolgtnicht mehr durch das ASP-Objekt Request, sondern mit Anwei-sungen wie:

string sr = Radius.Text;

Im Script-Code kann in einer objektorientierten Weise auf dasWeb-Steuerelement zugegriffen werden.

Ebenfalls nicht verwendet wird das ASP-Objekt Response, son-dern es wird direkt das Text-Property des Web-SteuerelementsLabel belegt.

Volumen.Text= V.ToString();

Auch hier gilt wieder, dass der Ausdruck Volumen durch die iddes Labeletiketts bei der Definition begründet ist.

<asp:Label id="Volumen" runat="Server" />

Es ist Ihnen sicherlich auch aufgefallen, dass sich die Seite dieZustände über die Rückmeldungen hinweg gemerkt hat. Alldie lästige Arbeit übernehmen die Web-Controls.

Weiter stellen Sie fest, dass das Tag <form> keine weiteren At-tribute besitzt, insbesondere kein active-Attribut und kein me-thod-Attribut. Wenn Sie aber im Browser den HTML-Codebetrachten, dann sehen Sie sehr wohl, dass diese Attribute imHTML-Stream letztendlich vorkommen.

Page 328: Otmar Ganahl - C Sharp

C #

328 9

Eine wichtige Feststellung ist auch, dass sämtliche Arbeit, diemit der Anzeige der UI-Schnittstelle zu tun hat, von den Web-Steuerelementen abgedeckt ist (z.B. Zustände der Steuerele-mente erhalten). Die Ereignisse der Steuerelemente werden inFunktionen abstrahiert.

Die Denkweise der „Rückmeldungen“ wird mit dem ASP-Modell weg abstrahiert. Für den Entwickler entsteht also einModell, das mit herkömmlichen Programmiermodellen ver-gleichbar ist, und daher natürlich auch strukturiertes Entwi-ckeln besser unterstützt. Noch nicht gelöst ist die Trennungzwischen Sicht und Funktion.

Trennung zwischen Sicht und FunktionIm nächsten Beispiel trennen Sie die Funktionalität von derSicht. Ändern Sie den Code in der Datei Sphere.aspx wie folgt:

CD-BeispielASPNET2

<%@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="lFläche" runat="Server" /> </form> </BODY></HTML>

Neu ist das einleitende Direktive <%@Page..., auf das gleicheingegangen wird. Ansonsten finden sich im Code nur nochdie Definitionen von Web-Controls. Auch sind sämtlichen Aus-gabeelementen Steuerelemente vom Typ Label zugeordnet.Alle Controls erhalten über das Attribut id einen Namen. Eben-

Page 329: Otmar Ganahl - C Sharp

ASP.NET3299

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 Eigen-schaften von einer Klasse SphereMath erbt (Inherits), was auchimmer das bedeuten soll. Mit Src="Sphere.cs“ wird angegeben,wo diese Klasse definiert ist. In diesem Fall in der Datei Sphe-re.cs.

Legen Sie daher im Ordner zusätzlich mit einem beliebigenEditor eine Datei Sphere.cs an, und geben Sie folgenden C#-Code ein.

using System;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;

public class SphereMath: Page{ protected Label lMainText; protected Label lRadius;

protected TextBox tbRadius; protected Button btCompute; protected Label lDatum; protected Label lVolumen; protected Label lFläche;

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();

Page 330: Otmar Ganahl - C Sharp

C #

330 9

lFläche.Text = "Fläche =" + A.ToString(); }}

In dieser Datei wird eine Klasse SphereMath definiert. DieseKlasse ist abgeleitet von der Klasse Page, die im NamensraumSystem.Web.UI definiert ist. Die Klasse Page repräsentiert eineWeb-Seite. In der Klasse SphereMath sind nun Member-Variab-len vom Typ Label, TextBox und Button vereinbart. Diese Typensind im Namensraum System.Web.UI.WebControls zu finden.(Verwechseln Sie diese Steuerelemente nicht mit denen imNamensraum System.Windows.Forms!) Die Namen dieserMembers korrespondieren mit den Namen der Elemente, die inder .aspx-Seite definiert wurden.

Die Methode Page_Load wird, wie Sie richtig vermuten, beimLaden der Seite aufgerufen. In dieser Methode können Sie nunsämtliche Steuerelement-Objekte initialisieren, indem Sie aufdie Eigenschaften der Klassen zugreifen. Sie sehen, Sie könnennun die Seite als ein Objekt mit Steuerelementen betrachten!Die C#-Quellcode-Datei, die mit er .aspx-Seite korrespondiertwird code-behind genannt.

Öffnen Sie nun die Seite SphereMath.aspx mit dem Browser!Eventuelle syntaktische Fehler werden im Browser dargestellt.

Eine Unschönheit ist im Code noch vorhanden. In der .aspx-Sei-te 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 Da-tei Sphere.cs fügen Sie nun dem Event Click die Bearbeitungs-routine hinzu. Sie sehen, es kann hier der bekannte Event-Machanismus verwendet werden.

CD-BeispielASPNET3

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

Page 331: Otmar Ganahl - C Sharp

ASP.NET3319

Verwendung von kompiliertem CodeBeim erstmaligen Abrufen einer .aspx-Seite wird der IIS fest-stellen, dass der zu dieser Seite zugehörige Quellcode nochkompiliert werden muss. ASP.NET kompiliert den Quellcodeund cached diesen. (ASP.NET erzeugt intern sogar noch eine ei-gene Klasse und verwendet die Klasse im Quellcode als Basis-klasse!) Der Kompiliervorgang kann dauern (bei komplexenSeiten auch einige Sekunden), aber das findet nur beim erstenAufruf statt. Bei allen weiteren Aufrufen wird dann die gecach-te Version verwendet. Sie sehen also, Sie können gänzlich ohneEntwicklungssystem, nur allein mit einem Texteditor komple-xe Web-Applikationen erstellen.

Diesen anfänglichen Kompiliervorgang können Sie auch ver-meiden, wenn Sie den code-behind explizit zu einem Assemblykompilieren und dieses dann in den Web-Ordner unterbrin-gen. Sie müssen dabei allerdings einiges berücksichtigen. Imnächsten Beispiel wird die Web-Applikation Sphere in kompi-lierter Form untergebracht.

Erzeugen Sie erst einmal aus Spere.cs ein Assembly. Dazu star-ten Sie den Visual Studio .NET Comman Prompt, wechseln inden Ordner der Applikation (/inetpub/wwwroot/aspnet) undgeben folgendes Kommando ein:

csc /t:library sphere.cs

Im Ordner der Applikation (/inetpub/wwwroot/aspnet) richtenSie einen Unterordner /bin ein und kopieren in dieses das ausdem Kompiliervorgang entstandene Assembly sphere.dll.

Nun fehlt noch eine kleine Änderung in der Datei Sphere.aspx.Die Page-Direktive beschränkt sich nun auf die Angabe derKlasse.

<%@ Page Inherits="Sphere" %>

ASP.NET sucht sich dann die Klasse in den Assemblies, die imUnterordner /bin zu finden sind.

Leider funktioniert die Anwendung aber noch nicht, es wirdeine Fehlermeldung auftreten. Sie müssen nämlich dem Ord-ner der Applikation einen so genannten Anwendungsnamengeben. Dazu öffnen Sie den Internet Informationsdieste Mana-ger über Start > Einstellungen > Systemsteuerung > Verwaltung.

Page 332: Otmar Ganahl - C Sharp

C #

332 9

Im Navigationsbaum StandardWebsite finden Sie den Ordnerder Applikation ASPNET. Öffnen Sie für diesen Ordner über dasKontextmenü den Eigenschaftsdialog. Erzeugen Sie dann mitdem Button Erstellen einen Anwendungsnamen und löschenSie die Schreib- und Lesezugriffsberechtigungen.

Nun sollte es funktionieren!

ZusammenfassungDas Web-Programmiermodell ASP.NET unterscheidet sich voll-kommen vom herkömmlichen ASP-Programmiermodell. EinWeb-Seite unter ASP.NET besteht im Allgemeinen aus zweiDateien, nämlich aus einer .aspx-Datei und einer Code-Datei(oder einer kompilierten Form). Diese beiden Dateien korres-pondieren zueinander.

Die .aspx-Datei enthält vorteilhaft ausschließlich HTML-Einträ-ge und asp-Steuerelementeinträge. Die zugehörige Code-Da-tei definiert mindestens eine Klasse, die von Page abgeleitetist. Diese Klasse repräsentiert die Web-Seite. Sämtliche Steuer-

ASPNET-Eigenschaften

Abb. 9.4

Page 333: Otmar Ganahl - C Sharp

ASP.NET3339

elemente, die in der .aspx-Seite eingetragen sind, kommen inder Klasse als Member-Variablen vor. In der .aspx-Datei wirddie Verbindung zur Code-Datei in Form einer Direktive (Page)hergestellt.

Bevor Sie nun im Kapitel weitermachen, stellen Sie sicher, dassSie die Zusammenhänge zwischen .aspx-Datei, .cs-Datei undAssembly .dll verstanden haben.

ASP.NET-Anwendungen mit VS Studio entwickelnGanz bewusst wurde das erste Beispiel ohne Verwendung desEntwicklungssystems erstellt: So sind die Zusammenhängeklarer erkennbar. In diesem Kapitel werden Sie kennen lernen,wie Sie Visual Studio.NET verwenden können, um effizientWeb-Anwendungen zu produzieren.

Erzeugen Sie zuerst eine neue Projektmappe mit dem NamenASP. In dieser Projektmappe legen Sie ein C#-ASP.NET-Projektan.

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

ASP.NET-Webanwendung erzeugen

Abb. 9.5

Page 334: Otmar Ganahl - C Sharp

C #

334 9

Sie werden feststellen, dass der Wizard unter c:\inetpub\www-root einen neuen Ordner MathServices angelegt hat. In diesemOrdner befinden sich nun die Dateien zu diesem Projekt. Impersonalisierten Ordner („Dokumente und /Einstellun-ge/UserXXX/\VSWebCashe“) wird übrigens das gesamte Pro-jekt gecached. Werfen Sie mit dem Datei-Explorer einen Blickin den Ordner MathServices (dieser befindet sich im c:\inet-pub\wwwroot).

Neben den Projektdateien finden Sie hier die Datei WebForm1.aspx mit der zugehörigen (code-behind) Datei WebForm1.aspx.cs. Außerdem hat der Assistent auch noch eine .resx-Dateifür diese Seite angelegt. Sie können unter ASP.NET Ressourcenin gleicher Weise verwenden, wie Sie das bei der Windows-Programmierung kennen gelernt haben.

Ebenfalls wurde vom Assistenten eine Datei Global.asax mitGlobal.asax.cs inklusiv einer .resx-Datei angelegt. Die Datei mitdem Namen Global.asax ist analog zur Datei Global.asa unterASP zu verstehen. Dazu aber später mehr im Detail.

Die Datei Web.Config stellt die XML-Konfigurationsdatei beiASP.NET-Anwendungen dar. Diese wurden schon im Kapitel 7:Konfigurationsdateien erwähnt. Kehren Sie nun zum Entwick-lungssystem zurück. Ändern Sie im Projektmappen-Explorererst einmal den Namen der Datei WebForms1.aspx mit demKontextmenü Umbenennen auf Sphere.aspx.

Dateien eines Projektesvom Typ .NET-Web-

Anwendung

Abb. 9.6

Page 335: Otmar Ganahl - C Sharp

ASP.NET3359

Wenn Sie einen Doppelklick im Projektmappen-Explorer aufdie Datei Sphere.aspx tätigen, dann stellt sich diese Seite imDesigner dar (vgl. Designer bei Windows-Projekten). Links un-ten befindet sich ein Umschaltbutton auf die HTML-Sicht, diedann den Code der ASP-Seite darstellt. Wenn Sie im Projekt-mappen-Explorer die Seite Sphere.aspx markieren, können Siemit der rechten Maustaste im Kontextmenü Code anzeigen zurzugehörigen code-behind, in diesem Fall eine C#-QuelldateiSphere.aspx.cs navigieren (Sie können auch über die Werk-zeugleiste des Projektmappen-Explorers die Sichten umschal-ten).

Stellen Sie nun die code-behind Datei Sphere.aspx.cs dar!

using System;using System.Collections;using System.ComponentModel;using System.Data;using System.Drawing;using System.Web;using System.Web.SessionState;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.HtmlControls;

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() {

Page 336: Otmar Ganahl - C Sharp

C #

336 9

this.Load += new System.EventHandler(this.Page_Load);

} #endregion }}

Sie sehen hier, dass ein Namensraum MathServices (entspre-chend dem Projektnamen) erstellt wurde. In diesem Namens-raum befindet sich die Definition einer Klasse WebForm1, dievon Page abgeleitet ist. Geben Sie dieser Klasse auch einenbesseren Namen, nämlich SphereMath.

Im Code finden Sie die schon bekannte Methode Page_Loadund die Methode OnInit, die ihrerseits die Methode Initialize-Component aufruft. Wenn Sie hier Analogien zum .NET-Win-dows-Programmiermodell sehen, dann sind Sie vollkommenrichtig.

Navigieren Sie nun einmal in die HTML-Ansicht der Datei Sphe-re.aspx. In der einleitenden Direktive finden Sie den Eintrag In-herits="MathServices.SphereMath“, also den Verweis auf dieKlasse, die diese Seite repräsentieren soll (vgl. Diskussion inKapitel 8: ADO.NET!).

Fügen Sie über den Designer folgende Web-Steuerelementehinzu:

Label lMainText

Label lRadius

TextBox tbRadius

Button btCompute

Label lDatum

Label lVolumen

Label lFläche

Weisen Sie allen Steuerelementen im Eigenschaftsfenster dieoben angegebenen Namen zu (ID) und löschen Sie sämtlicheText-Eigenschaften der verwendeten Steuerelemente.

Page 337: Otmar Ganahl - C Sharp

ASP.NET3379

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

Für jedes Steuerelement wurde nun ein Eintrag getätigt, ähn-lich wie Sie es aus dem einführenden Beispiel her kennen.

Jetzt werfen Sie einmal einen Blick in die Datei Sphere.aspx.cs(code-behind).

protected Label lMainText;protected Label lRadius;protected TextBox btRadius;protected lDatum;

Anordnen von Web-Steuerelementen im Designer

Abb. 9.7

HTML-Ansicht

Abb. 9.8

Page 338: Otmar Ganahl - C Sharp

C #

338 9

protected Button btCompute;protected lVolumen;protected lFläche;

In der Klasse SphereMath wurden Member-Variablen eingetra-gen, die zu den Steuerelementen der Seite Sphere.aspx korres-pondieren. Sie können nun sämtliche Funktionalität hier inForm von C#-Code programmieren.

Initialisieren Sie zuerst einmal die Steuerelemente in der Me-thode 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 größer erscheint, wird die Eigenschaft Fontdes Labels entsprechend manipuliert. Im Label Datum soll dieaktuelle Zeit erscheinen und der Button soll rot dargestelltwerden, daher die Eigenschaft des Buttons BackColor auf dieFarbe Color.Red stellen.

Starten Sie einmal die Applikation mit S+%. Sie können dieApplikation also auch aus dem Entwicklungssystem herausstarten. Oder aber über die URL http://localhost/MathServices/Sphere.aspx von einem Browser.

Es fehlt noch die mathematische Berechnung der Kugeldaten.Diese soll beim Drücken des Buttons Compute stattfinden.Dazu müssen Sie sich beim Event Click des Buttons anmelden.Sie können dies aber auch über den Designer durchführen.Wechseln Sie dazu in die Designer-Ansicht der Datei Sphere.as-px und markieren Sie das Steuerelement btCompute und las-sen Sie sich im Eigenschaftsfenster die Ereignisse desSteuerelements anzeigen. Ein Doppelklick auf das EreignisClick erzeugt Ihnen im Code die entsprechende Behandlungs-

Page 339: Otmar Ganahl - C Sharp

ASP.NET3399

routine und meldet diese auch beim Steuerelement an (Siekennen diesen Vorgang aus dem Kapitel 6: Windows-Applikati-onen).

Die Behandlungsroutine „füllen“ Sie nun mit dem Ihnen schonbekannten 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(); lFläche.Text = "Fläche =" + 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(Menüpunkt Ansicht > Quelltext anzeigen), den der IIS zumBrowser gesandt hat. Sie sehen, es ist reiner HTML-Code, denASP.NET erzeugt hat!

Wenn Sie im Beispiel bei der Eingabe keine gültige Zahl einge-ben, kommt es zu einem Fehler. ASP.NET gibt dann eine ent-sprechende Meldung zurück.

Die Zeile

double r = Double.Parse(sr);

Hinzfügen von Ereignissen im Eigenschaftsfenster

Abb. 9.9

Page 340: Otmar Ganahl - C Sharp

C #

340 9

wird nämlich fehlschlagen und eine Exception auslösen. Dieauftretende Fehlermeldung ist nicht gerade anwenderfreund-lich. Ä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(); lFläche.Text = "Fläche =" + A.ToString(); } catch(Exception ex) { lVolumen.Text = ex.Message; }}

Schützen Sie den Code mit einem try-Block. Im Fall eines Feh-lers verwenden Sie das Label lVolumen, um die Fehlermeldungauch auszugeben.

Ausgabe Exception

Abb. 9.10

Page 341: Otmar Ganahl - C Sharp

ASP.NET3419

Debugging von ASP.NET-AnwendungenMit ASP.NET lassen sich ASP-Applikationen nun auch Debug-gen. Das ist erwähnenswert, weil dies unter ASP (alt) nichtmöglich war. Sämtliche Features des Debuggers sind nun an-wendbar, wie das Setzen von Breakpoints, Auslesen von Wer-ten aus Objekten usw.

ZusammenfassungMit Visual Studio.NET lassen sich schnell und effizient Web-An-wendungen entwickeln. Ein wenig Zeit braucht es, bis Sie die„Bedienung“ des Entwicklungssystems beherrschen.

ASP.NET-Anwendungen, die mit Visual Studio.NET erzeugtwerden, verwenden das code-behind-Prinzip. Im Ordner befin-den sich neben dem C#-Quellcode auch die kompilierten Ver-sionen!

Hinweis bei der Verwendung der CD-Beispiele:

Wenn Sie die Beispiele von beiliegenden CD verwenden möch-ten, können Sie diese nicht direkt auf das Dateisystem kopie-ren und mit dem Entwicklungssystem öffnen. FolgendeVorgehensweise wird empfohlen: Legen Sie das Projekt mit Vi-sual Studio.NET in eben gezeigter Form an, löschen Sie dieQuellcode-Dateien im Projektordner und ersetzen Sie diesemit den Dateien auf der CD.

Web-Steuerelemente

Gemeinsame EigenschaftenDie Web-Steuerelemente sind im Namensraum Sys-tem.Web.UI.WebControls untergebracht. Sie sind alle von derBasisklasse WebControl abgleitet. Daher haben diese einigegemeinsame Eigenschaften. Alle diese Eigenschaften könnenSie dynamisch verändern, indem Sie auf die entsprechendenMembers der Objekte zugreifen. Hier sind einige Eigenschaftenaufgelistet, die alle Web-Steuerelemente haben:

Page 342: Otmar Ganahl - C Sharp

C #

342 9

Im Folgenden soll eine Seite TestPage erzeugt werden, auf dersich einige Steuerelemente befinden. Die Absicht liegt darin,dass Sie die Verwendung einiger wichtiger Steuerelemente er-lernen. Wenn Sie diese beherrschen, machen Ihnen die weite-ren Steuerelemente keine Schwierigkeiten. Es wird daher aufeine Erklärung der weiteren Web-Steuerelemente verzichtet.Sollten Sie ganz spezielle Informationen benötigen, dannschlagen Sie in der MSDN nach.

BackColor HintergrundfarbeBorderColor RahmenfarbeBorderStyle RahmenartBorderWith RahmenbreiteFont SchriftartForeColor VordergrundfarbeHeight Höhe des SteuerelementsID ID des SteuerlementsTabIndex Position der Tab-ReihenfolgeToolTip Tooltip des SteuerlementsVisible SichtbarkeitWidth Breite

Test-Steuerelemente

Abb. 9.11

Page 343: Otmar Ganahl - C Sharp

ASP.NET3439

Legen Sie eine neue ASP.NET-Web-Anwendung mit dem Na-men TestPage an. Die vom Assistenten erzeugte DateiWebForm1.aspx nennen Sie in MainPage.aspx um. Die Klasse inder Datei MainPage.aspx.cs WebForm1 nennen Sie um aufMainPage.

HyperLink-SteuerelementCD-BeispielTestPage

Fügen Sie über den Designer der Seite ein Steuerelement vomTyp HyperLink hinzu und geben Sie diesem den ID-Wert HL. ImC#-Code wird dann das Steuerelement ebenfalls ersichtlich.

protected System.Web.UI.WebControls.HyperLink HL;

In der Methode Page_Load können Sie dieses Steuerelementnun 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 einemKlick auf den Text geöffnet wird.

Image-SteuerelementCD-BeispielTestPage

Interessant ist das Web-Steuerelement Image. Platzieren Sieein Image-Steuerelement und weisen Sie einen ID-Namen zu.In der Klasse MainPage wurde folgendes Element hinzugefügt:

protected System.Web.UI.WebControls.Image Im;

Es genügt, wenn Sie die Eigenschaft ImageUrl belegen. WennSie keinen Pfad angeben, dann sucht .NET im Pfad derASP.NET-Applikation. Belegen Sie das Member ebenfalls in derMethode Page_Load. Stellen Sie auch sicher, dass sich aucheine Grafikdatei an der angegebenen Stelle befindet.

Page 344: Otmar Ganahl - C Sharp

C #

344 9

private void Page_Load(object sender, System.EventArgs e){ ... ...

Im.ImageUrl = @"newton.gif";

}

CheckBox-SteuerelementFügen Sie über den Designer der Web-Seite ein Label mit demNamen lTime und eine CheckBox mit dem Namen cbLongViewein. Fügen Sie auch gleich mit dem Designer eine Bearbei-tungsroutine für das Ereignis CheckedChanged der CheckBoxhinzu.

Die Bearbeitungsroutine implementieren Sie wie folgt:

CD-BeispielTestPage

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 Steu-erelements fest und belegen in Abhängigkeit davon das LabellTime einmal mit einer „langen“ bzw. „kurzen“ Zeitdarstel-lung.

Event über Designerhinzufügen

Abb. 9.12

Page 345: Otmar Ganahl - C Sharp

ASP.NET3459

Das Label lTime wird in Page_Load mit der „kurzen“ Zeitdar-stellung 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 KlasseCheckBox auf true setzen. Warum? Wenn Sie im Browser dieCheckBox betätigen, dann muss eine http-POST Anforderunggestellt werden. Das machen die Web-Steuerelemente nichtstandardmäßig (Ausnahme: Steuerelement Button).

Beachten Sie das, man kann wegen dieses Umstandes schonstundenlang nach einem vermeintlichen Fehler suchen.

RadiobuttonListFügen Sie mit dem Designer der Web-Seite ein RadioButton-List-Steuerelement mit dem Namen rblTimeView hinzu undlassen Sie sich auch gleich eine Bearbeitungsroutine für das Er-eignis SelectedIndexChanged erzeugen. In der MethodePage_Load initialisieren Sie dieses Steuerelement wie folgt:

CD-BeispielTestPage

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; }}

Page 346: Otmar Ganahl - C Sharp

C #

346 9

Sie fügen in der Initialisierung nun dieser RadioButtonList ingezeigter Form Items hinzu. Die Methode Page_Load wird beijeder Seitenanforderung aufgerufen. D.h. bei einer GET- alsauch bei einer POST-Anforderung. Die Items dürfen aber nurbeim erstmaligen Aufruf (GET-Aufruf) belegt werden (ansons-ten würden bei jedem Aufruf der RadioButtonList zwei neueItems zugeordnet werden). Die Klasse Page besitzt ein MemberIsPostBack. Hier kann festgestellt werden, ob die Seite auf-grund einer POST- oder aber einer GET-Anforderung erzeugtwird. In der gezeigten Form findet eine Initialisierung nur beieiner GET-Anforderung statt. Vergessen Sie auch nicht, dasMember 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 einenButton der RadioButtonList betätigen. Sie stellen in gezeigterForm fest, welcher Button aktiviert wurde und setzen das La-bel lTime entsprechend. Außerdem belegen sie auch die Check-Box cbLongView neu, da sich ja das Anzeigeformat des LabelslTime geändert hat.

Richtigerweise müssen Sie auch noch die Behandlungsroutineauf das Ereignis CheckedChanged der CheckBox cbLongViewentsprechend nachziehen.

private void cbLongView_CheckedChanged( object sender, System.EventArgs e){ if(cbLongView.Checked == true) {

Page 347: Otmar Ganahl - C Sharp

ASP.NET3479

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

ListBoxFügen Sie nun der Web-Seite noch ein Steuerelement Listboxhinzu. Geben Sie diesem den Namen lbPhotos und erzeugen Sieauch gleich eine Behandlungsroutine zum Ereignis Selected-IndexChanged der Klasse ListBox. Dieses belegen Sie in derMethode Page_Load analog zum RadioButtonList.

CD-BeispielTestPage

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 kön-nen. Der Code der Behandlungsroutine muss daher wie folgtaussehen:

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";

Page 348: Otmar Ganahl - C Sharp

C #

348 9

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

ZusammenfassungSie haben nun einige Steuerelemente kennen gelernt. ASP.NETbietet noch eine Reihe weiterer interessanter Steuerelementean. Experimentieren Sie auch mit diesen Steuerelementen.

Kundenspezifische SteuerelementeDie Tatsache, dass Web-Steuerelemente Objekte sind, erlaubtauch die Erzeugung von eigenen kundenspezifischen Steuer-elementen.

Sie werden nun in einem einfachen Beispiel die dahinter lie-gende Struktur kennen lernen. Sie ist relativ komplex, abernicht kompliziert. Einmal erkannt, sollte diese Ihnen dann kei-ne Schwierigkeiten mehr bereiten.

Basisklasse ControlErzeugen Sie in der Projektmappe ein neues Projekt vom TypWeb-Steuerelementbibliothek, und geben Sie diesem den Na-men MyWebControls. Dieses Projekt wird dann ein Assemblyerzeugen, das sämtliche eigens entwickelten serverseitigenWeb-Steuerelemente beinhalten wird. Ein Blick auf den Pro-jektmappen-Explorer zeigt, dass eine C#-Quellcodedatei mitdem Namen WebCustomControl1.cs angelegt wurde. Benen-nen Sie diese Quelldatei um auf MyWebControls.cs. In dieserQuellcodedatei wird ein Namensraum MyWebControls ange-legt und gleich auch ein Rumpf-Code für ein Web-Steuerele-ment erzeugt. Löschen Sie alles, denn aus didaktischenGründen werden Sie ein Steuerelement von Grund auf erzeu-gen. Geben Sie dann folgenden Code ein:

CD-BeispielMyWebControls1

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

namespace MyWebControls

Page 349: Otmar Ganahl - C Sharp

ASP.NET3499

{ 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 Mo-ment alles. Wenn das Steuerelement eine Ausgabe tätigensoll, wird über das Objekt output vom Typ HtmlTextWriter einHTML-Stream ausgegeben. Das geschieht programmtech-nisch, wenn Sie die Methode Write des HtmlTextWriter-Ob-jekts aufrufen.

Dieser Code sollte kompilierfähig sein und ein Assembly mitdem Namen MyWebControls.dll erzeugen.

In einem weiteren Projekt werden Sie nun das neue Steuerele-ment testen. Dazu erzeugen Sie in der Projektmappe eine neueASP.NET-Webanwendung und geben diesem Projekt den Na-men TestMyWebControls. Den Namen der Datei WebForm1.as-px benennen Sie auf MainForm.aspx um.

Da Sie nun eigene Web-Steuerelemente verwenden wollen,die sich im Assembly MyWebControls befinden, müssen Sie ei-nen Verweis zu diesem Assembly herstellen.

In der HTML-Ansicht der Datei MainForm.aspx ist nun aller-dings eine spezielle Register-Direktive notwendig. Fügen Siediese 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, indem sich die Steuerelemente befinden, bekannt gegeben. Für

Page 350: Otmar Ganahl - C Sharp

C #

350 9

dieses Steuerelement wird auch ein TagPrefix definiert. Damitkönnen Sie nun diese Steuerelemente der ASP-Seite hinzufü-gen. Leider können Sie das nicht über den Designer erledigen.Sie müssen dies händisch durchführen.

CD-BeispielTestMyWebControls1

<form id="Form1" method="post" runat="server"> <MyConts:Time id="myTime" runat="server" /></form>

Sie sehen, statt dem Prefix asp (das für die ASP.NET-Steuerele-mente verwendet wird) verwenden Sie hier das Prefix My-Conts, das Sie in der Register-Direktive registriert haben. Danngeben Sie den Namen des Steuerelements an.

Ein Aufruf dieser Seite ergibt folgende Ausgabe im Browser:

Kundenspezifische AttributeStatten Sie nun das Steuerelement Time mit einer spezifischeEigenschaft TimeView aus. Über diese Eigenschaft soll die For-matierung der Zeit gesteuert werden können. Im Projekt My-WebControls definieren Sie zuerst eine Enumeration TimeViewmit zwei Einträgen LongTime und ShortTime.

CD-BeispielMyWebControls2

public enum TimeView{ LongTime,ShortTime}

KundenspezifischesSteuerelement im Browser

Abb. 9.13

Page 351: Otmar Ganahl - C Sharp

ASP.NET3519

public class Time:Control{ public TimeView View = TimeView.LongTime;

protected override void Render( HtmlTextWriter output) { string Time; if(View == TimeView.ShortTime) Time=DateTime.Now.ToShortTimeString();

else Time = DateTime.Now.ToLongTimeString();

output.Write("<h1>" + Time +"</h1>"); }}

Fügen Sie der Klasse Time die Member-Variable View hinzuund steuern Sie die Erzeugung des Ausgabe-Strings in der Me-thode Render über den Status der Member-Variable View.

In der HTML-Ansicht der .aspx-Seite können Sie nun unter Ver-wendung des Namens der Member-Variable View die Eigen-schaft setzen.

CD-BeispielTestMyWebControls2

<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 indiesem Falle folgende Ansicht:

Das ist schon bemerkenswert. In der .aspx-Seite definieren Siein Form von HTML-Attribute die Eigenschaften des Steuerele-ments. Attribute sind aber Strings, und daher muss eine Kon-vertierung in intelligenter Form durchgeführt werden. Ist dieMember-Variable ein String, wird eine simple Zuweisung statt-finden, ist die Member-Variable aber ein integer oder float, hathier eine Konvertierung string nach Zahl statt zu finden. Dasfunktioniert sogar für Enumerationen, wie obiges Beispielzeigt. Sollte eine Konvertierung aus irgendwelchen Gründennicht möglich sein, dann kommt es zu einem ASP-Parse-Fehler.

Page 352: Otmar Ganahl - C Sharp

C #

352 9

Fügen Sie eine weitere Eigenschaft hinzu. Es soll möglich sein,die Hintergrundfarbe des Steuerelements zuzuweisen. Dafürführen Sie ein Property TimeColor ein.

CD-BeispielMyWebControls3

using System;using System.Web.UI;using System.Web.UI.WebControls;using System.ComponentModel;using 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(

Browseransicht nachBelegen eines Attributs

Abb. 9.14

Page 353: Otmar Ganahl - C Sharp

ASP.NET3539

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 müssen dafürsorgen, dass ein entsprechender HTML-Stream erzeugt wird,der die Hintergrundfarbe auch wirklich im Browser setzt. Fürdie Farbe Rot würde die entsprechende HMTL-Anweisung wiefolgt aussehen:

<h1 style='color:Red'>17:03:58</h1>

Da der Datentyp Color natürlich nicht mit der Farbencodierungunter HTML übereinstimmt, müssen Sie eine Konvertierungexplizit durchführen. Dies geschieht im Beispiel mit der stati-schen Methode ToHtml der Klasse ColorTranslator (Sie müssenfür Color den Namensraum System.Drawing freigeben!)

In der ASP-Seite können Sie nun die Farbe wie folgt festlegen:

CD-BeispielTestMyControls3

<MyConts:Time runat="server" View="ShortTime" TimeColor="Red" />

Starten Sie die Applikation und betrachten Sie auch einmalden Quellcode der HTML-Datei, die dem Browser zugeschicktwurde.

Es fällt Ihnen sicherlich auf, dass nun die Erzeugung des HTML-Streams ein wenig aufwändiger und vor allem auch fehleran-fälliger wird. Die Klasse HtmlTextWriter unterstützt Sie aller-dings noch mit weiteren Methoden, die die Ausgabe ein wenigkomplexer gestalten.

Page 354: Otmar Ganahl - C Sharp

C #

354 9

CD-BeispielMyWebControls4

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 können HTML-Attribut-Einstellungen für den anschließenden Tag angegeben werden.Die Methode RenderBeginTag öffnet einen Tag, der dann überRenderEndTag() wieder geschlossen wird. Ein interner Stackübernimmt hier die Verwaltung der End-Tags. Haben Sie die-ses Verfahren einmal verstanden, macht die Erzeugung vonHTML-Streams deutlich weniger fehleranfällig.

Eingebettete ObjekteVerpacken Sie nun noch sämtliche spezifische Eigenschaftenin eine eigene Klasse TimeControlProperties.

CD-BeispielMyWebControls5

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

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

Page 355: Otmar Ganahl - C Sharp

ASP.NET3559

private Color _TimeColor; public Color TimeColor { get{return _TimeColor;} set{_TimeColor = value;} } }

public class Time:Control { 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)); output.RenderBeginTag("h1"); output.Write(Time); output.RenderEndTag(); } }}

Hier wurde der Klasse eine Member-Variable des Typs Time-ControlProperties zugefügt. Damit verwalten Sie also sämtli-che Eigenschaften in der Member-Variable Props. Wie wird nunaber in der ASP-Seite dieses eingebettete Objekt Props belegt?

CD-BeispielTestMyControls5

<MyConts:Time runat="server" Props-View="ShortTime" Props-TimeColor="Red" />

Verwenden Sie den Bindestrich, um eine Eigenschaft eines ein-gebetteten Objektes zu belegen.

Page 356: Otmar Ganahl - C Sharp

C #

356 9

Die Attribut-Klasse StyleASP.NET bietet eine Klasse Style an, welche die Verwendungvon immer wieder vorkommenden Eigenschaften wie Farben,Ränder, Fonts usw. deutlich vereinfacht. Die Verwendung istim Folgenden gezeigt (es ist hier nur mehr der Code der Klassedargestellt).

CD-BeispielMyWebControls6

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 zusätzliche Member-Variable StockPropsvom Typ Style erhalten. Mit der Methode AddAttributesToRen-der der Klasse Style wird nachfolgender Tag mit den entspre-chenden Attributen belegt. Die Klasse Style kann folgendeHTML-Attribute halten und ein Tag entsprechend belegen.

BackColorBorderColorBorderStyleBorderWidthFontForeColor

Page 357: Otmar Ganahl - C Sharp

ASP.NET3579

CssClassHeigthWidth

Die entsprechenden Werte für diese HTML-Attribute entneh-men Sie am besten aus der MSDN.

Hier noch ein Beispiel für die Zuweisung der Attribute imHTML-Code der .aspx-Datei:

CD-BeispielTestMyControls6

<MyConts:Timerunat=serverProps-View="ShortTime"

Props-TimeColor="Blue"StockProps-BackColor="Red"StockProps-BorderStyle="Solid" />

Basisklasse WebControlWenn Sie das Steuerelement Time statt von Control von Web-Control ableiten, dann erben Sie ein Style-Objekt mit. Damitwird die Sache noch einfacher.

CD-BeispielMyWebControls7

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 können Sie nun direktauf die Eigenschaften der Style-Klasse zugreifen.

Page 358: Otmar Ganahl - C Sharp

C #

358 9

CD-BeispielTextMyControls7

<MyConts:Time runat=server Props-DisplayStyle=ShortTime BackColor=Red BorderStyle=Solid />

Für sehr einfache Steuerelemente lässt sich dieser Code nocheinmal vereinfachen, wenn man einige Features der KlasseWebControl ausnutzt.

CD-BeispielMyWebControls8

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 WebControlauf, und geben als Konstruktionsparameter einen HTML-Tag(im Beispiel h1) mit. Die Klasse erzeugt einen HTML-Stream mitdiesem angegebenen Tag, versieht diesen mit den Attributender Style-Klasse, ruft dann die Methode RenderContents aufund schließt zu guter Letzt den Stream mit dem entsprechen-den End-Tag.

Composite ControlsUnter ASP.NET ist es auch möglich Steuerelemente zu erzeu-gen, die sich aus mehreren anderen Steuerelementen zusam-mensetzen (Composite Control). Im folgenden Beispiel wirdIhnen gezeigt, wie das prinzipiell funktioniert. Das zusammen-gesetzte Steuerelement soll sich aus dem kundenspezifischen

Page 359: Otmar Ganahl - C Sharp

ASP.NET3599

Steuerelement Time und einer CheckBox zusammensetzen.Über die CheckBox kann dann die View des SteuerelementsTime gesteuert werden. Dazu erzeugen Sie im bestehendenProjekt MyWebControls (und im Namensraum MyWebCon-trols) eine neue Klasse TimeBox. Leiten Sie diese Klasse vonWebControl und auch INamingContainer ab.

CD-BeispielMyWebControls9

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;

Page 360: Otmar Ganahl - C Sharp

C #

360 9

RenderChildren(output); }}

In der Klasse wird ein Steuerelement vom Typ CheckBox undeines vom Typ Time in Form der Member-Variablen check undtime angelegt. Eine public-Member-Variable (Checked) erlaubtauch einen Zugriff von außen. In der von WebControl virtuellvererbten Methode CreateChildControls werden diese einge-betteten Steuerelemente erzeugt und entsprechend mit Ei-genschaften belegt. Die erzeugten Steuerelemente werdendann in der von WebControl geerbten Liste Controls hinzuge-fügt. In der .aspx-Seite können Sie nun dieses Steuerelementwie gewohnt anlegen.

CD-BeispielTestMyControls9

<MyConts:TimeBox runat=server Checked=true />

Wenn die ASP-Seite das Steuerelement TimeBox „zeichnen“muss, dann ruft ASP die Methode RenderChildren (eine Metho-de der Klasse WebControl) auf, diese durchläuft die Liste Con-trols und ruft jeweils die Methode Render der eingebettetenSteuerelemente auf.

Der CheckBox wird eine Bearbeitungsmethode auf das EreignisCheckedChanged mit dem Namen OnCheckedChanged(...) hin-zugefügt. Diese testet den Zustand der Checkbox und belegtdas Member Checked. In der nun notwendigen Methode Ren-der belegen sie zuerst das Attribut Props.DisplayStyle des ein-gebetteten Steuerelementes time und rufen dann explizit dieMethode RenderChildren(...) auf. Vergessen Sie auf keinen Falldie Eigenschaft AutoPostBack der CheckBox auf true zu setzen.Nur dann wird eine neue Seite (http-POST) angefordert.

Statusverwaltung unter ASP.NETASP.NET bietet mehrere Möglichkeiten, um den Status einerApplikation zu verwalten.

SessionApplicationCache

Sie wissen, das Web-Protokoll ist über die verschiedenen Web-Anforderungen (Requests) „statuslos“. Um dennoch einen Sta-

Page 361: Otmar Ganahl - C Sharp

ASP.NET3619

tus zwischen Benutzer und Server zu halten, muss der Clienteinen Schlüssel anbieten, über den dann der Server auf den Be-nutzer schließen kann, und dies über mehrere Anforderungenhinaus. D.h. bei jedem Aufruf hat der Client diesen Schlüsselmitzugeben. Klassische Applikationen verwenden hierzu http-Cookies. Nun gibt es aber immer mehr Benutzer, die http-Cookies nicht akzeptieren. ASP.NET bietet ein Verfahren an,das nicht auf Cookies basiert, und somit auch auf Browsernfunktioniert, die Cookies verweigern.

Über das Session-Objekt lässt sich ein benutzerabhängiger Sta-tus verwalten. Damit könnten Daten in der Applikation ver-waltet werden, die pro Benutzer gültig, und von anderenBenutzern strikt getrennt werden.

Das Session-Objekt implementiert den Datenspeicher in Formeiner Hash-Tabelle (Schlüssel/Werte-Paar). Gespeichert wer-den Referenzen auf object und somit können beliebige Datenin diesem Datenspeicher gehalten werden, da ja unter .NETSystem.Object Basisklasse sämtlicher Objekte darstellt.

Session("[Key]") = [Value];

Das Session-Objekt überlagert den Indexer-Operator. Für denSchlüssel werden Strings verwendet.

Session["Start"] = DateTime.Now;

In diesem Beispiel wird der Schlüssel Start verwendet. Mit die-sem Schlüssel wird dann die Zeit in Form eines DateTime-Ob-jektes abgespeichert. Auf dieses Objekt kann nun jederzeitwieder über den Schlüssel zugegriffen werden.

DateTime time = (DateTime)Session["Start"];

Da Objekte im Datenspeicher Session in Form von Referenzenauf object gehalten werden, ist natürlich ein „casting“ auf denjeweiligen Typ notwendig.

Im Wesentlichen gleich funktioniert das Application-Objekt.Auch dieses Objekt implementiert einen Datenspeicher inForm einer Hash-Tabelle, wobei auch hier der Schlüssel inForm eines Strings definiert wird.

Application["Start"] = DateTime.Now;DateTime timeApp = (DateTime)Application["Start"];

Page 362: Otmar Ganahl - C Sharp

C #

362 9

Sie werden in einem einfachen Beispiel die Handhabung desSession- und Application-Objektes kennen lernen. Nehmen Siean, Sie wollen den Zeitpunkt des Starts einer Applikation unddes Starts einer Sitzung speichern und darstellen. So wird esnicht viel Sinn machen, den Zeitpunkt in Form einer Member-Variable in der Page-Klasse (bzw. von dieser abgeleitet) zu hal-ten, da diese bei einer Anforderung der Seite jedes Mal neu er-zeugt wird. Sie können aber Daten im Session- bzw.Application-Objekt, das jeder ASP.NET-Applikation zur Verfü-gung steht, abspeichern.

Application und SessionLegen Sie für dieses Beispiel ein neues ASP.NET-Web Anwen-dung mit dem Namen State an. Geben Sie der vom Wizard er-zeugten Datei WebForm1.aspx einen besseren Namen, z.B.StartPage.aspx. Dies machen Sie auch für die Klasse WebForm1(StartPage). Mit dem Designer fügen Sie nun Labels hinzu, da-mit Sie folgende Sicht erhalten.

Die Namen der Labels entnehmen Sie aus folgendem Auszugaus der Quellcode-Datei StartPage.cs.

protected System.Web.UI.WebControls.Label Label1;protected System.Web.UI.WebControls.Label Label2;protected System.Web.UI.WebControls.Label Label3;protected System.Web.UI.WebControls.Label lStartApplication;

Startzeiten

Abb. 9.15

Page 363: Otmar Ganahl - C Sharp

ASP.NET3639

protected System.Web.UI.WebControls.Label lStartSession;protected System.Web.UI.WebControls.Label lStartPage;

In dem vom Wizard erzeugten Projekt befindet sich auch eineASP-Seite global.asax. Wer schon bisher ASP programmierthat, tut sich leicht, denn hier hat global.asax dieselbe Funktionwie global.asa bei klassischen ASP-Projekten.

Diese Datei implementiert die Klasse Global (abgeleitet vonSystem.Web.HttpApplication) mit einer Anzahl von Metho-den, die von der ASP.NET-Applikation zu gegebener Zeit aufge-rufen werden. Die Namen der Methoden sind selbst erklärend.Unter anderem werden die folgenden Methoden implemen-tiert, die sich für das Beispiel bestens eignen, um die Startzei-ten zu bekommen.

CD-BeispielState1

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) belegenSie nun in der Methode Page_Load(...) die Steuerelemente u.a.mit den Daten aus den Datenspeichern Session und Applicati-on.

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();}

Page 364: Otmar Ganahl - C Sharp

C #

364 9

Starten Sie nun mehrere Instanzen des Web-Browsers. Jede In-stanz entspricht einer Session.

Erweitern Sie diese Applikation mit einem Zähler auf die Seite.Jedesmal, wenn die Seite aufgerufen wird, soll ein Zähler aufder Ebene der Applikation um eins vergrößert werden.

CD-BeispielState2

protected void Application_Start(Object sender, EventArgs e){ Application["Start"] = DateTime.Now; Application["PageCounter"] = 0;}

In der Datei global.asax initialisieren Sie einen Eintrag Applica-tion[-"PageCounter"] mit 0.

In der Methode Page_Load(...) der Klasse StartPage in der DateiStartPage.aspx müssen Sie allerdings bei der Verwendung die-ses Speicherplatzes aufpassen!

Nachfolgender Code ist nämlich sehr fehleranfällig.

int count = (int)Application["PageCounter"];Application["PageCounter"] = count+1;lPageCounter.Text = count.ToString();

Warum? .NET-Komponenten sind „free threaded“, d.h. diesesind in mehreren Threads lauffähig. Wann das Betriebssystemzwischen den Threads (Ablaufpfaden) umschaltet, ist nichtvorhersehbar. Im schlimmsten Fall könnte das genau dannpassieren, wenn ein Applikationsdatenspeicher gerade be-schrieben wird. Wenn nun die Thread-Umschaltung auf hal-bem Wege stattfindet und ein Objekt im anderen Thread nunden erst halb belegten Speicherplatz ausliest, so ist es offen-kundig, dass dann falsche Werte ausgelesen werden. Der Pro-grammierer hat dafür zu sorgen, dass dieser Fall nichteintreten kann. Zu diesem Zweck implementiert die Klasse Ap-plication 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();

Page 365: Otmar Ganahl - C Sharp

ASP.NET3659

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();}

Wenn Sie allerdings die Zugriffe auf die Seite auch über die Ap-plikation speichern wollen, dann müssen diese natürlich ir-gendwo persistent gehalten werden. Das kann in einerDatenbank oder aber in einer normalen Datei (z.B. einer XML-Datei) sein. Bei Applikationsstart würden hier die Werte aus-gelesen.

In der Methode Application_End(...) der Klasse Global ist derOrt, wo Sie die Werte persistent machen.

Seitenzähler

Abb. 9.16

Page 366: Otmar Ganahl - C Sharp

C #

366 9

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

CacheIm Folgenden werden Sie eine weitere Verwendung dieses glo-balen Speicherbereiches kennen lernen. Sie werden diese zu-erst mit dem Application-Objekt lösen. Nachfolgend soll dasBeispiel dann umgestellt werden auf das Objekt Cache, das beibestimmten Anwendungen deutliche Vorteile gegenüber demApplication-Objekt hat.

Nehmen Sie an, Sie hätten eine XML-Datei mit dem NamenPersonen.xml. In dieser Datei sind Adressen in einem XML-For-mat gespeichert. Sie finden die Datei auf der beiliegenden CD.

XML-DateiPersonen.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>[email protected]</eMail> </Person> <Person> <Vorname>Daisy</Vorname> <Nachname>Duck</Nachname> <Anrede>Frau</Anrede> <eMail>[email protected]</eMail> </Person> <Person> <Vorname>Dagobert</Vorname> <Nachname>Duck</Nachname> <Anrede>Herr §</Anrede> <eMail>[email protected]</eMail> </Person></Personen>

Page 367: Otmar Ganahl - C Sharp

ASP.NET3679

Sie sehen, die XML-Datei enthält einen Verweis auf ein zuge-höriges Style-Sheet Personen.xsl. Dieses definiert die Transfor-mation zu einer HTML-Seite. Auch die Datei finden Sie auf derbeiliegenden CD. Das HTML-Transformationsergebnis der Da-tei Personen.xml mit der Datei Personen.xsl hätte folgendes Er-scheinungsbild im Browser:

Erzeugen Sie nun ein neues Projekt vom Typ ASP.NET Weban-wendung mit dem Namen TestCache. Geben Sie den Dateien ingewohnter Form bessere Namen (z.B. StartPage.aspx und Start-Page.aspx.cs). Wechseln Sie zum C#-Code der Startseit (code-behind) und taufen Sie die Klasse WebForm1 auf StartPage um.

Im HTMLCode der .aspx-Seite fügen Sie (am besten per Desig-ner) ein Web-Steuerelement XML ein. Nennen Sie es Show-Addresses

<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 denglobalen Datenspeicher Application bringen.

XSL-Transformationsergebnis

Abb. 9.17

Page 368: Otmar Ganahl - C Sharp

C #

368 9

CD-BeispielTestCache

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 mitder entsprechenden Datei (Personen.xml) initialisiert. Da dieMethode in der „Root“ der Applikation erwartet wird, verwen-den Sie vorteilhaft die Methode MapPath, um einen vollständi-gen und vor allem richtigen Pfad zu erhalten.

Weiter wird dann ein Objekt vom Typ XslTransform (xsl), initia-lisiert mit der XSL-Datei Personen.xsl in den Datenspeicher ge-bracht. Damit stehen diese Objekte für die ganze Applikationzur Verfügung.

In der Datei StartPage.aspx.cs implementieren Sie nun folgen-den 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 Transformdes Web-Steuerelements XML mit den Daten aus dem Daten-speicher.

Vergessen Sie auch nicht die folgenden Namensräume freizu-geben (in StartPage.aspx.s und Global.asax.cs).

Page 369: Otmar Ganahl - C Sharp

ASP.NET3699

using System.Xml;using System.Xml.Xsl;

In der Methode Page_Load der Klasse StartPage fügen Sie nunfolgenden Code ein:

CD-BeispielTestCache

private void Page_Load( object sender, System.EventArgs e){ ShowAddresses.Document = (XmlDocument)Application["Personen.xml"]; ShowAddresses.Transform = (XslTransform)Application["Personen.xsl"];}

Die Transformation führt dann das Web-Steuerelement XMLdurch. Sie müssen „nur“ die Properties Document und Trans-form mit den globalen Objekten belegen. Also werden die Da-teien Personen.xml und Personen.xsl nur einmal (beiApplikationsstart) geladen, und stehen somit allen Sessionsund Pages global zur Verfügung. Die Seiten werden somitblitzschnell aufgebaut! Tolle Sache, nicht wahr?

Das Objekt Application bietet sich im ersten Moment gut fürDaten an, die über alle Benutzer gehalten werden müssen. Nurwenn sich im Beispiel die Datei Personen.xml ändern würde,weil z.B. ein neuer Adresseintrag in der XML-Datei durchge-führt wurde, dann müsste die Applikation neu gestartet wer-den, damit die Daten auch in der Applikation zur Verfügungstehen. Eine Lösung wäre, wenn die Daten bei jedemPage_Load(...) von der Datei gelesen würden. Dies ist aber ausLaufzeitgründen nicht zu empfehlen!

Um genau dieser Problematik zu entgegnen, wurde imASP.NET-Programmiermodell der Typ System.Web.Cache ein-geführt. (Jede ASP.NET Applikation besitzt ein Objekt mit demNamen Cache vom Typ System.Web.Cache.) Der DatenspeicherCache ist dem Datenspeicher Application sehr ähnlich, hat aberdarüber hinausgehende Funktionalität.

Daten im Objekt Cache können „ablaufen“. Wenn das der Fallist, dann müssen diese neu geladen werden. Es werden vonASP.NET Ereignisse angeboten, die zur Folge haben, dass Da-ten aus dem Cache entfernt werden, und somit im Bedarfsfallneu geladen werden müssten.

Page 370: Otmar Ganahl - C Sharp

C #

370 9

Änderung einer DateiDefinierte Zeit ist abgelaufen

Ändern Sie das Beispiel nun so ab, dass statt des Datenspei-chers Application der Datenspeicher Cache verwendet wird.Sämtlichen Code in Application_Start können Sie entfernen.Der Klasse StartPage fügen Sie zwei neue Member-Variablenbei:

XmlDocument dom = new XmlDocument();XslTransform xsl = new XslTransform();

In Page_Load fügen 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.Ca-ching zu öffnen.

In der Methode Page_Load(...) wird zuerst geprüft, ob im KeyXml im Datenspeicher Cache ein Element abgespeichert ist. Istdas nicht der Fall, dann wird zuerst das XmlDocument-Objektdom geladen und anschließend in den Cache gebracht und

Page 371: Otmar Ganahl - C Sharp

ASP.NET3719

zwar mit einer Abhängigkeit (CacheDependency). Die Abhän-gigkeit ist in diesem Fall eine Dateiabhängigkeit. D.h., wennsich die Datei aus irgendeinem Grund ändern würde, wird die-se augenblicklich aus dem Cache entfernt (null) und würdebeim nächsten Aufruf der Datei neu geladen werden. Eine Ab-hängigkeit gegenüber einer Datei (oder auch Ordner) wirdüber ein Objekt der Klasse CacheDependency unter Angabe desPfades der Datei (oder Ordner) erzeugt. In den Cache wird dasObjekt mittels der Methode Insert unter Angabe eines Keys(String), dem Objekt und der Art der Abhängigkeit geladen.

Dasselbe Prozedere führen Sie mit der Datei Personen.xsldurch.

Wenn Sie nun die Applikation starten und währenddessenauch eine Änderung in der Datei Personen.xml durchführen, sowird die Applikation automatisch die neue Datei laden und inden Cache bringen. (Fügen Sie als Test ein Label in die ASP-Sei-te ein, das eine Ausgabe tätigt, wenn es zu einem Nachladendes Caches kommt.)

In diesem Beispiel haben Sie eine Abhängigkeit bezüglich einerDatei (Ordner ist ebenfalls möglich) verwendet. Sehr oft bietetsich auch eine Abhängigkeit zurzeit an, d.h. wenn eine be-stimmte Zeit abgelaufen ist, dann wird der Eintrag im Cachegelöscht und (wenn auch so programmiert) wieder nachgela-den.

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 ent-fernt und damit bei Bedarf wieder nachgeladen.

Page 372: Otmar Ganahl - C Sharp

C #

372 9

ZusammenfassungSie haben in diesem Kapitel nun einen Überblick auf dasASP.NET-Programmier-Modell erhalten. Web-Seiten lassensich mit diesem Modell objektorientiert entwickeln. DieSteuerelemente repräsentieren serverseitig HTML-Code, denpraktisch jeder Browser versteht. Sämtliche Funktionalität desErzeugens von HTML-Code ist in diesen Klassen untergebracht.

Die Erzeugung von kundenspezifischen Steuerelementen er-möglicht die Unterbringung von immer wiederkehrenderFunktionalität in eine eigene Komponente. Sämtliche Featuresder Sprache C# können verwendet werden. Die Analogie zumWindows-Programmiermodell ist unübersehbar. Damit wer-den sich auch klassische Windows-Programmierer leicht tun.

Der Code einer Applikation wird zur Laufzeit kompiliert. Damitist eine bedeutende Performanz-Steigerung gegenüber demklassischen ASP zu erwarten.

Im nächsten Kapitel wenden Sie sich einem weiteren, überausinteressanten Feature zu: Web-Services.

Page 373: Otmar Ganahl - C Sharp

Web-Services

Einleitung 374

Ein einfaches Web-Service-Beispiel 374

Entwicklung und Anwendung vonWeb-Services unter Visual Studio.NET

381

Zusammenfassung 388

Page 374: Otmar Ganahl - C Sharp

C #

374 10

EinleitungIn den Anfangszeiten des Internets waren Web-Seiten stati-sche HTML-Seiten, die mit http-GET von einem Server herun-tergeladen werden konnten. Prinzipiell konnten das beliebigeDateien sein, sie mussten nur existieren. Entsprechen die In-halte dieser Dateien dem HTML-Standard, dann können spezi-elle Browser diese entsprechend darstellen. Mit ASP undanderen Script-Techniken werden HTML-Seiten auf dem Servernun dynamisch erzeugt und auf Anforderungen zurückgege-ben. Damit wurde die Leistungsfähigkeit von Web-Seiten dras-tisch gesteigert (siehe Kapitel 9, ASP.NET).

Eine weitere Steigerung der Leistungsfähigkeit erwartet mandurch die WebServices. Hier bietet ein Server Funktionalitätähnlich einer Funktion an. D.h. mittels http-GET und http-POST können statt Web-Seiten Funktionen aufgerufen wer-den, denen Parameter mitgegeben werden und die ein Ergeb-nis auch wieder zurückgeben. SOAP definiert ein solchesProtokoll (Small Object Access Protocol). Der Server gibt hier alsAntwort auf eine Anforderung das Ergebnis in Form einerXML-Datei zurück. Beliebige Clients können sich nun diesenDiensten (Service) bedienen, solange diese fähig sind http-Be-fehle abzusetzen und mit XML Daten umzugehen.

WebServices sind relativ neu, und erste Anwendungen stehenin den Startlöchern. In diesem Kapitel werden Sie lernen, wiedie .NET-Laufzeitumgebung um den IIS (Internet InformationServer) diese Technologie unterstützt.

Ein einfaches Web-Service-BeispielIn einem ersten einfachen Beispiel werden Sie nun einen Web-Service auf dem IIS erzeugen. Ebenfalls beinhaltet dieses Bei-spiel auch eine kleine Client-Applikation (in Form einer Konso-lenanwendung), die auf diesen Web-Service zugreift. Dieseserste Beispiel wird ohne Verwendung des Entwicklungssys-tems durchgeführt, weil Sie hier die Zusammenhänge am bes-ten erkennen werden.

Der WebService wird die aus dem Kapitel 9, ASP.NET bekanntenFunktionen für die Berechnung des Volumens und der Flächeeiner Kugel implementieren.

Page 375: Otmar Ganahl - C Sharp

Web-Services37510

MyWebServicesErstellen Sie zuerst im Ordner c:\inetpub\wwwroot einen Un-terordner mit dem Namen MyWebServices. In diesem Ordnererzeugen Sie mit einem beliebigen Texteditor eine Datei mitdem Namen MathServices.asmx und geben darin folgendenC#-Code:

CD-BeispielMathWebServices

<%@ 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 für den IIS die Information,dass diese Datei ein WebService beinhaltet. Diese Datei mussmit der Direktive WebService beginnen.

<%@ WebService language="C#" class="MathWebServices"%>

In dieser Datei wird nun in C# eine Klasse mit dem NamenMathWebServices erstellt, die von System.Web.Services.Web-Service abgeleitet ist. Die Klasse implementiert zwei Metho-den, die mit dem Attribut [WebMethod] versehen sind.

Damit haben Sie ein WebService erzeugt! Wenn Sie nun dieseSeite aufrufen, dann kompiliert der IIS diese Datei in gleicherWeise, wie Sie es unter Kaptitel 9, ASP.NET kennen gelernt ha-

Page 376: Otmar Ganahl - C Sharp

C #

376 10

ben, und stellt die Funktionalität als WebService bereit. Unter.NET ein Web-Service anzulegen ist also eine triviale Sache,wenn Sie C# beherrschen. Sie brauchen nicht einmal ein Ent-wicklungssystem! Wie Sie nun auf diesen Web-Service zugrei-fen können, werden Sie gleich erfahren.

Web-Service im Web-BrowserDie Web-Service-Spezifikation schreibt vor, dass der Web-Ser-ver auch eine Möglichkeit anbieten muss, die Informationenüber den Web-Service zu erfragen. Das sind z.B. die Methoden-namen, die das Service anbietet, die Parameter und Rückgabe-werte der Methoden, usw. Diese Informationen werden inXML-Form zurückgegeben.

Starten Sie den Browser und geben Sie folgende URL ein:

http://localhost/MyWebServices/MathWebServices.asmx?wsdl

(WSDL steht für WebService Description Language). Sollten Sieeinen syntaktischen Fehler in der .asmx-Datei gemacht haben,dann wird dem Browser eine Fehlermeldung übermittelt. Liegtkein Fehler vor, dann erhalten Sie eine XML-Information zu-rück.

In Abbildung 10.1 sehen Sie einen Auszug aus dieser Datei.Sichten Sie diese Datei einmal grob, und es wird Ihnen auffal-len, dass die Methodennamen, Parameternamen und Rückga-bewerte im XML-Stream vorkommen. Achtung, das ist nichtein Web-Service-Aufruf, sondern die Anforderung der Informa-tionen zum Service.

Wie wird nun ein WebService-Aufruf getätigt? Geben Sie Brow-ser folgende URL ein:

http://localhost/MyWebServices/MathWebServices.asmx/SphereVolume?r=4.1

Sie geben die URL der .asmx-Seite mit Angabe der WebService-Methode und der Übergabeparameter ein. Die Reaktion des IISist wieder eine XML-Information mit dem Wert des Rückgabe-ergebnisses.

Page 377: Otmar Ganahl - C Sharp

Web-Services37710

Wenn Sie übrigens im Web-Browser nur die URL der .asmx-Sei-te angeben, wird die Web-Service-Information über ein Style-sheet sogar noch in eine für Menschen besser lesbare Versiontransformiert.

http://localhost/MyWebServices/MathWebServices.asmx

Der Browser zeigt nun die beiden Methoden an. Die Beschrei-bung der Methoden kann genauer in Erfahrung gebracht wer-den, ja es können diese Methoden sogar aufgerufen werden.

Informationen zum Web-Service

Rückgabeergebnis in XML-Form

Abb. 10.1

Abb. 10.2

Page 378: Otmar Ganahl - C Sharp

C #

378 10

Eine kurze Zusammenfassung:

Ein Web-Service bietet Leistung an, die einer Funktion,wie sie Programmierer kennen, sehr ähnlich sind. DerAufruf gleicht einer Anforderung einer Web-Seite, dasErgebnis ist ein XML-Stream.

Web-Service-Information

Test der Web-Service-Funktionen

Abb. 10.3

Abb. 10.4

Page 379: Otmar Ganahl - C Sharp

Web-Services37910

Die Web-Service-Spezifikation sieht vor, dass die Funk-tionalität eines WebServices erfragt werden kann. DieInformation wird ebenfalls in Form eines XML-Streams bereitgestellt.Der IIS in Zusammenarbeit mit der .NET-Laufzeitum-gebung erlaubt die einfache Erzeugung eines Web-Services. In einer .asmx-Seite mit einer einleitendenDirektive und Definition einer Klasse, die von Sys-tem.Web.Services.WebService abgeleitet, reicht aus.Alle Methoden, die mit dem Attribut [WebMethod] ge-kennzeichnet sind, werden vom IIS als Web-Service-Funktionen exportiert.

Client-Programme zu MathWebServiceWenn Sie auf ein Programm schreiben wollen, das auf Web-Services zugreift, dann müssen Sie es dazu bringen, dass es ei-nen http-Aufruf erzeugt, wie Sie es im einleitenden Beispielgemacht haben. Außerdem wird das Programm dann dasXML-Ergebnis entsprechend interpretieren müssen.

Am besten, Sie hätten als Client-Programmierer eine Methodemit folgendem Prototyp zur Verfügung:

double SphereVolume(double r);

Sämtliche Arbeit (Aufruf gestalten und XML-Ergebnis interpre-tieren) soll in dieser Methode durchgeführt werden. Das istnichts anderes als ein Proxy (Stellvertreter). Das .NET-SDKstellt das Hilfsprogramm WSDL.EXE zur Verfügung. Mit die-sem Werkzeug kann eine C#-Quellcode-Datei erzeugt werden,die den Proxy-Code enthält.

Web-Service-ProxyLegen Sie einen Ordner mit dem Namen TestMathWebServicean und führen Sie in diesem über den Visual Studio.NET Com-mand Prompt (Start > Programme > Microsoft Visual Studio.NET> Visual Studio.NET Tools > Visual Studio.NET CommandPrompt) folgende Anweisung durch.

WSDL http://localhost/MyWebServices/MathWebServices.asmx

Page 380: Otmar Ganahl - C Sharp

C #

380 10

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

CD-BeispielTestMathWebServices

public class MathWebServices:System.Web.Services.Protocols.SoapHttpClientProtocol {...}

Ein kurzer Blick in die Datei zeigt, dass in dieser eine Klasse mitdem Namen MathWebService definiert wurde. Erzeugen Sienun im selben Ordner mit einem Texteditor die Datei App.csund 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 Rückgabeergebnis wird gleich auf die Konsole ausgege-ben. Nun das Ganze noch kompilieren:

csc /t:exe MathWebServices.cs App.cs

Dann können Sie die Applikation App.exe starten. Es wird dasErgebnis ausgegeben. Werfen Sie noch einmal einen Blick inden Quellcode der Proxy-Klasse, und betrachten Sie den Kon-struktor der Klasse:

public MathWebServices(){ this.Url= http://localhost _ /mywebservices/mathwebservices.asmx";}

Page 381: Otmar Ganahl - C Sharp

Web-Services38110

Der Konstruktor belegt das Member Url hart kodiert. Dies istnatürlich problematisch, da eine Verlagerung des Servers einmassives Problem darstellen würde. Sie können aber jederzeitim Applikationscode diese Member überschreiben.

Besser wäre 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 belegtwird, ist alles perfekt.

Eine kurze Zusammenfassung:

Da die Beschreibung eines Web-Services laut Spezifi-kation jederzeit vom Server erfragt werden kann, soll-te die automatische Erzeugung eines Proxys für diespezifische Entwicklungsumgebung kein Problem dar-stellen. Unter .NET heißt dieses Tool WSDL.EXE und er-zeugt einen C#-Quellcode für einen Proxy dieser Web-Services.Über diese Proxy-Klasse in dieser Quellcode-Dateikann in angenehmer Form programmtechnisch aufdas Web-Service zugegriffen werden.

Entwicklung und Anwendung von Web-Services unter Visual Studio.NETEs ist schon bemerkenswert, wie einfach sich Web-Services(oder auch klassische Web-Anwendungen) auch ohne Hilfedes Entwicklungssystems erstellen lassen.

Page 382: Otmar Ganahl - C Sharp

C #

382 10

Visual Studio.NET bietet aber mächtige Assistenten an, die dieErzeugung und vor allem die Anwendung von Web-Servicesnoch mehr vereinfachen. Im Folgenden werden Sie einen klei-nen Web-Service mit der Entwicklungsumgebung Visual Stu-dio.NET entwickeln.

Web-Service-AnwendungDas WebService wird eine Funktion GetDayOfDate implemen-tieren. Sie werden dieser Funktion ein Datum in String-Formmitgeben können, und es wird der Wochentag zurückgege-ben. Eine weitere Funktion GetDaySpan errechnet die Tagezwischen zwei Datumsangaben.

Erzeugen Sie zuerst eine neue Projektmappe mit dem NamenWebService und erstellen Sie in dieser ein Projekt mit dem Na-men DateServices. Verwenden Sie die Projektvorlage ASP.NET-Webdienst. Im Projektmappen-Explorer sehen Sie, dass eineDatei Service1.asmx angelegt wurde. Nennen Sie diese um aufden Namen DateFunctions.asmx. Beachten Sie, dass dieserName in der URL sichtbar wird.

Für die .asmx-Datei wird der Designer angezeigt, Sie könnenaber auf den code-behind wechseln, genau so, wie Sie es inKapitel 9: ASP.NET kennen gelernt haben. (Visual Studio.NETbedient sich also auch der code-behind-Technik.) In der code-behind-Datei DateFunctions.asmx.cs findet sich nun eine Klas-se Service1. Auch diese taufen Sie bitte um auf DateFunctions.(nicht vergessen, auch den Konstruktor auf diesen Namen um-zunennen). Entfernen Sie auch den Namensraum DateServices– er wird nicht benötigt.

CD-BeispielDateServices

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)

Page 383: Otmar Ganahl - C Sharp

Web-Services38310

{ 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 Datums-wert übersetzt. Tritt bei diesem „Parsen“ ein Fehler auf, wirddie auftretende Exception abgefangen und ein entsprechenderFehlerwert zurückgegeben. Starten Sie die Applikation imBrowser, wird die Web-Service-Information der Applikationdargestellt. Testen Sie nun die Funktionen des Web-Services.

Analog zu einem klassischen ASP.NET-Projekt hat der Assistentin c:\inetpub\wwwroot einen Ordner DateServices angelegt.Hier befinden sich sämtliche Dateien, auch die Verwaltungs-dateien für das Entwicklungssystem Visual Studio.Net.

Page 384: Otmar Ganahl - C Sharp

C #

384 10

Client-AnwendungenDas eben angefertigte Web-Service soll nun getestet werden.Dazu ein kleines Konsolenprogramm: Legen Sie in der aktuellenProjektmappe ein neues Projekt (Vorlage Konsolenanwendung)an und geben Sie diesem den Namen TestServiceConsole. Nen-nen Sie die Datei Class1.cs auf App.cs um. Nun brauchen Sie zu-erst einen Proxy. Diesen könnten Sie, wie im einleitendenBeispiel kennen gelernt, aus der Konsole über das Hilfspro-gramm WSDL.EXE erzeugen. Aber genau diese Aufgabe könnenSie über das Entwicklungssystem durchführen. Markieren Sieim Projektmappen-Explorer den Eintrag Verweise des ProjektesTestServiceConsole und betätigen Sie nun den Kontextmenü-eintrag Webverweis hinzufügen...

In dem nun erscheinenden Dialog geben Sie die URL-Adressedes Web-Services ein. Im speziellen Fall:

http://localhost/DateServices/DateFunctions.asmx

Wenn alles klappt, wird im Dialog die Information des Web-Services angezeigt. Mit Verweis hinzufügen erstellt der Assis-tent dann den Proxy.

Werfen Sie nun einen Blick auf den Projektmappen-Explorer.

Damit Sie diese Darstellung sehen, müssen Sie allerdings inder Werkzeugleiste des Projektmappen-Explorers den ButtonAlle Dateien anzeigen aktivieren.

Webverweis hinzufügen

Abb. 10.5

Page 385: Otmar Ganahl - C Sharp

Web-Services38510

Im neuen Eintrag Webverweise finden Sie nun die Datei Refe-rence.cs. Dies ist der erzeugte Proxy. Ein Blick in diese Dateizeigt, dass die Proxy-Klasse in einem eigenen Namensraumdefiniert ist (hier im Namensraum TestServiceConsole.local-host).

Im Quellcode der Applikation öffnen Sie am besten diesen Na-mensraum. Damit können Sie dann den Code wie folgt reali-sieren:

CD-BeispielTestServiceConsole

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); } }}

Web-Service Proxy im Projektmappen-Explorer

Abb. 10.6

Page 386: Otmar Ganahl - C Sharp

C #

386 10

Dieses Programm wird allerdings nur auf Ihrem Rechner funk-tionieren. Warum? Weil in der Proxy-Klasse die URL des Web-Services hart kodiert eingegeben ist. Und dort steht „local-host“, d.h. das Programm wird den Web-Service immer auf dereigenen Maschine suchen.

Ändern Sie daher das Programm ab, dass es konfigurierbar ist.Erzeugen Sie eine XML-Datei mit dem Namen TestServiceCon-sole.exe.config und geben Sie folgende Konfigurationsdatenein. (Mehr über Konfigurationen lesen Sie im Kapitel 7: Konfi-gurationsdateien.)

<?xml version="1.0" encoding="UTF-8"?><configuration> <appSettings> <add key="URLDateServices" value="http://esche/DateServices/ DateFunctions.asmx" /> </appSettings></configuration>

Über den Key URLDateServices können Sie nun die URL dyna-misch 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);

Page 387: Otmar Ganahl - C Sharp

Web-Services38710

} }}

Testen Sie noch die zweite Funktion GetDaySpan, der Übunghalber in einem Windows-Programm.

Erzeugen Sie also in der aktuellen Projektmappe ein neues Pro-jekt (Projektvorlage Windows-Anwendung) mit dem NamenTestServiceWindows. Die Datei Form1.cs taufen Sie gleich umauf MainWnd.cs. Die Klasse Form1 im Quellcode benennen Sieebenfalls auf MainWnd um. Wechseln Sie nun zum Designerund fügen Sie zwei DateTimePicker-Steuerelemente (mit denNamen dpDate1 und dpDate2), zwei Labels für die Beschrif-tung, und ein Label für die Ausgabe der Differenztage (NamelDifferenz) ein.

Erzeugen Sie nun mit dem Designer für das Ereignis (Event) Va-lueChanged des Steuerelements dpDate1 eine Bearbeitungs-methode. Dieselbe Bearbeitungsmethode weisen Sie auchdem Steuerelement dpDate2 zu.

Wie im Projekt TestServiceConsole erzeugen Sie nun einen Pro-xy für den Web-Service DateServices/Datefunction.asmx. InMainWnd.cs öffnen Sie den Namensraum TestServiceWin-dows.localhost und fügen der Klasse MainWnd ein Membervom Typ DateFunctions hinzu (DateFunctions ist die Proxy-Klasse des Web-Services).

CD-BeispielTestServiceWindows

DateFunctions df;

Window Web-Service-Client in Aktion

Abb. 10.7

Page 388: Otmar Ganahl - C Sharp

C #

388 10

Im Konstruktor erzeugen Sie den Proxy, und belegen die URLdes Web-Services dynamisch aus einem Eintrag in der Konfi-gurationsdatei.

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 rufenSie die Web-Service-Funktion GetDaySpan auf und weisendann das Rückgabeergebnis dem Ausgabe-Label zu. Damit dasGanze auch noch funktioniert, müssen Sie noch eine Konfigu-rationsdatei TestServiceWindows.exe.config erzeugen. Sie be-sitzt denselben Inhalt wie die Konfigurationsdatei desBeispiels TestServiceConsole.

ZusammenfassungIn diesem Kapitel haben Sie kennen gelernt, wie Sie mit VisualStudio.NET Web-Services erzeugen können. Außerdem wurdein zwei Beispielen gezeigt, wie auf diese Services programm-technisch zugegriffen werden kann.

Es soll hier noch einmal betont werden, dass Web-Servicesplattformunabhängig sind. D.h. Sie können in dieser Formauch auf Web-Services zugreifen, die auf einer Nicht-Win-dows-Plattform laufen, da ja die Schnittstelle zur „Außenwelt“http und XML darstellt. Ebenfalls können auch andere Plattfor-men auf die Web-Services zugreifen, die auf einer Windows-Plattform ausgeführt werden.

Page 389: Otmar Ganahl - C Sharp

Web-Services38910

Vielleicht haben Sie eine gute Idee, Funktionalität in Form vonWeb-Services der Welt bereitzustellen. Die Werkzeuge sindvorhanden, es liegt nur noch an Ihnen, diese Ideen umzuset-zen.

Page 390: Otmar Ganahl - C Sharp
Page 391: Otmar Ganahl - C Sharp

Remoting unter .NET

Einleitung 392

Remoting-Infrastruktur 393

Client-aktivierte Objekte 395

Server-aktivierte Objekte 403

Erzeugung von Remote-Objekten mitdem new-Operator

406

Channel 407

Lebensdauer (Leased Based Lifetime) 408

Konfigurieren des Servers und desClients über Konfigurationsdateien

414

IIS Hosting 418

Asynchrone Aufrufe 421

Events Remote-fähiger Objekte 426

Übergabe von Objekten in Remote-Methoden 426

Zusammenfassung 427

Page 392: Otmar Ganahl - C Sharp

C #

392 11

EinleitungRemoting ist sicherlich einer der interessantesten Aspekte inder modernen Softwareentwicklung. Das Internet, das bisherServer mit einander verbindet und Clients erlaubt auf diese zu-zugreifen, entwickelt sich immer mehr in Richtung vernetzterComputer allgemein. D.h. es existiert ein riesiges globalesNetz, an dem jeder Computer, der auch einen Internetan-schluss besitzt, eingebunden ist. Eine Kommunikation unterei-nander wird ermöglicht. Die .NET-Architektur und .NET-Basisklassen unterstützen dieses Vorhaben gänzlich.

Die Kommunikation zwischen zwei Prozessen, die sich auchauf unterschiedlichen Rechnern befinden können, ist auch fürerfahrene Softwareentwickler immer wieder eine Herausfor-derung. Viele Aspekte sind hier zu beachten, man denke nur anSicherheit, Verfügbarkeit, Verlässlichkeit und Fehlerbehand-lung. Eine Kommunikation zwischen Prozessen auf unter-schiedlichen Maschinen erfolgt über ein Netzwerk. UnterWin32 sind es die WinSockets, die dem Programmierer den di-rekten Zugang zu Netzwerkdiensten erlauben. Auf Basis dieserSockets wurden Softwareschichten aufgebaut, die es letztend-lich einem Programmierer einfach machen, mit anderen Pro-zessen zu kommunizieren. Moderne Implementierungen sindsogar teilweise objektorientiert, wie z.B. CORBA oderCOM/DCOM/COM+.

Unter Win32 wurde sehr erfolgreich COM/DCOM/COM+ alsverteiltes Objektmodell verwendet und es wurde viel Softwareauf dieser Basis entwickelt. Ursprünglich als Komponenten-modell wurde COM zu DCOM, einem verteilten Objektmodellerweitert, und erlaubte so auch entfernte Objekte zu erzeugenund deren Methoden aufzurufen. Die Nachteile wie Synchro-nismus der Aufrufe wurden mittels weiterer Konzepte wie z.B.Message Queue Server gelöst. Mit COM+ wurden dann deutli-che Verbesserungen eingeführt, welche auch die Entwicklungvon hoch skalierbaren Server-Anwendungen erlaubten, wiediese bei Web-Applikationen notwendig sind.

Leider eigneten sich die Sprachelemente der herkömmlichenProgrammiersprachen nur bedingt, dieser Entwicklung Rech-nung zu tragen. Unter C++ konnte vor allem durch Templateseine gewisse Übersichtlichkeit gewahrt werden, aber jeder er-

Page 393: Otmar Ganahl - C Sharp

Remoting unter .NET39311

fahrene Programmierer wird bestätigen, dass ein C++-Objektsich von einem COM-Objekt deutlich unterscheidet und nurbedingt vergleichbar ist. Eine C++-Klasse auf eine Remote-fähi-ge COM+-Klasse zu erweitern, ist auch für erfahrene Entwick-ler eine Herausforderung.

C# als neue „C“-Sprache und vor allem .NET trägt nun diesenAspekten Rechnung. Sie werden sehen, dass die Entwicklungeiner Remote-fähigen Klasse sich nicht sehr von der Entwick-lung einer normalen C#-Klasse unterscheidet, und dass es vorallem keine Begrenzung bezüglich der Datentypen gibt, wiedies bei COM der Fall war. Auch werden sämtliche modernenFeatures der objektorientierten Sprachen, allen voran die Ver-erbung, in diesem Programmiermodell unterstützt.

Remoting-Infrastruktur

Hier sehen Sie ein Modell für die grundsätzliche Architektur,die .NET für Remote-Anwendungen verwendet. VergleichenSie diese mit DCOM, die Ihnen vermutlich vertraut ist. Auf derClient-Seite haben Sie ein Stückchen Code, welcher das Server-objekt repräsentiert (proxy zu deutsch „Stellvertreter“). DieserProxy stellt bezüglich der Schnittstelle ein Zwilling des eigent-lichen Objektes dar. Die Implementierungen der Methodendes Proxys allerdings leiten die Übergabeparameter der Me-thode an das Serverobjekt weiter. Unter der COM-Terminolo-gie wurde das Kodieren dieser Information (binäre) ebenfallsdem Proxy zugeordnet. Die .NET-Architektur fügt hier aller-dings noch einen so genannten Formatter ein. Dieser Format-ter hat die Aufgabe der Kodierung (auf der Clientseite) und der

Remote Infrastruktur

Abb. 11.1

Page 394: Otmar Ganahl - C Sharp

C #

394 11

Dekodierung (auf der Serverseite). Prinzipiell können damit be-liebige Methoden der Kodierung verwendet werden. Die aus-gelieferte .NET-Umgebung erlaubt standardmäßig zwei Artender Kodierung (binär und XML). Allerdings ist es jedem frei ge-stellt auch eigene Formatter zu schreiben.

Dasselbe gilt auch für den Channel (Kanal). Unter dem .NET-Remoting-Modell sind Channels austauschbar (pluggable). Dieausgelieferte .NET-Umgebung bietet standardmäßig einenTCP-Kanal und einen http-Kanal an. Vor allem die Kombinati-on http als Kanal und XML als Formatter eignet sich hervorra-gend für die Kommunikation von Prozessen über dieInternetschiene, da für sämtliche Firewalls http und XML keinProblem darstellt (sehr wohl aber für spezifische binäre For-matter).

Die Erzeugung von Proxys (Proxy-Code) geschieht unter COR-BA, DCOM und anderen verteilten Programmiermodelle meistdurch eine IDL (Interface Definition Language). In einer eigenenSprache werden u.a. die Remote-fähigen Methoden und Para-meter definiert, dieser Code wird dann einem IDL-Compiler„gefüttert“, der dann daraus C- bzw. C++-Code für den Proxy(inklusive Kodierung und Dekodierung) erzeugt hat.

Unter .NET ist dies nicht mehr notwendig, da ja alle Klassenausnahmslos ihre Beschreibung in Form von Metadaten imCode mitführen, und diese Beschreibung sogar zur Laufzeitausgelesen werden kann. Im Fall des Remotings wird der Proxyaus dieser Beschreibung zur Laufzeit erzeugt. Damit kann die-ser Schritt entfallen und vereinfacht die Entwicklung deutlich.Da alle verwendeten Datentypen unter .NET auf die .NET-Ty-pen zurückzuführen sind, gibt es auch keine diesbezüglichenEinschränkungen bei Datentypen für Remote-fähige Klassen.

Die .NET-Architektur bietet dem Entwickler gegenüber COMmehrere Remoting-Möglichkeiten an. Diese sind hier als Über-sicht dargestellt, und Sie werden sie im Verlauf dieses Kapitelskennen lernen.

Page 395: Otmar Ganahl - C Sharp

Remoting unter .NET39511

Client-aktivierte Objekte

FolderFileEnum-KlasseFür die nachfolgenden Experimente werden Sie eine Klasseentwickeln, die aus der Dateistruktur die Ordner bzw. auch Da-teien ermitteln kann. Diese Klasse wird dann Remote-fähig ge-staltet.

Der Code dieser Klasse ist hier abgebildet. Die Klasse werdenSie in einem eigenen Dll-Assembly unterbringen. Legen Siedazu eine neue Projektmappe mit dem Namen Remoting an. Indieser Projektmappe erzeugen Sie nun ein Projekt vom Typ C#-Klassenbibliothek mit dem Namen FolderFileEnum. Der Quell-codedatei Class1.cs geben Sie den besseren Namen FolderFile-Enum.cs, der Namensraum FolderFileEnum ist hier nicht sehrsinnvoll und daher löschen Sie diesen. Den Namen der KlasseClass1 ändern Sie auf FolderFileEnum und geben dann folgen-den Code ein.

CD-BeispielFolderFileEnum

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 aufgelöst",ToString()); } public string[] GetFolders( string root,string searchPattern) { try { //zu Testzwecken – Ausgabe beim Server

Page 396: Otmar Ganahl - C Sharp

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; } }}

Page 397: Otmar Ganahl - C Sharp

Remoting unter .NET39711

Die Klasse FolderFileEnum implementiert einen Konstruktorund den Destruktor, die rein zu Testzwecken eine Ausgabe aufder Konsole tätigen, damit Sie bei den nachfolgenden Experi-menten den Überblick bewahren, wo und wann Objekte ange-legt bzw. vom Garbage Collector wieder entsorgt werden.An Funktionalität implementiert die Klasse die Methoden Get-Files(...) und GetFolders(…). Als Übergabeparameter besitzenbeide Methoden zwei Strings mit Angabe der Root (Ordner)und eines Suchstrings, der auch Wildcards beinhalten kann.Zurück geben diese Methoden ein Feld aus Strings, mit den Da-teien bzw. Ordnern, die im angegebenen Root sind und denSuchstring erfüllen. Für Testzwecke erzeugen diese Methodenebenfalls eine Ausgabe auf der Konsole, damit der Aufruf die-ser Methoden auch überprüft werden kann. Sollte innerhalbder Methoden etwas fehlschlagen, wird eine entsprechendeFehlermeldung im String Feld zurückgegeben.Sie sehen, dass im Wesentlichen dies eine ganz normale C#-Klasse darstellt, nur dass die Klasse von MarshalByRefObjectabgeleitet ist. Dazu aber später!Um diese Klasse zu testen, legen Sie ein Konsolenprojekt mitdem Namen FolderFileEnumTest an, referenzieren auf obigesAssembly 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]); } }}

Page 398: Otmar Ganahl - C Sharp

C #

398 11

Zuerst wird ein Objekt obj vom Typ FolderFileEnum erzeugt. Ineiner „Endlos“-Schleife liest das Programm einen String vonder Konsole ein. Mit Eingabe eines Rufezeichens wird dieSchleife verlassen und das Programm beendet. Im anderen Fallwird dieser String der Methode GetFolders(...) im Parameterroot übergeben. Das Ergebnis (alle Ordner in dieser Root) wirdim Feld folders abgelegt und dann werden die Einträge auf derKonsole ausgegeben. (Beachten Sie, dass Sie C:\ und nicht nurC: eingeben müssen, wenn Sie die Ordner in der Root der Fest-platte C erhalten wollen!)

Wenn die Klasse funktioniert, wird im nächsten Beispiel einRemoting-Versuch gestartet.

ServerSie werden nun einen Server schreiben, der Objekte dieser de-finierten Klasse halten kann. Hierzu erzeugen Sie in der Pro-jektmappe ein neues Konsolenprojekt mit dem NamenRemServer, geben in bewährter Weise der Datei Class1.cs denNamen App.cs. Stellen Sie dann eine Referenz zum Assembly Fi-leFolderEnum.dll her.

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

Ausgabepfad ändern

Abb. 11.2

Page 399: Otmar Ganahl - C Sharp

Remoting unter .NET39911

und per Kontextmenü den Menüpunkt Eigenschaften aktivie-ren). Im Eigenschaftsdialog aktivieren Sie im Navigationsbaumden Eintrag Konfigurationseigenschaften > Erstellen und än-dern den Ausgabepfad auf den oben erwähnten Ordner.

Damit werden sämtliche Aufgaben, insbesondere die ausführ-bare .exe-Datei in diesen Ordner geschrieben. Existiert der an-gegebene Ordner nicht, wird dieser beim ersten Mal angelegt.Diese Vorgehensweise wird Ihnen dann die Tests erleichtern.

CD-BeispielRemServer1

using System;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);

/*------------------------------------------*\ * 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 Sys-tem.Runtime.Remoting.dll einzurichten. In der Main()-Methodewird zuerst ein Objekt chan vom Typ HttpChannel mit Port8085 erzeugt und mit der Methode RegisterChannel der Klas-sen Remoting.Configuration registriert. Die Serverapplikationerhält einen Namen mit (im Beispiel „TestServer“). Über diesenNamen können dann Instanzen von sämtlichen Klassen, wel-

Page 400: Otmar Ganahl - C Sharp

C #

400 11

che über die Methode RegisterActivatedServiceType der KlasseRemotingConfiguration und Angabe der Klasse (typeof(Folder-FileEnum) registriert wurden, erzeugt werden. Voraussetzungist allerdings, dass diese Klassen von MarshalByRefObject ab-geleitet wurden. Damit wäre der Server fertig und Sie wendensich der Programmierung eines Clients zu.

ClientLegen Sie in der Projektmappe ein neues Konsolenprojekt mitdem Namen TestClient an, ändern Sie den Ausgabepfad desProjektes 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 Sys-tem.Runtime.Remoting einzurichten.

CD-BeispielTestClient1

using System;using System.IO;using System.Runtime.Remoting;using System.Runtime.Remoting.Channels;using System.Runtime.Remoting.Channels.Tcp;using System.Runtime.Remoting.Channels.Http;using 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(

Page 401: Otmar Ganahl - C Sharp

Remoting unter .NET40111

"\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]); } return 0; }}

Starten Sie nun den Server, aber nicht aus der Entwicklungs-umgebung heraus, sondern navigieren Sie mit dem Datei-Ex-plorer in den Ordner c:\RemServer. Anschließend starten Sieden Client (Sie können diesen auch vom Entwicklungssystemheraus starten). Auf der Konsole des Servers können Sie dannfeststellen, dass serverseitig ein Objekt angelegt wurde (Aus-gabe des Konstruktors). Geben Sie nun über die Client-Konsoleeinen Pfad ein. Auf dem Server wird nun die Methode GetFol-der(...) ausgeführt und das Ergebnis auf der Client-Konsoleausgegeben.

Der Client-Code unterscheidet sich vom Projekt FolderFile-EnumTest nur bezüglich der Erzeugung des Objektes vom TypFolderFileEnum. Sie erzeugen das Objekt über die MethodeCreateInstance der Klasse Activator.

Server-Applikation

Abb. 11.3

Page 402: Otmar Ganahl - C Sharp

C #

402 11

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 ei-ner Klasse, die der Server bereitstellt (FolderFileEnum) und eineAttributen-Liste, die im ersten Eintrag die URL des Servers be-inhaltet. Dieser setzt sich zusammen aus dem Protokoll (http),der IP Adresse bzw. des Namens der Servermaschine (im spezi-ellen Fall localhost) und dem Applikationsnamen, der im Ser-verprogramm der Applikation zugeordnet wurde (TestServer).Damit wird also auf dem Server das Objekt erzeugt. Für dieAuflösung des Objektes ist der Garbage Collector zuständig.Wenn Sie das Serverprogramm beenden, werden Sie die Aus-gabe des Destruktors auf der Konsole des Servers erkennen.Auf der Clientseite wird das Objekt (obj) als Proxy erzeugt. Siewerden nun einwenden, dass auch auf der Clientseite das As-sembly FolderFileEnum benötigt wird. Deshalb, weil die Be-schreibung des Objektes benötigt wird, und diese befindet sichbekanntlich ja in den Metadaten des Assembly.Erzeugen Sie im Client auch ein zweites Objekt, und Sie wer-den sehen, dass im Server auch eine zweite Instanz entsteht.Experimentieren Sie nun noch mit einem tatsächlichen Remo-te-Fall. Dafür installieren Sie auf einem beliebigen Rechner imNetzwerk Ihr Serverprogramm in einem eigenen Ordner. Sie

Client-Applikation

Abb. 11.4

Page 403: Otmar Ganahl - C Sharp

Remoting unter .NET40311

wissen, Sie brauchen dazu nur RemServer.exe und FolderFile-Enum.dll zu kopieren, und starten dann auf dem anderenRechner den Server. Beim Client geben Sie nun statt localhostden Rechnernamen an. Ändern Sie dann noch den Client so ab,dass Sie den Host bei Start des Clientprogramms über die Kon-sole eingeben können.

Server-aktivierte ObjekteDas Konzept der Server-aktivierten Objekte ist neu. Ändern Siezuerst den Server so ab, dass die Aktivierung serverseitig er-folgt:

CD-BeispielRemServer2

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);

Console.WriteLine("FolderFileServer gestartet..."); Console.WriteLine("<enter> um zu beenden..."); Console.ReadLine(); }}

Sie registrieren nun die Klasse mittels der statischen MethodeRegisterWellKnownServiceTyp(…), geben hierzu die bereitzu-stellende Klasse an, einen Namen, über den die Clients Verbin-dung aufnehmen können, und als letzten Parameter einen Typ(hier SingleCall, dazu aber später). Auffallend ist, dass hierkein Applikationsname angegeben wird, sondern jede Klasse,die über Remoting vom Server angeboten wird, einen eigenenNamen bekommt.

Page 404: Otmar Ganahl - C Sharp

C #

404 11

Eine kleine Änderung ist auch auf der Client-Seite durchzufüh-ren:

CD-BeispielTestClient2

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 direkteine URL als Parameter bekommt, die mit dem im Servercodevergebenen URL-Namen übereinstimmen muss.

Das Verhalten ist allerdings ein gänzlich anderes. Starten Siehierzu wieder den Server in C:\RemServer und dann das Client-programm (am besten über das Entwicklungssystem). Es wirdIhnen sicherlich sofort auffallen, dass das Programm schnellerstartet. Fragen Sie nun mehrmals nach einem Pfad im Serveran. Sie werden feststellen, dass für jeden Aufruf speziell einObjekt im Server angelegt wird. Dies ist das Prinzip von server-seitig aktivierten Objekten. Es ist klar, dass in solchen Objektennatürlich keinerlei Status über die Aufrufe hinweg gehaltenwerden kann. Wenn Sie eine Anzahl von Aufrufen tätigen,dann wird nach einer gewissen Zeit, der Garbage Collector ak-tiv und entsorgt die Objekte.

Bei diesem Programm macht es auch nichts aus, wenn der Ser-ver einmal beendet, und anschließend wieder gestartet wird.Das Clientprogramm wird davon nichts merken. Das Server-programm erzeugt ja bei jedem Aufruf ein Objekt neu. DiesesVerhalten werden Sie bei Client-aktivierten Objekten nichtfeststellen. Wenn hier der Server einmal beendet wird, dannwird der Client nicht weiterarbeiten können. Sie sehen, Remo-ting auf Basis von Server-aktivieren Objekten ist ein robusterAnsatz. Bei verteilten Anwendungen über das Internet ist diesnatürlich eine Voraussetzung, da hier stabile Verbindungen(noch) nicht den Standard darstellen. Das Ganze geht natürlichauf Kosten der Tatsachen, dass ein solches Objekt keinen Sta-tus zwischen den Aufrufen speichern kann.

Page 405: Otmar Ganahl - C Sharp

Remoting unter .NET40511

Die Registrierung der Klasse FolderFileEnum mit dem URI-Na-men FFE erfolgte hier im Modus SingleCall. In diesem Moduswird 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 (SingleCall)

Abb. 11.5

Serveraktivierte Objekte (Singleton)

Abb. 11.6

Page 406: Otmar Ganahl - C Sharp

C #

406 11

Hier wird beim ersten Aufruf im Server ein Objekt angelegt. Al-lerdings wird bei nachfolgenden Aufrufen dieses Objekt für dieAbarbeitung der Anfrage verwendet. Damit existiert im Ser-verprogramm nur genau eine Instanz dieser Klasse. Dieses Ob-jekt wird auch im Fall von mehreren unterschiedlichen Clientsgeteilt.

Erzeugung von Remote-Objekten mit dem new-OperatorClientseitig wird ein Proxy (Stellvertreter) eines Objektes imServer über die Methode CreateInstance (Client aktiviert) bzw.GetObject (Server aktiviert) der Klasse Activator erzeugt. DieseMethoden geben eine Referenz auf ein Proxy-Objekt zurück,das dann in die entsprechende Klasse gecastet werden muss.

Über die Methode RegisterActivatedClientType (bei Server akti-vierten Objekten) bzw. RegisterWellKnownClientType (bei Cli-ent-aktivierten Objekten) wird die .NET-Laufzeitumgebunginformiert, dass beim Anlegen des Objektes über den new-Operator ein entsprechender Proxy des Typs erzeugt werdensoll. Damit vereinfacht sich die Erzeugung eines Remote-Ob-jektes deutlich.

CD-BeispielTestClient4

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

FolderFileEnum obj = new FolderFileEnum(); . . .}

bzw.

CD-BeispielTestClient5

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

Page 407: Otmar Ganahl - C Sharp

Remoting unter .NET40711

FolderFileEnum obj = new FolderFileEnum(); . . .}

Damit reduziert sich das Programmieren auf der Clientseiteauf die Registrierung der Remote-Klasse unter Angabe desTyps und der URI. Von der Handhabung unterscheidet sich dieVerwendung von Remote-Objekten dann nicht mehr von „nor-malen“ Objekten.

ChannelBisher haben Sie Remoting über einen http-Kanal durchge-führt. Standardmäßig wird ein XML (SOAP)-Formatter für dieEncodierung verwendet. Wie schon in der Einführung erwähnt,ist die .NET-Remote-Architektur sehr anpassungsfähig, und eskönnen beliebige Kanäle, aber auch Formatter eingesetzt wer-den. Derzeit bietet die .NET-Umgebung einen tcp-Kanal an.Um über den tcp-Kanal zu kommunizieren müssten folgendeÄnderungen durchgeführt 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 ab-geleitet. Sie können sich nun vorstellen, auch eigene Klassenzu erzeugen, die von Channel abgleitet sind, und dann dieseverwenden. Denken Sie z.B. an einen Kanal über die serielleSchnittstelle. Dasselbe gilt auch für die Formatter. Die Erzeu-gung von kundenspezifischen Kanälen und Formattern würdeüber den Umfang des Buches hinausgehen. Hier wird wiederauf die MSDN verwiesen.

Page 408: Otmar Ganahl - C Sharp

C #

408 11

Lebensdauer (Leased Based Lifetime)Alle Objekte, die außerhalb einer Applikation Referenzen ha-ben, wird eine „Mietzeit“ (lease time) zugeordnet. Wenn dieseZeit abläuft, wird das Objekt von der .NET-Remoting-Laufzeit-schicht getrennt und beim nächsten Garbage Collector-Durch-lauf entsorgt. Alle Objekte haben eine Default-lease periodzugeordnet und es gibt nun mehrere Möglichkeiten diese Zeitzu manipulieren, um die Objekt-Lebensdauer zu verwalten.

Diese Möglichkeiten umfassen:

Das Serverobjekt kann seine lease time auf unendlichstellen, sodass der GC diese nie auflösen wird.Ein Client kann die lease time eines Objektes vom Ser-ver erfragen und diese auch bei Bedarf verlängern.Ein Client kann einen so genannten „Sponsor“ regist-rieren. Wenn die Zeit eines Objektes abläuft, wird der„Sponsor“ kontaktiert, und dieser kann dann bei Be-darf die Zeit wieder verlängern.Wenn das Property ILease.RenewOnCallTime gesetztist, dann wird bei jedem Aufruf des Remote-Objektesdie Ablaufzeit neu gesetzt.

Die Schnittstelle ILeaseExperimentieren Sie vorerst im Client mit den Lebensdauer-werten. Bevor Sie mit den Experimenten starten, bringen Sieden RemServer dazu, dass die Objekte Client-aktiviert erzeugtwerden. Im Client-Code fügen Sie, nachdem der Proxy erzeugtwurde, nachfolgenden Code hinzu. Die Schnittstelle ILease istim Namensraum System.Runtime.Remoting.Lifetime definiert.Geben Sie daher diesen Namensraum frei.

CD-BeispielTestClient6

FolderFileEnum obj = new FolderFileEnum();

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

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

Page 409: Otmar Ganahl - C Sharp

Remoting unter .NET40911

Console.WriteLine("RenewOnCallTime: {0}",lease.RenewOnCallTime);Console.WriteLine("SponsorshipTimeout: {0}",lease.SponsorshipTimeout);Console.WriteLine("InitialLeaseTime: {0}",lease.InitialLeaseTime);...

Über die Methode obj.GetLifetimeService() (von MarshalByRef-Object geerbt) kann ein Lease-Objekt erzeugt werden und dieEigenschaften über dieses Objekt ausgelesen werden. Auf demBildschirm haben Sie dann folgende Ausgabe:

Sie sehen, dass die Ablaufzeit 5 Minuten beträgt. Die noch rest-liche Zeit beträgt ungefähr 4.59 Minuten. Der Status ist aktiv,d.h. die Zeit ist nicht abgelaufen. Bei jedem Aufruf verlängertsich die Ablaufzeit um 2 Minuten. Bezüglich SponsorshipTime-out hören Sie später mehr. Wenn Sie die Geduld haben, fünfMinuten abzuwarten, ohne allerdings einen Aufruf zu generie-ren, werden Sie bemerken, dass das Objekt im Server nun vomClient getrennt wurde, und ein Aufruf des Clients hat eineException zur Folge, da keine Verbindung zum Serverobjektmehr besteht. Das gezeigte Modell der Verwaltung der Lebens-dauer eines Objektes im Server ist eine bedeutende Verbesse-rung gegenüber DCOM. Bricht einmal eine Verbindung ab (z.B.weil ein Client auf einem anderen Rechner abstürzt), werdeneventuell gehaltene Ressourcen im Server wieder freigegeben.

Lease-Times

Abb. 11.7

Page 410: Otmar Ganahl - C Sharp

C #

410 11

LifetimeServicesDurch die Klasse LifetimeServices im Namensraum System.Run-time.Remoting.Lifetime können für einen Server die Zeiten„global“ eingestellt werden. Rufen Sie in der Main()-Methodedes Servers folgende Zeilen auf:

CD-BeispielRemServer7

static void Main(){ LifetimeServices.LeaseTime = TimeSpan.FromMinutes(20); LifetimeServices.RenewOnCallTime = TimeSpan.FromMinutes(10); LifetimeServices.SponsorshipTimeout = TimeSpan.FromMinutes(5); ... ...}

Achtung, Sie müssen noch den Namensraum System.Runti-me.Remoting.Lifetime öffnen! Sie erhalten nun in der Ausgabedes Clients die neuen Lease-Zeiten aufgelistet.

Es ist auch möglich, die Lease-Zeiten für ein Objekt spezifischzu definieren. Das muss allerdings in der Definition des Remo-te-fähigen Objektes geschehen. Durch das Überschreiben derMethode InitializeLifetimeService der Basisklasse MarshalByRef-Obj können für die Initialisierung der Lebensdauer spezifischeWerte angegeben werden. (Achtung: Dies geschieht im Projekt

Neue Lease-Times

Abb. 11.8

Page 411: Otmar Ganahl - C Sharp

Remoting unter .NET41111

FolderFileEnum. Vergessen Sie nicht in der Quellcodedatei denNamensraum System.Runtime.Remoting.Lifetime freizugeben.)

CD-BeispielFolderFileEnum8

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 Sekun-den gestellt. Hier wird die Verbindung schon nach 10 Sekun-den abgebrochen.

Mit Ausnahme der Renew-Zeit können die Lease-Zeiten einesObjektes nur dann geändert werden, wenn sich das Objekt imStatus LeaseState.Initial befindet. Würde das nicht beachtet

Objekt-spezifische Lease-Times

Abb. 11.9

Page 412: Otmar Ganahl - C Sharp

C #

412 11

werden, so löst die .NET-Laufzeitumgebung eine Exceptionaus. Dies ist auch der Grund, warum in der obigen Implemen-tierung auf diesen Status geprüft wird.

Die Lease-Zeit RenewOnCallTime kann aber jederzeit neu be-legt werden, auch im Client. Allerdings darf diese Lease-Zeitnie kleiner gemacht werden. Dies kann dann in folgender Formgeschehen:

ILease lease = (ILease) obj.GetLifetimeService();lease.Renew(TimeSpan.FromMinutes(20.0));

SponsorEin „Sponsor“ ist ein Objekt, das die Schnittstelle ISponsor im-plementiert. Hier ein Beispiel:

CD-BeispielFolderFileEnum9

[Serializable]public class Sponsor: MarshalByRefObject,ISponsor{ public TimeSpan Renewal(ILease l) { Console.WriteLine("Renewal wurde aufgerufen"); return new TimeSpan(0,0,0,5,0); }}

Fügen Sie diese Klasse im Projekt FolderFileEnum in der DateiFolderFileEnum.cs hinzu.

Die Schnittstelle ISponsor besitzt genau eine Methode Re-newal. Wenn ein Objekt einen Sponsor registriert, dann wirddie Methode Renewal der Schnittstelle ISponsor vom Lease-Time-Manager der .NET-Laufzeitumgebung aufgerufen, wenndie Lease-Zeit abgelaufen ist. Der Sponsor gibt ein TimeSpan-Objekt mit der neuen Lease-Zeit zurück. Diese wird dann vomLease-Time-Manager verwendet, um die neue Lease-Zeit desObjektes festzulegen. Im Beispiel wird zu Testzwecken nocheine Ausgabe auf die Konsole durchgeführt. Dieses Sponsor-Objekt kann im Allgemeinen überall verteilt im Netzwerk sein.Allerdings wird verlangt, dass die Sponsor-Klasse Remote-fä-hig ist. Sollte das Sponsor-Objekt nicht antworten, wird das Re-mote-Objekt nach Ablauf der SponsorshipTimeout von der.NET-Remoting-Laufzeitschicht getrennt.

Page 413: Otmar Ganahl - C Sharp

Remoting unter .NET41311

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 indiesem Beispiel die InitialLeaseTime auf 10 Sekunden. Mit derMethode Register der Schnittstelle ILease wird nun ein Spon-sor-Objekt zugeordnet.

Nach Ablauf der Lease-Zeit ruft der Lease-Time-Manager derLaufzeitumgebung den Sponsor auf, um einen neue Lease-Zeitzu erfragen. Sie sehen im Experiment deutlich, wann dieseAnfrage auftritt, da Sie eine Konsolenausgabe darauf pro-grammiert haben. Zugegeben, die Implementierung des Spon-sor-Objektes ist im Beispiel ziemlich einfallslos, da immer diegleiche Zeit zur Verfügung gestellt wird. Dies kann natürlichdynamisch und in Abhängigkeit von anderen Zuständen ge-schehen.

Lease-Time Sponsoring

Abb. 11.10

Page 414: Otmar Ganahl - C Sharp

C #

414 11

Konfigurieren des Servers und des Clients über KonfigurationsdateienSämtliche Remote-Einstellungen vom Server als auch Clientlassen sich über Konfigurationsdateien deklarativ verwalten.Sollten Sie sich im Rahmen des Studiums diese Buches nochnicht mit Konfigurationsdateien beschäftigt haben, sollten Siezuerst das Kapitel 7: Konfigurationsdateien durchlesen. Beach-ten Sie im Folgenden, dass XML-Texte case-sensitiv sind!

Server-KonfigurationErstellen Sie im Ordner c:\RemServer (dies ist der Ausgabepfad-Ordner des Server-Beispiels) eine XML-Datei mit dem NamenRemserver.exe.config und geben Sie folgende Konfiguration inXML ein. Sie können die XML-Datei natürlich aus dem Entwick-lungssystem heraus erstellen, und damit die sehr angeneh-men XML-Features des Editors nutzen (über Menüpunkt Datei> Neu > Datei > XML-Datei).

Auf der CD zu finden imProjekt 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" />

Page 415: Otmar Ganahl - C Sharp

Remoting unter .NET41511

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

Sie wissen, das Tag <configuration> ist das Root-Tag aller Kon-figurationsdateien unter .NET. Innerhalb des Tags <system.run-time.remoting> können nun sämtliche Remoting-spezifischeEinstellungen deklarativ durchgeführt werden.

<application name="TestServer">

Dieser Name (der Applikation) ist vor allem bei Client-aktivier-ten Objekten sehr wichtig, weil er einen Bestandteil der URIdarstellt.

<channels> <channel ref="http" port="8085" /> <channel ref="tcp" port="8084" /></channels>

Innerhalb von <channels> können nun beliebig viele Kanäle ge-öffnet werden. Jeder Kanal wird in einem eigenen Tag <chan-nel> definiert. Sie geben hier den Typ des Kanals an (ref="http“bzw. ref="tcp“) und den Port dieses Kanals. Im Beispiel wurdenzwei Kanäle geöffnet.

<service> <activated type="FolderFileEnum, FolderFileEnum" /> <wellknown mode="SingleCall" type="FolderFileEnum, FolderFileEnum" objectUri="FFE" /></service>

Innerhalb des Tags <service> lassen sich nun diejenigen Klas-sen angeben, die der Server per Remoting anbieten soll. In die-sem Fall sollte der Server die FolderFileEnum-Objekte einmalals Client-aktivierte Objekte (<activated>) und einmal als Ser-ver-aktivierte Objekte <wellknown> bereitstellen. Lassen Siesich nicht wegen des zweimaligen Vorkommens von FolderFi-leEnum irritieren. In den Tags wird der Name der Klasse, die derServer bereitstellt angegeben und anschließend das Assembly,in welchem die Klasse definiert ist. Im speziellen Fall stimmenAssembly-Name und Klassennamen überein!

Page 416: Otmar Ganahl - C Sharp

C #

416 11

Im Tag <wellknown> kann darüber hinaus noch ein Modus an-gegeben werden (SingleCall bzw. Singleton) und die Eigen-schaft objectUri.

<lifetime leaseTime="20M" sponsorshipTimeout="10M" renewOnCallTime="3M" />

Im Tag <lifetime> können die spezifischen Werte der Lebens-dauerverwaltung deklarativ eingestellt werden. Im speziellenFall wurden alle Angaben in Minuten durchgeführt.

Überraschend einfach gestaltet sich nun die Implementierungdes Servers. Sie reduziert sich auf den Einzeiler

RemotingConfiguration.Configure( "RemServer.exe.config");

in der Hauptmethode Main(). Der Rest des Codes ist dafür ver-antwortlich, dass das Serverprogramm weiterlebt bzw. defi-niert beendet werden kann.

CD-BeispielRemServer10

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-KonfigurationBetrachten Sie nun die clientseitigen Einstellungen über dieKonfigurationsdatei. Auch hier erzeugen Sie im Ordner der Cli-ent-Applikation (c:\TestClient) eine XML-Datei mit dem NamenTestClient.exe.config und fügen folgende XML-Elemente hinzu.

Page 417: Otmar Ganahl - C Sharp

Remoting unter .NET41711

Auf der CD zu finden im Projekt TestClient10

<?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 Wesentli-chen den Ort des Servers (in Form einer url) als auch den Typ(Client-aktiviert oder Server-aktiviert). Hier die zwei Möglich-keiten:

<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 einenEinzeiler:

public static int Main(string [] args){ RemotingConfiguration.Configure( "TestClient.exe.config");

FolderFileEnum obj = new FolderFileEnum();

... ...}

Sämtliche Objekte in der Konfigurationsdatei definierten Ob-jekte können nun per new-Operator angelegt werden.

Page 418: Otmar Ganahl - C Sharp

C #

418 11

Achten Sie auch darauf, dass die XML-Konfigurationsdateiensich auch im selben Ordner befinden, wie die ausführbare exe-Datei. (Beachten Sie dies insbesondere, wenn Sie die Beispielevon der CD herunterladen!)

IIS HostingWenn Sie schon mit dem DCOM-Programmiermodell (Distri-buted Component Object Model) gearbeitet haben, dann ver-missen Sie bisher sicherlich das Feature der „Selbstaktivie-rung“. Wenn unter DCOM eine Anfrage an ein Serverprogrammgetätigt wird und das Serverprogramm nicht gestartet ist,dann wird das Programm von der COM-Laufzeitschicht gestar-tet. Dies ist bei den Beispielen nicht der Fall. Der Server mussexplizit gestartet werden und gestartet sein, wenn Clients sei-ne Dienste in Anspruch nehmen wollen. Es gibt auch keinenvergleichbaren Ansatz der Selbstaktivierung in der .NET-Lauf-zeitumgebung.

Exe-Server in dieser Form werden self-hosted .NET servers ge-nannt. Alle self-hosted Server müssen per Hand gestartet wer-den. .NET-Remote-Objekte können aber auch in anderenApplikationen leben. So z.B. in einem Windows-Service, wel-cher automatisch beim Booten des Systems startet.

.NET Remote-Objekte können auch im IIS (Internet InformationServer) installiert werden. In diesem Fall wird der Server auto-matisch gestartet. Zusätzlich können Sie sämtliche Sicher-heitsmechanismen des IIS nutzen. Im Folgenden wird dasBeispiel FolderFileEnum über den IIS Client zur Verfügung ge-stellt. Voraussetzung für dieses Beispiel ist natürlich, dass derIIS auf Ihrem System installiert ist.

Legen Sie einen Ordner mit dem Namen FolderFileEnumIIS aufIhrem Rechner an. In diesem Ordner erzeugen Sie dann einenUnterordner mit dem Namen \bin und kopieren in diesen Ord-ner das Assembly FolderFileEnum.dll. Im Ordner FolderFileEnum-IIS erzeugen Sie eine Datei mit dem Namen Web.Config undgeben folgende Konfigurationseinstellungen ein:

CD-BeispielFolderFileEnumIIS

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

Page 419: Otmar Ganahl - C Sharp

Remoting unter .NET41911

<wellknown mode="SingleCall" objectUri="FFES.rem" type="FolderFileEnum, FolderFileEnum" /> </service> </application> </system.runtime.remoting></configuration>

Starten Sie nun den Internetdienste-Manager (über Program-me > Verwaltung bzw. Start > Einstellungen > Systemsteuerung> Verwaltung).

Markieren Sie Standardwebsite und erstellen Sie im Kontext-menü unter Neu > Virtuelles Verzeichnis eine neues virtuellesVerzeichnis. Geben Sie dem Verzeichnis den Alias-Namen Fol-derFileEnumServer und weisen Sie diesem das physische Ver-zeichnis FolderFileEnumIIS zu. Die Zugriffsberechtigungbeschränken Sie auf „Ausführen“.

Im Internetdienste-Manager erscheint nun zusätzlich diesesvirtuelle Verzeichnis. Über das Kontextmenü Eigenschaftenkönnen Sie sämtliche Eigenschaften jederzeit ändern, so auchdie unterschiedlichsten Sicherheitsaspekte.

Internetdienste-Manager

Abb. 11.11

Page 420: Otmar Ganahl - C Sharp

C #

420 11

Zugriffsberechtigungeneinschränken

Eigenschaften-Dialog einesWebfolders

Abb. 11.12

Abb. 11.13

Page 421: Otmar Ganahl - C Sharp

Remoting unter .NET42111

Die neuen Einstellungen für das Clientprogramm können Sienun deklarativ über die Datei TestClient.exe.config einstellen.Im Folgenden die Werte:

Auf der CD zu finden im Projekt TestClient11

<?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 Portnummermüssen Sie nicht explizit angeben.

Beachten Sie unbedingt, dass der Name für die objectUri mitder Dateierweiterung .rem bzw. .soap ausgestattet wird. An-sonsten funktioniert das Beispiel nicht. Der IIS verlangt dies.Keine Ahnung warum, es ist einfach so!

Das Ganze funktioniert natürlich auch über das Netzwerk.Statt localhost geben Sie die IP-Adresse oder aber den DNS-Na-men des Servers an.

Der IIS kann nur Server-aktivierte Objekte bereitstellen.

Asynchrone AufrufeDenken Sie an eine Client-Server-Applikation. Der Client rufteine Methode eines Objektes auf, das im Server lebt. Weiterhinangenommen, dass die Durchführung der Methode eine län-gere Zeit in Anspruch nimmt. Der Client-Thread ist nun solan-ge gesperrt, bis das Rückgabeergebnis des Aufrufes über denRemote-Kanal übertragen wurde. Diese Art von Aufrufen wird„synchron“ genannt. Die Zeit zwischen Aufruf und Entgegen-nahme von Rückgabewerten des Aufrufes könnte vom Clientaber für andere sinnvolle Arbeit genutzt werden. In diesem Fallmüssen Sie den Aufruf asynchron gestalten. Ein asynchroner

Page 422: Otmar Ganahl - C Sharp

C #

422 11

Aufruf kann in zwei logische Teile aufgesplittert werden. Dererste Teil startet den asynchronen Aufruf, der zweite Teil ver-waltet in irgendeiner Form das Ergebnis des Aufrufes.

Am besten Sie experimentieren an einem Beispiel:

CD-BeispielTestClient12

using System;using System.IO;using System.Runtime.Remoting;using System.Runtime.Remoting.Channels;using System.Runtime.Remoting.Channels.Tcp;using System.Runtime.Remoting.Channels.Http;using System.Runtime.Remoting.Activation;using 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();

Page 423: Otmar Ganahl - C Sharp

Remoting unter .NET42311

return 0; }}

Asynchrone Aufrufe lassen sich unter C# sehr einfach über De-legates durchführen. Für die Methode, die Sie asynchron aufru-fen wollen, wird zuerst ein Delegate erzeugt. (Sollten Sie mitDelegates 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 FolderFile-Enum erzeugt, ein Objekt vom Typ Delegate, und dieses wirddann mit der Methode des Objektes initialisiert.

GetFolderDel del = new GetFolderDel(obj.GetFolders);

Über dieses Delegate kann nun die Methode BeginInvoke auf-gerufen werden. Beachten Sie, dass diese Methode dieselbenParameter aufweist wie die Delegate-Definition, zusätzlichzwei Parameter, welche Sie in diesem Beispiel mit null bele-gen. Zu diesen Parametern aber später.

IAsyncResult ar = del.BeginInvoke("c:\\","*.*",null,null);

Diese Methode gibt ein Objekt vom Typ IAsyncResult zurück.Über dieses Objekt können Sie später das Ergebnis des Aufru-fes erfahren. Damit ist ein Aufruf abgesetzt und der Clientkann eine beliebige Arbeit fortsetzen. Nachdem diese Arbeitbeendet ist, hätte der Client nun Zeit, das Rückgabeergebnisauszuwerten. Aber vielleicht ist der Remote-Aufruf noch garnicht zu Ende, und so macht es viel Sinn, eben zu warten, bisdas Rückgabeergebnis auch tatsächlich vorhanden ist.

ar.AsyncWaitHandle.WaitOne(10000,false);

WaitOne(), eine Methode des eingebetteten Objektes Async-WaitHandel, stellt ein Synchronisationsobjekt dar. Durch dieseMethode wird der aktuellen Thread in den Suspended Modus(verbraucht damit auch keine Rechenzeit) und „wacht“ erstwieder auf, wenn das Ereignis (Rückgabeergebnis eingetrof-

Page 424: Otmar Ganahl - C Sharp

C #

424 11

fen) oder aber das Timeout, welches in Form von ms bei derMethode WaitOne(...) mitgegeben wird, abgelaufen ist.

Über ar.IsCompleted können Sie sich vergewissern, dass derAufruf komplett durchgeführt ist und können dann über dasDelegate die Methode EndInvoke(...) aufrufen. Diese gibt danndas Rückgabeergebnis preis.

Eine Methode, mit einem void-Return Wert und mit aus-schließlichen Input-Parametern kann mit dem [OneWay]-At-tribut ausgestattet werden. Damit wird die Methodeautomatisch asynchron aufgerufen (fire and forget).

[OneWay]public void Send(string mes){ //...}

Damit das Attribut erkannt wird, müssen Sie den Namens-raum System.Runtime.Remoting.Messaging öffnen.

Der Aufruf BeginInvoke(...) kann auch in einer anderen Formgestaltet werden. Hier kommen nun die schon erwähnten zu-sätzlichen 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();

Page 425: Otmar Ganahl - C Sharp

Remoting unter .NET42511

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 Sieaber erst ein Objekt vom Typ AsyncCallback. Der Konstruktordieser Klasse verlangt nach einer Funktion (in Form einer Me-thode eines Objektes oder aber auch in statische Ausführung,wie bei diesem Beispiel). Diese Funktion wird dann vom Serveraufgerufen, sobald die Arbeit beim Server beendet ist.

AsyncCallback cb = new AsyncCallback(Result);

Diese Funktion (im Beispiel Result) muss allerdings einem ge-wissen 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 Rückgabeergebnis hat, können Sie die-se mit dem Attribut [OneWay] ausstatten. Damit ruft der Ser-ver diese Methode dann ebenfalls asynchron auf, was denServer natürlich entlastet. In der Parameterliste der Funktionbekommen Sie eine Schnittstelle IAsyncResult mit. Dieses cas-ten Sie auf ein AsyncResult-Objekt und können dann über dieEigenschaft AsyncDelegate, die genau das Delegate referen-ziert, das auch die Methode BeginInvoke(...) aufgerufen hat, dieMethode EndInvoke(...) aufrufen.

Page 426: Otmar Ganahl - C Sharp

C #

426 11

Events Remote-fähiger ObjekteRemote-Objekte können auch Events implementieren, undzwar in gleicher Weise wie Sie diese im Kapitel 1 kennen ge-lernt haben. Es ist nur zu beachten, dass das Remote-Objektdie Liste der angemeldeten Clients halten muss. Das ist auchder Grund, warum in diesem Fall die Objekte nicht Server-akti-vierbar sein dürfen, sondern nur Client-aktivierbar!

Übergabe von Objekten in Remote-MethodenBisher wurden noch keine näheren Überlegungen in Bezug derÜbergabe von Objekten in Remoting-Methoden-Aufrufen ge-tätigt. Auch diese Objekte müssen ja über den Kanal übertra-gen werden. Bisher hatten Sie vornehmlich C#-Grunddatenty-pen verwendet, aber es gibt hier keine Beschränkung auf dieGrunddatentypen.

Das .NET-Remoting-Modell unterscheidet zwei Arten der Über-tragung von Objekten in der Parameterliste von Remoting-Me-thoden.

marshal by referenzNur Objekte von Klassen, die selbst von MarshalByRefObjektabgeleitet sind, können in Form einer Referenz übertragenwerden. Dabei wird nicht das Objekt übertragen (dieses „lebt“remote), sondern ein Proxy.

marshal by valueDamit Objekte einer Klasse auch als Teilnehmer von Remote-Methoden arbeiten können, müssen diese die Schnittstelle ISe-rialize implementieren oder aber mit dem Attribute [Seriali-zable] gekennzeichnet sein. Objekte dieser Art haben „leben“nicht „remote“, sondern werden immer als ganze Objekteübertragen.

Sämtliche Objekte, die nicht serialisierbar sind bzw. nicht vonMarshalByRefObjekt abgeleitet sind, können nicht als Parame-ter bzw. Rückgabewerte von Remote-Methoden arbeiten!

Page 427: Otmar Ganahl - C Sharp

Remoting unter .NET42711

ZusammenfassungIn diesem Kapitel haben Sie die .NET-Remoting-Architekturkennen gelernt. Das ist eine robuste Architektur, die auch mitinstabilen Verbindungen zurecht kommt. Vor allem aber wer-den Standard-Protokolle wie XML und http unterstützt. Damitkönnen Win32-Prozesse auch über die Internetschiene hinwegkommunizieren.

Im Gegensatz zu den Web-Services müssen Sie aber alle Si-cherheitsaspekte selbst verwalten! Seien sich dessen bewusst!Daher wird die Interprozesskommunikation auf Basis der .NET-Remoting-Architektur sich eher im Intranet abspielen. Für dieanderen Fälle sind Web-Services meist die bessere Lösung,auch wenn mit Web-Services nicht alles realisiert werdenkann, was die .NET-Remoting-Architektur anbietet.

Page 428: Otmar Ganahl - C Sharp
Page 429: Otmar Ganahl - C Sharp

.NET-Klassen

Einleitung 430

Die Klasse System.Object 431

Die Klasse System.String 435

Die Klasse System.IO.Path 436

Die Klassen DirectoryInfo,FileInfo 439

Die Klasse WebClient 440

Page 430: Otmar Ganahl - C Sharp

C #

430 12

Einleitung

In Abbildung 12.1 ist der prinzipielle Aufbau der .NET-Klassen-struktur abgebildet. Alle Klassen unter .NET basieren auf derCommon Language Runtime (CTS). Für Programmierer sind dieKlassen im Common Type System (CTS) von zentraler Bedeu-tung. Hier sind alle Grunddatentypen definiert, allen voranSystem.Object. Alle weiteren Typen unter .NET basieren aufdiesen Klassen.

Sämtliche .NET-Sprachen basieren auf diesem Typ-System,und das ist auch der Grund, warum Klassen über die Sprach-grenzen hinweg verwendbar sind.

Oberhalb der CLR sind die Basisklassen der .NET-Laufzeitumge-bung angeordnet. Threading, Sicherheit und Ein/Ausgabe-Funktionalität sind hier untergebracht. Aber Sie sehen auch,dass ADO.NET-Klassen, Webzugriffsklassen und XML hier im-plementiert sind. Diese gehören also zur Laufzeitumgebung.Hier finden sich also Klassen, die in einer Multi-Tier-Architek-tur typischerweise der Service-Schicht zugeordnet würden.

Über der Service-Schicht befinden sich Klassen zur Erzeugungvon grafischen Oberflächen (ASP.NET und Window Form). ImBlock Window Form sind eine Reihe von Klassendefinitionenzusammengefasst, die für die klassische Fenster-Programmie-rung notwendig sind. Theoretisch ist auch eine Portierung die-

.NET-Klassenstruktur

Abb. 12.1

Page 431: Otmar Ganahl - C Sharp

.NET-Klassen43112

ser Klassen auf ein anderes Betriebssystem denkbar. DieKlassenarchitektur von ASP.NET ist speziell für die Zusammen-arbeit mit dem IIS (Internet Information Server) gedacht.

Es werden in diesem Kapitel auch noch einige interessanteund wichtige .NET-Klassen näher vorgestellt. Die .NET-Lauf-zeitumgebung bietet eine Unzahl von Klassen und damitFunktionalität an, alle diese vorzustellen würde den Rahmendieses Buches bei Weitem sprengen. Nehmen Sie sich aber hierund da die Zeit, diese Klassen zu erforschen. (nach dem Motto:Jede Woche eine neue Klasse J. Die MSDN stellt hier eine uner-schöpfliche Quelle dar.

Die Klasse System.ObjectIm Kapitel 2: C# – eine neue Programmiersprache haben Sieschon einiges über diese Basisklasse gehört. Hier wird aber aufeinige Aspekte dieser Klasse eingegangen.

Dass die Klasse System.Object die ultimative Basisklasse allerObjekte darstellt, sollte Ihnen nun bekannt sein. Eine Reihevon Methoden dieser Basisklasse sind virtuell ausgeführt. Ineiner Referenz vom Typ System.Objekt kann daher auf jedesbeliebige Objekt verwiesen werden. Eine Funktion mit einemÜbergabeparametertyp System.Object kann auch jeder beliebi-ge Typ mitgegeben werden. (Hinweis: C/C++-Programmierermussten hier immer auf void* ausweichen.)

ReferenceEqualsDie Methode ReferenceEquals ist eine statische Methode. Sietestet, ob zwei Referenzen auf dasselbe Objekt zeigen. Sie gibttrue zurück, wenn die Referenzen denselben Speicherbereichadressieren, ansonsten false. Sie ist statisch und kann dahernicht überschrieben werden.

CD-BeispielSO

using System;class App{ static void Main() { string T1 = "Hallo"; string T2 = T1;

Page 432: Otmar Ganahl - C Sharp

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 Klas-se String ein Referenztyp darstellt. Der Vergleich von i und jgibt den Wert false. Es handelt sich um zwei unterschiedlicheSpeicherbereiche (der Grund liegt darin, dass der Datentyp inteine Wert-Typ darstellt).

EqualsSystem.Object implementiert Equals als statische als auch alsnichtstatische 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 zurück, wenndie zu vergleichenden Referenenzen auf dasselbe Objekt ver-weisen. Möchten Sie ein anderes Verhalten haben, dann mussdiese Methode überschrieben werden. Das ist bei der KlasseSystem.Value geschehen. Die Implementierung gibt dann truezurück, wenn die Objekte binär gleich sind.

Im Kapitel 2: C# – eine neue Programmiersprache haben Sieeine Klasse Fraction programmiert. Diese Klasse wurde mitdem C#-Schlüsselwort class definiert und stellt somit eine Re-ferenzklasse dar. Da Fraction einen mathematischen Datentypmodelliert, ist die Default-Implementierung von Equals un-günstig. Mathematische Gleichheit ist dann vorhanden, wennzwei Objekte denselben Wert repräsentieren. Beim Typ Frac-tion genügt nicht einmal die Prüfung auf binäre Gleichheit,das die Bruchzahlen 4/2 und 8/4 mathematisch gleich sind.Daher könnte eine Implementierung wie folgt aussehen:

Page 433: Otmar Ganahl - C Sharp

.NET-Klassen43312

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 (Zählerund Nenner sind gleich) ist, dann sind die Werte mathema-tisch gleich.

Die statische Variante von Equals sollte dasselbe machen wiedie nichtstatische. Warum ist aber dann eine statische Metho-de Equals überhaupt notwendig? Der Unterschied liegt darin,dass damit auch null-Referenzen verwendet werden können.Die Verwendung der statischen Methode ist somit um einigessicherer!

Die virtuelle Methode Equals wird sehr intensiv von anderenKlassen der .NET-Laufzeitumgebung verwendet. Vor allem,wenn eine Klasse als Schlüssel (key) verwendet wird (z.B. in ei-ner Hash-Tabelle), dann kommt es zu intensiven Aufrufen die-ser Methode. Beachten Sie dies, da Sie hier einen großenEinfluss auf das Laufzeitverhalten ausüben können.

In welchem Zusammenhang steht die Methode Equals mitdem Vergleichsoperator ==? Sie müssen diesen Operatorebenfalls überlagern. Implementieren Sie den Vergleichsope-rator einer Klasse aber nicht mit der nichtstatischen MethodeEquals.

if(null == obj)...

Diese Zeile würde dann nämlich eine Exception auslösen, weilauf null die Methode Equals aufgerufen würde. Machen Sie eslieber umgekehrt. Implementieren Sie den Vergleichsoperatorund verwenden den Vergleichsoperator zur Implementierungder Equals-Methode.

GetHashCode()System.Object implementiert die Methode GetHashCode(). EinHash ist ein Integerschlüssel (key) für eine spezielle Instanz ei-ner Klasse. Dieser Schlüssel ist sozusagen eine auf einen Inte-gerwert reduzierte Version des Inhalts des Objektes. Hashes

Page 434: Otmar Ganahl - C Sharp

C #

434 12

sind für viele Algorithmen, vornehmlich für Sortier- und Such-algorithmen von großem Vorteil. Damit können diese Abläufeentscheidend beschleunigt werden. Das .NET-Framework be-sitzt solche Hashtable-Klassen und diese verwenden intensivdie Methode GetHashCode(). Damit sind sehr schnelle Sor-tieralgorithmen möglich. Prinzipiell gilt folgende Regel: Wennzwei Objekte bei einem Vergleich über die Methode Equals alsgleich gelten, dann sollten diese auch denselben Hashcode zu-rückgeben. Die Default-Implementierung gibt für jedes Objekteinen unterschiedlichen Hashcode zurück. Es wird angeraten,auch die Methode GetHashcode zu überschreiben, wenn dieMethode Equals überschrieben wird.

ToString()Diese Methode ist virtuell ausgeführt und sollte einer sinnvol-len Text-Repräsentierung des Objektes entsprechen. Wenn Siediese Methode nicht überschreiben, wird der Klassenname alsString zurückgegeben.

GetType()Über die Methode GetType kann jederzeit ein Objekt vom TypSystem.Type erzeugt werden. Über dieses Objekt kann dannauf die Metadaten der Klasse zugegriffen werden. Die Metho-de GetType kann nicht überschrieben werden.

Finalize()Die Methode Finalize wird vom Garbage Collector (GC) aufge-rufen, kurz bevor es entsorgt wird. Sie ist mit dem Destruktoraus C++ vergleichbar. Um diese Methode zu überschreiben,müssen Sie sich unter C# der Destruktorsyntax bedienen (pu-blic ~Fraction()...).

Sie haben keine Kontrolle darüber, wann der Aufruf von Finali-ze erfolgt. Aus diesem Grund ist das Verlagern von Aufräumar-beiten, wie z.B. Schließen von Datenbanken, Dateien etc. in dieMethode Finalize nicht zu empfehlen. In dieser Hinsicht unter-scheidet sich C# grundsätzlich von C++. Besser ist, wenn dieArbeit in Methoden wie z.B. Close implementiert wird und die-se dann explizit aufgerufen werden.

Page 435: Otmar Ganahl - C Sharp

.NET-Klassen43512

Die Klasse System.StringSämtliche Strings unter .NET sind Instanzen der Klasse Sys-tem.String (C#-Schlüsselwort string). Strings werden unter.NET immer in Unicode gehalten. Das gilt übrigens auch fürden Datentyp System.Char. Da unter C# char ein Synonym fürSystem.Char darstellt, müssen vor allem C/C++-Programmiereraufpassen. Eine Variable vom Typ char „verbraucht“ zweiBytes, da ein Unicode-Zeichen untergebracht werden muss.Wenn Sie ein Byte brauchen, dann verwenden Sie System.Bytebzw. das C#-Schlüsselwort byte. Nun aber wieder zurück zuden Strings.

Die Klasse String ist „immutable“, auf deutsch unabänderlich.D.h., dass sämtliche Operationen auf ein String-Objekt ein mo-difiziertes Objekt zurückgeben, und nicht das Objekt selbstmodifizieren. Um das zu verdeutlichen ein kleines Beispiel:

CD-BeispielString1

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 selbstnicht ändert. Die Methode gibt einen modifizierten neuenString als Rückgabewert zurück.

SplitDie Klasse implementiert einer Reihe von Methoden. Als sehrangenehm erweist sich die Überlagerung des +-Operators. Eswerden jetzt nicht alle Methoden der Klasse System.String auf-gelistet. Sie sind selbsterklärend. Erwähnenswert ist allerdings

Page 436: Otmar Ganahl - C Sharp

C #

436 12

die Methode Split, die dem Autor jedenfalls schon unzähligeStunden an Arbeit erspart hat.

CD-BeispielString2

using System;

class App{ static void Main() { string Text = "[email protected]"; 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 Feldaus Separatoren. Die Anzahl ist beliebig. Der String wird dannin Einzelstrings zerlegt und in einem Feld untergebracht. ImBeispiel wird also die E-Mail-Adresse in einzelne Strings aufge-splittet (george, busch, washington, com).

Dadurch, dass das System.String ein „immutable“-Typ dar-stellt, könnten intensive Stringverarbeitungen (vor allemdann, wenn Strings verändert werden müssen) ineffizientsein. Für diesen Fall bietet die .NET die Klasse StringBuilder an.Hier können Sie einzelne Zeichen manipulieren. Mehr dazu er-fahren Sie in der MSDN.

Die Klasse System.IO.PathHaben Sie sich schon einmal mit Pfaden, Dateinamen, Datei-namenserweiterungen usw. herumgeschlagen? Eine mühsa-me Sache! Ein Beispiel gefällig?

C:\Eigene Dateien\Bilder\Foto.jpg

Hier haben Sie einen String, der eine .jpg-Datei in einem Ord-ner auf dem Datenträger C: bezeichnet. Wenn Sie nun den Da-teinamen ohne Dateinamenserweiterung aus diesem Stringextrahieren wollen (Foto), müssen Sie in der Stringverarbei-tung schön fit sein. Vermutlich werden Sie das Zeichen '.'(Punkt) im String suchen, dann das Zeichen '\' (Backslash)

Page 437: Otmar Ganahl - C Sharp

.NET-Klassen43712

und die dazwischenliegenden Zeichen als den Dateinamen in-terpretieren. Wurde vielfach so programmiert und hat auchfunktioniert, bis dann Microsoft das Zeichen '.' auch in Datei-namen erlaubte (z.B. Foto.Urlaub.jpg). Ab diesem Zeitpunktfunktionierten viele Programme nicht mehr, da, wie Sie sichdenken können, mit obigem Algorithmus folgenschwere Ne-beneffekte entstehen können. Hier einige andere, erlaubte Va-riationen:

C:/Eigene Dateien/Bilder/Foto.jpgC:\\EigeneDateien\Bilder/Foto.Urlaub.jpg//Rechnername/c/Eigene Dateien/Foto.Urlaub.jpg

Sie sehen, auch das Separator-Zeichen kann unterschiedlichgestaltet sein (Slash, Backslash). All diese Fälle zu berücksichti-gen ist eine aufwändige Sache, darüber hinaus nicht einmalohne Weiteres auf andere Plattformen portabel, denn wersagt, dass auch auf anderen Plattformen für die Separator-Zei-chen Slash bzw. Backslash verwendet werden?

Die Klasse PathUm all diesen Schwierigkeiten aus dem Weg zu gehen, über-geben Sie diese Arbeit am besten der Klasse Path aus dem Na-mensraum System.IO. Diese Klasse bietet eine Menge vonnützlichen statischen Methoden an, die das Programmiererle-ben verschönern.

Die Klasse Path im Namensraum System.IO erlaubt Ihnen ein-fach und schnell Operationen wie das Extrahieren des Pfades,Dateinamens oder Dateinamenserweiterung sowie das Zu-sammenstellen von Pfadnamenstrings. Außerdem ist dieseKlasse plattformunabhängig gestaltet, d.h. sie verwendetdas/die dem Betriebssystem zugrunde liegende(n) Pfad-Sepa-rator-Zeichen. Alle Methoden der Path-Klasse sind statisch,und Sie benötigen daher keine Instanz dieser Klasse, um dieMethoden zu verwenden. Werfen Sie einmal einen Blick auf ei-nige dieser Methoden (am besten mit einem Beispiel!):

CD-BeispielPath

using System;using System.IO;

class App{

Page 438: Otmar Ganahl - C Sharp

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 selbsterklärend.

Urlaubsfoto.jpg.jpgUrlaubsfotoc:\Eigene Dateien\Bilderc:\Eigene Dateien\Bilder\Urlaubsfoto.jpgc:\\/TrueEigene Dateien\Bilder

Es ist ratsam, ausschließlich diese Klasse für Datei- und Pfad-namensmanipulationen zu verwenden. Neben der Arbeitser-sparnis ist ihre Applikation auch plattformunabhängig undwenn vielleicht einmal eine Portierung auf „Nicht-Microsoft-Betriebssysteme“ vorkommen soll, so bereitet ihre Applikationwenigstens bei den Dateisystemmanipulationen keine Proble-me.

Page 439: Otmar Ganahl - C Sharp

.NET-Klassen43912

Die Klassen DirectoryInfo,FileInfoDie Klassen Directory, DirectoryInfo, File und FileInfo im Na-mensraum System.IO bieten Ihnen mächtige Methoden zumErzeugen, Löschen, Verschieben, Kopieren und Umbenennenvon Ordnern und Dateien.

Die Klassen Directory und File beinhaltet im Wesentlichen die-selben Funktionalitäten wie die Klassen DirectoryInfo und File-Info, nur dass in den Klassen Directory und File die Methodenstatisch sind, und daher keine Objektinstanz für die Verwen-dung benötigen. Methoden der Klassen DirectoryInfo und File-Info sind vorwiegend nicht statisch und können daher nurüber eine Objektinstanz aufgerufen werden.

Lernen Sie die Verwendung der Klassen, indem Sie in einemkleinen Konsolenprogramm experimentieren.

CD-BeispielFilehandling

using System;using System.IO;

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]); }

Page 440: Otmar Ganahl - C Sharp

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 Ord-ner- und Dateieigenschaften wie z.B. LastAcessTime haben.Auch das Erzeugen, Löschen, Kopieren und Verschieben vonOrdnern oder Dateien ist mit diesen Klasse keine Hexerei.

Hier Codefragmente, die die Verwendung verdeutlichen sol-len:

File.Copy(@"c:\Urlaubsfoto.jpg",@"c:\Fotos");File.Delete(@"c:\Urlaubsfoto.jpg");

Experimentieren Sie mit diesen Klassen, damit Sie die Funkti-onsweise kennen lernen. Nehmen Sie auch bewusst dengrundsätzlichen Unterschied von Directory/File und Directory-Info/FileInfo wahr.

Die Klasse WebClientDass Sie irgendwann einmal in Ihrem Programm eine Dateivom Web herunterladen sollten, wird zukünftig immer wahr-scheinlicher. .NET bietet dazu die interessante Klasse WebCli-ent an. Die Klasse ist im Namensraum System.Net definiertund im Assembly System.dll implementiert. Nachfolgend wirdIhnen die Verwendung dieser Klasse an einem Beispiel de-monstriert.

CD-BeispielWebClient1

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

Page 441: Otmar Ganahl - C Sharp

.NET-Klassen44112

//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 können Sie mithilfe der Klasse WebClient auf zwei Artenprogrammtechnisch herunterladen. Die Methode DownloadFi-le speichert die Daten direkt in eine anzugebende Datei ab. DieMethode OpenRead gibt Ihnen einen Stream zurück, den Siedann über ein StreamReader-Objekt bearbeiten können.

Experimentieren Sie mit diesem Beispiel!

Page 442: Otmar Ganahl - C Sharp
Page 443: Otmar Ganahl - C Sharp

Ausgewählte C#-Kapitel

Interoperabilität mit COM 444

Casting 448

C#-Präprozessor 452

Page 444: Otmar Ganahl - C Sharp

C #

444 13

Interoperabilität mit COMCOM (Component Object Model) war bisher das (zumindestteilweise) objektorientierte Programmiermodell auf denWin32-Plattformen. Überall auf der Welt wurden Unmengenvon Softwarekomponenten auf Basis von COM geschrieben.Microsoft hat große Anstrengungen unternommen, dassCOM-Componenten in der .NET-Umgebung verwendbar sind.Aber auch umgekehrt, .NET-Komponenten sind ebenfalls in ei-ner klassischen COM-Umgebung einsetzbar. COM-Komponen-ten sind meist in Dlls untergebracht. Damit diese auf einemSystem verwendbar sind, müssen diese in der Registrierunguntergebracht sein. Das geschieht beim Installieren. COM-Komponenten exportieren ihre Funktionalität in Form vonSchnittstellen. Außerdem definiert COM auch einen Event-Me-chanismus.

Einbinden von ActiveX-SteuerelementenDie ActiveX-Steuerelemente sind ebenfalls COM-Komponen-ten. Viele Softwarehersteller haben große Investitionen in dieProgrammierung von ActiveX-Steuerelementen getätigt. WieSie ActiveX-Steuerelemente in einem Windows-Programmverwenden soll folgendes Beispiel demonstrieren.

Suchmaschinen-Browser

Abb. 13.1

Page 445: Otmar Ganahl - C Sharp

Ausgewählte C -Kapitel44513

Microsoft bietet Programmierern seinen Web-Browser in Formeines ActiveX-Steuerelements an. Dieses soll im Beispielpro-gramm verwendet werden. In einer TextBox wird ein Suchbe-griff eingegeben. Auf Basis dieses Suchbegriffs werden danndie Suchmaschinen Altavista und Google umschaltbar in ei-nem TabControl dargestellt (siehe Abb. 13.1).

Legen Sie dazu ein neues Projekt an (Projektvorlage Windows-Anwendung). Nennen Sie das Projekt SearchBrowser. NennenSie dann auch die vom Assistenten erzeugten Datei- und Klas-sennamen um. Im Designer fügen Sie dann ein TabControl ein.Geben Sie dem TabControl den Namen tabBrowsers. Im Eigen-schaftsfenster des tabBrowsers können Sie nun TabPages zu-ordnen. Fügen Sie zwei Sichten mit den Namen AltavistaPageund GooglePage hinzu. In diese Sichten müssen Sie nun jeweilsdas ActiveX-Steuerelement platzieren.

Dazu wechseln Sie zur ToolBox und aktivieren im Eintrag Win-dow Forms über das Kontexmenü den Eintrag Toolbox anpas-sen. Im folgenden Dialog werden alle registrierten ActiveX-Steuerelemente aufgelistet.

Suchen Sie sich das ActiveX-Steuerelement Microsoft Web-Browser und aktivieren Sie dieses (Abb. 13.2). Das Steuerele-ment erscheint nun in der Toolbox. Sie können in gleicherWeise dieses Steuerelement verwenden wie .NET-Steuerele-

ActiveX-Steuerelement aktivieren

Abb. 13.2

Page 446: Otmar Ganahl - C Sharp

C #

446 13

mente. Bei diesem Vorgang erzeugt das Entwicklungssystemeine Wrapper-Klasse, die nach außen eine .NET-Schnittstelleanbietet. Intern greift diese Klasse über COM-Mechanismenauf das tatsächliche Objekt zu. Visual Stuido.NET verpackt die-se Klasse dann auch gleich in ein Assembly, das direkt in denOrdner der ausführbaren Datei kopiert wird!

Ziehen Sie nun je ein WebBrowser-Steuerelement in die Pagesdes TabControls. Geben Sie diesen den Namen WebBrowser-Google und WebBrowserAltavista. Nun noch eine TextBox(tbSearchText) und einen Button (btSearch) einfügen. Mit demDesigner erzeugen Sie auch gleich eine Bearbeitungsroutinefür das Event Click.

Die Position der Steuerelemente im Fenster soll von der Größedes Fensters abhängig gemacht werden. Fügen Sie daher derForm-Klasse eine Methode SetSizes hinzu, die dann sämtlicheGrößen und Positionen der Steuerelemente berechnet unddiese auch anordnet.

CD-BeispielSearchBrowser

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;

Toolbox mit ActiveX-Steuerelement

Abb. 13.3

Page 447: Otmar Ganahl - C Sharp

Ausgewählte C -Kapitel44713

tbSearchText.Location=new Point(15,sTab.Height+20); btSearch.Location = new Point(15,sTab.Height+40);}

Fügen Sie noch Bearbeitungsroutinen für die Ereignisse Load-Page und SizeChanged des Hauptfensters hinzu (natürlich mitdem Designer) und rufen Sie in diesen die Methode SetSizesauf.

In der Bearbeitungsroutine des Buttons btSearch sollen nun diebeiden WebBrowser-Steuerelemente auf die entsprechendenSuchseiten 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 können nun sämtliche Methoden und Properties des Steuer-elements aufrufen, so auch die Methode Navigate. Unter COMwerden der Methode Navigate Zeiger mitgegeben, damit Rück-gabewerte belegt werden können. Der .NET-Wrapper verlangthier nach Referenzen. Sie brauchen die Rückgabeergebnisseaber nicht, daher geben Sie Dummy-Objekte mit, die Sie natür-lich auch erzeugen müssen und mit dem ref-Schlüsselwort inder Parameterliste mitgeben. Die Zusammenstellung desSuchstrings ist natürlich von der Suchmaschine abhängig.

Page 448: Otmar Ganahl - C Sharp

C #

448 13

ZusammenfassungUnter .NET wird auf ActiveX-Steuerelemente überWrapper-Klassen zugegriffen. Diese Klassen imple-mentieren die eigentlichen COM-Aufrufe.Visual Studio.NET kann diese Wrapper-Klassen auto-matisch aus den Informationen der Typbibliothek desSteuerelements erzeugen.

CastingC-Programmierer können Castings (Typumwandlungen)durchführen, wann immer und wo immer sie wollen. Diese„Freiheit“ birgt allerdings auch Gefahren in sich, von der sicherjeder C-Programmierer sein Liedchen singen kann.

Unter C++ ist es sogar möglich, jedem Datentyp Casting-Ope-ratoren zu definieren, d.h. das Verhalten bei der Umwandlungvon einem Datentyp zu einem anderen Datentyp zu kontrollie-ren.

Explizites und implizites CastingMan unterscheidet explizites Casting und implizites Casting.

byte b = 5;int i;i=b;//implizites Casting

Beim impliziten Casting erfolgt die Typumwandlung sozusa-gen automatisch.

byte b;int i=5;b=i;//implizites Casting

Unter C sehr wohl möglich (maximal erscheint vielleicht eineWarnung), verweigert der C#-Kompiler dieses Casting, da eshier naturgemäß zu einem Datenverlust kommt. Der Program-mierer muss den Kompiler explizit beauftragen, diesen Vor-gang durchzuführen.

Page 449: Otmar Ganahl - C Sharp

Ausgewählte C -Kapitel44913

byte b;int i=5;b=(byte)i;//explizites Casting

Es liegt nun in der Verantwortung und Beurteilung des Pro-grammierers, ob dies auch Sinn macht.

Unter C# wird Casting wesentlich restriktiver gehandhabt alsunter C. Und das ist auch gut so. Wie auch unter C++ könnenaber unter C# spezifische Casting-Operatoren geschriebenwerden. Somit kann das Verhalten bei einer Casting-Operationgenau kontrolliert werden.

Das Prinzip wird an einem kleinen Beispiel veranschaulicht:

CD-BeispielCasting1

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)

Page 450: Otmar Ganahl - C Sharp

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 Währungen Euro undSchweizer Franken. Innerhalb der Klassen wird der Betrag inCents bzw. Rappen gehalten. Drei Konstruktoren erlauben dieBelegung. Die Methode ToString ist so überlagert, dass dieAusgabe 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 möglich fPreis = (CHF)ePreis; //explizit nicht möglich }}

Hier sehen Sie diese beiden Klassen in der Anwendung. Ein Be-trag von 5 Euro und 20 Cent wird im Objekt ePreis gehalten. Ei-nem Objekt vom Typ CHF kann weder explizit noch implizit dasObjekt ePreis vom Typ Euro zugeordnet werden. Der C#-Kom-piler verweigert das (nicht nur aus geldmarktpolitischen Grün-den :-). Fügen Sie nun der Klasse Euro einen Umwandlungs-operator hinzu.

Page 451: Otmar Ganahl - C Sharp

Ausgewählte C -Kapitel45113

Schlüsselwort explicitDefinition eines explizi-ten Casting-Operators

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. Um-wandlungsoperatoren müssen immer in Form von statischenMethoden implementiert werden. Syntaktisch verwenden Siehier das Schlüsselwort operator, geben dann den Rückgabetypder Operation an (CHF) und in der Übergabeparameterliste daszu konvertierende Objekt (Euro eu). Damit ist diese Zeile mög-lich!

fPreis = (CHF)ePreis;

Allerdings ist die implizite Verwendung nicht möglich.

fPreis = ePreis;

Der Grund liegt darin, dass bei der Implementierung dasSchlüsselwort explicit verwendet wurde. Wenn Sie statt expli-cit das Schlüsselwort implicit verwenden, kompiliert auch die-se Zeile ohne Schwierigkeiten.

Schlüsselwort implicitCD-BeispielCasting2

public static implicit operator CHF(Euro eu){ ...}

Im Kapitel 2, C# – eine neue Programmiersprache ist Ihnenbeim Unterkapitel Überlagern von Operatoren vielleicht aufge-fallen, dass der Zuweisungsoperator unter C# nicht überlager-bar ist. Sie können dasselbe Verhalten aber meist über dieDefinition eines impliziten Casting-Operators erreichen.

ePreis = 3.92;

Damit diese Zeile funktioniert, muss ein impliziter Casting-Operator definiert werden. Dieser kann für die Klasse Euro infolgender Form realisiert werden.

Page 452: Otmar Ganahl - C Sharp

C #

452 13

CD-BeispielCasting3

public static implicit operator Euro(double val){ Euro t = new Euro(); t.Cent = (int)(val*100); return t;}

Die Definition von Casting-Operatoren ermöglicht einige inte-ressante Aspekte, die Sie beim Klassendesign berücksichtigensollten.

C#-PräprozessorWie auch C und C++ unterstützt C# das Konzept des Präpro-zessors. Der Präprozessor manipuliert in Abhängigkeit von Prä-prozessoranweisungen den Kompiliervorgang. Wie auch unterC/C++ werden Präprozessoranweisungen mit dem Zeichen #eingeleitet.

Beachten Sie aber, dass C# nicht alle Präprozessoranweisun-gen, die C und C++ kennt, implementiert. So sind unter C# kei-ne Makros möglich! Und dies ist doch eine wesentlicheEinschränkung, werden doch gerade Makros in C und C++ in-tensiv verwendet. Doch gerade diese Makros waren Grund fürviele irritierende Nebeneffekte.

#define und #undefDie Anweisung #define erlaubt die Definition von Präprozes-sor-Symbolen. Diese Symbole können dann bei späteren Prä-prozessoranweisungen berücksichtigt werden. Diese Anwei-sungen dürfen nur ganz am Beginn einer Quellcodedateiauftreten!

#define TRACE

Die Anweisung #undef kann ein Symbol wieder auflösen.

#if, #elif, #else, #endifDiese Anweisungen werden verwendet, um den Kompiliervor-gang in Abhängigkeit einer Bedingung zu prüfen.

Page 453: Otmar Ganahl - C Sharp

Ausgewählte C -Kapitel45313

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, wenndas Symbol TRACE definiert ist! Mit #else und #elif könnennoch komplexere Kontrollstrukturen für den Kompiliervor-gang definiert werden.

#if TRACE==false...#endif

Der Code innerhalb der #if-#endif-Anweisung würde nur dannkompiliert werden, wenn das Symbol TRACE nicht definiertwurde.

Die unter C und C++ sehr beliebte Verwendung des #if-State-ments, um ganze Codebereiche auszublenden (statt auszu-kommentieren), kann auch unter C# angewendet werden.

#if false...#endif

Sämtlicher Code innerhalb der #if-#endif-Anweisung wirdvom Kompiler nicht berücksichtigt.

#region, #endregionDiese Anweisungen haben keinen Einfluss auf den Kompilier-vorgang. Diese dienen dazu, bestimmte Codeabschnitte zukennzeichnen bzw. mit einem Namen zu versehen. Herstellervon Entwicklungssystemen könnten diese Anweisung berück-sichtigen, und vor allem bei Editoren die Ansicht auf dem Bild-schirm steuern (Visual Studio.NET berücksichtigt dieseAnweisungen).

Page 454: Otmar Ganahl - C Sharp

C #

454 13

#region Windows Form Designer generated code...#endregion

#warning, #errorMit diesen Anweisungen können Fehlermeldungen bzw. War-nungen im Ausgabefenster des Kompilers erzeugt werden.Hier ein Beispiel, das eine Warnung ausgibt, wenn über dasSymbol der TRACE-Modus zugeschaltet ist.

#if TRACE #warning "Vor der Auslieferung TRACE entfernen"#endif

Im Ausgabefenster des Kompilers wird dann folgende War-nung erscheinen, wenn das Symbol TRACE definiert ist.

warning CS1030: #warning: '"Vor der Auslieferung TRACE entfernen"'

Page 455: Otmar Ganahl - C Sharp

Fallbeispiel WWWPhotoPool

Beschreibung und Architektur derWWWPhotoPool-Applikation

456

CD-Beispiel 459

Page 456: Otmar Ganahl - C Sharp

C #

456 14

Beschreibung und Architektur der WWWPhotoPool-ApplikationWWWPhotoPool ist eine Applikation, die Fotos, Bilder etc. inunterschiedlichen Formaten verwaltet. Zentrales Element derApplikation ist der WWWPhotoPool-Server (Abb. 14.1). DieserServer hält in einer Datenbank die Informationen über die Fo-tos und Bilder. Allerdings sind die Fotos nicht in der Datenbankoder auf dem WWWPhotoPool-Server gespeichert, sondern inder Datenbank steht nur die Information über dieses Foto, un-ter anderem auch die tatsächlichen Speicherorte (URL). Fotoskönnen prinzipiell von jedermann (Publisher) über diesen Ser-ver publiziert werden. Dafür muss sich der Publisher allerdingsregistrieren. Wenn er registriert ist, dann kann er über einWeb-Service (PhotoPublishingService) seine Fotos in denWWWPhotoPool-Server eintragen lassen. Dazu muss der Pub-lisher Speicherplatz zur Verfügung stellen, der über Web er-reichbar ist. Der Web-Service PhotoPublishingService ist überein Passwort geschützt. Der WWWPhotoPool-Server bieteteinen weiteren Web-Service (PhotoRechercheService) an. Überdiesen Service können innerhalb der publizierten Fotos Recher-chen durchgeführt werden. Allerdings können über diesen Ser-vice Fotos nicht heruntergeladen werden, aber die URL derFotos kann erfahren werden. Über diese URL kann dann indirektem Kontakt (peer-to-peer) auf die Fotos zugegriffen wer-den. WWWPhotoPool-Server bietet seine Dienstleistungenalso nur über Web-Services an.

Irgendjemand auf der Welt kommt nun auf die Idee, die Fotosauf diesem WWWPhotoServer über eine eigene Web-Applikati-on zur Verfügung zu stellen. Dazu stellt er auf seinem Servereine Web-Seite (PhotoWeb) bereit, die ihrerseits über das Web-Service PhotoRechercheService auf den WWWPhotoPool-Ser-ver zugreift. Beliebige Browser können über diese Seite nunRecherchen durchführen und beliebige Fotos herunterladen(hier wieder über peer-to-peer). In Abbildung 14.1.sind die Zu-sammenhänge grafisch dargestellt.

WWWPhotoPool-ServerDie Daten auf dem PhotoPool-Server werden in einer SQL-Da-tenbank gehalten. Diese besteht aus zwei Tabellen.

Page 457: Otmar Ganahl - C Sharp

Fallbeispiel WWWPhotoPool45714

Die Tabelle tPublisher speichert die Publisher, die ihre Fotosüber WWWPhotoPool bereitstellen möchten. Sie sehen, einemPublisher ist ein Benutzername (Account) und ein Passwort zu-geordnet. Außerdem ist seine E-Mail-Adresse und die Server-URL eingetragen. Stellen Sie sich vor, ein angemeldeter Publis-her kümmert sich nicht mehr um seine Einträge, und stellt denSpeicherbereich auf seiner Maschine nicht mehr bereit. DerAnwender wird es erst merken, wenn er direkt über die peer-to-peer-Verbindung diese Fotos herunterladen möchte. Umdiesen Umstand zu entschärfen, implementiert der WWWPho-toPool-Server einen Prozess ReliabilityTest.exe (siehe Abbil-dung 14.1). Dieser prüft von Zeit zu Zeit, ob der Publisher über

System-Architektur

Tabelle tPublisher

Abb. 14.1

Abb. 14.2

Page 458: Otmar Ganahl - C Sharp

C #

458 14

Web erreichbar ist. Die Anzahl dieser Versuche (AccessTry) unddie Anzahl der erfolgreichen Versuche (AccessSuccess) werdenebenfalls in dieser Tabelle abgespeichert. Damit kann ein Ver-hältnis bezüglich der Verfügbarkeit errechnet werden. Wirdhier ein bestimmter Wert unterschritten, wirft der Reliability-Test-Prozess den Publisher aus der Datenbank und löscht auchalle Foto-Einträge in der Datenbank. Bevor dies eintritt wird ernoch einmal über E-Mail verwarnt.

In der Tabelle tPhoto werden nun die Informationen der Fotosabgespeichert. Neben einem Titel, einem Eintragsdatum undeinem beliebigen Kommentar ist hier natürlich die tatsächli-che URL des Fotos abgespeichert. Der Eintrag IDPublisher stellteine Relation zur Tabelle tPublisher dar. Auf dem Server exis-tiert auch ein Windowsprogramm für den Administrator (Pho-toPoolAdmin.exe). Damit kann die Datenbasis elegant verwal-tet werden (z.B. Anlegen, Update usw. eines Publishers). DerWWWPhotoPool-Server bietet seine Dienste über zwei Web-Service-Schnittstellen an. Die Web-Service-Schnittstelle Photo-PublisherService erlaubt das Registrieren bzw. Löschen vonFoto-Einträgen. Auch eine Änderung der Stammdaten des Pu-blishers ist über diesen Web-Service möglich. Diese Schnitt-stelle ist also primär für die Publisher gedacht und ist pass-wortgeschützt. Die Web-Service-Schnittstelle PhotoRechercheService ist für die Anwender (Consumer) gedacht. Über dieseSchnittstelle kann letztendlich die URL eines interessanten Fo-tos erfahren werden. Wenn diese URL bekannt ist, kann aufdas Foto auch zugegriffen werden.

PhotoWebServerDas ist eine „normale“ ASP.NET-Anwendung. Diese Web-Seitebietet einem Internetbenutzer eine grafische Oberfläche, umim Foto-Pool Recherchen durchzuführen. Die ASP.NET-Anwen-

Tabelle tPhoto

Abb. 14.3

Page 459: Otmar Ganahl - C Sharp

Fallbeispiel WWWPhotoPool45914

dung selbst kommuniziert aber mit dem eigentlichen WWW-PhotoPool-Server über das Web-Service PhotoRechercheService(siehe Abb. 14.1). Normale Web-Clients können somit auf dieFotos im Foto-Pool zugreifen.

PublisherEin Publisher kann über die Web-Service-Schnittstelle Photo-PublisherService seine Einträge verwalten. Der Publisher mussauf seiner Maschine einen Speicherplatz freigeben, über dendann übers Web zugegriffen werden kann. Die Maschinenmüssen also auch übers Web erreichbar sein! Für die Verwal-tung der Fotos auf seiner Maschine bekommt der Publisher eineigenes Windowsprogramm zur Verfügung. Über dieses Pro-gramm kann er Fotos in den Speicherbereich kopieren undgleichzeitig auch beim WWWPhotoPool-Server registrieren.Natürlich kann er diese auch löschen. Seine Stammdaten aufdem WWWPhotoPool-Server kann er ebenfalls über dieses Pro-gramm verwalten.

CD-BeispielSämtlicher Code für dieses Beispiel befindet sich auf der beilie-genden CD. Da die detaillierte Beschreibung den Seitenrah-men dieses Buches sprengen würde, finden Sie diese ebenfallsauf der CD in Form eines Winword-Dokuments. Es werden da-bei folgende Themen abgedeckt:

WWWPhotoPool-ServerData-Access-Assembly (SQL-Datenbank, ADO.NET, Da-taSet, DataGrid, XML-Konfiguration)Zwei Web-Services (ASP.NET-WebService, Session-Ver-waltung, Logon, XML-Konfiguration)PhotoAdmin.exe (Windowsprogramm)ReliabilityText.exe (Konsolenprogramm, E-Mail)

Page 460: Otmar Ganahl - C Sharp

C #

460 14

PublisherPublisherAdmin.exe (Windowsprogramm, kundenspe-zifische Windowssteuerelemente, XML-Konfigurati-on, Web-Service-Zugriffe , Webzugriffe, Dateiverarbei-tung ...)

PhotoWebPhotoWeb.aspx (ASP.NET, kundenspezifische Web-Steuerelemente, XML-Konfiguration, Session-Verwal-tung, Web-Service-Zugriffe, peer-to-peer ...)

Page 461: Otmar Ganahl - C Sharp

Stichwortverzeichnis461

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

Aabstract . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84, 86abstrakte Basisklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84Activator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401Activator.GetObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .404active . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327Active Server Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318ActiveX Data Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285ActiveX-Steuerelementen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444AddAttributesToRender . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356AddResource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252AddStyleAttribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354ADO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285ADO.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283

Page 462: Otmar Ganahl - C Sharp

C #

462

Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120AL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70, 236API-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .186Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360Application Programming Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . 70appSettings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276, 277Architektur eines Win32-Windowsprogramms . . . . . . . . . . . . . . . .188Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82, 89ArrayList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96as . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88asmx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375, 376ASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317ASP-Seite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321aspx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326Assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36, 119, 120Assembly Linker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .126AssemblyInfo.cs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134asynchrone Aufrufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112, 145, 161attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .149AttributeUsage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113Aufbau des Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127Aufzählungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .58Ausgabepfad ändern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398Ausnahmeverarbeitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69AutoPostBack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .345

BBackColor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342Bad Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .148Basisklasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77Basisklassenkonstruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80Baugruppen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120BeginInvoke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423, 424

Page 463: Otmar Ganahl - C Sharp

Stichwortverzeichnis463

Bilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246Bindung, späte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83Bitmaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223, 236, 246bool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49, 98Boolean Equals(Object) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52BorderColor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342BorderStyle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342BorderWith . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342Boxing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99, 100Bruchzahl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42Brush . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236, 237build number . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134Buildaktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249Build-Prozess . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .31, 35Button . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198byte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49Byte-Stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252

CC# Codedatei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29C# Mechanismus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38C/C++ Mechanismus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360, 366CacheDependency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .371case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99Casting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81, 88, 173, 448catch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .71catch-Block . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72CDATA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161Channel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394, 407char . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49Character Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147CheckBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344child . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

Page 464: Otmar Ganahl - C Sharp

C #

464

child elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145ChildNodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32, 44, 46Clear . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97Click . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417Client-aktivierte Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395ClientSize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234CLR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53, 190cmiClearAll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221cmiSend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221code-behind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330, 331, 334, 382Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89Color . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196ColorTranslator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .353COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22, 36, 120, 392, 444COM+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392Command Prompt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37, 123Comment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161Common Language Runtime . . . . . . . . . . . . . . . . . . . . . . . . . . .53, 190, 430Component Object Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .36, 120, 444Composite Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .358COM-Programmiermodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36config . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268, 415ConfigurationSettings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .272Connection Points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106Console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35, 45Container . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .144ContextMenu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348, 357CORBA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392Count . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97Create . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271

Page 465: Otmar Ganahl - C Sharp

Stichwortverzeichnis465

CreateInstance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401CreateNavigator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .173CSC.EXE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124CTS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430CultureInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261Current . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94CurrentCulture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261CurrentUICulture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261

DDAO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284Data Source Name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292DataColumn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303DataGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .313DataRow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303DataRowState . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .306DataSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287, 300, 302DataSet und XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .306DataTables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303Datei-Öffnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287DateTime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298DateTimePicker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387DCOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22, 392Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341Debug-Informationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .31decendant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149decimal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .49, 78default . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99delegate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102Delegates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .101delegieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103DELETE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294DeleteCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .309denominator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

Page 466: Otmar Ganahl - C Sharp

C #

466

Designer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209Device . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241Dialoge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .225DialogResult . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228, 229DispatchMessage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .189Dispose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213DLL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120DLL-Hölle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .22, 138DNS-Name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161document root . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143DocumentType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .162DOM-Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156DOM-Objektmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160, 165double . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49DoubleClick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .233do-while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100DrawEllipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .237DrawImage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247DrawLine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .237DrawString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241DTD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163dynamic link library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120dynamische Bibliotheken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120

EEH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69Eigenschaftsfenster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214Einbinden von Steuerelementen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197eingebettete Ressource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249Einsprungsfunktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144, 162else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98EndElement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162endif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452

Page 467: Otmar Ganahl - C Sharp

Stichwortverzeichnis467

EndInvoke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424End-Tag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141, 145, 157Entity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162Entwurfsansicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .209Enumerationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58Equals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432Erweitern eines Steuerelements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109EventArgs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112EventHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .110Event-Mechanismus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106, 196, 214, 426Exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .69, 71, 383Exception-Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69EXE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .192, 249ExecuteNonQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293ExecuteReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293ExecuteScaler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293explicit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451Extensible Markup Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140

FFarbendarstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196Felder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89Fensterereignisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196Fill . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302FillEllipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237Finalize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75Firewalls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394float . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49Font . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237, 342for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100foreach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .95, 100ForeColor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342

Page 468: Otmar Ganahl - C Sharp

C #

468

Formatter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393, 407FormDesigner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194, 196, 209forward cursor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .158, 162fraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .42Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70FromArgb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196FromFile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247frühe Bindung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .83Funktionszeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101, 103

GGAC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135GACUTIL.EXE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137Garbage Collecting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .23Garbage Collector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .47, 54, 77, 402GC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .47GDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236GDI+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236get . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69GetConfig . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .272GetCustomAttributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115GetElementsByTagName . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166GetEnumerator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95GetExecutingAssembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254GetHashCode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .433GetMessage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .189GetObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .254, 404GetString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254GetType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434Global . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363Global Assembly Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135global.asax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334, 364goto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .99, 100Graphic Device Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236

Page 469: Otmar Ganahl - C Sharp

Stichwortverzeichnis469

Graphics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238GraphicsUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243GroupBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208Grunddatentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44guarded-Block . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72GUID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .135Gültigkeitsbereich . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

HHash-Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361Hashtable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97Heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46, 55, 90Height . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .321HtmlTextWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353HttpChannel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399, 407http-GET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322, 374http-POST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322, 374HyperLink . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343

IIAsyncResult . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423IConfigurationSectionHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269ID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342IDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112, 394IEnumerable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94, 100IEnumerate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94IEnumerator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98IIS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318, 374IIS Hosting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418IL-Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130ILDASM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128, 129ILDASM (IL Disassembler) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128ILease . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .408

Page 470: Otmar Ganahl - C Sharp

C #

470

ImageList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222, 223Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246Image-Steuerelement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .343implicit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451INamingContainer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359Indexer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93inetpub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331Infoset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142Inherits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329InitializeComponent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213, 215, 258, 336InitializeLifetimeService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413InitialLeaseTime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411InnerText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .168InnerXmlText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .168InsertCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309Installationsmechanismen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .22int . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49Int32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207Int32 GetHashCode() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86Interface Definition Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394Intermediate Language Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121, 130internal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85internal protected . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85Internet Information Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .318, 374Interoperabilität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444Interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .325is . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88IsCompleted . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424ISerialize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426ISponsor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412IsPostBack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346Item . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221Iterationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100IXPathNavigator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

Page 471: Otmar Ganahl - C Sharp

Stichwortverzeichnis471

JJagged Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91JAVA-Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130JIT-Kompiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130JScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322just-in-time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130

Kkey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43, 53Klassen und Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42Kommentare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32, 146Konfigurationsdateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265, 268Konsole . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .123Konsole-Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192Konstruktoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .45, 80Kontextmenu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .221Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98Koordinatensystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199, 241Kulturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257kundenspezifische Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350kundenspezifische Steuerelemente . . . . . . . . . . . . . . . . . . . . . . 230, 348

LLabel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259lease time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .408Leased Based Lifetime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .408Length . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238LifetimeServices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410ListBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347LoadPage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447localhost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333Localizable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258

Page 472: Otmar Ganahl - C Sharp

C #

472

Location . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199long . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49low coupling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88

MMachine.config . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .273Main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32major version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134managed heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46, 53, 57Manifest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122, 129Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97Markup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140marshal by referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426marshal by value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426MarshalByRefObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395, 397Matrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81Members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214Member-Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45, 80Menü . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .219Menüliste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .219Message Queue Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392Metadaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36, 121, 122method-Attribut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .327Methode mit Bindung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60Microsoft Data Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287minor version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134Modifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122MouseEventArgs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221MoveNext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94Mscorlib.dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37MSDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287, 288MSDN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32

Page 473: Otmar Ganahl - C Sharp

Stichwortverzeichnis473

Multicast-Deletates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104multifile assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131Multifile-Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .125

NNamensraum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34namespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34Navigate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447NavigateUrl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343nested elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145new . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46, 81None . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162null . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88numerator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

Oobject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57Objektorientierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325ODBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284OLE-DB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285OleDbCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286OleDbConnection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286OleDbDataAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286OleDbReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286OleDbTransaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286OnClick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196, 231OnPaint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238OpenFileDialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .62, 63out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64, 66output.Write . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353override . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52, 83

Page 474: Otmar Ganahl - C Sharp

C #

474

PPage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241, 242, 329Page_Load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330PageUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243, 246PaintArgs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241parent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .149parent element . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145Parse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207, 339PathProperties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106Pen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236, 237Performanz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76PictureBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226, 233PIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .144Platzhalter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .45Point . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238PointF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238, 244Präprozessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452primitive types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44private . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85private Assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135Processing Instructions, PI's . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .143, 144ProcessingInstruction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162Programm-Loader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32Projektmappe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .28Projektmappen-Explorer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29, 38Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67, 214protected . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .81, 85Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379, 380, 393public . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .41, 60, 67, 85Publish-Subcribe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106Pull-Modells . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160Push-Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160

Page 475: Otmar Ganahl - C Sharp

Stichwortverzeichnis475

RRadiobuttonList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345RadionButtons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201, 207, 208Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161, 297, 298ReadElementString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161ReadXml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .306RecordSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287Rectangle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .236, 238RectangleF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238ref . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64ReferenceEquals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48, 124Referenzobjekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54Referenztyp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89RegisterActivatedClientType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .406RegisterChannel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399RegisterWellKnownClientType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .406RegisterWellKnownServiceTyp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403Registrierungsdatenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22Release-Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .31Remoting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391Remoting.Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399RemoveAt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97RenderBeginTag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354RenderChildren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .360RenderEndTag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354Renew . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .411RenewOnCallTime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323, 324Reset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94ResGen.exe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250ResourceManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251, 254, 258ResourceReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250ResourceWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250Response . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323, 327

Page 476: Otmar Ganahl - C Sharp

C #

476

Ressource-Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .251, 254Ressourcen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121, 248resx-Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251ResXResourceReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250ResXResourceWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250return . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100revision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134RichTextBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217root node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143RotateTransform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246runat= . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329

SSatelliten-Dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262SAX-Programmiermodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160sbyte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49ScaleTransform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246Schemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147Schieberegler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .201Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100Schlüsselpaar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136Schnittstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .86, 444Schutzklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85Schutzklassen-Modifizierer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41Schutzkonzept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .47scope operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .34Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .374sealed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84section . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268sectionGroup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274SEH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69SELECT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296SelectCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .302, 309SelectNodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171

Page 477: Otmar Ganahl - C Sharp

Stichwortverzeichnis477

self . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149self-hosted . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418Serialisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .175Serializable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426Server aktivierte Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403Server-Explorer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288, 290Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .360set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69SGML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141Shared Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .135short . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49Show() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225ShowDialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229ShowDialog() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225SignalHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108single file assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121SingleCall . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403, 405Singlefile-Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .123Singleton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405Size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199, 236, 238SizeChanged . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233SizeF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238SizeMode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233Small Object Access Protocol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374smart array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93SMTP-Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40, 218sn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .374, 407Soap 1.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154Sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392Software Engineering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22SolidBrush . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .237, 240Speicherleck . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76Sponsor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412SponsorshipTimeout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .409

Page 478: Otmar Ganahl - C Sharp

C #

478

Sprungbefehle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284SQL Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287SqlCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286, 292SqlConnection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286, 292SqlDataAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286, 300SqlDataReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286, 297SqlTranaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46, 54, 57, 89Stack-Unwinding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77standard entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147Standard Generalized Markup Language . . . . . . . . . . . . . . . . . . . . . . 141Start-Tag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141, 145, 157statische Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61Statusverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360Steuerelement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197StretchImage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .233string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49String ToString() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52strong name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136struct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55Structured Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69Structured Query Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284Strukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356, 357Stylesheet Language for Transformation . . . . . . . . . . . . . . . . . . . . . . 151submit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .322subscribe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99switch-case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44, 110System.Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89, 92System.Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112System.Boolean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49System.Char . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

Page 479: Otmar Ganahl - C Sharp

Stichwortverzeichnis479

System.Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94System.Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269System.Configuration.NameValueFileSectionHandler . . . . 275, 276System.Data.OleDb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286System.Data.Sql . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286System.Delegate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102System.dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192System.Drawing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .195, 199, 236System.Drawing2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236System.EventHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .110System.Globalization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261System.Int16 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49System.Int32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49System.Int64 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49System.MulticastDelegate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102System.Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .51System.Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112system.runtime.remoting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400, 415System.Runtime.Remoting.Lifetime . . . . . . . . . . . . . . . . . . . . . . . 408, 411System.Sbyte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49System.Threading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261System.UInt16 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49System.UInt32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49System.UInt64 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49System.ValueType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54, 55System.Web.Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369System.Web.dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40System.Web.HttpApplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363System.Web.Services.WebService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375System.Web.UI.WebControls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330, 349System.Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187System.Windows.Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192System.xml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160System.Xml.dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157, 302System.Xml.Serializer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176

Page 480: Otmar Ganahl - C Sharp

C #

480

System.Xml.Xsl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174Sytem.Byte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

TTabControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445TabIndex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342TabPages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445TagPrefix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350TcpChannel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407tcp-Kanal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .322TextBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201, 217Text-Property . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .45Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .188throw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71ToolBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .222ToolTip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223, 342ToString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .434, 450TrackBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .201Transformationsmatrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244TranslateTransform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246try . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71try-Block . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .72Type GetType() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52typeof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400Typisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .325Typ-Metadaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

UÜberladen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61Überladen von Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61Überladen von Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62uint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49ulong . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

Page 481: Otmar Ganahl - C Sharp

Stichwortverzeichnis481

Unboxing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57Unicode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147Update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295, 313UpdateCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .309Url . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381User Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106ushort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49using . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

VValidationType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164Validierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97VBScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322Verarbeitungsanweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77Versionsnummer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98virtual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83virtual tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83virtuelle Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82Visible . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342

WW3C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .140, 160Web.Config . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418Web-Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445WebControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .357, 360WebForm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327WebMethod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .116WebServices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373, 374, 375Web-Steuerelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326, 341well formed document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142Werkzeugleiste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222Wertetyp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

Page 482: Otmar Ganahl - C Sharp

C #

482

Wertobjekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .54while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100WhiteSpace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .162Width . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238, 342Windows-Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .192WindowsForms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213Windows-Steuerelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200winexe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192WinSockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392WM_KEYDOWN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .188WM_LBUTTONDOWN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .188WM_PAINT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241Wrapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446WriteElementString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157WriteXml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306WWWPhotoPool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456

XXCOPY-Installing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267XDR-Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139, 140XML Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154XML Schemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154XmlAttribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165XmlComment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165XmlDeclaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165XmlDocument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156, 165, 368XML-Dokument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143XmlElement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165XmlEntity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165XML-Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140XML-Informationsmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141XML-Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153XmlNode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165, 166

Page 483: Otmar Ganahl - C Sharp

Stichwortverzeichnis483

XmlNodeList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .166, 171XmlNodeType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161XmlNotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165XmlReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160XmlRootAttribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182XML-Serialisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .175XmlSerializer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176xml-stylesheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144XmlText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165XmlTextReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160XmlTextWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156, 157XmlValidatingReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160, 163XmlWhitespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165XmlWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156XPATH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148, 171, 172, 279XPath-Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154XPathDocument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172, 173XPathNodeIterator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .173XSD-Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163XSL/T Transformationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154XSLT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150, 173XSLT-Prozess . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151XslTransform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368XSL-Transformationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .173, 174

ZZeichenreferenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147