Kapitel 4 Schnelleinstieg in C#
Das vorliegende Kapitel enthält einen kurzen
Überblick über C#. Hierbei werden grundlegende Programmierkenntnisse
vorausgesetzt, d. h., es wird nicht jedes Detail ausführlich
besprochen. Sollten Ihnen die Erläuterungen hier nicht ausreichen,
finden Sie detailliertere Informationen unter den jeweiligen Themen
in späteren Kapiteln.
4.1 Hello, Universe
 
Als ein Anhänger von SETI1 dachte ich, dass ein »Hello, Universe«-Programm angemessener
sei als das übliche »Hello, World«-Programm.
using System;
class Hello
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, Universe");
// Befehlszeilenargumente durchlaufen
// und ausgeben
for (int arg = 0; arg < args.Length; arg++)
Console.WriteLine("Arg {0}: {1}", arg, args[arg]);
}
}
Wie bereits besprochen, weist die .NET-Laufzeitumgebung
einen einheitlichen Namespace für alle Programminformationen (oder Metadaten) auf. Die using System-Klausel
ist eine Methode zum Referenzieren der Klassen, die sich im System-Namespace
befinden, daher können diese verwendet werden, ohne dass der Typenname mit System eingeleitet werden muss.
Der System-Namespace enthält viele nützliche
Klassen, beispielsweise die Klasse Console, die zur Kommunikation
mit der Konsole (oder DOS-Box oder Befehlszeile für diejenigen,
die noch nie eine Konsole gesehen haben) eingesetzt wird.
Da in C# keine globalen Funktionen vorhanden sind,
wird im Beispiel eine Hello-Klasse mit statischer Main-Funktion
deklariert, die als Startpunkt der Ausführung dient. Main
kann ohne Parameter oder mit einem Zeichenfolgenarray deklariert werden. Da es sich um die Startfunktion handelt, muss
die Funktion statisch sein, d. h., sie ist nicht mit einer Objektinstanz
verknüpft.
Die erste Zeile der Funktion ruft die WriteLine-Funktion
der Console-Klasse auf, die ein »Hello, Universe«
an die Konsole übermittelt. Der Schleifenoperator For durchläuft die übergebenen
Parameter und schreibt eine Zeile für jeden Parameter in der Befehlszeile.
4.2 Namespaces und Using-Klausel
 
Die Namespaces in der .NET-Laufzeitumgebung dienen
der Anordnung von Klassen und anderen Typen in einer einzigen hierarchischen
Struktur. Die ordnungsgemäße Verwendung der Namespaces vereinfacht
den Einsatz von Klassen und verhindert Kollisionen mit Klassen, die von anderen Programmierern stammen.
Namespaces kann man sich als Möglichkeit zur
Festlegung sehr langer Namen für Klassen und andere Typen vorstellen,
ohne dass immer ein vollständiger Name eingegeben werden muss.
Namespaces werden mit Hilfe der namespace-Anweisung
definiert. Für Strukturen mit mehreren Ebenen können Namespaces
verschachtelt werden:
namespace Outer
{
namespace Inner
{
class MyClass
{
public static void Function() {}
}
}
}
Dieses Beispiel erfordert sehr viele Eingaben sowie
Einschübe, eine Vereinfachung lautet:
namespace Outer.Inner
{
class MyClass
{
public static void Function() {}
}
}
Jede Quelldatei kann beliebig viele Namespaces definieren.
Wie bereits im Abschnitt »Hello, Universe«
erwähnt, werden die Metadaten für Typen über using
in das aktuelle Programm importiert, sodass die Typen leichter referenziert
werden können. Using ist lediglich ein Shortcut, der den Eingabeaufwand verringert, der zur Referenzierung von Elementen
anfällt, wie die nachfolgende Tabelle zeigt.
Using-Klausel
|
Quellzeile
|
<keine>
|
System.Console.WriteLine(»Hello«);
|
Using System
|
Console.WriteLine(»Hello«);
|
Kollisionen zwischen gleichnamigen Typen oder Namespaces
können immer durch den vollqualifizierten Typennamen beseitigt werden. Dies kann ein sehr langer Name
sein, wenn es sich um eine stark verschachtelte Klasse handelt, daher
hier eine Variante der Using-Klausel, die das Definieren
eines Alias für eine Klasse ermöglicht:
using ThatConsoleClass = System.Console;
class Hello
{
public static void Main()
{
ThatConsoleClass.WriteLine("Hello");
}
}
Damit der Code lesbarer wird, werden in den verwendeten
Beispielen selten Namespaces verwendet, im tatsächlichen Code sollten
sie jedoch eingesetzt werden.
4.3 Namespace und Assemblierung
 
