3.7 Konstruktoren 

Konstruktoren sind spezielle Methoden, die nur dann aufgerufen werden, wenn mit dem reservierten Wort new eine neue Instanz einer Klasse erzeugt wird. Sie dienen zur kontrollierten Initialisierung von Objekten, um beispielsweise Feldern die erforderlichen Anfangswerte zuzuweisen, die Verbindung zu einer Datenbank aufzubauen oder eine Datei zu öffnen. Damit werden unzulässige Objektzustände vermieden, damit einem Objekt nicht nach Abschluss seiner Instanziierung substanzielle Startwerte fehlen.
Genauso wie Methoden lassen sich Konstruktoren überladen; sie können demnach parameterlos oder vielfach parametrisiert sein. Allerdings haben Konstruktoren grundsätzlich keinen Rückgabewert, auch nicht void. Bei der Definition eines Konstruktors wird zuerst ein Zugriffsmodifizierer angegeben, direkt dahinter der Klassenbezeichner. Damit sieht der parameterlose Konstruktor der Klasse Circle wie folgt aus:
public Circle() { }
Wird ein Objekt mit
Circle kreis = new Circle();
erzeugt, verbergen sich hinter dem Objekterstellungsprozess zwei Schritte:
- Der erforderliche Speicher für die Daten des Objekts wird reserviert.
- Ein Konstruktor wird aufgerufen, der das Objekt initialisiert.
Ein Blick in die aktuelle Implementierung der Klasse Circle wirft die Frage auf, wo der Konstruktor zu finden ist, der für die Initialisierung verantwortlich ist. Die Antwort ist einfach: Die augenblickliche Klassenimplementierung enthält zwar explizit keinen Konstruktor, er existiert aber dennoch, allerdings implizit. Diesen Konstruktor, der parameterlos ist, bezeichnet man auch als Standardkonstruktor. Der Standardkonstruktor ist grundsätzlich immer vorhanden – zumindest solange wir keinen parametrisierten definieren.
3.7.1 Konstruktoren bereitstellen 

