Kapitel 35 C# im Vergleich zu anderen Sprachen
In diesem Kapitel soll C# mit anderen Sprachen verglichen
werden. C#, C++ und Java gehen auf dieselben Wurzeln zurück und
ähneln sich daher stärker als viele andere Sprachen. Visual Basic
ist C# nicht so ähnlich wie die zuvor genannten Sprachen, weist
jedoch immer noch einige übereinstimmende syntaktische Elemente
auf.
In einem gesonderten Abschnitt dieses Kapitels werden
die .NET-Versionen von Visual C++ und Visual Basic besprochen,
da diese sich von ihren jeweiligen Vorgängerversionen ebenfalls
unterscheiden.
35.1 Unterschiede zwischen C# und C/C++
 
Der C#-Code wird C- und C++-Programmierern vertraut vorkommen, dennoch gibt
es einige wesentliche und verschiedene weniger bedeutende Unterschiede.
Im Folgenden erhalten Sie einen Überblick über diese Unterschiede.
Einen detaillierteren Vergleich finden Sie im entsprechenden Microsoft-Whitepaper,
C# for the C++ Programmer.
35.1.1 Eine verwaltete Umgebung
 
C# wird in der .NET-Laufzeitumgebung ausgeführt.
Dies bedeutet nicht nur, dass viele Vorgänge nicht der Steuerung
des Programmierers unterliegen, gleichzeitig wird auch ein brandneuer
Satz Frameworks bereitgestellt. Alles in allem ergeben sich hierdurch
verschiedene Änderungen.
|
Das Löschen von Objekten
erfolgt über die Speicherbereinigung und wird ausgeführt,
wenn das Objekt nicht länger benötigt wird. Destruktoren (= Finalisierungsroutinen) können zur Durchführung
von Bereinigungsaufgaben eingesetzt werden, dies jedoch nicht im gleichen
Umfang wie bei C++-Destruktoren. |
|
In C# gibt es keine Zeiger.
Nun, es gibt Zeiger im unsafe-Modus, diese werden jedoch
selten eingesetzt. Statt dessen werden Verweise verwendet, die denen
von C++ ähneln, jedoch nicht alle der C++-Einschränkungen
aufweisen. |
|
Quellen werden in Assemblierungen
kompiliert, die sowohl den kompilierten Code (ausgedrückt in der
.NET-Zwischensprache IL) als auch Metadaten zur Beschreibung des kompilierten
Codes enthalten. Alle .NET-Sprachen fragen über die Metadaten die
gleichen Informationen ab, wie sie in den C++-.h-Dateien
enthalten sind; die include-Dateien fallen weg. |
|
Das Aufrufen von systemeigenem
Code ist etwas zeitaufwendiger. |
|
Es ist keine C/C++-Laufzeitbibliothek vorhanden. Die hiermit ausgeführten Operationen,
beispielsweise die Zeichenfolgenbearbeitung, E/A-Operationen und andere Routinen, können mit dem .NET-Laufzeitsystem
ausgeführt werden und befinden sich in dem Namespace, dessen Name
mit System beginnt. |
|
Anstelle einer Fehlerrückgabe
wird die Ausnahmebehandlung verwendet. |
35.1.2 .NET-Objekte
 
C#-Objekte gehen allesamt auf die Basisklasse object
zurück, daher ist nur eine Einfachvererbung von Klassen möglich,
obwohl das mehrfache Implementieren von Schnittstellen zulässig
ist.
Kleine, schlanke Objekte, beispielsweise Datentypen,
können als Strukturen deklariert werden (so genannte Wertetypen),
d. h., sie werden nicht dem Heap, sondern einem Stack zugeordnet.
C#-Strukturen und andere Wertetypen (einschließlich
der integrierten Datentypen) können in Situationen eingesetzt werden,
in denen ein Objektboxing erforderlich ist. Bei diesem Vorgang werden die
Werte in einen dem Heap zugeordneten Wrapper kopiert, der mit den dem
Heap zugeordneten Objekten kompatibel ist (auch Verweisobjekte genannt).
Dies vereinheitlicht das Typensystem und ermöglicht die Verwendung
von Variablen als Objekte. Gleichzeitig entsteht kein Overhead, wenn
eine Vereinheitlichung nicht erforderlich ist.
C# unterstützt Eigenschaften und Indizierer
zum Trennen des Benutzermodells eines Objekts von der Objektimplementierung und
unterstützt Zuweisungen und Ereignisse zum Kapseln der Funktionalität
von Zeigern und Rückrufen.
C# stellt das params-Schlüsselwort
bereit, um eine den vararg-Funktionen ähnliche Unterstützung
zu bieten.
35.1.3 C#-Anweisungen
 
