Seminararbeit im Rahmen des DISCOURSE-Workshops 2004 Thema: C# Autor: Johannes Zapotoczky Email: [email protected] HU Berlin Wintersemester 2003/2004 C# - Eine Einführung Johannes Zapotoczky Inhalt: Einleitung............................................................................................................................. - 3 1. Überblick ......................................................................................................................... - 3 1.1. Merkmale, Features ................................................................................................. - 3 1.2. Einfaches C#-Programm .......................................................................................... - 3 1.3. Gliederung von C#-Programmen ............................................................................. - 4 1.4. Executable vs. DLL .................................................................................................. - 4 2. lexikalische Symbole ....................................................................................................... - 4 2.1. Namen, Schlüsselwerte, Namenskonventionen ....................................................... - 4 2.2. Zahlen und Zeichenketten........................................................................................ - 4 2.3. Kommentare............................................................................................................. - 5 3. Typen .............................................................................................................................. - 5 4. Ausdrücke ....................................................................................................................... - 6 5. Deklarationen .................................................................................................................. - 6 5.1. Deklarationsraum ..................................................................................................... - 6 5.2. Regeln ...................................................................................................................... - 6 5.3. Namespaces ............................................................................................................ - 6 5.4. Klassen, Interfaces, Structs...................................................................................... - 7 5.5. Blöcke und lokale Variablen ..................................................................................... - 7 6. Anweisungen................................................................................................................... - 7 6.1. Elementare Anweisungen, if-Anweisung, switch-Anweisung ................................... - 7 6.2. Schleifen................................................................................................................... - 7 6.3. Sprünge und return-Anweisung................................................................................ - 7 7. Klassen und Structs ........................................................................................................ - 8 7.1. Klassen und Structs ................................................................................................. - 8 7.2. Parameterübergabe ................................................................................................. - 8 7.3. Überladen von Methoden ......................................................................................... - 8 7.4. Konstruktoren/Destruktoren ..................................................................................... - 8 7.5. Properties ................................................................................................................. - 9 7.6. Überladene Operatoren............................................................................................ - 9 7.7. Unterschiede zu C++ und Java ................................................................................ - 9 8. Vererbung...................................................................................................................... - 10 8.1. Syntax, Allgemeines ............................................................................................... - 10 8.2. Abstrakte Klassen / Properties / Indexer ................................................................ - 10 8.3. Versiegelte Klassen................................................................................................ - 10 8.4. Klasse object (System.Object) ............................................................................... - 10 9. Interfaces....................................................................................................................... - 10 10. Delegates und Events ................................................................................................. - 11 11. Ausnahmen (Exceptions) ............................................................................................ - 11 12. Namespaces und Assemblies ..................................................................................... - 12 12.1. Namespaces ........................................................................................................ - 12 12.2. C#-Namespaces vs. Java-Packages.................................................................... - 12 12.3. Assemblies ........................................................................................................... - 12 13. Attribute ....................................................................................................................... - 13 14. Native Calls ................................................................................................................. - 13 15. Threads ....................................................................................................................... - 13 16. Generierte Dokumentation .......................................................................................... - 14 16.1. Spezialkommentare.............................................................................................. - 14 16.2. XML-Tags............................................................................................................. - 14 17. Base Class Library (BCL)............................................................................................ - 14 18. Neue Features in C# 2.0 ............................................................................................. - 15 Literaturverzeichnis ........................................................................................................... - 15 - -2- C# - Eine Einführung Johannes Zapotoczky Einleitung Das vorliegende Dokument ist im Rahmen des DISCOURSE-Workshops 2004 entstanden. Es gibt eine Einführung in C#, die die grundlegenden Konstrukte knapp zusammenfasst. Ziel war, ein Dokument zu erstellen, dass ohne großen Aufwand schnell einen Überblick über die Sprache und ihre Ähnlichkeiten zu Java verschafft. Da kaum Beispiele gegeben werden, und da die Kenntnis von Konzepten weitestgehend vorausgesetzt wird, richtet sich diese Einführung eher an Personen mit Programmiererfahrung. 1. Überblick 1.1. Merkmale, Features Generell ist eine hohe Ähnlichkeit von C# zu Java zu beobachten. Neben der Objektorientierung mit einfacher Vererbung äußert sich diese in Interfaces, Exceptions, Threads, Namespaces (in Java: packages), strenger Typprüfung, Garbage Collection, Reflections, dem dynamischen Laden von Code, sowie spezieller StringUnterstützung. Auch aus der C++-Welt sind einige Merkmale eingeflossen: das Überladen von Operatoren und die Zeigerarithmetik (in Unsafe Code), außerdem einige syntaktische Details. Daneben gibt es aber auch eine Reihe von neuen Merkmalen wie Referenzparameter, Objekte am Stack (Structs), Blockmatrizen, Enumerationen, ein uniformes Typsystem, gotos (!), Attribute, systemnahes Programmieren und Versionisierung. Zusätzlich gibt es auch noch einiges an sog. „syntactical sugar“, wie Komponentenunterstützung (properties, events), Delegates, Indexers, Operator Overloading, einen foreach-Operator, Boxing/Unboxing, u.v.a.m.. Zusammengefasst könnte man sagen, dass C# ungefähr 70% Ähnlichkeit zu Java, 10% zu C++, 5% zu Visual Basic hat und ca. 15% Neues enthält. 1.2. Einfaches C#-Programm Als minimalen Einstieg in die C#-Welt soll folgendes (unvermeidliches) HelloWorldBeispielprogramm dienen: File Hello.cs Das Übersetzen in C# erfolgt auf der Kommandozeile mit csc Hello.cs Die Ausführung erfolgt mit Hello class HelloWorld { Es ist zu sehen, dass Dateiname und static void Main() { Console.WriteLine("HelloWorld"); Klassenname nicht (wie z.B. in Java) } übereinstimmen müssen. } Die Hauptmethode muss jedoch immer Main heißen. Das HelloWorld-Programm verwendet den Namespace System und gibt auf die Konsole aus. using System; -3- C# - Eine Einführung Johannes Zapotoczky 1.3. Gliederung von C#-Programmen Ein Programm besteht aus verschiedenen Dateien, die unterschiedlichen Namespaces zugeordnet sind, die ihrerseits wieder verschiedene Klassen beinhalten können. Ist kein Namespace angegeben, wird der (namenlose) Standard-Namespace verwendet. Ein Namespace kann neben Klassen auch Structs, Interfaces, Delegates und Enums enthalten, sowie in verschiedenen Files „wiedereröffnet“ werden. Einfachster Fall eines Programms ist genau ein File mit genau einer Klasse. 1.4. Executable vs. DLL Verwendet man für ein Programm mehrere Dateien, so gibt es einerseits die Möglichkeit, die Dateien gemeinsam zu kompilieren (z.B. in der Form csc /target:exe file1.cs file2.cs). Andererseits ist es auch möglich, zunächst aus der einen Datei eine DLL zu erstellen, und diese dann beim Kompilieren der anderen einzubinden. Beispiel: zuerst: csc /target:library file1.cs dann: csc /reference:file1.dll file2.cs Beide Vorgehensweisen erzeugen letztendlich eine ausführbare Datei file2.exe. Der zweite Fall erlaubt jedoch das Erstellen der exe-Datei ohne die Quellen von file1. 2. lexikalische Symbole 2.1. Namen, Schlüsselwerte, Namenskonventionen Die Sytax eines Namens lässt sich einfach durch einen regulären Ausdruck beschreiben: Name = (letter | _ | @) {letter | digit | _} Für Namen ist der gesamte (?) Unicode erlaubt! Namen können auch UnicodeEscapesequencen enthalten (z.B. \u03c0 für π). Groß- und Kleinschreibung ist signifikant. Ein @ am Anfang dient zur Unterscheidung von Namen und Schlüsselwörtern (mit @ ist es ein Name, z.B. @if). So kann die Vielzahl von Schlüsselwörtern in C# (76 im Gegensatz zu 47 in Java) mit Hilfe des @-Zeichens auch als Namen verwendet werden. Die Namenskonventionen sind indirekt aus der Base Class Library ableitbar. Bzgl. Groß-/Kleinschreibung gilt, dass jeder Wortanfang groß geschrieben wird (z.B. SetValue). Anfangsbuchstaben werden immer großgeschrieben, Ausnahmen sind solche Variablen, Konstanten und Felder, die man nicht von außen sieht. Bzgl. des ersten Wortes gilt, dass void-Methoden mit einem Verb beginnen, alles andere mit einem Substantiv. Lediglich enum-Konstanten oder bool-Members dürfen mit einem Adjektiv beginnen. 2.2. Zahlen und Zeichenketten Ganze Zahlen sind aus folgenden Syntaxregeln aufgebaut: DecConstant = digit {digit} {IntSuffix} HexConstant = 0x hexDigit {hexDigit} {IntSuffix} IntSuffix = u | U | l | L Die Typen setzen sich danach auf folgende Weise zusammen: ohne Suffix: kleinster aus int, uint, long, ulong; Suffix u, U: kleinster aus uint, ulong; Suffix l, L: kleinster aus long, ulong. -4- C# - Eine Einführung Johannes Zapotoczky Gleitkommazahlen sind nach folgendem Schema aufgebaut: RealConstant = [Digits] [.[Digits]] [Exp] [RealSuffix] Digits = digit {digit} Exp = (e | E) [+|-] Digits RealSuffix = f | F | d | D | m | M Die Typen setzen sich auf folgende Weise zusammen: ohne Suffix: double; Suffix f, F: float; Suffix d, D: double; Suffix m, M: decimal Zeichenketten sind folgendermaßen aufgebaut: CharConstant = char StringConstant = “{char}“ char ist dabei ein beliebiges Zeichen außer Ende-Hochkomma, Zeilenende oder \ 2.3. Kommentare Wie in Java oder C/C++ gibt es Zeilenende-Kommentare (//) und Klammerkommentare (/*... */). Darüber hinaus gibt es spezielle Dokumentationskommentare, die mit /// eingeleitet werden (und jeweils nur bis zum Zeilenende gelten (dazu mehr in 16.)). 3. Typen In C# sind alle Typen zu object kompatibel. Sie können object-Variablen zugewiesen werden und verstehen object-Operationen. Typen lassen sich untergliedern in Werttypen, Referenztypen und Zeiger. Zu den Werttypen gehören neben den einfachen Typen (z.B. char, int) auch Enums und Structs. Referenztypen umfassen Klassen, Interfaces, Arrays und Delegates. Enumerationen sind Aufzählungstypen aus benannten Konstanten. Sie erlauben Vergleiche und die Operationen +,-, ++,--, &, |, und ~. Enumerationen erben sämtliche Eigenschaften von object. Die Klasse System.Enum stellt Operationen auf Enumerationen bereit. Arrays funktionieren weitgehend wie aus Java/C++ bekannt. Eine Besonderheit ist der kompakte Rechteckzugriff auf Matrizen. Nützliche Array-Operationen stellt die Klasse System.Array zur Verfügung. Variabel lange Arrays und assoziative Arrays lassen sich mit ArrayList und Hashtable realisieren (zu finden in System.Collections). Strings können als Standardtyp string verwendet werden. Sie sind nicht modifizierbar (StringBuilder). Die Klasse System.String definiert viele String-Operationen. Structs sind Werttypen, die Objekte werden durch Deklaration direkt am Stack angelegt. Sie dürfen keinen parameterlosen Konstruktor deklarieren (dieser existiert standardmäßig und kann auch verwendet werden). Klassen sind im Gegensatz zu Structs Referenztypen, Objekte werden am Heap angelegt. Die Deklaration parameterloser Konstruktoren ist erlaubt. Außerdem unterstützen sie Vererbung und können Destruktoren haben. Die Basisklasse aller Referenztypen ist System.Object. Interessant dabei ist, dass auch die Werttypen zu object kompatibel sind. Bei der Zuweisung eines Wertes an ein Referenzobjekt, wird der Wert in ein Heap-Objekt eingepackt, und bei entsprechender Anweisung wieder ausgepackt. In C# nennt man diesen Mechanismus Boxing bzw. Unboxing. -5- C# - Eine Einführung Johannes Zapotoczky 4. Ausdrücke Operatoren und Vorrangregeln sind in C# sehr ähnlich zu Java und werden hier nicht näher behandelt. Arithmetische Ausdrücke haben numerische oder auf char basierende Operandentypen. Ergebnistyp ist immer der kleinste numerische Typ, der beide Operandentypen einschließt, aber zumindest int. Ausnahmen sind hier nur uint (wird zu long), sowie ulong (kann mit keinem anderen Operandentyp in einem arithm. Ausdruck verwendet werden). Vergleichsausdrücke haben als Ergebnistyp immer bool. Operandentypen können numerisch und auf char oder enum basierend sein. Bei == und != zusätzlich auch Referenzen oder auf bool basierend. Überläufe werden normalerweise nicht(!) erkannt. Sie müssen per Exception abgefangen werden oder explizit als Compiler-Option (csc /checked File.cs) eingeschaltet werden. 5. Deklarationen 5.1. Deklarationsraum Den zu einer Deklaration zugehörigen Programmbereich nennt man Deklarationsraum. In C# gibt es die folgenden Deklarationsräume: Namespace: Deklaration von Klassen, Interfaces, Structs, Enums, Delegation Klasse, Interface, Struct: Deklaration von Feldern, Methoden, ... Enum: Deklaration von Enumerationskonstanten Block: Deklaration lokaler Variablen 5.2. Regeln Folgende Deklarationsregeln müssen in C# beachtet werden: - Kein Name darf in einem Deklarationsraum auf gleicher Ebene mehrfach deklariert werden. - Er darf aber in inneren Deklarationsbereichen neu deklariert werden (außer in inneren Anweisungsblöcken). Sichtbarkeitsregeln: - Ein Name ist in seinem ganzen Deklarationsraum sichtbar, lokale Variablen jedoch erst ab ihrer Deklaration. - Deklarationen in inneren Deklarationsräumen verdecken gleichnamige Deklarationen aus äußeren Deklarationsräumen. - Kein Name ist außerhalb seines Deklarationsraums sichtbar. - Die Sichtbarkeit kann mit Attributen eingeschränkt werden (private, protected, internal, ...) 5.3. Namespaces Namespaces in C# sind vergleichbar zu Packages in Java. Gleichnamige Namespaces in verschiedenen Dateien bilden einen gemeinsamen Deklarationsraum. Eingeschachtelte Namespaces bilden einen eigenen Deklarationsraum. Fremde Namespaces können durch das Schlüsselwort using importiert werden, oder aber als Qualifikation vor den verwendeten Namen geschrieben werden. Viele Programme arbeiten mit dem Namespace System. -6- C# - Eine Einführung Johannes Zapotoczky 5.4. Klassen, Interfaces, Structs In Klassen und Structs können Felder und Konstanten, Methoden, Konstruktoren und Destruktoren, Properties, Indexers, Events, überladene Operatoren und geschachtelte Typen deklariert werden. Da der Deklarationsraum der Basisklasse nicht zum Deklarationsraum der Unterklasse gehört, sind gleichnamige Deklarationen in der Unterklasse möglich. Interfaces erlauben die Deklaration von Methoden, Properties, Indexers und Events. In Enums können nur Enumerationskonstanten deklariert werden. 5.5. Blöcke und lokale Variablen Wie auch z.B. in Java unterscheidet man Methoden-, geschachtelte und SchleifenBlöcke. Dabei schließt der Deklarationsraum eines Blocks die Deklarationsräume geschachtelter Blöcke ein. Wichtig zu beachten ist auch, dass formale Parameter zum Deklarationsraum des Methodenblocks gehören. Lokale Variablen dürfen in inneren Blöcken deklariert werden – sie müssen aber überschneidungsfrei zu übergeordneten Blöcken sein. 6. Anweisungen 6.1. Elementare Anweisungen, if-Anweisung, switch-Anweisung Die elementaren Anweisungen (Zuweisung, Methodenaufruf) sind in C# analog zu Java und werden hier nicht näher betrachtet. Die switch-Anweisung birgt einige Besonderheiten: • einem Code-Block können mehrere Bedingungen zugeordnet werden, dies wird auf folgende Weise notiert: case bed1: case bed2: Anweisungen; break; • jede case-Anweisung muss mit break (oder return, goto, throw) enden, es ist kein Fall-Through erlaubt. • falls keine Marke passt, wird default verwendet, fehlt default, wird nach dem switch normal weitergemacht. • es sind Sprünge in switch-Statements erlaubt, andere Bedingungen können direkt angesteuert werden (Schlüsselwort goto). 6.2. Schleifen Schleifen (while, do while, for) funktionieren wie in Java und werden hier nicht näher behandelt. Eine Besonderheit in C# ist das foreach-Statement. Damit lässt sich über beliebige Collections und Arrays iterieren. 6.3. Sprünge und return-Anweisung Zum Aussprung aus Schleifen und switch-Anweisungen kann break; verwendet werden. In Schleifen kann continue; verwendet werden, um an den Schleifenanfang zurückzuspringen. In switch-Statements kann goto verwendet werden, um eine case-Marke anzuspringen. Zum Aussprung aus Methoden kann (wie auch in Java) return verwendet werden. -7- C# - Eine Einführung Johannes Zapotoczky 7. Klassen und Structs 7.1. Klassen und Structs Der Inhalt von Klassen und Structs kann mehreren Zielen dienen. Objektorientierung wird durch Felder und Konstanten, Methoden, sowie Konstruktoren und Destruktoren unterstützt. Die Komponentenorientierte Programmierung unterstützen Properties und Events. Nicht zuletzt der Annehmlichkeit dienen Indexers sowie überladene Operatoren. Darüber hinaus gibt es noch geschachtelte Typen. Klassen zeichnen sich dadurch aus, dass Objekte am Heap angelegt werden (Klassen sind also Referenztypen). Sie müssen immer mit new erzeugt werden und können erben, vererben und Interfaces implementieren. Bei Structs werden Objekte am Stack angelegt (Structs sind Werttypen). Sie können, müssen aber nicht mit new erzeugt werden (sind dann aber auch nicht initialisiert). Ihre Felder dürfen bei der Deklaration nicht initialisiert werden. Deklarierte Konstruktoren müssen mindestens einen Parameter haben. Structs können weder erben noch vererben, dafür aber Interfaces implementieren. 7.2. Parameterübergabe In C# gibt es drei Parameterübergabemechanismen: • call by value (Eingangsparameter) Der formale Parameter ist eine Kopie des aktuellen Parameters (= beliebiger Ausdruck). • call by reference (Übergangsparameter) Der formale Parameter ist ein anderer Name für den aktuellen Parameter (es wird die Adresse des akt. Parameters übergeben). Der aktuelle Parameter muss eine Variable sein, • call by result (Ausgangsparameter) Wie Referenzparameter, allerdings wird er zur Rückgabe von Werten verwendet. Darf in der entsprechenden Methode nicht verwendet werden, bevor ihm ein Wert zugewiesen wurde. C# erlaubt, dass die letzten n Parameter beliebig viele Werte eines bestimmten Typs sind (durch den sog. Params-Parameter). 7.3. Überladen von Methoden Methoden einer Klasse dürfen gleich heißen, wenn sie eine unterschiedliche Anzahl von Parametern haben, oder überhaupt unterschiedliche Parametertypen haben, bzw. auch wenn sie unterschiedliche Parameterarten (wie z.B. value, ref/out) haben. Methoden dürfen sich jedoch nicht nur im Funktionstyp, durch einen paramsParameter oder durch ref gegenüber out unterscheiden! 7.4. Konstruktoren/Destruktoren In C# gibt es sowohl für Klassen als auch für Structs Konstruktoren. Bei Klassen dürfen Konstruktoren überladen werden. Konstruktoren können sich gegenseitig aufrufen (allerdings im Kopf des Kontruktors, nicht im Rumpf wie bei Java). Die zu den Felddeklarationen gehörigen Initialisierungen werden vor dem Konstruktor aufgerufen. Hat eine Klasse keinen Konstruktor, so wird automatisch ein parameterloser default-Konstruktor angelegt. Hat eine Klasse jedoch bereits einen Konstruktor, so wird kein default-Konstruktor mehr angelegt! -8- C# - Eine Einführung Johannes Zapotoczky Jeder Struct hat einen parameterlosen default-Konstruktor, der alle Felder initialisiert (auch wenn es andere Konstruktoren gibt). Aus diesem Grund dürfen Structs keinen expliziten parameterlosen Konstruktor haben. Struct-Konstruktoren müssen sämtliche Felder des Structs initialisieren. Sowohl Klassen als auch Structs können statische Konstruktoren haben. Diese müssen parameterlos sein und haben keine Sichtbarkeitsangabe (public/private). Pro Klasse oder Struct darf es nur einen statischen Konstruktor geben. Ein statischer Konstruktor wird genau einmal ausgeführt, und zwar bevor das erste Objekt der Klasse erzeugt oder das erste Mal auf eine statische Variable der Klasse zugegriffen wird. Destruktoren in C# entsprechen Finalizern in Java. Hat ein Objekt einen solchen, so wird er aufgerufen, bevor der Garbage Collector das Objekt freigibt. Structs dürfen keine Destruktoren haben (Grund unklar). Insgesamt sind im C#-Kontext Destruktoren eher gefährlich und unnötig. 7.5. Properties Eine weitere Besonderheit von C# sind Properties. Properties sind im Prinzip eine syntaktische Kurzform von get/set-Methoden und können wie Felder benutzt werden (statt einer echten Zuweisung wird dann die set/get-Methode verwendet). Der Zugriff geht durch Inlining der get/set-Aufrufe genauso schnell wie normaler Feldzugriff. Es kann get oder set fehlen – so sind write-only und read-only-Daten möglich. Sämtliche Zuweisungsoperatoren können mit Properties verwendet werden. Insgesamt sind Properties ein sehr nützliches Konstrukt. Sie ermöglichen neben einer Validierung beim Zugriff, dass die Benutzersicht und die Implementierung der Daten unterschiedlich sein können und sind damit quasi ein Ersatz für Felder in Interfaces. Darüber hinaus sind Daten über Reflection deutlich als Property erkennbar – dies ist ein wichtiges Feature für die komponentenorientierte Programmierung. Mit dem get/set-Konzept der Properties arbeiten auch Indexer – sie sind nichts anderes als programmierbare Operatoren zum indizieren einer Folge. 7.6. Überladene Operatoren Es ist auch möglich Operatoren zu überladen – man erzeugt dazu eine statisch Methode, die wie ein Operator verwendet werden kann (Anwendungsbeispiel: den +Operator für die Addition von Brüchen überladen). Wichtig hierbei ist, dass man bei Überladung von && und ||, auch &, |, true und false überladen muss. Auch Konversionsoperatoren können überladen werden. Dabei unterscheidet man zwischen impliziter Konversion (kein Genauigkeitsverlust, Konversion immer möglich (z.B. long = int)) und expliziter Konversion (Laufzeitprüfung nötig, evtl. Genauigkeitsverlust durch Abschneiden (z.B. int = (int) long)). 7.7. Unterschiede zu C++ und Java In C# gibt es keine anonymen Klassen wie in Java. Außerdem auch (noch) keine Templates wie in C++. Darüber hinaus gibt es eine andere Standardsichtbarkeit für Members (C#: private, Java: package) und Klassen (C#: internal, Java: package). -9- C# - Eine Einführung Johannes Zapotoczky 8. Vererbung 8.1. Syntax, Allgemeines Syntaktisch wird die Vererbung in C# durch “:“ angezeigt (statt z.B. extends in Java). Die Konstruktoren der Superklasse werden nicht vererbt. Klassen können nur von einer (nicht mehreren) Klasse erben – und grundsätzlich nicht von Structs. Structs wiederum können gar nicht erben, aber – genau wie auch Klassen – mehrere Interfaces implementieren. Sämtliche Klassen sind direkt oder indirekt von object abgeleitet, Structs sind über Boxing ebenfalls zu object kompatibel. Überschreibbare Methoden müssen in C# als virtual deklariert werden, überschreibende Methoden müssen als override deklariert werden. Überschreibende Methoden müssen dieselbe Schnittstelle haben wie die überschriebenen Methoden, d.h. die gleichen Parameteranzahlen und Parametertypen (inkl. Funktionstyp) und auch die gleichen Sichtbarkeitsattribute. Properties und Indexer können ebenfalls überschrieben werden (mit virtual/override). Statische Methoden jedoch können nicht überschrieben werden. 8.2. Abstrakte Klassen / Properties / Indexer Abstrakte Methoden haben keinen Anweisungsteil und sind implizit virtual. Wenn eine Klasse abstrakte Methoden enthält (d.h. deklariert oder auch erbt und nicht überschreibt), so muss sie ebenfalls als abstract deklariert werden. Die Objekterzeugung aus abstrakten Klassen ist nicht möglich. 8.3. Versiegelte Klassen Versiegelte Klassen werden in C# mit dem Schlüsselwort sealed gekennzeichnet. Sealed-Klassen können nicht erweitert werden (entspricht dem final in Java), können aber selbst Unterklassen sein. Override-Methoden können auch einzeln als sealed deklariert werden. Zweck dieser Versiegelung ist es mehr Sicherheit herzustellen (versehentliches Erweitern der Klasse wird verhindert) und zu erlauben, dass Methoden u.U. statisch gebunden aufgerufen werden. 8.4. Klasse object (System.Object) Die Klasse System.object enthält einige Methoden, die direkt zu verwenden sind (GetType und MemberwiseClone), sowie einige in Unterklassen zu überschreibende virtual-Methoden (Equals, ToString, GetHashCode). 9. Interfaces Interfaces in C# sind Schnittstellen rein abstrakter Natur und enthalten keinen Code. Sie dürfen nur Methoden, Properties, Indexers und Events enthalten (also keine Felder, Konstanten, Konstruktoren, Destruktoren, Operatoren oder innere Typen). Interface-Mambers sind implizit public abstract (virtual). Interface-Members dürfen nicht static sein. Sowohl Klassen als auch Structs können mehrere Interfaces implementieren, Interfaces können andere Interfaces erweitern. Interfaces in C# sind (ähnlich wie in Java) Konstrukte, die zur Kompensation der fehlenden Mehrfachvererbung dienen: eine Klasse kann beliebig viele Interfaces implementieren. Umgekehrt gelten für Interfaces einige Vorschriften, die befolgt werden müssen: - 10 - C# - Eine Einführung Johannes Zapotoczky • Jede geerbte Interface-Methode muss implementiert oder von einer anderen Klasse geerbt werden. • Beim Überschreiben von Interface-Methoden darf man kein override angeben, außer man überschreibt eine von einer Klasse geerbte Methode. • Ein Interface darf durch eine abstrakte Klasse implementiert werden. • Wenn Subklassen einer Klasse, die ein Interface implementiert eine Methode überschreiben sollen, so muss man die Methode als virtual deklarieren. Vorsicht geboten ist auch wenn zwei geerbte Interfaces eine gleichnamige Methode enthalten: dies führt leicht zu Name Clashes. 10. Delegates und Events Delegates sind Methodentypen, die es erlauben Methoden an delegate-Variablen zuzuweisen. In der Folge kann jede passende Methode einer Delegate-Variablen zugewiesen werden. Delegate-Variablen darf auch null zugewiesen werden. Jedoch darf die Delegate-Variable nicht aufgerufen werden, wenn sie keinen Delegate-Wert enthält (sonst Exception). Delegate-Variablen können in Datenstrukturen gespeichert werden, oder als Parameter übergeben werden. Die Delegate-Variable speichert sowohl die Methode als auch das Empfängerobjekt. Delegate-Variablen können auch mehrere Werte zugleich aufnehmen, jedoch ist hierbei zu beachten, dass ein Multicast-Delegate der eine Funktion ist, den letzten Funktionswert liefert. Hat ein Multicast-Delegate out-Parameter, wird der Parameter des letzten Aufrufs gebildet. Ref-Parameter werden durch alle Methoden durchgereicht. In Java übernehmen Interfaces die Rolle des Delegates, allerdings ist die Behandlung dadurch etwas aufwändiger, ein Aufruf statischer Methoden ist gar nicht möglich. Neben Delegates gibt es noch das Konzept von Events, die eine bessere Kapselung ermöglichen (nur die Klasse, die das Event deklariert, darf es auslösen). EventFelder dürfen darüber hinaus von außen nur mit += und -= modifiziert werden. 11. Ausnahmen (Exceptions) Die try-Anweisung in C# ähnelt syntaktisch stark dem Java-Pendant. Catch-Klauseln werden in der Reihenfolge ihrer Aufschreibung getestet, die optionale finally-Klausel wird immer ausgeführt. Ein Exception-Name in der catch-Klausel ist nicht notwendig. Der Exception-Typ muss immer von System.Exception abgeleitet sein – fehlt er, wird System.Exception angenommen. Die Auslösung von Ausnahmen erfolgt allgemein implizit durch eine ungültige Operation, explizit durch die throw-Anweisung, indirekt auch über den Aufruf einer Methode, die eine Ausnahme auslöst, aber nicht behandelt. Delegates werden bei der Suche nach catch-Klauseln wie normale Methoden behandelt. Grundsätzlicher Unterschied zu Java ist, dass Ausnahmen in C# nicht behandelt werden müssen. Es gibt keine Unterscheidung zwischen Checked Exceptions und Unchecked Exception (Grund ist unklar). Dies ist zwar bequemer, führt aber zu weniger robuster und zu unsicherer Software. - 11 - C# - Eine Einführung Johannes Zapotoczky 12. Namespaces und Assemblies 12.1. Namespaces Namespaces wurden schon in 5.3. vorgestellt, sie sind das C#-Konzept für eine mit Java-Packages vergleichbaren Funktionalität. In C# kann eine Datei mehrere Namespaces haben – umgekehrt kann sich (und das ist die Regel) ein Namespace über mehrere Dateien erstrecken. Gleichnamige Namespaces bilden einen gemeinsamen Deklarationsraum. Typen, die in keinem Namespace enthalten sind, kommen in den default-Namespace (Global Namespace). Bei der Verwendung fremder Namespaces muss darauf geachtet werden, dass sie entweder mit using importiert, oder aber als Qualifikation vor den fremden Namen geschrieben werden müssen. 12.2. C#-Namespaces vs. Java-Packages Da dieses Konzept auch in Java in ähnlicher Weise verwendet wird, sollen hier die konkreten Unterschiede aufgezeigt werden. Während in C# eine Datei mehrere Namespaces enthalten kann, kann in Java eine Datei nur eine Package-Angabe haben. Außerdem werden in C# Namespaces nicht wie in Java auf Verzeichnisse abgebildet. Darüber hinaus werden in Java Klassen importiert, während in C# Namespaces importiert werden. Analog werden die Namespaces auch in andere Namespaces importiert – nicht wie in Java: dort werden die Klassen in Dateien importiert. Als Besonderheit kann man in C# Alias-Namen verwenden. Das Konzept der Sichtbarkeit innerhalb eines Pakets (bei Java Standard) wiederum gibt es in C# nur in Assemblies, nicht in Namespaces. 12.3. Assemblies Assemblies sind Laufzeiteinheiten aus mehreren Typen und sonstigen Ressourcen (z.B. Icons). Wichtige Begriffe im Assembly-Konzept sind die Auslieferungseinheit (kleinere Teile können nicht ausgeliefert werden) und die Versionisierungseinheit (alle Typen eines Assembly haben die gleiche Versionsnummer). Ein Assembly kann mehrere Namespaces enthalten, umgekehrt kann ein Namespace auch auf mehrere Assemblies aufgeteilt sein. Ein Assembly kann auch aus mehreren Dateien bestehen, die durch ein „Manifest“ (Inhaltsverzeichnis) zusammengehalten werden. Vergleichbare (mit Einschränkungen) Konzepte sind jar-Files in Java oder Komponenten in .NET. Jede Kompilation erzeugt ein Assembly oder ein Modul aus verschiedenen Sourcefiles, Modulen und Bibliotheken. Weitere Module oder Ressourcen können mit dem Assembly-Linker (al) eingebunden werden. Dies ist ein weiterer Unterschied zu Java, in dem für jede Klasse ein .class-File erzeugt wird. Assemblies können zur Laufzeit eingebunden werden, ebenso gibt es eine Versionisierung (die Versionsnummer wird bei der Kompilation gespeichert – später wird die Bibliothek übereinstimmiger Version geladen). - 12 - C# - Eine Einführung Johannes Zapotoczky 13. Attribute Attribute in C# dienen als benutzerdefinierte Informationen über Programmelemente. Man kann sie an Typen, Members, etc. anhängen, und sie erweitern die vordefinierten Attribute wie public, sealed oder abstract. Attribute werden als Klassen implementiert, die von System.Attribute abgeleitet sind. Einige CLR-Services (Serialisierung, Remoting, COM-Interoperatibilität) arbeiten mit dem Attribut-Konzept, da die Attribute zur Laufzeit abgefragt werden können. Es sind immer auch mehrere Attribute zuordenbar. Darüber hinaus sind auch Attribute mit Parametern möglich. Es ist insbesondere auch möglich eigene Attribute zu definieren, sodass man sich diese Funktionalität auf multiple Weise zunutze machen kann. 14. Native Calls Es ist in C# möglich, Funktionen von Win32-DLLs aufzurufen. Grundlage dieser Funktionalität ist der Namespace System.Runtime.InteropServices, der dazu entsprechend mit using importiert werden muss. Der eigentliche Import der DLL erfolgt als Deklaration innerhalb der verwendeten Klasse mit der Syntax [DllImport(“lib.dll“)]. Zusätzlich gibt es noch weitere Attribute, die den Import weiter spezifizieren (EntryPoint, CharSet, ExactSpelling, SetLastError, CallingConvention). Die gewünschte externe Methode muss mitsamt ihrer Signatur bekannt sein und ebenfalls angegeben werden: z.B. static extern MethodenName(typ1 v1, typ2 v2);. Der Aufruf kann nun einfach wie bei einer normalen Methode erfolgen. C#-Typen werden automatisch auf die richtigen Win32-Typen abgebildet. Diese Standard-Abbildung kann mit Hilfe von sog. Parameter-Marshaling umgangen werden (z.B. statt nur string s kann man folgendes Konstrukt verwenden: [MarshalAs(UnmanagedType.LPStr)] string s, d.h. s wird nun in LPStr abgebildet). Darüber hinaus ist auch noch die Übergabe von Klassen und Structs möglich (Schlüsselwort StructLayout(LayoutKind.<kind>, Näheres s. Literaturangaben). 15. Threads Während in Java die Behandlung von Threads relativ restriktiv ist (eigener Thread muss Unterklasse von Thread sein, Thread-Aktionen müssen in Methode run stecken, kein Stop (bzw. deprecated weil gefährlich)), ist diese in C# deutlich komfortabler. Es ist keine Unterklasse von Thread notwendig, und es können beliebige Methoden als Thread gestartet werden. Es gibt eine ThreadAbortException, welche abgefangen werden kann (wird allerdings am Ende von catch automatisch wieder aufgelöst, außer man ruft ResetAbort auf). Es werden alle finally-Blöcke ausgeführt, auch das Exit aus einem Monitor. Es gibt keine synchronized-Methoden wie in Java, aber ein entsprechendes Attribut ([MethodImpl(MethodImplOptions.Synchronized)]), mit dem die Synchronisation sichergestellt werden kann. - 13 - C# - Eine Einführung Johannes Zapotoczky 16. Generierte Dokumentation 16.1. Spezialkommentare Ähnlich zu Javadoc in Java gibt es in C# Spezialkommentare, aus denen automatisch Dokumentation generiert werden kann. Diese werden mit /// eingeleitet und sind nur zeilenweit gültig. Die Übersetzung erfolgt mit dem Kommando csc /doc:File.xml File.cs Bei der Übersetzung wird auf Konsistenz und Vollständigkeit geprüft. D.h. wenn Parameter dokumentiert werden, dann müssen alle Parameter dokumentiert werden, außerdem müssen Namen von Programmelementen korrekt geschrieben werden. Darüber hinaus werden Querverweise in qualifizierte Namen expandiert. Als Ergebnis entsteht eine XML-Datei mit kommentierten Programmelementen, die dann mit z.B. XSL weiterverarbeitet werden kann. 16.2. XML-Tags Es sind beliebige eigene XML-Tags erlaubt (z.B. <author>, <version>, etc.). Bei den vordefinierten Tags unterscheidet man zwischen Tags, die allein stehend sind, und solchen die Teil einer anderen Beschreibung sind. Als allein stehende Tags gibt es: <summary> Kurzbeschreibung eines Programmelements </summary> <remarks> Ausführliche Beschreibung eines Programmelements </remarks> <example> Beliebiger Beispieltext (Aufrufbeispiel, etc.) </example> <param name=“ParamName“> Bedeutung des Parameters </param> <returns> Bedeutung des Rückgabewerts </returns> <exception [cref=”ExceptionType”]> (bei Dok. Einer Methode:) Beschreibung der Exception </exception> Tags, die Teil einer anderen Beschreibung sind: <code> Mehrzeilige Codestücke </code> <c> kurze Codestücke im Quelltext </c> <see cref=“ProgramElement“> Name des Querverweises </see> <paramref name=”ParamName”> Name des Parameters </paramref> 17. Base Class Library (BCL) In der Base Class Library sind viele nützliche Klassen bereits vordefiniert (Ähnlichkeit zur Java-API). Zu erwähnen sind insbesondere die Klasse Math (für mathematische Operationen und Anwendungen), die Klasse Random (Generator für Zufallszahlen), die Klasse String (Menge von Operationen auf Strings), die Klasse Convert (Konversionen zwischen String und numerischen Typen), die umfangreichen CollectionKlassen (Array, ArrayList, ListDictionary, Hashtable, SortedList, Stack, Queue, BitArray, BitVector32, sowie der Iterator IEnumerable), die Stream-, Reader- und WriterKlassen und die Klassen File und FileInfo sowie Directory und DirectoryInfo. Um selbst BCL-„freundliche“ Klassen zu schreiben, sollte man darauf achten, die Methoden Equals, ToString und GetHashCode, sowie die Operatoren == und != zu überschreiben. Ggf. sind auch noch die Interfaces IClonable und ICompareable zu implementieren. - 14 - C# - Eine Einführung Johannes Zapotoczky 18. Neue Features in C# 2.0 Für die nächste C#-Version (2.0) sind einige neue Features vorgesehen, die hier nur kurz vorgestellt werden: • Generische Typen: Generizität hat den Vorteil einer homogenen Datenstruktur mit Typprüfung zur Compilezeit. Darüber hinaus kann die Effizienz gesteigert werden (kein Boxing, keine Typumwandlungen). Generizität gibt es auch in Ada, Eiffel, C++ (Templates) und wird auch in Java 1.5 eingeführt. • Iteratoren Das Iterator-Konzept, das es ja schon gibt, wird funktional und strukturell stark erweitert. • Vereinfachte Delegate-Erzeugung • Anonyme Methoden Anonyme Methoden erlauben, den Methodencode in-place anzugeben – so ist keine Deklaration einer benannten Methode nötig. • Partielle Typen Darunter versteht man Klassen aus mehreren Teilen. Zweck dieses Features ist, dass Teile nach Funktionalitäten gruppiert werden können. So können verschiedene Entwickler gleichzeitig an derselben Klasse arbeiten. Oder es kann z.B. der erste Teil maschinengeneriert, der zweite handgeschrieben sein. • Statische Klassen (dürfen nur statische Felder und Methoden enthalten) Literaturverzeichnis [1] [2] [3] [4] [5] C# - Die neue Sprache für .NET, H. Mössenböck http://www.ssw.uni-linz.ac.at/Teaching/Lectures/CSharp/Tutorial/ C# im Vergleich mit Java und C++, Michael Kühne, Patrick Frahm http://stud.fh-wedel.de/~ia4415/einfuehrung.html C# - Die neue Sprache für Microsofts .NET-Plattform, Eric Gunnerson, Galileo Press 2001-2002. C#, Golo Haas http://www.guidetocsharp.de/csharp/index.html C#: Spracheigenschaften und Vergleich mit Java, Thomas Lehr http://www.fh-wedel.de/~si/seminare/ws02/Ausarbeitung/4.csharp/csharp0.htm - 15 -