.Net7 から追加された INumber<T> インターフェースを用いて、
値オブジェクトの基底クラスとなるような record クラスを作成しました。
INumber<T> 型を使用するジェネリッククラスとすることで、
int や double などを場合分けせずに記述することが可能となります。
プリミティブな値型と同じような感覚で扱え、
重要なドメインルールを持つクラスを簡単に作成できるようにすることが目的です。
/// <summary>値の基底クラス</summary>
public abstract record ValueBase<TValue, TSelf>
where TValue : INumber<TValue>
where TSelf : ValueBase<TValue, TSelf> {
private static readonly Func<TValue, TSelf> CreateObject;
/// <summary>値</summary>
public TValue Value { get; }
/// <summary>静的コンストラクタ</summary>
static ValueBase() {
var constructorInfo = typeof(TSelf).GetConstructor([typeof(TValue)])
?? throw new NotSupportedException();
CreateObject = v => (TSelf)constructorInfo.Invoke([v]);
}
/// <summary>コンストラクタ</summary>
protected ValueBase(TValue value) {
Value = value;
}
/// <summary>暗黙的型変換</summary>
public static implicit operator TValue(ValueBase<TValue, TSelf> valueObject) {
return valueObject.Value;
}
/// <summary>+オペレーター</summary>
public static TSelf operator +(ValueBase<TValue, TSelf> lhs, ValueBase<TValue, TSelf> rhs) {
return CreateObject(lhs.Value + rhs.Value);
}
/// <summary>ToString</summary>
public sealed override string ToString() {
return Value.ToString()!;
}
}
静的コンストラクタでは、リフレクションを用いてTValue型を引数にとり
継承先のオブジェクトを生成する関数を作成しています。
この例では四則演算のうち加算のみ実装しています。
値オブジェクトから数値への暗黙変換を許可しています。
またrecord 型の継承先クラスではコンパイラにより ToString() が自動でオーバーライドされるので、
数値のみが出力されるように ToString() を sealed にしています。
使用する側は以下のようにします。
/// <summary>得点クラス</summary>
public sealed record Score : ValueBase<int, Score> {
public Score(int value) : base(value) {}
}
// 使用例
Score score10 = new Score(10);
Score score20 = new Score(20);
Score total = score10 + score20;
Console.WriteLine(total); // 30 が出力される