C#-Anweisungen ähneln den C++-Anweisungen in vielerlei Hinsicht. Es gibt zwei merkliche Unterschiede:
|
Das new-Schlüsselwort bedeutet »Abrufen einer neuen Kopie von«.
Das Objekt ist dem Heap zugeordnet, wenn es sich um einen Verweistyp
handelt, und ist dem Stack oder intern zugeordnet, wenn es sich um einen
Wertetyp handelt. |
|
Alle Anweisungen, mit
denen eine boolesche Bedingung geprüft wird, erfordern jetzt eine
Variable vom Typ bool. Es findet keine automatische Konvertierung
von int in bool statt, daher ist if (i)
unzulässig. |
|
Switch-Anweisungen verhindern Fehlschläge, um die Fehlerzahl zu
verringern. Switch kann auch für Zeichenfolgenwerte
verwendet werden. |
|
Für das Durchlaufen
von Objekten und Auflistungen kann foreach eingesetzt werden. |
|
Über checked
und unchecked werden arithmetische Operationen und Konvertierungen
auf einen Überlauf geprüft. |
|
Feste Zuordnungen erfordern,
dass Objekte vor der Verwendung über einen festen Wert verfügen. |
35.1.4 Attribute
 
Attribute dienen der Weitergabe beschreibender Daten
vom Programmierer an anderen Code. Bei diesem Code kann es sich um die
Laufzeitumgebung, einen Designer, ein Tool zur Codeanalyse oder um ein
benutzerdefiniertes Tool handeln. Attributinformationen werden über einen Vorgang abgerufen, der als
Reflektion bezeichnet wird.
Attribute werden von eckigen Klammern umschlossen
und können für Klassen, Mitglieder, Parameter und weitere
Codeelemente gesetzt werden. Beispiel:
[CodeReview("1/1/199", Comment="Rockin'")]
class Test
{
}
35.1.5 Versionssteuerung
 
C# ermöglicht gegenüber C++ eine verbesserte
Versionssteuerung. Da das Laufzeitsystem den Mitgliedsentwurf handhabt,
stellt die binäre Kompatibilität kein Problem dar. Die Laufzeit
bietet, falls gewünscht, die Möglichkeit zur Verwendung nebeneinander
existierender Komponentenversionen sowie eine geeignete Semantik zur
Versionssteuerung für Frameworks, C# ermöglicht dem Programmierer
die Angabe des beabsichtigten Versionszwecks.
35.1.6 Codeorganisation
 
C# verwendet keine Headerdateien, sämtlicher
Code wird intern geschrieben, und während eine Präprozessorunterstützung
für bedingten Code vorhanden ist, werden Makros nicht unterstützt.
Die Einschränkungen ermöglichen dem Compiler eine schnellere
Analyse des C#-Codes und ermöglichen es darüber hinaus einer
Entwicklungsumgebung, den C#-Code besser zu verstehen.
Zusätzlich liegen im C#-Code weder Reihenfolgenabhängigkeiten
noch Vorwärtsdeklarationen vor. Die Reihenfolge der Klassen in
den Quelldateien ist unerheblich, die Klassen können nach Wunsch
umgestellt werden.
35.1.7 Fehlende C++-Funktionen
 
Die folgenden C++-Funktionen stehen in C# nicht zur
Verfügung:
|
Mehrfachvererbung |
|
Const-Mitgliedsfunktionen oder -Parameter. Const-Felder werden
unterstützt. |
|
Globale Variablen |
|
Typedef |
|
Konvertierung durch Erstellungsroutine |
|
Standardargumente für Funktionsparameter |
35.2 Unterschiede zwischen C# und Java
 
C# und Java gehen auf gleiche Wurzeln zurück,
daher ist es nicht überraschend, dass zwischen den beiden Sprachen
Ähnlichkeiten vorhanden sind. Dennoch gibt es auch einige Unterschiede.
Der größte Unterschied besteht darin, dass sich C# oberhalb
der.NET-Frameworks und der .NET-Laufzeit befindet und Java den Frameworks und der Laufzeit von Java übergeordnet ist.
35.2.1 Datentypen
 
