Dieser Artikel bietet eine Übersicht einiger Sprachelemente von C#.
Bedingte Ausführung (if, else, switch) Der if-Block hat zur Folge, dass Anweisungen zwischen geschweiften Klammern nur in Kraft treten, wenn die angegebene Bedingung erfüllt ist, also ihr Wert true ist.
Der else-Block kann nur in Verbindung mit einem if-Block stehen und wird nur ausgeführt, wenn die Anweisungen des if-Blocks nicht ausgeführt wurden.
if (Bedingung) { Anweisungen; } else if (Bedingung) { Anweisungen; } else { Anweisungen; }
Die switch-Anweisung stelle eine Fallunterscheidung dar, welche je nach Wert des Ausdrucks, andere Anweisungen ausgeführt.
Es wird immer bei dem case-Label fortgefahren, dessen Wert mit dem Ausdruck in der switch-Anweisung übereinstimmt. Wenn kein Fall zutrifft, wird zum optionalen default-Zweig gesprungen.
switch (Ausdruck) { case Fall_1: Anweisungen; break; case Fall_2: Anweisungen; break; default: Anweisungen; break; }
Im Gegensatz zu C und C++ hat die C#-switch-Anweisung keine fall-through-Semantik. Nicht-leere Zweige müssen mit einem break, continue, goto, throw oder return enden; leere Zweige können jedoch von weiteren case-Zweigen fortgesetzt werden, um mehrere Fälle mit einem Satz von Anweisungen zu behandeln:
switch (Ausdruck) { case Fall_1: case Fall_2: Anweisungen; break; }
In C# sind auch Strings als Prüfausdruck erlaubt:
string cmd; //... switch (cmd) { case "run": Anweisungen; break; case "save": Anweisungen; break; default: Anweisungen; break; }
Schleifen (for, do, while, foreach) Wird eine for-Schleife ausgeführt, wird zuerst der Startausdruck gültig. Ist dieser vollständig bearbeitet, werden die Anweisungen im Schleifenrumpf und anschließend der Inkrementierungsausdruck solange wiederholt abgearbeitet, bis die Gültigkeitsbedingung false ergibt, d. h., ungültig wird.
for (Startausdruck; Gültigkeitsbedingung; Inkrementierungsausdruck) { Anweisungen; }
Die while-Schleife ist dagegen recht primitiv: sie wiederholt die Anweisungen, solange die Bedingung true zurückgibt. Die Bedingung der while-Schleife wird immer vor dem Anweisungsblock ausgewertet. Wenn die Bedingung von Anfang an nicht erfüllt ist, wird der Schleifenrumpf nicht durchlaufen.
while (Bedingung) { Anweisungen; }
Die Bedingung der Do-While-Schleife wird immer nach dem Anweisungsblock geprüft. Die Schleife wird daher mindestens ein Mal durchlaufen.
do { Anweisungen; } while (Bedingung);
Mit der foreach-Schleife wird durch alle Mitglieder einer Sequenz iteriert. In der Schleife besteht nur lesender Zugriff auf die Schleifenvariable.
foreach (Typ Variablename in Sequenz) { Anweisungen; }
Die Sprunganweisungen break, continue, goto, return, yield return/break for (int i = 0; i < 10; ++i) { if (i < 8) continue; Console.WriteLine("Continue wurde nicht ausgeführt."); }
Die Anweisung continue veranlasst den nächsten Durchlauf einer Schleife (Dabei wird der restliche Code im Schleifenkörper nicht abgearbeitet). Im Beispiel wird der Text nur zweimal ausgegeben.
for (int i = 0; i < 100; ++i) { if (i == 5) break; Console.WriteLine(i); }
Die Anweisung break veranlasst das Programm, die nächste umschließende Schleife (oder das umschließende switch) zu verlassen. In diesem Beispiel werden nur die Zahlen 0, 1, 2, 3 und 4 ausgegeben.
int a = 1; Top: a++; if (a <= 5) goto Top; Console.WriteLine("a sollte jetzt 6 sein.");
Mit goto springt das Programm an das angegebene Sprungziel.
Die Benutzung von goto sollte jedoch möglichst vermieden werden, da dadurch der Quellcode in der Regel unleserlicher wird. Es gilt jedoch als akzeptiertes Sprachmittel, um tief verschachtelte Schleifen zu verlassen, da in diesen Fällen der Code mit goto lesbarer ist als durch mehrfache Verwendung von break oder anderen Sprachmitteln. Siehe auch Spaghetticode.
Innerhalb einer switch-Anweisung kann mittels goto case bzw. goto default zu einem der Fälle gesprungen werden.
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. Wertetyp als Rückgabewert zurückgeliefert. Methoden ohne Rückgabewert werden mit dem Schlüsselwort void gekennzeichnet.
Eine Besonderheit bildet der Ausdruck yield return. Zweck ist es, in verkürzter Schreibweise eine Rückgabesequenz für eine Methode oder eine Eigenschaft zu erzeugen. Der Compiler nutzt hierzu einen eigenen Typ, der von System.Collections.Generic.IEnumerable<T> abgeleitet ist und somit mit einem foreach-Block durchlaufen werden kann. Jeder Aufruf von yield return fügt bis zum Verlassen der Methode/Eigenschaft ein neues Element der Sequenz hinzu. Wird das Konstrukt nicht einmal aufgerufen, ist die Sequenz leer:
private int[] zahlen = new int[] { 5980, 23980 };
public IEnumerable<int> ZahlenMinusEins { get
foreach (int zahl in this.zahlen)
yield return zahl - 1;
public IEnumerable<double> ToDouble(IEnumerable<int> intZahlen) { foreach (int zahl in intZahlen)
yield return (double)zahl;
Hierdurch muss keine temporäre Liste erzeugt werden, die die umgewandelten Zahlen erst zwischenspeichert und dann zurückgibt:
private int[] zahlen = new int[] { 5980, 23980 };
// bspw. aus einer Eigenschaft heraus public IEnumerable<int> ZahlenMinusEins { get
List<int> rückgabe = new List<int>();
foreach (int zahl in this.zahlen)
rückgabe.Add(zahl −1);
return rückgabe;
Jedes Element, das zurückgegeben wird, muss implizit in den Typ der Rückgabesequenzelemente konvertierbar sein.
Zum Abbrechen des Vorgangs kann die Anweisung yield break verwendet werden:
// bspw. aus einer Methode heraus public IEnumerable<double> ToDouble(IEnumerable<int> intZahlen) { int i = 0; foreach (int zahl in intZahlen)
if (i++ == 3)
// nach 3 Durchläufen beenden yield break;
yield return (double)zahl;
Die Anweisungen return und yield return können nicht gemeinsam verwendet werden.
Bei der Verwendung muss beachtet werden, dass die zurückgegebene Sequenz die ihr zugrundeliegende Logik verzögert ausführt, was bedeutet, dass der erste Durchlauf ein und derselben Sequenz andere Werte liefern kann, als beim zweiten Mal:
// interner Zähler private int i = 0;
public IEnumerable<DateTime> GetNow() { // aktuelle Uhrzeit (einziges Element) yield return DateTime.Now; }
public IEnumerable<int> GetZahl() { // internen Zähler um 1 erhöhen (einziges Element) yield return this.i++; }
public void TestZahl() { IEnumerable<int> sequenz = this.GetZahl();
foreach (int zahl in sequenz) Console.WriteLine(zahl); // 0
foreach (int zahl in sequenz) Console.WriteLine(zahl); // 1
foreach (int zahl in sequenz) Console.WriteLine(zahl); // 2 }
public void TestZeit() { IEnumerable<DateTime> sequenz = this.GetNow();
// einziges Element mit der aktuellen Uhrzeit foreach (DateTime now in sequenz) Console.WriteLine(now);
// 2 Sekunden warten Thread.Sleep(2000);
// einziges Element wie der Wert aus // + ca. 2 Sekunden foreach (DateTime now in sequenz) Console.WriteLine(now); }
Die using-Anweisung Die using-Anweisung definiert einen Geltungsbereich, an dessen Ende der Speicher von nicht mehr benötigten Objekten automatisch freigegeben wird. Bei begrenzten Ressourcen wie Dateihandler stellt die using-Anweisung sicher, dass diese immer ausnahmesicher bereinigt werden.
using (Font myFont = new Font("Arial", 10.0f)) { g.DrawString("Hallo Welt!", myFont, Brushes.Black); }
Hier wird ein Font-Objekt erzeugt, das am Ende des Blocks automatisch durch Aufruf seiner Dispose-Methode wieder freigegeben wird. Dies geschieht selbst dann, wenn in dem Block eine Ausnahme ausgelöst wird. Der Vorteil liegt in der vereinfachten Schreibweise, denn intern wird daraus folgendes Konstrukt, das ansonsten manuell so formuliert werden müsste:
Font myFont = new Font("Arial", 10.0f);
try { g.DrawString("Hallo Welt!", myFont, Brushes.Black); } finally { if (myFont != null)
myFont.Dispose();
Klassen müssen die System.IDisposable-Schnittstelle implementieren, damit die using-Anweisung auf diese Weise eingesetzt werden kann.
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.
Übertragen auf die objektorientierte Programmierung und C# ist ein Objekt ein Exemplar (siehe Schlüsselwort new) einer Klasse. Eine Klasse kann man dabei als Bauplan oder Baustelle von Objekten ansehen.
Jedes Objekt kann mittels der in ihm enthaltenen Felder (Variablen) verschiedene Zustände annehmen. Natürlich kann jede Klasse auch Methoden (Funktionen, die Tätigkeiten bewirken) anbieten. Zugriffe auf die Variablen können mittels Eigenschaften, einer speziellen Art von Funktionen die nach außen hin wie Felder aussehen, auf bestimmte zulässige Änderungen beschränkt werden, wodurch auch sogenannte Ereignisse ausgelöst werden können, über deren Stattfinden sich andere Objekte informieren lassen können. Wie in der objektorientierten Programmierung üblich, können sich alle vier Arten von Klassenbestandteilen wahlweise sowohl auf die einzelnen Exemplare als auch auf die Klasse als solche beziehen.
Klasse Feld(er) Eigenschaft(en) Methode(n) Ereignis(se)
Beispiel für den Bauplan eines Autos: class Auto { // Konstruktor, dient zur Erzeugung public Auto(string name, System.Drawing.Color farbe)
this.GeschwindigkeitKMH = 0; this.Name = name; this.Farbe = farbe;
// Eigenschaften: public double GeschwindigkeitKMH { get; private set; } public string Name { get; private set; } public System.Drawing.Color Farbe { get; private set; }
// Feld: private bool motorLaeuft = false;
// Methoden: public void Beschleunigung(double AenderungKMH)
this.GeschwindigkeitKMH += AenderungKMH; GeschwindigkeitGeaendert(this.GeschwindigkeitKMH);
public void VollBremsung()
this.GeschwindigkeitKMH = 0d; GeschwindigkeitGeaendert(this.GeschwindigkeitKMH);
public void MotorStarten()
if (!this.motorLaeuft)
this.motorLaeuft = true; MotorEreignis(this.motorLaeuft);
public void AutoStoppen()
if (this.GeschwindigkeitKMH != 0d) { VollBremsung(); } if (this.motorLaeuft)
this.motorLaeuft = false; MotorEreignis(this.motorLaeuft);
public void Umlackieren(System.Drawing.Color neueFarbe) { this.Farbe = neueFarbe; }
// Delegat-Typen. Sie könnten auch außerhalb der Klasse definiert sein. public delegate void MotorDelegat(bool laeuft); public delegate void GeschwindigkeitDelegat(double geschwindigkeit);
// Ereignisse public event MotorDelegat MotorEreignis; public event GeschwindigkeitDelegat GeschwindigkeitGeaendert;
Die Klasse namens „object“ Die Klasse object (ein Alias für System.Object) ist ein Referenztyp, von dem jede Klasse abgeleitet wird. So kann durch implizite Typumwandlung jeder Objekttyp in object umgewandelt werden.
.NET (und damit auch C#) unterscheidet zwischen Werttypen und Referenztypen. Werttypen sind jedoch auch (über den Zwischenschritt ValueType) von object abgeleitet. Deshalb kann (mittels eines boxing/unboxing genannten Verfahrens) auch eine Variable vom Typ object auf einen Werttyp, z. B. int verweisen.
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 { get; } public double Y { get; } public double Z { get; }
public Koordinate(double x, double y, double z)
X = x; Y = y; Z = z;
public override string ToString()
return $"x: {X}, y: {Y}, z: {Z}";