C#-Crashkurs
C# (ausgesprochen als C Sharp) ist eine objekt- und ereignisorientierte Programmiersprache von Microsoft, die für die .NET-Strategie entwickelt wurde. C#-Anwendungen werden in einen Zwischencode (CIL, Common Intermediate Language) übersetzt, der dann zur Laufzeit in Maschinencode übersetzt bzw. ausgeführt wird. Dadurch ist die Programmiersprache plattformunabhängig. Es muss lediglich eine CLI-Implementierung für die jeweilige Plattform existieren. Die gängigste Implementierung ist das Microsoft .NET Framework für Windows.
Dieser Crashkurs beschreibt die Grundlagen der Programmiersprache C# (in der Microsoft-Welt oftmals auch als Visual C# bezeichnet). Dieser Crashkurs soll lediglich als Referenz bzw. zum Auffrischen der Kenntnisse dienen. Falls Sie an einem ausführlichen Tutorial zu C# interessiert sind, so können Sie unser Tutorial auf unserer Partnerwebsite besuchen.
Syntax
Der Syntax von C# orientiert sich an anderen C-ähnlichen Programmiersprachen (wie z. B. C++ oder Java). In C# haben Sie Anweisungen, welche mit einem Semikolon abgeschlossen werden müssen. Des Weiteren können Anweisungen gruppiert werden. Hierfür dient ein sogenannter Anweisungsblock, welcher mit Hilfe von geschweiften Klammern notiert wird. Solche Anweisungsblöcke kommen z. B. bei Abfragen, Schleifen oder Funktionen zum Einsatz. An einigen Stellen (z. B. Abfragen oder Schleifen) können die geschweiften Klammern weggelassen werden, wenn innerhalb des Anweisungsblocks nur eine Anweisung notiert wird.
Variablen und Datentypen
Eine Variable ist immer an einen Datentyp gebunden. Dieser muss bei der Deklaration angegeben werden. Zusätzlich muss ein Name angegeben werden, welcher innerhalb des Gültigkeitsbereichs eindeutig sein muss und sich nicht mit anderen Bestandteilen (z. B. Klassen- oder Schnittstellennamen) überschneiden darf.
int meineZahl;
Für eine bessere Lesbarkeit des Quellcodes empfiehlt sich die Angabe eines Datentyppräfixes im Variablennamen. Dies sieht z. B. so aus:
int iMeineZahl;
Möchten Sie einer Variablen einen Wert zuweisen, so benötigen Sie den Zuweisungsoperator =
:
iMeineZahl = 123;
Die erste Zuweisung einer Variablen wird auch als Variableninitialisierung bezeichnet. Die Deklaration und Initialisierung kann bei Bedarf auch kombiniert werden und somit innerhalb einer einzelnen Anweisung durchgeführt werden:
int iMeineZahl = 123;
An Stelle zur expliziten Angabe eines Datentyps kann auch das Schlüsselwort var
verwendet werden. Dabei steht
der Datentyp zwar noch nicht bei der Deklaration fest, jedoch wird die Variable bei der Initialisierung an den Datentyp
gebunden, d. h. wurde einmal ein Wert zugewiesen, so kann bei einer zweiten Zuweisung der Datentyp nicht mehr geändert werden.
Folgender Code wäre möglich:
var iMeineZahl = 123; iMeineZahl = 456;
Folgender Code hingegen würde einen Compilerfehler auslösen:
var iMeineZahl = 123; iMeineZahl = 4.56;
Es ist also letztendlich Geschmackssache, welche Schreibweise man bevorzugt. Die Notation mit var
ist kürzer,
wohingegen bei der Notation mit dem Datentyp sofort erkennbar ist, welchen Datentyp die Variable hat.
C# kennt keine primitive Datentypen. In C# wird jedoch zwischen Werttypen und Verweistypen unterschieden. Von den Werttypen gibt es zwei unterschiedliche Arten: Strukturen und Enumerationen (Aufzählungen). Strukturen sind mit Klassen zu vergleichen. Der Unterschied liegt im Prinzip darin, dass eine Struktur ein Wertetyp ist und somit auch auf dem Stack-Speicher abgelegt wird. Alle Datentypen, welche Sie u. U. von anderen Programmiersprachen als primitive Datentypen kennen, sind in C# als Struktur definiert. Für eine kürzere Notation gibt es Aliase, welche auf die jeweilige Struktur zeigen. Im Regelfall wird nicht der Strukturname, sondern der Alias notiert. Die folgende Tabelle zeigt diese „vordefinierten“ Datentypen:
Datentyp | Alias | Speicherbedarf | Wertebereich | Suffix | Beschreibung |
---|---|---|---|---|---|
Boolean | bool | 8 Bits | true oder false | - | Wahrheitswert |
Char | char | 16 Bits | '\u0000' .. '\uFFFF' | - | Unicode-Zeichen |
SByte | sbyte | 8 Bits | -128 .. 127 | - | Ganzzahl |
Byte | byte | 8 Bits | 0 .. 255 | - | Ganzzahl |
Int16 | short | 16 Bits | -32.768 .. 32.767 | - | Ganzzahl |
UInt16 | ushort | 16 Bits | 0 .. 65.535 | - | Ganzzahl |
Int32 | int | 32 Bits | -2.147.483.648 .. 2.147.483.647 | - | Ganzzahl |
UInt32 | uint | 32 Bits | 0 .. 4.294.967.296 | u | Ganzzahl |
Int64 | long | 64 Bits | -9.223.372.036.854.775.808 .. 9.223.372.036.854.775.807 | l | Ganzzahl |
UInt64 | ulong | 64 Bits | 0 .. 18.446.744.073.709.551.616 | ul | Ganzzahl |
Single | float | 32 Bits | ±1,4E−45 .. ±3,4E+38 | f | Gleitkommazahl |
Double | double | 64 Bits | ±4,9E−324 .. ±1,7E+308 | - | Gleitkommazahl |
Decimal | decimal | 128 Bits | ±1,0E−28 .. ±7,9E+28 | m | Gleitkommadezimalzahl |
Enumerationstypen werden immer dann verwendet, wenn bestimmte Zustände oder Werte in Form eines Namens abgelegt werden sollen. Eine Enumeration enthält eine Aufzählung mehrerer solcher Namen:
enum Monate { Januar, Februar, Maerz, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember }
Der Zugriff auf einen Enumerationswert erfolgt mit Hilfe des Punktoperators:
Monate eAkteullerMonat = Monate.Februar;
Bei Bedarf können Sie den einzelnen Namen auch noch einen nummerischen Wert geben:
enum Farben { Rot = 0xFF0000, Gruen = 0x00FF00, Blau = 0x0000FF }
Kommen wir als nächstes zu den Verweistypen: Die Daten (dabei handelt es sich um ein Objekt) von Verweistypen werden auf
dem Heap gespeichert. Eine Referenz zum Heap wird jedoch weiterhin auf dem Stack gespeichert. Sie können einer Variablen für
einen Verweistyp über den Wert null
mitteilen, dass es kein Objekt gibt (Referenz „ins Leere“). Alle Klassen
leiten automatisch von der Klasse Object
ab. Diese Klasse ist zudem über den Alias object
erreichbar.
Eine weitere Klasse, mit welcher Sie oft arbeiten werden, ist String
. String
ermöglicht es Ihnen,
Zeichenketten zu speichern. Auch für die String
-Klasse ist ein Alias verfügbar: string
. Auf das Thema
Zeichenketten und Objektorientierung gehen wir jedoch später noch genauer ein.
Zahlen und Mathematik
Ganzzahlen (auch Festpunktzahlen genannt) werden in C# i. d. R. in der Dezimalschreibweise notiert:
int iZahl = 123;
Optional ist es auch möglich, eine Zahl in hexadezimaler Schreibweise anzugeben. Dafür muss das Suffix 0x
und die Zahl in hexadezimaler Schreibweise angegeben werden:
int iZahl = 0x7B;
Bei Gleitkommazahlen (oftmals auch als Gleitpunktzahlen bezeichnet) muss darauf geachtet werden, dass als Trennzeichen nicht das Komma, sondern der Punkt verwendet wird:
double dZahl = 123.45;
C# unterstützt die Verwendung von Operatoren für die mathematischen Grundrechenarten: +
(Addition),
-
(Subtraktion), *
(Multiplikation) und /
(Division). C# beachtet die Punkt-Vor-Strich-Regelung
und erlaubt die Verwendung von Klammern.
O = G + M; V = (G * h) / 3;
Die Klasse Math
verfügt über einige statische Funktionen, welche Ihnen beim Arbeiten mit Zahlen behilflich sein
können. Die Funktionen Min()
und Max()
können dazu verwendet werden, den kleineren bzw. größeren Wert
von zwei übergebenen Zahlen zu ermitteln:
int iMin = Math.Min(a, b); int iMax = Math.Max(a, b);
Die Funktion Pow()
ermöglicht es Ihnen, eine Potenzrechnung durchzuführen. Mit der Funktion Sqrt()
können Sie die Quadratwurzel einer Zahl berechnen.
double dZahl = 9; double dQuadrat = Math.Pow(dZahl, 2); double dWurzel = Math.Sqrt(dZahl);
Um Zahlen zu runden, können Sie die Funktionen Floor()
(Zahl abrunden), Ceiling()
(Zahl aufrunden)
und Round()
(Zahl kaufmännisch runden) verwenden. Der Funktion Round()
kann bei Bedarf ein Parameter
übergeben werden, mit welchem Sie festlegen können, auf wie viele Nachkommastellen die Zahl gerundet werden soll.
double dZahl = 2.54; double dKleinereZahl = Math.Floor(dZahl); double dGroessereZahl = Math.Ceiling(dZahl);
Zeichenketten
Zeichenketten werden in C# durch den Datentyp string
(Alias für die Klasse String
) dargestellt.
Zeichenketten werden in doppelten Anführungszeichen notiert. Ein einzelnes Zeichen (Datentyp char
) hingegen
wird in einfachen Anführungszeichen angegeben. string
gehört jedoch, anders als die meisten anderen Datentypen
(wie z. B. int
, double
und char
), zu den Verweistypen, d. h. die Zuweisung des Werts
null
(keine Referenz auf ein Objekt) ist möglich und der eigentliche Wert wird auf dem Heap gespeichert.
string sName = "Max Mustermann";
Zeichenketten können über das Pluszeichen +
kombiniert werden. Diesen Vorgang nennt man auch Verkettung.
string sVorname = "Max"; string sNachname = "Mustermann"; string sName = sVorname + " " + sNachname;
Die Klasse String
enthält die Eigenschaft Length
, welche die Länge der Zeichenkette enthält:
string sName = "Max Mustermann"; int iLaenge = sName.Length;
Auf ein Objekt des Datentyps string
können Sie mit Hilfe des Indexoperators []
zugreifen, wie wenn
es ein Array wäre. Dadurch haben Sie die Möglichkeit, einzelne Zeichen direkt auszulesen:
string sName = "Max Mustermann"; char cErstesZeichen = sName[0]; char cZweitesZeichen = sName[1];
Die Klasse String
enthält zudem auch einige Methoden: StartsWith()
(prüft ob die Zeichenkette mit dem
angegebenen Zeichen oder der Zeichenkette beginnt), EndsWith()
(prüft ob die Zeichenkette mit dem angegebenen
Zeichen oder der Zeichenkette beginnt), IndexOf()
(erstes Vorkommen des angegebenen Zeichens oder der Zeichenkette),
LastIndexOf()
(letztes Vorkommen des angegebenen Zeichens oder der Zeichenkette), ToLower()
(wandelt
die Zeichenkette in Kleinbuchstaben um), ToUpper()
(wandelt die Zeichenkette in Großbuchstaben um), Replace()
(entfernt die angegebene(n) Zeichen oder Zeichenkette durch eine andere), Substring()
(extrahiert einen Teil einer
Zeichenkette an Hand der Position und Länge) und Split()
(teilt einer Zeichenkette an dem angegebenen Zeichen oder
der Zeichenkette auf und gibt ein Array zurück). Alle genannten Funktionen, die die Zeichenkette verändern, verändern nicht die
eigene Zeichenkette, sondern geben eine Kopie mit den durchgeführten Änderungen zurück. Hierzu ein paar Beispiele:
string sText = "Dies ist ein Text."; string sKleinerText = sText.ToLower(); string sGrosserText = sText.ToUpper(); int iLeerzeichen = sText.IndexOf(' ');
Arrays und Listen
Ein Array erlaubt es, mehrere Elemente eines beliebigen Datentyps in einer Variablen zu speichern. Um ein Array zu deklarieren, wird der Datentyp, gefolgt von einem eckigen Klammernpaar und einem ganz normalen Variablennamen, notiert. Zwischen den eckigen Klammern wird nichts angegeben, d. h. die Größe des Arrays ist zum Zeitpunkt der Deklaration unwichtig:
int[] aZahlenliste;
Anders ist dies bei der Zuweisung, denn hier muss die Größe angegeben werden. Da Arrays ebenfalls Objekte sind, werden
diese (wie Objekte von anderen Klassen auch) mit dem Schlüsselwort new
instanziiert (erstellt).
aZahlenliste = new int[10];
Natürlich kann die Deklaration und Initialisierung kombiniert werden:
int[] aZahlenliste = new int[10];
Möchten Sie auf ein Element (engl. item) eines Arrays zugreifen, so benötigen Sie den Indexoperator []
.
Innerhalb der Klammern geben Sie den Index an. Dabei handelt es sich i. d. R. um einen nummerischen Wert und beginnt
bei 0, d. h. das erste Element hat den Index 0, das zweite den Index 1 usw..
int iErsteZahl = aZahlenliste[0];
Sie können mit diesem Syntax natürlich nicht nur ein Element auslesen, sondern auch ändern:
aZahlenliste[2] = 468;
Eine der wichtigsten Eigenschaft eines Array-Objekts ist Length
, welche die Länge des Arrays wiederspiegelt:
int iLaenge = aZahlenliste.Length;
Übrigens: Die Klasse Array
verfügt über einige statische Funktionen, die Ihnen beim Arbeiten mit Arrays
hilfreich sein könnten. So erlauben Ihnen die Funktionen IndexOf()
und LastIndexOf()
das Suchen von
Einträgen (ähnlich wie das Suchen von Zeichen in einer Zeichenkette). Die Funktion Reverse()
kehrt die Reihenfolge
aller Einträge in einem Array um. Das Sortieren eines Arrays können Sie mit der Funktion Sort()
durchführen.
Als Liste bezeichnet man eine Art von Arrays, welche zur Laufzeit von der Größe verändert werden können. Die wichtigsten
Klassen für Listen sind List<T>
und ArrayList
. List<T>
ist stark typisiert
bzw. generisch, d. h. der Datentyp steht, wie bei Arrays auch, bei der Deklaration fest. In ArrayList
können Werte
eines beliebigen Datentyps gespeichert werden. Dies kann jedoch zu Problemen führen, weshalb List<T>
bevorzugt
werden sollte.
ArrayList lDatenliste = new ArrayList; List<int> lZahlenliste = new List<int>;
Beide Listtypen implementieren die IList
-Schnittstelle, wodurch die meisten Eigenschaften und Funktionen für beide
Typen verwendet werden können.
Um einer Liste einen Eintrag hinzuzufügen, wird die Add()
-Methode verwendet. Der Eintrag wird dabei an das
Ende angehängt. Möchten Sie das Element an einer bestimmten Stelle (nullbasierter Index) einfügen, so können Sie die Funktion
Insert()
verwenden.
lZahlenliste.Add(123); lZahlenliste.Add(789); lZahlenliste.Insert (1, 456);
Das Entfernen eines Elements ist mit Hilfe der Funktionen Remove()
und RemoveAt()
möglich.
Remove()
entfernt das erste Vorkommen des übergebenen Werts, wohingegen RemoveAt()
das Element eines
bestimmten Index entfernt.
lZahlenliste.Remove(789); lZahlenliste.RemoveAt(1);
Möchten Sie den Listeninhalt löschen, so können Sie die Funktion Clear()
aufrufen:
lZahlenliste.Clear();
Falls Sie ein Element suchen möchten, so kann Ihnen die Funktion Contains()
und IndexOf()
behilflich
sein. Contains()
prüft ob der angegebene Wert in der Liste vorkommt. IndexOf()
prüft dies zwar
ebenfalls, gibt jedoch keinen Wahrheitswert zurück, sondern den Index des ersten Vorkommens.
bool bEinsZweiDrei = lZahlenliste.Contains(123); int iEinsZweiDrei = lZahlenliste.IndexOf(123);
Die Länge der Liste können Sie mit der Eigenschaft Count
ermitteln:
int iLaenge = lZahlenliste.Count;
Möchten Sie auf die Elemente zugreifen, so können Sie auf die Liste mit Hilfe des Indexoperators []
zugreifen,
wie wenn es ein Array wäre:
int iErsteZahl = lZahlenliste[0]; int iZweiteZahl = lZahlenliste[1];
Bedingungen und Abfragen
Ein wichtiger Bestandteil von Programmiersprachen ist die Möglichkeit, Variablen oder Rückgabewerte von Funktionen auf einen
bestimmten Zustand zu prüfen. C# bietet uns hier zwei Arten von Abfragen an: einfache Verzweigungen (mittels if
-else
)
und mehrfache Verzweigungen (mittels switch
-case
).
Für eine einfache Verzweigung ist es notwendig, eine Bedingung aufzustellen. Solche Bedingungen werden auch an anderen Stellen
(z. B. bei Schleifen) benötigt. Eine Bedingung muss in C# dabei immer einen boolschen Wert (Wahrheitswert) ergeben, d. h.
true
(wahr) oder false
(unwahr). Wenn wir nun jedoch keine Variable vom Typ bool
prüfen
wollen, so müssen wir einen solchen Wert erzeugen. Dies ist mit Hilfe von folgenden Vergleichsoperatoren möglich:
== | gleich |
---|---|
!= | ungleich |
< | kleiner als |
<= | kleiner als oder gleich |
> | größer als |
>= | größer als oder gleich |
Wichtig: Der Vergleich von zwei Verweistypen mit Hilfe der Operatoren ==
und !=
vergleicht im
Regelfall nicht den Inhalt, sondern die Referenz (also die Adresse). Es ist jedoch möglich, Operatoren in Klassen zu überladen.
Dies ist z. B. bei Zeichenketten (Klasse String
) der Fall. Deshalb ergibt der Vergleich von zwei Zeichenketten
mit dem ==
-Operator, sofern beide Zeichenketten den gleichen Inhalt haben, true
, obwohl sich die
Adressen unterscheiden.
Hierzu folgendes Beispiel mit Verwendung einer if
-Abfrage (einfache Verzweigung):
if (a > b) { Console.WriteLine("Zahl a ist größer als b"); }
Mit Hilfe des sogenannten else
-Zweigs ist es möglich, zusätzlich den Fall abzudecken, falls die Bedingung nicht
zutrifft:
if (a > b) { Console.WriteLine("Zahl a ist größer als b"); } else { Console.WriteLine("Zahl a ist nicht größer als b"); }
Solche einfache Verzweigungen können beliebig verschachtelt werden. Es ist aber auch möglich, zwischen if
-
und else
-Block weitere else if
-Blöcke zu notieren. Dies sieht dann z. B. so aus:
if (a == b) { Console.WriteLine("Zahl a ist gleich groß wie Zahl b"); } else if (a > b) { Console.WriteLine("Zahl a ist größer als b"); } else { Console.WriteLine("Zahl a ist kleiner als b"); }
Bedingungen können über &&
(logisches Und) und ||
(logisches Oder) verknüpft werden. Wird
&&
und ||
innerhalb einer Bedingung verwendet, so sollten (runde) Klammern zur Gruppierung
genutzt werden. Möchten Sie den Wahrheitswert einer Bedingung umkehren (negieren), so können Sie das Ausrufezeichen !
verwenden.
if (a > b && a > c) { Console.WriteLine("Zahl a ist größer als b und c"); }
Eine weitere Möglichkeit, um einen Vergleich bzw. eine Abfrage durchzuführen, ist die mehrfache Verzweigung. Dafür wird ein
switch
-Block notiert, in welchem der Wert / die Variable, welche mit anderen Werten verglichen werden soll, angegeben
wird. Innerhalb des switch
-Blocks werden nun ein oder mehrere case
-Blöcke notiert. Dort wird dann
jeweils der Vergleichswert angegeben. Es wird immer nur der Code des case
-Blocks ausgeführt, bei welchem die
„Bedingung“ zutrifft.
switch (iMultiplikator) { case 1: Console.WriteLine(iValue + " V"); break; case 1000: Console.WriteLine((iValue / 1000) + " KV"); break; case 1000000: Console.WriteLine((iValue / 1000000) + " MV"); break; }
Möchten Sie für mehrere Fälle den gleichen Code ausführen, so ist es möglich, case
-Blöcke zu stapeln:
case 0: case 1: Console.WriteLine(iValue + " V"); break;
Des Weiteren ist es möglich, einen default
-Block anzugeben. Der Code des default
-Blocks wird ausgeführt,
wenn keine der definierten Fälle (case
-Blöcke) zutreffen:
default: Console.WriteLine(iValue + " V"); break;
Schleifen
Schleifen erlauben es, bestimmte Vorgänge mehrmals auszuführen. C# bietet uns hierfür 4 verschiedene Schleifen an. Die
einfachste Schleife ist die while
-Schleife. Die while
-Schleife besteht aus einer Bedingung und einem
Anweisungsblock. Der Anweisungsblock wird solange ausgeführt wie die Bedingung zutrifft. Die Bedingung wird dabei vor dem
Ausführen der Anweisungen geprüft (kopfgesteuerte Schleife).
int i = 0; while (i < 10) { Console.WriteLine(i); i++; }
Manchmal kann es jedoch auch hilfreich sein, eine Bedingung erst nach dem Ausführen des Codes zu prüfen. Dies hat dann auch
zur Folge, dass der Code mindestens einmal ausgeführt wird. Eine solche fußgesteuerte Schleife lässt sich mit der
do
-while
-Schleife realisieren. Im folgenden Beispiel besteht kein Unterschied zum obigen Beispiel.
int i = 0; do { Console.WriteLine(i); i++; } while (i < 10);
Für die oben verwendeten Zählvorgänge wird i. d. R. eine for
-Schleife verwendet. Die for
-Schleife
hat einen ausgeprägten Schleifenkopf. Dort kann eine Variablendeklaration und / oder -initialisierung, eine Bedingung
und eine Anweisung, welche nach dem Ausführen des Schleifenrumpfs ausgeführt werden soll (meistens die Inkrementierung einer
Variablen), angegeben werden. Die Bedingung wird, wie bei der while
-Schleife auch, vor dem Ausführen des
Anweisungsblocks überprüft.
for (int i = 0; i < 10; i++) { Console.WriteLine(i); }
Für die Iteration über ein Array oder eine Liste kann die foreach
-Schleife verwendet werden:
foreach (int iZahl : lZahlenliste) { Console.WriteLine(iZahl); }
Möchten Sie das Verhalten einer Schleife beeinflussen, so können Ihnen die Schlüsselwörter break
und
continue
behilflich sein. break
führt dazu, dass die Schleife beendet wird. Mit dem
Schlüsselwort continue
ist es möglich, die aktuelle Ausführung zu beenden und die Bedingung erneut zu prüfen.
Das folgende Beispiel würde die Zahlen 0 bis 7, ausgenommen der Zahl 3, ausgeben:
for (int i = 0; i < 10; i++) { if (i == 8) break; if (i == 3) continue; Console.WriteLine(i); }
Funktionen
Schon des öfteren haben wir nun Funktionen genutzt, doch es ist auch möglich, eigene Funktionen zu definieren. Funktionen die nur durch eine Objektinstanz aufgerufen werden können, werden auch als Methoden bezeichnet.
Um eine Funktion zu definieren, benötigen wir einen Zugriffsmodifizierer (public
, protected
oder
private
), einen Rückgabetyp (Datentyp des Rückgabewerts), einen Namen und eine Parameterliste. Auf die Bedeutung
der Zugriffsmodifizierer gehen wir nachher noch genauer ein. Als Rückgabetyp kann ein beliebiger Datentyp gewählt
werden. Gibt die Funktion keinen Wert zurück, so muss als Rückgabetyp void
angegeben werden. Der Name einer Funktion
ist frei wählbar, muss jedoch eindeutig sein. Die Parameterliste wird in runden Klammern angegeben. Dabei besteht jede
Parameterdeklaration aus Datentyp und Variablenname. Der Variablenname muss innerhalb der Funktion und deren Parameter eindeutig
sein. Besitzt die Funktion keine Parameter, so wird eine leere Parameterliste notiert:
public void gebeTextAus() { Console.WriteLine("Hallo Welt!"); }
Hier ein weiteres Beispiel mit Parametern:
public void addiereZahlen(int iZahlA, int iZahlB) { Console.WriteLine("Ergebnis:" + (iZahlA + iZahlB)); }
Möchten Sie von einer Funktionen einen Wert zurückgeben, so können Sie eine return
-Anweisung angeben. Die
Verwendung von return
ist auch bei Funktionen ohne Rückgabewert möglich, da das return
-Statement die
Ausführung der Funktion beendet.
public int addiereZahlen(int iZahlA, int iZahlB) { return iZahlA + iZahlB; }
In C# ist es auch möglich, Funktionen zu überladen. Beim Überladen von Funktionen gibt es mehrere Funktionen mit dem gleichen Namen, die sich jedoch von der Anzahl, Reihenfolge oder den Datentypen von Parametern unterscheiden.
public int addiereZahlen(int iZahlA, int iZahlB) { return iZahlA + iZahlB; } public int addiereZahlen(int iZahlA, int iZahlB, int iZahlC) { return iZahlA + iZahlB + iZahlC; }
Die bisher hier vorgestellten Funktionen sind alles Methoden, d. h. der Aufruf dieser Funktionen ist nur dann möglich, wenn ein
Objekt der Klasse instanziiert wurde. Möchten Sie eine Funktion, die auch ohne Objektinstanz aufgerufen werden kann, anlegen,
so müssen Sie zwischen dem Zugriffsmodifizierer und dem Rückgabetyp das Schlüsselwort static
angeben. Man spricht
dann auch von einer statischen Funktion. Verwendet werden statische Funktionen oft für Hilfsfunktionen o. Ä..
public static int addiereZahlen(int iZahlA, int iZahlB) { return iZahlA + iZahlB; }
Klassen und Objekte
C# ist eine objektorientierte Programmiersprache, weshalb natürlich Klassen und Objekte zentraler Bestandteil der Sprache sind. Mit Hilfe von Klassen lassen sich komplexe Datentypen definieren, die mehrere Daten kapseln und / oder bestimmte Logikverhalten und Algorithmen implementieren können. Die Klasse stellt dabei eine Art Bauplan dar.
Im Regelfall wird jede Klasse in einer eigenen Datei abgelegt. Bei der Deklaration einer Klasse werden ein Zugriffsmodifizierer,
das Schlüsselwort class
und ein Klassenname angegeben:
public class Fernseher { }
Innerhalb der Klasse können Variablen, Eigenschaften (dazu gleich mehr) und Methoden notiert werden:
public class Fernseher { private string sMarke; private int iBildschirmdiagonale; public void setMarke(string sMarke) { this.sMarke = sMarke; } public string getMarke() { return sMarke; } public void setBildschirmdiagonale(int iBildschirmdiagonale) { this.iBildschirmdiagonale = iBildschirmdiagonale; } public int getBildschirmdiagonale() { return iBildschirmdiagonale; } }
Übrigens: Das this
-Schlüsselwort verweist auf die aktuelle Objektinstanz und kann immer dann verwendet
werden, wenn es zu Namenskonflikten kommt. Dies ist im Beispiel bei den setX()
-Methoden der Fall, da als
Parametername der gleiche Name wie für die Klassenvariable (auch als Membervariable bezeichnet) verwendet wurde.
Möchten Sie nun von dieser Klasse ein Objekt erzeugen (man spricht auch von einer Objektinstanz), so benötigen Sie
das new
-Schlüsselwort:
Fernseher oMeinFernseher = new Fernseher();
Der Zugriff auf Variablen, Funktionen etc. erfolgt mittels des Punktoperators .
:
oMeinFernseher.setMarke("Samsung"); oMeinFernseher.setBildschirmdiagonale(39);
Übrigens: Auch beim Zugriff auf statische Bestandteile wird der Punktoperator .
verwendet. Da im Regelfall
keine Objektinstanz vorliegt, muss hier der Klassenname, der Punktoperator und anschließend der Name der statischen Variablen,
Funktion etc. notiert werden.
Wie bereits angesprochen, gibt es in C# drei unterschiedliche Zugriffsmodifizierer: public
, protected
und private
. Der Zugriffsmodifizierer private
beschränkt den Zugriff auf die eigene Klasse. Mit
dem Zugriffsmodifizierer protected
ist es möglich, dass auch andere Klassen, die von der betroffenen Klasse erben,
zugreifen können. Der Zugriffsmodifizierer public
erlaubt zusätzlich den „Zugriff von außen“, d. h. ein Zugriff
ist außerhalb der betroffenen Klassen möglich. Dieser Grund sorgt auch dafür, dass im obigen Beispiel die Methoden
setMarke()
und setBildschirmdiagonale()
aufgerufen werden können. Würden Sie auf die Variablen
sMarke
und iBildschirmdiagonale
zugreifen wollen, so würde Ihnen der Zugriff verwehrt werden. Es ist auch
möglich, Zugriffsmodifizierer wegzulassen. In diesem Fall wird private
angenommen.
Variablen sollten grundsätzlich den Zugriffsmodifizierer private
oder protected
haben. Deshalb müssen
für Variablen im Regelfall getX()
und setX()
-Methoden programmiert werden. In C# wird diese Technik
kaum angewendet, da hierfür die sogenannten Eigenschaften vorgesehen sind. Dies sieht z. B. so aus:
public int Bildschirmdiagonale { get; set; }
Ein Vorteil von Eigenschaften ist, dass Sie den Zugriff beschränken können. Im folgenden Beispiel wird eine Eigenschaft definiert, die zwar von überall gelesen werden kann, jedoch nur von der Klasse gesetzt werden darf:
public int Bildschirmdiagonale { get; private set; }
Des Weiteren ist es möglich, bei Eigenschaften einen Programmcode zu hinterlegen (z. B. um den Wert zu prüfen). In einem solchen Fall wird dann jedoch meistens zusätzlich eine Variable benötigt, die den Wert speichert:
private int iBildschirmdiagonale; public int Bildschirmdiagonale { get { return iBildschirmdiagonale; } set { if (value >= 20 && value <= 80) { iBildschirmdiagonale = value; } } }
Wie Ihnen vermutlich aufgefallen ist, wird bei der Instanziierung eines Objekts der Klassenname, gefolgt von runden Klammern, angegeben.
Dies kommt daher, da bei der Objektinstanziierung der sogenannte Konstruktor aufgerufen wird. Befindet sich in der Klasse
kein Konstruktor so wird automatisch ein leerer Standardkonstruktor angelegt. Es ist jedoch möglich, eigene Konstruktoren anzulegen.
Der Konstruktor hat keinen Rückgabetyp (auch nicht void
) und besitzt immer den gleichen Namen wie die Klasse:
public Fernseher(string sMarke) { this.sMarke = sMarke; this.iBildschirmdiagonale = 39; } public Fernseher(string sMarke, int iBildschirmdiagonale) { this.sMarke = sMarke; this.iBildschirmdiagonale = iBildschirmdiagonale; }
Übrigens: Neben dem Konstruktor gibt es auch einen Destruktor. Der Destruktor wird aufgerufen, wenn das Objekt zerstört wird. Die Objektzerstörung wird jedoch durch den Garbage Collector der CLI verwaltet, weshalb der exakte Zeitpunkt nicht direkt feststeht. Verwendet werden Destruktoren, um bestehende Ressourcen wieder freizugeben bzw. Verbindungen zu trennen.
Zuletzt wollen wir uns noch kurz mit dem Thema Vererbung auseinandersetzen. C# ermöglicht es, dass eine Klasse Variablen, Eigenschaften und Funktionen einer anderen Klasse erbt. Dafür werden bei der Klassendeklaration hinter dem Klassennamen zusätzlich ein Doppelpunkt und der Name der Elternklasse (oft auch als Basisklasse bezeichnet) angegeben.
public class SmartTV : Fernseher { }
Übrigens: Soll eine Klasse Schnittstellen implementieren, so können diese hier ebenfalls angegeben werden. Mehrere Namen werden durch Komma getrennt.
Wichtig: In C# werden Klassen, Strukturen und Schnittstellen in Namensräumen (namespace
-Block) verwaltet.
Möchten Sie einen Namensraum importieren, so benötigen Sie die using
-Anweisung (z. B. using System.Collections.Generic
um den Datentyp List<T>
nutzen zu können).
Fehlerbehandlungen
Einige Funktionen des .NET Frameworks (oder auch Funktionen von Fremd-Libraries sowie eigene Funktionen) können Ausnahmefehler
(engl. exceptions) auslösen. Ausnahmefehler können und sollten jedoch im Programmcode abgefangen werden. Dafür
wird der Code, welcher einen Ausnahmefehler auslösen kann, innerhalb eines try
-Blocks notiert. Anschließend muss
mindestens ein catch
-Block notiert werden. Der catch
-Block kennzeichnet sich durch das Schlüsselwort
catch
und ein rundes Klammernpaar, in welchem ein Datentyp (Klasse Exception
oder eine der vielen
Kindklassen) und ein Variablenname angegeben wird. Werden innerhalb des try
-Blocks unterschiedliche Ausnahmefehler
ausgelöst (man spricht auch davon, dass Ausnahmefehler geworfen werden), so ist es u. U. nützlich, mehrere catch
-Blöcke
zu notieren. Möchten Sie jedoch alle Fehler gleich behandeln, so können Sie auch nur einen catch
-Block notieren,
bei welchem Sie Exception
als Datentyp angeben.
List<int> lZahlenliste = new List<int>(); try { // Folgender Aufruf löst einen Ausnahmefehler aus, da der Index größer als die Länge ist lZahlenliste.Insert(1, 123); } catch (ArgumentOutOfRangeException ex) { // Ausgabe der Fehlermeldung Console.WriteLine(ex.ToString()); }
Übrigens: Wird ein Ausnahmefehler ausgelöst, so wird die Ausführung des try
-Blocks sofort abgebrochen.
Wenn Sie jedoch auf Dateien zugreifen oder Verbindungen geöffnet haben, sollten Sie diese zunächst schließen. Deshalb kann
nach den try
- und catch
-Blöcken ein finally
-Block notiert werden. Der dort notierte Code
wird immer ausgeführt, egal ob der try
-Block vollständig ausgeführt wurde oder auf Grund eines Fehlers abgebrochen
wurde.
Übrigens: Möchten Sie Ausnahmefehler selber auslösen, so benötigen Sie das Schlüsselwort throw
und ein Objekt
von einer der Exception-Klassen. Natürlich können Sie auch eigene Exception-Klassen definieren. Diese müssen lediglich von einer
der bestehenden Exception-Klassen ableiten.
Ereignisse
C# ist eine ereignisorientierte Programmiersprache. Dadurch haben Sie die Möglichkeit, Ereignisse zu registrieren (oftmals auch
als abonnieren bezeichnet) und dadurch auf bestimmte eingetretene Events zu reagieren (ohne einen Status dauerhaft
prüfen zu müssen). Um ein Ereignis zu registrieren, benötigen Sie den +=
-Operator. Je nach Event unterscheidet
sich dabei auch die Ereignishandler-Klasse. Dem Konstruktor der Ereignishandler-Klasse wird die Referenz auf die Funktion
(mit Hilfe des Funktionsnamens) übergeben.
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKeyPressed);
Sie können auch jederzeit ein bestehendes Ereignisabonnement kündigen:
Console.CancelKeyPress -= OnCancelKeyPressed;