Ein Objekt kann nur dann innerhalb einer C#-Quelldatei verwendet werden, wenn es durch den C#-Compiler
ermittelt werden kann. Standardmäßig öffnet der Compiler
nur die Assemblierung mscorlib.dll, die die Hauptfunktionen der Common Language
Runtime enthält.
Zur Referenzierung von Objekten, die sich in anderen
Assemblierungen befinden, muss der Name der assemblierten Datei an den
Compiler übergeben werden. Dies kann über die Befehlszeile
und die Option /r:<Assemblierung> oder innerhalb der Visual Studio-IDE durch Hinzufügen eines Verweises auf das C#-Projekt
geschehen.
Üblicherweise besteht eine Beziehung zwischen
dem Namespace, in dem sich das Objekt befindet, und dem Namen der Assemblierung,
in der das Objekt gespeichert ist. Die Typen im Namespace System.Net beispielsweise befinden sich in der Assemblierung
System.Net.dll. Assemblierungstypen basieren üblicherweise
auf den Verwendungsmustern des Objekts in dieser Assemblierung; ein
sehr häufig oder selten verwendeter Typ in einem Namespace kann
in einer eigenen Assemblierung vorliegen.
Der exakte Name der Assemblierung, in der ein Objekt
enthalten ist, kann der Objektdokumentation entnommen werden.
4.4 Grundlegende Datentypen
 
C# unterstützt die üblichen Datentypensätze.
Für jeden von C# unterstützten Datentyp ist ein entsprechender
.NET Common Language Runtime-Typ vorhanden. Der Datentyp int in C#
wird beispielsweise dem Typ System.Int32 in der Laufzeitumgebung zugeordnet. System.Int32
kann fast überall dort verwendet werden, wo int zum Einsatz kommt. Dieses Vorgehen wird
jedoch nicht empfohlen, da es die Lesbarkeit des Codes herabsetzt.
Die grundlegenden Datentypen werden in der nachstehenden
Tabelle beschrieben. Die Laufzeittypen befinden sich allesamt im System-Namespace der .NET Common Language Runtime.
Typ
|
Byte
|
Laufzeittyp
|
Beschreibung
|
byte
|
1
|
Byte
|
Byte-Wert ohne Vorzeichen
|
sbyte
|
1
|
SByte
|
Byte-Wert mit Vorzeichen
|
short
|
2
|
Int16
|
Short-Wert mit Vorzeichen
|
ushort
|
2
|
UInt16
|
Short-Wert ohne Vorzeichen
|
int
|
4
|
Int32
|
Integer-Wert mit Vorzeichen
|
uint
|
4
|
UInt32
|
Integer-Wert ohne Vorzeichen
|
long
|
8
|
Int64
|
Langer integer-Wert mit Vorzeichen
|
ulong
|
8
|
UInt64
|
Langer integer-Wert ohne Vorzeichen
|
float
|
4
|
Single
|
Gleitkommazahl
|
double
|
8
|
Double
|
Gleitkommazahl mit doppelter Genauigkeit
|
decimal
|
8
|
Decimal
|
Zahl mit fester Genauigkeit
|
string
|
String
|
Unicode-Zeichenfolge
|
|
char
|
Char
|
Unicode-Zeichen
|
|
bool
|
Boolean
|
Boolescher Wert
|
|
Der Unterschied zwischen den grundlegenden (integrierten)
Datentypen von C# ist eher formell, da benutzerdefinierte
Typen auf die gleiche Weise verwendet werden können wie die integrierten
Typen. Tatsächlich besteht der einzige wirkliche Unterschied zwischen
den integrierten und den benutzerdefinierten Datentypen darin, dass es möglich ist, für die integrierten
Datentypen literale Werte zu schreiben.
Datentypen werden in Werte- und Verweistypen unterteilt. Wertetypen werden entweder dem Stack zugeordnet oder sind strukturintern zugewiesen.
Verweistypen sind Heaps zugeordnet.
Sowohl die Verweis- als auch die Wertetypen werden von der Basisklasse object abgeleitet. In Fällen, in denen ein
Wertetyp als object fungieren muss, wird ein Wrapper, der
den Wertetyp als Verweisobjekt erscheinen lässt, dem Heap zugeordnet; der
Wert des Wertetyps wird kopiert. Dieser Vorgang wird als Boxing bezeichnet, der umgekehrte Vorgang als
Unboxing. Durch das Boxing und Unboxing kann ein
beliebiger Typ als object behandelt werden.
Dies macht folgenden Code möglich:
using System;
class Hello
{
public static void Main(string[] args)
{
Console.WriteLine("Value is: {0}", 3.ToString());
}
}
Hier wurde für den integer-Wert
3 ein Boxing durchgeführt; die Funktion Int32.ToString()
wird für den geboxten Wert aufgerufen.
C#-Arrays können entweder in mehrdimensionaler oder
unregelmäßiger (jagged) Form deklariert werden. Erweiterte
Datenstrukturen wie Stacks und Hashtabellen sind im Namespace System.Collection
enthalten.
4.5 Klassen, Strukturen und Schnittstellen
 
