部分クラスを使用してクラスを実装する

完了

クラスまたはメソッドの定義を 2 つ以上のソース ファイルに分割できます。 各ソース ファイルには型またはメソッド定義のセクションが含まれており、アプリケーションのコンパイル時にすべての部分が結合されます。

部分クラス

クラス定義を分割することが望ましい状況がいくつかあります。

  • 個別のファイルに対してクラスを宣言すると、複数のプログラマが同時にクラスに対して作業できるようになります。
  • 自動的に生成されたソースを含むソース ファイルを再作成しなくても、クラスにコードを追加できます。 Visual Studio では、Windows フォームや Web サービス ラッパー コードなどを作成するときに、この方法が使用されます。 Visual Studio によって作成されたファイルを変更しなくても、これらのクラスを使用するコードを作成できます。
  • ソース ジェネレーターは、クラスで追加の機能を生成できます。

クラス定義を分割するには、partial キーワード修飾子を使用します。 実際には、各部分クラスは通常、個別のファイルで定義されるため、時間の経過に伴うクラスの管理と拡張が容易になります。

次の Employee 例は、クラスを 2 つのファイル (Employee_Part1.csEmployee_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.

partial キーワードは、クラスの他の部分を名前空間で定義できることを示します。 すべての部分で、partial キーワードを使用する必要があります。 最終的な型を形成するには、コンパイル時にすべてのパーツを使用できる必要があります。 publicprivateなど、すべてのパーツに同じアクセシビリティが必要です。

いずれかの部分が抽象として宣言されている場合、型全体が抽象と見なされます。 いずれかの部分がシールとして宣言されている場合、型全体がシールと見なされます。 いずれかの部分で基本型が宣言されている場合、型全体がそのクラスを継承します。

基底クラスを指定するすべての部分は一致する必要がありますが、基底クラスを省略する部分は基本型を継承します。 パーツは異なる基本インターフェイスを指定でき、最終的な型は、すべての部分宣言で一覧表示されるすべてのインターフェイスを実装します。 部分定義で宣言されたクラス、構造体、またはインターフェイスメンバーは、他のすべての部分で使用できます。 最後の型は、コンパイル時にすべてのパーツの組み合わせです。

手記

partial 修飾子は、デリゲート宣言または列挙宣言では使用できません。

次の例は、入れ子になった型が部分的でない場合でも、入れ子になった型が部分的である可能性があることを示しています。


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

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

コンパイル時に、部分型定義の属性がマージされます。 たとえば、次の宣言を考えてみます。


[SerializableAttribute]
partial class Moon { }

[ObsoleteAttribute]
partial class Moon { }

これらは、次の宣言と同じです。


[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }

以下は、すべての部分型定義からマージされます。

  • XML コメント。 ただし、部分メンバーの両方の宣言にコメントが含まれている場合は、実装メンバーからのコメントのみが含まれます。
  • インターフェイス
  • generic-type パラメーター属性
  • クラス属性
  • メンバーズ

たとえば、次の宣言を考えてみます。


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

これらは、次の宣言と同じです。


class Earth : Planet, IRotate, IRevolve { }

部分クラス定義に関する制限事項

部分クラス定義を使用する場合は、いくつかの規則に従う必要があります。

  • 同じ型の部分を意味するすべての部分型定義は、partial で変更する必要があります。 たとえば、次のクラス宣言ではエラーが生成されます。

    
    public partial class A { }
    //public class A { }  // Error, must also be marked partial
    
    
  • 部分修飾子は、キーワード クラス、構造体、またはインターフェイスの直前にのみ表示できます。

  • 次の例に示すように、部分型定義では入れ子になった部分型を使用できます。

    
    partial class ClassWithNestedClass
    {
        partial class NestedClass { }
    }
    
    partial class ClassWithNestedClass
    {
        partial class NestedClass { }
    }
    
    
  • 同じ型の部分である部分型定義はすべて、同じアセンブリと同じモジュール (.exe または .dll ファイル) で定義する必要があります。 部分定義は複数のモジュールにまたがることができます。

  • クラス名とジェネリック型パラメーターは、すべての部分型定義で一致する必要があります。 ジェネリック型は部分型にすることができます。 各部分宣言では、同じ順序で同じパラメーター名を使用する必要があります。

  • 部分型定義の次のキーワードは省略可能ですが、1 つの部分型定義に存在する場合は、同じ型の他の部分定義で同じキーワードを指定する必要があります。

    • 公共
    • 非公開
    • 保護
    • 国内
    • 要約
    • シールド
    • 基底クラス
    • new 修飾子 (入れ子になった部分)
    • ジェネリック制約

部分クラスを実装する

次の例では、Coords クラスのフィールドとコンストラクターは、1 つの部分クラス定義 (Coords_Part1.cs) で宣言され、PrintCoords メソッドは別の部分クラス定義 (Coords_Part2.cs) で宣言されています。 この分離は、保守容易にするために部分クラスを複数のファイルに分割する方法を示しています。


// 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

部分メンバー

部分クラスには、部分メンバーを含めることができます。 クラスの 1 つの部分には、メンバーのシグネチャが含まれています。 実装は、同じ部分または別の部分で定義できます。

シグネチャが次の規則に従っている場合、部分メソッドの実装は必要ありません。

  • この宣言には、アクセス修飾子は含まれません。 このメソッドは、既定でプライベート アクセスを持っています。

  • 戻り値の型は voidです。

  • out 修飾子を持つパラメーターはありません。

  • メソッド宣言には、次の修飾子を含めることはできません。

    • バーチャル
    • オーバーライド
    • シールド
    • 新機能
    • extern

メソッドとメソッドのすべての呼び出しは、実装がない場合、コンパイル時に削除されます。

プロパティやインデクサーなど、これらすべての制限に準拠していないメソッドは、実装を提供する必要があります。 その実装は、ソース ジェネレーターによって提供される場合があります。 部分プロパティは、自動的に実装されるプロパティを使用して実装することはできません。 コンパイラは、自動的に実装されるプロパティと部分プロパティの宣言を区別できません。

C# 13 以降では、部分プロパティの実装宣言では、フィールドに基づくプロパティを使用して実装宣言を定義できます。 フィールド に基づくプロパティは、フィールド キーワードがプロパティのコンパイラ合成バッキング フィールドにアクセスする簡潔な構文を提供します。 たとえば、次のコードを記述できます。


// 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; }
}

