C#14新機能

C#14 新機能 C02_幽霊コードを定義パーツで契約による実装をするという考え方

partialコンストラクターの復習

前回、partialクラスのコンストラクターを扱いました。ここでは、そのpartialコンストラクターに至るまでの経緯として、自動生成側にコンストラクターを直接書いた場合に何が起きるのかを補足しておきます。

partialクラスとして2つのファイル(手書き側のForm1.csと、自動生成側のForm1.Designer.cs)に分かれている場合、コンストラクターをどちらか一方に書くこと自体はもともと可能でした。

自動生成側にコンストラクターを書くとどうなるか

まず、Form1.Designer.cs(自動生成側)を開いた状態です。この時点ではコンストラクターは定義されておらず、エラーもありません。

図1: Form1.Designer.cs(自動生成側)。まだコンストラクターは書かれていない状態

ここで、Form1.Designer.cs側に public Form1() { } というコンストラクターを書き始めます。しかしForm1.cs(手書き側)には、もともと public Form1() { InitializeComponent(); } というコンストラクターがすでに存在しているため、書いている途中からエラーが発生します。

図2: 自動生成側にコンストラクターを書き始めると、手書き側の定義と重複してエラーになる

これは、同じクラスの中に同じシグネチャのコンストラクターが2つ存在することになるため、「同じ定義がある」という趣旨のエラーになるためです。手書き側からすると、自分では何も変えていないのに急にエラーになるので、なぜかわからなくなってしまいます。

手書き側を消すと「幽霊コード」になる

このエラーを解消するには、手書き側(Form1.cs)のコンストラクターを書けなくする、つまり削除するかコメントアウトする必要があります。もともとForm1.csにあったコンストラクターは次のようなものでした。

図3: Form1.cs(手書き側)にもともと書かれていたコンストラクター

このコンストラクターをコメントアウトすると、自動生成側のコンストラクターだけが有効な定義として残り、エラーは消えます。

図4: 手書き側のコンストラクターをコメントアウトすると、エラーが解消される

この状態のForm1.csを見ると、コンストラクターの姿はコメントとしてしか残っていません。実際の処理はすべてForm1.Designer.cs側に書かれており、そちらだけを見ればアプリケーションは問題なく動作します。

図5: 自動生成側(Form1.Designer.cs)にInitializeComponent()の呼び出しまで書き終えた状態。処理の実体はここに隠れている

これがまさに「幽霊メソッド」「幽霊コード」と呼べる状態です。手書き側のファイルだけを見ている開発者からすると、コンストラクターがどこにあるのか、そもそも存在するのかどうかもわからないまま、実際には存在して動いている、ということになってしまいます。

つまり、今までも自動生成側にコンストラクターを書くこと自体ができなかったわけではありません。書いてしまうと手書き側に支障が出る(書けなくなる、または存在が見えなくなる)という問題があったのです。

partialで「契約による実装」にする

この問題を解消するのが、前回扱ったpartialコンストラクターです。Form1.cs(手書き側)に、実装を持たない次のような定義パーツを書きます。

図6: 手書き側にpartialコンストラクターの定義パーツを書く。エラーは発生しない

public partial Form1(); という宣言だけの一文(定義パーツ)を手書き側に書き、実装本体は自動生成側の public partial Form1() { InitializeComponent(); } に持たせます。こうすることで、手書き側は「このクラスにはコンストラクターが存在し、実装は別の場所(自動生成側)にある」ということを、コードの上で明示的に理解した状態になります。

これは、自動生成側が一方的にコンストラクターを使っているのではなく、手書き側もその存在を合意した上で成り立っている、という意味で「契約による実装」と言えます。partialで両方のファイルが同じメンバーの存在について合意していることが、コード上にはっきり表れるわけです。

コンストラクター自体は手書き側に書けない点への対策

ただし、この方法でもコンストラクター本体を手書き側に書くことはできません。定義パーツはあくまで宣言のみで、実装は自動生成側に置く必要があるからです。この制約への対策として、前回はInitのような通常の部分メソッド(partialメソッド)を別途用意し、コンストラクターの代わりにそちらを手書き側で使ってもらう、という流れで解決していました。

まとめると、自動生成側にコンストラクターを書くこと自体は以前から可能でしたが、書いてしまうと手書き側に支障が出て「幽霊コード」化してしまう問題がありました。partialコンストラクターにより、手書き側と自動生成側が合意(契約)した上でコンストラクターを分割して書けるようになり、この問題が解消された、ということになります。

C#14 新機能