C#14新機能

C#14 新機能 C01_partialコンストラクター

今回は「partialイベントとコンストラクター」というC# 14の新機能のうち、コンストラクターの方を扱います。

図1 Microsoft Learn「C# 14 の新機能」ページ。partial イベントとコンストラクターの項目

partialというキーワードは、1つのクラスをファイルとして2つに分けて記述できるようにするものです。手書きで実装する部分と、コード生成ツールなどが自動生成する部分とをファイル単位で分離できるという用途で使われます。

partialはクラス以外にも、構造体・インターフェース・メソッド・プロパティなどにも以前から使用できましたが、C# 14ではこれに加えてイベントとコンストラクターにもpartialが使えるようになりました。今回はこのうちコンストラクターの使い方を、Windowsフォームアプリ(.NET 10)のプロジェクトを例に見ていきます。

Windowsフォームアプリにおける既存のpartial構造

Windowsフォームアプリのプロジェクトを新規作成すると、Form1はもともとpartial classとして、デザイナーが生成するコード(Form1.Designer.cs)と、プログラマーが手書きするコード(Form1.cs)の2つのファイルに分かれています。

図2 Form1.cs(手書き側)。コンストラクターがありInitializeComponent()を呼び出している

図3 Form1.Designer.cs(自動生成側)。partial classの宣言のみで、これまではコンストラクターは書けなかった

これまでコンストラクターにはpartialが使えなかったため、Form1.Designer.cs側にはコンストラクターがなく、Form1.cs側だけにpublic Form1()というコンストラクターがあり、その中でInitializeComponent()を呼び出す、という構成になっていました。

デザイナー側にコンストラクターを書いてみる

ここで試しに、Form1.Designer.cs側にもコンストラクターを書いて、そちらでInitializeComponent()を呼ばせてみます。public partial Form1()という形でpartialを付けて書くと、次のようにコンパイルエラーになります。

図4 Form1.Designer.cs側にpublic partial Form1()を追加した状態

図5 CS9276:部分的メンバー「Form1.Form1()」には定義パーツが必要です、というコンパイルエラー

これは、partialを付けたコンストラクターには「定義パーツ」が必要である、というエラーです。partialメンバーは、実装を書く側と、定義だけを書く側の2つに分かれる必要があります。デザイナー側で実装を書くのであれば、手書き側には定義だけを書いておく必要がある、ということです。

そこでForm1.cs側の既存のコンストラクターを、public partial Form1();という定義パーツ(中括弧の実装を持たず、セミコロンで終わる形)に書き換えます。

図6 Form1.cs側をpublic partial Form1();という定義パーツに書き換えた状態

こうすることでコンパイルエラーが解消し、実行してもこれまでどおり動作します。定義パーツを手書き側に残しておくことで、「コンストラクターはもう1つの自動生成側のファイルに実装されている」ということを開発者が明示的に分かるようになっています。

図7 エラーが解消し、実行できる状態になったForm1.cs

// Form1.Designer.cs(自動生成側・実装パーツ)
public partial Form1()
{
  InitializeComponent();
}

// Form1.cs(手書き側・定義パーツ)
public partial Form1();

ログを追加して動作を確認する

デザイナー側のコンストラクターにSystem.Diagnostics.Debug.WriteLine(“AAAA”)というログ出力を追加し、実行して出力ウィンドウにAAAAと表示されることを確認します。

図8 Form1.Designer.csのコンストラクターにDebug.WriteLine(“AAAA”)を追加

部分メソッドで手書き側にも初期処理を追加する

この構成だと、コンストラクターの実装自体はデザイナー側(自動生成側)にしか書けないため、コンストラクターが動いたタイミングで手書き側にも初期処理を入れたい、という場合に困ってしまいます。そこでpartialメソッド(部分メソッド)を利用します。

デザイナー側にprivate partial void Init();という定義パーツを追加し、コンストラクターの最後でInit();を呼び出すようにします。

図9 Form1.Designer.cs。コンストラクター末尾でInit()を呼び出し、private partial void Init();を定義パーツとして宣言

そして手書き側のForm1.csに、同じシグネチャでprivate partial void Init()の実装を追加し、その中でSystem.Diagnostics.Debug.WriteLine(“BBBB”)を呼び出すようにします。

図10 Form1.cs側にInit()の実装パーツを追加し、Debug.WriteLine(“BBBB”)を実行

この状態で実行すると、コンストラクターが動いた際にAAAAとBBBBの両方のログが出力されることが確認できます。デザイナー側でコンストラクターの実装自体を持たせつつ、手書き側でも部分メソッドを使えば独自の初期処理を追加できる、という使い分けになります。

// Form1.Designer.cs(自動生成側)
public partial Form1()
{
  InitializeComponent();
  System.Diagnostics.Debug.WriteLine("AAAA");
  Init();
}

private partial void Init();

// Form1.cs(手書き側)
public partial Form1();

private partial void Init()
{
  System.Diagnostics.Debug.WriteLine("BBBB");
}

 

C#14 新機能