C# verfügt über primitivere Datentypen
als Java. In der folgenden Tabelle werden die Java-Typen und deren C#-Äquivalente zusammengefasst:
C#-Typ
|
Java-Typ
|
Kommentar
|
sbyte
|
byte
|
C#-byte hat kein Vorzeichen
|
short
|
short
|
|
int
|
int
|
|
long
|
long
|
|
bool
|
Boolean
|
|
float
|
float
|
|
double
|
double
|
|
char
|
char
|
|
string
|
string
|
|
object
|
object
|
|
byte
|
Byte-Wert ohne Vorzeichen
|
|
ushort
|
Short-Wert ohne Vorzeichen
|
|
uint
|
Integer-Wert ohne Vorzeichen
|
|
ulong
|
Long-Wert ohne Vorzeichen
|
|
decimal
|
Finanzdaten-/Währungstyp
|
|
In Java werden die primitiven und die objektbasierten
Datentypen voneinander getrennt. Damit die primitiven Typen
an der objektbasierten Welt teilhaben können (beispielsweise in
einer Auflistung), müssen sie in einer Instanz einer Wrapperklasse platziert werden, und die Wrapperklasse wird anschließend
in der Auflistung platziert.
C# geht dieses Problem anders an. In C# sind die
primitiven Typen (wie bei Java) dem Stack zugeordnet, sie werden jedoch
auch als von der ultimativen Basisklasse object abgeleitet
betrachtet. Dies bedeutet, dass für die primitiven Typen Mitgliedsfunktionen
definiert und aufgerufen werden können. Mit anderen Worten, der
folgende Code ist zulässig:
using System;
class Test
{
public static void Main()
{
Console.WriteLine(5.ToString());
}
}
Die Konstante 5 weist den Typ int
auf, das ToString()-Mitglied ist für den Typ
int definiert, daher kann der Compiler einen Aufruf generieren
und den int an die Mitgliedsfunktion übergeben, als
handele es sich um ein Objekt.
Dies funktioniert prima, wenn der Compiler die Handhabung
eines primitiven Typs kennt, jedoch nicht, wenn ein primitiver Typ mit
den dem Heap zugeordneten Objekten einer Auflistung zusammenarbeiten
muss. Immer dann, wenn ein primitiver Typ verwendet wird und ein Parameter
vom Typ object erforderlich ist, führt der Compiler
ein automatisches Boxing des primitiven Typs in einen dem Heap zugeordneten
Wrapper durch. Hier ein Beispiel zum Boxing:
using System;
class Test
{
public static void Main()
{
int v = 55;
object o = v; // Boxing von v in o
Console.WriteLine("Value is: {0}", o);
int v2 = (int) o; // Unboxing zurück in int
}
}
In diesem Codeabschnitt wird der integer-Wert
in ein object geboxt und der Mitgliedsfunktion Console.WriteLine()
als Objektparameter übergeben. Das Deklarieren der Objektvariable
erfolgt nur zur Verdeutlichung; im tatsächlichen Code würde
v direkt übergeben, das Boxing würde auf Aufruferseite
erfolgen. Der geboxte int-Wert kann durch eine cast-Operation
extrahiert werden.
35.2.2 Erweitern des Typensystems
 
Die primitiven C#-Typen (mit Ausnahme von string und object)
werden auch als Wertetypen bezeichnet, da Variablen dieser Typen tatsächliche
Werte enthalten. Andere Typen werden Verweistypen genannt, da die zugehörigen
Variablen Verweise enthalten.
In C# kann ein Programmierer das Typensystem durch
Implementierung eines benutzerdefinierten Wertetyps erweitern. Diese
Typen werden mit Hilfe des Schlüsselwortes struct
implementiert und verhalten sich ähnlich wie die integrierten Wertetypen.
Sie sind dem Stack zugeordnet, können Mitgliedsfunktionen aufweisen,
und bei Bedarf wird ein Boxing oder Unboxing durchgeführt. Tatsächlich sind alle primitiven
C#-Typen als Wertetypen implementiert, der einzige syntaktische Unterschied
zwischen den integrierten Typen und den benutzerdefinierten Typen besteht
darin, dass die integrierten Typen als Konstanten geschrieben werden
können.
Damit sich die benutzerdefinierten Typen natürlich
verhalten, können C#-Strukturen arithmetische Operatoren überladen, um numerische
Operationen und Konvertierungen auszuführen, d. h., zwischen
Strukturen und anderen Typen können implizite und explizite Konvertierungen
erfolgen. C# unterstützt des Weiteren auch das Überladen von
Klassen.
Ein struct wird mit Hilfe der gleichen
Syntax geschrieben wie class, abgesehen davon, dass eine
Struktur (neben der impliziten Basisklasse object) keine
Basisklasse besitzen kann. Schnittstellen können jedoch implementiert
werden.
35.2.3 Klassen
 
