Implementieren von Klassen mit partiellen Klassen

Abgeschlossen

Es ist möglich, die Definition einer Klasse oder Methode über zwei oder mehr Quelldateien aufzuteilen. Jede Quelldatei enthält einen Abschnitt der Definition eines Typs bzw. einer Methode. Die Teile werden bei der Kompilierung der Anwendung miteinander kombiniert.

Partielle Klassen

Es gibt mehrere Situationen, in denen die Aufteilung einer Klassendefinition wünschenswert ist:

  • Durch das Deklarieren einer Klasse über separate Dateien können mehrere programmierende Personen gleichzeitig daran arbeiten.
  • Sie können der Klasse Code hinzufügen, ohne die Quelldatei neu erstellen zu müssen, die eine automatisch generierte Quelle enthält. Visual Studio verwendet diesen Ansatz beim Erstellen von Windows Forms, Webdienstwrappercode usw. Sie können Code erstellen, der diese Klassen verwendet, ohne die von Visual Studio erstellte Datei ändern zu müssen.
  • Quellgeneratoren können zusätzliche Funktionen in einer Klasse generieren.

Um eine Klassendefinition aufzuteilen, verwenden Sie den partiellen Schlüsselwortmodifizierer. In der Praxis wird jede partielle Klasse in der Regel in einer separaten Datei definiert, wodurch es einfacher ist, die Klasse im Laufe der Zeit zu verwalten und zu erweitern.

Das folgende Employee-Beispiel veranschaulicht, wie die Klasse in zwei Dateien aufgeteilt werden kann: Employee_Part1.cs und Employee_Part2.cs.


// This is in Employee_Part1.cs
public partial class Employee
{
    public void DoWork()
    {
        Console.WriteLine("Employee is working.");
    }
}


// This is in Employee_Part2.cs
public partial class Employee
{
    public void GoToLunch()
    {
        Console.WriteLine("Employee is at lunch.");
    }
}


//Main program demonstrating the Employee class usage
public class Program
{
    public static void Main()
    {
        Employee emp = new Employee();
        emp.DoWork();
        emp.GoToLunch();
    }
}

// Expected Output:
// Employee is working.
// Employee is at lunch.

Das partial-Schlüsselwort gibt an, dass andere Teile der Klasse im Namespace definiert werden können. Alle Teile müssen das partial-Schlüsselwort verwenden. Alle Teile müssen zur Kompilierungszeit verfügbar sein, um den endgültigen Typ zu bilden. Alle Teile müssen über die gleiche Barrierefreiheit verfügen, z. B. public, private, usw.

Wenn ein Teil abstrahiert wird, wird der gesamte Typ als abstrakt betrachtet. Wenn ein Teil versiegelt ist, wird der gesamte Typ als versiegelt betrachtet. Wenn ein Teil einen Basistyp deklariert, erbt der gesamte Typ diese Klasse.

Alle Teile, die eine Basisklasse angeben, müssen übereinstimmen, aber Teile, die eine Basisklasse weglassen, erben weiterhin den Basistyp. Teile können unterschiedliche Basisschnittstellen angeben, und der letzte Typ implementiert alle Schnittstellen, die von allen partiellen Deklarationen aufgelistet werden. Alle Klassen-, Struktur- oder Schnittstellenmitglieder, die in einer partiellen Definition deklariert sind, stehen allen anderen Teilen zur Verfügung. Der letzte Typ ist die Kombination aller Teile zur Kompilierungszeit.

Hinweis

Der Modifizierer partial ist für Delegat- oder Enumerationsdeklarationen nicht verfügbar.

Das folgende Beispiel zeigt, dass verschachtelte Typen partiell sein können, auch wenn der Typ, in dem sie verschachtelt sind, selbst nicht partiell ist.


class Container
{
    partial class Nested
    {
        void Test() { }
    }

    partial class Nested
    {
        void Test2() { }
    }
}

Zur Kompilierungszeit werden Attribute partieller Typdefinitionen zusammengeführt. Betrachten Sie beispielsweise die folgenden Deklarationen:


[SerializableAttribute]
partial class Moon { }

[ObsoleteAttribute]
partial class Moon { }

Sie entsprechen den folgenden Deklarationen:


[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }

Im Folgenden werden alle Teiltypdefinitionen zusammengeführt:

  • XML-Kommentare. Wenn jedoch beide Deklarationen eines partiellen Mitglieds Kommentare enthalten, werden nur die Kommentare aus dem implementierenden Mitglied einbezogen.
  • Schnittstellen
  • Attribute für generische Typparameter
  • Klassenattribute
  • Mitglieder

