Beruflich Dokumente
Kultur Dokumente
Inhaltsverzeichnis
Bedingte Ausführung (if, else, switch)
Schleifen (for, do, while, foreach)
Die Sprunganweisungen break, continue, goto, return, yield return/break
Die using-Anweisung
Objekte und Klassen
Die Klasse namens „object“
Strukturen (struct)
Aufzählungen (Enumerationen)
Flags
Zugriffsmodifikatoren
Datentypen, Operatoren, Eigenschaften und Konstanten
Datentypen und Speicherbedarf
Konstanten (Schlüsselwort const)
Operatoren
Operatoren überladen
Eigenschaften (Schlüsselwörter get, set und value)
Indexer
Darstellung spezieller Zeichen oder Zeichenfolgen („escapen“)
Vererbung
Schnittstellen
Vererbung von Schnittstellen
Das Schlüsselwort base
Versiegelte Klassen
Statische Klassen
Erweiterungsmethoden
Methoden
Anonyme Methoden
Lambdaausdrücke
LINQ
Typumwandlungen
Benutzerdefinierte Typumwandlungen
Parametrische Polymorphie (Generics)
Definitionsseitige Ko- und Kontravarianz
Assemblies
Attribute (Metadaten)
Ausnahmen/Exceptions
Throw
Unsicherer Code
Kommentare und Dokumentation
Schlüsselwörter
Reservierte Schlüsselwörter
Kontextschlüsselwörter
Siehe auch
Literatur
Einzelnachweise
Der else-Block, der nur in Verbindung mit einem if-Block stehen kann, wird nur dann ausgeführt,
wenn die Anweisungen des if-Blocks nicht ausgeführt wurden.
if (Bedingung)
{
Anweisungen;
}
else if (Bedingung)
{
Anweisungen;
}
else
{
Anweisungen;
}
Die switch-Anweisung ist die Darstellung eines Falles, in dem, je nach Wert eines Ausdrucks, andere
Anweisungen ausgeführt werden müssen.
Es wird immer bei dem case-Label fortgefahren, dessen Wert mit dem Ausdruck in der switch-
Anweisung übereinstimmt. Wenn keiner der Fälle zutrifft, wird zum optionalen default-Zweig
gesprungen.
switch (Ausdruck)
{
case Fall_1:
Anweisungen;
break;
case Fall_2:
Anweisungen;
break;
default:
Anweisungen;
break;
}
switch (Ausdruck)
{
case Fall_1:
case Fall_2:
Anweisungen;
break;
}
string cmd;
//...
switch (cmd)
{
case "run":
Anweisungen;
break;
case "save":
Anweisungen;
break;
default:
Anweisungen;
break;
}
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 ausgeführt. 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.
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.
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.");
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.
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:
Hierdurch muss keine temporäre Liste erzeugt werden, die die umgewandelten Zahlen erst
zwischenspeichert und dann zurückgibt:
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:
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;
// 2 Sekunden warten
Thread.Sleep(2000);
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.
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:
try
{
g.DrawString("Hallo Welt!", myFont, Brushes.Black);
}
finally
{
if (myFont != null)
{
myFont.Dispose();
}
}
Jedes Objekt kann durch verschiedene Attribute beschrieben werden und verschiedene Zustände annehmen
und diese auch auslösen.
Ü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 Gerüst eines Objektes
ansehen.
Eine Klasse besitzt Eigenschaften (Variablen), Methoden (die Tätigkeiten darstellen) und Ereignisse, die die
Folge von Zuständen sind bzw. diese auslösen.
Klasse
Eigenschaft(en)
Methode(n)
Ereignis(se)
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; }
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; }
//Ereignisse
public event MotorDelegat MotorEreignis;
public event GeschwindigkeitDelegat GeschwindigkeitGeaendert;
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.
C# fasst eine über das Schlüsselwort struct definierte Struktur als einen Wertetyp auf. 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 Strukturinstanz im Vergleich zu einem Objekt besteht in ihrer Repräsentation im
Speicher. Da keine zusätzlichen Speicherreferenzen benötigt werden, wie es bei einem Objekt erforderlich
ist (Referenztyp), können Strukturinstanzen wesentlich ressourcenschonender eingesetzt werden. So
basieren beispielsweise alle primitiven 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.
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.
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, sodass mehrere
Elemente zu einem neuen kombiniert werden können. Hierzu müssen den zu kombinierenden Elementen
Zweierpotenzen als Werte zugewiesen werden, damit eine Kombination ermöglicht wird.
[Flags]
public enum AccessMode
{
None = 0,
Read = 1,
Write = 2,
// Erhält den Wert 3 (Kombination aus 1 und 2)
ReadAndWrite = Read | Write
}
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
abstract Abstrakte Member sind zwar deklariert, aber nicht implementiert. Die Implementierung erfolgt
zwingend in der abgeleiteten Klasse. Von einer abstrakten Klasse kann keine Instanz erzeugt
werden.
internal internal beschränkt den Zugriff auf Klassen und deren Mitglieder auf eine Assembly.
new Hier ist nicht der Aufruf des Konstruktors gemeint. Mit dem new-Modifizierer wird ein Member
der Basisklasse verdeckt. Der Klassen-Member besitzt dieselbe Signatur, aber eine andere
Funktionalität und steht mit dem verdeckten Member der Basisklasse nicht in Beziehung. Beim
Aufruf sind alle Basisklassenmember mit demselben Namen ausgeblendet. Ohne diesen
Modifizierer gibt der Compiler eine Warnung aus.
override Der Modifizierer override überschreibt die abstrakte oder virtuelle Implementierung einer
Methode oder Eigenschaft bzw. eines Indexers oder Ereignisses der Basisklasse. override-
Member sind automatisch virtual. In der Vererbungshierarchie handelt es sich um eine
weitere Implementierung.
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).
protected Der Modifikator protected erlaubt den Zugriff nur in der eigenen Vererbungshierarchie, also nur
für die deklarierende Klasse und alle abgeleiteten Klassen.
protected Der Modifikator protected internal ist eine Kombination aus den Modifikatoren
internal protected und internal. Die Methode ist sichtbar für die deklarierende und für alle
abgeleiteten Klassen sowie für alle Klassen innerhalb der Assembly. Eine Sichtbarkeit nur für
abgeleitete Klassen innerhalb der Assembly existiert in C# nicht. In diesem Fall muss auf den
Modifikator internal zurückgegriffen werden.
public Auf als public gekennzeichnete Klassen oder Klassenmitglieder (z. B. Methoden oder
Eigenschaften) kann unbeschränkt zugegriffen werden. Sie werden deshalb auch als „öffentlich“
bezeichnet.
sealed sealed kann auf Klassen, Instanzmethoden und Eigenschaften angewendet werden. Von
versiegelten Klassen kann nicht abgeleitet werden. Eine versiegelte Methode überschreibt eine
virtuelle Methode der Basisklasse. Die versiegelte Methode bleibt für erbende Klassen sichtbar,
eine weitere Überschreibung durch sie ist jedoch nicht mehr möglich. Dies ist somit die letzte
Implementierung in der Vererbungshierarchie.
static static-Member sind klassenspezifisch, aber nicht instanzspezifisch. Von statischen Klassen
kann keine Instanz erzeugt werden. Statische Member einer Klasse werden mit dem Namen der
Klasse, nicht mit dem Namen der Objektinstanz aufgerufen (Beispiel: Math.Sqrt()). Für jede
Instanz einer Klasse wird eine separate Kopie aller Instanzenfelder erzeugt, bei statischen
Feldern ist lediglich eine Kopie vorhanden.
virtual Der virtual-Modifizierer gibt an, dass es sich um die erste Implementierung in der
Vererbungshierarchie handelt. Virtuelle Member können in einer abgeleiteten Klasse mit dem
Modifikator override überschrieben werden. Fehlt virtual bei einem Member, handelt es
sich um die einzige Implementierung.
Hinweise:
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 Automatismus, 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 nicht eine 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:
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 sind in C# nicht elementar, sondern Datentyp Bit Suffix Vorz. Alias für (struct type)
objektbasiert. Jeder der in der Tabelle
bool 8 - System.Boolean
aufgeführten Datentypen stellt einen Alias auf
eine Klasse des Namensraumes System dar. byte 8 N System.Byte
Beispielsweise wird der Datentyp bool durch char 16 - System.Char
die Klasse System.Boolean abgebildet.
decimal 128 m, M - System.Decimal
Durch die Objektbasiertheit ist es möglich,
Methoden auf Datentypen anzuwenden: double 64 d, D - System.Double
float 32 f, F - System.Single
1234.ToString();
int i = 17; int 32 J System.Int32
i.ToString();
long 64 l, L J System.Int64
Vergleichbar mit C++, und anders als bei Java, sbyte 8 J System.SByte
gibt es unter C# vorzeichenbehaftete und short 16 J System.Int16
vorzeichenlose Datentypen. Diese werden
durch Voranstellen des Buchstabens s (für uint 32 u, U N System.UInt32
signed, englisch für vorzeichenbehaftet) und ulong 64 ul, UL N System.UInt64
durch Voranstellen des Buchstabens u (für
ushort 16 N System.UInt16
unsigned, englisch für vorzeichenlos)
gekennzeichnet (sbyte, uint, ulong,
ushort, mit Ausnahme von short). Die Gleitkomma-Datentypen (float, double, decimal)
können neben einfacher auch doppelte Genauigkeit aufweisen und unterscheiden sich im Speicherbedarf.
Dadurch ändert sich die Genauigkeit, was in der Anzahl der möglichen Nachkommastellen zum Ausdruck
kommt.
Einem mit const deklarierten „Objekt“ kann nach der Deklaration und Initialisierung kein neuer Inhalt
zugewiesen werden. Das „Objekt“ wird dadurch zu einer Konstanten.
Es muss dabei vom Compiler festgestellt werden können, dass der Wert, der einer Konstante zugewiesen
wird, unveränderlich ist. Es ist also auch möglich, eine Konstante von einem Referenztypen zu definieren,
allerdings darf dieser nur null zugewiesen werden.
Grund dafür ist, dass der Compiler alle Verwendungen von Konstanten bereits zum Zeitpunkt des
Kompilierens ersetzt. Strukturen können nicht konstant sein, da sie in einem Konstruktor einen
Zufallsgenerator benutzen könnten.
Fehlerhafte Zuweisungen einer Konstanten werden mit dem Kompilierfehler CS0133 vom C-Sharp-
Kompilierer moniert.
using System;
using System.IO;
//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 schließen
fs.Close();
//Daten ggf. bearbeiten.
//...
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.
Operatoren
Operatoren führen verschiedene Operationen an Werten durch und erzeugen dabei einen neuen Wert. Je
nach Anzahl der Operanden wird zwischen unäre, binäre und ternäre Operatoren unterschieden. Die
Reihenfolge der Auswertung wird durch die Priorität und Assoziativität bestimmt und kann durch
Klammerausdrücken geändert werden.
Operatorrangfolge Operator Beschreibung
x.y Memberzugriff
x++ Postinkrement
x-- Postdekrement
new T(...) Objekt- und Delegaterstellung
-x Negation
!x Logische Negation
++x Präinkrement
--x Prädekrement
* Multiplikation
3. Multiplikative Operatoren / Division
% Modulo
x << y Linksverschiebung
5. Schiebeoperatoren
x >> y Rechtsverschiebung
x<y Kleiner als
8. Logische, bedingte und NULL- x && y Wertet y nur aus, wenn x den Wert true hat.
Operatoren
x || y Wertet y nur aus, wenn x den Wert false hat.
Operatoren überladen
struct SpecialDouble
{
public SpecialDouble(double argument)
{
_value = argument;
}
// explizite Typkonvertierung:
// Nachkommastellen gehen verloren, Exception kann auftreten
public static explicit operator int(SpecialDouble argument)
{
return (int)argument._value;
}
double _value;
}
Einschränkungen:
Mindestens ein Parameter der Methode für die Überladung muss den Typ besitzen, für den
der Operator überladen wird.
Vergleichsoperatoren können nur paarweise überladen werden.
Es können keine neuen Symbole verwendet werden. Nur die Operator-Symbole sind
erlaubt, die in der Sprache definiert sind. Siehe dazu: Tabelle Operatoren
Die Operatoren =, ., ?:, ->, new, is, sizeof, typeof und => können nicht überladen
werden.
Die Priorität eines benutzerdefinierten Operators kann nicht geändert werden. Der Vorrang
und die Orientierung basiert auf dem Symbol des Operators.
Die Anzahl der Operanden ist ebenfalls an das Symbol des Operators gebunden.
Operatoren, die nicht reserviert sind, können nicht implementiert werden.
Um den Operator [] zu simulieren, können Indexer verwendet werden. Die Operatoren && und ||
können nicht direkt überladen werden. Sie werden über die speziellen überladbaren Operatoren true und
false ausgewertet.
Eine Eigenschaft (property) ist eine Sicht auf eine öffentliche Variable einer Klasse. Die Variable selbst
wird durch einen Zugriffsmodifikator 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 Zugriffsmodifikator
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.
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 Eigenschaft 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:
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.
Ab C# 3.0 ist es möglich, Eigenschaften automatisch zu implementieren. Dies ist eine verkürzte
Schreibweise für Eigenschaften bei denen es sich um den Zugriff auf eine Variable handelt, die innerhalb
der Klasse bzw. Struktur als Feld deklariert wurde.
struct Point
{
public double X
{
get { return this.x; }
set { this.x = value; }
}
public double Y
{
get { return this.y; }
set { this.y = value;}
}
private double x, y;
}
struct Point
{
public double X { get; set; }
public double Y { get; set; }
}
Mit Hilfe des Objektinitialisierer (ab .NET 3.5) ist ein Konstruktor überflüssig:
Indexer
Der Indexer ist die Standardeigenschaft von einem Objekt. Der Aufruf geschieht wie bei einem Array mit
eckigen Klammern.
Beispiel: Zugriff auf die 32 Bits einer int Variable mit Hilfe eines Indexers.
using System;
namespace DemoIndexers
{
class Program
{
struct IntBits
{
public bool this[int index]
{
get { return (bits & (1 << index)) != 0; }
set
{
if (value)
{
bits |= (1 << index);
}
else
{
bits &= ~(1 << index);
}
}
}
Wie bei Eigenschaften kann der Zugriff auf nur lesend oder nur schreibend beschränkt werden, indem man
den get-Accessor bzw. set-Accessor weglässt.
Vererbung
Schnittstellen
Schnittstellen dienen in C# zur Definition von Methoden, ihrer Parameter, ihrer Rückgabewerte sowie von
möglichen Ausnahmen.
Schnittstellen in C# ähneln den Schnittstellen der Programmiersprache Java. Anders als in Java, dürfen
Schnittstellen in C# keine Konstanten enthalten und auch keine Zugriffsmodifikator 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 Klasse durch das Voranstellen des Namens der
Schnittstelle gekennzeichnet werden. Dabei wird die jeweilige Funktion nur dann aufgerufen, wenn der
Zeiger auf das Objekt vom entsprechenden Typ ist:
Ausgabe
A.A
A.B
A.C
Auch Schnittstellen ohne Methodendefinition sind möglich. Sie dienen dann als sogenannte
Markierungsschnittstellen (engl. marker interface). Auf die Verwendung von marker interfaces sollte zu
Gunsten von Attributen verzichtet werden. Schnittstellen können jedoch keine statischen Methoden
definieren.
Das Einbinden einer Schnittstelle erfolgt analog zur Beerbung einer Klasse. Schnittstellen werden per
Konvention mit einem führenden „I“ (für Interface) benannt.
Das Überschreiben einer Methode durch eine abgeleitete Klasse kann mit sealed verhindert werden:
interface IMessage {
string Message { get; }
}
interface IMessage {
string Message { get; }
}
Das Schlüsselwort wird im Zusammenhang von Vererbung genutzt. Vereinfacht gesagt ist die Basisklasse,
das was this für die aktuelle Klasse ist. Java hingegen sieht hierfür das Schlüsselwort super vor.
Nun folgt ein Beispiel, das die Verwendung von base zeigt:
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.
Es ist jedoch möglich versiegelte Klassen mit Erweiterungsmethoden zu erweitern.
Statische Klassen
Analog zu Visual Basic .NET Modulen, können in C# Klassen definiert werden, die ausschließlich aus
statischen Elementen bestehen:
/* Dies würde der Compiler als Fehler ansehen, da diese Methode nicht statisch ist
Erweiterungsmethoden
→ Hauptartikel: Erweiterungsmethode
Ab der Version 3.0 können Datentypen erweitert werden. Hierzu wird eine statische Klasse definiert.
Erweiterungsmethoden (engl. extensions) beinhalten jeweils einen ersten Parameter, der mit dem
Schlüsselwort this beginnt, gefolgt von der gewöhnlichen Definition des Parameters:
using System;
namespace MeinNamespace
{
public static class ExtensionKlasse
{
public static int MalDrei(this int zahl)
{
return zahl * 3;
}
}
public static class Programm
{
public static void Main()
{
// 5979
Console.WriteLine(1993.MalDrei());
}
}
}
Sofern die Klasse ExtensionKlasse für eine andere Klasse sichtbar ist, werden nun alle Zahlen vom Typ int
mit der Methode MalDrei erweitert ohne aber den Typ int wirklich zu ändern. Der Compiler macht hierbei
intern nichts anderes als die Methode MalDrei der Klasse ExtensionKlasse aufzurufen und den Wert 1993
als ersten Parameter zu übergeben.
Methoden
Anonyme Methoden
Anonyme Methoden werden u. a. verwendet, um Code für ein Event zu hinterlegen, ohne in einer Klasse
eine Methode mit einem eindeutigen Namen definieren zu müssen. Anstelle des Methodennamens steht das
Schlüsselwort delegate:
Lambdaausdrücke
Ab der Version 3.0 besteht die Möglichkeit, anonyme Methoden in kürzerer Form zu definieren. Dies
geschieht mit dem Operator Lambda => (ausgesprochen: „wechselt zu“). Auf der linken Seite des
Lambda-Operators werden die Eingabeparameter angegeben, auf der rechten Seite befindet sich der
Anweisungsblock bzw. ein Ausdruck. Handelt es sich um einen Anweisungsblock, spricht man von einem
Anweisungslambda. Ein Ausdruckslambda, wie zum Beispiel x => x * x, ist hingegen ein Delegate,
dessen einzige Anweisung ein return ist. Der Typ der Eingabeparameter kann weggelassen werden,
wenn der Compiler diese ableiten kann. Lambda-Ausdrücke können sich auf äußere Variablen beziehen,
die im Bereich der einschließenden Methode oder des Typs liegen, in dem der Lambda-Ausdruck definiert
wurde.
LINQ
→ Hauptartikel: LINQ
Implementiert wird die Funktionalität durch sogenannte LINQ-Provider, die die namentlich definierten
Methoden zur Verfügung stellen. Einer davon ist zum Beispiel LINQ-to-Objects.
// wähle nun zum Schluss als Element für die Liste namens 'liste'
// den Wert aus dem Property 'Vorname' jedes Elements aus
select m.Vorname;
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.
(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 −1,
//nicht in einem unchecked{}-Block eingeschlossen, wird jedoch eine Ausnahme
geworfen.
unchecked {
int aInt = (int) aLong;
}
//Umwandlung nach object
object aObject = aInt as object;
//ungültige Typumwandlung, liste2 erhält den Wert null
IList liste2 = aObject as IList;
Die unchecked-Anweisung bzw. Anweisungsblock dient dazu den Überlauf einer Ganzzahl zu
ignorieren. Mit checked hingegen wird bei einem Überlauf ein OverflowException ausgelöst.
Zu den Typumwandlungen gehört auch das sogenannte „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“.
Benutzerdefinierte Typumwandlungen
C# erlaubt die Definition von benutzerdefinierten Typumwandlungen. Diese können als explizit oder
implizit markiert werden. Implizite benutzerdefinierte Typumwandlung kommen u. a. bei der
Überladungsauflösung zum tragen, während explizite dann verwendet werden, wenn oben genannte
explizite Typumwandlungssyntax benutzt wird.
Kovarianz: K<out T>; Wenn A Subtyp von B ist, dann ist K<A> Subtyp von K<B>,
Kontravarianz: K<in T>; Wenn A Subtyp von B ist, dann ist K<B> Subtyp von K<A>,
Invarianz: K<T>; Es gibt keine Untertypbeziehung zwischen Instanziierungen von K mit
verschiedenen Typargumenten T.
(Jedem Typparameter kann unabhängig von anderen eine Varianzannotation mitgegeben werden.)
Neben der Fortsetzung von Untertypbeziehungen von Typargumenten auf Instanzen von Generics
beeinflusst die Varianzannotation auch, an welchen Stellen ein Typparameter benutzt werden darf. So sind
beispielsweise die Benutzung von kovarianten Typparametern als Typen von Methodenargumenten und die
Benutzung von kontravarianten Typparametern als Rückgabetypen nicht erlaubt.
Assemblies
Siehe .NET Assemblies
Attribute (Metadaten)
Attribute geben die Möglichkeit Metadaten für Assemblies, Funktionen, Parameter, Klassen, Strukturen,
Enumerationen oder Felder 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 – Die Fehlermeldung lautet sonst: „Es wurde eine
steuernde ‚IUnknown‘ ungleich NULL angegeben, aber entweder war die angeforderte Schnittstelle nicht
'IUnknown', oder der Provider unterstützt keine COM Aggregation.“
[STAThread()]
public static void Main(string[] argumente)
{
//...
}
Attribute selbst sind wiederum Klassen, die von der Klasse Attribute abgeleitet sind, und können beliebig
selbst definiert werden.
Beispiel:
[AttributeUsage(AttributeTargets.All)]
public class Autor : System.Attribute
{
public int Age
{
get;
set;
}
Verwendung:
Ausnahmen/Exceptions
Schema:
try {
// öffnet den Block um den Codeteil, dessen
// Fehler abgefangen werden sollen.
// ...unsichere Verarbeitung...
}
catch (ExceptionTypException exception_data) {
// Fängt alle Exceptions vom Typ ExceptionTypException
// ...tut was wenn ExceptionTypException geworfen wurde...
}
catch (Exception ex){
//Fängt alle Exceptions welche von Exception abgeleitet wurden
// ...tut was wenn Exception geworfen wurde...
}
finally {
// Wird zwingend ausgeführt
}
Es ist nicht zwingend erforderlich, dass immer alle Blöcke (catch, finally) angegeben werden. Den
Umständen entsprechend kann auf einen try-Block auch direkt ein finally-Block folgen, wenn
beispielsweise keine Behandlung einer Ausnahme erwünscht ist. Zudem ist es nicht zwingend erforderlich,
dass auf ein try-catch-Konstrukt ein finally-Block folgen muss. Es ist jedoch nicht möglich, nur
einen try-Block zu definieren. Ein try-Block muss mindestens von einem weiteren Block gefolgt
werden.
Zudem ist es möglich, mehrere catch-Blöcke zu definieren. Wird im try-Bereich eine Ausnahme
ausgelöst, so werden alle vorhandenen catch-Blöcke der Reihe nach durchgegangen, um zu sehen,
welcher Block sich um die Ausnahme kümmert. Somit ist es möglich, gezielt verschiedene Reaktionen auf
Ausnahmen zu programmieren.
Wird eine Ausnahme von keinem catch-Block abgefangen, so wird diese an die nächsthöhere Ebene
weitergegeben.
Exception
SystemException
IndexOutOfRangeException
NullReferenceException
InvalidOperationException
ArgumentException
ArgumentNullException
ArithmeticException
ArithmeticOutOfRangeException
OverflowException
DllNotFoundException
Bei der Implementierung eigener, nicht-kritischer Ausnahmen, ist darauf zu achten, nicht von der Klasse
Exception abzuleiten, sondern von ApplicationException.
Throw
Mittels throw ist es möglich eine, in einem catch-Block, aufgefangene Ausnahme eine Ebene höher zu
„werfen“ (weitergeben). Somit ist es möglich, dass auch nachfolgender Code von der aufgetretenen
Ausnahme informiert wird und somit seinerseits Aktionen unternehmen kann, um auf die Ausnahme zu
reagieren. Der Aufruf-Stack bleibt erhalten.
Durch throw kann auch eine neue Ausnahme ausgelöst werden, beispielsweise eine programmspezifische
Ausnahme, die nicht durch bereits vorhandene C#-Ausnahmen abgedeckt wird.
Unsicherer Code
Durch die Codeverifizierung und .NET-Zugriffsverifizierung werden bei C# Speicherzugriffsfehler
verhindert. 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);
}
}
}
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 derselben Zeile enden und es kann ihnen Quelltext folgen, der vom Compiler
ausgewertet wird. Alles was innerhalb des Blockkommentars steht, wird vom Compiler übergangen.
Hinweis: Es sind auch Kommentare innerhalb einer Anweisung bzw. Deklaration möglich, z. B. zur
Kommentierung einzelner Methodenparameter. 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 gibt 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 Übergebenen <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)
{
return Math.Max(Math.Abs(a), Math.Abs(b));
}
Alternativ kann auch eine externe Ressource referenziert werden, die die Dokumentation enthält:
Schlüsselwörter
Reservierte Schlüsselwörter
Die folgenden Bezeichner sind reservierte Schlüsselwörter und dürfen nicht für eigene Bezeichner
verwendet werden, sofern ihnen nicht ein @-Zeichen vorangestellt wird (zum Beispiel @else).
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
Kontextschlüsselwörter
Die folgenden Bezeichner sind Kontextschlüsselwörter, das heißt innerhalb eines gültigen Kontextes – und
nur dort – haben sie eine besondere Bedeutung. Sie stellen aber keine reservierten Wörter dar, das heißt
außerhalb ihres Kontextes sind sie für eigene Bezeichner erlaubt.
Siehe auch
ADO.NET
ASP.NET
Mono
Anonymer Datentyp
.NET Framework Class Library. (http://msdn.microsoft.com/en-us/library/gg145045(v=VS.11
0).aspx) In: MSDN. Microsoft, abgerufen am 11. Mai 2013 (englisch, Klassenbibliotheken
des .NET-Frameworks).
NDoc (http://ndoc.sourceforge.net/)
Literatur
Andreas Kühnel: Visual C♯ 2012 das umfassende Handbuch. 6., aktualisierte und erweiterte
Auflage. Galileo Press, Bonn 2013, ISBN 978-3-8362-1997-6.
Einzelnachweise
1. Benutzerdefinierte bedingte logische Operatoren. (http://msdn.microsoft.com/de-de/library/aa
691312(v=vs.71).aspx) Microsoft Developer Network (MSDN), abgerufen am 25. April 2011.
Diese Seite wurde zuletzt am 18. März 2023 um 17:28 Uhr bearbeitet.
Der Text ist unter der Lizenz „Creative Commons Attribution/Share Alike“ verfügbar; Informationen zu den Urhebern
und zum Lizenzstatus eingebundener Mediendateien (etwa Bilder oder Videos) können im Regelfall durch Anklicken
dieser abgerufen werden. Möglicherweise unterliegen die Inhalte jeweils zusätzlichen Bedingungen. Durch die
Nutzung dieser Website erklären Sie sich mit den Nutzungsbedingungen und der Datenschutzrichtlinie
einverstanden.
Wikipedia® ist eine eingetragene Marke der Wikimedia Foundation Inc.