C#-Klassen ähneln den Java-Klassen sehr stark.
Im Hinblick auf Konstanten, Basisklassen und Erstellungsroutinen, statische
Konstruktoren, virtuelle Funktionen, Ausblenden und Versionssteuerung, Mitgliedszugriff,
ref- und out-Parameter sowie das Identifizieren
von Typen bestehen jedoch Unterschiede.
Konstanten
Java verwendet zum Deklarieren einer Klassenkonstante
static final. In C# wird static final
durch const ersetzt. Zusätzlich fügt C# das Schlüsselwort
readonly hinzu, das verwendet wird, wenn die Konstantenwerte
zur Kompilierungszeit nicht ermittelt werden können. Readonly-Felder
können nur über eine Initialisierungsroutine oder eine Klassenerstellungsroutine
gesetzt werden.
Basisklassen und Erstellungsroutinen
C# verwendet sowohl für das Definieren der Basisklasse
und die Schnittstellen einer Klasse als auch für das Aufrufen weiterer
Erstellungsroutinen die C++-Syntax. Eine C#-Klasse kann folgendermaßen
aussehen:
class MyObject: Control, IFormattable
{
public Control(int value)
{
this.value = value;
}
public Control() : base(value)
{
}
int value;
}
Statische Erstellungsroutinen
Anstelle eines statischen Initialisierungsblocks
verwendet C# statische Erstellungsroutinen, die mit Hilfe des Schlüsselworts
static geschrieben werden, das einer parameterlosen Erstellungsroutine
vorangestellt wird.
Virtuelle Funktionen, Ausblenden und Versionssteuerung
In C# sind standardmäßig alle Funktionen
nicht virtuell, um eine virtuelle Funktion zu erhalten, muss explizit
virtual angegeben werden. Aufgrund dieser Tatsache gibt
es keine finalen Methoden in C#, auch wenn das Äquivalent zu einer
finalen Klasse mit Hilfe von sealed erreicht werden kann.
C# bietet eine bessere Versionsunterstützung als Java, woraus sich einige kleinere Änderungen
ergeben. Die Methodenüberladung erfolgt statt nach Signatur nach
Name, d. h., das Hinzufügen von Klassen
zu einer Basisklasse wirkt sich nicht auf das Programmverhalten aus.
Sehen Sie sich folgendes Beispiel an:
public class B
{
}
public class D: B
{
public void Process(object o) {}
}
class Test
{
public static void Main()
{
D d = new D();
d.Process(15); // Aufruf durchführen
}
}
Wenn der Provider der Basisklasse eine Verarbeitungsfunktion
mit größerer Übereinstimmung hinzufügt, ändert
sich das Verhalten:
public class B
{
public void Process(int v) {}
}
public class D: B
{
public void Process(object o) {}
}
class Test
{
public static void Main()
{
D d = new D();
d.Process(15); // Aufruf durchführen
}
}
In Java führt dies zum Aufruf der Klassenimplementierung, was wahrscheinlich falsch ist. In C# setzt das
Programm seine Arbeit fort.
Zur Handhabung des ähnlichen Falles für
virtuelle Funktionen muss in C# die Versionssemantik explizit angegeben werden. Wenn es sich bei Process()
um eine virtuelle Funktion der abgeleiteten Klasse handelt, würde
Java annehmen, dass es sich bei jeder Basisklassenfunktion mit übereinstimmender Signatur um eine Basis
für die virtuelle Funktion handelt, was in den meisten Fällen
nicht stimmt.
In C# werden virtuelle Funktionen nur außer
Kraft gesetzt, wenn das Schlüsselwort override verwendet
wird. Weitere Informationen hierzu finden Sie in Kapitel 11, Versionssteuerung
mit new und override.
Mitgliedszugriff
Neben den Zugriffsbezeichnern public,
private und protected steht in C# der Zugriffsbezeichner
internal zur Verfügung. Auf Mitglieder mit dem Zugriff
internal kann von Klassen desselben Projekts aus, jedoch
nicht von außerhalb des Projekts zugegriffen werden.
Ref- und Out-Parameter
In Java werden Parameter immer nach Wert übergeben.
C# ermöglicht mit dem Schlüsselwort ref das Übergeben
von Parametern als Verweis. Dies ermöglicht der Mitgliedsfunktion,
den Wert des Parameters zu ändern.
C# bietet darüber hinaus die Möglichkeit,
Parameter mit Hilfe des out-Schlüsselwortes zu definieren,
das genau wie ref funktioniert, abgesehen davon, dass die
als Parameter übergebene Variable den Wert nicht vor dem Aufruf
kennen muss.
Identifizieren von Typen
Java verwendet die Methode GetClass(), um ein Class-Objekt mit Informationen
zum aufgerufenen Objekt zurückzugeben. Das Type-Objekt
ist das .NET-Äquivalent zum Class-Objekt und kann
auf verschiedene Weise abgerufen werden:
|
Durch Aufrufen der GetType()-Methode
für eine Objektinstanz |
|
Durch Verwenden des typeof-Operators
für den Typennamen |
|
Durch Ermitteln des Typs
nach Name mit der Klasse System.Reflection |
35.2.4 Schnittstellen
 
Während Java-Schnittstellen Konstanten besitzen können, ist dies in C#
nicht möglich. Bei der Implementierung von Schnittstellen stellt
C# eine explizite Schnittstellenimplementierung bereit. Dies ermöglicht einer Klasse die Implementierung
zweier Schnittstellen unterschiedlicher Quelle und gleichem Mitgliedsnamen.
Die explizite Schnittstellenimplementierung kann auch dazu verwendet werden, Schnittstellenimplementierungen
vor dem Benutzer zu verbergen. Weitere Informationen zu diesem Thema finden
Sie in Kapitel 10, Schnittstellen.
35.2.5 Eigenschaften und Indizierer
 
