Sprachelemente von C-SharpDieser Artikel bietet eine Übersicht einiger Sprachelemente von C#. Bedingte Ausführung (if, else, switch)Der Der if (Bedingung)
{
Anweisungen;
}
else if (Bedingung)
{
Anweisungen;
}
else
{
Anweisungen;
}
Die Es wird immer bei dem 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 (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 (Startausdruck; Gültigkeitsbedingung; Inkrementierungsausdruck)
{
Anweisungen;
}
Die 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 (Typ Variablename in Sequenz)
{
Anweisungen;
}
Die Sprunganweisungen break, continue, goto, return, yield return/breakfor (int i = 0; i < 10; ++i)
{
if (i < 8)
continue;
Console.WriteLine("Continue wurde nicht ausgeführt.");
}
Die Anweisung for (int i = 0; i < 100; ++i)
{
if (i == 5)
break;
Console.WriteLine(i);
}
Die Anweisung int a = 1;
Top:
a++;
if (a <= 5)
goto Top;
Console.WriteLine("a sollte jetzt 6 sein.");
Mit Die Benutzung von Innerhalb einer int result = ImQuadrat(2);
static int ImQuadrat(int x)
{
int a;
a = x*x;
return a;
}
Mit Eine Besonderheit bildet der Ausdruck 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 // 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 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();
// [1] einziges Element mit der aktuellen Uhrzeit
foreach (DateTime now in sequenz)
Console.WriteLine(now);
// 2 Sekunden warten
Thread.Sleep(2000);
// [2] einziges Element wie der Wert aus [1]
// + ca. 2 Sekunden
foreach (DateTime now in sequenz)
Console.WriteLine(now);
}
Die using-AnweisungDie using (Font myFont = new Font("Arial", 10.0f))
{
g.DrawString("Hallo Welt!", myFont, Brushes.Black);
}
Hier wird ein 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 Objekte und KlassenWenn 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 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.
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 .NET (und damit auch C#) unterscheidet zwischen Werttypen und Referenztypen. Werttypen sind jedoch auch (über den Zwischenschritt ValueType) von 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}";
}
}
C# fasst eine über das Schlüsselwort 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 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 public enum Wochentag
{
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. Wochentag Tag = Wochentag.Mittwoch;
System.Console.WriteLine(Tag); // Ausgabe: Mittwoch
System.Console.WriteLine((int)Tag); // Ausgabe: 3
System.Console.WriteLine((Wochentag)3); // Ausgabe: Mittwoch
FlagsNeben 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
}
ZugriffsmodifikatorenZugriffsmodifikatoren 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.
Hinweise:
Datentypen, Operatoren, Eigenschaften und KonstantenC# 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 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: // 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
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 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 ( Konstanten (Schlüsselwort const)Einem mit 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;
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 Zugriffsart auf eine Datei beschreibt (Enum)
const FileMode fm = FileMode.Open;
//Es wird eine Konstante festgelegt, welche den Namen der zu öffnenden 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 schließen
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 OperatorenOperatoren führen verschiedene Operationen an Werten durch und erzeugen dabei einen neuen Wert. Je nach Anzahl der Operanden wird zwischen unären, binären und ternären Operatoren unterschieden. Die Reihenfolge der Auswertung wird durch die Priorität und Assoziativität bestimmt und kann durch Klammerausdrücken geändert werden.
Operatoren überladenC# bietet die Möglichkeit, Operatoren für benutzerdefinierte Datentypen zu implementieren. Als benutzerdefinierter Datentyp gilt eine selbst geschriebene Klasse oder Struktur. Die Implementierung geschieht mit öffentlichen statischen Methoden. Statt eines Namens tragen sie das Schlüsselwort struct SpecialDouble
{
public SpecialDouble(double argument)
{
_value = argument;
}
// SpecialDouble lhs = 9d, rhs = 0.5, result;
// result = lhs ^ rhs; // Compiler ersetzt Operator ^ mit dieser Methode
public static SpecialDouble operator ^(SpecialDouble lhs, SpecialDouble rhs)
{
return Math.Pow(lhs._value, rhs._value);
}
// SpecialDouble lhs = 9d; // 9d wird mit 'new SpecialDouble(9d)' ersetzt
public static implicit operator SpecialDouble(double argument)
{
return new SpecialDouble(argument);
}
public static implicit operator double(SpecialDouble argument)
{
return argument._value;
}
// explizite Typkonvertierung:
// Nachkommastellen gehen verloren, Exception kann auftreten
public static explicit operator int(SpecialDouble argument)
{
return (int)argument._value;
}
double _value;
}
Einschränkungen:
Um den Operator Die Operation Die Operation 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 Zugriffsmodifikator wie 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 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 public class EigenschaftBeispiel
{
private string _wohnort;
public string Wohnort
{
get
{
return _wohnort;
}
set
{
_wohnort = "12345 " + value;
}
}
}
Durch das „Weglassen“ des Schlüsselwortes Beispiel für den Zugriff auf die oben definierte Eigenschaft EigenschaftBeispiel instanz = new EigenschaftBeispiel();
instanz.Wohnort = "Musterstadt";
Console.WriteLine(instanz.Wohnort);
// Ausgabe: ''12345 Musterstadt''
Würde bei der obigen Definition der Eigenschaft ‚Wohnort‘ der Neben dem einfachen Setzen oder Lesen einer Eigenschaft, können im 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. Beispiel anhand der Struktur Point: 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;
}
Das gleiche Beispiel mit Hilfe automatisch implementierter Eigenschaften: struct Point
{
public double X { get; set; }
public double Y { get; set; }
}
Mit Hilfe des Objektinitialisierers (ab .NET 3.5) ist ein Konstruktor überflüssig: Point p = new Point { X = 1.2, Y = -3.75 }; // Objektinitialisierer
Console.WriteLine(p.X); // Ausgabe: 1,2
Console.WriteLine(p.Y); // Ausgabe: −3,75
IndexerDer Indexer ist die Standardeigenschaft von einem Objekt. Der Aufruf geschieht wie bei einem Array mit eckigen Klammern. Beispiel: Zugriff auf die 32 Bits einer 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);
}
}
}
private int bits;
}
static void Main(string[] args)
{
IntBits bits = new IntBits();
Console.WriteLine(bits[6]); // False
bits[2] = true;
Console.WriteLine(bits[2]); // True
bits[2] = false;
Console.WriteLine(bits[2]); // False
}
}
}
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. Unterschiede zu Arrays, Methoden und Eigenschaften:
Darstellung spezieller Zeichen oder Zeichenfolgen („escapen“)
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-16). VererbungSchnittstellenMehrfachvererbung wird in C# nur in Form von Schnittstellen (engl. interfaces) unterstützt. Schnittstellen 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 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: public static void Main()
{
Klasse k = new Klasse();
(k as A).MethodeA();
(k as B).MethodeA();
k.MethodeA();
Console.ReadLine();
}
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. Vererbung von Schnittstellen
Das Überschreiben einer Methode durch eine abgeleitete Klasse kann mit interface IMessage {
string Message { get; }
}
public class MyClass : IMessage {
public virtual void OnMessage() {
// kann von abgeleiteter Klasse implementiert werden
}
// kann nicht überschrieben werden
public sealed string Message {
get {
OnMessage();
return "MyClass";
}
}
}
Ein Interface kann auch durch eine Basisklasse implementiert werden: interface IMessage {
string Message { get; }
}
public class Messenger {
public string Message {
get { return "Messenger"; }
}
}
public class MyClass : Messenger, IMessage {
// interface bereits implementiert
}
Das Schlüsselwort baseDas Schlüsselwort wird im Zusammenhang von Vererbung genutzt. Vereinfacht gesagt ist die Basisklasse, das was Nun folgt ein Beispiel, das die Verwendung von 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 KlassenVersiegelte 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 Statische KlassenAnalog zu Visual Basic .NET Modulen, können in C# Klassen definiert werden, die ausschließlich aus statischen Elementen bestehen: static class MeineStatischeKlasse
{
public static int StatischeEigenschaft
{
get { return 5979; }
}
public static void StatischeMethode()
{
}
/* Dies würde der Compiler als Fehler ansehen, da diese Methode nicht statisch ist
public void NichtStatischeMethode()
{
}
*/
}
ErweiterungsmethodenAb 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. MethodenAnonyme MethodenAnonyme 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: Button btn = new Button()
{
Name = "MeinButton",
Text = "Klick mich!"
};
btn.Click += delegate(object sender, EventArgs e)
{
Button button = (Button)sender;
MessageBox.Show("Der Button '" + button.Name + "' wurde angeklickt!");
};
LambdaausdrückeAb der Version 3.0 besteht die Möglichkeit, anonyme Methoden in kürzerer Form zu definieren. Dies geschieht mit dem Operator Lambda Button btn = new Button()
{
Name = "MeinButton",
Text = "Klick mich!"
};
// 'sender' wird implizit als System.Object betrachtet
//
// 'e' wird implizit als System.EventArgs betrachtet
btn.Click += (sender, e) => {
Button button = (Button)sender;
MessageBox.Show("Der Button '" + button.Name + "' wurde angeklickt!");
};
LINQLINQ definiert drei Dinge:
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. // nimm die Liste der Mitarbeiter und
// schreibe jedes Element nach 'm'
var liste = from m in this.MitarbeiterListe
// lies das Property 'Nachname' und nimm nur die
// Elemente, die gleich 'Mustermann' sind
where m.Nachname == "Mustermann"
// sortiere zuerst ABsteigend nach dem Property 'Vorname'
// dann AUFsteigend nach dem Property 'MitarbeiterNummer'
orderby m.Vorname descending, m.MitarbeiterNummer
// 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;
In MySQL könnte der obere Ausdruck bspw. folgendermaßen aussehen: SELECT m.Vorname FROM MitarbeiterListe AS m
WHERE m.Nachname = "Mustermann"
ORDER BY m.Vorname DESC, m.MitarbeiterNummer ASC
TypumwandlungenIn 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:
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;
//ungültige Typumwandlung, löst zur Laufzeit eine InvalidCastException aus,
//da das Object nicht vom Typ Liste ist.
IList liste = (IList)aObject;
//Löst den Kompilierfehler CS0077 aus, da int kein Referenztyp ist
int aInt2 = aLong as int;
}
}
Die 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 TypumwandlungenC# 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. Siehe auch: Operatoren überladen, Explizite Typumwandlung Parametrische Polymorphie (Generics)Bei der Definition von Interfaces, Klassen, Strukturen, Delegate-Typen und Methoden können Typparameter angegeben und gegebenenfalls mit Einschränkungen versehen werden. Werden Typparameter angegeben, so erhält man generische Interfaces, Klassen, und so weiter. Bei der späteren Benutzung solcher Generics füllt man die Typparameter mit konkreten Typargumenten. Definitionsseitige Ko- und KontravarianzAb C#4 ist es möglich, Typparametern von Interfaces und Delegate-Typen eine Varianzannotation mitzugeben. Diese beeinflusst die Subtyprelation. Angenommen, der generische Typ heiße
(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. AssembliesAttribute (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;
}
public Autor(string name)
{
//...
}
}
Verwendung: [Autor("Name des Autors")]
[Autor("Name des 2. Autors",Age=20)]
[Version(1,0,0,0)]
public class IrgendeineKlasse()
{
//...
}
Abfrage von Attributen: Autor[] a = typeof(IrgendeineKlasse).GetCustomAttributes(typeof(Autor), false) as Autor[];
Ausnahmen/ExceptionsSchema: 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 ( Zudem ist es möglich, mehrere Wird eine Ausnahme von keinem Beispiele vordefinierter Ausnahme-Klassen:
Bei der Implementierung eigener, nicht-kritischer Ausnahmen, ist darauf zu achten, nicht von der Klasse ThrowMittels Durch Unsicherer CodeDurch 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);
}
}
}
Zudem können Klassen und Methoden als unsafe deklariert werden. Kommentare und DokumentationIn 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 derselben Zeile enden und es kann ihnen Quelltext folgen, der vom Compiler ausgewertet wird. Alles was innerhalb des Blockkommentars steht, wird vom Compiler übergangen. // Dies ist ein Zeilenkommentar, der mit dem Zeilenumbruch endet
System.Console.WriteLine("Ein Befehl"); // Ein Zeilenkommentar am Zeilenende
/* Dies ist ein Blockkommentar, der in der gleichen Zeile endet */
System.Console.WriteLine("Ein weiterer Befehl"); /* Ein mehrzeiliger
Blockkommentar */System.Console.WriteLine("Noch ein Befehl");
System.Console.WriteLine("Befehl 1"); /* Kommentar */ System.Console.WriteLine("Befehl 2");
System.Console/* Kommentar */.WriteLine( /* Kommentar */ "..." )
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. Alternativ kann auch ein Blockkommentar verwendet werden, der mit /** statt /* eingeleitet wird. /// <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));
}
Statt die Dokumentation explizit einzufügen, 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 (NDoc). Wenn die Entwicklungsumgebung dazu in der Lage ist, kann sie die formalisierte Dokumentation dazu benutzen, um an Verwendungsstellen aufgerufener Funktionen deren Beschreibung als Tooltip anzuzeigen; ebenso bei der Eingabe von Funktionsparametern. SchlüsselwörterReservierte SchlüsselwörterDie folgenden Bezeichner sind reservierte Schlüsselwörter und dürfen nicht für eigene Bezeichner verwendet werden, sofern ihnen nicht ein abstract as KontextschlüsselwörterDie 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. add alias ascending Siehe auch
Literatur
Einzelnachweise
|