Sprachelemente von C-Sharp

Übersicht einiger Sprachelemente von C#
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 28. November 2005 um 11:05 Uhr durch Mr.bloom (Diskussion | Beiträge) (''goto'' kein alleiniges OO-Problem. Formulierung noch verschärft :-)). Sie kann sich erheblich von der aktuellen Version unterscheiden.

Dieser Artikel bietet eine Übersicht der wesentlichsten Sprachelemente von C#.

Bedingte Ausführung (if, else)

 if (Bedingung) {
   Anweisungen;
 }
 [else if (Bedingung) {
   Anweisungen;
 }]
 [else {
   Anweisungen;
 }]

Schleifen (for, do, while, foreach)

 for (Startausdruck; Gültigkeitsbedingung; Inkrementierungsausdruck) 
 {
   Anweisungen;
 }

Die Bedingung der While-Schleife wird immer vor dem Anweisungsblock ausgeführt. Wenn die Bedingung von Anfang an nicht erfüllt ist, wird die Schleife also nicht durchlaufen.

 while (Bedingung) 
 {
   Anweisungen;
 }

Die Bedingung der Do-While-Schleife wird immer nach dem Anweisungsblock ausgeführt. Die Schleife wird daher mindestens ein mal durchlaufen.

 do 
 {
   Anweisungen;
 } while (Bedingung);

Mit der foreach-Schleife wird durch alle Mitglieder einer Collection iteriert. In der Schleife besteht nur lesender Zugriff auf die Variable.

 foreach(Datentyp Variablename in Collection)
 {
   Anweisungen;
 }

Die Sprungbefehle break, continue, goto und return

 for (int i = 0; i < 10; ++i) 
 {
   if (i < 8)
     continue;
   Console.WriteLine("Continue wurde nicht ausgeführt.");
 }