In Java-Programmen werden Eigenschaften häufig durch das Deklarieren
von get- und set-Methoden eingesetzt. In C#
erscheint eine Eigenschaft dem Benutzer einer Klasse als Feld, bietet
jedoch get- und set-Zugriffsroutinen zum Durchführen
von Lese- oder Schreiboperationen.
Ein Indizierer ähnelt einer Eigenschaft, statt
jedoch wie ein Feld auszusehen, wird der Indizierer dem Benutzer als
Array angezeigt. Wie die Eigenschaften besitzen Indizierer get-
und set-Zugriffsroutinen; im Gegensatz zu den Eigenscahften
kann ein Indizierer jedoch für verschiedene Typen überladen
werden. Dies ermöglicht das Indizieren von Datenbankzeilen nach
Spaltenzahl und Spaltenname sowie das Indizieren von Hashtabellen nach
Hashschlüssel.
35.2.6 Zuweisungen und Ereignisse
 
Wenn ein Objekt in Java einen Rückruf benötigt,
wird mit einer Schnittstelle angegeben, wie das Objekt gebildet wird,
und eine Methode innerhalb der Schnittstelle wird für den Rückruf
aufgerufen. Bei C#-Schnittstellen kann ein ähnlicher Ansatz verwendet werden.
C# stellt Zuweisungen bereit, die man sich wie typensichere
Funktionszeiger vorstellen kann. Eine Klasse kann eine Zuweisung
für eine Funktion der Klasse erstellen, anschließend kann
die Zuweisung an eine Funktion übergeben werden, die diese Zuweisung
akzeptiert. Anschließend kann die Funktion die Zuweisung aufrufen.
C# baut auf Zuweisungen mit Ereignissen auf, die
von den .NET-Frameworks verwendet werden. Ereignisse implementieren das
Veröffentlichen-Abonnieren-Prinzip; wenn ein Objekt (beispielsweise
ein Steuerelement) ein Klickereignis unterstützt, kann eine beliebige
Anzahl weiterer Klassen eine Zuweisung registrieren, die bei Auslösen
des Ereignisses aufgerufen werden soll.
35.2.7 Attribute
 
Attribute dienen der Weitergabe beschreibender Daten
vom Programmierer an anderen Code. Bei diesem Code kann es sich um die
Laufzeitumgebung, einen Designer, ein Tool zur Codeanalyse oder um
ein benutzerdefiniertes Tool handeln. Attributinformationen werden über einen Vorgang abgerufen, der als
Reflektion bezeichnet wird.
Attribute werden von eckigen Klammern umschlossen
und können für Klassen, Mitglieder, Parameter und weitere
Codeelemente gesetzt werden. Beispiel:
[CodeReview("1/1/199", Comment="Rockin'")]
class Test
{
}
35.2.8 Anweisungen
 