Betrachten Sie beispielsweise die folgenden Deklarationen:


partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }

Sie entsprechen den folgenden Deklarationen:


class Earth : Planet, IRotate, IRevolve { }

Einschränkungen für partielle Klassendefinitionen

Es gibt mehrere Regeln, die sie befolgen müssen, wenn Sie mit partiellen Klassendefinitionen arbeiten:

  • Alle Teiltypdefinitionen, die Teile desselben Typs sein sollen, müssen als partiell geändert werden. Beispielsweise generieren die folgenden Klassendeklarationen einen Fehler:

    
    public partial class A { }
    //public class A { }  // Error, must also be marked partial
    
    
  • Der partielle Modifizierer kann nur unmittelbar vor der Schlüsselwortklasse, Struktur oder Schnittstelle angezeigt werden.

  • Geschachtelte partielle Typen sind in partiellen Typdefinitionen zulässig, wie im folgenden Beispiel veranschaulicht:

    
    partial class ClassWithNestedClass
    {
        partial class NestedClass { }
    }
    
    partial class ClassWithNestedClass
    {
        partial class NestedClass { }
    }
    
    
  • All partiellen Typdefinitionen, die Teile desselben Typs sein sollen, müssen in derselben Assembly und demselben Modul (EXE- oder DLL-Datei) definiert werden. Partielle Definitionen können nicht mehrere Module umfassen.

  • Der Klassenname und die generischen Parameter müssen für alle partiellen Typdefinitionen übereinstimmen. Generische Typen können partiell sein. Jede partielle Deklaration muss dieselben Parameternamen in derselben Reihenfolge verwenden.

  • Die folgenden Schlüsselwörter für eine partielle Typdefinition sind optional, aber wenn sie in einer partiellen Typdefinition vorhanden sind, muss dasselbe in einer anderen partiellen Typdefinition für denselben Typ angegeben werden:

    • public
    • Privat
    • Geschützt
    • Intern
    • Kurzfassung
    • Versiegelt
    • Basisklasse
    • Neuer Modifizierer (geschachtelte Teile)
    • Allgemeine Einschränkungen

Implementieren partieller Klassen

Im folgenden Beispiel werden die Felder und der Konstruktor der Coords-Klasse in einer partiellen Klassendefinition (Coords_Part1.cs) deklariert, und die PrintCoords-Methode wird in einer anderen partiellen Klassendefinition (Coords_Part2.cs) deklariert. Diese Aufteilung veranschaulicht, wie partielle Klassen auf mehrere Dateien aufgeteilt werden können, um die Wartung zu erleichtern.


// This is in Coords_Part1.cs
 public partial class Coords
 {
     private int x;
     private int y;

     public Coords(int x, int y)
     {
         this.x = x;
         this.y = y;
     }
 }

 // This is in Coords_Part2.cs
 public partial class Coords
 {
     public void PrintCoords()
     {
         Console.WriteLine("Coords: {0},{1}", x, y);
     }
 }

// Main program demonstrating the Coords class usage
 class TestCoords
 {
     static void Main()
     {
         Coords myCoords = new Coords(10, 15);
         myCoords.PrintCoords();

         // Keep the console window open in debug mode.
         Console.WriteLine("Press any key to exit.");
         Console.ReadKey();
     }
 }
 // Output: Coords: 10,15

Partielle Mitglieder

Eine partielle Klasse kann ein partielles Element enthalten. Ein Teil der Klasse enthält die Signatur des Elements. Eine Implementierung kann im selben Teil oder einem anderen Teil definiert werden.

Eine Implementierung ist für eine partielle Methode nicht erforderlich, wenn die Signatur den folgenden Regeln entspricht:

  • Die Deklaration enthält keine Zugriffsmodifizierer. Die Methode hat standardmäßig privaten Zugriff.

  • Der Rückgabetyp ist void.

  • Keiner der Parameter hat den Modifizierer out.

  • Die Methodendeklaration kann keine der folgenden Modifizierer enthalten:

    • Virtuell
    • Überschreiben
    • Versiegelt
    • neu
    • extern

Die Methode und alle Aufrufe der Methode werden zur Kompilierungszeit entfernt, wenn keine Implementierung vorhanden ist.