field は、get アクセサーまたは set アクセサー、またはその両方で使用できます。

大事な

field キーワードは、C# 13 のプレビュー機能です。 <LangVersion> コンテキスト キーワードを使用するには、.NET 9 を使用し、プロジェクト ファイルでプレビューする field 要素を設定する必要があります。

fieldという名前のフィールドを持つクラスでは、field キーワード機能の使用に注意する必要があります。 新しい field キーワードは、プロパティ アクセサーのスコープ内の field という名前のフィールドをシャドウします。 フィールド変数の名前を変更するか、@ トークンを使用してフィールド識別子を @fieldとして参照できます。

部分メソッドを使用すると、クラスの 1 つの部分の実装者がメンバーを宣言できます。 クラスの別の部分の実装者は、そのメンバーを定義できます。 この分離が役立つシナリオには、定型コードを生成するテンプレートとソース ジェネレーターの 2 つがあります。

  • テンプレート コード: 生成されたコードがメソッドを呼び出すことができるように、テンプレートはメソッド名とシグネチャを予約します。 これらのメソッドは、開発者がメソッドを実装するかどうかを決定できるようにする制限に従います。 メソッドが実装されていない場合、コンパイラはメソッドシグネチャとメソッドのすべての呼び出しを削除します。 メソッドの呼び出し (呼び出しの引数の評価から発生する結果を含む) は、実行時には影響しません。 したがって、部分クラスのコードは、実装が指定されていない場合でも、部分メソッドを自由に使用できます。 メソッドが呼び出されても実装されていない場合、コンパイル時または実行時エラーは発生しません。

  • ソース ジェネレーター: ソース ジェネレーターは、メンバーの実装を提供します。 人間の開発者は、メンバー宣言を追加できます (多くの場合、ソース ジェネレーターによって読み取られた属性を使用します)。 開発者は、これらのメンバーを呼び出すコードを記述できます。 ソース ジェネレーターはコンパイル中に実行され、実装を提供します。 このシナリオでは、多くの場合、実装されない可能性がある部分メンバーの制限に従っていません。

    
    // Definition in file1.cs
    partial void OnNameChanged();
    
    // Implementation in file2.cs
    partial void OnNameChanged()
    {
      // method body
    }
    
    
  • 部分メンバー宣言は、コンテキスト キーワード partial で始まる必要があります。

  • 部分型の両方の部分の部分メンバー署名が一致する必要があります。

  • 部分メンバーには、静的修飾子と安全でない修飾子を含めることができます。

  • 部分メンバーはジェネリックにすることができます。 制約は、メソッド宣言の定義と実装で同じである必要があります。 パラメーター名と型パラメーター名は、定義する宣言と実装する宣言で同じである必要はありません。

  • デリゲートは、定義および実装された部分メソッドに対して作成できますが、実装されていない部分メソッドには作成できません。