ある型をジェネリック型引数の型にキャストしたいというとき、object
を経由するという手筋がある。
private readonly struct WithBox<T> { public readonly double Value; public WithBox(double value) => Value = value; public T Convert() { if (typeof(T) == typeof(int)) { // 以下は型制約抜きではコンパイルが通らない // return (T)Value; // object 経由だと成立する return (T)(object)(int)Value; } throw new InvalidOperationException(); } }
上記のようなキャストで、無事T
が返る。
ただ、今回のようにキャスト対象が値型だとボクシング、Tが値型ならさらにアンボクシングが入る。
これらに嫌な気持ちを持つ人も少なくないはず。
これらを避けるため、デリゲートを用いる手法がある。
参考:ジェネリックメソッドで値型を返す時にボックス化させない方法 - cactuaroid blog
private readonly struct WithoutBox<T> { public readonly double Value; public WithoutBox(double value) => Value = value; public T Convert() { if(typeof(T) == typeof(int)) { var func = (Func<double, T>)(object)Conv; // Value のボクシング・アンボクシングがない return func(Value); } throw new InvalidOperationException(); } private static int Conv(double d) => (int)d; }
なるほど! という感じ。
ただ、単純にキャストする場合と較べてデリゲートを使うという気にかかる点はある。
実際のところどれくらいの効能なのだろうか。気になったら試すが吉。
[Benchmark] public int CastWithBox() { WithBox<int> v = new(Math.PI); return v.Convert(); } [Benchmark] public int CastWithoutBox() { WithoutBox<int> v = new(Math.PI); return v.Convert(); }
| Method | Mean | Error | StdDev | Median | Allocated | |--------------- |----------:|----------:|----------:|----------:|----------:| | CastWithBox | 0.0180 ns | 0.2963 ns | 0.0162 ns | 0.0222 ns | - | | CastWithoutBox | 6.3944 ns | 1.2450 ns | 0.0682 ns | 6.4225 ns | - |
😥
Keep it simple への敗北。
なお、標準ライブラリもobject
経由キャスト。
参考:Source Browser(リンク先double.TryConvertTo
)
なんだか悔しくてさらにUnsafe.As<int, T>
利用も考えたけれど、object
経由キャストで既に刹那な時間なのでここで終わり。