Jede Methode, die nicht allen diesen Einschränkungen entspricht, einschließlich Eigenschaften und Indexern, muss eine Implementierung bereitstellen. Diese Implementierung kann von einem Quellgenerator bereitgestellt werden. Partielle Eigenschaften können nicht mithilfe automatisch implementierter Eigenschaften implementiert werden. Der Compiler kann nicht zwischen einer automatisch implementierten Eigenschaft und der deklarierenden Deklaration einer partiellen Eigenschaft unterscheiden.

Ab C# 13 kann die Implementierungsdeklaration für eine partielle Eigenschaft feldbezogene Eigenschaften verwenden, um die Implementierungsdeklaration zu definieren. Eine feldgestützte Eigenschaft bietet eine prägnante Syntax, bei der das Feldschlüsselwort auf das vom Compiler synthetisierte Hintergrundfeld für die Eigenschaft zugreift. Sie können beispielsweise den folgenden Code schreiben:


// in file1.cs
public partial class PropertyBag
{
    // Defining declaration
    public partial int MyProperty { get; set; }
}

// In file2.cs
public partial class PropertyBag
{
    // Defining declaration
    public partial int MyProperty { get => field; set; }
}

Sie können field entweder im get- oder im set-Zugriff verwenden oder in beiden.

Wichtig

Das Schlüsselwort field ist eine Previewfunktion in C# 13. Sie müssen .NET 9 verwenden und festlegen, dass das Element <LangVersion> in der Projektdatei in der Vorschau angezeigt wird, um das Kontextschlüsselwort field zu verwenden.

Achten Sie darauf, die Schlüsselwortfunktion field in einer Klasse zu verwenden, die ein Feld mit dem Namen field hat. Das neue Schlüsselwort field überschreibt ein Feld namens field im Geltungsbereich eines Eigenschaftszugriffs. Sie können entweder den Namen der Feldvariable ändern oder das Token @ verwenden, um auf den Feldbezeichner zu verweisen als @field.

Partielle Methoden ermöglichen es der Implementierung eines Teils einer Klasse, ein Element zu deklarieren. Der Implementierer eines anderen Teils der Klasse kann dieses Mitglied definieren. Es gibt zwei Szenarien, in denen diese Aufteilung nützlich ist: Vorlagen, die Codebausteine generieren, und Quellgeneratoren.

  • Vorlagencode: Die Vorlage reserviert einen Methodennamen und eine Signatur, sodass generierter Code die Methode aufrufen kann. Diese Methoden folgen den Einschränkungen, die es einem Entwickler ermöglichen, zu entscheiden, ob die Methode implementiert werden soll. Wenn die Methode nicht implementiert ist, entfernt der Compiler die Methodensignatur und alle Aufrufe der Methode. Die Aufrufe der Methode, einschließlich aller Ergebnisse, die sich aus der Auswertung der Argumente in den Aufrufen ergeben würden, haben zur Laufzeit keine Auswirkungen. Daher kann jeder Code in der partiellen Klasse eine partielle Methode frei verwenden, auch wenn die Implementierung nicht bereitgestellt wird. Wenn die Methode aufgerufen, jedoch nicht implementiert wird, ergeben sich keine Kompilierungszeit- oder Laufzeitfehler.

  • Quellgeneratoren: Quellgeneratoren stellen eine Implementierung für Mitglieder bereit. Die entwickelnde Person kann die Mitgliedsdeklaration hinzufügen (häufig mit Attributen, die vom Quellgenerator gelesen werden). Die entwickelnde Person kann Code schreiben, der diese Mitglieder aufruft. Der Quellgenerator wird während der Kompilierung ausgeführt und stellt die Implementierung bereit. In diesem Szenario werden die Einschränkungen für partielle Mitglieder, die möglicherweise nicht implementiert werden, nicht häufig befolgt.

    
    // Definition in file1.cs
    partial void OnNameChanged();
    
    // Implementation in file2.cs
    partial void OnNameChanged()
    {
      // method body
    }
    
    
  • Deklarationen für partielle Mitglieder müssen mit dem kontextbezogenen Schlüsselwort „partiell“ beginnen.

  • Die Signaturen von partiellen Mitgliedern müssen in beiden Teilen des Teiltyps übereinstimmen.

  • Partielle Mitglieder können statische und unsichere Modifizierer aufweisen.

  • Das partielle Element kann generisch sein. Einschränkungen müssen bei der definierenden und implementierenden Methodendeklaration identisch sein. Parameter- und Typparameternamen müssen in der Implementierungsdeklaration nicht identisch sein wie in der definierenden Deklaration.

  • Sie können einen Delegat zu einer partiellen Methode definieren und implementieren, aber nicht an eine partielle Methode, die keine Implementierung hat.