İçeriğe geç / Skip to content / Zum Inhalt

C# Konstruktor: der heimliche Held der Objekterzeugung

Ahmet Balaman

Zuletzt aktualisiert:

4 Min. Lesezeit

.NETC#OOPConstructorProgramming Fundamentals
C# Konstruktor: der heimliche Held der Objekterzeugung

Im vorherigen Beitrag haben wir die Konzepte Klasse und Objekt kennengelernt. Wir haben Objekte erzeugt und ihren Eigenschaften Werte zugewiesen. Jedes Mal mussten wir aber Folgendes tun:

Car car1 = new Car();
car1.Brand = "Toyota";
car1.Model = "Corolla";
car1.Year = 2024;
car1.Color = "White";

Vier Codezeilen, nur um ein einziges Auto zu erzeugen. Was, wenn wir 100 Autos brauchen? Und was, wenn wir vergessen, einzelne Eigenschaften zu setzen?

Genau das löst der Konstruktor.

Was ist ein Konstruktor?

Ein Konstruktor ist eine besondere Methode, die beim Erzeugen eines Objekts automatisch aufgerufen wird. Sein Name muss dem Klassennamen entsprechen, und er hat keinen Rückgabetyp. Microsofts Dokumentation zu Konstruktoren listet zudem Schritt für Schritt auf, in welcher Reihenfolge Felder, Feldinitialisierer und Konstruktoren beim Erzeugen einer Instanz ausgeführt werden.

public class Car
{
    public string Brand { get; set; }
    public string Model { get; set; }
    public int Year { get; set; }
    public string Color { get; set; }
    
    // Constructor
    public Car(string brand, string model, int year, string color)
    {
        Brand = brand;
        Model = model;
        Year = year;
        Color = color;
        Console.WriteLine($"🚗 New {brand} {model} manufactured!");
    }
}

// Usage - Single line!
Car car1 = new Car("Toyota", "Corolla", 2024, "White");

Sehen Sie? Eine Zeile statt vier. Und das Beste: Es ist nun unmöglich, eine Eigenschaft zu übersehen, weil der Konstruktor sie einfordert.

Der Standardkonstruktor: der stille Held

Schreiben Sie keinen eigenen Konstruktor, erzeugt C# im Hintergrund einen für Sie. Das ist der Standardkonstruktor.

public class Box
{
    public int Width { get; set; }
    public int Height { get; set; }
}

// C# creates this behind the scenes:
// public Box() { }

Box box1 = new Box();  // This works
box1.Width = 10;
box1.Height = 20;

Aber Achtung! Sobald Sie einen eigenen Konstruktor schreiben, verschwindet der Standardkonstruktor:

public class Box
{
    public int Width { get; set; }
    public int Height { get; set; }
    
    public Box(int width, int height)
    {
        Width = width;
        Height = height;
    }
}

Box box1 = new Box();  // ❌ ERROR! No parameterless constructor
Box box2 = new Box(10, 20);  // ✅ Works

Konstruktor-Überladung: Auswahl anbieten

Manchmal möchten Sie den Nutzenden verschiedene Möglichkeiten bieten: „Sie können nur den Namen angeben – oder alles.“ Das nennt man Konstruktor-Überladung.

public class Player
{
    public string Name { get; set; }
    public int Level { get; set; }
    public int Health { get; set; }
    public string Class { get; set; }
    
    // Constructor 1: Name only
    public Player(string name)
    {
        Name = name;
        Level = 1;       // Default values
        Health = 100;
        Class = "Warrior";
    }
    
    // Constructor 2: Name and class
    public Player(string name, string playerClass)
    {
        Name = name;
        Class = playerClass;
        Level = 1;
        Health = 100;
    }
    
    // Constructor 3: Fully equipped
    public Player(string name, string playerClass, int level, int health)
    {
        Name = name;
        Class = playerClass;
        Level = level;
        Health = health;
    }
    
    public void ShowInfo()
    {
        Console.WriteLine($"⚔️ {Name} | Class: {Class} | Level: {Level} | Health: {Health}");
    }
}

Jetzt können Objekte ganz nach Bedarf erzeugt werden:

Player player1 = new Player("John");
Player player2 = new Player("Emma", "Mage");
Player player3 = new Player("Mike", "Archer", 50, 500);

player1.ShowInfo();  // ⚔️ John | Class: Warrior | Level: 1 | Health: 100
player2.ShowInfo();  // ⚔️ Emma | Class: Mage | Level: 1 | Health: 100
player3.ShowInfo();  // ⚔️ Mike | Class: Archer | Level: 50 | Health: 500

Konstruktorverkettung mit this()

Ist Ihnen die Codewiederholung zwischen den Konstruktoren aufgefallen? Wir führen jedes Mal dieselben Zuweisungen aus. Mit this() rufen wir aus einem Konstruktor einen anderen auf:

Drei kurze Konstruktoren verketten sich über this() zu einem Hauptkonstruktor, in dem Zuweisung und Prüfung stattfinden