In C# wird das Schlüsselwort class
zum Deklarieren eines (dem Heap zugeordneten) Verweistyps verwendet,
das Schlüsselwort struct dient der Deklaration von
Wertetypen. Strukturen (Schlüsselwort struct) werden
für schlanke kleine Objekte verwendet, die wie integrierte Typen
fungieren müssen, in allen anderen Fällen werden Klassen eingesetzt.
Der int-Typ ist beispielsweise ein Wertetyp, der Typ string ist ein Verweistyp. Das folgende Diagramm zeigt die Funktionsweise
dieser Typen:
int v = 123;
string s = "Hello There";
C# und die .NET-Laufzeitumgebung bieten keine Unterstützung für die Mehrfachvererbung
bei Klassen, unterstützen jedoch die mehrfache Implementierung
von Schnittstellen.
4.6 Anweisungen
 
Die C#-Anweisungen ähneln denen von C++ sehr stark. Die Unterschiede bestehen in einigen
Änderungen, durch die weniger Fehler auftreten, sowie in einigen
neuen Anweisungen. Die Anweisung foreach wird zum Durchlaufen von Arrays und Auflistungen
eingesetzt, die Anweisung lock wird für das gegenseitige Ausschließen
in Threadingszenarien verwendet. Die Anweisungen checked und unchecked dienen der Überlaufsteuerung in arithmetischen Operationen und Konvertierungen.
4.7 Enum
 
Aufzählungsbezeichner (enum) werden zum einfachen und typensicheren
Deklarieren bezogener Konstanten verwendet – beispielsweise
zum Auflisten der Farben, die ein Steuerelement annehmen kann. Beispiel:
enum Colors
{
red,
green,
blue
}
Diese Aufzählungsbezeichner werden in Kapitel 20,
Aufzählungsbezeichner, näher erläutert.
4.8 Zuweisungen und Ereignisse
 
Zuweisungen (delegate) sind eine typensichere, objektorientierte
Implementierung von Funktionszeigern und werden in vielen Situationen
eingesetzt, in denen eine Komponente die Komponente aufrufen muss, die
sie verwendet. Zuweisungen werden weitestgehend als Grundlage für
Ereignisse eingesetzt, die das einfache Registrieren von Zuweisungen
für ein Ereignis ermöglichen. Zuweisungen werden in Kapitel 22
ausführlich behandelt.
In den .NET-Frameworks finden Zuweisungen und Ereignisse breite Anwendung.
4.9 Eigenschaften und Indizierer
 
C# unterstützt Eigenschaften und Indizierer,
die zur Trennung der Schnittstelle eines Objekts von der Objektimplementierung nützlich sind. Statt einem Benutzer direkten
Zugriff auf ein Feld oder Array zu gewähren, ermöglichen Eigenschaften
oder Indizierer einem angegebenen Anweisungsblock diesen Zugriff, während
die Verwendung von Feld oder Array weiterhin möglich ist. Hier
ein einfaches Beispiel:
using System;
class Circle
{
public int X
{
get
{
return(x);
}
set
{
x = value;
// Objekt hier zeichnen.
}
}
int x;
}
class Test
{
public static void Main()
{
Circle c = new Circle();
c.X = 35;
}
}
In diesem Beispiel wird die Zugriffsroutine get oder set aufgerufen, wenn auf die Eigenschaft X
verwiesen wird.
4.10 Attribute
 
In C# und den .NET-Frameworks verwendet der Codeentwickler
Attribute dazu, erklärende Informationen an Codeabschnitte zu übermitteln,
die an diesen Informationen interessiert sind. So kann angegeben werden,
welche Felder eines Objekts serialisiert werden sollten, welcher Transaktionskontext bei der Objektausführung zu verwenden ist, für welche Felder ein Marshaling in systemeigene Funktionen durchgeführt werden
sollte oder wie eine Klasse in einem Klassenbrowser angezeigt wird.
Attribute werden von eckigen Klammern umschlossen.
Ein typisches Attribut kann folgendermaßen aussehen:
[CodeReview("12/31/1999", Comment="Gut gemacht")]
Attributinformationen werden zur Laufzeit über einen Vorgang abgerufen,
der als Reflektion bezeichnet wird. Neue Attribute können leicht
geschrieben und auf Codeelemente angewendet werden (beispielsweise auf
Klassen, Mitglieder oder Parameter) und per Reflektion abgerufen werden.
1
Search for Extraterrestrial Intelligence (Suche
nach außerirdischer Intelligenz). Weitere Informationen finden
Sie unter http://www.teamseti.org.
|