Die C#-Anweisungen werden dem Java-Programmierer bekannt vorkommen,
es gibt jedoch einige neue Anweisungen und ein paar Unterschiede bei
den vorhandenen Anweisungen zu beachten.
import kontra using
Die import-Anweisung wird in Java dazu
eingesetzt, ein Paket zu ermitteln und die Typen in die aktuelle Datei
zu importieren.
In C# wird diese Operation aufgeteilt. Die Assemblierungen,
von denen ein Codeabschnitt abhängt, müssen explizit angegeben
werden, entweder über die Befehlszeile mit Hilfe der Option /r
oder in der Visual Studio-IDE. Die grundlegenden Systemfunktionen (aktuell die in mscorlib.dll enthaltenen
Funktionen) sind die einzigen, die automatisch vom Compiler importiert
werden.
Nachdem eine Assemblierung referenziert wurde, können die enthaltenen
Typen verwendet werden, müssen jedoch unter Verwendung des vollqualifizierten
Namens angegeben werden. Die reguläre Ausdrucksklasse heißt
beispielsweise System.Text.RegularExpressions.Regex. Dieser
Klassenname kann direkt verwendet werden, oder es werden mit Hilfe der
using-Anweisung die Typen eines Namespaces in den Namespace oberster Ebene importiert. Mit der folgenden using-Klausel
using System.Text.RegularExpressions;
kann die Klasse einfach durch Einsatz von Regex
angegeben werden. Es gibt außerdem eine Variante der using-Anweisung,
die zur Vermeidung von Namenskollisionen das Angeben eines Typenalias
ermöglicht.
Überlauf
Java kann weder bei Konvertierungen noch bei mathematischen
Fehlern Überläufe ermitteln.
In C# kann eine solche Ermittlung durch die checked- und unchecked-Anweisungen und Operatoren gesteuert werden.
Konvertierungen und mathematische Operationen, die in einem als checked
deklarierten Kontext erfolgen, erzeugen Ausnahmen, wenn die Operation
zu einem Überlauf oder Fehlern führt; die Ausführung
einer solchen Operation in einem als unchecked deklarierten
Kontext führt nie zu einer Fehlerausgabe. Der Standardkontext wird
durch das Compilerflag /checked gesteuert.
Unsicherer Code
Der so genannte unsichere Code in C# ermöglicht
die Verwendung von Zeigervariablen und wird eingesetzt, wenn die Leistung
extrem wichtig ist oder eine Integration mit vorhandener Software benötigt
wird, beispielsweise mit COM-Objekten oder systemeigenem Code in DLLs. Die fixed-Anweisung wird dazu verwendet,
ein Objekt »festzunageln«, damit es bei einer Speicherbereinigung
nicht verschoben wird.
Da von der Laufzeit nicht geprüft werden kann,
ob der unsichere Code gefahrlos ausgeführt werden kann, kann eine
Ausführung nur erfolgen, wenn dem Code vom Laufzeitsystem vertraut
wird. Dies verhindert eine Ausführung in Downloadszenarien.
Zeichenfolgen
Das C#-Zeichenfolgenobjekt kann für den Zugriff auf bestimmte Zeichen
indiziert werden. Beim Zeichenfolgenvergleich werden nicht die Zeichenfolgenverweise,
sondern die Zeichenfolgenwerte miteinander verglichen.
Zeichenfolgenliterale weisen in C# ebenfalls Unterschiede auf; C# unterstützt
Escapezeichen innerhalb von Zeichenfolgen, die zum Einfügen
von Sonderzeichen verwendet werden. Die Zeichenfolge \t
wird beispielsweise in ein Tabulatorzeichen übersetzt.
Dokumentation
Die XML-Dokumentation in C# ähnelt Javadoc,
C# gibt jedoch nicht die Struktur der Dokumentation vor, und der Compiler
prüft die Richtigkeit der Dokumentation und erzeugt eindeutige
Bezeichner für Verknüpfungen.
Weitere Unterschiede
Es gibt einige weitere Unterschiede:
|
Der >>>-Operator
ist in C# nicht vorhanden, da der >>-Operator ein
verschiedenes Verhalten für Typen mit und ohne Vorzeichen aufweist. |
|
Anstelle von instanceof wird der is-Operator verwendet. |
|
Es ist keine benannte
break-Anweisung vorhanden, diese wird durch goto ersetzt. |
|
Die switch-Anweisung verbietet das »Durchfallen« von Code,
switch kann für Zeichenfolgenvariablen eingesetzt
werden. |
|
Es ist nur eine Arraydeklarationssyntax verfügar: int[] arr. |
|
C# ermöglicht bei
Verwendung des params-Schlüsselwortes eine variable Anzahl Parameter. |
35.3 Unterschiede zwischen C# und Visual Basic 6
 
C# und Visual Basic 6 sind relativ unterschiedliche
Sprachen. C# ist eine objektorientierte Sprache, Visual Basic 6
bietet nur beschränkte objektorientierte Funktionen. VB7 weist zusätzliche objektorientierte Funktionen
auf, daher kann eine Lektüre der VB7-Dokumentation sehr aufschlussreich sein.
35.4 Codeaussehen
 
In Visual Basic enden Anweisungsblöcke
auf eine Art von END-Anweisung, und es dürfen sich nicht mehrere Anweisungen
in einer Zeile befinden. In C# werden Blöcke mit geschweiften Klammern
({}) gekennzeichnet, und die Position der Zeilenumbrüche spielt
keine Rolle, da das Ende einer Anweisung mit einem Semikolon gekennzeichnet
wird. Obwohl der nachfolgende Code vielleicht keinen guten Stil darstellt
und hässlich zu lesen ist, ist er in C# möglich:
for (int j = 0; j < 10; j++) {if (j == 5) Func(j);
else return;}
Diese Zeile trägt die gleiche Bedeutung wie
der folgende Codeabschnitt:
for (int j = 0; j < 10; j++)
{
if (j == 5)
Func(j);
else
return;
}
Auf diese Weise wird der Programmierer zwar weniger
eingeschränkt, es sind jedoch auch Abkommen bezüglich des
Stils erforderlich.
35.4.1 Datentypen und Variablen
 