public class Player
{
    public string Name { get; set; }
    public int Level { get; set; }
    public int Health { get; set; }
    public string Class { get; set; }
    
    // Main constructor - All parameters
    public Player(string name, string playerClass, int level, int health)
    {
        Name = name;
        Class = playerClass;
        Level = level;
        Health = health;
        Console.WriteLine($"✨ {name} joined the game!");
    }
    
    // Name only - Call main constructor
    public Player(string name) : this(name, "Warrior", 1, 100)
    {
        // Extra code can be added here
    }
    
    // Name and class - Call main constructor
    public Player(string name, string playerClass) : this(name, playerClass, 1, 100)
    {
    }
}

Die Syntax : this(...) bedeutet: „Führe zuerst jenen Konstruktor aus und danach den Rumpf dieses Konstruktors.“ Das Gegenstück : base(...) ruft den Konstruktor der Elternklasse auf; dem begegnen wir im Leitfaden zu Vererbung und Polymorphie.

Optionale Parameter: der moderne Weg

Eine schöne Eigenschaft von C#: optionale Parameter. Damit erreichen Sie dieselbe Flexibilität mit einem einzigen Konstruktor statt mit Überladungen.

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int Stock { get; set; }
    public string Category { get; set; }
    public bool IsActive { get; set; }
    
    public Product(
        string name, 
        decimal price, 
        int stock = 0,                    // Default: 0
        string category = "General",     // Default: "General"
        bool isActive = true)            // Default: true
    {
        Name = name;
        Price = price;
        Stock = stock;
        Category = category;
        IsActive = isActive;
    }
}

// Different usage patterns
Product product1 = new Product("Laptop", 1500);
Product product2 = new Product("Mouse", 50, 100);
Product product3 = new Product("Keyboard", 80, 50, "Electronics");
Product product4 = new Product("Monitor", 800, category: "Electronics");  // Named parameter

Dank benannter Parameter können Sie mittlere Parameter überspringen. Sehr praktisch!

Praxisbeispiel: ein Bestellsystem im E-Commerce

Fügen wir alles Gelernte zusammen. Stellen Sie sich vor, in einem echten E-Commerce-System werden Bestellungen erzeugt:

public class Order
{
    public string OrderNumber { get; private set; }
    public string CustomerName { get; set; }
    public List<string> Products { get; set; }
    public decimal TotalAmount { get; set; }
    public DateTime CreatedDate { get; private set; }
    public string Status { get; private set; }
    
    // Private constructor - Cannot be created directly from outside
    private Order()
    {
        OrderNumber = GenerateOrderNumber();
        CreatedDate = DateTime.Now;
        Status = "Pending";
        Products = new List<string>();
    }
    
    // Public constructor - Standard order
    public Order(string customerName) : this()
    {
        CustomerName = customerName;
    }
    
    // Fully equipped order
    public Order(string customerName, List<string> products, decimal totalAmount) : this()
    {
        CustomerName = customerName;
        Products = products;
        TotalAmount = totalAmount;
    }
    
    private string GenerateOrderNumber()
    {
        return $"ORD-{DateTime.Now:yyyyMMdd}-{new Random().Next(1000, 9999)}";
    }
    
    public void AddProduct(string productName, decimal price)
    {
        Products.Add(productName);
        TotalAmount += price;
        Console.WriteLine($"✅ '{productName}' added to cart. Total: ${TotalAmount:F2}");
    }
    
    public void ShowOrderSummary()
    {
        Console.WriteLine("\n" + new string('=', 40));
        Console.WriteLine($"📦 ORDER DETAILS");
        Console.WriteLine(new string('=', 40));
        Console.WriteLine($"Order No: {OrderNumber}");
        Console.WriteLine($"Customer: {CustomerName}");
        Console.WriteLine($"Date: {CreatedDate:MM/dd/yyyy HH:mm}");
        Console.WriteLine($"Status: {Status}");
        Console.WriteLine("\nProducts:");
        foreach (var product in Products)
        {
            Console.WriteLine($"  • {product}");
        }
        Console.WriteLine($"\n💰 TOTAL: ${TotalAmount:F2}");
        Console.WriteLine(new string('=', 40));
    }
}

Verwendung:

// Start empty order
Order order1 = new Order("John Smith");
order1.AddProduct("iPhone 15", 999);
order1.AddProduct("AirPods Pro", 249);
order1.AddProduct("MagSafe Charger", 39);
order1.ShowOrderSummary();

// Order with ready product list
var products = new List<string> { "MacBook Pro", "Magic Mouse", "USB-C Hub" };
Order order2 = new Order("Emma Johnson", products, 2500);
order2.ShowOrderSummary();

In einem echten Projekt entsteht ein Order-Objekt meist aus einer HTTP-Anfrage; wie Sie die Klasse hinter einen Endpunkt stellen, zeige ich im Einstieg in Minimal APIs.

Ausgabe:

✅ 'iPhone 15' added to cart. Total: $999.00
✅ 'AirPods Pro' added to cart. Total: $1248.00
✅ 'MagSafe Charger' added to cart. Total: $1287.00

