.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 が出力される