Start Debugging
2025-04-08 更新日 2025-04-12 csharp-14csharpdotnetdotnet-10 Edit on GitHub

C# 14 の partial コンストラクターとイベント

C# 14 ではインスタンスコンストラクターとイベントを partial メンバーとして宣言でき、定義をファイル間で分割することで、よりすっきりしたコード生成と関心の分離を実現できます。

C# 14 では、インスタンスコンストラクターイベントpartial メンバーとして宣言できる新機能が導入されました。これにより、partial クラスの 2 つの部分にまたがってコンストラクターやイベントの定義を分割できます。これは、C# が長年サポートしてきた partial メソッドや partial プロパティと同様の仕組みです。partial クラスの一方の部分でコンストラクターやイベントを 宣言 し、もう一方の部分で 実装 することができます。これは、片方のファイルが自動生成され、もう片方が開発者によって手動で保守されるようなコード生成のシナリオで特に有用です。

C# 14 の partial コンストラクター

partial コンストラクター を使うと、partial クラス内でクラスのコンストラクターを 2 つの宣言に分割できます。一方の宣言はシグネチャだけを提供し (定義宣言)、もう一方は実際の本体を提供します (実装宣言)。コンパイラーがこれらを統合し、実行時には単一のコンストラクターとして振る舞うようにします。

partial コンストラクターの主なルール:

以下は partial コンストラクターの実例です。クラスが 2 つのファイルに分割され、自動生成された部分がコンストラクターを宣言し、もう一方の部分がそれを実装している様子を想像してください。

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

この例では、Car クラスのコンストラクターが分割されています。最初の部分は Car(string model) が存在することを宣言し、2 番目の部分が引数を検証してメッセージを出力する実装を提供します。定義宣言にはコードを含められず、; で終わるシグネチャだけです。実装宣言には本体があり、(Car が他のクラスを継承している場合は) 基底クラスへの呼び出しや、this() で別のコンストラクターを呼ぶこともできます。コンパイル時にコンパイラーは各部分がちょうど 1 つずつ存在することを確認し、コンストラクターを単一の統一されたものとして扱います。

C# 14 の partial イベント

partial イベント を使うと、partial クラス内でイベントの定義を同様に分割できます。クラスの一方の部分でイベントを定義し (通常の自動イベント宣言と同じく add/remove のロジックは持たない)、もう一方の部分が add/remove アクセサー (購読者がイベントハンドラーを追加または削除したときに実行されるコード) を提供します。

partial イベントの主なルール:

partial イベントを示す例を以下に挙げます。一方のファイルがイベントを宣言し、もう一方がカスタムの add/remove ロジックを実装します。

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

この例では、Sensor クラスの DataReceived イベントが 2 つのファイルに分かれています。クラスの自動生成された部分は、EventHandler 型の DataReceived イベントが存在することを宣言します。開発者の側の部分は、購読者を保持するプライベートフィールドを定義し、メッセージを記録してそのフィールドを更新する addremove ブロックを実装することで、実際の実装を提供します。他のコードが sensor.DataReceived += Handler; を実行すると、カスタムの add ロジックが走ります。同様に、購読の解除ではカスタムの remove ロジックが走ります。クラスは OnDataReceived メソッドなどから _dataReceivedHandlers を使ってイベントを発火できます。実装部分がなければ、定義宣言だけではコンパイルできません。イベント自体には裏付けがないため、C# はイベントの機能を完成させる実装部分を必要とします。

以前の C# バージョンとの比較

C# 14 より前は、コンストラクターやイベントを partial として宣言することはできませんでした。C# 13 およびそれ以前では、partial キーワードはクラス、メソッド、(C# 13 以降は) プロパティ/インデクサーにしか使えませんでした。コンストラクターやイベントを partial にしようとすると、コンパイラーはエラーを出していました。この制限のため、コンストラクターやイベントの実装をファイル間で分割する手段がありませんでした。

開発者は同様の柔軟性を得るために、しばしば回避策を用いていました。たとえば、自動生成コードがオブジェクト構築中に何らかのカスタムロジックを呼び出す必要があるシナリオを考えてみてください。partial コンストラクターがなかった時代の一般的な手法は、コンストラクター内で partial メソッド を使うことです。クラスの自動生成された部分がそのメソッドを呼び出し、メソッドの実装は別のファイルに置く、というやり方です。古い C# コードでは、たとえば次のような形が見られました。

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

2 番目のファイルで、開発者は InitializeComponents() のあとに追加コードを実行するために partial メソッド OnConstructed を実装できました。このパターンは構築時にカスタムコードを差し込めるものの、間接的でややぎこちないものでした。同様にイベントの場合は、簡単な回避策がなく、イベントの add/remove ロジックをファイル間で分割できないため、機能を継承して上書きするか生成コードを書き換えるかしか選択肢がありませんでした。

C# 14 の partial コンストラクターとイベントによって、こうした回避策はもう必要ありません。コンストラクターやイベントを直接 partial として宣言し、実装を別途提供できるので、コードはより素直になります。新しい方法はわかりやすく、ダミーの partial メソッドやその他のトリックを必要としません。

ユースケースとメリット

partial コンストラクターとイベントの主な恩恵を受けるのは、コード生成とツール連携 に関わるシナリオです。多くのフレームワークやツールは C# コードを生成し (たとえば UI デザイナー、ORM、インターフェイスのスキャフォールディングツールなど)、開発者が自動生成コードを編集せずに生成クラスを拡張できるよう、クラスを partial として印を付けることがよくあります。C# 14 では:

これらすべてのケースで、partial コンストラクターとイベントはコードのメンテナンスを容易にします。自動生成コードは「何が存在すべきか」を宣言でき、手書きのコードは「開発者が本当に実装したいこと」だけに絞れます。partial メンバーの使用に実行時のパフォーマンスコストはなく、分割はコンパイル時にのみ存在します。生成されるプログラムは、コンストラクターやイベント全体を 1 か所に書いた場合とまったく同じように振る舞います。

インスタンスコンストラクターとイベントを partial にできるようにしたことで、C# 14 は partial クラス機能をほぼすべてのメンバー型に対応する形へと拡張しました。この強化により、自動生成コードとカスタムコードのきれいな分離を、言語としてより支援できるようになります。開発者はクラスの振る舞いを小手先の手段なしで整理・拡張する柔軟性を得られ、ソースジェネレーターは機能をモジュール的に注入する強力な新しい手段を手に入れます。大規模なフレームワークに取り組む場合でも、ただ可読性のためにコードを分けたい場合でも、partial コンストラクターとイベントは C# 14 でより表現力が高く便利なアプローチを提供します。

出典

Comments

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

< 戻る