Obwohl viele der in VB und C# verwendeten Datentypen
übereinstimmen, gibt es einige wichtige Unterschiede, ein ähnlicher
Name kann beispielsweise einen anderen Datentyp bezeichnen.
Der wichtigste Unterschied besteht darin, dass C#
bei Variablendeklaration und -verwendung strikter ist. Alle Variablen
müssen vor der Verwendung deklariert werden, und sie müssen
mit einem bestimmten Typ deklariert werden – es ist kein Variant-Typ vorhanden, der einen beliebigen Typ enthalten kann1 .
Variablendeklarationen erfolgen einfach durch Einfügen des Typnamens
vor der Variablen; es gibt keine dim-Anweisung.
Konvertierungen
Konvertierungen zwischen Typen werden in C# ebenfalls
strenger gehandhabt als in Visual Basic. C# kennt zwei Arten der
Konvertierung, die implizite und die explizite Konvertierung. Implizite
Konvertierungen sind diejenigen, bei denen kein Datenverlust auftritt
– d. h., der Quellwert passt stets in die Zielvariable. Beispiel:
int v = 55;
long x = v;
Das Zuweisen von v zu x
ist möglich, da int-Variablen immer in long-Variablen passen.
Explizite Konvertierungen dagegen sind Konvertierungen,
bei denen ein Datenverlust auftreten kann bzw. die fehlschlagen können.
Aufgrund dieser Tatsache muss die Konvertierung mit Hilfe einer Typumwandlung
explizit angegeben werden:
long x = 55;
int v = (int) x;
Obwohl die Konvertierung in diesem Fall sicher ist,
kann long Zahlen enthalten, die zu groß sind, um in
einen int-Wert zu passen, daher ist eine Typumwandlung
erforderlich.
Wenn das Ermitteln eines Überlaufs während
der Konvertierung von Bedeutung ist, kann mit der checked-Anweisung
die Überlaufermittlung aktiviert werden. Weitere Informationen
hierzu finden Sie in Kapitel 15, Konvertierungen.
Datentypenunterschiede
In Visual Basic lauten die ganzzahligen Datentypen
Integer und Long. In C# werden diese durch
die Typen short und int ersetzt. Es ist ebenfalls
ein long-Typ vorhanden, bei diesem handelt es sich jedoch
um einen 64-Bit-Typ (8-Byte). Dies sollten Sie im Hinterkopf behalten,
denn wenn Sie in C# dort Long einsetzen, wo Sie in Visual Basic long verwenden,
werden die Programme sehr viel größer und langsamer. Byte
dagegen kann fast mit byte gleichgesetzt werden.
C# verfügt des Weiteren über die Datentypen
ushort, uint und ulong (ohne
Vorzeichen) sowie den Typ sbyte, einen byte-Wert
mit Vorzeichen. Diese sind in bestimmten Situationen nützlich,
funktionieren jedoch nicht in allen weiteren .NET-Sprachen und sollten
daher sparsam eingesetzt werden.
Die Gleitkommatypen Single und Double
werden in float und double umbenannt, und
der Boolean-Typ wird zu bool.
Zeichenfolgen
Viele der integrierten Funktionen von Visual Basic
sind für C#-Zeichenfolgentypen nicht verfügbar. Es gibt Funktionen
für das Suchen von Zeichenfolgen, das Extrahieren von Teilzeichenfolgen
und das Durchführen weiterer Operationen. Siehe hierzu die Dokumentation
des System.String-Typs.
Die Zeichenfolgenverkettung erfolgt nicht über den &-Operator,
sondern über +.
Arrays
In C# erhält das erste Element eines Arrays
immer den Index 0, und es gibt keine Möglichkeit, höhere
oder niedrigere Grenzen festzulegen oder ein redim für
Arrays auszuführen. Über ArrayList im Namespace
System.Collection wird jedoch eine Dimensionierung bereitgestellt,
zusammen mit weiteren nützlichen Auflistungsklassen.
35.4.2 Operatoren und Ausdrücke
 
Die C#-Operatoren weisen gegenüber Visual Basic einige wenige
Unterschiede auf, daher müssen Sie sich mit diesen besonders vertraut
machen.
VB-Operator
|
C#-Äquivalent
|
^
|
Keiner. Siehe Math.Pow()
|
Mod
|
%
|
&
|
+
|
=
|
==
|
<>
|
!=
|
Like
|
Keiner. System.Text.RegularExpressions.Regex
erledigt einige dieser Aufgaben, ist jedoch komplexer.
|
Is
|
Keiner. Der C#-Operator is trägt
eine andere Bedeutung.
|
And
|
&&
|
Or
|
||
|
Xor
|
^
|
Eqv
|
Keiner. A Eqv B entspricht !(A
^ B).
|
Imp
|
Keiner.
|
35.4.3 Klassen, Typen, Funktionen und Schnittstellen
 
Da C# eine objektorientierte Sprache ist2 , stellen Klassen die hauptsächliche Organisationseinheit
dar; Code oder Variablen werden nicht in globalen Bereichen verwaltet,
sondern immer mit einer spezifischen Klasse verknüpft. Dies führt
zu Code, der recht anders als der Visual Basic-Code strukturiert
und organisiert wird. Dennoch gibt es weiterhin Gemeinsamkeiten. Eigenschaften
können weiterhin verwendet werden, auch wenn sie eine andere Syntax
aufweisen und keine Standardeigenschaften vorhanden sind.
Funktionen
In C# müssen Funktionsparameter einen deklarierten
Typ aufweisen, und anstelle von ByVal wird mit Hilfe von ref angegeben,
dass der Wert einer übergebenen Variable bearbeitet werden kann.
Die Funktion ParamArray kann durch Verwenden des params-Schlüsselwortes ausgeführt werden.
35.4.4 Steuerung und Programmfluss
 
