ACHTUNG
Dieses Kapitel stellt keine allgemeine Einführung in die objektorientierte Programmierung dar. Dazu gibt es spezielle Literatur, die häufig sogar unabhängig von einer konkreten Programmiersprache ist. Wie eine Anwendung optimal in Klassen, Methoden, Namespaces etc. aufgeteilt wird, ist einerseits mit viel Erfahrung verbunden und andererseits von dem zukünftigen Aufgabenbereich der Anwendung abhängig.Abbildung 5.1: Aufbau einer Klasse mit privatem und öffentlichem Teil
Wozu dienen nun Klassen? Angenommen Sie möchten eine Anwendung entwickeln, die zur Verwaltung Ihrer DVD-Sammlung dient. Jede DVD besitzt bestimmte Merkmale wie einen Titel, einen Preis, das Erscheinungsjahr und eine Kategorie. Diese Merkmale einer DVD können durch jeweils eine Variable repräsentiert werden. Weiterhin kann eine DVD ausgeliehen (durch einen Bekannten, der sie dann 3 Jahre später beim Umzug wiederfindet) oder verkauft werden. Diese Operationen werden durch Methoden implementiert. Eine Klasse definiert nun einen Bauplan für eine konkrete DVD mitsamt den darauf ausführbaren Operationen (der Schnittstelle).INFO
Die Kapselung sollte eigentlich auch die Sicherstellung des Geheimnisprinzips ermöglichen, d.h. man möchte den internen Aufbau einer Klasse vor deren Anwender verstecken. Dies ist unter .NET aber leider nicht möglich, da sich mittels Reflection (vgl. dazu später in diesem Buch) alle Informationen über den Aufbau einer Klasse ermitteln lassen. Dennoch trägt die Kapselung durch die Bereitstellung der öffentlichen Schnittstelle dazu bei, Programmierfehler zu vermeiden, diese beim Auftreten schneller zu finden und ein besseres Klassendesign zu schaffen.
Die Klasse DVD
besitzt zwei private Variablen und zwei öffentliche Methoden. Von der Klasse DVD
werden nun zwei konkrete Objekte erzeugt.
Jedes Objekt besitzt dabei individuelle Werte in den beiden Eigenschaften Erscheinungsjahr und Titel.
Abbildung 5.2: Objekterzeugung
Die Klasse DVD
enthält die beiden Variablen Titel
und Erscheinungsjahr sowie die Methode Ausleihen()
. Allerdings enthält die Methode
noch keine Funktionalität und auf die beiden Variablen kann nur innerhalb der Klasse zugegriffen werden.
public class DVD { private string Titel; private int Erscheinungsjahr; public void Ausleihen(string name) { ... } }Listing 5.1: DVDSammlung.cs
class
deklariert.
public
oder internal
versehen werden. Diese werden noch vor das Schlüsselwort class
geschrieben. Geben Sie keinen Modifizierer an, ist internal die Standardeinstellung.
class
folgt der Name der Klasse, der ein gültiger Bezeichner sein muss.
private
deklariert, d.h., auf sie kann nur innerhalb der Klassendeklaration zugegriffen werden.
int
-Typen.
Eine Liste aller Standardwerte können Sie in der Hilfe unter Default Values Table
einsehen.
Häufig wird statt von Instanzvariablen auch von Feldern gesprochen (engl. fields). Da dies zu Verwirrung mit dem Begriff "Feld" im Zusammenhang mit Arrays führen kann,
wird im Folgenden immer von Instanzvariablen gesprochen.
readonly
als schreibgeschützt deklariert werden.
Jetzt kann die Variable nur noch bei ihrer Deklaration und in einem Konstruktor der Klasse initialisiert (geschrieben) werden. Selbst in einer anderen Methode der Klasse kann auf eine solche
Variable nicht mehr schreibend zugegriffen werden. Der einzige Unterschied zu einer Konstanten besteht darin, dass eine als readonly
markierte Variable auch im Konstruktor
initialisiert werden kann. Durch die Verwendung mehrerer Konstruktoren kann sie dadurch unterschiedliche Werte erhalten.
Die beiden Variablen Computername und Benutzername werden im Konstruktor der Klasse Systeminfo
initialisiert. Auf die Variablen kann aber von außen (über
Instanzen der Klasse) nur lesend zugegriffen werden.
public class SystemInfo { public readonly string Computername; public readonly string Benutzername; public void SystemInfo() { Computername = "MeinPC"; Benutzername = "Nelly"; } }
Zugriffsmodi | Beschreibung |
public | Auf diese Member kann innerhalb des Typs, von abgeleiteten Typen und von Objekten des Typs aus zugegriffen werden. |
protected | Auf diese Member kann innerhalb des Typs und von abgeleiteten Typen aus zugegriffen werden. Objekte können niemals auf protected-Member zugreifen. |
private | Auf diese Member kann nur innerhalb der Typdefinition (auch von verschachtelten Typen aus) zugegriffen werden. |
protected internal | Auf diese Member kann innerhalb des Typs und von abgeleiteten Typen aus zugegriffen werden. Außerdem kann von allen Typdefinitionen der Assembly darauf zugegriffen werden, in welcher der Typ deklariert wurde. |
internal | Es kann von allen Typdefinitionen innerhalb der jeweiligen Assembly darauf zugegriffen werden. |
new
verwendet. Bei seinem Aufruf stellt er zuerst Speicherplatz für ein Objekt des verwendeten Typs bereit,
initialisiert ihn und gibt eine Referenz darauf zurück. Diese Referenz können Sie dann in einer Variablen speichern und darüber auf das Objekt zugreifen.
Es wird eine Variable Dvd1
(auch Referenzvariable genannt, da sie ein Objekt referenziert) vom Typ DVD
deklariert. Über new DVD()
wird
ihr eine Referenz auf ein neues DVD
-Objekt zugewiesen. Die Deklaration und die Erzeugung des Objekts können auch über zwei separate Anweisungen erfolgen.
DVD Dvd1 = new DVD(); // oder DVD Dvd1; Dvd1 = new DVD();
INFO
Eine Ausnahme bei der Objekterzeugung bilden die "primitiven" Datentypen und der DatentypString
. Diesen können Sie über ein
Gleichheitszeichen einen neuen Wert zuweisen. Auf diese Weise wird ein Objekt erzeugt und der Wert des Typs festgelegt.
string s = "Dies ist ein Schlüsselwort"; int i = 10; Int32 i = 10;
new
, den Typnamen und ein rundes Klammerpaar an.
var
sofort der Name des Objekts angegeben wird. Der Typ fehlt also und wird später
vom Compiler generiert. Mittels new
wird dann ein Objekt erzeugt. Anstatt hier wieder den Typnamen und runde Klammern zu verwenden, werden direkt hinter
new
in geschweiften Klammern Eigenschaft/Wert-Paare angegeben. Mehrere Paare werden durch Komma getrennt.
var <name> = new { Eigenschaft1 = Wert1, ... };
Vom Compiler wird durch die erste Anweisung ein unbenannter Typ erstellt. Des Weiteren wird ein Objekt von diesem Typ erzeugt, auf das über die Variable person zugegriffen werden kann. Der unbenannte Typ besitzt die beiden Eigenschaften Name und Vorname, auf die nur lesend zugegriffen werden kann.
var person = new { Name = "Meier", Vorname = "Paule" }; string n = person.Name; Console.WriteLine(person.Vorname);
null
. Wenn Sie nicht wissen, ob eine
Referenzvariable auf ein Objekt verweist, sollten Sie einen Test auf den Wert null
durchführen. Ansonsten kann es beim Zugriff auf das nicht vorhandene
Objekt zu einer NullReferenceException
kommen.
Die Klasse Person
enthält ein Datenelement Name
. Vor dem Zugriff wird geprüft, dass die Referenz nicht auf null
verweist. Nur in
diesem Fall wird auf das Datenelement Name
zugegriffen.
Person p; if(p != null) p.Name = "Meier";
null
annehmen. Ab der Version 2.0 können dies nun über einen neuen,
speziellen Typ auch die Werttypen wie int
oder double
. Auf diese Weise kann man z.B. kennzeichnen, dass eine Variable von einem Werttyp noch
nicht initialisiert wurde. Bei Datenbankanwendungen ist dies z.B. nützlich, da dort zwischen dem Wert null
(überhaupt kein Wert vorhanden) oder dem
Wert 0 oder "" (der tatsächliche Wert ist die Zahl 0 oder ein leerer String) unterschieden wird. Für nullbare Typen (in der Hilfe zu finden unter: auf NULL festlegbare Typen),
d.h. Typen, die null
-Werte zulassen, wurden eine spezielle Syntax und einige Hilfemethoden eingeführt. Dazu wird dem betreffenden Datentyp ein Fragezeichen angefügt.
Nullbare Typen sind Instanzen der generischen Struktur System.Nullable<T>
.
int? i = null; double? d = 1.0; d = null;
int?
. Diese Syntax ist dabei die Kurzform
von System.Nullable<T>
, wobei T
für den Typnamen steht.
HasValue
die true
zurückgibt, wenn ein Wert vorhanden ist, und false
, wenn
die Variable den Wert null
enthält. Über die Eigenschaft Value
wird der gespeicherte Wert zurückgeliefert. Ist beim Zugriff auf Value
der Wert null
,
wird eine InvalidOperationException
ausgelöst.
null
zugewiesen,
tritt eine InvalidOperationException
auf.
??
zur Verfügung gestellt (NULL-Sammeloperator). Dieser liefert den Wert eines nullbaren Typs, wenn er ungleich null
ist, sonst
den danach angegebenen Wert. In der Anweisung
int? Nullwert = ...; int Variable = Nullwert ?? 0;wird Variable entweder der Wert von Nullwert zugewiesen, wenn dieser nicht
null
ist, ansonsten der Wert 0
.
??
können Sie auch die Methode System.Nullable.GetValueOrDefault()
verwenden, die den Standardwert eines Typs liefert, wenn er null
ist
und einem nicht nullbaren Typ zugewiesen werden soll, z.B. int Variable = Nullwert.GetValueOrDefault();
. Alternativ lässt sich der Methode auch ein individueller
Standardwert als Parameter übergeben, der dann zugewiesen wird.
null
erweitert und entspricht den Ergebnissen der Verknüpfung in SQL (siehe Hilfe-Index VERWENDEN VON AUF NULL FESTLEGBAREN TYPEN).
Das Beispiel zeigt die verschiedenen Operationen mit nullbaren Typen für den Datentyp int
. Eine Konvertierung von einem nullbaren in den nicht nullbaren Typ ist nur
über einen Cast möglich (10). Der umgekehrte Fall geht auch implizit (11). Mittels der Eigenschaft HasValue
wird in (13) geprüft, ob die Variable einen Wert enthält, der nicht null
ist. Auf den Wert kann über die Eigenschaft Value
(14) zugegriffen werden. Über den Operator ??
wird in Zeile (17) eine bedingte Zuweisung realisiert. Ist der Wert
von zahl1
nicht null
, wird dieser Wert verwendet, sonst der Wert -1. In (19) wird die Methode GetValueOrDefault()
genutzt, um einem einfachen Werttyp den Wert eines nullbaren
Typs oder im Falle von null
den Standardwert des Datentyps zuzuweisen.
01 using System; 02 namespace CSharpBuch.Kap05 03 { 04 public class NullbareTypen 05 { 06 static void Main(string[] args) 07 { 08 int? zahl1 = null; 09 int? zahl2 = 10; 10 int zahl3 = (int)zahl2; 11 zahl2 = zahl3; 12 13 if(zahl1.HasValue) // oder (zahl1 != null) 14 Console.WriteLine("zahl1 hat den Wert: " + zahl1.Value); 15 else 16 Console.WriteLine("zahl1 hat den Wert null."); 17 zahl3 = zahl1 ?? -1; 18 Console.WriteLine("zahl3 hat den Wert: " + zahl3); 19 zahl3 = zahl1.GetValueOrDefault(); 20 Console.WriteLine("zahl3 hat den Wert: " + zahl3); 21 Console.ReadLine(); 22 } 23 } 24 }Listing 5.2: NullbareTypen.cs
struct
. Objekte einer Struktur werden ebenfalls über new
erzeugt.
struct DVD { private string titel; private int erscheinungsjahr; public void Ausleihen(string name) {} } ... DVD Dvd1 = new DVD();
ValueType
abgeleitet wird (von der auch die anderen Werttypen wie System.Int32
abgeleitet sind), welche wiederum die Klasse Object
erweitert. Durch eine Zuweisung der Art
Struktur1 = Struktur2;werden die Werte der Elemente der
Struktur2
in die Elemente der Struktur1
kopiert. Da Strukturen Werttypen sind, werden sie nicht durch den Garbage
Collector beseitigt. Dies kann in einigen Fällen zu Geschwindigkeitsvorteilen führen. Besonders dann, wenn eine Struktur hauptsächlich als Datencontainer dient.
public struct Person { string name; int alter; } // Da Strukturen Werttypen sind, wird der Speicher für alle // Elemente bereits beim Anlegen des Arrays bereitgestellt Person[] personen = new Person[10]; // wäre Person eine Klasse, müssten Sie zuerst das Array und // danach für jedes Element ein Person-Objekt erzeugen for(int personIndex = 0; personIndex < 10; personIndex++) personen[personIndex] = new Person();Eine Struktur kann neben den Feldern auch Methoden, wie z.B.
Ausleihen()
in der Struktur DVD
, sowie Konstruktoren zum Initialisieren der
Felder enthalten. Die Definition eines parameterlosen Standardkonstruktors oder eines Destruktors ist allerdings nicht möglich. Eine Struktur besitzt allerdings
implizit einen parameterlosen Standardkonstruktor, der die Werttypen mit ihrem Standardwert (z.B. 0 bei int
) und alle Referenztypen mit null
initialisiert. Beachten Sie bei der Definition einer Struktur, dass die Feldvariablen nicht initialisiert werden dürfen. Entweder Sie verwenden die implizite
Initialisierung über den Standardkonstruktor oder Sie stellen einen eigenen Konstruktor bereit (der mindestens einen Parameter enthält), welcher die Instanzvariablen
initialisiert.
Auch wenn Strukturen nicht von Klassen oder anderen Strukturen erben können, so ist es doch möglich, Interfaces zu implementieren.
Im Beispiel wurde jeweils eine Klasse und eine Struktur mit dem gleichen öffentlichen Datenelement Name
deklariert. Dann wird ein Objekt der Klasse KPerson
erzeugt und der Variablen kp1
zugewiesen. Einer zweiten Variablen wird die erste Variable zugewiesen. Hier wird aber keine Kopie des Objekts erzeugt, sondern die Variable
kp2
verweist nun auch auf das Objekt kp1
. Über beide Variablen wird nun der Wert des Datenelements Name
geändert. Wie die Ausgabe zeigt, hat
die Änderung über kp1
den Wert von kp2
überschrieben. Die gleiche Vorgehensweise wird nun für den Typ SPerson
vorgenommen, wobei es sich hier
um eine Struktur handelt. Bei der Zuweisung sp2 = sp1;
handelt es sich um eine echte Kopie der Datenelemente, so dass die Änderung des Namens über sp1
keine
Auswirkungen auf sp2
hat.
using System; namespace CSharpBuch.Kap05 { public class StrukturenUndKlassen { static void Main(string[] args) { KPerson kp1 = new KPerson(); KPerson kp2 = kp1; kp2.Name = "Müller"; kp1.Name = "Meier"; Console.WriteLine("Klasse => Name: " + kp2.Name); SPerson sp1 = new SPerson(); SPerson sp2 = sp1; sp2.Name = "Müller"; sp1.Name = "Meier"; Console.WriteLine("Struktur => Name: " + sp2.Name); Console.ReadLine(); } } public class KPerson { public string Name; } public struct SPerson { public string Name; } }Listing 5.3: StrukturenUndKlassen.cs Ausgabe:
Klasse => Name: Meier Struktur => Name: Müller
INFO
Sind einige Datenelemente einer Struktur Referenzvariablen, werden hier ebenfalls Kopien erzeugt. Dabei ist aber zu beachten, dass diese Variablen selbst zwar unterschiedlich sind, aber auf das gleiche Objekt verweisen, da von dem Objekt selbst keine Kopie erzeugt wird (Beispiel StrukturRefKopie.cs).WriteLine()
der Klasse Console
ist ein Beispiel für eine Methode, die in einer (Konsolen-)Anwendung immer wieder zur Ausgabe von Text benötigt wird.
Die Funktionalität dieser Anwendung wurde auf vier Methoden verteilt. Damit diese innerhalb der Methode Main()
verwendet werden können, müssen sie als static
deklariert werden (da statische Methoden nur andere statische Methoden aufrufen können). Nach der Aufforderung, die Länge eines Passworts anzugeben, geben Sie eine Zahl auf der
Konsole ein, ansonsten wird eine Exception ausgelöst. Dann wird ein Passwort "generiert". Dieses besteht einfach aus einer Folge der Buchstaben ABC...
in der eingegebenen Länge
(Methode PasswortErzeugen()
). Auch wenn die Methoden in diesem Beispiel sehr wenige Anweisungen enthalten, ist die Aufteilung durchaus sinnvoll. Man könnte z.B. die Methode
AnzahlZeichenEinlesen()
dazu verwenden, beliebige Zahlen über die Konsole einzulesen. Es müsste nur dafür gesorgt werden, dass die Aufforderung zur Eingabe variabel ist.
Die Methode PasswortErzeugen()
ist natürlich in der Praxis so nicht brauchbar, zumindest was das generierte Passwort angeht. Damit es tatsächlich ein zufälliges Passwort
wird, müssen Sie später aber nur diese Methode ändern, der Rest der Anwendung bleibt unberührt. Die Methode PasswortAusgeben()
kann z.B. dahingehend geändert werden, dass
sie das Passwort in eine Datei schreibt.
using System; namespace CSharpBuch.Kap05 { public class PasswortGeneratorProj { static void Main(string[] args) { int anzZeichen; string passwort; BegruessungAnzeigen(); AnzZeichen = AnzahlZeichenEinlesen(); passwort = PasswortErzeugen(anzZeichen); PasswortAusgeben(passwort); } private static void BegruessungAnzeigen() { Console.WriteLine("Es wird ein Passwort erzeugt, dass " + "die von Ihnen angegebene Länge besitze."); } private static int AnzahlZeichenEinlesen() { Console.WriteLine("Wie lang soll das Passwort sein?"); return Convert.ToInt32(Console.ReadLine()); } private static string PasswortErzeugen(int laenge) { string passwort = ""; for(int i = 0; i < laenge; i++) passwort = passwort + (char)(i + 65); return passwort; } private static void PasswortAusgeben(string passwort) { Console.WriteLine("Das generierte Passwort ist: " + passwort); Console.ReadLine(); } } }Listing 5.4: PasswortGenerator.cs
void
zurückgeben. Dabei kennzeichnet void die Tatsache, dass kein Wert zurückgegeben wird. Die Angabe von void
ist in
diesem Fall aber unbedingt notwendig.
public
, protected
, private
, protected internal
oder
internal
voranstellen. Ohne Angabe eines Zugriffsmodifizierers ist private
in Klassen voreingestellt.
if
- und der else
-Zweig,
dem keine Anweisungen mehr folgen) muss über eine return
-Anweisung verfügen. Ansonsten meldet der Compiler einen Fehler. Der return
-Anweisung wird der
Rückgabewert der Methode angefügt.
[public | protected | private | internal]Name() { // Anweisungen return [ ]; }
INFO
Wenn der Compiler Anweisungen bemerkt, die niemals ausgeführt werden können, nimmt er bestimmte Prüfungen nicht vor. Im folgenden Codeauszug enthält derelse
-Zweig z.B. keine -Anweisung. Da er aber niemals aufgerufen wird, gibt der Compiler auch keine Fehlermeldung (allerdings eine
Warnung) aus. Eine Syntaxprüfung nimmt der Compiler allerdings in jedem Fall vor.
private static int GetZahl()
{
if(true)
return 10;
else
Console.WriteLine("Hallo");
}
Die Klasse Mathe
deklariert die Methode GetPI()
, die den (verkürzten) Wert der mathematischen Konstante Pi
zurückliefert. Um
die Methode zu nutzen, muss ein Objekt der Klasse Mathe
erzeugt werden. Über die Referenzvariable m
kann dann die Methode aufgerufen werden.
public class Mathe { public double getPI() { return 3.14; } } public class Test { static void Main(string[] args) { Mathe m = new Mathe(); // Objekt erzeugen Console.WriteLine(m.GetPI()); } }Listing 5.5: EinfacheMethoden.cs
INFO
Die Namen der Parameter werden standardmäßig in Kamelschreibweise angegeben, d.h. das erste Hauptwort wird klein, und alle folgenden groß geschrieben.ref
oder out
voranzustellen. Dabei kennzeichnet ref
einen
Parameter, der einen Wert an die Methode übergibt und in der Methode verändert werden kann. Ein out
-Parameter muss vor der Übergabe an die Methode nicht initialisiert werden,
da er nur einen Wert aus der Methode zurückgibt.
params
vorangestellt werden. Als Datentyp ist ein eindimensionales Array anzugeben.
ref
oder out
) vor dem Datentyp stehen.
Bei der Parameterübergabe als Wert (by value) bekommt der formale Parameter zur Laufzeit eine Kopie des Wertes des aktuellen Parameters zugewiesen. Wird der Wert des formalen
Parameters innerhalb der Methode geändert, hat dies keine Auswirkung auf die Variable, die als aktueller Parameter angegeben wurde (vgl. folgendes Beispiel: ParameterUebergeben.cs).
ref
oder out
werden Referenzparameter deklariert. Die Angabe ist bei Werttypen erforderlich, wenn Änderungen des Variablenwertes innerhalb der
Methode auch nach der Beendigung der Methode zur Verfügung stehen sollen. Die als Parameter angegebene Variable kann also dauerhaft innerhalb der aufgerufenen Methode geändert werden.
Abbildung 5.3: Parameterübergabe als Wert und als Referenz
Die Klasse ParameterUebergeben
enthält zwei Methoden Add1()
und Add2()
, in denen jeweils der Wert des Parameters um eins erhöht wird. Der
Unterschied zwischen beiden Methoden liegt lediglich in der Art der Parameterübergabe. Der Methode Add1()
wird der Parameter als Wert und der Methode Add2()
als
Referenz übergeben. Nach Aufruf der beiden Methoden von Main()
aus erfolgt die Ausgabe des Wertes der als Parameter übergebenen Variablen i
. An den Ausgaben sehen Sie,
dass der Aufruf von Add1()
im Gegensatz zu Add2()
keinen Einfluss auf den späteren Wert von i
hat.
using System; namespace CSharpBuch.Kap05 { public class ParameterUebergeben { static void Main(string[] args) { int i = 1; Add1(i); Console.WriteLine("i = " + i); Add2(ref i); Console.WriteLine("i = " + i); Console.ReadLine(); } static void Add1(int x) { x++; Console.WriteLine("x = " + x); } static void Add2(ref int y) { y++; Console.WriteLine("y = " + y); } } }Listing 5.6: ParameterUebergeben.cs
ref
, muss der Parameter vorher initialisiert werden. Bei der Verwendung von out
ist dies nicht erforderlich.
In beiden Fällen muss das Schlüsselwort aber sowohl in der Methodendeklaration, als auch beim Methodenaufruf vor dem entsprechenden Parameter angegeben werden.
return
maximal einen Wert zurückliefern. Oft reicht das jedoch nicht aus. Dieses Problem lässt sich leicht lösen, indem Sie der Methode
Variablen für die Wertrückgabe als Referenzparameter übergeben. Wertänderungen dieser Parameter bleiben dann auch außerhalb der Methode erhalten. Definieren Sie
Parameter, die nur für die Wertrückgabe benötigt werden, müssen die übergebenen Variablen nicht einmal initialisiert werden. Diese Parameter sind dann durch das
Schlüsselwort out
zu kennzeichnen. Sie werden als Rückgabe- oder auch als Ausgabeparameter bezeichnet.
Die Methode Init()
nimmt zwei Parameter entgegen, die innerhalb der Methode mit Werten belegt werden. Da die Parameter mit out
deklariert sind,
müssen die übergebenen Variablen nicht initialisiert werden und die Werte sind nach Beendigung der Methode verfügbar. Dies ist aus der Ausgabe ersichtlich, die dem Methodenaufruf folgt.
static void Init(out int x, out int y) { x = 5; y = 8; } ... // Aufruf in der Main-Methode: int j, k; Init(out j, out k); Console.WriteLine("j = " + j + ", k = " + k); // Ausgabe: j = 5, k = 8
INFO
Da einout
-Parameter nicht initialisiert ist, wird er wie eine lokale Variable behandelt. Er muss also vor der Verwendung auf der
rechten Seite initialisiert werden.
static void Init(out int x, out int y) { int i = x; // Compilerfehler
params
ist es möglich, eine Parameterliste zu deklarieren, die beliebig viele Parameter entgegennehmen kann. Dieses Schlüsselwort wird nur
in der Methodendeklaration angegeben, nicht beim Methodenaufruf. Beachten Sie, dass hinter einem params
-Parameter kein weiterer Parameter folgen darf. Damit kann
dieses Schlüsselwort auch nur einmal in der Methodendeklaration verwendet werden. Das übergebene Array darf nur eindimensional sein (Arrays werden später noch
genauer vorgestellt) und es wird als Wert übergeben. Das heißt, die Werte innerhalb des Arrays können in einer Methode nicht dauerhaft verändert werden.
Der Methode Anzahl()
kann eine Liste von beliebig vielen Objekten übergeben werden. Sie liefert die Anzahl der tatsächlich übergebenen Objekte zurück. In der
Main()
-Methode wird die Methode Anzahl()
mit vier Parametern aufgerufen. Da in C# Werttypen indirekt Objekte sind (d.h., in diese über Boxing konvertiert
werden), kann als Parametertyp der allgemeinste Typ Object
genutzt werden. Die zurückgegebene Anzahl der Objekte wird ausgegeben. Die Ausgabe lautet hier: 4 Objekte übergeben
public class VariableParameterliste { static void Main(string[] args) { Console.WriteLine(Anzahl(1, 2.2, "drei", "vier") + " Objekte übergeben"); } private static int Anzahl(params object[] liste) { return liste.Length; } }Listing 5.7: VariableParameterliste.cs
Rechne(zahl1: 100, zahl2: 300)
.
Rechne(100, zahl2: 300); // Ok Rechne(zahl2: 300, zahl1: 100); // Ok Rechne(zahl1: 100, 300); // nicht erlaubt
Der Aufruf der beispielhaften Methode Ausgabe()
erfolgt hier über drei verschiedene Varianten. Zuerst wird der Standardaufruf ohne die Verwendung von benannten
Parametern genutzt. Im folgenden Aufruf werden die benannten Parameter genutzt und es wird ersichtlich, das die Reihenfolge keine Rolle spielt. Der dritte Aufruf zeigt schließlich
das auch Positionsparameter mit benannten gemischt werden können, solange die Positionsparameter (also die normalen Parameter ohne Angabe des Namen) an ersten Stelle stehen.
public class BenannteParameter { static void Main(string[] args) { BenannteParameter bPara = new BenannteParameter(); bPara.Ausgabe(10, "Meier", 100.99); bPara.Ausgabe(name: "Schulze", betrag: 200.01, zahl: 100); bPara.Ausgabe(1000, betrag: 12.34, name: "Kunze"); } public void Ausgabe(int zahl, string name, double betrag) { Console.WriteLine("zahl: " + zahl); Console.WriteLine("name: " + name); Console.WriteLine("betrag: " + betrag); } }Listing 5.8: BenannteParameter.cs
params
) bei denen Sie für einen Parameter beliebig viele Werte angeben konnten gestattet die
Verwendung optionaler Parameter, dass nicht für alle Parameter Werte beim Methodenaufruf übergeben werden müssen. Dazu werden den Parametern bei der Deklaration zusätzlich
Standardwerte zugewiesen die diese annehmen, wenn sie nicht explizit angegeben werden.
Speziell für die COM-Programmierung sind optionale Parameter sehr hilfreich, da hier meist nur wenige Parameter von sehr umfangreichen Parameterlisten (10 bis 20 Parameter)
verwendet werden.
Rechne(10, , 20)
).
Die Methode Ausgabe()
besitzt in diesem Beispiel drei optionale Parameter, die jeweils mit null
vorbelegt sind. Es wurden dazu nullbare Typen verwendete um
tatsächlich später erkennen zu können, das diese nicht beim Aufruf mit einem Wert belegt wurden. Für die Ausgabe des Wertes wurde der NULL-Sammeloperator ??
verwendet
der entweder den Wert des Parameters liefert wenn dieser nicht null
ist oder im anderen Fall den nach dem ??
-Operator angegebenen Wert. Alternativ könnte auch
mit der Methode HasValue()
der Parameter geprüft werden, ob dieser einen von null verschiedenen Wert besitzt und entsprechend darauf reagiert werden.
public class OptionaleParameter { static void Main(string[] args) { OptionaleParameter oPara = new OptionaleParameter(); oPara.Ausgabe(); oPara.Ausgabe(name: "Meier"); oPara.Ausgabe(100, betrag: 12.34); } public void Ausgabe(int? zahl = null, string name = null, double? betrag = null) { Console.WriteLine("zahl: " + (zahl ?? 0).ToString()); Console.WriteLine("name: " + (name ?? "-")); Console.WriteLine("betrag: " + (betrag ?? 0.0).ToString()); } }Listing 5.9: OptionaleParameter.cs
Console.WriteLine()
. Ihr können sowohl verschiedene Werttypen als auch Objekte übergeben werden. Als
Ergebnis gibt sie immer die Textrepräsentation der Werttypen oder des Objekts auf der Konsole aus. Beim Überladen von Methoden müssen Sie folgende Regeln beachten:
Die Methode TextAusgeben()
wird in diesem Beispiel dreimal deklariert. Sie kann sowohl einen int
-, einen double
-, als auch einen string
-Parameter
entgegennehmen. Der übergebene Parameter und sein Datentyp werden ausgegeben. Aufgrund der Überladung müssen nicht für jeden Parametertyp neue Namen erdacht werden, was die Anwendung der Methoden erleichtert.
using System; namespace CSharpBuch.Kap05 { public class MethodeUeberladen { static void Main(string[] args) { TextAusgeben("Hallo"); TextAusgeben(23.456); TextAusgeben(25); Console.ReadLine(); } static void TextAusgeben(int x) { Console.WriteLine(x + " ist eine ganze Zahl"); } static void TextAusgeben(double x) { Console.WriteLine(x + " ist eine Gleitkommazahl"); } static void TextAusgeben(string x) { Console.WriteLine(x + " ist eine Zeichenkette"); } } }Listing 5.10: MethodenUeberladen.cs
ref
und in einer anderen mit out
deklariert wird.
Die Überladung kann aber für Parameter erfolgen, die in einer Methode mit ref
oder out
deklariert sind, in einer weiteren Methode jedoch keiner von beiden verwendet wird.
Nicht erlaubte Kombination:
void methode(ref int i) void methode(out int i) // erlaubte Kombination: void methode(ref int i) void methode(int i)
INFO
Haben Sie eine Methode mit einer variablen Parameterliste überladen, wird diese nur dann verwendet, wenn keine der anderen Methoden passt, auch wenn dazu eine interne Typumwandlung durchgeführte werden muss.DllImport
. Als Parameter übergeben Sie den Modulnamen der DLL ohne Endung. Es ist noch die Angabe weiterer
Parameter möglich. Unter dem Attribut wird die Methode deklariert und die Modifizierer static
und extern
werden davor gesetzt. Dieser Abschnitt muss sich innerhalb
einer Klasse befinden, über die dann die Methode aufgerufen werden kann.
[DllImport("")] static extern <type> <Name>(<Parameter>);
Das Attribut DllImport
befindet sich im Namespace System.Runtime.InteropServices
, der zu Beginn mit eingebunden werden muss. Zum Aufruf der
statischen Methode Sleep()
ist der Namespace System.Threading
erforderlich, da hieraus die Klasse Thread
benötigt wird. Durch den Aufruf
von Sleep()
wird die Anwendung für die übergebene Anzahl Millisekunden angehalten. In der Klasse ExterneMethoden
wird dann die Funktion MessageBeep()
importiert
(die einen Piepton erzeugt). Dieser wird ein Parameter vom Typ uint
übergeben, der festlegt, welcher Sound-Typ erzeugt wird. Die Übergabe von 0xFFFFFFFF
erzeugt den
Standard-Piepser. Durch den "geschickten" Einsatz der for
-Anweisung in Verbindung mit dem Modulo-Operator und der Methode Sleep()
erhalten Sie einen Klingelton,
der das Herz eines jeden Besitzers eines polyphonen Handys gefrieren lässt.
using System; using System.Runtime.InteropServices; using System.Threading; namespace CSharpBuch.Kap05 { public class ExterneMethoden { [DllImport("user32")] static extern bool MessageBeep(uint type); static void Main(string[] args) { for(int i = 0; i < 60; i++) { if(i % 10 == 0) Thread.Sleep(300); if(i % 4 == 0) Thread.Sleep(50); MessageBeep(0xFFFFFFFF); } } } }Listing 5.11: ExterneMethoden.cs
TIPP
Der Vorteil beim Einbinden externer Methoden bzw. Funktionen liegt natürlich in der Wiederverwendbarkeit bereits vorhandener Softwaremodule. Es sind ja noch nicht längst alle Funktionen des Windows APIs in .NET verfügbar. Weiterhin bieten einige Hersteller nur DLLs als Schnittstelle zu spezieller Hardware oder anderen Dingen an, so dass Sie diese in .NET-Anwendungen importieren müssen.Nachteilig ist, dass Sie Unmanaged Code verwenden (also Code der nicht durch die CLR überwacht wird) und dadurch unter anderem den Sicherheitsmechanismus von .NET aushebeln.new
aufgerufen wird. Er hat denselben Namen wie die Klasse und
gibt keinen Wert zurück. Häufig gibt es mehrere Überladungen des Konstruktors (also mehrere Konstruktoren mit unterschiedlichen Parametern), um verschiedene Möglichkeiten zu schaffen,
ein Objekt schon bei dessen Erzeugung zu initialisieren. Wird in einer Klasse oder Struktur kein Konstruktor definiert, wird automatisch ein Standardkonstruktor (Defaultkonstruktor) bereitgestellt,
der keine Parameter besitzt. Der Konstruktor übernimmt normalerweise die Initialisierung der Felder einer Klasse.
public
vereinbart und tragen den Namen der Klasse.
new
wird der passende Konstruktor aufgerufen.
int
-Zahlen mit dem Wert 0
.
new
) aufzurufen.
new
instanziert werden kann.
Diese Konstruktoren werden z.B. in Klassen verwendet, die nur statische Member besitzen. Hier ist es weder notwendig noch sinnvoll, eine Instanz zu erzeugen, vgl. später in diesem Kapitel.
Die Klasse Buch
besitzt drei Überladungen des Konstruktors, in denen die Instanzvariablen mit den übergebenen Werten initialisiert werden. In der Main()
-Methode werden
zwei der Konstruktoren bei der Verwendung des Operators new
aufgerufen. Anschließend werden zur Überprüfung die Inhalte der Variablen eines Buch
-Objekts über die
Methode ObjektAnzeigen()
ausgegeben. Über this
wird hier Bezug auf die Instanzvariablen der Klasse genommen. Diese Angabe ist notwendig, da die Parameter den gleichen
Namen wie diese Variablen besitzen.
using System; namespace CSharpBuch.Kap05 { public class ObjekteErzeugen { static void Main(string[] args) { Buch buch = new Buch("Märchen", "Gebrüder Grimm"); Console.WriteLine(buch.ObjektAnzeigen()); buch = new Buch("Mord im Orientexpress", "A. Christie", 1978); Console.WriteLine(buch.ObjektAnzeigen()); Console.ReadLine(); } } public class Buch { private string titel; private string autor; private int erscheinungsJahr; public Buch(string titel, int jahr) { this.titel = titel; this.erscheinungsJahr = jahr; this.autor = ""; } public Buch(string titel, string autor) { this.titel = titel; this.autor = autor; this.erscheinungsJahr = 0; } public Buch(string titel, string autor, int jahr) { this.titel = titel; this.erscheinungsJahr = jahr; this.autor = autor; } public string ObjektAnzeigen() { if(erscheinungsJahr == 0) return (autor + ": \"" + titel + "\", Erscheinungsjahr unbekannt"); else if(Autor == "") return ("\"" + titel + "\", " + erscheinungsJahr + " Autor unbekannt"); else return (autor + ": \"" + titel + "\", " + erscheinungsJahr); } } }Listing 5.12: ObjekteErzeugen.cs
this
mit einem Doppelpunkt angefügt und in Klammern werden die Parameter angegeben. In diesem Fall wird
zuerst der Konstruktor aufgerufen, der die passenden Parameter zum Aufruf von this
besitzt. Danach wird erst der eigentlich aufgerufene Konstruktor ausgeführt.
INFO
Die Bedeutung von this wird im nächsten Kapitel genauer erläutert.
In den weniger umfangreichen Konstruktoren der Klasse Buch
werden die Standardwerte einfach an den "allumfassenden" Konstruktor übergeben. Nachdem dieser
ausgeführt wurde, werden die Anweisungen im zuerst aufgerufenen Konstruktor ausgeführt.
public Buch(string titel, int jahr): this(titel, "", jahr) { Console.WriteLine("Konstruktor (titel, jahr)"); } public Buch(string titel, string autor, int jahr) { Console.WriteLine("Konstruktor (titel, autor, jahr)"); this.titel = titel; this.erscheinungsJahr = jahr; this.autor = autor; }Listing 5.13: ObjekteErzeugen2.cs Ausgabe:
Konstruktor (titel, autor, jahr) Konstruktor (titel, jahr)"
Typ t = new Typ{ Variable|Eigenschaft =Initialisieren Sie eine Variable bzw. Eigenschaft über den Konstruktor und zusätzlich über eine Initialisiererliste, wird zuerst der Konstruktor aufgerufen und danach der Wert durch die Initialisiererliste zugewiesen. Der Konstruktor wird also in jedem Fall zuerst ausgeführt., ...}; Typ t = new Typ( ){ Variable|Eigenschaft = , ...};
Person
besitzt eine öffentliche Variable (nur zu Demonstrationszwecken) und eine öffentliche Eigenschaft. Die Eigenschaft nutzt dabei die Spracherweiterung der
automatisch implementierten Eigenschaften (Eigenschaften werden später noch ausführlich erläutert).
Die Variable und die Eigenschaft werden nun auf drei unterschiedliche Weisen initialisiert (Name und Vorname, Name, Vorname). Normalerweise hätte man dazu drei Konstruktoren
bereitstellen müssen. Durch die Verwendung der Objektinitialisierer entfällt dieser Aufwand.
Zusätzlich zur Objektinitialisierung können auch andere Konstruktoren verwendet werden. Das runde Klammerpaar muss in diesem Fall vor der Objektinitialisierung stehen und es
wird die explizite Angabe eines parameterlosen Konstruktors für die beiden anderen Objektinitialisierungen benötigt.
namespace CSharpBuch.Kap05 { public class ObjektInitialisierer { static void Main(string[] args) { Person p1 = new Person{ Name = "Meierle", Vorname = "Paule" }; Person p2 = new Person{ Name = "Schulze" }; Person p3 = new Person("Schmidt"){ Vorname = "Paula" }; } } public class Person { public string Name; public string Vorname { get; set; } public Person() {} public Person(string Name) { this.Name = Name; } } }Listing 5.14: ObjektInitialisierer.cs
~
), z.B. ~Buch()
.
Dem letzten Beispiel wird nun noch ein Destruktor für die Klasse Buch
hinzugefügt. Dieser gibt eine Meldung auf der Konsole aus, wenn ein Objekt zerstört wird. Wenn Sie die
Anwendung ausführen, werden Sie feststellen, dass der Destruktor erst nach dem Aufruf von ReadLine()
aktiviert wird. Dies liegt daran, dass die Buch
-Objekte
zwar nicht mehr benötigt werden, der Garbage Collector aber noch nicht zum Einsatz gekommen ist.
~Buch() { Console.WriteLine("Buch-Objekt zerstört"); }Listing 5.15: ObjekteErzeugen.cs
TIPP
Um den Zeitpunkt für die Freigabe von Ressourcen besser beeinflussen zu können, wird in .NET die Implementierung einerDispose()
-Methode vorgeschlagen,
die aufgerufen wird, wenn ein Objekt nicht mehr benötigt wird. Mehr dazu erfahren Sie im Kapitel zu Interfaces, die in diesem Zusammenhang eine wichtige Rolle spielen.INFO
Leere Destruktoren sollten nicht angegeben werden, sie führen zu Performanceverlusten. Es ist möglich, den Garbage Collector überGC.Collect()
direkt
aufzurufen. Dies ist jedoch nicht ratsam, da es meist ebenfalls zu Performance-Verlusten führt.
Einem partiellen Typ wird das Schlüsselwort partial
vorangestellt.
partial class Mathe { }
partial
versehen werden, das direkt vor class
, interface
oder struct
anzugeben ist.
abstract
oder sealed
gekennzeichnet, so gilt dies später auch für den gesamten Typ.
Das Hauptprogramm verwendet die Klasse Mathe
, die wiederum durch zwei partielle Klassen in den Dateien Teil1.cs und Teil2.cs
implementiert wird. Wichtig ist, dass für die Implementierung der Klasse Mathe
immer der gleiche Namespace und die gleichen Zugriffsmodifizierer verwendet werden. Durch das Konzept der
partiellen Klassen können nun z.B. komplexe Funktionen in eigene Dateien ausgelagert werden.
using System; namespace CSharpBuch.Kap05 { public class PartielleTypen { static void Main(string[] args) { Mathe m = new Mathe(); Console.WriteLine("3 + 4 = " + m.Add(3, 4)); Console.WriteLine("3 - 4 = " + m.Minus(3, 4)); Console.ReadLine(); } } }Listing 5.16: PartielleTypen.cs
using System; namespace CSharpBuch.Kap05 { public partial class Mathe { public int Add(int zahl1, int zahl2) { return zahl1 + zahl2; } } }Listing 5.17: Teil1.cs
using System; namespace CSharpBuch.Kap05 { public partial class Mathe { public int Minus(int zahl1, int zahl2) { return zahl1 - zahl2; } } }Listing 5.18: Teil2.cs
private
. Die Angabe eines Zugriffsmodifizierers wie public oder protected ist nicht erlaubt.
abstract
, extern
, new
, override
, sealed
und virtual
sind nicht erlaubt. Allerdings dürfen partielle Methoden static
sein.
void
. Parameter darf die Methode zwar besitzen, allerdings sind keine out
-Parameter erlaubt.
Die Klasse Person
wird über zwei partielle Klassen definiert. In der ersten Klasse befindet sich nur der Methodenrumpf, um eine Ausgabe durchzuführen. Außerdem besitzt
sie eine öffentliche Methode AusgabeDurchfuehren()
, welche die private Methode Ausgabe()
aufruft.
Person
wird nun die Methode Ausgabe()
implementiert. Allerdings ist sie im Moment auskommentiert. Dies führt aber zu keinem
Compiler- oder Laufzeitfehler. Es wird einfach die Methode vom Compiler ignoriert. Werden die Kommentarzeichen entfernt, wird die Konsolenausgabe durchgeführt.
namespace CSharpBuch.Kap05 { public class PartielleMethoden { static void Main(string[] args) { Person p = new Person(); p.AusgabeDurchfuehren(); } } public partial class Person { public void AusgabeDurchfuehren() { Ausgabe(); } partial void Ausgabe(); // nur Deklaration } public partial class Person { /*** entfernen, um die Methode zu nutzen partial void Ausgabe() { Console.WriteLine("Hallo"); } */ } }Listing 5.19: PartielleMethoden.cs
Main()
. Jetzt soll den statischen Teilen einer Anwendung etwas mehr Augenmerk
geschenkt werden. Eine Klasse stellt ja so etwas wie einen Bauplan für Objekte dar. Jedes Objekt besitzt eine eigene Kopie der Datenelemente seiner Klasse (die nicht-statisch sind).
Nicht-statische Methoden können nur über Objekte aufgerufen werden, da sie mit den nicht-statischen Datenelementen der Objekte arbeiten (d.h. mit ihren eigenen Kopien der Datenelemente).
Sie existieren sozusagen nur in einem Objekt.
In vielen Fällen ist es aber nicht notwendig bzw. sinnvoll, ein Objekt einer Klasse zu erzeugen, nur um deren Methoden zu nutzen. Allerdings zwingt das .NET Framework die Entwickler,
alle Methoden in einer Klasse unterzubringen. Eine Lösung bieten statische Klassenelemente, welche direkt über eine Klasse, d.h. den Klassennamen, aufgerufen werden können, ohne dass
eine Instanz der Klasse erstellt werden muss. Statische Methoden sind also vergleichbar mit den Funktionen und Prozeduren klassischer Programmiersprachen, mit der Ausnahme, dass
sie sich in einer Klasse befinden.
Mathematische oder Zeichenkettenoperationen stellen ideale Kandidaten für statische Klassenelemente dar. So wird z.B. eine Methode zum Addieren zweier Zahlen mit den beiden Zahlen als Parameter aufgerufen und liefert die Summe als Ergebnis. Die Methode benötigt keine weiteren Informationen, d.h., sie benötigt insbesondere keinen Zugriff auf die Werte von Instanzvariablen, da kein Zustand über mehrere Methodenaufrufe hinweg gespeichert werden muss. Die Implementierung als statische Methode vermeidet, dass für den Aufruf der Methode erst ein Objekt erstellt werden muss.
public class Mathe { public static int Add(int zahl1, int zahl2) { return zahl1 + zahl2; } } ... // und der Aufruf int ergebnis = Mathe.Add(10, 11);
this
-Zeiger (vgl. Kapitel 7) verwendet werden, da kein Objekt verfügbar ist, auf das this
verweisen könnte.
C# kennt drei verschiedene Einsatzgebiete des Schlüsselwortes static
. Sie können es zusammen mit Variablen, Methoden und seit der Version 2.0 auch mit Klassen verwenden.
static
gekennzeichnet. Optional kann ein Zugriffsmodifizierer vorangestellt werden.
static
deklariert, müssen alle Klassenelemente statisch sein. Von statischen Klassen können keine anderen Klassen abgeleitet werden (sie sind automatisch sealed
-
vgl. Kapitel 7). Der Vorteil von statischen Klassen ist, dass von ihnen niemals Instanzen erstellt werden können und dies bereits durch den Compiler sichergestellt werden kann. Bisher
konnten Sie dies nur über einen privaten Konstruktor verhindern. Der Vorteil dieser Vorgehensweise ist, dass Optimierungen durchgeführt werden können.
static
und den Klassennamen, gefolgt von einem Klammerpaar, deklariert. Sie haben keine Parameter und keinen weiteren Zugriffsmodifizierer. Statische Klassenelemente werden initialisiert, bevor der
statischen Konstruktor aufgerufen wird. Statische Konstruktoren können in statischen wie auch in "normalen" Klassen verwendet werden, um die statischen Bestandteile zu initialisieren.
[Modifizierer] staticVariablenname; [Modifizierer] static Methodenname() [Modifizierer] static class Klassenname {}
Im Beispiel wird eine statische Klasse Mathe
deklariert, welche nur statische Elemente enthält. Über die statische Variable aufrufe soll die Anzahl der Methodenaufrufe gezählt werden.
Die Variable wird in einem statischen Konstruktor initialisiert. Über zwei statische Methoden wird die Addition durchgeführt und der Wert der Variablen aufrufe
zurückgeliefert. Die
Methoden der Klasse werden in der Methode Main()
direkt über den Klassennamen aufgerufen. Die Verwendung einer solchen statischen Variablen ist z.B. für manuelle Statistik- und
Durchsatzmessungen geeignet.
Format()
der Klasse String
dient der Ausgabe von formatierten Zeichenketten. Die verwendeten Platzhalter {0}
werden durch die Werte der
folgenden Parameter der Reihenfolge nach ersetzt.
using System; namespace CSharpBuch.Kap05 { public class StatischeElemente { static void Main(string[] args) { Console.WriteLine(String.Format("3 + 4 = {0}", Mathe.Add(3, 4))); Console.WriteLine(String.Format("6 + 5 = {0}", Mathe.Add(6, 5))); Console.WriteLine(String.Format("Mathe wurde {0} mal verwendet.", Mathe.GetAufrufe())); } } public static class Mathe { public const double PI = 3.14; private static int aufrufe; public static Mathe() { aufrufe = 0; } public static int Add(int zahl1, int zahl2) { aufrufe++; return zahl1 + zahl2; } public static int GetAufrufe() { return aufrufe; } } }Listing 5.20: StatischeElemente.cs
Main()
als static
deklariert wird. Da noch keine Instanz der umgebenden Klasse existiert, wenn
die .NET-Anwendung gestartet wird, muss diese Methode unabhängig von einem Objekt ausgeführt werden können. Die Angabe von public
ist nicht notwendig, da die Anwendung über
die Assembly gestartet wird, welche die Methode Main()
enthält. Und da Main()
implizit internal ist, kann die Methode auch aufgerufen werden. Als Rückgabetyp
kommt neben void auch int
in Frage. Weiterhin kann Main()
mit einer Parameterliste string[] args
aufgerufen werden. Dadurch ist es möglich, einer
Anwendung Parameter zu übergeben (z.B. beim Aufruf über die Kommandozeile). Für die Implementierung der Methode Main()
ergeben sich damit die folgenden vier Möglichkeiten:
int Main() int Main(string[] args) void Main() void Main(string[] args)
this
vorangestellt.
Die Erweiterungsmethode wird sozusagen auf ein Objekt vom Typ der zu erweiternden Klasse angewandt.
static
),
hat diese Methode beim Aufruf immer Vorrang. Eine Fehlermeldung oder Warnung gibt es allerdings nicht.
Sie können beispielsweise eine Erweiterung für die Klasse String
erstellen, welche die Anzahl der Zeichen des Strings zurückliefert (also die Eigenschaft Length
auswertet). Die Namen der Erweiterungsklasse und Erweiterungsmethode sind hierbei beliebig. Die Erweiterungsmethode kann nun wie jede andere Methode der Klasse String
aufgerufen werden.
public static class StringErweiterung { public static int ZeichenAnzahl(this string para) { return para.Length; } } ... string name = "Meierle"; Console.WriteLine(name.ZeichenAnzahl());
TIPP
Auch wenn Erweiterungsmethoden ein interessantes Feature darstellen, sollten Sie damit sparsam umgehen. In großen Klassenbibliotheken kann die Trennung zwischen einem Typ und seinen Erweiterungsmethoden zu Unübersichtlichkeit und im schlechtesten Fall zu Fehlverhalten führen, wenn beispielsweise später eine Methode mit der Signatur der Erweiterungsmethode einer Klasse hinzugefügt wird (z.B. über Vererbung).
Die Klasse Person
besitzt zwei öffentliche Instanzvariablen. Diese sollen mit einer Methode ausgegeben werden, die als Erweiterungsmethode implementiert
wird. Dazu wird eine weitere statische Klasse PersonErweiterung
bereitgestellt, welche die Methode Ausgabe()
besitzt. Als erster Parameter
muss ein Objekt vom Typ der zu erweiternden Klasse übergeben werden. Der zweite Parameter soll als Trennzeichen für die Ausgabe dienen. Zum Test wird ein Person
-Objekt
über einen Objektinitialisierer erzeugt und danach wird für das Objekt die Methode Ausgabe()
aufgerufen.
using System; namespace CSharpBuch.Kap05 { public class ErweiterungsMethoden { static void Main(string[] args) { Person p = new Person { Name="Meierle", Vorname="Paule" }; p.Ausgabe(", "); } } public class Person { public string Name; public string Vorname; } public static class PersonErweiterung { public static void Ausgabe(this Person pers, string trenner) { Console.WriteLine(pers.Name + trenner + pers.Vorname); } } }Listing 5.21: ErweiterungsMethoden.cs
Bruch
-Objekten gerechnet werden kann. Eine andere (theoretische)
Anwendungsmöglichkeit wäre, dass für eine Klasse Maschinenteil
eine Addition definiert wird, so dass aus mehreren Maschinenteilen eine Maschine erzeugt werden kann
(rein softwaretechnisch natürlich). Es lassen sich zwar nicht alle Operatoren überladen, aber die meisten. Die folgenden Operatoren können Sie dazu verwenden:
+ - * / % & | ^ ! ~ ++ -- true, false << >> == != < > <= >=
operator
angegeben, gefolgt vom
Operatorsymbol. Zwischen beiden kann optional ein Leerzeichen stehen.
ref
- und out
-Parameter verwendet werden. Mindestens einer der Parameter muss dem Typ entsprechen, in dem sich die Operatormethode befindet.
<
und >
. Für einige Operatoren ist es außerdem erforderlich,
dass bestimmte Methoden wie Equals()
und GetHashCode()
überschrieben werden.
explicit
und implicit
definieren. Mehr zu den beiden Schlüsselwörtern finden Sie in der Hilfe.
public staticoperator [Symbol](Operand1, ...) { }
Zur Bruchrechnung existiert im .NET Framework noch keine geeignete Klasse. Die folgende Klasse Bruch
besitzt zwei private Instanzvariablen, welche den Wert des Zählers und des
Nenners verwalten, einen Konstruktor, mit dem beide Werte initialisiert werden, zwei Get-Methoden, um den Zähler und Nenner des Bruchs abzufragen, einen überladenen Operator zur
Addition zweier Brüche und eine Methode ToString()
, welche eine geeignete Textdarstellung eines Bruches zurückliefert. In der Methode Main()
werden zum Test
zwei Brüche addiert und das Ergebnis wird ausgegeben.
using System; namespace CSharpBuch.Kap05 { public class OperatorenUeberladen { static void Main(string[] args) { Bruch b1 = new Bruch(3, 4); Bruch b2 = new Bruch(7, 5); Bruch b3 = b1 + b2; Console.WriteLine(b1.ToString() + "+" + b2.ToString() + "=" + b3.ToString()); } } public class Bruch { private int zaehler; private int nenner; public Bruch(int zaehler, int nenner) { this.zaehler = zaehler; this.nenner = nenner; } public int GetZaehler() { return zaehler; } public int GetNenner() { return nenner; } public static Bruch operator +(Bruch op1, Bruch op2) { int nenner = op1.nenner * op2.nenner; int zaehler = nenner / op1.nenner * op1.zaehler + nenner / op2.nenner * op2.zaehler; Bruch b = new Bruch(zaehler, nenner); return b; } public override string ToString() { return zaehler.ToString() + "/" + nenner.ToString(); } } }Listing 5.22: OperatorenUeberladen.cs
TIPP
Beachten Sie beim Überladen eines Operators, dass Sie dabei seine Bedeutung nicht verschleiern. So kann man sich sicher schwer vorstellen, wie zweiDVD
-Objekte voneinander abgezogen oder multipliziert werden sollen. Bruch
um Operatoren für die Subtraktion, Multiplikation und Division. Testen Sie die Operatoren in einer Anwendung.
int
-Zahl entgegennimmt. Weiterhin soll sie zwei out
-Parameter besitzen,
welche die Anzahl der Stellen dieser Zahl sowie die Quersumme zurückgeben.
Die Zahl 12345
hat beispielsweise fünf Stellen und die Quersumme ist 15
(1+2+3+4+5
).
Bruch
aus Aufgabe 2 über partielle Klassen. Erstellen Sie dazu zwei C#-Dateien, die jeweils einen Teil der
Implementierung der Klasse Bruch enthalten.
double
-Parameter per ref
-Parameterübergabe an eine Methode. Diese Methode berechnet für jedes Element der
Struktur den Bruttopreis und gibt 3% Rabatt, wenn der Nettopreis größer als 100 Euro ist. Im ref
-Parameter wird der Gesamtbruttobetrag zurückgegeben.