Start Debugging
2025-04-08 Aktualisiert 2025-04-12 csharp-14csharpdotnetdotnet-10 Edit on GitHub

Partielle Konstruktoren und Ereignisse in C# 14

C# 14 ermöglicht, Instanzkonstruktoren und Ereignisse als partielle Member zu deklarieren und Definitionen über mehrere Dateien aufzuteilen, für sauberere Codegenerierung und bessere Trennung der Belange.

C# 14 führt eine neue Möglichkeit ein, Instanzkonstruktoren und Ereignisse als partial-Member zu deklarieren. Das heißt, Sie können die Definition eines Konstruktors oder Ereignisses auf zwei Teile einer partiellen Klasse aufteilen, ähnlich wie C# das schon lange für partielle Methoden und partielle Eigenschaften erlaubt. Ein Teil einer partiellen Klasse kann einen Konstruktor oder ein Ereignis deklarieren, ein anderer Teil kann es implementieren. Das ist besonders nützlich in Szenarien wie der Codegenerierung, in denen eine Datei automatisch generiert und eine andere manuell von Entwicklern gepflegt wird.

Partielle Konstruktoren in C# 14

Partielle Konstruktoren erlauben Ihnen, einen Klassenkonstruktor in zwei Deklarationen innerhalb einer partial-Klasse aufzuteilen. Eine Deklaration liefert nur die Signatur (eine definierende Deklaration), eine andere den eigentlichen Rumpf (eine implementierende Deklaration). Der Compiler führt sie zusammen, sodass sie sich zur Laufzeit wie ein einziger Konstruktor verhalten.

Wichtige Regeln für partielle Konstruktoren:

Hier ein Beispiel für einen partiellen Konstruktor in Aktion. Stellen Sie sich eine Klasse vor, die auf zwei Dateien aufgeteilt ist: Ein automatisch generierter Teil deklariert einen Konstruktor, ein anderer Teil implementiert ihn.

// File: Car.AutoGenerated.cs (auto-generated partial class)
public partial class Car
{
    // Defining partial constructor (signature only, no body)
    public partial Car(string model);
}

// File: Car.cs (developer-maintained partial class)
public partial class Car
{
    // Implementing partial constructor (actual body)
    public partial Car(string model)
    {
        // (Optional) Can call another constructor or base constructor here
        // : base() or : this(...) would be placed after the parameter list if needed.
        
        if (string.IsNullOrEmpty(model))
            throw new ArgumentException("Model must be provided", nameof(model));
        Console.WriteLine($"Car model set to {model}");
    }
}

In diesem Beispiel ist der Konstruktor der Klasse Car aufgeteilt: Der erste Teil deklariert, dass Car(string model) existiert, der zweite Teil liefert die Implementierung, die das Argument prüft und eine Nachricht ausgibt. Die definierende Deklaration darf keinen Code enthalten — sie ist nur die Signatur, die mit ; endet. Die implementierende Deklaration enthält den Rumpf und könnte zusätzlich einen Aufruf der Basisklasse enthalten (wenn Car von einer anderen Klasse erbte) oder einen anderen Konstruktor über this() aufrufen. Zur Kompilierzeit stellt der Compiler sicher, dass es genau einen Teil von jeder Sorte gibt, und behandelt den Konstruktor als einen einzigen einheitlichen Konstruktor.

Partielle Ereignisse in C# 14

Partielle Ereignisse erlauben eine ähnliche Aufteilung der Definition für Ereignisse in einer partiellen Klasse. Ein Teil der Klasse definiert das Ereignis (ohne add/remove-Logik, wie eine normale automatische Ereignisdeklaration), und ein anderer Teil liefert die add/remove-Accessoren (den Code, der ausgeführt wird, wenn Abonnenten Event-Handler anhängen oder entfernen).

Wichtige Regeln für partielle Ereignisse:

Hier ein Beispiel, das ein partielles Ereignis veranschaulicht. Eine Datei deklariert das Ereignis, eine andere implementiert die individuelle add/remove-Logik:

// File: Sensor.AutoGenerated.cs (auto-generated partial class)
public partial class Sensor
{
    // Defining partial event (no body, just declaration)
    public partial event EventHandler DataReceived;
}

// File: Sensor.cs (developer-maintained partial class)
public partial class Sensor
{
    // Implementing partial event (with custom add/remove logic)
    private EventHandler _dataReceivedHandlers;  // backing storage for event handlers

    public partial event EventHandler DataReceived
    {
        add 
        {
            Console.WriteLine("Subscriber added to DataReceived");
            _dataReceivedHandlers += value;
        }
        remove 
        {
            Console.WriteLine("Subscriber removed from DataReceived");
            _dataReceivedHandlers -= value;
        }
    }

    // Optional: a method to raise the event safely from this class
    protected void OnDataReceived(EventArgs e)
    {
        _dataReceivedHandlers?.Invoke(this, e);
    }
}