C# und Visual Basic verfügen über
ähnliche Steuerungsstrukturen, die verwendete Syntax unterscheidet
sich jedoch leicht.
If Then
In C# gibt es keine Then-Anweisung; nach der Bedingung folgt die Anweisung oder der
Anweisungsblock, die/der bei erfüllter Bedingung ausgeführt
werden soll. Im Anschluss an Anweisung oder Anweisungsblock kann eine
optionale else-Anweisung vorliegen.
Der folgende Visual Basic-Code
If size < 60 Then
value = 50
Else
value = 55
order = 12
End If
kann umgeschrieben werden zu
if (size < 60)
value = 50;
else
{
value = 55;
order = 12;
}
In C# gibt es keine ElseIf-Anweisung.
For
Die Syntax von for-Schleifen ist in
C# anders, das Konzept bleibt jedoch dasselbe, abgesehen davon, dass
die am Ende einer Schleife durchgeführte Operation in C# explizit
angegeben werden muss. Mit anderen Worten, der folgende Visual Basic-Code
For i = 1 To 100
' Weiterer Code hier
}
kann umgeschrieben werden zu
for (int i = 0; i < 10; i++)
{
// Weiterer Code hier
}
For Each
C# unterstützt die For Each-Syntax
über die foreach-Anweisung, die für Arrays, Auflistungsklassen
und weitere Klassen eingesetzt werden kann, die eine geeignete Schnittstelle
offen legen.
Do Loop
C# weist zwei Schleifenkonstruktionen aus, die das
Do Loop ersetzen. Die while-Anweisung
wird zum Durchlaufen einer Schleife verwendet, während eine Bedingung
erfüllt ist, do while arbeitet auf die gleiche
Weise, abgesehen davon, dass auch ein Schleifendurchlauf vollzogen wird,
wenn die Bedingung nicht erfüllt ist. Der folgende Visual Basic-Code
I = 1
fact = 1
Do While I <= n
fact = fact * I
I = I + 1
Loop
kann folgendermaßen umgeschrieben werden:
int I = 1;
int fact = 1;
while (I <= n)
{
fact = fact * I;
I++;
}
Eine Schleife kann mit Hilfe der break-Anweisung
verlassen oder im nächsten Durchlauf mit der continue-Anweisung
fortgesetzt werden.
35.4.5 Select Case
 
Die switch-Anweisung in C# führt
die gleiche Aufgabe aus wie Select Case. Dieser VB-Code
Select Case x
Case 1
Func1
Case 2
Func2
Case 3
Func2
Case Else
Func3
End Select
kann folgendermaßen umgeschrieben werden:
switch (x)
{
case 1:
Func1();
break;
case 2:
case 3:
Func2();
break;
default:
Func3();
break;
}
35.4.6 On Error
 
In C# gibt es keine On Error-Anweisung.
Fehlerbedingungen in .NET werden über Ausnahmen gehandhabt. Weitere
Informationen zu diesem Thema finden Sie in Kapitel 4, Ausnahmebehandlung.
35.4.7 Fehlende Anweisungen
 
In C# sind weder With, Choose
noch ein Äquivalent zu Switch verfügbar. Des
Weiteren kann auch nicht auf die CallByName-Funktion zurückgegriffen
werden, auch wenn diese Operation über die Reflektion ausgeführt
werden kann.
35.5 Weitere .NET-Sprachen
 
Visual C++ und Visual Basic wurden beide
zur Verwendung in der .NET-Welt erweitert.
In der Visual C++-Welt wurde der Sprache ein
Satz »verwalteter Erweiterungen« hinzugefügt, um den
Programmierern das Erzeugen und Verwenden von Komponenten für die
Common Language Runtime zu ermöglichen. Das Visual C++-Modell
stattet den Programmierer mit umfangreicheren Steuerungsmöglichkeiten
aus als das C#-Modell, da sowohl verwaltete (Speicherbereinigung) als
auch nicht verwaltete Objekte (new und delete)
geschrieben werden können.
Eine .NET-Komponente wird durch das Verwenden von
Schlüsselworten zum Bearbeiten der Bedeutung vorhandener C++-Konstruktionen
erstellt. Wenn beispielsweise das Schlüsselwort __gc
einer Klassendefinition vorangestellt wird, ermöglicht dieses Vorgehen
die Erstellung einer verwalteten Klasse und verbietet der Klasse die
Verwendung von Konstruktionen, die in der .NET-Umgebung nicht ausgedrückt
werden können (beispielsweise die Mehrfachvererbung). Von den verwalteten
Erweiterungen aus können auch die .NET-Systemklassen verwendet
werden.
Visual Basic hat ebenfalls erhebliche Verbesserungen
erfahren. Es werden nun objektorientierte Konzepte wie Vererbung, Kapselung
und Überladung unterstützt, damit eine nahtlose Integration
in die .NET-Umgebung gewährleistet ist.
1
Der Typ object kann einen beliebigen Typ enthalten,
es ist jedoch bekannt, welcher Typ enthalten ist.
2
Siehe hierzu Kapitel 1, Grundlagen der objektorientierten
Programmierung.
|