C#-Introduction - Hu

Transcription

C#-Introduction - Hu
Seminararbeit im Rahmen des DISCOURSE-Workshops 2004
Thema:
C#
Autor: Johannes Zapotoczky
Email: jzapotoc@informatik.hu-berlin.de
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 -