Um nach der Instanziierung dem Circle-Objekt individuelle Daten zuzuweisen, muss der Benutzer der Klasse jede Eigenschaft einzeln aufrufen, z. B.:
kreis.Radius = 10; kreis.XCoordinate = 20; kreis.YCoordinate = 20;
Wäre es nicht sinnvoller, einem Circle-Objekt schon bei dessen Instanziierung den Radius mitzuteilen, vielleicht sogar auch noch gleichzeitig die Bezugspunktkoordinaten? Genau diese Aufgabe können entsprechend parametrisierte Konstruktoren übernehmen.
Das Beispiel der Circle-Klasse wollen wir nun so ergänzen, dass bei der Instanziierung unter drei Erstellungsoptionen ausgewählt werden kann:
- Ein Circle-Objekt kann wie bisher ohne Übergabe von Initialisierungsdaten erzeugt werden.
- Einem Circle-Objekt kann bei der Instanziierung sein Radius übergeben werden.
- Bei der Instanziierung kann sowohl der Radius als auch die Lage des Bezugspunktes festgelegt werden.
Der Code der Circle-Klasse kann, um diese Forderungen zu erfüllen, wie folgt ergänzt werden:
public Circle() {} public Circle(double radius) { Radius = radius; } public Circle(double radius, int xPos, int yPos) { XCoordinate = xPos; YCoordinate = yPos; Radius = radius; }
Weiter oben haben Sie im Zusammenhang mit der Bereitstellung der Methode MoveXY erfahren, dass die Zuweisung an ein gekapseltes Feld immer über die Eigenschaftsmethode führen sollte. An dieser Stelle wird dieser Sachverhalt bei der Zuweisung des Radius besonders deutlich. Würden Sie den vom Parameter radius beschriebenen Wert direkt dem gleichnamigen Feld zuweisen, könnte das Circle-Objekt tatsächlich einen negativen Radius haben. Die Überprüfung in der Eigenschaftsmethode garantiert aber, dass der Radius des Kreises niemals negativ sein kann.
Hinweis |
In Abschnitt 3.5.7, »Automatisch implementierte Eigenschaften«, haben Sie bereits die vereinfachte Objektinitialisierung kennengelernt. Noch einmal zur Erinnerung: Mit Circle kreis = new Circle() { XCoordinate = 23, lassen sich den Eigenschaften ebenfalls sofort innerhalb einer Anweisungszeile Werte zuweisen. Warum also dann noch Konstruktoren? Betrachten Sie die Konstruktoren eher als ein historisch bedingtes Konstrukt, das in jeder ernsthaften OOP-Umgebung anzutreffen ist. Die gezeigte vereinfachte Objektinitialisierung wurde erst mit dem Erscheinen von .NET 3.5 eingeführt, um eine Technologie namens LINQ zu unterstützen, die wir uns in diesem Buch auch noch ansehen werden. Es ist empfehlenswert, eine Klasse mit Konstruktoren auszustatten, da diese allgemein üblich sind und außerdem zu den Metadaten gehören. |
3.7.2 Parametrisierte Konstruktoren und die Objektinitialisierung 

Weiter oben wurde bereits gezeigt, dass Sie den Aufruf des Konstruktors um die Initialisierung ausgewählter Felder erweitern können:
Circle kreis = new Circle { XCoordinate = 23, Radius = 9 };
Diese Art der Wertzuweisung beschränkt sich nicht nur auf parameterlose Konstruktoren und kann auch auf parametrisierte angewandt werden:
Circle kreis = new Circle(50){ XCoordinate = 23, YCoordinate = 100 };
Hier wird der Radius dem einfach parametrisierten Konstruktor übergeben. Die Lagekoordinaten folgen anschließend in geschweiften Klammern. Die Zuweisung im Konstruktor erfolgt dabei vor der Zuweisung der Initialisierungswerte in den geschweiften Klammern. Das ist wichtig zu wissen, denn ein mit
Circle kreis = new Circle(50) { Radius = 678 };
erzeugtes Objekt hat den Radius 678.
3.7.3 Konstruktoraufrufe 

Im Allgemeinen werden Konstruktoren dazu benutzt, den Feldern eines Objekts bestimmte Startwerte zuzuweisen. Um ein Circle-Objekt zu erzeugen, stehen mit der obigen Konstruktorüberladung drei Möglichkeiten zur Verfügung:
- Es wird ein Kreis ohne die Übergabe eines Arguments erzeugt. Dabei wird der parameterlose Konstruktor aufgerufen:
Circle kreis = new Circle();
-
- Der Kreis hat in diesem Fall den Radius 0, die Bezugspunktkoordinaten werden ebenfalls mit 0 initialisiert.
- Ein neuer Kreis wird nur mit dem Radius definiert, z. B.:
Circle kreis = new Circle(10);
-
- Es wird der Konstruktor aufgerufen, der ein Argument erwartet. Da den Bezugspunktkoordinaten keine Daten zugewiesen werden, sind deren Werte 0.
- Einem Kreis werden bei der Erzeugung sowohl der Radius als auch die Bezugspunktkoordinaten übergeben:
Circle meinKreis = new Circle(10, 15, 20);
Bei der Instanziierung einer Klasse muss der C#-Compiler selbst herausfinden, welcher Konstruktor aufgerufen werden soll. Dazu werden die Typen der übergebenen Argumente mit denen der Konstruktoren verglichen. Liegt eine Doppeldeutigkeit vor oder können die Argumente nicht zugeordnet werden, löst der Compiler einen Fehler aus.
3.7.4 Definition von Konstruktoren 

Trotz der Ähnlichkeit zwischen Konstruktoren und Methoden unterliegen Konstruktoren bestimmten, teilweise auch abweichenden Regeln:
- Die Bezeichner der Konstruktoren einer Klasse entsprechen dem Klassenbezeichner.
- Konstruktoren haben grundsätzlich keinen Rückgabewert, auch nicht void.
- Die Parameterliste eines Konstruktors ist beliebig.
- Der Konstruktor einer Klasse wird bei der Instanziierung mit dem Schlüsselwort new aufgerufen.
- Ein Konstruktor kann nicht auf einem bereits bestehenden Objekt aufgerufen werden, beispielsweise um Instanzvariablen andere Werte zuzuweisen.
Enthält die Klasse keinen expliziten Konstruktor, wird bei der Erzeugung eines Objekts ein impliziter, parameterloser Standardkonstruktor aufgerufen. Nun folgt noch eine weitere sehr wichtige Regel:
Regel |
Der implizite Standardkonstruktor existiert nur dann, wenn er nicht durch einen parametrisierten Konstruktor überladen wird. |
Wenn Sie nur einen einzigen parametrisierten Konstruktor implementieren, dann enthält die Klasse keinen impliziten Standardkonstruktor mehr. Sie können dann mit
Circle kreis = new Circle();
kein Objekt mehr erzeugen. Wollen Sie das dennoch sicherstellen, muss der parametrisierte Konstruktor ausdrücklich codiert werden – so wie im Beispiel der Klasse Circle. Aus diesem Grund haben wir auch in Circle einen parameterlosen Konstruktor definiert, obwohl er keinen Code enthält. Wir entsprechen damit unserer selbst auferlegten Forderung, ein Circle-Objekt ohne Startwerte erzeugen zu können.
3.7.5 »internal«-Konstruktoren 

Als public deklarierte Konstruktoren stehen allen Benutzern der Klasse zur Verfügung. Eine andere Anwendung kann die öffentlichen Konstruktoren dazu benutzen, ein Objekt nach den Maßstäben zu erzeugen, die in der Parameterliste des Konstruktors festgelegt sind. Manchmal ist es jedoch wünschenswert, einen bestimmten Konstruktor nur in der aktuellen Anwendung offenzulegen (also in der Anwendung, in der die Klasse definiert ist), um damit eine bestimmte Instanziierung aus anderen Anwendungen heraus zu unterbinden. Mit dem Zugriffsmodifizierer internal können Sie eine solche Einschränkung realisieren. Denken Sie jedoch daran, dass der implizite Standardkonstruktor grundsätzlich immer öffentlich (public) ist.
3.7.6 »private«-Konstruktoren 

Sie werden immer wieder Klassen entwickeln, die nicht instanziiert werden dürfen. Um die Instanziierung zu verhindern, muss der parameterlose Konstruktor, der standardmäßig public ist, mit einem private-Zugriffsmodifizierer überschrieben werden.
public class Demo { private Demo() { ... } ... }
3.7.7 Konstruktorenaufrufe umleiten 

Konstruktoren können wie Methoden überladen werden. Jeder Konstruktor enthält dabei typischerweise eine Implementierung, die aufgrund der ihm übergebenen Argumente für ihn spezifisch ist. Manchmal kommt es vor, dass der Konstruktor einer Klasse Programmcode enthält, der von einem zweiten Konstruktor ebenfalls implementiert werden muss. Sehen wir uns dazu die beiden parametrisierten Konstruktoren der Klasse Circle an:
public Circle(double radius) { Radius = radius; } public Circle(double radius, int xPos, int yPos) { XCoordinate = xPos; YCoordinate = yPos; Radius = radius; }
Es fällt auf, dass in beiden Konstruktoren der Radius des Circle-Objekts festgelegt wird. Es liegt nahe, zur Vereinfachung den einfach parametrisierten Konstruktor aus dem dreifach parametrisierten heraus aufzurufen.
Da Konstruktoren eine besondere Spielart der Methoden darstellen und über den Operator new aufgerufen werden, stellt C# eine syntaktische Variante bereit, mit der aus einem Konstruktor heraus ein anderer Konstruktor derselben Klasse aufgerufen wird. Hier kommt erneut die Referenz this ins Spiel:
public Circle(double radius) { Radius = radius; } public Circle(double radius, int xPos, int yPos) : this(radius) { XCoordinate = xPos; YCoordinate = yPos; }
Die Signatur des dreifach parametrisierten Konstruktors ist um
: this(radius)
ergänzt worden. Dies hat den Aufruf eines anderen Konstruktors zur Folge, in unserem Fall den Aufruf des einfach parametrisierten Konstruktors. Gleichzeitig wird der vom Aufrufer übergebene Radius weitergeleitet, den der dreifach parametrisierte Konstruktor in seiner Parameterliste entgegennimmt. Der implizit aufgerufene einfach parametrisierte Konstruktor wird ausgeführt und gibt die Kontrolle danach an den aufrufenden Konstruktor zurück.
Eine Umleitung des Konstruktoraufrufs darf auch über mehrere Konstruktoren hinweg erfolgen und muss nicht zwangsläufig in Richtung zu den Konstruktoren mit einer geringeren Anzahl von Parametern erfolgen. Es kann durchaus ein zweifach parametrisierter Konstruktor den drei- oder vierfach parametrisierten Konstruktor derselben Klasse aufrufen.