Microsoft Visual C# 2008 Schri tt für Schri tt Microsoft Visual C# 2008 – Schritt für Schritt Mirco De Roni, 2010 Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Inhaltsverzeichnis 1 Willkommen bei C#............................................................................................................ 5 Ihr erstes C#-Programm schreiben ...................................................................................... 5 Namespaces und Assemblys............................................................................................... 5 Eine grafische Anwendung erstellen .................................................................................... 6 2 Mit Variablen, Operatoren und Ausdrücke arbeiten ........................................................ 7 Anweisungen verstehen ...................................................................................................... 7 Bezeichner verwenden ........................................................................................................ 7 Schlüsselwörter identifizieren .............................................................................................. 7 Variablen verwenden ........................................................................................................... 8 Mit primitiven Datentypen arbeiten ...................................................................................... 8 Arithmetische Operatoren verwenden.................................................................................. 9 Variablen inkrementieren und dekrementieren .................................................................. 10 Implizit typisierte lokale Variablen deklarieren ................................................................... 11 3 Methoden schreiben und Gültigkeitsbereiche anwenden ............................................ 12 Methoden deklarieren ........................................................................................................ 12 Methoden aufrufen ............................................................................................................ 12 Gültigkeitsbereiche anwenden ........................................................................................... 13 4 Entscheidungsanweisungen verwenden ....................................................................... 15 Boolesche Variablen deklarieren ....................................................................................... 15 Boolesche Operatoren verwenden .................................................................................... 15 Entscheidungen mit if-Anweisungen treffen .................................................................... 16 switch-Anweisungen verwenden ........................................................................................ 17 5 Verbundzuweisungen und Schleifen verwenden .......................................................... 19 Verbundzuweisungsoperatoren verwenden ....................................................................... 19 Schleifen mit while-Anweisungen schreiben.................................................................... 19 Schleifen mit for-Anweisungen schreiben ........................................................................ 19 Schleifen mit do-Anweisungen schreiben .......................................................................... 20 6 Fehler und Ausnahmen behandeln ................................................................................ 21 Code probieren und Ausnahmen abfangen ....................................................................... 21 Ganzzahlarithmetik mit checked und unchecked ........................................................... 22 Ausnahmen auslösen ........................................................................................................ 23 Einen finally-Block verwenden ..................................................................................... 23 7 Klassen und Objekte erstellen und verwalten ............................................................... 24 Was ist Klassifizierung? ..................................................................................................... 24 Was ist Kapselung? ........................................................................................................... 24 Eine Klasse definieren und verwenden .............................................................................. 24 Den Zugriff kontrollieren .................................................................................................... 25 Mit Konstruktoren arbeiten ................................................................................................. 25 Statische Methoden und Daten verstehen ......................................................................... 27 8 Werte und Verweise verstehen ....................................................................................... 30 Werttypvariablen und Klassen kopieren............................................................................. 30 NULL-Werte und Typen, die NULL-Werte zulassen........................................................... 30 Parameter mit ref und out übergeben ................................................................................ 31 Wie der Computerspeicher organisiert ist .......................................................................... 32 Die Klasse System.Object ................................................................................................. 32 Daten sicher konvertieren .................................................................................................. 32 9 Werttypen mit Enumerationen und Strukturen erstellen .............................................. 33 Mit Enumerationen arbeiten ............................................................................................... 33 Mit Strukturen arbeiten ...................................................................................................... 33 10 Arrays und Auflistungen verwenden ............................................................................. 35 Was ist ein Array? ............................................................................................................. 35 Was sind Auflistungsklassen? ........................................................................................... 36 11 Parameterarrays verstehen ............................................................................................ 41 Mirco De Roni -2- Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Arrayargumente verwenden .............................................................................................. 41 12 Vererbung richtig einsetzen............................................................................................ 42 Was ist Vererbung? ........................................................................................................... 42 Vererbung einsetzen ......................................................................................................... 42 13 Schnittstellen erstellen und abstrakte Klassen deklarieren ......................................... 45 Schnittstellen verstehen..................................................................................................... 45 Schnittstellensyntax ...................................................................................................... 45 Schnittstelleneinschränkungen...................................................................................... 45 Eine Schnittstelle implementieren ................................................................................. 45 Auf eine Klasse über ihre Schnittstelle verweisen ......................................................... 47 Mit mehreren Schnittstellen arbeiten ............................................................................. 47 Abstrakte Klassen.............................................................................................................. 47 Versiegelte Klassen ........................................................................................................... 49 14 Garbage Collection und Ressourcenverwaltung einsetzen ......................................... 50 Der Lebenszyklus von Objekten ........................................................................................ 50 Destruktoren erstellen ................................................................................................... 50 Warum wird der Garbage Collector verwendet? ............................................................ 51 Wie funktioniert der Garbage Collector?........................................................................ 51 Ressourcenverwaltung ...................................................................................................... 52 Freigabemethoden ........................................................................................................ 52 Ausnahmesichere Freigabe von Ressourcen ................................................................ 52 Die using-Anweisung .................................................................................................... 53 15 Eigenschaften implementieren, um auf Attribute zuzugreifen ..................................... 54 Was sind Eigenschaften? .................................................................................................. 54 Eigenschaften verwenden ............................................................................................. 55 Schreibgeschützte Eigenschaften ................................................................................. 55 Lesegeschützte Eigenschaften ..................................................................................... 55 Zugriffsmodifizierer bei Eigenschaften .......................................................................... 55 Einschränkungen von Eigenschaften verstehen ................................................................ 56 Schnittstelleneigenschaften deklarieren............................................................................. 56 16 Delegaten und Ereignisse ............................................................................................... 57 Delegaten deklarieren und verwenden .............................................................................. 57 Benachrichtigungen mit Ereignissen realisieren ................................................................ 58 Ein Ereignis deklarieren ................................................................................................ 58 Ein Ereignis abonnieren ................................................................................................ 58 Das Abonnement eines Ereignisses kündigen .............................................................. 58 Ein Ereignis auslösen.................................................................................................... 58 17 Einführung in Generics ................................................................................................... 60 Eine generische Klasse erstellen ....................................................................................... 60 Die Theorie der binären Bäume .................................................................................... 60 Eine Binärbaum-Klasse mit Generics erstellen.............................................................. 60 18 Speicherinterne Daten mit Abfrageausdrücken abrufen .............................................. 62 Was ist LINQ? ................................................................................................................... 62 LINQ in einer C#-Anwendung einsetzen ............................................................................ 62 Daten auswählen .......................................................................................................... 64 Daten filtern .................................................................................................................. 64 Daten sortieren, gruppieren und zusammenfassen ....................................................... 64 Daten verknüpfen.......................................................................................................... 65 19 Überladen von Operatoren ............................................................................................. 66 Operatoren verstehen ........................................................................................................ 66 Operatoreneinschränkungen ......................................................................................... 66 Überladene Operatoren ................................................................................................ 66 Verbundzuweisungen verstehen........................................................................................ 67 20 Einführung in Windows Presentation Foundation ........................................................ 68 Eine WPF-Anwendung erstellen ........................................................................................ 68 Mirco De Roni -3- Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 21 22 23 24 Steuerelemente in das Formular aufnehmen ..................................................................... 69 Ereignisse in einem WPF-Formular behandeln .................................................................. 69 Mit Menüs und Dialogfeldern arbeiten ........................................................................... 70 Richtlinien für Menüs ......................................................................................................... 70 Menüs und Menüereignisse ............................................................................................... 70 Kontextmenüs ................................................................................................................... 71 Windows-Standarddialogfelder .......................................................................................... 71 Die Klasse SaveFileDialog verwenden .......................................................................... 71 Eine Datenbank verwenden ............................................................................................ 72 Eine Datenbank mit ADO.NET abfragen ............................................................................ 72 Die Northwind-Datenbank ............................................................................................. 72 Bestellinformationen mit ADO.NET abfragen ................................................................ 72 Eine Datenbank mit DLINQ abfragen................................................................................. 74 Eine Entitätsklasse definieren ....................................................................................... 74 Bestellinformationen mit einer DLINQ-Abfrage abrufen ................................................. 75 Einen Webdienst erstellen und verwenden ................................................................... 77 Was ist ein Webdienst? ..................................................................................................... 77 Die Rolle von SOAP ...................................................................................................... 77 Was ist die Web Services Description Language? ........................................................ 77 Einen Webdienst erstellen ................................................................................................. 77 Quellenverzeichnis .......................................................................................................... 79 Mirco De Roni -4- Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 1 Willkommen bei C# Microsoft Visual C# ist Microsofts leistungsfähigste komponentenorientierte Programmiersprache. C# spielt in der Architektur des Microsoft .NET Frameworks eine entscheidende Rolle und man hat Parallelen gezogen zu der Rolle, die die Programmiersprache C in der Entwicklung von Unix eingenommen hat. Ihr erstes C#-Programm schreiben Die Datei Program.cs definiert eine Klasse Program, die eine Methode Main enthält. Alle Methoden müssen innerhalb einer Klasse definiert werden. Die Methode Main ist eine spezielle Methode und stellt den Programmeintrittspunkt dar. static void Main(string[] args) { System.Console.WriteLine("Hello World"); } Wichtig Die Programmiersprache C# berücksichtigt die Gross-/ Kleinschreibung. Deshalb müssen Sie Main mit einem grossen M schreiben. IntelliSense-Symbole: Symbol Bedeutung Methode Eigenschaft Klasse Struktur Enumeration Schnittstelle Delegat Erweiterungsmethode Hinweis Häufig enthalten Codezeilen zwei Schrägstriche und einen darauf folgenden Text. Dabei handelt es sich um Kommentare. Der Compiler ignoriert sie, aber sie sind sehr nützlich für den Entwickler, weil sich damit dokumentieren lässt, was ein Programm tatsächlich bewirkt. Console.ReadLine(); //Warten, bis der Benutzer Eingabe-Taste drückt Der Compiler überspringt den gesamten Text beginnend bei den beiden Schrägstrichen bis zum Ende der Zeile. Einen mehrzeiligen Kommentar leiten Sie mit den Zeichen /* ein. Daraufhin überspringt der Compiler alles, bis er die abschliessende Sequenz */ findet, die auch erst viele Zeilen weiter unten erscheinen kann. Tipp Wenn das Ausgabefenster nicht erscheint, wählen Sie im Menü Ansicht den Befehl Ausgabe, um das Fenster einzublenden. Namespaces und Assemblys Eine using-Anweisung bringt einfach die Elemente in einem Namespace in den Gültigkeitsbereich und befreit Sie davon, die Namen von Klassen in Ihrem Code vollständig qualifizieren zu müssen. Klassen werden zu Assemblys kompiliert. Eine Assembly ist eine Datei, die normalerweise die Erweiterung .dll besitzt. Streng genommen sind aber ausführbare Programme mit der Dateierweiterung .exe ebenfalls Assemblys. Mirco De Roni -5- Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Eine Assembly kann viele Klassen enthalten. Die Klassen der .NET Framework-Klassenbibliothek wie zum Beispiel System.Console werden in Assemblys bereitgestellt, die auf dem Computer zusammen mit Visual Studio installiert werden. Die .NET Framework-Klassenbibliothek enthält mehrere Tausend Klassen. Die .NET-Klassenbibliothek ist in mehrere Assemblys aufgeteilt und zwar nach funktionellen Bereichen, zu denen die enthaltenen Klassen in Beziehung stehen. Zum Beispiel gibt es eine Kernassembly, die sämtliche allgemeinen Klassen wie zum Beispiel System.Console enthält und es gibt weitere Assemblys mit Klassen für die Manipulation von Datenbanken, den Zugriff auf Webdienste, das erstellen grafischer Benutzeroberflächen usw. Möchten Sie eine Klasse in einer Assembly verwenden, müssen Sie Ihrem Projekt einen Verweis auf diese Assembly hinzufügen. Dann können Sie using-Anweisungen in Ihrem Code einfügen, die die Elemente in den Namespaces in dieser Assembly in den Gültigkeitsbereich bringen. Zwischen einer Assembly und einem Namespace besteht nicht unbedingt eine 1:1-Verbindung. Eine einzelne Assembly kann Klassen für mehrere Namespaces enthalten und ein und derselbe Namespace kann sich über mehrere Assemblys erstrecken. Im Projektmappen-Explorer klicken Sie mit der rechten Maustaste auf den Ordner Verweise und dann auf Verweis hinzufügen. Eine grafische Anwendung erstellen Visual Studio 2008 bietet Ihnen zwei Ansichten einer grafischen Anwendung: die Entwurfsansicht und die Codeansicht. Im Code- und Testeditorfenster modifizieren und verwalten Sie den Code und die Logik für eine grafische Anwendung und im Fenster der Entwurfsansicht gestalten Sie das Layout der Benutzeroberfläche. Hinweis Visual Studio 2008 bringt zwei Vorlagen für grafische Anwendungen mit: Windows-Forms-Anwendung und WPF-Anwendung. Windows Forms ist eine Technologie, die mit .NET Framework 1.0 eingeführt wurde. Die erweiterte Technologie WPF (Windows Presentation Foundation) ist seit .NET Framework 3.0 präsent. Gegenüber Windows Forms bietet sie viele zusätzliche Features und Fähigkeiten. using using using using using using using using using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Windows; System.Windows.Controls; System.Windows.Data; System.Windows.Documents; System.Windows.Input; System.Windows.Media; System.Windows.Media.Imaging; System.Windows.Navigation; System.Windows.Shapes; namespace WpfApplication { /// <summary> /// Interaktionslogik für Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); } } } Mirco De Roni -6- Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 2 Mit Variablen, Operatoren und Ausdrücke arbeiten Anweisungen verstehen Eine Anweisung ist ein Befehl, der eine bestimmte Aktion ausführt. In C# müssen Anweisungen ganz bestimmten Regeln entsprechen, die das Format und die Konstruktion der Anweisungen beschreiben. Die Gesamtheit dieser Regeln bezeichnet man als Syntax. Eine der einfachsten und gleichzeitig auch wichtigsten Syntaxregeln von C# besagt, dass alle Anweisungen immer mit einem Semikolon abzuschliessen sind. Bezeichner verwenden Bezeichner sind die Namen, mit denen Sie die Elemente – wie zum Beispiel Namespaces, Klassen, Methoden und Variablen – im Programm ansprechen. In C# müssen Sie sich an die folgenden Syntaxregeln halten, wenn Sie Bezeichner wählen: Es sind nur Buchstaben (Gross- und Kleinbuchstaben), Ziffer und Unterstriche zulässig. Ein Bezeichner muss mit einem Buchstaben beginnen, wobei ein Unterstrich als Buchstabe gilt. Wichtig C# ist eine Sprache, bei der unbedingt auf die Gross-/ Kleinschreibung zu achten ist. Schlüsselwörter identifizieren Die Programmiersprache C# reserviert 77 Bezeichner für den Eigenbedarf, die Sie nicht als Bezeichner für Ihre Zwecke verwenden dürfen. Jedes dieser so genannten Schlüsselwörter hat eine spezielle Bedeutung. Beispiele für Schlüsselwörter sind class, namespace und using. Die folgende Tabelle zeigt alle Schlüsselwörter im Überblick: abstract as base bool break byte case catch char checked class const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual void volatile while Tipp Im Code- und Texteditorfenster von Visual Studio 2008 erscheinen die Schlüsselwörter nach der Eingabe in blauer Schrift. C# verwendet zudem die folgenden Bezeichner. Diese sind nicht für C# reserviert und Sie können prinzipiell diese Namen als Bezeichner für Ihre eigenen Methoden, Variablen und Klassen verwenden. Allerdings sollten Sie dies nach Möglichkeit vermeiden. from get group into Mirco De Roni join let orderby partial select set value where -7- yield Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Variablen verwenden Eine Variable ist eine Speicherstelle, die einen Wert enthält. Jeder Variablen in einem Programm müssen Sie einen eindeutigen Namen geben. Den Namen der Variablen verwenden Sie, um auf den Wert zu verweisen, den die Variable speichert. Variablen benennen Für die Benennung von Variablen sollten Sie sich eine bestimmte Konvention aneignen, um Unklarheiten hinsichtlich Ihrer selbst definierten Variablen von vornherein zu vermeiden. Die folgenden Liste gibt einige Empfehlungen: Verwenden Sie keine Unterstriche Erstellen Sie keine Bezeichner, die sie lediglich durch die Gross-/ Kleinschreibung unterscheiden. Verwenden Sie also im selben Programm keine Variable mit dem Namen vorname und eine andere mit dem Namen Vorname, weil sich diese leicht verwechseln lassen. Beginnen Sie den Namen mit einem Kleinbuchstaben Besteht ein Bezeichner aus mehreren Wörtern, beginnen Sie das zweite und alle folgenden Wörter mit einem Grossbuchstaben. Hinweis Bezeichner, die sich nur durch die Gross-/Kleinschreibung unterscheiden, können die Widerverwendung von Klassen behindern, wenn Sie in Ihren Anwendungen auch andere Sprachen wie Visual Basic nutzen, bei denen die Gross-/Kleinschreibung keine Rolle spielt. Wichtig Die beiden ersten Empfehlungen sollten Sie als obligatorisch behandeln, da sie sich auf die Kompatibilität der Common Language Specification (CLS) beziehen. Möchten Sie Programme schreiben, die mit anderen Programmiersprachen (beispielsweise Microsoft Visual Basic) zusammenarbeiten können, müssen Sie diesen Empfehlungen folgen. Variablen deklarieren Variablen nehmen Werte auf. C# kann viele unterschiedliche Typen von Werten speichern und verarbeiten – Ganzzahlen, Gleitkommazahlen und Zeichenfolgen. Wenn Sie eine Variable deklarieren, müssen Sie angeben, welchen Typ von Daten sie speichern soll. Den Typ und den Namen einer Variablen legen Sie in einer Deklarationsanweisung fest. Hinweis Microsoft Visual Basic-Programmierer sollten beachten, dass C# keine implizite Variablendeklaration erlaubt. Alle Variablen müssen Sie vor ihrer Verwendung explizit deklarieren. Nachdem die Variable deklariert wurde, kann ihr einen Wert zugewiesen werden. Die folgende Anweisung weist der Variable vorname den Wert Mirco zu. Auch hier ist wieder das abschliessende Semikolon erforderlich. string vorname = ""; vorname = "Mirco"; Tipp Wenn Sie im Code- und Texteditorfenster von Visual Studio 2008 den Mauszeiger auf eine Variable setzen, erscheint nach kurzer Zeit ein Tooltipp, der den Wert der Variablen anzeigt. Mit primitiven Datentypen arbeiten C# verfügt über eine Reihe von vordefinierten Datentypen – die so genannten primitiven Typen. Die folgende Tabelle zeigt die gebräuchlichsten primitiven Datentypen in C#. Datentyp int long float Mirco De Roni Beschreibung Ganze Zahlen Ganze Zahlen Gleitkommazahlen einfacher Genauigkeit -8- Grösse (Bits) 32 64 32 Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ double decimal string char bool Gleitkommazahlen doppelter Genauigkeit Währungswerte Zeichenfolge Einzelzeichen Boolescher Wert 64 128 16 Bit je Zeichen 16 8 Arithmetische Operatoren verwenden C# unterstützt die allgemein bekannten arithmetischen Operationen: Pluszeichen (+) für Addition Minuszeichen (-) für Subtraktion Sternchen (*) für Multiplikation Schrägstrich (/) für Division. Diese Symbole bezeichnet man als Operatoren, da sie auf Werten operieren, um neue Werte zu erzeugen. Operatoren und Typen Nicht alle Operatoren sind auf alle Datentypen anwendbar. Ob Sie einen Operator auf einen Wert anwenden können, hängt vom Datentyp dieses Wertes ab. Beispielsweise lassen sich alle arithmetischen Operatoren auf die Datentypen char, int, long, float, double und decimal anwenden. Jedoch können Sie die arithmetischen Operatoren – mit Ausnahme des Plusoperators – nicht für Werte der Datentypen string oder bool einsetzen. Mit dem +-Operator können Sie zwei Zeichenfolgen miteinander verketten. Tipp Mit der Methode Int32.Parse des .NET Framework lässt sich ein Zeichenfolgenwert in eine Ganzzahl umwandeln, wenn Sie arithmetische Berechnungen auf Werten, die in Zeichenfolgen enthalten sind, ausführen müssen. Numerische Typen und unendliche Werte Zum Beispiel ist das Ergebnis der Division einer beliebigen Zahl durch null der Wert Infinity (Unendlichkeit). Da er ausserhalb des Wertebereichs der Typen int, long und decimal liegt, führt die Auswertung eines Ausdrucks wie zum Beispiel 2/0 zu einem Fehler. Die Typen double und float besitzen jedoch einen speziellen Wert, der die Unendlichkeit darstellen kann. Der Ausdruck 2.0/0.0 liefert damit den Wert Infinity. C# unterstützt ausserdem einen nicht so bekannten Operator: den Restwert- oder Modulo-Operator, der durch das Prozentsymbol (%) dargestellt wird. Das Ergebnis von x % y ist der Rest einer Division von x durch y. So ist 5 % 2 gleich 1, da 5 dividiert durch 2 gleich 2 mit dem Rest 1 ergibt. Die Operatorrangfolge steuern Die Rangfolge der Operatoren regelt die Reihenfolge, in der die Operatoren eines Ausdrucks ausgewertet werden. 2+3*4 Die Reihenfolge der Operationen ist wichtig, weil sie das Ergebnis verändert: Wenn Sie zuerst die Addition und dann die Multiplikation ausführen, bildet das Ergebnis der Addition (2 + 3) den linken Operanden des *-Operators und das Ergebnis des ganzen Ausdrucks ist 5 * 4 oder 20. Führen Sie zuerst die Multiplikation und dann die Addition aus, bildet das Ergebnis der Multiplikation (3 * 4) den rechten Operanden des +-Operators und das Ergebnis des gesamten Ausdrucks ist 2 + 12 oder 14. In C# besitzen die multiplikativen Operatoren (*, /, %) einen höheren Vorrang als die additiven Operatoren (+, -). Im oben angegebenen Ausdruck wird demnach zuerst die Multiplikation und dann die Addition ausgeführt. Das Ergebnis lautet also 14. Mirco De Roni -9- Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Mit Klammern können Sie die Operatorrangfolge ausser Kraft setzen und die Bindung von Operanden an Operatoren gezielt ändern. (2 + 3) * 4 Assoziativität und die Auswertung von Ausdrücken Die Assoziativität (Orientierung) gibt an, in welcher Richtung (nach links oder rechts) die Operatoren eines Operators ausgewertet werden. 4/2*6 Wenn Sie die Division zuerst ausführen, bildet das Ergebnis der Division (4 / 2) den linken Operanden der Multiplikation und das Ergebnis des gesamten Ausdrucks ist (4 / 2) * 6 oder 12. Führen Sie zuerst die Multiplikation aus, bildet das Ergebnis der Multiplikation (2 * 6) den rechten Operanden des /-Operators und das Ergebnis des gesamten Ausdrucks ist 4 / (2 * 6) oder 4 / 12. In diesem Fall bestimmt die Assoziativität der Operatoren, wie der Ausdruck ausgewertet wird. Die Operatoren * und / sind linksassoziativ, d.h. die Operanden werden von links nach rechts ausgewertet. Assoziativität und der Zuweisungsoperator In C# ist das Gleichheitszeichen ein Operator. Alle Operatoren geben einen Wert zurück, der sich aus den Operanden ergibt. Beim Zuweisungsoperator (=) ist das nicht anders. Er verknüpft zwei Operanden. Der Operand auf seiner rechten Seite wird ausgewertet und dann im Operand auf seiner linken Seite gespeichert. Der Wert des Zuweisungsoperators ist der Wert, der dem linken Operanden zugewiesen wurde. Variablen inkrementieren und dekrementieren Mit dem Operator + können Sie zum Wert einer Variablen 1 addieren: count = count + 1; Da das Addieren von 1 in C# recht häufig vorkommt, gibt es für diesen Zweck einen eigenen Operator: den Operator ++. Um die Variable count um 1 zu inkrementieren, schreibt man die folgende Anweisung: count ++; Analog lässt sich in C# mit dem Operator -- von einer Variablen 1 subtrahieren, zum Beispiel: count--; Präfix und Postfix Setzt man das Operatorsymbol vor die Variable, spricht man von der Präfixnotation des Operators, steht das Operatorsymbol nach der Variablen, ist das die so genannte Postfixnotation. count++; ++count; count--; --count; //Postfix-Inkrement //Präfix-Inkrement //Postfix-Dekrement //Präfx-Dekrement Beispiel: int zahl; zahl = 10; Console.WriteLine(zahl++); zahl = 10; Console.WriteLine(++zahl); Mirco De Roni //zahl ist 11, ausgegeben wird 10 //zahl ist 11, ausgegeben wird 11 - 10 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Implizit typisierte lokale Variablen deklarieren Der C#-Compiler kann den Typ eines Ausdrucks, mit dem Sie eine Variable initialisieren, schnell herausfinden und Ihnen sagen, ob er dem Typ der Variablen entspricht. Ausserdem können Sie den C#Compiler anweisen, den Typ einer Variablen von einem Ausdruck herzuleiten und diesen Typ zu verwenden. Dazu deklarieren Sie die Variable mit dem Schüsselwort var anstelle des Typs: var zahl = 10; var name = "Mirco De Roni"; Die Variablen zahl und name werden als implizit typisierte Variablen bezeichnet. Das Schlüsselwort var veranlasst den Compiler, den Typ der Variablen aus den Typen der Ausdrücke, die zu ihrer Initialisierung verwendet werden, herzuleiten. Das Schlüsselwort var lässt sich nur verwenden, wenn Sie einen Ausdruck angeben, um eine Variable zu initialisieren. Die folgende Deklaration ist nicht zulässig und verursacht einen Kompilierfehler: var zahl; Fehler Implizit typisierte lokale Variablen müssen initialisiert werden. Mirco De Roni - 11 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 3 Methoden schreiben und Gültigkeitsbereiche anwenden Methoden deklarieren Eine Methode ist eine benannte Sequenz von Anweisungen. Jede Methode besitzt einen Namen und ein Körper. Der Name der Methode sollte ein aussagekräftiger Bezeichner sein, der auf den allgemeinen Zweck der Methode hinweist. Der Methodenkörper enthält die eigentlichen Anweisungen, die beim Aufrufen der Methode ausgeführt werden. Die meisten Methoden können Daten übernehmen, um sie zu verarbeiten und Informationen zurückzugeben, die in der Regel das Ergebnis der Verarbeitung repräsentieren. Methoden sind ein fundamentaler und leistungsfähiger Mechanismus. Die Syntax zur Deklaration von Methoden Die Syntax einer Microsoft Visual C#-Methode sieht folgendermassen aus: rückgabeTyp methodenName (parameterListe) { //Anweisungen } Der rückgabeTyp ist der Name eines Typs und gibt an, welche Art von Informationen die Methode zurückgibt. Wenn Sie eine Methode schreiben, die keinen Wert zurückgibt, müssen Sie das Schlüsselwort void anstelle eines Rückgabetyps verwenden. Der methodenName ist der Name, unter dem Sie die Methode aufrufen. Für Methodennamen gelten die gleichen Regeln wie für Variablennamen. Die optionale parameterListe beschreibt die Datentypen und Namen der Informationen, die Sie an die Methode übergeben können. Die Anweisungen im Körper der Methode sind die Codezeilen, die beim Aufruf der Methode ausgeführt werden. Wichtig Programmierer, die bisher mit C, C++ oder Microsoft Visual Basic gearbeitet haben, sollten beachten, dass C# keine globalen Methoden unterstützt. Alle Methoden müssen innerhalb einer Klasse erscheinen, andernfalls lässt sich der Code nicht kompilieren. Hinweis Die Typen aller Parameter und den Rückgabetyp einer Methode müssen Sie explizit spezifizieren. Das Schlüsselwort var dürfen Sie hierfür nicht verwenden. Rückkehranweisungen schreiben Wenn eine Methode Informationen zurückgeben soll, müssen Sie innerhalb der Methode eine Rückkehranweisung schreiben. Dazu verwenden Sie das Schlüsselwort return mit einem nachfolgenden Ausdruck, der den Rückgabewert berechnet und schliessen die Anweisung mit einem Semikolon ab. Der Typ des Ausdrucks muss derselbe sein, den Sie für die Methode spezifiziert haben. int calculate(int number1, int number2) { //... return number1 + number2; } Die return-Anweisung sollte am Ende der Methode stehen, da sie die Ausführung der Methode abschliesst. Alle nach der return-Anweisung stehenden Anweisungen werden nicht mehr ausgeführt. Daher erhalten Sie auch eine Warnung vom Compiler, wenn sich hinter der return-Anweisung weitere Anweisungen befinden. Methoden aufrufen Eine Methode rufen Sie nach ihrem Namen auf, um ihre programmierte Aufgabe durchführen zu lassen. Wenn die Methode Informationen benötigt, müssen Sie die angeforderten Informationen bereitstellen. Gibt die Methode Informationen zurück, sollten Sie diese Informationen in geeigneter Weise weiterverarbeiten. Mirco De Roni - 12 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Die Syntax zum Aufruf einer Methode spezifizieren Die Syntax eines C#-Methodenaufrufs sieht folgendermassen aus: ergebnis = methodenName (argumentListe); Der methodenName muss exakt mit dem Namen der Methode übereinstimmen, die Sie aufrufen wollen. Denken Sie daran, dass C# zwischen Gross- und Kleinschreibung unterscheidet. Die Zuweisung ergebnis = ist optional. Wenn sie angegeben ist, nimmt die durch ergebnis dargestellte Variable den Wert auf, den die Methode zurückgibt. Handelt es sich um eine voidMethode, müssen Sie die Zuweisung ergebnis = aus der Anweisung weglassen. Mit der argumentListe geben Sie die optionalen Informationen an, die die Methode übernimmt. Für jeden Parameter müssen Sie ein Argument bereitstellen und der Wert jedes Arguments muss mit dem Typ des betreffenden Parameters übereinstimmen. Wichtig Die runden Klammern sind in jedem Methodenaufruf erforderlich, auch beim Aufruf von Methoden, die keine Argumente übernehmen. Beispiele von Aufrufvarianten, die nicht erfolgreich verlaufen: calculate; calculate(); calculate(10); calculate("1", "Mirco De Roni"); //runde Klammern fehlen //zu wenig Argumente //zu wenig Argumente //falsche Datentypen Gültigkeitsbereiche anwenden Wenn sich eine Variable an einer bestimmten Stelle in einem Programm verwenden lässt, sagt man, dass die Variable in diesem Bereich gültig ist. Anders ausgedrückt ist der Gültigkeitsbereich einer Variablen einfach der Bereich des Programms, in dem eine Variable verwendbar ist. Gültigkeitsbereiche sind sowohl für Methoden als auch für Variablen definiert. Der Gültigkeitsbereich eines Bezeichners ist verknüpft mit der Stelle der Deklaration, die den Bezeichner in das Programm einführt. Lokale Gültigkeitsbereiche definieren Die öffnende und die schliessende geschweifte Klammer, die den Körper einer Methode umfassen, definieren einen Gültigkeitsbereich. Alle Variablen, die Sie innerhalb des Methodenkörpers definieren, sind nur innerhalb der Methode gültig. Diese Variablen bezeichnet man als lokale Variablen. class Person { void declaration() { string firstName = ""; string lastName = ""; int zipCode = 0; string city = ""; } void setValues() { firstName = "Mirco"; lastName = "De Roni"; zipCode = 6014; city = "Luzern"; } } Dieser Code lässt sich nicht kompilieren, weil setValues() versucht die Variablen zu verwenden, die aber nicht im Gültigkeitsbereich von setValues() liegen. Mirco De Roni - 13 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Gültigkeitsbereiche auf Klassenebene definieren Die durch die Klasse definierten Variablen bezeichnet man in C# als Felder. Im Unterschied zu lokalen Variablen ist es mit Feldern möglich, Informationen zwischen Methoden gemeinsam zu nutzen bzw. auszutauschen, wie es folgendes Beispiel zeigt: class Person { string firstName = ""; string lastName = ""; int zipCode = 0; string city = ""; void setValues() { firstName = "Mirco"; lastName = "De Roni"; zipCode = 6014; city = "Luzern"; } } Die Variablen sind innerhalb der Klasse, aber ausserhalb der Methode setValues() definiert. Folglich besitzen die Variablen den Gültigkeitsbereich auf Klassenebene und sind für alle Methoden in der Klasse zugänglich. Methoden überladen Wenn sich zwei Bezeichner mit dem gleichen Namen im selben Gültigkeitsbereich befinden, sagt man, dass sie überladen sind. Wenn Sie beispielsweise zwei lokale Variablen mit dem gleichen Namen in derselben Methode deklarieren, erhalten Sie einen Kompilierfehler angezeigt. Auch wenn Sie zwei Felder mit dem gleichen Namen in derselben Klasse deklarieren oder zwei identische Methoden in derselben Klasse erstellen, erhalten Sie Fehlermeldungen des Compilers. static void Main(string[] args) { Console.WriteLine("Das Ergebnis lautet: "); Console.WriteLine(10); } Überladen ist vor allem nützlich, wenn man die gleiche Operation auf unterschiedlichen Datentypen ausführen muss. Eine Methode können Sie überladen, wenn die einzelnen Implementierungen unterschiedliche Parametersätze haben, d.h. wenn zwar die Namen gleich sind, die Anzahl der Parameter aber unterschiedlich ist oder wenn sich die Typen der Parameter unterscheiden. Mirco De Roni - 14 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 4 Entscheidungsanweisungen verwenden Boolesche Variablen deklarieren Das Ergebnis eines booleschen Ausdrucks ist immer entweder wahr oder falsch. Microsoft Visual C# definiert den Datentyp bool. Eine Variable dieses Typs kann einen von zwei Werten aufnehmen: true (wahr) oder false (falsch). bool areYouReady = true; Console.WriteLine(areYouReady); Boolesche Operatoren verwenden Ein boolescher Operator ist ein Operator, dessen Ergebnis entweder true oder false ist. C# definiert mehrere boolesche Operatoren. Der einfachste boolesche Operator ist der logische Negationsoperator, der durch das Symbol ! (Ausrufezeichen) repräsentiert wird. Gleichheit und relationale Operatoren verstehen Zwei gebräuchliche boolesche Operatoren bezeichnen die Gleichheit (==) und Ungleichheit (!=). Operator Bedeutung Beispiel == != < <= > >= gleich ungleich kleiner als kleiner oder gleich grösser als grösser oder gleich jahr == 2009 jahr!= 0 jahr < 2010 jahr <= 2009 jahr > 2009 jahr >= 2010 Ergebnis, wenn jahr gleich 2010 ist false true false false true true Hinweis Verwechseln Sie den Gleichheitsoperator == nicht mit dem Zuweisungsoperator =. Bedingte logische Operatoren verstehen C# stellt zwei weitere boolesche Operatoren zur Verfügung: das logische AND mit dem Symbol && und das logische OR mit dem Symbol ||. Diese logischen Operatoren kombinieren boolesche Ausdrücke zu grösseren Ausdrücken. Es handelt sich um binäre Operatoren, die den relationalen Operatoren ähnlich sind, da ihre Ergebnisse entweder true oder false sind. Das Ergebnis des &&-Operators ist nur dann true, wenn beide boolesche Operanden true sind. int year = 2010; bool validYear = (year <= 2012) && (year >= 2000); Das Ergebnis des ||-Operators ist true, wenn mindestens einer seiner booleschen Operanden true ist. Mit dem ||-Operator lässt sich bestimmen, ob irgendein Ausdruck einer Kombination von booleschen Ausdrücken true ist. int year = 2010; bool validYear = (year < 2010) || (year >= 2010); Operatorvorrang und Assoziativität im Überblick Operatoren in derselben Kategorie besitzen die gleiche Priorität. Operatoren in einer höheren Kategorie haben Vorrang vor Operatoren in einer niedrigeren Kategorie. Kategorie primär unär Mirco De Roni Operatoren () ++ -! Beschreibung Überschreibt Vorrang Post-Inkrement Post-Dekrement Logisches NOT - 15 - Assoziativität links links Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ multiplikativ additiv relational Gleichheit bedingtes AND bedingtes OR Zuweisung + ++ -* / % + < <= > >= == != && || = Addition Subtraktion Prä-Inkrement Prä-Dekrement Multiplikation Division Modulo Addition Subtraktion Kleiner als Kleiner oder gleich Grösser als Grösser oder gleich Gleich Ungleich Logisches AND Logisches OR links links links links links links rechts Entscheidungen mit if-Anweisungen treffen Mit einer if-Anweisung können Sie abhängig vom Ergebnis eines booleschen Ausdrucks entscheiden, welcher von zwei unterschiedlichen Codeblöcken auszuführen ist. Die Syntax der if-Anweisung verstehen Die Syntax der if-Anweisung sieht wie folgt aus: if (boolescherAusdruck) { //Anweisung1 } else { //Anweisung2 } Wenn boolescherAusdruck den Wert true ergibt, wird Anweisung1 ausgeführt; andernfalls hat boolscherAusdruck den Wert false und das Programm führt Anweisung2 aus. Das Schlüsselwort else und die nachfolgende Anweisung2 sind optional. Fehlt der else-Zweig, passiert nichts, wenn boolscherAusdruck den Wert false ergibt. Anweisungen in Blöcke gruppieren Die oben angegebene Syntax der if-Anweisung spezifiziert eine einzelne Anweisung nach if (boolscherAusdruck) und eine einzelne Anweisung nach dem Schlüsselwort else. Manchmal sind mehrere Anweisungen auszuführen, wenn ein boolescher Ausdruck den Wert true ergibt. Man könnte die Anweisungen in einer neuen Methode zusammenfassen und dann diese Methode aufrufen. Einfacher ist aber, die Anweisungen in einem Block zu gruppieren. Ein Block ist einfach eine Folge von Anweisungen, die in geschweifte Klammern eingeschlossen sind. Zudem beginnt ein Block einen neuen Gültigkeitsbereich. int seconds = 0; int minutes = 0; if (seconds == 59) { seconds = 0; minutes++; } else { seconds++; } Mirco De Roni - 16 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ if-Anweisungen verschachteln Es ist möglich, if-Anweisungen innerhalb anderer if-Anweisungen zu verschachteln. Auf diese Art und Weise können Sie eine Reihe von booleschen Ausdrücken verketten, die nacheinander getestet werden, bis einer das Endergebnis true liefert. int weekday = 6; string dayName = ""; if(weekday == 1) { dayName = "Montag"; } else if (weekday == 2) { dayName = "Dienstag"; } else if (weekday == 3) { dayName = "Mittwoch"; } else if (weekday == 4) { dayName = "Donnerstag"; } else if (weekday == 5) { dayName = "Freitag"; } else if (weekday == 6) { dayName = "Samstag"; } else if (weekday == 7) { dayName = "Sonntag"; } else { dayName = "Unbekannt"; } switch-Anweisungen verwenden Wenn Sie verschachtelte if-Anweisungen programmieren, sind manchmal die einzelnen ifAnweisungen einander recht ähnlich, weil sie alle einen identischen Ausdruck auswerten. Der einzige Unterschied zwischen den if-Anweisungen besteht darin, dass sie das Ergebnis des Ausdrucks mit einem anderen Wert vergleichen. Im oberen Beispiel können Sie die verschachtelte if-Anweisung oftmals als switch-Anweisung formulieren, um das Programm effizienter und verständlicher zu machen. Die Syntax der switch-Anweisung verstehen Die Syntax einer switch-Anweisung hat folgendes Aussehen: switch (kontrollAusdruck) { case Ausdruck: //Anweisungen break; ... default: //Anweisungen break; } Mirco De Roni - 17 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Der kontrollAusdruck wird einmal ausgewertet und die Anweisungen im case-Zweig, dessen Ausdruck mit dem kontrollAusdruck identisch ist, werden bis zur break-Anweisung ausgeführt. Damit ist die Abarbeitung der switch-Anweisung beendet und die Programmausführung setzt mit der ersten Anweisung hinter der schliessenden geschweiften Klammer der switch-Anweisung fort. Wenn kein Wert mit dem Wert von kontrollAusdruck übereinstimmt, werden die Anweisungen nach der optionalen Marke default ausgeführt. Die im letzten Beispiel gezeigte verschachtelte if-Anweisung können Sie mit einer switch-Anweisung wie folgt formulieren: switch (day) { case 1: dayName break; case 2: dayName break; case 3: dayName break; case 4: dayName break; case 5: dayName break; case 6: dayName break; case 7: dayName break; default: dayName break; } = "Montag"; = "Dienstag"; = "Mittwoch"; = "Donnerstag"; = "Freitag"; = "Samstag"; = "Sonntag"; = "Unbekannt"; Regeln für die switch-Anweisung befolgen Auch wenn die switch-Anweisung recht nützlich ist, kann man sie nicht uneingeschränkt verwenden. Bei jeder switch-Anweisung müssen Sie sich an die folgenden Regeln halten: Die switch-Anweisung lässt sich nur mit primitiven Datentypen wie int oder string verwenden. Die case-Marken müssen konstante Ausdrücke wie 10 oder "10" sein. Ist es erforderlich, die Werte der case-Marken zur Laufzeit zu berechnen, müssen Sie mit einer if-Konstruktion arbeiten. Die case-Marken müsssen eindeutige Ausdrücke sein. Es ist möglich, dieselben Anweisungen für mehr als einen Wert auszuführen. Mirco De Roni - 18 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 5 Verbundzuweisungen und Schleifen verwenden Verbundzuweisungsoperatoren verwenden Die folgende Tabelle gibt diese so genannten Verbundzuweisungsoperatoren im Überblick an. Schreiben Sie nicht... variable = variable variable = variable variable = variable variable = variable variable = variable * / % + - zahl; zahl; zahl; zahl; zahl; sondern... variable variable variable variable variable *= /= %= += -= zahl; zahl; zahl; zahl; zahl; Tipp Die Verbundzuweisungsoperatoren besitzen den gleichen Vorrang und die gleiche Orientierung (nach rechts verbunden) wie der einfache Zuweisungsoperator. Schleifen mit while-Anweisungen schreiben Mit einer while-Anweisung kann man eine Anweisung wiederholt ausführen, solange ein boolescher Ausdruck true ist. Die Syntax der while-Anweisung lautet: while (boolescherAusdruck) { //Anweisungen } Die while-Anweisung wertet boolescherAusdruck aus. Ist das Ergebnis true, führt sie die Anweisungen aus und wertet dann erneut boolescherAusdruck aus. Ist der Ausdruck immer noch true, wird die Anweisung wiedertholt ausgeführt und der Ausdruck wieder getestet. Dieser Vorrang setzt sich solange fort, bis der boolesche Ausdruck den Wert false ergibt und damit die while-Schleife beendet ist. Der zu testende Ausdruck muss ein boolescher Ausdruck sein. Der boolesche Ausdruck muss in Klammern eingeschlossen sein. Liefert der boolesche Ausdruck bereits bei der ersten Auswertung false, wird die Anweisung überhaupt nicht ausgeführt. Wenn zwei oder mehr Anweisungen unter der Steuerung einer while-Anweisung laufen sollen, sind diese mit geschweiften Klammern als Block zu gruppieren. Der Code im folgenden Beispiel gibt in einer while-Schleife die Zahlen von 1 bis 10 auf der Konsole aus: int i = 1; while (i <= 10) { Console.WriteLine(i); i++; } Hinweis Die Variable i in der while-Schleife steuert die Anzahl der ausgeführten Iterationen (Schleifendurchläufe). Diese Konstruktion ist häufig anzutreffen und die Variable, die diese Rolle übernimmt, bezeichnet man auch als Sentinel. Schleifen mit for-Anweisungen schreiben Mit der for-Schleife können Sie eine while-Schleife knapper formulieren, indem Sie die Initialisierung, den booleschen Ausdruck und die Aktualisierung der Kontrollvariablen zusammenfassen. Die Syntax der for-Anweisung lautet: for (Initialisierung; boolescherAusdruck; Aktualisierung) { //Anweisungen Mirco De Roni - 19 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ } Die bereits vorgestellte while-Schleife, die die Ganzzahlen von 1 bis 10 anzeigt, lässt sich mit der forSchleife wie folgt formulieren: for (int i = 1; i <= 10; i++) { Console.WriteLine(i); } Die Initialisierung findet nur einmal am Anfang der Schleife statt. Wenn der boolesche Ausdruck den Wert true ergibt, wird die Anweisung ausgeführt. Danach wird die Variable aktualisiert und der boolesche Ausdruck erneut ausgewertet. Ist die Bedingung immer noch true, wird wieder die Anweisung ausgeführt, die Variable aktualisiert, der boolesche Ausdruck ausgewertet usw. Hinweis Die Initialisierung, der boolesche Ausdruck und die Anweisung zur Aktualisierung der Steuervariablen in einer for-Anweisung müssen immer durch Semikolons voneinander getrennt werden, selbst wenn Sie diese Teile weglassen. Den Gültigkeitsbereich von for-Anweisungen verstehen Die Variable, die Sie im Körper der for-Anweisung deklariert haben, ist nur innerhalb der forAnweisung gültig und verschwindet, sobald die for-Schleife beendet ist. Das folgende Beispiel erzeugt einen Kompilierfehler: for (int i = 1; i <= 10; i++) { ... } Console.WriteLine(i); Schleifen mit do-Anweisungen schreiben Die while- und for-Anweisungen testen den booleschen Ausdruck am Beginn der Schleife. Wenn also der Ausdruck bereits beim allerersten Test false liefert, wird der Schleifenkörper überhaupt nicht ausgeführt. Die do-Anweisung verhält sich anders. Sie testet ihren booleschen Ausdruck nach jeder Iteration und führt somit den Schleifenkörper mindestens einmal aus. Die Syntax der do-Anweisung sieht wie folgt aus: do { //Anweisungen } while (boolescher Ausdruck); Der folgende Code stellt eine do-Version des Beispiels dar, Zahlen von 1 bis 10 auf der Konsole auszugeben: int i = 1; do { Console.WriteLine(i); i++; } while (i <= 10); Mirco De Roni - 20 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 6 Fehler und Ausnahmen behandeln Code probieren und Ausnahmen abfangen In C# ist es relativ einfach, den Code, der den Hauptablauf des Programms implementiert, vom Fehlerbehandlungscode zu trennen. Dies geschieht mit Ausnahmen und Ausnahme-Handlern. try { //Anweisungen, die eine Exception auslösen könnten. } catch (Exception ex) { //Wird eine Exception ausgelöst, enthält ex Informationen dazu. } Eine Ausnahme behandeln Der catch-Handler verwendet eine Syntax ähnlich der eines Methodenparameters, um die abzufangende Ausnahme zu spezifizieren. Wenn der Code eine Exception auslöst, wird die Variable ex mit einem Objekt gefüllt, das die Einzelheiten der Ausnahme enthält. Die Message-Eigenschaft enthält eine Textbeschreibung des Fehlers. Auf diese Informationen können Sie zurückgreifen, wenn Sie die Ausnahme behandeln, die Details in einer Protokolldatei aufzeichnen oder dem Benutzer eine aussagekräftige Fehlermeldung liefern. Unbehandelte Ausnahmen Wichtig Nach dem Abfangen einer Ausnahme setzt die Programmausführung in der Methode fort, die den catchBlock enthält, der die Ausnahme abgefangen hat. Ist die Ausnahme in einer anderen Methode als der mit dem catch-Handler aufgetreten, kehrt die Steuerung nicht zu der Methode zurück, die die Ausnahme verursacht hat. Mehrere catch-Handler verwenden Unterschiedliche Fehler erzeugen unterschiedliche Arten von Ausnahmen, um unterschiedliche Arten von Fehlern darzustellen. Um diese Situation zu entsprechen, können Sie mehrere catch-Handler bereitstellen und nacheinander anordnen, wie es das folgende Beispiel zeigt: try { //... } catch (NullReferenceException nEx) { //... } catch (OverflowException oEx) { //... } Mehrere Ausnahmen abfangen Der von C# und Microsoft .NET Framework bereitgestellte Mechanismus zum Abfangen von Ausnahmen ist recht umfassend. Im .NET Framework sind viele unterschiedliche Ausnahmen definiert und alle Programme, die Sie schreiben, können die meisten davon auslösen! Ausnahmen sind in Familien – so genannten Vererbungshierarchien – organisiert. Wenn Sie einen catch-Handler für Exception vorsehen, fängt er jede mögliche Ausnahme ab, die auftreten kann. Hinweis Die Exception-Familie umfasst eine breite Palette von Ausnahmen, von denen viele für die verschiedenen Teile des .NET Framework vorgesehen sind. Auch wenn einige davon recht ausgefallen sind, ist es immerhin nützlich zu wissen, wie man sie abfängt. Das nächste Beispiel zeigt, wie Sie alle möglichen Ausnahmen abfangen: Mirco De Roni - 21 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ try { //... } catch (Exception ex) //Dies ist ein allgemeiner catch-Handler { //... } Ganzzahlarithmetik mit checked und unchecked Tipp Wenn Sie in einem Programm den kleinsten oder grössten Wert von int bestimmen möchten, können Sie die Eigenschaften int.MinValue und int.MaxValue verwenden. Unabhängig davon, wie Sie eine Anwendung kompilieren, können Sie mit den Schlüsselwörtern checked und unchecked die Überlaufprüfungen in Teilen einer Anwendung selektiv ein- bzw. ausschalten. Geprüfte Anweisungen schreiben Eine geprüfte Anweisung ist ein Anweisungsblock, der mit dem Schlüsselwort checked eingeleitet wird. Alle Ganzzahlberechnungen in einer geprüften Anweisung lösen eine OverflowException–Ausnahme aus, wenn eine Ganzzahlberechnung im Block einen Überlauf erzeugt. int maxNumber = int.MaxValue; checked { int willThrow = maxNumber++; Console.WriteLine("Diese Zeile wird nie erreicht."); } Wichtig Geprüft werden nur die Ganzzahlberechnungen, die sich direkt innerhalb des checked-Blocks befinden. Handelt es sich beispielsweise bei einer der geprüften Anweisungen um einen Methodenaufruf, schliesst die Überprüfung die Anweisungen in dieser Methode nicht mit ein. Mit dem Schlüsselwort unchecked können Sie einen ungeprüften Anweisungsblock erzeugen. Alle Ganzzahlberechnungen innerhalb eines ungeprüften Blocks werden dann nicht auf einen Überlauf hin geprüft und lösen auch keine OverflowException aus. int maxNumber = int.MaxValue; unchecked { int wontThrow = maxNumber++; Console.WriteLine("Diese Zeile wird erreicht."); } Geprüfte Ausdrücke schreiben Mit den Schlüsselwörtern checked und unchecked können Sie auch die Überlaufprüfung in Ganzzahlausdrücken steuern. int willThrow = checked(int.MaxValue + 1); int wontThrow = unchecked(int.MaxValue + 1); Wichtig Mit den Schlüsselwörtern checked und unchecked können Sie die Überlaufprüfung von Gleitkommaberechnungen nicht steuern. Die Schlüsselwörter checked und unchecked funktionieren nur bei arithmetischen Operationen mit ganzzahligen Werten. Mirco De Roni - 22 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Ausnahmen auslösen Die Klassenbibliotheken vom .NET Framework enthalten jede Menge Ausnahmeklassen, die für bestimmte Situationen konzipiert sind. Meistens finden Sie eine Klasse, die Ihre Ausnahmebehandlung sinnvoll beschreibt. Für unsere Zwecke ist die .NET Framework-Klasse ArgumentOutOfRangeException passend. Eine Ausnahme lösen Sie mit der throw-Anweisung aus, wie es in folgendem Beispiel zu sehen ist: public static string getMonthName(int month) { switch (month) { case 1: return "Januar"; case 2: return "Februar"; case 3: return "März"; ... default: throw new ArgumentOutOfRangeException("Ungültiger Monat"); } } Die throw-Anweisung benötigt ein Ausnahmeobjekt, das auszulösen ist. Dieses Beispiel erzeugt mit einem Ausdruck ein neues ArgumentOutOfRangeException-Objekt. Das Objekt wird über seinen Konstruktor mit einer Zeichenfolge initialisiert, die seine Message-Eigenschaft füllt. Einen finally-Block verwenden Wenn eine Ausnahme ausgelöst wird, darf man nicht vergessen, dass die Ausnahme den Ablauf durch das Programm verändert. Es ist also nicht garantiert, dass eine Anweisung immer ausgeführt wird, wenn die vorherige Anweisung abgeschlossen ist – denn die vorherige Anweisung könnte eine Ausnahme auslösen. Um sicherzustellen, dass eine Anweisung immer – d.h. unabhängig davon, ob eine Ausnahme aufgerufen ist oder nicht – ausgeführt wird, platziert man sie in einem finally-Block. Ein finally-Block steht immer unmittelbar nach einem try-Block oder direkt im Anschluss an den letzten catch-Handler nach einem try-Block. Der finally-Block wird in jedem Fall ausgeführt. string path = openFileDialog.FileName; FileInfo fileInfo = new FileInfo(path); txtBoxFileName.Text = fileInfo.Name; txtBoxSource.Text = ""; TextReader reader = null; try { reader = fileInfo.OpenText(); string line = reader.ReadLine(); while (line != null) { txtBoxSource.Text += line + "\n"; line = reader.ReadLine(); } } finally { if (reader != null) { reader.Close(); } } Mirco De Roni - 23 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 7 Klassen und Objekte erstellen und verwalten Microsoft .NET Framework enthält sehr viele Klassen. Mit dem komfortablen Mechanismus von Klassen modellieren Sie die Entitäten, die von Anwendungen manipuliert werden. Eine Entität kann ein spezifisches Element (z.B. einen Kunden) oder etwas abstraktes (z.B. eine Transaktion) repräsentieren. Ein grosser Teil im Entwurfsprozess jedes Systems beschäftigt sich damit, die wichtigen Entitäten herauszuarbeiten und dann zu analysieren, welche Informationen sie benötigen und welche Funktionen sie ausführen sollen. Felder speichern die Informationen, die eine Klasse aufnimmt und Methoden implementieren die Operationen, die eine Klasse ausführen kann. Was ist Klassifizierung? Im Wort Klassifizierung steckt das Wort Klasse. Wenn Sie eine Klasse entwerfen, fassen Sie Informationen systematisch in einer sinnvollen Entität zusammen. Es handelt sich dabei um eine Klassifizierung, einen Vorgang, der jedem geläufig ist. Zum Beispiel besitzen alle Autos gemeinsame Verhaltensweisen (lenken, bremsen, beschleunigen usw.) und gemeinsame Attribute (ein Lenkrad, einen Motor usw.). Was ist Kapselung? Kapselung ist ein wichtiges Prinzip, wenn man Klassen definiert. Dahinter steckt die Idee, dass sich ein Programm, das eine Klasse verwendet, nicht darum kümmern sollte, wie die Klasse eigentlich intern arbeitet. Das Programm erzeugt einfach eine Instanz einer Klasse und ruft ihre Methoden auf. Eine Klasse muss gegebenenfalls alle Arten von internen Zustandsinformationen verwalten, um ihre verschiedene Methoden auszuführen. Diese zusätzlichen Zustandsinformationen und Aktivitäten werden mithilfe der Klasse vor dem Programm verborgen. Demzufolge bezeichnet man Kapselung auch als das Verbergen von Informationen. Prinzipiell hat die Kapselung zwei Aufgaben: Methoden und Daten in einer Klasse zusammenfassen Die Zugänglichkeit der Methoden und Daten steuern Eine Klasse definieren und verwenden Wenn Sie eine neue Klasse erstellen wollen, dann machen Sie folgendes: Im Projektmappen-Explorer klicken Sie mit der rechten Maustaste auf die Projektdatei und dann auf Hinzufügen und anschliessend wählen Sie Klasse aus. In C# wird die Klasse mit dem Schlüsselwort class definiert. class Person { //Membervariablen string m_FirstName = ""; string m_LastName = ""; int m_ZipCode = 0; string m_City = ""; //Methoden string getFirstName() { return m_FirstName; } void setFirstName(string firstName) { m_FirstName = firstName; } } Sie erzeugen eine Variable, deren Typ Sie als Person spezifizieren und initialisieren dann die Variable mit gültigen Daten, wie es folgendes Beispiel zeigt: Person p = new Person(); Mirco De Roni //Eine Klasse vom Typ Person instanziieren - 24 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Beachten Sie das Schlüsselwort new. Das Schlüsselwort new erzeugt eine neue Instanz einer Klasse. Unter einem Objekt versteht man die Instanz einer Klasse. Wichtig Bringen Sie die Begriffe Klasse und Objekt nicht durcheinander. Eine Klasse ist die Definition eines Typs. Ein Objekt ist eine Instanz dieses Typs und wird erzeugt, wenn das Programm läuft. Den Zugriff kontrollieren Wenn Sie Ihre Methoden und Daten in einer Klasse kapseln, bildet die Klasse eine Grenze zur äusseren Welt. In der Klasse definierte Felder und Methoden sind anderen Methoden innerhalb der Klasse zugänglich, aber nicht der Umgebung. Sie können die Definition eines Feldes oder einer Methode mit den Schlüsselwörtern public und private modifizieren und damit steuern, ob das jeweilige Element von aussen zugänglich ist. Methoden oder Felder sind privat, wenn sie nur aus der Klasse selbst heraus zugänglich sind. Um eine Methode oder ein Feld als privat zu deklarieren, schreiben Sie das Schlüsselwort private vor die jeweilige Deklaration. Eigentlich ist das die Standardeinstellung, doch gehört es zum Programmierstil, die privaten Methoden und Felder explizit zu kennzeichnen, um Missverständnissen vorzubeugen. Methoden oder Felder sind öffentlich, wenn sie sowohl aus der Klasse selbst heraus als auch von ausserhalb der Klasse zugänglich sind. Um eine Methode oder ein Feld als öffentlich zu deklarieren, schreiben Sie das Schlüsselwort public vor die jeweilige Deklaration. class Person { //Membervariablen private string m_FirstName = ""; private string m_LastName = ""; private int m_ZipCode = 0; private string m_City = ""; //Methoden public string getFirstName() { return m_FirstName; } public void setFirstName(string firstName) { m_FirstName = firstName; } } Person p = new Person(); //Eine Klasse vom Typ Person instanziieren p.setFirstName("Mirco"); string firstName = p.getFirstName(); Tipp Die Felder in einer Klasse werden automatisch je nach ihrem Typ mit 0, false oder null initialisiert. Allerdings empfiehlt es sich, die Initialisierung von Feldern explizit zu unterstützen. Namenskonventionen und Zugänglichkeit Klassennamen sollten mit einem Grossbuchstaben beginnen und die Namen der Konstruktoren müssen exakt mit dem Namen der Klasse übereinstimmen. Mit Konstruktoren arbeiten Ein Konstruktor ist eine spezielle Methode mit dem gleichen Namen wie die Klasse. Der Konstruktor kann Parameter übernehmen, aber keinen Wert zurückgeben. Wenn Sie selbst keinen Konstruktor schreiben, generiert der Compiler automatisch einen Standardkonstruktor. Einen eigenen Standardkonstruktor können Sie ganz leicht schreiben – fügen Sie einfach eine öffentliche Methode mit dem gleichen Namen wie die Klasse hinzu und legen Sie keinen Rückgabewert fest. Mirco De Roni - 25 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Das folgende Beispiel zeigt die Klasse Person mit einem Standardkonstruktor: class Person { //Membervariablen private string m_FirstName = ""; private string m_LastName = ""; private int m_ZipCode = 0; private string m_City = ""; //Standardkonstruktor public Person() { ... } //Methoden public string getFirstName() { return m_FirstName; } public void setFirstName(string firstName) { m_FirstName = firstName; } } Hinweis Im Sprachgebrauch von C# ist der Standardkonstruktor ein Konstruktor, der keine Parameter übernimmt. Es spielt dabei keine Rolle, ob der Compiler ihn erzeugt oder Sie selbst ihn schreiben. Beachten Sie, dass der Konstruktor als public markiert ist. Wenn dieses Schlüsselwort fehlt, ist der Konstruktor privat. Ein privater Konstruktor lässt sich nicht von ausserhalb der Klasse aufrufen. Konstruktoren überladen Sie können unterschiedliche Versionen eines Konstruktors schreiben. Beispielsweise lässt sich die Klasse Person mit einem Konstruktor ergänzen, der den Vorname als Parameter übernimmt: class Person { //Membervariablen private string m_FirstName = ""; private string m_LastName = ""; private int m_ZipCode = 0; private string m_City = ""; //Standardkonstruktor public Person() { ... } //Überladener Konstruktor public Person(string firstName) { setFirstName(firstName); } //Methoden public string getFirstName() { return m_FirstName; } public void setFirstName(string firstName) Mirco De Roni - 26 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ { m_FirstName = firstName; } } Person p = new Person("Mirco"); string firstName = p.getFirstName(); Wenn Sie die Anwendung erstellen, ermittelt der Compiler anhand der Parameter, die Sie mit dem Operator new spezifizieren, welcher Konstruktor aufgerufen werden soll. Partielle Klassen Eine Klasse versammelt eine Reihe von Methoden, Feldern und Konstruktoren sowie andere Elemente. Eine funktionell umfangreiche Klasse kann ziemlich gross werden. Mit C# ist es möglich, den Quellcode für eine Klasse in separate Dateien aufzuteilen. Dieses Feature nutzt Visual Studio 2008 für Windows Presentation Foundation (WPF)-Anwendungen. Wenn Sie eine Klasse auf mehrere Dateien aufteilen, definieren Sie die Teile der Klasse mit dem Schlüsselwort partial in jeder Datei. Person1.cs partial class Person { //Standardkonstruktor public Person() { ... } //Überladener Konstruktor public Person(string firstName) { setFirstName(firstName); } } Person2.cs partial class Person { //Membervariablen private string m_FirstName = ""; private string m_LastName = ""; private int m_ZipCode = 0; private string m_City = ""; //Methoden public string getFirstName() { return m_FirstName; } public void setFirstName(string firstName) { m_FirstName = firstName; } } Statische Methoden und Daten verstehen In C# müssen alle Methoden innerhalb einer Klasse deklariert sein. Wenn Sie aber eine Methode oder ein Feld als static deklarieren, können Sie die Methode aufrufen oder auf das Feld zugreifen, indem Sie den Namen der Klasse verwenden. Es ist keine Instanz erforderlich. Mirco De Roni - 27 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Beispiel: class Circle { private double m_Diameter; public static int m_NumCircles = 0; private const double Pi = 3.1415; //Standardkonstruktor public Circle() { m_Diameter = 0; m_NumCircles++; } //Überladener Konstruktor public Circle(double d) { setDiameter(d); m_NumCircles++; } //Methoden public void setDiameter(double d) { m_Diameter = d; } public static double getArea(double d) { return Math.Pow(d, 2) * Pi / 4; } } Console.WriteLine("Fläche: " + Circle.getArea(5.75)); Wenn Sie eine Methode als static definieren, hat sie keinen Zugriff auf irgendwelche Instanzfelder, die für die Klasse definiert sind, sondern kann nur auf Felder zugreifen, die als static markiert sind. Ein gemeinsam nutzbares Feld erstellen Sie können mit dem Schlüsselwort static auch Felder definieren. Damit lässt sich ein einzelnes Feld erzeugen und zwischen allen Objekten gemeinsam nutzen, die von derselben Klasse erstellt wurden. Beispiel: class Circle { ... public static int m_NumCircles = 0; } Console.WriteLine("Anzahl der Circle-Objekte: " + Circle.m_NumCircles); Tipp Statische Methoden bezeichnet man auch als Klassenmethoden. Dagegen spricht man nicht von Klassenfeldern, sondern bleibt einfach bei der Bezeichnung statische Felder oder statische Variablen. Ein statisches Feld mit dem Schlüsselwort const erstellen Man kann auch ein statisches Feld deklarieren, dessen Wert sich nicht ändern lässt. Hierfür ist das Schlüsselwort const (d.h. konstant) vorgesehen. Bei einem konstanten Feld erscheint zwar nicht das static in der Deklaration, dennoch ist ein konstantes Feld auch statisch. Mirco De Roni - 28 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Beispiel: class Circle { ... private const double Pi = 3.1415; } Statische Klassen Die Sprache C# erlaubt es zudem auch, eine Klasse als static zu deklarieren. Eine statische Klasse kann nur statische Member enthalten. Eine statische Klasse hat einzig den Zweck, als Träger für Hilfsmethoden und Felder zu fungieren und sie kann auch weder Instanzdaten noch Instanzmethoden enthalten. Wenn Sie zum Beispiel Ihre eigene Version der Klasse Math definieren, die nur statische Member enthält, sieht die Klasse wie folgt aus: public static class Math { public static double { ... return a; } public static double { ... return d; } public static double { ... return a; } public static double { ... return d; } ... } Mirco De Roni Sin(double a) Cos(double d) Tan(double a) Sqrt(double d) - 29 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 8 Werte und Verweise verstehen Werttypvariablen und Klassen kopieren Die Typen wie int, float, double und char werden zusammen als Werttypen bezeichnet. Für eine als Werttyp deklarierte Variable generiert der Compiler Code, der einen ausreichend grossen Speicherblock für die Aufnahme des entsprechenden Wertes zuordnet. Bei Klassentypen liegen die Dinge anders. Wenn Sie eine Person-Variable deklarieren, generiert der Compiler keinen Code, der einen ausreichend grossen Speicherblock für einen Person-Typ zuordnet. Er reserviert lediglich einen kleinen Speicherbereich für den Verweis auf einen anderen Speicherblock (d.h. dessen Adresse), der schliesslich ein Person-Objekt enthält. Die Adresse gibt die Position eines Elements im Speicher an. Der Speicher für das Person-Objekt selbst wird nur zugeordnet, wenn ein Objekt mit dem Schlüsselwort new erstellt wird. Eine Klasse ist ein Beispiel für einen Verweistyp. Verweistypen speichern Verweise auf Speicherblöcke. Hinweis Die meisten primitiven Typen der Sprache C# sind Werttypen. Eine Ausnahme ist der Typ string, der einen Verweistyp darstellt. NULL-Werte und Typen, die NULL-Werte zulassen Wenn Sie eine Variable deklarieren, sollten Sie sie immer auch initialisieren. Die Werttypen werden häufig wie folgt deklariert: int i = 0; double d = 0.0; Das folgende Codebeispiel initialisiert die Person-Variable p, weist sie später dann einer anderen Instanz der Klasse Person zu: Person p = new Person("Mirco"); Person copy = null; if (copy == null) { copy = p; } Console.WriteLine("Vorname: " + p.getFirstName()); Console.WriteLine("Vorname: " + copy.getFirstName()); Die if-Anweisung stellt fest, ob die Variable initialisiert ist. In C# können Sie den Wert null jeder Verweisvariablen zuweisen. Der Wert null bedeutet einfach, dass die Variable auf kein Objekt im Speicher verweist. Auf NULL festlegbare Typen verwenden Der Wert null ist nützlich, um Verweistypen zu initialisieren. Allerdings ist null selbst ein Verweis und kann keinem Werttyp zugewiesen werden. Deshalb ist die folgende Anweisung in C# nicht zulässig: int i = null; Allerdings definiert C# ein Modifizierer, mit dem Sie eine Variable als nullfähigen Werttyp deklarieren können. Ein nullfähiger Werttyp verhält sich in ähnlicher Weise wie der ursprüngliche Wert, doch können Sie ihm den Wert null zuweisen. Mit einem Fragezeichen (?) kennzeichnen Sie den Werttyp als nullfähigen Typ: int? i = null; Einen nullfähigen Wert können Sie keiner Variablen eines normalen Werttyps zuweisen. Mirco De Roni - 30 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Die Eigenschaften von auf NULL festlegbaren Typen verstehen Die Eigenschaft HasValue zeigt an, ob ein auf NULL festlegbarer Typ einen Wert enthält oder null ist und über die Eigenschaft Value können Sie den Wert eines auf NULL festlegbaren Typs, der nicht null ist, abrufen: int? i = null; if (i.HasValue) { Console.WriteLine(i.Value); } else { i = 10; } Hinweis Die Value-Eigenschaft eines auf NULL festlegbaren Typs ist schreibgeschützt. Mit dieser Eigenschaft können Sie den Wert einer Variablen lesen, ihn aber nicht ändern. Eine auf NULL festlegbare Variable aktualisieren Sie mit einer gewöhnlichen Zuweisungsanweisung. Parameter mit ref und out übergeben Parameter mit ref deklarieren Wenn Sie einem Parameter das Schlüsselwort ref voranstellen, wird der Parameter ein Alias für das eigentliche Argument und ist nicht mehr eine Kopie des Arguments. Bei einem ref-Parameter wirken sich alle Änderungen, die Sie am Parameter vornehmen, automatisch auch auf das ursprüngliche Argument aus, weil sowohl der Parameter als auch das Argument auf dasselbe Objekt verweisen. static void Main(string[] args) { int arg = 25; DoWork(ref arg); Console.WriteLine(arg); //gibt 26 aus } static void DoWork(ref int param) { param++; } Parameter mit out deklarieren Der Compiler überprüft, ob der Code einem ref-Parameter einen Wert zugewiesen hat, bevor die Methode aufgerufen wird. Allerdings gibt es auch Situationen, in denen man den Parameter in der Methode selbst initialisieren und deshalb ein nicht initialisiertes Argument an die Methode übergeben möchte. Für diesen Zweck ist das Schlüsselwort out vorgesehen. Das Schlüsselwort out hat Ähnlichkeiten mit ref. Einem Parameter können Sie das Schlüsselwort out voranstellen, sodass der Parameter zu einem Alias für das Argument wird. Das Schlüsselwort out steht für output, also Ausgabe. static void Main(string[] args) { int arg; DoWork(out arg); Console.WriteLine(arg); //gibt 25 aus } static void DoWork(out int param) { param = 25; } Mirco De Roni - 31 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Wie der Computerspeicher organisiert ist Computer legen im Speicher die auszuführenden Programme und die von ihnen verwendeten Daten ab. Betriebssysteme und Laufzeitumgebungen gliedern häufig den Speicher für die Aufnahme von Daten in zwei getrennte Bereiche, die unterschiedlich verwaltet werden. Für diese beiden Speicherbereiche haben sich die Bezeichnungen Stack und Heap eingebürgert. Stack und Heap haben zwei grundsätzlich verschiedene Aufgaben: Wenn Sie eine Methode aufrufen, wird der für ihre Parameter und lokalen Variablen benötigte Speicher immer vom Stack angefordert. Endet die Methode, wird der für die Parameter und lokalen Variablen angeforderte Speicher automatisch an den Stack zurückgegeben und ist wieder verfügbar, wenn das Programm eine andere Methode aufruft. Wenn Sie mit dem Schlüsselwort new und einem Konstruktoraufruf ein Objekt erstellen, wird der für das Erstellen des Objekts erforderliche Speicher immer vom Heap angefordert. Hinweis Alle Werttypen werden auf dem Stack und alle Verweistypen (Objekte) auf dem Heap erstellt, obwohl der Verweis selbst auf dem Stack untergebracht wird. Auf NULL festlegbare Typen sind Verweistypen und werden somit auf dem Heap angelegt. Die Klasse System.Object Zu den wichtigsten Verweistypen in Microsoft .NET Framework gehört die Klasse object im Namespace System. Person p = new Person("Mirco"); object o = p; Daten sicher konvertieren Mit zwei recht nützlichen Operatoren hilft Ihnen C#, die Typumwandlung eleganter zu realisieren. Da sind die Operatoren is und as. Der Operator is Mit dem Operator is können Sie überprüfen, ob der Typ eines Objekts dem erwarteten Typ entspricht: Person p = new Person("Mirco"); object o = p; if (o is Person) { Person temp = (Person)o; } Der Operator as Der Operator as übernimmt eine ähnliche Rolle wie is, aber in einer etwas gekürzten Form. Den asOperator verwenden Sie wie folgt: Person p = new Person("Mirco"); object o = p; Person temp = o as Person; if (temp != null) { ... //Typumwandlung war erfolgreich } Mirco De Roni - 32 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 9 Werttypen mit Enumerationen und Strukturen erstellen Mit Enumerationen arbeiten Mit dem Schlüsselwort enum lässt sich ein Enumerationstyp (d.h. ein Aufzählungstyp) erstellen, dessen Werte auf eine Menge symbolischer Namen beschränkt sind. Einen Enumerationstyp deklarieren Einen Enumerationstyp definieren Sie mit dem Schlüsselwort enum. Der folgende Beispielcode zeigt, wie Sie einen Enumerationstyp für Jahreszeiten erstellen, dessen literale Werte auf die symbolischen Namen Spring, Summer, Autumn und Winter beschränkt sind: enum Season { Spring, Summer, Autumn, Winter } Eine Enumeration verwenden Nachdem Sie einen Enumerationstyp deklariert haben, können Sie ihn genau wie jeden anderen Typ verwenden. enum Season { Spring, Summer, Autumn, Winter } Season colorful = Season.Autumn; Console.WriteLine(colorful); Literalwerte für eine Enumeration wählen Jedem Element einer Enumeration ist ein bestimmet Zahlenwert zugeordnet. Standardmässig beginnt die Nummerierung bei 0 für das erste Element und wird für das jeweils folgende um 1 erhöht. Es ist auch möglich, den zugrunde liegenden Ganzzahlwert einer Enumerationsvariablen abzurufen. Dazu müssen Sie ihren zugrunde liegenden Typ umwandeln. enum Season { Spring, Summer, Autumn, Winter } Season colorful = Season.Autumn; Console.WriteLine((int)colorful); //gibt '2' aus Mit Strukturen arbeiten Eine Struktur kann ihre eigenen Felder, Methoden und Konstruktoren genau wie eine Klasse besitzen. Eine Struktur deklarieren Einen eigenen Strukturtyp deklarieren Sie mit dem Schlüsselwort struct, an den sich der Name des Typs und in einem Paar geschweifter Klammern der Körper der Struktur anschliessen. Das folgende Beispiel zeigt eine Struktur Time mit drei öffentlichen Feldern: struct Time { public int hours, minutes, seconds; } Wie bei Klassen ist es allerdings in den meisten Fällen nicht zu empfehlen, die Felder einer Struktur öffentlich zu deklarieren. Es gibt keine Möglichkeit zu garantieren, dass die öffentlichen Felder gültige Werte enthalten. Zum Beispiel könnte jedermann den Wert von minutes oder seconds auf einen Wert grösser 60 setzen. Besser ist es also, die Felder als private zu deklarieren und die Struktur mit Konstruktoren und Methoden zu versehen, um diese Felder zu initialisieren und zu ändern, wie es das folgende Beispiel zeigt: struct Time { private int hours, minutes, seconds; public Time(int h, int m, int s) { hours = h % 24; minutes = m % 60; Mirco De Roni - 33 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ seconds = s % 60; } public int { return } public int { return } public int { return } getHours() hours; getMinutes() minutes; getSeconds() seconds; } Time t = new Time(2, 30, 15); Console.WriteLine("Stunden: " + t.getHours()); Console.WriteLine("Minuten: " + t.getMinutes()); Console.WriteLine("Sekunden: " + t.getSeconds()); Mit Strukturen implementiert man vor allem einfache Konzepte, deren Hauptmerkmal ihr Wert ist. Unterschiede zwischen Strukturen und Klassen Auch wenn sich Strukturen und Klassen syntaktisch sehr ähnlich sind, gibt es einige wichtige Unterschiede. Für eine Struktur können Sie keinen Standardkonstruktor deklarieren. In einer Klasse können Sie die Instanzfelder auch dort initialisieren, wo sie deklariert werden. Das ist in einer Struktur nicht möglich. Die folgende Tabelle fasst die Unterschiede zwischen Strukturen und Klassen noch einmal zusammen. Frage Handelt es sich um einen Werttyp oder einen Verweistyp? Werden Instanzen auf dem Stack oder auf dem Heap angelegt? Struktur Eine Struktur ist ein Werttyp. Klasse Eine Klasse ist ein Verweistyp. Strukturinstanzen heissen Werte und existieren auf dem Stack. Kann man einen Standardkonstruktor deklarieren? Generiert der Compiler einen Standardkonstruktor, wenn man einen eigenen Konstruktor deklariert? Initialisiert der Compiler automatisch ein Feld, das man im eigenen Konstruktor nicht initialisiert hat? Ist es erlaubt, Instanzfelder an der Stelle ihrer Deklaration zu initialisieren? Nein Klasseninstanzen heissen Objekte und existieren auf dem Heap. Ja Ja Nein Nein Ja Nein Ja Mirco De Roni - 34 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 10 Arrays und Auflistungen verwenden Was ist ein Array? Ein Array (Datenfeld) ist eine ungeordnete Sequenz von Elementen. Alle Elemente eines Arrays besitzen den gleichen Typ. Die Elemente eines Arrays sind in einem zusammenhängenden Speicherblock abgelegt und man greift auf die Elemente über einen ganzzahligen Index zu. Arrayvariablen deklarieren Um eine Arrayvariable zu deklarieren, schreiben Sie den Namen des Elementtyps, ein Paar eckige Klammern und den Variablennamen. Die eckigen Klammern signalisieren, dass die Variable ein Array ist. int[] personalIDs; //Persönliche Identifikationsnummern Hinweis Microsoft Visual Basic-Programmierer sollten darauf achten, hier eckige Klammern und keine runden Klammern zu schreiben. Programmierer in C und C++ werden bemerken, dass die Grösse des Arrays nicht nur Deklaration gehört. Tipp Es ist zweckmässig den Plural für Arraynamen zu verwenden. Arrayinstanzen erzeugen Arrays sind Verweistypen – unabhängig vom Typ ihrer Elemente. Eine Arrayvariable verweist also auf eine Arrayinstanz auf dem Heap und speichert ihre Arrayelemente nicht direkt auf dem Stack. Wenn Sie eine Klassenvariable deklarieren, wird für das Objekt erst dann Speicher zugewiesen, wenn Sie die Instanz mit new erzeugen. Bei Arrays ist es genauso: Die Grösse eines Arrays geben Sie noch nicht an, wenn Sie die Arrayvariable deklarieren, sondern erst, wenn Sie die Arrayinstanz tatsächlich erstellen. Um eine Arrayinstanz zu erzeugen, schreiben Sie das Schlüsselwort new, den Namen des Elementtyps und in eckigen Klammern die Grösse des zu erzeugenden Arrays. personalIDs = new int[5]; Um beispielsweise ein zweidimensionales Array anzulegen, erstellen Sie ein Array, das zwei ganzzahlige Indizes verlangt. int[,] table = new int[2, 4]; Arrayvariablen initialisieren int[] personalIDs = new int[5] { 2, 4, 6, 8, 10 }; Ein implizit typisiertes Array erstellen var names = new[] { "Mirco", "Jessica", "Victoria", "Francesco" }; Auf einzelne Elemente des Arrays zugreifen Um auf ein einzelnes Arrayelement zuzugreifen, geben Sie einen Index an, der das gewünschte Element bezeichnet. Arrayindizes sind nullbasiert. Das erste Element eines Arrays befindet sich am Index 0. Console.WriteLine("Name: " + names[0]); Ein Array durchlaufen Für Arrays sind mehrere nützliche Eigenschaften und Methoden vordefiniert. Die Eigenschaft Length gibt darüber Auskunft, wie viele Elemente ein Array enthält. Die Eigenschaft Length können Sie auch nutzen, um die Elemente eines Arrays in einer for-Anweisung zu durchlaufen. Der folgende Beispielcode gibt alle Werte des Arrays names auf der Konsole aus: var names = new[] { "Mirco", "Jessica", "Victoria", "Francesco" }; Mirco De Roni - 35 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ for (int index = 0; index < names.Length; index++) { Console.WriteLine("Name: " + names[index]); } Das folgende Codefragment hat die gleiche Funktonalität wie das vorherige Beispiel, nur verwendet es foreach statt for: var names = new[] { "Mirco", "Jessica", "Victoria", "Francesco" }; foreach (string aName in names) { Console.WriteLine("Name: " + aName.ToString()); } Die foreach-Anweisung durchläuft immer das gesamte Array. Mit einer for-Schleife ist es dagegen problemlos möglich, nur einen bestimmten Teil des Arrays zu bearbeiten oder bestimmte Elemente zu überspringen. Die foreach-Anweisung durchläuft das Array immer vom Index 0 bis zum Index Length – 1. Mit einer for-Schleife können Sie das Array auch von hinten nach vorn durchlaufen. Wenn Sie innerhalb der Schleife den Index des Elements und nicht nur seinen Wert benötigen, sind Sie auf eine for-Schleife angewiesen. Eine for-Schleife ist auch erforderlich, wenn Sie die Elemente des Arrays modifizieren möchten, da es sich bei der Iterationsvariablen der foreach-Anweisung um eine schreibgeschützte Kopie der einzelnen Arrayelemente handelt. Die Iterationsvariable können Sie als var deklarieren und es dem C#-Compiler überlassen, den Typ der Variablen aus dem Typ der Elemente im Array herzuleiten. Dies ist vor allem nützlich, wenn Sie den Typ der Elemente im Array nicht kennen. Was sind Auflistungsklassen? Arrays sind nützlich, haben aber auch ihre Grenzen. Allerdings stellen Arrays nur eine Möglichkeit dar, Elemente des gleichen Typs zu gruppieren. Das Microsoft .NET Framework bietet mehrere Klassen, die ebenfalls Elemente in andere spezialisierten Formen sammeln. Es handelt sich um Auflistungsklassen (Collections), die im Namespace System.Collections existieren. Die grundlegenden Auflistungsklassen übernehmen, speichern und liefern ihre Elemente als Objekte. Der Elementtyp einer Auflistungsklasse ist also object. Die Auflistungsklasse ArrayList Die Klasse ArrayList ist nützlich, wenn Sie mit Arrayelementen hantieren müssen. In bestimmten Situationen können normale Arrays zu restriktiv sein: Wenn Sie die Grösse eines Arrays verändern wollen, müssen Sie ein neues Array erzeugen, die Elemente kopieren und dann alle Arrayverweise auf das ursprüngliche Array aktualisieren, damit diese auf das neue Array zeigen. Wenn Sie ein Element aus dem Array entfernen wollen, müssen Sie alle nachfolgenden Elemente um einen Platz nach vorn verschieben. Wenn Sie in ein Array ein Element einfügen wollen, müssen Sie alle Elemente ab der gewünschten Einfügeposition um eine Position nach hinten verschieben, um einen freien Platz zu schaffen. Allerdings verlieren Sie dabei das letzte Element des Arrays! Die Klasse ArrayList beseitigt diese Einschränkungen: Mit der Methode Remove lässt sich ein Element aus einer ArrayList entfernen. Die ArrayList ordnet ihre Elemente automatisch neu an. Die Methode Add der ArrayList fügt ein Element am Ende der Auflistung an. Die ArrayList passt ihre Grösse bei Bedarf automatisch an. Mit der Methode Insert fügen Sie ein Element in der Mitte einer ArrayList ein. Auch in diesem Fall passt die ArrayList bei Bedarf ihre Grösse automatisch an. Mirco De Roni - 36 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Auf ein vorhandenes Element in einem ArrayList-Objekt können Sie mit der normalen Arraynotation verweisen, d.h. den Index des Elements in eckigen Klammern angeben. Das folgende Beispiel zeigt, wie Sie eine ArrayList erstellen, manipulieren und den Inhalt durchlaufen: using System; using System.Collections; ArrayList numbers = new ArrayList(); //Die ArrayList füllen foreach (int nr in new int[10] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 }) { numbers.Add(nr); } //Ein Element an die vorletzte Position einfügen numbers.Insert(numbers.Count - 1, 40); //Entferne das Element, dessen Wert 8 beträgt numbers.Remove(8); //Entferne das Element an der Position 4 numbers.RemoveAt(4); //Durchlaufe die Elemente mit einer for-Schleife for (int i = 0; i < numbers.Count; i++) { Console.WriteLine(numbers[i]); } //Durchlaufe die Elemente mit einer foreach-Anweisung foreach (int aNr in numbers) { Console.WriteLine(aNr); } Der Code liefert folgende Ausgabe: 2 4 6 10 14 16 18 40 20 Die Auflistungsklasse Queue Die Klasse Queue (Warteschlange) implementiert einen FIFO-Mechanismus (First In, First Out). Ein Element wird von hinten in die Warteschlange eingefügt (Enqueue-Operation) und von vorn aus der Warteschlange entnommen (Dequeue-Operation). Der folgende Code realisiert eine Warteschlange und ihre Operationen: using System; using System.Collections; Queue numbers = new Queue(); //Die Warteschlange füllen foreach (int nr in new int[4] { 2, 4, 6, 8}) { Mirco De Roni - 37 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ numbers.Enqueue(nr); Console.WriteLine(nr + " wurde in die Warteschlange aufgenommen"); } //Die Warteschlange durchlaufen foreach (int aNr in numbers) { Console.WriteLine(aNr); } //Die Warteschlange leeren while (numbers.Count > 0) { Console.WriteLine(numbers.Dequeue() + " hat die Warteschlange verlassen"); } Dieser Code liefert folgende Ausgabe: 2 wurde in die Warteschlange aufgenommen 4 wurde in die Warteschlange aufgenommen 6 wurde in die Warteschlange aufgenommen 8 wurde in die Warteschlange aufgenommen 2 4 6 8 2 hat die Warteschlange verlassen 4 hat die Warteschlange verlassen 6 hat die Warteschlange verlassen 8 hat die Warteschlange verlassen Die Auflistungsklasse Stack Die Klasse Stack (Stapel) implementiert einen LIFO-Mechanismus (Last In, First Out). Um ein Element in den Stack einzufügen, wird es auf die Spitze des Stapels gelegt (Push-Operation) und um es aus dem Stack zu entfernen, wird es von der Spitze des Stapels genommen (Pop-Operation). using System; using System.Collections; Stack numbers = new Stack(); //Den Stack füllen foreach (int nr in new int[4] { 2, 4, 6, 8 }) { numbers.Push(nr); Console.WriteLine(nr + " wurde auf den Stack gelegt"); } //Den Stack durchlaufen foreach (int aNr in numbers) { Console.WriteLine(aNr); } //Den Stack leeren while (numbers.Count > 0) { Console.WriteLine(numbers.Pop() + " wurde vom Stack genommen"); } Mirco De Roni - 38 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Dieses Programm erzeugt folgende Ausgabe: 2 wurde auf den Stack gelegt 4 wurde auf den Stack gelegt 6 wurde auf den Stack gelegt 8 wurde auf den Stack gelegt 8 6 4 2 8 wurde vom Stack genommen 6 wurde vom Stack genommen 4 wurde vom Stack genommen 2 wurde vom Stack genommen Die Auflistungsklasse Hashtable Die Hashtable verwaltet intern zwei object-Arrays – eines für die Schlüssel, von denen Sie die Zuordnung herstellen und eines für die Werte, auf die Sie abbilden. Wenn Sie ein Schlüssel-Wert-Paar in eine Hashtable einfügen, zeichnet sie automatisch auf, welcher Schlüssel zu welchem Wert gehört. Aus dem Konzept der Hashtable-Klasse ergeben sich einige wichtige Konsequenzen: Eine Hashtable kann keine doppelten Schlüssel enthalten. Wenn Sie mit der Methode Add einen Schlüssel hinzufügen möchten, der bereits im Schlüsselarray vorhanden ist, wird eine Ausnahme ausgelöst. Intern ist eine Hashtable eine Datenstruktur mit geringer Datendichte, die am besten funktioniert, wenn ihr genügend Speicher zur Verfügung steht. Die Grösse einer Hashtable im Speicher kann schnell zunehmen, wenn Sie weitere Elemente einfügen. Wenn Sie eine Hashtable mit einer foreach-Anweisung durchlaufen, erhalten Sie einen DictionaryEntry zurück. Die Klasse DictionaryEntry bietet den Zugriff auf die Schlüssel- und Wertelemente in beiden Arrays über die Key- und die Value-Eigenschaften. using System; using System.Collections; Hashtable ages = new Hashtable(); //Die Hashtable füllen ages.Add("Mirco", 18); ages.Add("Jessica", 20); ages.Add("Victoria", 26); ages.Add("Francesco", 34); //Die Hashtable durchlaufen foreach (DictionaryEntry anElement in ages) { string name = (string)anElement.Key; int age = (int)anElement.Value; Console.WriteLine("Name: " + name + ", Alter: " + age); } Die Ausgabe dieses Programms lautet: Name: Mirco, Alter: 18 Name: Jessica, Alter: 20 Name: Victoria, Alter: 26 Name: Francesco, Alter: 34 Mirco De Roni - 39 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Die Klasse SortedList Die Klasse SortedList ist der Klasse Hashtable dahingehend ähnlich, dass sie die Zuordnung von Schlüsseln zu Werten erlaubt. Der wesentliche Unterschied besteht darin, dass das Schlüsselarray immer sortiert ist. Wie die Klasse Hashtable kann eine SortedList keine doppelten Schlüssel enthalten. Wenn Sie eine SortedList mit einer foreach-Anweisung durchlaufen, erhalten Sie einen DictionaryEntry zurück. Allerdings werden die DictionaryEntry-Objekte sortiert nach der Key-Eigenschaft zurückgegeben. using System; using System.Collections; SortedList ages = new SortedList(); //Die SortedList füllen ages.Add("Mirco", 18); ages.Add("Jessica", 20); ages.Add("Victoria", 26); ages.Add("Francesco", 34); //Die SortedList durchlaufen foreach (DictionaryEntry anElement in ages) { string name = (string)anElement.Key; int age = (int)anElement.Value; Console.WriteLine("Name: " + name + ", Alter: " + age); } Die Ausgabe dieses Programms ist alphabetisch nach dem Namen sortiert: Name: Francesco, Alter: 34 Name: Jessica, Alter: 20 Name: Mirco, Alter: 18 Name: Victoria, Alter: 26 Arrays und Auflistungen im Vergleich Die folgende Übersicht fasst die wichtigsten Unterschiede zwischen Arrays und Auflistungen zusammen: Bei einem Array deklarieren Sie den Typ der Elemente, die das Array speichert. Bei einer Auflistung geben Sie keinen Typ an, weil sie ihre Elemente als Objekte speichert. Eine Arrayinstanz besitzt eine feste Grösse und kann weder wachsen noch schrumpfen. Eine Auflistung passt ihre Grösse bei Bedarf dynamisch an. Ein Array kann mehrere Dimensionen umfassen. Eine Auflistung ist linear. Mirco De Roni - 40 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 11 Parameterarrays verstehen Arrayargumente verwenden Um beispielsweise die kleinste Zahl unter mehreren int-Werten zu ermitteln, können Sie eine statische Methode Min schreiben, die als einzelnen Parameter ein Array von int-Werten übernimmt: class Util { public static int Min(params int[] paramList) { if (paramList == null || paramList.Length == 0) { throw new ArgumentException("Util.Min: Nicht genügend Argumente"); } int currentMin = paramList[0]; foreach (int i in paramList) { if (i < currentMin) { currentMin = i; } } return currentMin; } } Console.WriteLine(Util.Min(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)); Das Schlüsselwort params dient als Modifizierer für Arrayparameter. Es bedeutet für die Methode Min, dass man sie mit einer beliebigen Anzahl von Parametern aufrufen kann. Mirco De Roni - 41 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 12 Vererbung richtig einsetzen Was ist Vererbung? Vererbung im Bereich der Programmierung meint in erster Linie Klassifizierung – Beziehungen zwischen Klassen. Vererbung einsetzen Basisklassen und abgeleitete Klassen Eine Klasse, die von einer anderen Klasse erbt, wird mit folgender Syntax deklariert: class AbgeleiteteKlasse : BasisKlasse { ... } Die abgeleitete Klasse erbt von der Basisklasse und die Methoden der Basisklasse werden ebenfalls Teil der abgeleiteten Klasse. In C# darf eine Klasse von höchstens einer anderen Klasse erben, d.h. eine Klasse darf nicht von zwei oder mehreren anderen Klassen abgeleitet werden. Wichtig Vererbung lässt sich nicht mit Strukturen einsetzen. Eine Struktur kann weder von einer Klasse noch von einer anderen Struktur erben. class Vehicle { public void StartEngine(string noiseToMakeWhenStarting) { Console.WriteLine("Motor starten: {0}", noiseToMakeWhenStarting); } public void StopEngine(string noiseToMakeWhenStopping) { Console.WriteLine("Motor anhalten: {0}", noiseToMakeWhenStopping); } public virtual void Drive() { Console.WriteLine("Standardimplementierung der Methode Drive"); } } class Car : Vehicle { public void Accelerate() { Console.WriteLine("Beschleunigen"); } public void Brake() { Console.WriteLine("Bremsen"); } public override void Drive() { Console.WriteLine("Autofahren"); } } Mirco De Roni - 42 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Die Konstruktoren der Basisklasse aufrufen Eine abgeleitete Klasse erhält automatisch alle Felder von der Basisklasse. Diese Felder sind normalerweise zu initialisieren, wenn ein Objekt erstellt wird. In der Regel führt man eine derartige Initialisierung in einem Konstruktor durch. Es gehört zum guten Programmierstil, in einem Konstruktor der abgeleiteten Klasse als Teil der Initialisierung den Konstruktor für ihre Basisklasse aufzurufen. Mit dem Schlüsselwort base rufen Sie einen Basisklassenkonstruktor auf, wenn Sie den Konstruktor für eine erbende Klasse definieren. class Vehicle //Basisklasse { public Vehicle() //Konstruktor für Basisklasse { ... } } ... class Car : Vehicle { public Car() : base() { ... } ... } //abgeleitete Klasse Klassen zuweisen Console.WriteLine("\nAutofahrt"); Car car = new Car(); car.StartEngine("Brumm brumm"); car.Accelerate(); car.Drive(); car.Brake(); car.StopEngine("Puff puff"); Console.WriteLine("\nPolymorphie testen"); Vehicle v = car; v.Drive(); Überschreibungsmethoden Wenn in einer Basisklasse eine Methode als virtual deklariert ist, kann eine abgeleitete Klasse mit dem Schlüsselwort override eine andere Implementierung dieser Methode deklarieren. Wenn Sie mithilfe der Schlüsselwörter virtual und override polymorphe Methoden deklarieren, müssen Sie ein paar Regeln beachten: Eine private Methode dürfen Sie nicht mit den Schlüsselwörtern virtual oder override deklarieren. Andernfalls erhalten Sie eine Fehlermeldung des Compilers. Die beiden Methoden müssen identisch sein, d.h. sie müssen den gleichen Namen, die gleichen Parametertypen und den gleichen Rückgabetyp besitzen. Die beiden Methoden müssen den gleichen Zugriffsmodifizierer haben. Es lassen sich nur virtuelle Methoden überschreiben. Ist die Methode in der abgeleiteten Klasse nicht mit dem Schlüsselwort override deklariert, überschreibt sie auch nicht die Methode. Eine mit dem Schlüsselwort override deklarierte Methode ist implizit virtuell und kann selbst in einer abgeleiteten Klasse überschrieben werden. Geschützter Zugriff Die Schlüsselwörter private und public sind die beiden Zugriffsmodifiziere: Auf öffentliche Felder und Methoden einer Klasse kann jeder zugreifen, während private Felder und Methoden einer Klasse nur für die Klasse selbst zugänglich sind. Mirco De Roni - 43 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Vererbung ist ein leistungsfähiger Mechanismus, um Klassen zu verbinden und zwischen einer abgeleiteten Klasse und ihrer Basisklasse besteht zweifellos eine sehr spezielle enge Beziehung. Häufig ist es nützlich, wenn abgeleitete Klassen auf bestimmte Member der Basisklasse zugreifen dürfen, während dieselben Member gegenüber Klassen, die nicht zur Hierarchie gehören, ausgeblendet werden. In dieser Situation können Sie Member mit dem Schlüsselwort protected (geschützt) markieren. Ist eine Klasse A von einer anderen Klasse B abgeleitet, kann sie auf die geschützten Member der Klasse B zugreifen. Mit anderen Worten ist ein geschützter Member der Klasse B innerhalb der abgeleiteten Klasse A praktisch öffentlich. Ist eine Klasse A nicht von einer anderen Klasse B abgeleitet, kann sie auf einen geschützten Member der Klasse B nicht zugreifen. Mit anderen Worten ist innerhalb der Klasse A ein geschützter Member der Klasse B praktisch privat. Öffentliche Felder verletzen das Prinzip der Kapselung, da alle Benutzer der Klasse direkten und uneingeschränkten Zugriff auf die Felder haben. Hinweis Auf einen geschützten Member einer Basisklasse können Sie nicht nur in einer abgeleiteten Klasse zugreifen, sondern auch in Klassen, die von der abgeleiteten Klasse abgeleitet sind. Ein geschützter Member der Basisklasse bewahrt seine geschützte Zugriffseigenschaft in einer abgeleiteten Klasse und ist auch in davon abgeleiteten Klassen zugänglich. Mirco De Roni - 44 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 13 Schnittstellen erstellen und abstrakte Klassen deklarieren Schnittstellen verstehen Wenn eine Klasse eine Schnittstelle implementiert, garantiert die Schnittstelle, dass die Klasse alle in der Schnittstelle spezifizierten Methoden enthält. Dieser Mechanismus stellt sicher, dass man die Methode auf allen Objekten in der Auflistung aufrufen und die Objekte sortieren kann. Die Schnittstelle sagt Ihnen, was der Name, der Rückgabetyp und die Parameter der Methode sind. Dagegen hat die Schnittstelle nichts damit zu tun, wie die Methode konkret implementiert ist. Die Schnittstelle gibt darüber Auskunft, wie Sie ein Objekt verwenden möchten und nicht, wie die Verwendung überhaupt implementiert ist. Schnittstellensyntax Eine Schnittstelle deklarieren Sie mit dem Schlüsselwort interface anstelle von class oder struct. Innerhalb der Schnittstelle deklarieren Sie Methoden genauso wie in einer Klasse oder Struktur, ausser dass Sie den Methodenkörper durch ein Semikolon ersetzen und niemals einen Zugriffsmodifizierer (public, private oder protected) angeben. Der folgende Beispielcode zeigt eine Schnittstellendeklaration: interface IDescription { void setDescription(string s); } Tipp Die Dokumentation zu Microsoft .NET Framework empfiehlt Schnittstellennamen den Grossbuchstaben I als Präfix voranzustellen, um deutlich auf eine Schnittstelle (Interface) hinzuweisen. Schnittstelleneinschränkungen Als grundlegendes Konzept müssen Sie sich merken, dass eine Schnittstelle niemals irgendwelche Implementierungen enthält. Daraus ergeben sich folgende Einschränkungen: In einer Schnittstelle sind keinerlei Felder erlaubt, nicht einmal statische. Ein Feld ist ein Implementierungsdetail einer Klasse oder Struktur. Für eine Schnittstelle dürfen Sie keinerlei Konstruktoren definieren. Ein Konstruktor gilt ebenfalls als Implementierungsdetail einer Klasse oder Struktur. In einer Schnittstelle dürfen Sie keinen Destruktor definieren. Ein Destruktor enthält die Anweisungen, mit denen eine Objektinstanz abgebaut wird. Es sind keine Zugriffsmodifizierer erlaubt. Alle Methoden in einer Schnittstelle sind implizit öffentlich. In einer Schnittstelle dürfen Sie keine Typen (Enumerationen, Strukturen, Klassen oder Schnittstellen) verschachteln. Sie dürfen keine Schnittstelle von einer Struktur oder Klasse ableiten. Allerdings kann eine Schnittstelle von einer anderen Schnittstelle erben. Eine Schnittstelle implementieren Um eine Schnittstelle zu implementieren, deklarieren Sie eine Klasse oder eine Struktur, die von der Schnittstelle abgeleitet ist und die alle Methoden der Schnittstelle implementiert. interface INamed { string getDescription(); void setDescription(string s); } public class Person : INamed { //Membervariablen private string m_FirstName; Mirco De Roni - 45 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ private string m_LastName; private string m_Fullname; //Standardkonstruktor public Person() { m_FirstName = ""; m_LastName = ""; m_Fullname = ""; } //Überladener Konstruktor public Person(string firstName, string lastName) { m_FirstName = firstName; m_LastName = lastName; m_Fullname = m_FirstName + " " + m_LastName; } //Methoden string INamed.getDescription() { return m_Fullname; } public void setDescription(string fullName) { m_Fullname = fullName; } } class Program { static void Main(string[] args) { Person p1 = new Person("Mirco", "De Roni"); Person p2 = new Person(); p2.setDescription("Victoria Muster"); INamed n1 = new Person("Jessica", "Beispiel"); Ouput(p1); Ouput(p2); Ouput(n1); Console.ReadLine(); } static void Ouput(INamed n) { Console.WriteLine(n.getDescription()); } } Wenn Sie ein Schnittstelle implementieren, müssen Sie sicherstellen, dass jede Methode genau mit der entsprechenden Schnittstellenmethode übereinstimmt, wobei die folgenden Richtlinien zu beachten sind: Die Namen und Rückgabetypen der Methoden müssen genau übereinstimmen. Alle Parameter müssen exakt übereinstimmen. Vor den Methodennamen ist der Name der Schnittstelle zu setzen. Man bezeichnet dies als explizite Schnittstellenimplementierung. Alle Methoden, die eine Schnittstelle implementieren, müssen öffentlich zugänglich sein. Wenn Sie jedoch explizite Schnittstellenimplementierungen verwenden, sollten die Methoden keinen Zugriffsmodifizierer besitzen. Mirco De Roni - 46 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Besteht zwischen Schnittstellendefinition und ihrer deklarierten Implementierung irgendein Unterschied, lässt sich die Klasse nicht kompilieren. Vorteile expliziter Schnittstellenimplementierungen Mehrere Schnittstellen können durchaus Methoden mit identischen Namen, Rückgabetypen und Parametern enthalten. Implementiert eine Klasse mehrere Schnittstellen mit Methoden, die gemeinsame Signaturen besitzen, können Sie mit expliziter Schnittstellenimplementierung Mehrdeutigkeiten bei den Methodenimplementierungen beseitigen. Explizite Schnittstellenimplementierung gibt an, welche Methoden in einer Klasse zu welcher Schnittstelle gehören. Darüber hinaus sind die Methoden für jede Schnittstelle öffentlich zugänglich, jedoch nur über die Schnittstelle selbst. Auf eine Klasse über ihre Schnittstelle verweisen Sie können auf ein Objekt mithilfe einer Variablen verweisen, die als weiter oben in der Hierarchie definiert ist. INamed n1 = new Person("Jessica", "Beispiel"); Mit diesem Verfahren lassen sich Methoden definieren, die unterschiedliche Typen als Parameter übernehmen können, sofern die Typen eine bestimmte Schnittstelle implementieren. static void Ouput(INamed n) { Console.WriteLine(n.getDescription()); } Wenn Sie auf ein Objekt über eine Schnittstelle verweisen, können Sie nur Methoden aufrufen, die über die Schnittstelle zugänglich sind. Mit mehreren Schnittstellen arbeiten Eine Klasse kann höchstens eine Basisklasse besitzen, darf aber beliebig viele Schnittstellen implementieren. Alle Methoden, die die Klasse von allen ihren Schnittstellen erbt, muss sie aber auch implementieren. Abstrakte Klassen interface IVehicle { string Accelerate(); string Brake(); string Drive(); } class Bicycle: IVehicle { string IVehicle.Accelerate() { return "Beschleunigen"; } string IVehicle.Brake() { return "Bremsen"; } string IVehicle.Drive() { return "Fahren"; } } class Car: IVehicle { string IVehicle.Accelerate() Mirco De Roni - 47 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ { return "Beschleunigen"; } string IVehicle.Brake() { return "Bremsen"; } string IVehicle.Drive() { return "Fahren"; } } Doppelter Code sollte für Sie immer ein Alarmsignal sein. Überarbeiten Sie dann den Code, um die Wiederholung zu vermeiden und die Wartungskosten zu senken. Bei dieser Umgestaltung fasst man die gemeinsame Implementierung in einer neuen Klasse zusammen, die speziell für diesen Zweck erstellt wird. Um zu deklarieren, dass es nicht erlaubt ist, Instanzen einer Klasse zu erstellen, müssen Sie diese Klasse explizit als abstrakt deklarieren. Das geschieht mit dem Schlüsselwort abstract wie im folgenden Beispiel: abstract class Vehicle: IVehicle { string IVehicle.Accelerate() { return "Beschleunigen"; } string IVehicle.Brake() { return "Bremsen"; } string IVehicle.Drive() { return "Fahren"; } } class Bicycle: Vehicle { ... } class Car: Vehicle { ... } class Program { static void Main(string[] args) { Vehicle c = new Car(); Ouput(c); Console.ReadLine(); } static void Ouput(IVehicle v) { Console.WriteLine(v.Accelerate()); Mirco De Roni - 48 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Console.WriteLine(v.Brake()); Console.WriteLine(v.Drive()); } } Versiegelte Klassen Wenn Sie eine Klasse nicht von vornherein als Basisklasse konzipieren, ist es höchst unwahrscheinlich, dass sie in der Praxis als Basisklasse gute Dienste leisten wird. In C# können Sie nun mit dem Schlüsselwort sealed (versiegelt) verhindern, dass eine Klasse als Basisklasse verwendet wird. Zum Beispiel: sealed class Bicycle: Vehicle { ... } Wenn irgendeine Klasse versucht, Bicycle als Basisklasse zu verwenden, beschwert sich der Compiler mit einer Fehlermeldung. Eine versiegelte Klasse kann keine virtuellen Methoden deklarieren und eine abstrakte Klasse lässt sich nicht versiegeln. Mirco De Roni - 49 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 14 Garbage Collection und Ressourcenverwaltung einsetzen Der Lebenszyklus von Objekten Ein Objekt erzeugen Sie bekanntlich mit dem Operator new. Das folgende Beispiel erstellt eine neue Instanz der Klasse TextBox. TextBox message = new TextBox(); Die Objekterstellung läuft in zwei Phasen ab: 1. Die new-Operation fordert einen Speicherblock vom Heap an. Auf diese Phase der Objekterstellung haben Sie keinen Einfluss. 2. Die new-Operation konvertiert den unstrukturierten Speicher in ein Objekt – sie muss das Objekt initialisieren. Diese Phase können Sie mit einem Konstruktor steuern. Nachdem Sie ein Objekt erstellt haben, können Sie auf seine Member mit dem Punktoperator zugreifen. message.Text = "Visual C#"; Wie das Erstellen läuft auch das Zerstören von Objekten in zwei Phasen ab, die ein genaues Spiegelbild der Erstellungsphasen darstellen: 1. Die Laufzeit muss gewisse Aufräumungsarbeiten ausführen. Dies steuern Sie mit einem Destruktor. 2. Die Laufzeit muss den Speicher, der bisher zum Objekt gehört hat, wieder an den Heap zurückgeben. Die Zuordnung des Speichers, den das Objekt belegt hat, ist aufzuheben. Diese Phase können Sie nicht beeinflussen. Das Zerstören eines Objekts und die Rückgabe des Speichers an den Heap bezeichnet man als Garbage Collection (Speicherbereinigung). Destruktoren erstellen Bevor ein Objekt der Speicherbereinigung zum Opfer fällt, können Sie in einem Destruktor alle erforderlichen Aufräumungsarbeiten erledigen. Wie ein Konstruktor ist ein Destruktor eine spezielle Methode, die aber von der Laufzeit aufgerufen wird, nachdem der letzte Verweis auf ein Objekt verschwunden ist. Die Syntax für einen Destruktor besteht aus einer Tilde (~) und dem Name der Klasse. Die einfache Klasse im folgenden Beispiel zählt die aktiven Instanzen, indem sie eine statische Variable im Konstruktor inkrementiert und im Destruktor dieselbe Variable dekrementiert: class Tally { private static int m_InstanceCount = 0; public Tally() { m_InstanceCount++; } ~Tally() { m_InstanceCount--; } public static int getInstanceCount() { return m_InstanceCount; } } Mirco De Roni - 50 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Für Destruktoren gelten mehrere wichtige Einschränkungen: Destruktoren lassen sich nur auf Verweistypen anwenden. In einem Werttyp beispielweise Struktur, können Sie keinen Destruktor deklarieren. Für einen Destruktor können Sie keinen Zugriffsmodifizierer deklarieren. Den Konstruktor rufen Sie niemals in Ihrem eigenen Code auf – das erledigt der Garbage Collector als Instrument der Laufzeit. Destruktoren dürfen keine Parameter übernehmen. Auch aus diesem Grund rufen Sie den Destruktor niemals direkt auf. Der Compiler übersetzt den Destruktor automatisch in eine Überschreibung der Methode object.Finalize, d.h. der Code des Destruktors class Tally { ~Tally() { ... } } wird übersetzt in: class Tally { protected override void Finalize() { try {...} finally { base.Finalize(); } } } Warum wird der Garbage Collector verwendet? Sie vergessen ein Objekt zu zerstören. Damit wird der Destruktor des Objekts nicht aufgerufen, die Aufräumungsarbeiten finden nicht statt und der Speicher geht nicht an den Heap zurück. Dadurch steht bald kein Speicher mehr zur Verfügung. Sie versuchen, ein aktives Objekt zu zerstören. Wie Sie wissen, erfolgt der Zugriff auf Objekte über Verweise. Wenn eine Klasse einen Verweis auf ein zerstörtes Objekt enthält, handelt es sich um einen ungültigen Verweis. Letztlich verweist dieser ungültige Verweis auf nicht verwendeten Speicher oder ein vollkommen anderes Objekt im selben Speicherbereich. Sie versuchen, dasselbe Objekt mehrmals zu zerstören. Abhängig vom Code im Destruktor könnte das katastrophale Auswirkungen haben. Derartige Probleme sind einfach inakzeptabel in einer Sprache wie C#, bei der Stabilität und Sicherheit ganz oben auf der Liste der Entwurfsziele stehen. Deshalb ist der Garbage Collector dafür zuständig, die Objekte zu zerstören und garantiert dabei Folgendes: Er zerstört jedes Objekt und ruft seinen Destruktor auf. Wenn ein Programm endet, zerstört er alle noch ausstehende Objekte. Er zerstört jedes Objekt genau einmal. Er zerstört jedes Objekt erst, wenn es unerreichbar wird, d.h. wenn sich keine Verweise mehr auf das Objekt beziehen. Diese Garantien sind enorm nützlich und befreien den Programmierer von lästigen Verwaltungsarbeiten. Der Programmierer kann sich auf die eigentliche Programmlogik konzentrieren, was sich günstig auf die Produktivität auswirkt. Wie funktioniert der Garbage Collector? Der Garbage Collector läuft in einem eigenen Thread und kann nur zu bestimmten Zeitpunkten ausgeführt werden. Während er aktiv ist, halten andere Threads, die in Ihrer Anwendung laufen, vorübergehend an. Das geschieht deshalb, weil der Garbage Collector gegebenenfalls Objekte Mirco De Roni - 51 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ verschieben und Objektverweise aktualisieren muss. Das geht nicht mit Objekten, die ein Programm gerade verwendet. Der Garbage Collector unternimmt folgende Schritte: 1. Er erstellt eine Tabelle aller erreichbaren Objekte. Dazu verfolgt er wiederholt Verweisfelder innerhalb von Objekten. Der Garbage Collector erstellt diese Tabelle sehr sorgfältig und gewährleistet, dass zirkuläre Verweise nicht zu einer unendlichen Rekursion führen. Jedes Objekt, das nicht in dieser Tabelle erscheint, gilt als unerreichbar. 2. Der Garbage Collector prüft, ob die unerreichbaren Objekte einen Destruktor besitzen, der ausgeführt werden muss. Jedes unerreichbare Objekt, das eine Finalisierung verlangt, wird in eine spezielle Warteschlange gestellt, die so genannte Finalizer-Warteschlange. 3. Er hebt die Speicherreservierung der unerreichbaren Objekte auf. Dazu verschiebt er die erreichbaren Objekte im Heap nach unten, defragmentiert somit den Heap und gibt den Speicher an der Spitze des Heaps frei. Wenn der Garbage Collector ein erreichbares Objekt verschiebt, aktualisiert er auch alle Verweise auf das Objekt. 4. Zu diesem Zeitpunkt lässt der Garbage Collector die Fortsetzung anderer Threads zu. 5. In einem separaten Thread finalisiert der Garbage Collector die unerreichbaren Objekte. Ressourcenverwaltung Knappe Ressourcen müssen wieder freigegeben werden und zwar so schnell wie möglich. In diesen Situationen haben Sie nur die Option, die Ressourcen in eigener Regie freizugeben. Zu diesem Zweck gibt es Freigabemethoden. Besitzt eine Klasse eine Freigabemethode, können Sie sie explizit aufrufen und dabei steuern, wann die Ressource freigegeben wird. Freigabemethoden TextReader reader = new StreamReader(fullPathname); string line; while ((line = reader.ReadLine()) != null) { Console.WriteLine(line); } reader.Close(); Ausnahmesichere Freigabe von Ressourcen TextReader reader = new StreamReader(fullPathname); try { string line; while ((line = reader.ReadLine()) != null) { Console.WriteLine(line); } } finally { reader.Close(); } Allerdings hat eine derartige Variante mehrere Nachteile und ist damit keineswegs optimal: Wenn Sie mehr als eine Ressource freigeben müssen, wird das Verfahren schnell unhandlich. In manchen Fällen müssen Sie den Code modifizieren. Die Lösung lässt sich nicht abstrahieren. Das heisst, sie ist schwer verständlich und man muss den Code an allen Stellen wiederholen, wo diese Funktionalität notwendig ist. Der Verweis auf die Ressource bleibt auch nach dem finally-Block weiterhin im Gültigkeitsbereich. Die im Folgenden beschriebene using-Anweisung löst all diese Probleme. Mirco De Roni - 52 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Die using-Anweisung Die using-Anweisung stellt einen sauberen Mechanismus bereit, um die Lebenszeit von Ressourcen zu steuern. Man kann ein Objekt erzeugen und dieses Objekt wird zerstört, wenn der Block der usingAnweisung endet. using (TextReader reader = new StreamReader(fullPathname)) { string line; while ((line = reader.ReadLine()) != null) { Console.WriteLine(line); } } Mit einer using-Anweisung können Sie auf saubere, ausnahmesichere und robuste Art sicherstellen, dass eine Ressource immer automatisch freigegeben wird. Das löst alle Probleme, die bei der manuellen try/finally-Variante existieren. Jetzt verfügen Sie über eine Lösung, die sich skalieren lässt, wenn Sie mehrere Ressourcen freigeben müssen, die Logik des Programmcodes nicht stört, das Problem abstrahiert und wiederholten Code vermeidet, robust ist. Die innerhalb der using-Anweisung deklarierte Variable können Sie nicht mehr verwenden, nachdem die using-Anweisung beendet ist, weil die Variable nicht mehr gültig ist – andernfalls erhalten Sie eine Fehlermeldung des Compilers. Mirco De Roni - 53 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 15 Eigenschaften implementieren, um auf Attribute zuzugreifen Was sind Eigenschaften? Eine Eigenschaft ist eine Kreuzung aus Feld und Methode – sie sieht wie ein Feld aus, agiert aber wie eine Methode. Auf eine Eigenschaft greift man mit genau der gleichen Syntax wie auf ein Feld zu. Allerdings übersetzt der Compiler diese feldartige Syntax automatisch in Aufrufe von Accessormethoden. Eine Eigenschaftendeklaration sieht folgendermassen aus: ZugriffsModifizierer Typ EigenschaftenName { get { //Code für Lesemethode } set { //Code für Schreibmethode } } Eine Eigenschaft kann zwei Codeblöcke enthalten, die mit den Schlüsselwörtern get und set beginnen. Die Anweisungen im get-Block werden ausgeführt, wenn die Eigenschaft gelesen wird. Der set-Block enthält Anweisungen, die beim Schreiben der Eigenschaft ausgeführt werden. Der Typ der Eigenschaft spezifiziert den Typ der Daten, die sich mit den get- und set-Accessoren lesen und schreiben lassen. Das nächste Codefragment zeigt die Struktur ScreenPosition in einer neuen Fassung, die Eigenschaften verwendet. Beachten Sie dabei folgende Konventionen: Die Kleinbuchstaben x und y bezeichnen private Felder. Die Grossbuchstaben X und Y bezeichnen öffentliche Eigenschaften. Tipp Die Benennung der Felder und Eigenschaften folgt den Konventionen von Microsoft Visual C# für öffentliche und private Member: Öffentliche Felder und Eigenschaften sollten mit einem Grossbuchstaben beginnen, private Felder und Eigenschaften mit einem Kleinbuchstaben. struct ScreenPosition { private int x, y; private static int rangeCheckedX(int x) { return x; } private static int rangeCheckedY(int y) { return y; } public ScreenPosition(int X, int Y) { this.x = rangeCheckedX(X); this.y = rangeCheckedY(Y); } public int X { get { return this.x; } set { this.x = rangeCheckedX(value); } } Mirco De Roni - 54 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ public int Y { get { return this.y; } set { this.y = rangeCheckedY(value); } } } Eigenschaften verwenden Wenn Sie eine Eigenschaft in einem Ausdruck verwenden, geschieht das entweder in einem Lesekontext oder in einem Schreibkontext. static void Main(string[] args) { ScreenPosition position = new ScreenPosition(0, 0); int xPos = position.X; //ruft position.X get auf int yPos = position.Y; //ruft position.Y get auf Console.WriteLine("Position"); Console.WriteLine("X: " + xPos + "\nY: " + yPos); } Schreibgeschützte Eigenschaften Wenn Sie eine Eigenschaft deklarieren, die lediglich einen get-Accessor enthält, können Sie diese Eigenschaft nur in einem Lesekontext verwenden. struct ScreenPosition { ... public int X { get { return this.x; } } } Lesegeschützte Eigenschaften Dementsprechend können Sie auch eine Eigenschaft erstellen, die lediglich einen set-Accessor enthält. In diesem Fall lässt sich die Eigenschaft nur in einem Schreibkontext verwenden. struct ScreenPosition { ... public int X { set { this.x = rangeCheckedX(value); } } } Zugriffsmodifizierer bei Eigenschaften Den Zugriff auf eine Eigenschaft (öffentlich, privat oder geschützt) legen Sie fest, wenn Sie die Eigenschaft deklarieren. Allerdings ist es möglich, unterschiedliche Zugriffsmodifizierer für get- und setAccessoren anzugeben. struct ScreenPosition { ... public int X { get { return this.x; } private set { this.x = rangeCheckedX(value); } } public int Y Mirco De Roni - 55 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ { get { return this.y; } private set { this.y = rangeCheckedY(value); } } } Einschränkungen von Eigenschaften verstehen Eigenschaften weisen viele Ähnlichkeiten mit Feldern auf. Allerdings sind es keine echten Felder uns so gelten auch einige Einschränkungen für Eigenschaften: Einen Wert können Sie über eine Eigenschaft einer Struktur oder Klasse nur zuweisen, nachdem die Struktur oder Klasse initialisiert wurde. Eine Eigenschaft können Sie nicht als ref- oder out-Argument verwenden. Eine Eigenschaft darf höchstens einen get-Accessor und einen set-Accessor enthalten. Andere Methoden, Felder oder Eigenschaften sind in einer Eigenschaft nicht zulässig. Die get- und set-Accessoren dürfen keine Parameter übernehmen. Die zuzuweisenden Daten werden an den set-Accessor automatisch mithilfe der Variablen value übergeben. Eigenschaften können Sie nicht als const deklarieren. Schnittstelleneigenschaften deklarieren In Schnittstellen können Sie ausser Methoden auch Eigenschaften spezifizieren. Dazu verwenden Sie die Schlüsselwörter get und/oder set, ersetzen aber den Körper des get- oder set-Accessors durch ein Semikolon. Zum Beispiel: interface IScreenPosition { int X { get; set; } int Y { get; set; } } Jede Klasse oder Struktur, die diese Schnittstelle implementiert, muss dann auch die Eigenschaften X und Y mit den get- und set-Accessoren implementieren. Auch dazu wieder ein Beispiel: struct ScreenPosition: IScreenPosition { public int X { get { ... } set { ... } } public int Y { get { ... } set { ... } } } Mirco De Roni - 56 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 16 Delegaten und Ereignisse Delegaten deklarieren und verwenden Ein Delegat ist ein Zeiger auf eine Methode und Sie rufen ihn in der gleichen Weise wie eine Methode auf. Wenn Sie allerdings einen Delegaten aufrufen, führt die Laufzeit tatsächlich die Methode aus, auf die der Delegat verweist. Diese Methode wiederum können Sie dynamisch ändern, sodass der Code, der einen Delegaten aufruft, bei jedem Aufruf durchaus eine andere Methode ausführen könnte. Hinweis Die Abkürzung API bedeutet Application Programming Interface – Anwendungsprogrammierschnittstelle. Es handelt sich um eine Methode oder einen Satz von Methoden, die ein bestimmter Teil von Software verfügbar macht, damit Sie diese Software steuern können. Microsoft .NET Framework können Sie sich als Satz von APIs vorstellen, da Sie über dessen Methoden die .NET Common Language Runtime (CLR) und das Betriebssystem Microsoft Windows steuern können. Einen Delegaten deklarieren Sie wie folgt: delegate void Tick(int h, int m, int s); Beachten Sie die folgenden Punkte: Geben Sie das Schlüsselwort delegate an, wenn Sie einen Delegaten deklarieren. Ein Delegat definiert die Gestalt der Methoden, auf die er verweisen kann. Dazu spezifizieren Sie den Rückgabetyp und gegebenenfalls die Parameter. Nachdem Sie den Delegaten definiert haben, können Sie eine Instanz erzeugen und sie mit dem Verbundzuweisungsoperator += auf eine passende Methode verweisen lassen. class Ticker { public delegate void Tick(int h, int m, int s); private Tick tickers; public Ticker() { } public void Add(Tick newMethod) { this.tickers += newMethod; } } Sie können mit dem Schlüsselwort new ausserdem auch einen Delegaten explizit mit einer bestimmten Methode initialisieren: this.tickers = new Tick(newMethod); Die Methode rufen Sie folgendermassen über den Delegaten auf: this.tickers(); Einen Delegaten rufen Sie mit der gleichen Syntax wie eine Methode auf. Übernimmt die Methode, auf die der Delegat verweist, irgendwelche Parameter, geben Sie diese in den Klammern mit an. Ein Delegat bietet vor allem den Vorteil, dass er auf mehr als eine Methode verweisen kann. Diese Methoden fügen Sie dem Delegaten ganz einfach mit dem Operator += hinzu. Mirco De Roni - 57 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Mit dem Verbundzuweisungsoperator -= können Sie auch eine Methode aus einem Delegaten entfernen: public void Remove(Tick oldMethod) { this.tickers -= oldMethod; } Benachrichtigungen mit Ereignissen realisieren In .NET Framework ist es mit Ereignissen möglich, wichtige Aktionen zu definieren und abzufangen sowie einen Delegaten damit zu beauftragen, sich der Situation anzunehmen. Viele Klassen im .NETFramework machen Ereignisse verfügbar. Ein Ereignis deklarieren Ein Ereignis deklarieren Sie in einer Klasse, die als Ereignisquelle fungieren soll. Als Ereignisquelle kommt normalerweise eine Klasse infrage, die ihre Umgebung überwacht und ein Ereignis auslöst, sobald etwas Bedeutsames passiert. Ein Ereignis deklarieren Sie in ähnlicher Form wie ein Feld. Da jedoch Ereignisse für die Verwendung mit Delegaten konzipiert sind, muss der Typ eines Ereignisses ein Delegat sein. Zudem ist vor die Deklaration eines Ereignisses das Schlüsselwort event zu setzen. Die Syntax für die Deklaration eines Ereignisses sieht folgendermassen aus: event delegatTypName ereignisName public delegate void Tick(int h, int m, int s); public event Tick tick; Ein Ereignis abonnieren Wie Delegaten verfügen Ereignisse über einen Operator +=. Mit diesem Operator können Sie ein Ereignis abonnieren. class Ticker { public delegate void Tick(int h, int m, int s); public event Tick tick; ... } private Ticker ticker = new Ticker(); ticker.tick += this.RefreshTime; private void RefreshTime(int hh, int mm, int ss) { this.txtBoxDisplay.Text = string.Format("{0:D2}:{1:D2}:{2:D2}", hh, mm, ss); } Das Abonnement eines Ereignisses kündigen Der Aufruf des Operators -= entfernt die Methode aus der internen Delegatenauflistung des Ereignisses – das Ereignis wird gekündigt. Ein Ereignis auslösen Ein Ereignis lässt sich genau wie ein Delegat auslösen, indem man es wie eine Methode aufruft. Wenn Sie ein Ereignis auslösen, werden nacheinander alle gebundenen Delegaten aufgerufen. class Ticker { public delegate void Tick(int h, int m, int s); public event Tick tick; Mirco De Roni - 58 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ private void Notify(int hours, int minutes, int seconds) { if (this.tick != null) { this.tick(hours, minutes, seconds); } } } Mirco De Roni - 59 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 17 Einführung in Generics Eine generische Klasse erstellen C# stellt Generics bereit, um die notwendige Typumwandlung zu beseitigen, die Typsicherheit zu verbessern, den Umfang des erforderlichen Boxings zu verringern und es sicherer zu machen, verallgemeinerte Klasen und Methoden zu erstellen. Generische Klassen und Methoden übernehmen Typparameter. Diese spezifizieren den Typ der Objekte, auf denen sie arbeiten. Die .NET FrameworkKlassenbibliothek umfasst generische Versionen vieler Auflistungsklassen und Schnittstellen im Namespace System.Collections.Generic. Die Theorie der binären Bäume Ein binärer Baum ist eine rekursive Datenstruktur, die entweder leer sein kann oder drei Elemente umfasst: bestimmte Daten, die man normalerweise als Knoten bezeichnet und zwei Teilbäume, die selbst binäre Bäume sind. Es hat sich eingebürgert, die beiden Teilbäume als linken und rechten Teilbaum zu bezeichnen. Eine Binärbaum-Klasse mit Generics erstellen public class Tree<TItem> where TItem : IComparable<TItem> { public Tree(TItem nodeValue) { this.NodeData = nodeValue; this.LeftTree = null; this.RightTree = null; } public void Insert(TItem newItem) { TItem currentNodeValue = this.NodeData; if (currentNodeValue.CompareTo(newItem) > 0) { if (this.LeftTree == null) { this.LeftTree = new Tree<TItem>(newItem); } else { this.LeftTree.Insert(newItem); } } else { if (this.RightTree == null) { this.RightTree = new Tree<TItem>(newItem); } else { this.RightTree.Insert(newItem); } } } public void WalkTree() { if (this.LeftTree != null) { this.LeftTree.WalkTree(); } Console.WriteLine(this.NodeData.ToString()); Mirco De Roni - 60 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ if (this.RightTree != null) { this.RightTree.WalkTree(); } } public TItem NodeData { get; set; } public Tree<TItem> LeftTree { get; set; } public Tree<TItem> RightTree { get; set; } } class Program { static void Main(string[] args) { Tree<int> tree1 = new Tree<int>(10); tree1.Insert(5); tree1.Insert(11); tree1.Insert(5); tree1.Insert(-12); tree1.Insert(15); tree1.Insert(0); tree1.Insert(14); tree1.Insert(-8); tree1.Insert(10); tree1.Insert(8); tree1.Insert(8); tree1.WalkTree(); Tree<string> tree2 = new Tree<string>("Hello"); tree2.Insert("World"); tree2.Insert("How"); tree2.Insert("Are"); tree2.Insert("You"); tree2.Insert("Today"); tree2.Insert("I"); tree2.Insert("Hope"); tree2.Insert("You"); tree2.Insert("Are"); tree2.Insert("Feeling"); tree2.Insert("Well"); tree2.Insert("!"); tree2.WalkTree(); } } Mirco De Roni - 61 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 18 Speicherinterne Daten mit Abfrageausdrücken abrufen Was ist LINQ? Die Designer von LINQ (Language Integrated Query) haben sich an dem Konzept orientiert, nach dem relationale Datenbankmanagementsysteme wie zum Beispiel Microsoft SQL Server die für die Abfrage der Datenbank verwendete Sprache von der internen Darstellung der Daten in der Datenbank trennen. Entwickler, die auf eine SQL Server-Datenbank zugreifen, richten SQL (Structured Query Language)Anweisungen an das Datenbankmanagementsystem. SQL beschreibt auf einer höheren Ebene die Daten, die der Entwickler abrufen möchte, gibt aber nicht genau an, wie das Datenbankmanagementsystem diese Daten abrufen soll. Das sind Details, die das Datenbankmanagementsystem selbst steuert. Syntax und Semantik von LINQ erinnern stark an SQL und bieten viele der gleichen Vorteile. Die zugrunde liegende Struktur der abzufragenden Daten lässt sich ändern, ohne den Code ändern zu müssen, der die eigentlichen Abfragen ausführt. Doch auch wenn LINQ ähnlich wie SQL aussieht, ist es weit flexibler und beherrscht ein breiteres Spektrum an logischen Datenstrukturen. Zum Beispiel kann LINQ mit hierarchisch organisierten Daten umgehen, wie sie beispielsweise in einem XML-Dokument zu finden sind. LINQ in einer C#-Anwendung einsetzen class Employee { public int m_EmployeeID; public string m_FirstName; public string m_LastName; public string m_CompanyName; public string m_Department; public override string ToString() { return String.Format("{0} {1}", this.m_FirstName, this.m_LastName); } } class Address { public int m_fk_EmployeeID; public string m_City; } static void Main(string[] args) { var employees = new[] { new Employee { m_EmployeeID = 1, m_FirstName = "Mirco", m_LastName = "De Roni", m_CompanyName = "Informatik GmbH", m_Department = "Entwicklung" }, new Employee { m_EmployeeID = 2, m_FirstName = "Victoria", m_LastName = "Muster", m_CompanyName = "Schindler Informatik AG", m_Department = "Entwicklung" }, new Employee { m_EmployeeID = 3, m_FirstName = "Simon", m_LastName = "Birrer", m_CompanyName = "Informatik GmbH", m_Department = "Entwicklung" }, new Employee { m_EmployeeID = 4, m_FirstName = "Francesco", m_LastName = "Example", m_CompanyName = "Digitec", m_Department = "Verkauf" }, new Employee { m_EmployeeID = 5, m_FirstName = "Jessica", m_LastName = "Beispiel", m_CompanyName = "Bike Store", m_Department = "IT" }, new Employee { m_EmployeeID = 6, m_FirstName = "Hans", m_LastName = "Muster", m_CompanyName = "Informatik GmbH", m_Department = "Support" }, new Employee { m_EmployeeID = 7, m_FirstName = "Eric", m_LastName = "Long", m_CompanyName = "Bike Store", m_Department = "Support" }, new Employee { m_EmployeeID = 8, m_FirstName = "Katy", m_LastName = Mirco De Roni - 62 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ "Black", m_CompanyName = "PC Store", m_Department = "Marketing" }, }; var adresses = new[] { new Address { m_fk_EmployeeID = 1, m_City = "Luzern" }, new Address { m_fk_EmployeeID = 5, m_City = "Bern" }, }; Console.WriteLine("------------------------------------"); Console.WriteLine("Liste der Abteilungen"); Console.WriteLine("------------------------------------"); var departments = (from e in employees orderby e.m_Department ascending select e.m_Department).Distinct(); foreach (var aDepartment in departments) { Console.WriteLine("Abteilung: {0}", aDepartment); } int numberOfDepartments = (from e in employees select e.m_Department).Distinct().Count(); Console.WriteLine("\nAnzahl der Abteilungen: {0}", numberOfDepartments); Console.WriteLine("\n------------------------------------"); Console.WriteLine("Mitarbeiter in der Entwicklung-Abteilung"); Console.WriteLine("------------------------------------"); var devEmployees = from e in employees where String.Equals(e.m_Department, "Entwicklung") select e; foreach (var anEmployee in devEmployees) { Console.WriteLine(anEmployee); } Console.WriteLine("\n------------------------------------"); Console.WriteLine("Alle Mitarbeiter gruppiert nach Abteilung"); Console.WriteLine("------------------------------------"); var employeesGroupedByDepartment = from e in employees group e by e.m_Department; foreach (var aDepartment in employeesGroupedByDepartment) { Console.WriteLine("{0}", aDepartment.Key); foreach (var anEmployee in aDepartment) { Console.WriteLine("\t{0} {1}", anEmployee.m_FirstName, anEmployee.m_LastName); } } Console.WriteLine("\n------------------------------------"); Console.WriteLine("Liste der Firmen"); Console.WriteLine("------------------------------------"); var companies = (from e in employees select e.m_CompanyName).Distinct(); foreach (var aCompany in companies) { Console.WriteLine("Firma: {0}", aCompany); } int numberOfCompanies = (from e in employees select e.m_CompanyName).Distinct().Count(); Mirco De Roni - 63 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Console.WriteLine("\nAnzahl der Firmen: {0}", numberOfCompanies); Console.WriteLine("\n------------------------------------"); Console.WriteLine("Alle Mitarbeiter gruppiert nach Firma"); Console.WriteLine("------------------------------------"); var employeesGroupedByCompany = from e in employees group e by e.m_CompanyName; foreach (var aCompany in employeesGroupedByCompany) { Console.WriteLine("{0}", aCompany.Key); foreach (var anEmployee in aCompany) { Console.WriteLine("\t{0} {1}", anEmployee.m_FirstName, anEmployee.m_LastName); } } Console.WriteLine("\n------------------------------------"); Console.WriteLine("JOIN"); Console.WriteLine("------------------------------------"); var join = from e in employees join a in adresses on e.m_EmployeeID equals a.m_fk_EmployeeID select new { e.m_FirstName, e.m_LastName, a.m_City }; foreach (var anEmployee in join) { Console.WriteLine("{0} {1}, {2}", anEmployee.m_FirstName, anEmployee.m_LastName, anEmployee.m_City); } Console.ReadLine(); } Daten auswählen Die Daten wählen Sie mit dem Operator select aus. var companies = (from e in employees select e.m_CompanyName).Distinct(); foreach (var aCompany in companies) { Console.WriteLine("Firma: {0}", aCompany); } Daten filtern Die Daten filtern Sie mit dem where-Operator. var devEmployees = from e in employees where String.Equals(e.m_Department, "Entwicklung") select e; foreach (var anEmployee in devEmployees) { Console.WriteLine(anEmployee); } Daten sortieren, gruppieren und zusammenfassen Die Daten sortieren Sie wie folgt mit dem Operator orderby: var departments = (from e in employees orderby e.m_Department ascending select e.m_Department).Distinct(); foreach (var aDepartment in departments) { Mirco De Roni - 64 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Console.WriteLine("Abteilung: {0}", aDepartment); } Mit dem Operator group gruppieren Sie die Daten: var employeesGroupedByDepartment = from e in employees group e by e.m_Department; foreach (var aDepartment in employeesGroupedByDepartment) { Console.WriteLine("{0}", aDepartment.Key); foreach (var anEmployee in aDepartment) { Console.WriteLine("\t{0} {1}", anEmployee.m_FirstName, anEmployee.m_LastName); } } Auf die Ergebnisse der select-Methode können Sie viele der Zusammenfassungsmethoden wie Count, Max und Min direkt anwenden. int numberOfDepartments = (from e in employees select e.m_Department).Distinct().Count(); Console.WriteLine("Anzahl der Abteilungen: {0}", numberOfDepartments); Daten verknüpfen Genau wie SQL ist es mit LINQ möglich, mehrere Mengen von Daten miteinander über einen oder mehrere gemeinsame Schlüsselfelder zu verknüpfen. Mit dem Operator join lassen sich zwei Auflistungen über einen gemeinsamen Schlüssel verknüpfen. var join = from e in employees join a in adresses on e.m_EmployeeID equals a.m_fk_EmployeeID select new { e.m_FirstName, e.m_LastName, a.m_City }; foreach (var anEmployee in join) { Console.WriteLine("{0} {1}, {2}", anEmployee.m_FirstName, anEmployee.m_LastName, anEmployee.m_City); } Mirco De Roni - 65 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 19 Überladen von Operatoren Operatoren verstehen Mit Operatoren kombinieren Sie Operanden in Ausdrücken. Jeder Operator besitzt seine eigene Semantik abhängig vom Typ, mit dem er arbeitet. Jedes Operatorensymbol besitzt einen bestimmten Vorrang. Zum Beispiel hat der Operator * einen höheren Vorrang als der Operator +. Das bedeutet, dass der Ausdruck a + b * c gleichbedeutend mit a + (b * c) ist. Ausserdem ist jedem Operatorensymbol eine Orientierung oder Assoziativität zugeordnet, die entscheidet, ob der Operator von links nach rechts oder von rechts nach links auswertet. Ein unärer Operator wirkt auf nur einen Operanden. Zu dieser Kategorie gehört der Inkrementoperator (++). Ein binärer Operator – zum Beispiel der Multiplikationsoperator (*) – verknüpft zwei Operanden. Operatoreneinschränkungen Es gelten folgende Regeln: Vorrang und Orientierung eines Operators lassen sich nicht ändern. Der Vorrang und die Orientierung basieren auf dem Operatorsymbol und nicht auf dem Typ, auf den das Operatorsymbol angewendet wird. Die Anzahl der Operanden für einen Operator lässt sich nicht ändern. Sie können keine neuen Operatorensymbole erfinden. Beispielweise ist es nicht möglich, ein neues Operatorsymbol wie zum Beispiel ** für die Potenzierung einzuführen. In solchen Fällen müssen Sie eine Methode erstellen. Die Bedeutung der Operatoren in Verbindung mit integrierten Typen lässt sich nicht ändern. Bestimmte Operatorsymbole dürfen nicht überladen werden. Das betrifft beispielweise den Punktoperator. Überladene Operatoren Um das Verhalten eines eigenen Operators zu definieren, müssen Sie einen ausgewählten Operator überladen. Dazu verwenden Sie eine methodenähnliche Syntax mit einem Rückgabetyp und Parametern. struct Hour { private int value; public Hour(int initialValue) { this.value = initialValue; } public static Hour operator+(Hour lhs, Hour rhs) { return new Hour(lhs.value + rhs.value); } } Beachten Sie folgende Punkte: Der Operator ist öffentlich. Alle Operatoren müssen öffentlich sein. Der Operator ist statisch. Alle Operatoren müssen statisch sein. Operatoren sind niemals polymorph und dürfen nicht die Modifizierer virtual, abstract, override und sealed verwenden. Ein binärer Operator besitzt zwei explizite Argumente, ein unärer Operator ein explizites Argument. Mirco De Roni - 66 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Hour Example(Hour a, Hour b) { return a + b; } Verbundzuweisungen verstehen Ein Verbundzuweisungsoperator wird immer in Bezug auf seinen zugeordneten Operator ausgewertet. Mit anderen Worten wird die Anweisung a += b; automatisch als a = a + b; ausgewertet. Mirco De Roni - 67 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 20 Einführung in Windows Presentation Foundation Eine WPF-Anwendung erstellen Um eine WPF-Anwendung zu erstellen, klicken Sie in Visual Studio auf das Menü Datei Neu Projekt…. Anschliessend wählen Sie den Eintrag WPF-Anwendung. Die XAML-Definition des Formulars sieht folgendermassen aus: <Window x:Class="Einstieg.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <Grid> </Grid> </Window> Das Attribut class spezifiziert den vollqualifizierten Namen der Klasse, die das Formular implementiert. Die Vorlage WPF-Anwendung verwendet den Namen der Anwendung als Standardnamespace für Formulare. Die xmlns-Attribute geben die XML-Namespaces an, die die von WPF verwendeten Schemas definieren. Alle Steuerelemente und anderen Elemente, die Sie in eine WPF-Anwendung einbinden können, besitzen Definitionen in diesen Namespaces. Das Attribut Title spezifiziert den Text, der in der Titelleiste des Formulars erscheint. Mit den Attributen Height und Width werden die Standardwerte für Höhe und Breite des Formulars angegeben. Diese Werte können Sie entweder im XAML-Bereich oder im Eigenschaftenfenster dynamisch mithilfe von C#-Code ändern, der bei aktivem Formular ausgeführt wird. Konnektoren Ankerpunkte Tipp Im Fenster Eigenschaften können Sie viele Eigenschaften eines Steuerelements, wie zum Beispiel Margin, jedoch nicht alle Eigenschaften festlegen. Und manchmal ist es einfacher, die Werte direkt im XAML-Bereich einzutippen, sofern Sie dabei sorgfältig vorgehen. Hinweis Wenn Sie die Eigenschaften Width und Height des Buttons-Steuerelements nicht festlegen, nimmt die Schaltfläche das gesamte Formular ein. Ein Panel ist ein Steuerelement, das als Container für andere Steuerelemente fungiert und bestimmt, wie sie relativ zueinander angeordnet werden. Das Grid-Steuerelement ist ein Beispiel für ein PanelSteuerelement. Mirco De Roni - 68 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Mit WPF können Sie die Art und Weise beeinflussen, in der sich Steuerelemente wie Schaltflächen, Textfelder und Labels selbst auf einem Formular darstellen. Steuerelemente in das Formular aufnehmen Die WPF-Bibliothek enthält eine umfangreiche Palette von Steuerelementen. Die Aufgaben vieler Steuerelemente wie TextBox, ListBox, CheckBox oder ComboBox sind ohne weiteres klar, während leistungsfähigere Steuerelemente vielleicht nicht so bekannt sind. Alle verfügbaren Steuerelemente befinden sich in der Toolbox. Wenn Sie ein neues Element auf dem Formular darstellen wollen, ziehen Sie einfach ein Element aus der Toolbox auf das Formular. Danach können Sie das Element an die gewünschte Position verschieben und die Grösse anpassen. Ereignisse in einem WPF-Formular behandeln Als Entwickler haben Sie die Aufgabe, die Ereignisse abzufangen, die für Ihre Anwendung relevant sind. Mit entsprechendem Code reagieren Sie auf diese Ereignisse. Ein bekanntes Beispiel ist das ButtonSteuerelement (Schaltfläche). private void btnFinish_Click(object sender, RoutedEventArgs e) { Close(); } Der XAML-Code sieht dann wie folgt aus: <Button Height="23" HorizontalAlignment="Right" Margin="0,44,36,0" Name="btnFinish" VerticalAlignment="Top" Width="75" Click="btnFinish_Click">Beenden</Button> Mirco De Roni - 69 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 21 Mit Menüs und Dialogfeldern arbeiten Richtlinien für Menüs In den meisten Windows-Anwendungen befinden sich bestimmte Elemente in der Menüleiste immer an der gleichen Stelle und der Inhalt dieser Menüs ist in der Regel vorhersehbar. Auch die Reihenfolge der Befehle in den Menüs ist meistens identisch. So ist der Befehl Beenden in der Regel der letzte Befehl im Menü Datei. Neben den Standardbefehlen können sich im Menü Datei auch anwendungsspezifische Befehle befinden. Menüs und Menüereignisse WPF stellt das Steuerelement Menu als Container für Menübefehle bereit. Das Steuerelement Menu realisiert eine grundsätzliche Shell, mit der sich Menüs definieren lassen. Wie die meisten Aspekte von WPF ist das Steuerelement Menu sehr flexibel, sodass Sie eine Menüstruktur mit nahezu jedem WPFSteuerelementtyp definieren können. Menüs lassen sich mit dem XAML-Bereich in der Entwurfsansicht definieren und es ist auch möglich, Menüs zur Laufzeit mit Microsoft Visual C#-Code zu konstruieren. Um ein neues Menü darzustellen, ziehen Sie aus der Toolbox im Abschnitt Steuerelemente ein MenuSteuerelement auf das Formular. Anschliessend modifizieren Sie im XAML-Bereich die Definition des Menu-Steuerelements und fügen dann die MenuItem-Elemente hinzu. Als letztes fügen Sie dem Menü Neu noch ein Bild hinzu. Hierfür müssen Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt klicken und wählen Hinzufügen Vorhandenes Element…. Im Dialog Vorhandenes Element hinzufügen navigieren Sie zu einer Bilddatei und klicken dann auf Hinzufügen. <Menu Height="22" Name="menu1" VerticalAlignment="Top"> <MenuItem Header="Datei"> <MenuItem Header="Neues Mitglied" Name="mnuNewMember" Click="mnuNewMember_Click"> <MenuItem.Icon> <Image Source="user.png" /> </MenuItem.Icon> </MenuItem> <MenuItem Header="Öffnen" Name="mnuOpen" /> <Separator /> <MenuItem Header="Mitgliedsdaten speichern" Name="mnuSaveMember" Click="mnuSaveMember_Click" IsEnabled="False"> <MenuItem.Icon> <Image Source="save.png" /> </MenuItem.Icon> </MenuItem> <Separator /> <MenuItem Header="Beenden" Name="mnuFinish" Click="mnuFinish_Click" /> </MenuItem> <MenuItem Header="Hilfe"></MenuItem> </Menu> private void mnuFinish_Click(object sender, RoutedEventArgs e) { Close(); } Mirco De Roni - 70 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Kontextmenüs Viele Windows-Anwendungen arbeiten mit Popup-Menüs, die erscheinen, wenn der Benutzer ein Formular oder ein Steuerelement mit der rechten Maustaste anklickt. Diese Menüs sind in der Regel kontextabhängig und enthalten nur Befehle, die sich auf das Formular oder das Steuerelement mit dem Eingabefokus beziehen. Man bezeichnet derartige Menüs deshalb auch als Kontextmenüs. Einer WPFAnwendung können Sie Kontextmenüs ganz einfach mit der Klasse ContextMenu hinzufügen. <Window.Resources> <ContextMenu x:Key="textBoxMenu"> <MenuItem Header="Name löschen" Name="cMenuClear" Click="cMenuClear_Click"/> </ContextMenu> </Window.Resources> <TextBox Margin="85,53,8,0" Name="txtBoxFirstName" Height="23" ContextMenu="{StaticResource textBoxMenu}" VerticalAlignment="Top" IsEnabled="False"></TextBox> private void cMenuClear_Click(object sender, RoutedEventArgs e) { txtBoxFirstName.Text = String.Empty; } Windows-Standarddialogfelder Es gibt eine Reihe von Standardaufgaben, bei denen der Benutzer bestimmte Arten von Informationen bereitstellen muss. Die Microsoft .NET Framework-Klassenbibliothek stellt die Klassen OpenFileDialog und SaveFileDialog bereit, die als Wrapper (Hüllklassen) für diese allgemeinen Dialogfelder fungieren. Die Klasse SaveFileDialog verwenden SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.DefaultExt = ".txt"; saveFileDialog.AddExtension = true; saveFileDialog.FileName = "Members"; saveFileDialog.InitialDirectory = @"C:\Temp"; saveFileDialog.OverwritePrompt = true; saveFileDialog.Title = "Mitglieder"; saveFileDialog.ValidateNames = true; if (saveFileDialog.ShowDialog().Value) { using (StreamWriter writer = new StreamWriter(saveFileDialog.FileName)) { writer.WriteLine("Vorname: {0}", txtBoxFirstName.Text); writer.WriteLine("Nachname: {0}", txtBoxLastName.Text); MessageBox.Show("Mitgliedsdaten gespeichert", "Gespeichert"); } } Mirco De Roni - 71 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 22 Eine Datenbank verwenden Für dieses Kapitel müssen Sie Microsoft SQL Server installiert haben. Es empfiehlt sich, dass Sie ein Konto mit Administratorberechtigungen verwenden. Eine Datenbank mit ADO.NET abfragen Die ADO.NET-Klassenbibliothek enthält ein umfangreiches Framework, mit dem sich Anwendungen erstellen lassen, die Daten einer relationalen Datenbank abrufen und aktualisieren müssen. Jedes Datenbankmanagementsystem (beispielsweise SQL Server, Oracle usw.) verfügt über einen eigenen Datenanbieter, der eine Abstraktion der Mechanismen für die Verbindung zu einer Datenbank, das Ausführen von Abfragen und das Aktualisieren von Daten implementiert. Die Northwind-Datenbank Northwind Traders (kurz Northwind) ist eine fiktive Firma, die Feinkost mit exotischen Namen verkauft. Die Datenbank Northwind enthält mehrere Tabellen mit Informationen über die von der Firma verkauften Artikel, die Kunden der Firma, die von den Kunden ausgelösten Bestellungen, die Lieferanten, von denen Northwind Traders Waren zum Wiederverkauf bezieht, Speditionen für die Auslieferung der Waren an die Kunden sowie Mitarbeiter, die für Northwind Traders tätig sind. Als Vorbereitung müssen Sie zunächst die Northwind-Datenbank erstellen. Bestellinformationen mit ADO.NET abfragen Folgende using-Anweisung fügen Sie zuerst am Anfang der Datei hinzu: using System.Data.SqlClient; Der Namespace System.Data.SqlClient enthält die Klassen des SQL Server-Datenanbieters für ADO.NET. Diese Klassen sind spezialisierte Versionen der ADO.NET-Klassen, die für die Arbeit mit SQL Server optimiert wurden. Mirco De Roni - 72 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ namespace ReportOrders { class Program { static void Main(string[] args) { SqlConnection connection = new SqlConnection(); try { connection.ConnectionString = "Integrated Security=true;Initial Catalog=Northwind;" + "Data Source=PCSRV\\SQLEXPRESS"; connection.Open(); Console.Write("Bitte eine KundenID eingeben (5 Zeichen): "); string customerId = Console.ReadLine(); SqlCommand command = new SqlCommand(); command.Connection = connection; command.CommandText = "SELECT OrderID, OrderDate, ShippedDate, ShipName, ShipAddress, ShipCity, ShipCountry " + "FROM Orders " + "WHERE CustomerID='" + customerId + "'"; Console.WriteLine(command.CommandText + "\n"); SqlDataReader dataReader = command.ExecuteReader(); while (dataReader.Read()) { int orderId = dataReader.GetInt32(0); if (dataReader.IsDBNull(2)) { Console.WriteLine("Bestellung " + orderId + " noch nicht versandt\n\n"); } else { DateTime orderDate = dataReader.GetDateTime(1); DateTime shippedDate = dataReader.GetDateTime(2); string shipName = dataReader.GetString(3); string shipAddress = dataReader.GetString(4); string shipCity = dataReader.GetString(5); string shipCountry = dataReader.GetString(6); Console.WriteLine ( "Bestellung: " + orderId + "\n" + "Aufgegeben: " + orderDate + "\n" + "Geliefert: " + shippedDate + "\n" + "Lieferungsname: " + shipName + "\n" + "Lieferadresse: " + shipAddress + "\n" + "Stadt: " + shipCity + "\n" + "Land: " + shipCountry + "\n" ); } } dataReader.Close(); } catch (SqlException ex) { Console.WriteLine("Fehler beim Zugriff auf die Datenbank:\n" + ex.Message); } finally Mirco De Roni - 73 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ { connection.Close(); } Console.ReadLine(); } } } Eine Datenbank mit DLINQ abfragen LINQ bietet Abfrageausdrücke mit einer SQL-ähnlichen Syntax, um Abfragen auszuführen und eine Ergebnismenge zu generieren, die sich schrittweise durchlaufen lässt. Mit der erweiterten Form von LINQ namens DLINQ können Sie den Inhalt einer Datenbank abfragen und manipulieren. DLINQ setzt auf ADO.NET auf und bietet eine höhere Ebene der Abstraktion. Eine Entitätsklasse definieren LINQ verlangt, dass die abgefragten Objekte aufzählbar sind. Es muss sich um Auflistungen handeln, die die Schnittstelle IEnumerable implementieren. DLINQ kann seine eigenen aufzählbaren Auflistungen von Objekten erstellen und zwar basierend auf Klassen, die Sie definieren und die sich direkt auf Tabellen in einer Datenbank abbilden lassen. Diese Klassen bezeichnet man als Entitätsklassen. Wenn Sie die Verbindung zu einer Datenbank herstellen und eine Abfrage ausführen, kann DLINQ die von der Abfrage bezeichneten Daten abrufen und für jede abgerufene Zeile eine Instanz einer Entitätsklasse erstellen. CREATE TABLE "Products" ( "ProductID" "int" IDENTITY (1, 1) NOT NULL , "ProductName" nvarchar (40) NOT NULL , "SupplierID" "int" NULL , "UnitPrice" "money" NULL, CONSTRAINT "PK_Products" PRIMARY KEY CLUSTERED ("ProductID"), CONSTRAINT "FK_Products_Suppliers" FOREIGN KEY ("SupplierID") REFERENCES "dbo"."Suppliers" ("SupplierID") ) Eine Entitätsklasse, die der Tabelle Products entspricht, können Sie wie folgt definieren: [Table(Name = "Products")] public class Product { [Column(IsPrimaryKey = true, CanBeNull = false)] public int ProductID { get; set; } [Column(CanBeNull = false)] public string ProductName { get; set; } [Column] public int? SupplierID { get; set; } [Column(DbType = "money")] public decimal? UnitPrice { get; set; } } Das Attribut Table kennzeichnet diese Klasse als Entitätsklasse. Der Parameter Name gibt den Namen der korrespondierenden Tabelle in der Datenbank an. Wenn Sie den Parameter weglassen, nimmt DLINQ an, dass der Name der Entitätsklasse gleich dem Namen der entsprechenden Tabelle in der Datenbank ist. Das Attribut Column beschreibt, wie eine Spalte in der Tabelle Products einer Eigenschaft in der Klasse Product zugeordnet wird. Mirco De Roni - 74 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ Der Parameter IsPrimaryKey gibt an, dass die Eigenschaft einen Teil des Primärschlüssels bildet. Der Parameter CanBeNull gibt an, ob die Spalte in der Datenbank einen NULL-Wert enthalten kann. Der Standardwert für den Parameter CanBeNull ist true. Der Parameter DbType gibt den Typ der zugrunde liegenden Spalte in der Datenbank an. In vielen Fällen kann DLINQ Daten in einer Spalte der Datenbank erkennen und in den Typ der entsprechenden Eigenschaft in der Entitätsklasse konvertieren. Bestellinformationen mit einer DLINQ-Abfrage abrufen Als erstes klicken Sie im Menü Projekt auf Verweis hinzufügen. Gehen Sie im Dialogfeld Verweis hinzufügen auf die Registerkarte .NET, markieren Sie die Assembly System.Data.Linq und klicken Sie dann auf OK. Diese Assembly enthält die DLINQ-Typen und -Attribute. using using using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Data.Linq; System.Data.Linq.Mapping; System.Data.SqlClient; namespace DLINQOrders { class Program { static void Main(string[] args) { Northwind northwindDB = new Northwind( "Integrated Security=true;Initial Catalog=Northwind;" + "Data Source=PCSRV\\SQLEXPRESS"); try { Console.Write("Bitte eine KundenID eingeben (5 Zeichen): "); string customerId = Console.ReadLine(); var ordersQuery = from o in northwindDB.Orders where String.Equals(o.CustomerID, customerId) select o; foreach (var order in ordersQuery) { if (order.ShippedDate == null) { Console.WriteLine("Bestellung " + order.OrderID + " noch nicht versandt\n\n"); } else { Console.WriteLine ( "Bestellung: " + order.OrderID + "\n" + "Aufgegeben: " + order.OrderDate + "\n" + "Geliefert: " + order.ShippedDate + "\n" + "Lieferungsname: " + order.ShipName + "\n" + "Lieferadresse: " + order.ShipAddress + "\n" + "Stadt: " + order.ShipCity + "\n" + "Land: " + order.ShipCountry + "\n" ); } } } Mirco De Roni - 75 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ catch (SqlException ex) { Console.WriteLine("Fehler beim Zugriff auf die Datenbank:\n" + ex.Message); } Console.ReadLine(); } } [Table(Name = "Orders")] public class Order { [Column(IsPrimaryKey = true, CanBeNull = false)] public int OrderID { get; set; } [Column] public string CustomerID { get; set; } [Column] public DateTime? OrderDate { get; set; } [Column] public DateTime? ShippedDate { get; set; } [Column] public string ShipName { get; set; } [Column] public string ShipAddress { get; set; } [Column] public string ShipCity { get; set; } [Column] public string ShipCountry { get; set; } } public class Northwind : DataContext { public Table<Order> Orders; public Northwind(string connectionInfo) : base(connectionInfo) { } } } Mirco De Roni - 76 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 23 Einen Webdienst erstellen und verwenden Was ist ein Webdienst? Ein Webdienst – oder Web Service – ist eine Geschäftskomponente, die den Clients (Consumer) eine bestimmte Funktionalität bereitstellt. Die Rolle von SOAP Das Simple Object Access Protocol (SOAP) ist das Protokoll, nach dem die Clientanwendungen Anforderungen an Webdienste senden und Antworten von Webdiensten empfangen. SOAP ist ein einfaches und kompaktes Protokoll, das auf HTTP – dem im Web zum Senden und Empfangen von HTML-Seiten verwendeten Protokoll – aufsetzt. Es definiert eine XML-Grammatik für die Benennung von Methoden, die ein Client auf einem Webdienst aufrufen möchte, die Definition von Parametern und Rückgabewerten sowie die Beschreibung der Typen von Parametern und Rückgabewerten. Wenn ein Client einen Webdienst aufruft, muss er die Methode und ihre Parameter mithilfe dieser XMLGrammatik spezifizieren. SOAP ist ein Industriestandard mit der Aufgabe, die plattformübergreifende Interoperabilität zu verbessern. Die Stärke von SOAP liegt in seiner Einfachheit und gründet sich auch darauf, dass es auf anderen Industriestandard-Techniken basiert: HTTP und XML. Die SOAP-Spezifikation definiert vor allem das Format einer SOAP-Meldung, wie Daten zu kodieren sind, wie Meldungen (Methodenaufrufe) zu senden sind und wie Antworten verarbeitet werden. Was ist die Web Services Description Language? Der Körper einer SOAP-Meldung ist ein XML-Dokument. Wenn eine Clientanwendung eine Webmethode aufruft, erwartet der Webserver, dass der Client die Parameter für die Methode mit einem bestimmten Satz von Tags kodiert. Woher weiss nun ein Client, welche Tags oder welches XML-Schema er verwenden soll? Er erfährt es dadurch, dass der Webdienst nach Aufforderung eine Beschreibung von sich selbst liefert. Die Antwort des Webdienstes ist ein anderes XML-Dokument, das den Webdienst beschreibt. Das für dieses Dokument verwendete XML-Schema ist standardisiert worden und heisst Web Services Description Language (WSDL). Diese Beschreibung bietet genügend Informationen, damit eine Clientanwendung eine SOAP-Anforderung in einem Format konstruieren kann, die der Webserver verstehen sollte. Einen Webdienst erstellen Um einen Webdienst zu erstellen, klicken Sie in Visual Studio auf das Menü Datei Neu Projekt…. Anschliessend wählen Sie den Eintrag ASP.NET-Webdienstanwendung. Mirco De Roni - 77 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ using using using using using System; System.Collections.Generic; System.Linq; System.Web; System.Web.Services; namespace Webservice { /// <summary> /// Zusammenfassungsbeschreibung für Service1 /// </summary> [WebService(Namespace = "http://microsoft.com/webservices/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] // Um das Aufrufen dieses Webdiensts aus einem Skript mit ASP.NET AJAX zuzulassen, heben Sie die Auskommentierung der folgenden Zeile auf. // [System.Web.Script.Services.ScriptService] public class Service1 : System.Web.Services.WebService { [WebMethod] public string getMessage() { return "Mirco De Roni"; } } } Mirco De Roni - 78 - Zusammenfassung Microsoft Visual C# 2008 - Schritt für Schritt _____________________________________________________________________________________________ 24 Quellenverzeichnis Literatur Sharp, John, Microsoft Visual C# 2008 – Schritt für Schritt, 2008 Mirco De Roni - 79 - Zusammenfassung