Ein Konstruktor ist der Code, der läuft, wenn du ein Objekt mit new erzeugst. Er hat denselben Namen wie die Klasse, keinen Rückgabetyp, und seine Aufgabe ist, das neue Objekt in einen gültigen Zustand zu bringen, bevor es jemand verwendet.
Ausgabe:
Mug: 8.50
Rejected: price
Weil der Konstruktor einen negativen Preis ablehnt, kann kein Product mit negativem Preis existieren. Diese Garantie ist der Hauptgrund, Konstruktoren zu schreiben, statt Felder von außen zu setzen.
Der Standardkonstruktor und wann er verschwindet
Deklariert eine Klasse keinen Konstruktor, liefert der Compiler einen öffentlichen parameterlosen, der nichts weiter tut, als die Feldinitialisierer auszuführen. Deshalb funktioniert new BankAccount() bei einer Klasse, die nur Felder hat.
Sobald du irgendeinen Konstruktor deklarierst, wird dieser implizite nicht mehr erzeugt:
class Product
{
public string Name;
public Product(string name) { Name = name; }
}
var p = new Product(); // error CS7036: There is no argument given that corresponds to the required parameter 'name'
Das überrascht Leute, wenn ein Serializer, ein ORM oder eine generische new()-Einschränkung einen parameterlosen Konstruktor braucht. Wenn du beide willst, deklariere den parameterlosen selbst:
public Product() { } // or
public Product() : this("Unnamed") { } // delegate to the other one (next section)
Ein Konstruktor kann public, internal, protected oder private sein. Sein Zugriff entscheidet, wer damit Objekte erzeugen darf.
Überladung und Verkettung mit this(...)
Eine Klasse kann mehrere Konstruktoren mit verschiedenen Parameterlisten haben. Um nicht dieselben Zuweisungen in jeden zu kopieren, verkette sie: : this(...) ruft einen anderen Konstruktor derselben Klasse auf, bevor der aktuelle Rumpf läuft.
Ausgabe:
new Pizza():
full constructor: medium, classic, 0
size-only constructor body
parameterless constructor body
Result: medium classic with 0 toppings
Die Kette führt den vollständigsten Konstruktor zuerst aus und läuft zurück bis zu dem, den du aufgerufen hast. Halte die eigentliche Arbeit (Validierung, Zuweisungen) in einem Konstruktor und lass die anderen nur Standardwerte liefern.
Optionale Parameter sind eine Alternative: public Pizza(string size = "medium", string crust = "classic", int toppings = 0) ergibt einen Konstruktor, der alle drei Aufrufe abdeckt. Verkettung passt trotzdem besser, wenn die kürzeren Konstruktoren etwas berechnen müssen oder wenn die Änderung eines Standardwerts keine Neukompilierung der Aufrufer erfordern darf (Standardwerte optionaler Parameter werden beim Kompilieren in den aufrufenden Code kopiert).
Einen Konstruktor der Basisklasse mit base(...) aufrufen
Eine abgeleitete Klasse erbt keine Konstruktoren. Jeder Konstruktor der abgeleiteten Klasse muss zuerst einen Konstruktor der Basisklasse ausführen. Schreibst du nichts, fügt der Compiler einen Aufruf des parameterlosen Basiskonstruktors ein; hat die Basis keinen, musst du mit : base(...) einen wählen.
Die Reihenfolge, in der alles läuft, sollte man einmal gesehen haben:
Ausgabe:
Truck field initializer
Vehicle field initializer
Vehicle constructor body
Truck constructor body
KL-204, 3 axles
Zuerst laufen die Feldinitialisierer, die der abgeleiteten Klasse vor denen der Basisklasse, und dann laufen die Konstruktorrümpfe von der Basis nach unten. Wenn der Rumpf von Truck läuft, ist Plate also schon gesetzt. Die eine Falle in dieser Reihenfolge: Ruft der Basiskonstruktor eine virtual-Methode auf, die Truck überschreibt, läuft die Überschreibung vor dem Konstruktorrumpf von Truck und sieht Axles noch bei 0. Rufe in Konstruktoren keine virtuellen Methoden auf.
Statische Konstruktoren
Ein statischer Konstruktor initialisiert den Typ statt eines Objekts. Er hat keine Parameter und keinen Zugriffsmodifizierer, und die Runtime ruft ihn genau einmal auf, kurz bevor der Typ zum ersten Mal verwendet wird.
Ausgabe:
Program started
Loading tax table (runs once)
123.00
119.00
Die Runtime macht den statischen Konstruktor threadsicher: Selbst wenn mehrere Threads gleichzeitig auf den Typ zugreifen, läuft er einmal. Wirft er, wirft jede spätere Verwendung des Typs für die gesamte Lebensdauer des Prozesses TypeInitializationException, halte ihn also einfach und frei von I/O, das scheitern kann.
Konstruktor gegenüber Objektinitialisierer
Ein Objektinitialisierer, new Pizza("large") { Toppings = 3 }, ist kein zweiter Konstruktor. Der Compiler macht daraus „führe den Konstruktor aus, dann weise diese Member zu“. Nimm beides für das, was es gut kann:
- Konstruktorparameter für Werte, ohne die das Objekt nicht gültig sein kann. Jeder Aufrufer muss sie übergeben, und der Konstruktor kann sie prüfen.
- Initialisierer für optionale Einstellungen mit sinnvollen Standardwerten.
var order = new Order(customerId: 42) { Note = "Leave at the door", GiftWrap = true };
Seit C# 11 muss eine als required markierte Property im Initialisierer gesetzt werden, was der Initialisierersyntax einige Garantien eines Konstruktors gibt. Das behandelt die Seite zu Properties.
Private Konstruktoren
Ein private-Konstruktor bedeutet, dass nur die Klasse selbst Instanzen erzeugen kann. Zwei Muster nutzen das: eine statische Fabrikmethode, bei der die Klasse die Erzeugung kontrolliert und ein zwischengespeichertes Objekt oder null zurückgeben kann, und ein Singleton:
Ausgabe:
21
100
Der Compilerfehler für new Temperature(5m) außerhalb der Klasse ist CS0122, derselbe, den Zugriffsmodifizierer für jeden privaten Member erzeugen. Benannte Fabrikmethoden lösen eine echte Einschränkung: Zwei Konstruktoren können nicht beide ein einzelnes decimal nehmen, FromCelsius und FromFahrenheit aber schon. Eine Klasse mit nur statischen Membern (eine Hilfsklasse) sollte stattdessen als static deklariert werden, statt ihren Konstruktor zu verstecken.
Konstruktoren mit Ausdruckskörper
Ein Konstruktor, dessen Rumpf aus einer Anweisung besteht, kann => verwenden:
public Product(string name) => Name = name;
Mit Tupeln kannst du sogar mehrere Felder in einer Zeile zuweisen: public Point(int x, int y) => (X, Y) = (x, y);.
Primäre Konstruktoren (C# 12)
In C# 12 kann eine Klasse oder ein Struct ihre Konstruktorparameter direkt am Typ deklarieren. Die Parameter sind in jedem Member verfügbar:
public class Customer(string name, int id)
{
public string Name { get; } = name; // copy into a property
public string Label => $"#{id} {Name}"; // or read a parameter directly
public Customer(string name) : this(name, 0) { } // other constructors must chain to it
}
Zwei Unterschiede zu Records überraschen Leute. Die Parameter eines primären Konstruktors einer Klasse werden nicht zu öffentlichen Properties; du stellst sie selbst bereit. Und ein Parameter, der in einem Member verwendet wird, wird in ein verstecktes Feld erfasst, das veränderlich bleibt, name = "x"; in einer Methode kompiliert also. Primäre Konstruktoren sind bei Dependency Injection am besten aufgehoben, wo die Parameter Dienste sind, die die Klasse nur aufruft:
public class OrderService(IOrderRepository repo, ILogger<OrderService> log)
{
public Order Get(int id) => repo.Find(id);
}
Häufige Fehler
- Den parameterlosen Konstruktor verlieren. Ein Konstruktor mit Parametern entfernt den impliziten (CS7036 bei jedem
new X()). : base(...)vergessen, wenn die Basisklasse keinen parameterlosen Konstruktor hat. Der Compiler kann den impliziten Aufruf nicht einfügen und meldet, dass für den Pflichtparameter des Basiskonstruktors kein Argument da ist.- Virtuelle Methoden in einem Konstruktor aufrufen. Die Überschreibung läuft vor dem Rumpf des abgeleiteten Konstruktors.
- Schwere Arbeit in einem Konstruktor. Netzwerkaufrufe, Dateizugriffe oder alles Langsame machen Objekte teuer und Exceptions schwer behandelbar. Nimm für diese Arbeit eine statische Fabrikmethode oder eine async-Initialisierungsmethode.
- Einen Rückgabetyp schreiben.
public void Product()ist eine gewöhnliche Methode namensProduct, und der Compiler lehnt sie ab, weil ein Member nicht denselben Namen wie sein umgebender Typ haben darf (CS0542).
Häufig gestellte Fragen
Was ist ein Konstruktor in C#?
Ein Konstruktor ist eine spezielle Methode, die läuft, wenn ein Objekt mit new erzeugt wird. Er hat denselben Namen wie die Klasse und keinen Rückgabetyp: public Product(string name) { Name = name; }. Seine Aufgabe ist, das neue Objekt in einem gültigen Zustand zu hinterlassen, typischerweise indem er Felder aus seinen Parametern zuweist und ungültige Eingaben ablehnt.
Erzeugt C# automatisch einen Standardkonstruktor?
Nur wenn die Klasse überhaupt keinen Konstruktor deklariert. Dann fügt der Compiler einen öffentlichen parameterlosen hinzu, der jedes Feld bei seinem Initialisierer oder Standardwert belässt. Sobald du irgendeinen Konstruktor schreibst, zum Beispiel einen, der einen Namen nimmt, ist der implizite weg, und new Product() kompiliert nicht mehr (Fehler CS7036). Deklariere public Product() { } selbst, wenn du ihn noch brauchst.
Wie rufe ich in C# einen Konstruktor aus einem anderen auf?
Mit : this(...) nach der Parameterliste des Konstruktors: public Product(string name) : this(name, 0m) { }. Der Zielkonstruktor läuft zuerst, dann der Rumpf des aufgerufenen. Um einen Konstruktor der Basisklasse aufzurufen, nimm auf dieselbe Weise : base(...).
Was ist ein statischer Konstruktor in C#?
Ein Konstruktor, der als static markiert ist, ohne Parameter und ohne Zugriffsmodifizierer, und der einmal pro Typ läuft, bevor die erste Instanz erzeugt oder der erste statische Member verwendet wird. Er initialisiert statische Felder, die mehr als einen einzeiligen Initialisierer brauchen. Du kannst ihn nicht selbst aufrufen, und wenn er wirft, ist der Typ für den Rest des Programms unbrauchbar (TypeInitializationException).
Soll ich einen Konstruktor oder einen Objektinitialisierer verwenden?
Nimm Konstruktorparameter für Werte, ohne die das Objekt nicht gültig sein kann, weil der Compiler jeden Aufrufer zwingt, sie zu übergeben, und der Konstruktor sie prüfen kann. Nimm einen Objektinitialisierer (new Product("Mug") { Color = "blue" }) für optionale Einstellungen. Der Initialisierer läuft, nachdem der Konstruktor fertig ist.
Was sind primäre Konstruktoren in C# 12?
Parameter, die direkt in der Klassendeklaration stehen, class Customer(string name, int id) { ... }, und in jedem Member der Klasse verfügbar sind. Anders als die positionellen Parameter eines Records werden sie nicht zu öffentlichen Properties: Stelle sie explizit mit public string Name => name; oder public string Name { get; } = name; bereit.