========================================
📦 ORDER DETAILS
========================================
Order No: ORD-20260203-4721
Customer: John Smith
Date: 02/03/2026 14:30
Status: Pending

Products:
  • iPhone 15
  • AirPods Pro
  • MagSafe Charger

💰 TOTAL: $1287.00
========================================

Statischer Konstruktor: läuft beim Laden der Klasse

Manchmal soll etwas geschehen, wenn eine Klasse zum ersten Mal genutzt wird. Dafür ist der statische Konstruktor perfekt; seine Regeln stehen gesammelt in Microsofts Dokumentation zu statischen Konstruktoren:

public class Database
{
    public static string ConnectionString { get; private set; }
    public static bool IsConnected { get; private set; }
    
    // Static constructor - Runs once when class is first used
    static Database()
    {
        Console.WriteLine("🔌 Establishing database connection...");
        ConnectionString = "Server=localhost;Database=MyApp;";
        IsConnected = true;
        Console.WriteLine("✅ Connection ready!");
    }
    
    public static void ExecuteQuery(string sql)
    {
        if (IsConnected)
        {
            Console.WriteLine($"📊 Executing query: {sql}");
        }
    }
}

// Static constructor runs on first use
Database.ExecuteQuery("SELECT * FROM Users");
Database.ExecuteQuery("SELECT * FROM Products");

// Output:
// 🔌 Establishing database connection...
// ✅ Connection ready!
// 📊 Executing query: SELECT * FROM Users
// 📊 Executing query: SELECT * FROM Products

Der statische Konstruktor:

  • Kann keine Parameter entgegennehmen
  • Hat keinen Zugriffsmodifizierer
  • Läuft genau einmal
  • Wir können den Zeitpunkt nicht steuern (das entscheidet die CLR)

Validierung im Konstruktor: Robustheit zahlt sich aus

Der Konstruktor ist der ideale Ort, um beim Erzeugen eines Objekts die übergebenen Daten zu prüfen:

public class Email
{
    public string Address { get; private set; }
    public string Domain { get; private set; }
    
    public Email(string address)
    {
        if (string.IsNullOrWhiteSpace(address))
            throw new ArgumentException("Email address cannot be empty!");
        
        if (!address.Contains("@"))
            throw new ArgumentException("Invalid email format!");
        
        if (address.Length < 5)
            throw new ArgumentException("Email address is too short!");
        
        Address = address.ToLower().Trim();
        Domain = Address.Split('@')[1];
        
        Console.WriteLine($"✅ Email created: {Address}");
    }
}

try
{
    Email email1 = new Email("[email protected]");  // ✅ Works
    Email email2 = new Email("invalid-email");   // ❌ Throws error
}
catch (ArgumentException ex)
{
    Console.WriteLine($"❌ Error: {ex.Message}");
}

Zusammenfassung: Arten von Konstruktoren

Art Beschreibung Beispiel
Standard Ohne Parameter, wird von C# automatisch erzeugt public Class() {}
Parametrisiert Nimmt Werte entgegen public Class(int x) {}
Überladen Mehrere Konstruktoren Unterschiedliche Parameterkombinationen
Verkettet Ruft einen anderen Konstruktor auf : this(...)
Statisch Läuft beim Laden der Klasse static Class() {}
Privat Von außen nicht zugänglich Für das Factory-Pattern

Nächster Schritt: Zugriffsmodifizierer

Mit Konstruktoren erzeugen wir unsere Objekte sicherer und einheitlicher.

Im nächsten Beitrag behandeln wir Zugriffsmodifizierer. Was bedeuten public, private und protected? Warum ist es eine schlechte Idee, alles public zu machen? Und wofür sind Getter und Setter da?

Schreiben Sie weiter sauberen Code! 🚀


Dies ist der dritte Beitrag der Reihe „OOP mit C#“. Im vorherigen Beitrag ging es um Klasse und Objekt, im nächsten um Zugriffsmodifizierer.

Häufig gestellte Fragen

Was passiert, wenn ich den Konstruktor private mache?

Von außerhalb der Klasse kann niemand mit new ein Objekt erzeugen; nur die eigenen statischen Methoden der Klasse können eines liefern. Das ist eine bewusste Entscheidung für Factory-Methoden und das Singleton-Muster.

Was ist ein Primary Constructor?

Eine Kurzschreibweise aus C# 12: Sie schreiben die Parameter direkt in den Klassenkopf, etwa public class Car(string brand, string model), und verwenden sie im Klassenrumpf. Records kannten diese Syntax schon früher; bei gewöhnlichen Klassen verkürzt sie einfache Datenklassen deutlich.

Darf ich im Konstruktor eine Exception werfen?

Ja, das ist sogar empfehlenswert. Werfen Sie bei einem ungültigen Parameter eine ArgumentException, wird das Objekt gar nicht erst erzeugt, und der restliche Code trifft nie auf ein halb aufgebautes oder inkonsistentes Objekt.

Kommentare