部分コンストラクタと部分イベント(More partial members)
C# 14では、コンストラクタとイベントを部分メンバーとして宣言できるようになりました。
部分メンバーとは、クラスや構造体内で、メソッド、プロパティ、インデクサ、コンストラクタ、イベントなどを定義の宣言と実装の宣言に分割できる機能です。このときクラスと構造体には、partial修飾子を付ける必要があります。部分メンバーにより、ツールによる自動生成コードと開発者自身のコードを分離でき、コードの管理が容易になるというメリットがあります。
部分メンバーは、C# 3.0において部分クラスの考え方とともに部分メソッドが最初に利用可能になりました。このあと、C# 13においてプロパティとインデクサについても部分メンバーとしての利用が可能になりました(第7回を参照)。
C# 14においては、コンストラクタとイベントについても部分メンバーとしての利用が可能になり、以下の使い方が可能になりました。
- クラスの初期化ロジック(コンストラクタ)が複雑か多数ある場合、宣言と実装を分けてファイルを整理することで可読性がよくなる
- イベントハンドラの追加と削除(add/remove)を自前で制御したいが、イベントの存在だけを明示したいAPI設計に使える
部分コンストラクタは、以下のリストのように宣言にpartial修飾子を付与して、定義の宣言(シグネチャ)と実装の宣言(本体)とします。コンストラクタ初期化子(this()またはbase())を指定する場合は、本体側に指定します。考え方は部分メソッドと変わらないと言えます。
public partial class MyClass
{
// 定義の宣言(シグネチャのみ)
public partial MyClass(int value);
}
public partial class MyClass
{
// 実装の宣言(本体)
public partial MyClass(int value) : base()
{
Console.WriteLine($"部分コンストラクタが呼ばれました: {value}");
}
}
var myClass = new MyClass(42); // 部分コンストラクタが呼ばれました: 42
部分イベントも、以下のリストのようにevent宣言にpartialキーワードを付与して、定義と実装を分けて記述します。このとき、イベントの実装にはaddメソッド、removeメソッドの実装が必須です。それぞれ、イベントハンドラの追加(+=演算子)と削除(-=演算子)に相当します。
public partial class MyClass
{
public partial MyClass(int value);
// イベントのシグネチャ
public partial event Action<int>? PartialEvent;
}
public partial class MyClass
{
// コンストラクタにイベントハンドラ追加コードを追記
public partial MyClass(int value) : base()
{
Console.WriteLine($"部分コンストラクタが呼ばれました: {value}");
PartialEvent += Handler; // イベントハンドラ追加
}
// イベントハンドラ本体
public void Handler(int code)
{
Console.WriteLine($"イベント起動: {code}");
}
// イベントの実装
private Action<int>? _partialEvent;
public partial event Action<int>? PartialEvent
{
add {
_partialEvent += value;
Console.WriteLine("部分イベントにハンドラが追加されました。");
}
remove {
_partialEvent -= value;
Console.WriteLine("部分イベントからハンドラが削除されました。");
}
}
}
var myClass = new MyClass(42);
// 部分コンストラクタが呼ばれました: 42
// 部分イベントにハンドラが追加されました。
この改良にて、部分メンバーとしての機能は、ほぼ出揃った状況といえるでしょう。
ユーザー定義複合代入演算子(User defined compound assignment)
C# 14では、ユーザー定義の複合代入演算子が使えるようになりました。
改めての説明は不要と思いますが、複合代入演算子とは、四則演算などの実行と結果の代入を1個の演算子で可能にするものです。
int a = 100; a += 200; // a = a + 200 と等価
複合代入演算子には、+=の他に-=、*=、/=、%=、&=、|=、^=、<<=、>>=、>>>=があります。+=、-=、*=、/=についてはcheckedを指定したオーバフローチェックが付いた演算も可能です。
C#では、これらの演算子については直接オーバーロードできず、対応する二項演算(例えば+=なら+)のオーバーロードで代用されていました。つまり、a += bをa = a + bの演算とみなし、二項演算のオーバーロードを呼び出した後、代入していたわけです。二項演算のオーバーロードは静的メソッドとして実装されるので、必ずクラスおよび構造体として演算結果が生成されました。
この問題点は、クラスではnewによるインスタンス生成、サイズの大きな構造体では複製のためのコストが大きくなることです。
class ClassPlus
{
public int Value;
public static ClassPlus operator+(ClassPlus a, ClassPlus b)
{
return new ClassPlus { Value = a.Value + b.Value }; // 重い処理
}
}
var c = new ClassPlus { Value = 10 };
var d = new ClassPlus { Value = 20 };
c += d;
Console.WriteLine($"c.Value = {c.Value}"); // c.Value = 30
C# 14では、複合代入演算子が直接オーバーロード可能になり、この目的での二項演算のオーバーロードが使われなくなりました。オーバーロードは静的メソッドではなくインスタンスメソッドとして実装します。
つまり、自身を書き換えるメソッドとなり、記述方法にもよりますが大幅なパフォーマンスの向上を見込めます。
class ClassCompoundAssignment
{
public int Value;
public void operator+=(ClassCompoundAssignment c) // インスタンスメソッドとなる
{
this.Value += c.Value; // 自身を書き換える
}
}
var e = new ClassCompoundAssignment { Value = 10 };
var f = new ClassCompoundAssignment { Value = 20 };
e += f;
Console.WriteLine($"e.Value = {e.Value}"); // e.Value = 30
まとめ
今回は、シンプルになったラムダ引数の修飾子、バッキングフィールドなしでプロパティを記述できるfieldキーワード、部分メンバーとしてのコンストラクタとイベント、ユーザー定義可能な複合代入演算子を紹介しました。
C#では、本連載で紹介したようにより使い勝手を良くする改変が新バージョンで施されています。よりモダンで使い勝手のよい言語として成長し続けるC#を、今後も見守っていきたいものです。