Mit dem Befehl continue wird der nächste Durchlauf einer Schleife veranlasst. (Dabei wird der restliche Code in der Schleife nach dem continue Befehl übersprungen. Im Beispiel wird der Text nur 2 mal ausgegeben.

 for (int i = 0; i < 100; ++i) 
 {
   if (i == 5) 
     break;
   Console.WriteLine(i);
 }

Der Befehl break veranlasst das Programm, die nächste umschließende Schleife (oder switch) zu verlassen. In diesem Beispiel werden nur die Zahlen 0, 1, 2, 3 und 4 ausgegeben.

 int a = 1;
 
Top: if (a == 5) ++a; goto Top; Console.WriteLine("Ende des Programms");

Mit goto springt das Programm an die angegebene Markierung.

 int result = ImQuadrat(2);
 
static int ImQuadrat(int x) { int a; a = x*x; return a; }

Mit return wird die aktuelle Methode verlassen und der im Kopf der Methode vereinbarte Referenz- bzw. Datentyp als Rückgabewert zurückgeliefert. Methoden ohne Rückgabewert werden mit dem Schlüsselwort void gekennzeichnet.

Die Benutzung von goto sollte jedoch generell vermieden werden, da dadurch der Quellcode deutlich unleserlicher wird. Siehe auch Spagetticode.


Objekte und Klassen

Wenn man von einem Objekt spricht, handelt es sich dabei in der Umgangssprache normalerweise um ein reales Objekt oder einen Gegenstand des täglichen Lebens. Beispielsweise kann das ein Tier, ein Fahrzeug, ein Konto oder ähnliches sein.

Jedes Objekt kann durch verschiedene Attribute beschrieben werden und verschiedene Zustände annehmen und diese auch auslösen.

Übertragen auf die objektorierentierte Programmierung und C# ist ein Objekt die Instanz (siehe Schlüsselwort new) einer Klasse. Eine Klasse kann man dabei als Bauplan oder Gerüst eines Objektes ansehen.

Eine Klasse besitzt Eigenschaften (Variablen) bzw. Attribute, Methoden (die Zustände/Tätigkeiten darstellen) und Ereignisse, die die Folge von Zuständen sind bzw. diese auslösen.

Klasse
Attribut(e)
Methode(n)
Ereignis(se)

Die Klasse namens "object"

Die Klasse object ist ein Referenztyp, von dem jede Klasse abgeleitet wird. So kann durch explizite Typumwandlung jeder Objekttyp in object umgewandelt werden.

Im Unterschied zu anderen Sprachen unterscheidet C# nicht zwischen elementaren Datentypen und Referenztypen. Auch bei elementaren Datentypen handelt es sich in C# um Referenztypen, die von object abgeleitet werden.

Strukturen (struct)

Strukturen sind bereits als Sprachmittel aus Sprachen wie C++ oder Delphi (Records) bekannt. Sie dienen primär zur Erstellung eigener komplexer Datenstrukturen oder eigener Datentypen. So kann zum Beispiel eine Raumkoordinate, bestehend aus einer X-, einer Y- und einer Z-Position, durch eine Datenstruktur Koordinate abgebildet werden, die sich aus drei Gleitkommazahlen einfacher oder doppelter Genauigkeit zusammensetzt.

public struct Koordinate
{
  public double x;
  public double y;
  public double z;	
  public Koordinate(double x, double y, double z)
  {
     this.x = x;
     this.y = y;
     this.z = z;
  }
  public override string ToString()
  {
    return(string.Format("x: {0}, y: {1}, z: {2}", x, y, z));
  }
}

C# fasst eine über das Schlüsselwort struct definierte Struktur als einen Wertetyp auf – anders als beispielsweise C++. Strukturen in C# können außerdem Methoden, Eigenschaften, Konstruktoren und andere Elemente von Klassen aufweisen; sie können aber nicht beerbt werden.

Der Unterschied einer Struktur im Vergleich zu einer Klasse besteht in ihrer Repräsentation im Speicher. Da keine zusätzlichen Speicherreferenzen benötigt werden, wie es beim Objekt einer Klasse erforderlich ist (Referenztyp), können Strukturen wesentlich ressourcenschonender eingesetzt werden. So basieren beispielsweise alle Datentypen in C# auf Strukturen.

Aufzählungen (Enumerationen)

Aufzählungen dienen zur automatischen Nummerierung der in der Aufzählung enthaltenen Elemente. Die Syntax für die Definition von Aufzählungen verwendet das Schlüsselwort enum (Abkürzung für Enumeration).

Der in C# verwendete Aufzählungstyp ähnelt dem in C++, mit der Ausnahme, dass ein optionaler ganzzahliger Datentyp für die Nummerierung der Elemente angegeben werden kann. Ohne Angabe eines Datentyps wird int verwendet.

In Java werden, im Unterschied zur Sprache C#, Aufzählungstypen durch finalisierte statische Attribute in Klassen oder Schnittstellen nachgebildet (ab Java 1.5 sind sie aber auch ein Bestandteil der Sprache Java).

public enum Woche
{
  Montag = 1, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag, Sonntag
}

Die Elementnummerierung in C# beginnt bei 0. Es ist aber auch möglich, wie in C++, jedem Element – oder nur dem ersten Element – einen eigenen Startwert zuzuweisen (wie im obigen Beispiel). Dabei können sich die Anfangswerte wiederholen. Die Zählung beginnt dann jeweils von neuem bei dem definierten Startwert und Element.

In C# ist es auch möglich, ein bestimmtes Element einer Enumeration über seine Ordinalzahl anzusprechen. Hierzu ist aber eine explizite Typumwandlung notwendig.

Woche Tag = Woche.Mittwoch;
System.Console.WriteLine(Tag);      // Ausgabe: Mittwoch
System.Console.WriteLine((int)Tag); // Ausgabe: 3
System.Console.WriteLine((Woche)3); // Ausgabe: Mittwoch

Flags

Neben der beschriebenen Variante des enum-Aufzählungstyps existiert noch eine spezielle Variante von Enumeration. Flags definieren Enumerationen auf Bitebene und werden durch die Metainformation [Flags] vor der Enum-Deklaration definiert. Flag-Elemente können auf Bitebene verknüpft werden.


Zugriffsmodifikatoren

Zugriffsmodifikatoren regeln den Zugriff auf Klassen und deren Mitglieder (Methoden, Eigenschaften, Variablen, Felder und Ereignisse) in C#. Die folgende Tabelle führt die von C# unterstützten Zugriffsmodifikatoren auf und beschreibt deren Wirkung und den Sichtbarkeitskontext.

Name Wirkung
internal

internal beschränkt den Zugriff auf Klassen und deren Mitglieder auf eine Assembly. Im einfachsten Falle ist das die aktuelle Klasse (sofern kein Namensraum (namespace) definiert wurde), andernfalls ist der Zugriff auf den aktuellen Namensraum und dessen Mitglieder beschränkt (unter Java entspricht das der Beschränkung auf ein package).

private

Beschränkt den Zugriff auf eine Klasse und deren Mitglieder. Eine mit private deklarierte Klasse kann nur innerhalb der Klasse selbst instanziert werden (beispielsweise kann ein öffentlicher Konstruktor einer Klasse, oder die statische Funktion Main, einen privaten Konstruktor aufrufen, der nicht von außen aufgerufen werden kann).

Oft wird private verwendet, um das Singleton-Muster umzusetzen (z.B. bei der Verwendung einer Klasse als Fabrik; siehe Fabrikmuster) oder die Vererbung zu beeinflussen oder zu verbieten (siehe auch Schlüsselwort sealed). Hinweis: Eine abgeleitetet Klasse kann auf private Mitglieder der Basisklasse (vgl. Schlüsselwort base) ebenfalls nicht zugreifen. Soll ein solcher Zugriff möglich sein, so muss der Zugriffsmodifizierer protected verwendet werden.

protected

Dieser Modifikator ist mit dem Modifikator private identisch, bis auf einen Unterschied. Sofern die Klasse nicht mit dem Schlüsselwort sealed gegen das Beerben "versiegelt" wurde, können auch abgeleitete Klassen auf die Mitglieder einer Basisklasse (vgl. Schlüsselwort base) zugreifen oder diese überschreiben. Ansonsten ist die Sichtbarkeit auf die Klasse selbst beschränkt.

public

Auf als public gekennzeichnete Klasse oder Klassenmitglieder (z.B. Methoden oder Eigenschaften), kann unbeschränkt zugegriffen werden. Sie werden deshalb auch als "öffentlich" bezeichnet. Eine Klasse die eine statische Funktion Main enthält oder sonstige statische Methoden, muss immer als public deklariert werden, da sonst kein Zugriff möglich ist und es zu einem Fehler kommt.

Hinweise:

  • Per Voreinstellung sind Klassenmitglieder (Methoden, Eigenschaften usw.), denen kein Zugriffsmodifikator zugewiesen wurde, automatisch als private deklariert. Klassen selbst dagegen besitzen automatisch den Modifikator internal und sind nur im aktuellen Namensraum (namespace) sichtbar. Wurde kein Namensraum zugewiesen, so gilt der so genannte globale Deklarationsraum als Standardnamensraum.
  • Die Modifikatoren können bis auf protected und internal nicht miteinander kombiniert werden. protected internal spielt im Zusammenhang mit der Vererbung von Komponenten eine Rolle. Die Sichtbarkeit der Basisklasse wird von der abgeleiteten Klasse übernommen.
  • Schnittstellen sind immer öffentlich definiert. Die Zuordnung eines Zugriffsmodifikators (mit Ausnahme von public) ist nicht zulässig.

Datentypen, Eigenschaften und Konstanten

C# kennt zwei Arten von Datentypen: Wertetypen und Referenztypen. Referenztypen dürfen dabei nicht mit Zeigern gleichgesetzt werden, wie sie u.a. aus der Sprache C++ bekannt sind. Diese werden von C# auch unterstützt, aber nur im "unsicheren Modus" (engl. unsafe mode).

Wertetypen enthalten die Daten direkt, wobei Referenztypen im Gegensatz dazu nur Verweise auf die eigentlichen Daten, oder besser, Objekte darstellen. Beim Lesen und Schreiben von Wertetypen werden die Daten dagegen über einen Automatimus, Autoboxing genannt, in einer Instanz der jeweiligen Hüllenklasse (engl. wrapper) gespeichert oder aus ihr geladen.

Die Zuweisung eines Wertes bzw. einer Referenz kann während der Deklaration erfolgen oder später, sofern die Variable nicht als Konstante deklariert wurde. Die Deklaration erfolgt durch Angabe eines Datentyps gefolgt von einem Variablennamen:

// Datentyp Variable;
int i;
System.Collections.IList liste;

Es können auch mehrere Variablen des gleichen Typs zeitgleich deklariert werden:

// Datentyp Variable1, Variable2, ...;
int i, j, k;
System.Collections.IList liste1, liste2;

Ferner besteht die Möglichkeit, der Variablen bei der Deklaration auch gleich einen Wert oder eine Referenz zuzuweisen (Initialwert):

// Datentyp Variable=Wert/Referenz;
int i = 5;
int j = 2, k = 3;
System.Collections.IList liste = new System.Collections.ArrayList();

Auch die Mehrfachzuweisung eines Wertes an verschiedene Variablen ist möglich:

int i, j, k;
i = j = k = 123;

Einen Sonderfall der Zuweisung stellt die Deklaration von Feldern (Arrays) dar. Näheres hierzu im entsprechenden Abschnitt.

Datentypen und Speicherbedarf

Datentyp Bit Vorz. Hüllenklasse (struct type)
bool 1 - System.Boolean
byte 8 N System.Byte
char 16 - System.Char
decimal 128 - System.Decimal
double 64 - System.Double
float 32 - System.Single
int 32 J System.Int32
long 64 J System.Int64
sbyte 8 J System.SByte
short 16 J System.Int16
uint 32 N System.UInt32
ulong 64 N System.UInt64
ushort 16 N System.UInt16

Datentypen sind in C# nicht elementar, sondern objektbasiert. Jeder der in der Tabelle aufgeführten Datentypen stellt einen Alias auf eine Klasse des Namensraumes System dar. Beispielsweise wird der Datentyp bool durch die Klasse System.Boolean abgebildet. Man spricht dabei auch von einer Hüllenklasse (engl. wrapper class). Durch die Objektbasiertheit ist es möglich, Methoden auf Datentypen anzuwenden, die die entsprechende Hüllenklasse bereitstellt:

1234.ToString();

int i = 17;
i.ToString();

Vergleichbar mit C++, und anders als bei Java, gibt es unter C# vorzeichenbehaftete und vorzeichenlose Datentypen. Diese werden durch Voranstellen des Buchstabens s (für signed, englisch für vorzeichenbehaftet) und durch Voranstellen des Buchstabens u (für unsigned, englisch für vorzeichenlos) gekennzeichnet (sbyte, uint, ulong, ushort, mit Ausnahme von short). Die Fließkomma- bzw. Gleitkomma-Datentypen (float, double, decimal) können neben einfacher auch doppelte Genauigkeit aufweisen und haben einen variierenden Speicherbedarf. Dadurch ändert sich die Genauigkeit, was in der Anzahl der möglichen Nachkommastellen zum Ausdruck kommt.

Konstanten (Schlüsselwort const)

Einem mit const deklarierten "Objekt" kann nach der Deklaration und Initialisierung kein neuer Inhalt zugewiesen werden. Das "Objekt" wird dadurch zu einer Konstanten.

Eine Konstante kann dabei nur ein von einem Typ sein, von welchem der Compiler feststellen kann ob er auch konstant ist. Dies geht bei allen Grunddatentypen die der Compiler versteht, sowie allen Enums. Bei allen anderen Wertdatentypen (Structs) sowie Referenzetypen ist eine Deklaration einer Variablen als const nicht möglich.

Grund dafür ist, dass der Compiler alle Verwendungen zum Zeitpunkt des Kompilieren ersetzt. Bei Referenzedatentypen müsste er dort bereits die Speicheradresse zur Laufzeit wissen. Strukturen könnten ebenfalls nicht konstant sein, da sie in einem Konstruktor einen Zufallsgenerator benutzen könnten. Fehlerhafte Zuweisungen einer Konstanten werden mit dem Kompilerfehler CS0133 vom C-Sharpkompiler moniert.

using System;
using System.IO;

public class ConstBeispiel { public static void Main() { //Es wird eine Konstante für die Länge eines Arrays angelegt const int arrayLength = 1024; //Es wird eine Konstante festgelegt, welche die Zugriffart auf eine Datei beschreibt (Enum) const FileMode fm = FileMode.Open; //Es wird eine Konstante festgelegt, welche den Namen der zu öffnende Datei festlegt const string fileName = "beispiel.txt"; //Buffer erstellen byte[] readBuffer = new byte[arrayLength]; //Stream zur Datei öffnen Filestream fs = new FileStream(fileName,fm); //Daten Lesen fs.Read(readBuffer,0,readBuffer.Length); //Stream schliesen fs.Close(); //Daten ggf. bearbeiten. ... // FEHLER: IList wird ein Referenzdatentyp zugewiesen, nicht konstant const IList liste = new ArrayList();
// FEHLER: const + struct const TimeSpan zeitSpanne = new TimeSpan(10);
} }

Konstanten gibt es auch in anderen Sprachen (z.B. C++, Java). In Java werden Konstanten durch das Schlüsselwort final gekennzeichnet, in Fortran durch PARAMETER.

Eigenschaften (Schlüsselwörter get, set und value)

Eine Eigenschaft (property) ist eine Sicht auf eine öffentliche Variable einer Klasse. Die Variable selbst wird durch einen Zugriffsmodifizierer wie private oder protected (bei Variablen, die in abgeleiteten Klassen überschrieben werden sollen) für den Zugriff von außen gesperrt und über eine Eigenschaft zugänglich gemacht. Über die Eigenschaft kann dann bestimmt werden, ob ein lesender oder schreibender Zugriff auf die referenzierte Variable erfolgen darf. Beide Möglichkeiten sind auch miteinander kombinierbar.

Eine Eigenschaft wird durch Zuweisung eines Datentyps (der dem Datentyp der Variable entsprechen muss) zu einem Eigenschaftsnamen angelegt und hat eine ähnliche Struktur wie die Syntax einer Methode. Die Eigenschaft ist dabei wie eine Variable ansprechbar und ihr kann auch ein Zugriffsmodifizierer zugewiesen werden. Eine Eigenschaft enthält selbst keine Daten, sondern bildet diese auf die referenzierte Variable ab (vergleichbar mit einem Zeiger).

Zur Abfrage einer Eigenschaft existiert in C# das Schlüsselwort get und zum Setzen eines Wertes das Schlüsselwort set. Von außen stellt sich die Eigenschaft dann wie eine Variable dar und der Zugriff kann entsprechend erfolgen (vgl. VisualBasic).

Die Programmiersprache Java verfolgt mit den Set- und Get-Methoden (Bean-Pattern, Introspection) das gleiche Ziel - alle Zugriffe erfolgen nie direkt über eine Variable, sondern über die entsprechende Methode.

Beispiel einer Eigenschaftsdefinition Wohnort für eine private Variable (_wohnort):

public class EigenschaftBeispiel
{
  private string _wohnort;
public string Wohnort { get { return _wohnort; } set { _wohnort = "12345 " + value; } } }

Durch das "Weglassen" des Schlüsselwortes set oder des Schlüsselwortes get kann gesteuert werden, ob die Eigenschaft nur gelesen oder nur geschrieben werden darf. Das Schlüsselwort value ist dabei ein Platzhalter für den der Eigenschaften zugewiesenen Wert, der gesetzt werden soll. Er kann nur in Verbindung mit dem Schlüsselwort set im entsprechenden Block verwendet werden (und entspricht in etwa einer temporären lokalen Variable).

Beispiel für den Zugriff auf die oben definierte Eigenschaft Wohnort:

EigenschaftBeispiel instanz = new EigenschaftBeispiel();
instanz.Wohnort = "Musterstadt";
Console.WriteLine(instanz.Wohnort);
// Ausgabe: 12345 Musterstadt

Würde bei der obigen Definition der Eigenschaft 'Wohnort' der get-Block weggelassen, so würde der lesende Zugriff zu einem Zugriffsfehler führen (im Beispiel in der Zeile, in der die Ausgabe erfolgt).

Neben dem einfachen Setzen oder Lesen einer Eigenschaft, können im set-Block bzw. get-Block auch Operationen ausgeführt werden, beispielsweise die Potenzierung eines bei set übergebenen Wertes (value mal Exponent), bevor er der Variablen zugewiesen wird. Das gleiche gilt für das Schlüsselwort get. Theoretisch kann somit ein Zugriff für den Benutzer einer Klasse ganz unerwartete Ergebnisse bringen. Deshalb sollten alle Operationen, die Veränderungen auf einen Wert durchführen über normale Methoden abgebildet werden. Ausgenommen sind natürlich Wertprüfungen bei set.

Das Beispiel konkateniert den der Eigenschaft übergebenen Wert (hier: Musterstadt) zur Zeichenkette "12345 ". Diese Aktion ist syntaktisch und semantisch richtig, sie sollte dennoch in einer Methode ausgeführt werden.

Darstellung spezieller Zeichen oder Zeichenfolgen

Seq. Beschreibung Hexadezimal
\ Einleitung alternativer Interpretation 0x001B
\' Einfaches Anführungszeichen 0x0007
\" Doppeltes Anführungszeichen 0x0022
\\ Umgekehrter Schrägstrich 0x005C
\0 Null 0x0000
\a Signalton (engl. alarm) 0x0007
\b Rückschritt (engl. backspace) 0x0008
\f Seitenvorschub (engl. form feed) 0x000C
\n Zeilenwechsel (engl. new line) 0x000A
\r Wagenrücklauf (engl. carriage return) 0x000D
\t Tabulator, horizontal 0x0009
\v Tabulator, vertikal 0x000B
\x Hexadezimale Zeichenfolge für ein einzelnes Unicode-Zeichen
\u Zeichenfolge für Unicode-Zeichen in Zeichenliteralen.
\U Zeichenfolge für Unicode-Zeichen in Zeichenketten-Literalen.

Ein auf das Zeichen "\" (umgekehrter Schrägstrich, engl. backslash) folgendes Zeichen wird anders interpretiert als sonst. Dabei handelt es sich meistens um nicht darstellbare Zeichen. Soll der umgekehrte Schrägstrich selbst dargestellt werden, so muss er doppelt angegeben werden ("\\").

Hexadezimale Zeichenfolge als Platzhalter für ein einzelnes Unicode-Zeichen: Das Zeichen wird dabei aus dem Steuerzeichen \x gefolgt von dem hexadezimalen Wert des Zeichens gebildet.

Zeichenfolge für Unicode-Zeichen in Zeichenliteralen: Das Zeichen wird dabei aus dem Steuerzeichen \u gefolgt von dem hexadezimalen Wert des Zeichens gebildet, z. B. "\u20ac" für "€" Der Wert muss zwischen U+0000 und U+FFFF liegen.

Zeichenfolge für Unicode-Zeichen in Zeichenkettenliteralen: Das Zeichen wird dabei aus dem Steuerzeichen \U gefolgt von dem hexadezimalen Wert des Zeichens gebildet. Der Wert muss zwischen U+10000 und U+10FFFF liegen. Hinweis: Unicode-Zeichen im Wertbereich zwischen U+10000 und U+10FFFF sind nur für Zeichenfolgen-Literale zulässig und werden als zwei Unicode-"Ersatzzeichen" kodiert bzw. interpretiert (s. a. UTF-8).


Vererbung

Schnittstellen

Mehrfachvererbung wird in C# nur in Form von Schnittstellen (interface) unterstützt.

Schnittstellen (engl. interfaces) dienen in C# zur Definition von Methoden, ihrer Parameter, ihrer Rückgabewerte sowie von möglichen Ausnahmen.

An dieser Stelle ein Anwendungsbeispiel für die Mehrfachvererbung:

public class MyInt : IComparable, IDisposable
{
  // Implementierung
}

Schnittstellen in C# ähneln den Schnittstellen der Programmiersprache Java. Anders als in Java, dürfen Schnittstellen in C# keine Konstanten oder Konstruktoren enthalten und auch keine Zugriffsmodifizierer bei der Definition einer Methode vereinbaren.

public interface A
{
  void MethodeA();
}
public interface B
{
  void MethodeA();
  void MethodeB();
}
public class Klasse : A, B
{
  void  A.MethodeA() {Console.WriteLine("A.A");} // MethodeA aus Schnittstelle A
  void  B.MethodeA() {Console.WriteLine("A.B");}  // MethodeA aus Schnittstelle B
  public void MethodeA() {Console.WriteLine("A.C");}  //MethodeA für Klasse
  public void MethodeB() {Console.WriteLine("B.B");}  // MethodeB aus Schnittstelle B
}

Eine Klasse, die ein oder mehrere Schnittstellen einbindet, muss jede in der Schnittstelle definierte (virtuelle) Methode implementieren. Werden mehrere Schnittstellen eingebunden, die Methoden mit dem gleichen Namen und der gleichen Struktur besitzen (d.h. gleiche Parametertypen, Rückgabewerte usw.), so muss die jeweilige Methode in der implementierenden Klassen durch das Voranstellen des Namens der Schnittstelle gekennzeichnet werden. Dabei wird die jeweilige Funktion nur dann aufgerufen, wenn der Zeiger auf das Object vom entsprechenden Typ ist:

public static void Main()
{
  Klasse k = new Klasse();
  (k as A).MethodeA();
  (k as B).MethodeA();
  k.MethodeA();
  Console.ReadLine();
}


Ausgabe:
A.A
A.B
A.C

Auch Schnittstellen ohne Methodendefinition sind möglich. Sie dienen dann als so genannte Markierungsschnittstellen (engl. marker interface).

Das Einbinden einer Schnittstelle, deren Klassenname oft mit einem I beginnt um sie als Interface zu kennzeichnen, erfolgt analog zur Beerbung einer Klasse.


Das Schlüsselwort base

Das Schlüsselwort wird im Zusammenhang von Vererbung genutzt. Es ist, vereinfacht gesagt, das für die Basisklasse, was this für die aktuelle Klasse ist.

Nun folgt ein Beispiel, das die Verwendung von base zeigt:

public class Example : Basisklasse
{
  private int myMember;
  public Example() : base(3)
  {
    myMember = 2;
  }
}

In diesem Beispiel wurde die Verwendung nur anhand des Basisklassenkonstruktors gezeigt. Wie in der Einleitung beschrieben kann base auch für den Zugriff auf die Mitglieder der Basisklasse benutzt werden. Die Verwendung erfolgt äquivalent zur Verwendung von this bei der aktuellen Klasse.

Versiegelte Klassen

Versiegelte Klassen sind Klassen, von denen keine Ableitung möglich ist und die folglich nicht als Basisklassen benutzt werden können. Bekanntester Vertreter dieser Art von Klassen ist die Klasse String aus dem Namensraum System. Der Modifizierer sealed kennzeichnet Klassen als versiegelt.

Typumwandlungen

In C# ist jeder Variablen ein Datentyp zugeordnet. Manchmal ist es nötig, Typen von Variablen ineinander umzuwandeln. Zu diesem Zweck gibt es Typumwandlungsoperationen. Dabei gibt es implizite und explizite Typumwandlungen.

Eine implizite Typumwandlung erscheint nicht im Quelltext. Sie wird vom Compiler automatisch in den erzeugten Maschinen-Code eingefügt. Voraussetzung dafür ist, dass zwischen Ursprungs- und Zieltyp eine implizierte Typumwandlungsoperation existiert.

Für explizite Typumwandlungen sind in C# zwei Konstrukte vorgesehen:

(Zieldatentyp) Variable_des_Ursprungsdatentyps

Variable_des_Ursprungsdatentyps as Zieldatentyp

Während erstere Umwandlung im Fall einer ungültigen Typumwandlung eine Ausnahme auslöst, ist letztere nur möglich, wenn der Zieldatentyp ein Referenzdatentyp ist. Bei einer ungültigen Typumwandlung wird hier dem Ziel der Nullzeiger zugewiesen.

using System.Collections;
public class CastBeispiel
{
  public static void Main()
  {
    long aLong = long.MaxValue;
    //Typumwandlung nach int, aInt hat danach den Wert von int.MaxValue  
    int aInt = (int) aLong;
    //Umwandlung nach object 
    object aObject = aInt as object;
    //ungülgtige Typumwandlung, liste2 erhält den Wert null 
    IList liste2 = aObject as IList;
    
    //ungültige Typumwandlung, löst zur Laufzeit eine InvalidCastException aus,
    //da aObject nicht vom Typ Liste ist. 
    IList liste = (IList)aObject;
    //Löst den Compierfehler  CS0077 aus, da int kein Referenzetyp ist 
    int aInt2 = aLong as int;
    }
}

Zu den Typumwandlungen gehört auch das so genannte "Boxing". Es bezeichnet die Umwandlung zwischen Wert- und Referenztypen. Der Zieltyp wird wie bei der expliziten Konvertierung in Klammern vor den umzuwandelnden Typ geschrieben. Erfolgt dies implizit, so spricht man von "Autoboxing".

Siehe auch: Explizite Typumwandlung


Assemblies

Siehe .net Assemblies


Attribute (Metadaten)

Attribute geben die Möglichkeit Daten für Funktionen, Parameter, Klassen, Strukturen oder Attribute anzugeben. Diese können Informationen für den Compiler enthalten, für die Laufzeitumgebung oder über Reflexion während der Laufzeit ausgelesen werden. In C# werden diese mit eckigen Klammern über dem Ziel angegeben. Das STAThread-Attribut wird z.B. benötigt wenn ein Programm COM Interoperabilität unterstützen soll.

[STAThread()]
public static void Main(string[] argumente)
{
    //... 
}

Unsicherer Code

Durch die Codeverifizierung und .NET-Zugriffsverifizierung werden bei C# Speicherzugriffsfehler verhindert. Dieser Sicherheismechanismus kann abgeschaltet werden. Bei Verwendung von Zeigern werden diese Sicherheitsmechanismen umgangen. Dies ist nur in der Betriebsart "Unsafe Code" möglich.

Beispiel:

using System;
class us_code {
    public static void Main() {
        unsafe {
            int  i = 1; // i hat den Wert 1
            int* p = &i // p zeigt auf den Speicher von i mit dem Wert 1
            Console.WriteLine("p = " + p + ", i = " + i);
            i = 2;      // Sowohl i als auch p haben nun den Wert 2...
            Console.WriteLine("p = " + p + ", i = " + i);
         }
     }
}

Zudem können Klassen und Methoden als unsafe deklariert werden.

Kommentare und Dokumentation

In C# sind, wie in C, C++ und Java, sowohl Zeilen- als auch Blockkommentare möglich.

Zeilenkommentare beginnen dabei mit zwei aufeinanderfolgenden Schrägstrichen (//) und enden in der gleichen Zeile mit dem Zeilenumbruch. Alles was nach den Schrägstrichen folgt, wird bis zum Zeilenende als Kommentar angesehen und vom Compiler übergangen.

Blockkommentare, die sich über mehrere Zeilen erstrecken können, beginnen mit der Zeichenkombination /* und enden mit */.

Sowohl Zeilen- als auch Blockkommentare können zu Beginn oder auch mitten in einer Zeile beginnen. Blockkommentare können in der selben Zeile enden und es kann ihnen Quelltext folgen, der vom Compiler ausgewertet wird. Alles was innerhalb des Blockkommentars steht, wird vom Compiler übergangen.

  1. // Dies ist ein Zeilenkommentar, der mit dem Zeilenumbruch endet
  2. System.Console.WriteLine("Ein Befehl"); // Ein Zeilenkommentar am Zeilenende
  3. /* Dies ist ein Blockkommentar, der in der gleichen Zeile endet */
  4. System.Console.WriteLine("Ein weiterer Befehl"); /* Ein mehrzeiliger
    Blockkommentar */
    System.Console.WriteLine("Noch einer");
  5. System.Console.WriteLine("Befehl 1"); /* Kommentar */ System.Console.WriteLine("Befehl 2");
  6. System.Console/* Kommentar */.WriteLine( /* Kommentar */ "..." );

Hinweis: Es sind auch Kommentare innerhalb eines Befehls z.B. zur Kommentierung einzelner Methodenparameter möglich. Diese Art von Kommentaren sollte aus Gründen der Lesbarkeit und Wartbarkeit vermieden werden.

Zur Dokumentation von Methoden stellt C# in Form von Metadaten (Attribute) einen Mechanismus bereit, der es ermöglicht, eine XML-basierte Dokumentation erzeugen zu lassen.

Zur Dokumentation von Typen (das heißt, Klassen und deren Elemente wie Attribute oder Methoden) steht eine spezielle Form von Zeilenkommentaren bereit. Hierzu beginnt der Zeilenkommentar mit einem weiteren, dritten Schrägstrich (///) und befindet sich direkt über dem zu dokumentierenden Typ (z. B. einer Methode). Es folgen nun XML-Tags, die jeweils eine bestimmte Funktion bei der Dokumentation übernehmen, beispielsweise eine Zusammenfassung durch einen Summary-Tag.

/// <summary>
///  Diese Funktion gib den größeren Betrag zurück
/// </summary> 
/// <param name="a">Der erste Übergabeparameter</param>
/// <param name="b">Der zweite Übergabeparameter</param>
/// <returns>Die Zahl mit dem größeren Betrag  </returns>
/// <remarks>Diese Funktion gibt den größeren Betrag der beiden Übergegeben  <see cref="Int32"/>  zurück. 
/// Sind die Beträge gleich groß ist dies ein Fehler</remarks>
/// <exception cref="ArgumentException">Der Betrag der beiden Zahlen ist gleich groß</exception>
public int GetGreaterAbs(int a, int b)
{
	int absA  = Math.Abs(a);
	int absB = Math.Abs(b);
	if(absA==absB)
	{
		throw new ArgumentException("Der Betrag dieser beiden Zahlen ist gleich groß");
	}
	else if(absA>absB)
	{
		return absA;
	}
	else
	{
		return absB;
	}
}

Alternativ kann auch eine externe Ressource referenziert werden, die die Dokumentation enthält:

 /// <include file='xml_include_tag.doc' path='MyDocs/MyMembers[@name="test"]/*' />

Dokumentation im XMLDoc-API-Dokumentationsformat wird vom Compiler als ein normaler Zeilenkommentar angesehen. Erst durch Angabe der Compiler-Option "/doc" werden Kommentare, die mit drei Schrägstrichen beginnen, als XMLDoc erkannt und aus dem Quellcode in eine Datei extrahiert. Diese kann dann weiterverarbeitet werden, z.B. ähnlich wie bei Javadoc zur Erstellung einer HTML-Hilfe verwendet werden. (z.B mit NDoc)

Siehe auch: Kommentar (Programmierung)

Verfügbare Klassenbibliotheken

  • Collection
  • Threads
  • Reflection
  • CodeDOM
  • Mono
    • Languages
      • CSharp
      • MonoBASIC
      • Python
    • Runtime
      • LateBind
      • MonoBASIC
      • Python
    • Web
      • UI
        • Utils

Schlüsselwörter

abstract as base bool break byte case catch char checked class const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while