In diesem Beispiel besitzt die Klasse Sensor ein DataReceived-Ereignis, das auf zwei Dateien aufgeteilt ist. Der automatisch generierte Teil der Klasse deklariert, dass es ein DataReceived-Ereignis vom Typ EventHandler gibt. Der vom Entwickler verwaltete Teil liefert die eigentliche Implementierung: Er definiert ein privates Feld zur Speicherung der Abonnenten und implementiert die add- und remove-Blöcke, um eine Nachricht zu protokollieren und das private Feld zu aktualisieren. Wenn anderer Code sensor.DataReceived += Handler; ausführt, läuft die individuelle add-Logik. Genauso löst das Aufheben des Abonnements die individuelle remove-Logik aus. Die Klasse kann _dataReceivedHandlers verwenden, um das Ereignis auszulösen (zum Beispiel in der Methode OnDataReceived). Ohne den implementierenden Teil ließe sich die definierende Deklaration allein nicht kompilieren, da das Ereignis für sich genommen keine Stütze hat — C# verlangt, dass der implementierende Teil die Funktionalität des Ereignisses vervollständigt.

Vergleich mit früheren C#-Versionen

Vor C# 14 konnten Sie Konstruktoren oder Ereignisse nicht als partial deklarieren. In C# 13 und früher war das Schlüsselwort partial nur für Klassen, Methoden und (ab C# 13) Eigenschaften/Indexer gültig. Versuchten Sie, einen Konstruktor oder ein Ereignis als partial zu markieren, gab der Compiler einen Fehler aus. Diese Einschränkung bedeutete, dass es keine Möglichkeit gab, die Implementierung eines Konstruktors oder Ereignisses auf verschiedene Dateien aufzuteilen.

Entwickler griffen oft zu Workarounds, um eine ähnliche Flexibilität zu erreichen. Stellen Sie sich beispielsweise ein Szenario vor, in dem automatisch generierter Code beim Aufbau eines Objekts benutzerdefinierte Logik ausführen soll. Ohne partielle Konstruktoren bestand ein gängiges Muster darin, eine partielle Methode im Konstruktor zu verwenden. Der automatisch generierte Teil einer Klasse konnte diese Methode aufrufen, während die Methodenimplementierung in einer anderen Datei lag. In älterem C#-Code könnten Sie zum Beispiel Folgendes sehen:

// Before C# 14: using a partial method to extend constructor logic
public partial class Car
{
    public Car()
    {
        InitializeComponents();       // auto-generated initialization
        OnConstructed();              // call a partial method hook
    }

    // This partial method can be implemented in another file to add custom behavior
    partial void OnConstructed();
}

In der zweiten Datei konnte der Entwickler die partielle Methode OnConstructed implementieren, um nach InitializeComponents() zusätzlichen Code auszuführen. Dieses Muster erlaubte das Einschleusen benutzerdefinierten Codes zur Konstruktionszeit, war aber indirekt und etwas umständlich. Für Ereignisse gab es ebenfalls keinen einfachen Workaround — man musste entweder erben und die Funktionalität überschreiben oder den generierten Code verändern, da man die add/remove-Logik eines Ereignisses nicht über mehrere Dateien aufteilen konnte.

Mit den partiellen Konstruktoren und Ereignissen in C# 14 sind diese Workarounds nicht mehr notwendig. Sie können einen Konstruktor oder ein Ereignis direkt als partial deklarieren und die Implementierung separat liefern, was den Code geradliniger macht. Der neue Ansatz ist klarer und macht Dummy-Partialmethoden oder andere Tricks überflüssig.

Anwendungsfälle und Vorteile

Die Hauptnutznießer von partiellen Konstruktoren und Ereignissen sind Szenarien rund um Codegenerierung und Tooling. Viele Frameworks und Werkzeuge generieren C#-Code (zum Beispiel UI-Designer, ORMs oder Werkzeuge zum Scaffolding von Schnittstellen) und markieren Klassen oft als partial, damit Entwickler die generierten Klassen erweitern können, ohne den automatisch generierten Code zu bearbeiten. Mit C# 14:

In all diesen Fällen erleichtern partielle Konstruktoren und Ereignisse die Wartung des Codes. Der automatisch generierte Code kann erklären, was existieren muss, und der handgeschriebene Code kann auf das beschränkt sein, was der Entwickler tatsächlich implementieren möchte. Außerdem entstehen durch partielle Member keine Laufzeitkosten — die Aufteilung existiert nur zur Kompilierzeit. Das resultierende Programm verhält sich, als hätten Sie den vollständigen Konstruktor oder das vollständige Ereignis an einer einzigen Stelle geschrieben.

Indem Instanzkonstruktoren und Ereignisse partial sein dürfen, rundet C# 14 das Feature-Set partieller Klassen so ab, dass nahezu alle Member-Typen abgedeckt sind. Diese Erweiterung verbessert die Fähigkeit der Sprache, eine saubere Trennung zwischen automatisch generiertem und benutzerdefiniertem Code zu unterstützen. Entwickler erhalten Flexibilität, um Klassenverhalten ohne Tricks zu organisieren und zu erweitern, und Source Generators bekommen ein mächtiges neues Werkzeug, um Funktionalität modular einzubringen. Ob Sie an großen Frameworks arbeiten oder Code einfach aus Gründen der Übersichtlichkeit aufteilen — partielle Konstruktoren und Ereignisse bieten in C# 14 einen ausdrucksstärkeren und bequemeren Ansatz.

Quellen

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Zurück