てくメモ

trivial な notes

【C#】ジェネリック型引数の型の new()

ジェネリック型引数の型のインスタンスを作成したいという場合がある。

このときはまず、型制約のnew()制約が考えられる。

public interface IInterface { }

public IInterface GenericNew<T>()
    where T : IInterface, new()
{
    return new T();
}

ただ、これは内部的にリフレクションが利用されており、見た目は同じながら通常のnew()より重い。
参考:ジェネリック - C# によるプログラミング入門 | ++C++; // 未確認飛行 C


上記参考記事では、外からFunc<T>をもらう方が速いとしている。

public IInterface Factory<T>(Func<T> factory)
    where T : IInterface
{
    return factory();
}


また、Generic Math ことインターフェースの静的仮想メンバー導入後では、それを利用した方法もある。
参考:(C#) interface の静的仮想メンバーでジェネリックコンストラクタ - ネコのために鐘は鳴る

public interface IInterface<TSelf> : IInterface
{
    static abstract TSelf New();
}

public IInterface StaticMemberNew<T>()
    where T : IInterface<T>
{
    return T.New();
}


方法が複数あるのでとりあえず計測、ということでBenchmarkDotNet で測ってみる。

private class A : IInterface<A>
{
    public static A New() => new();
}
private class B : IInterface<B>
{
    public static B New() => new();
}
private class C : IInterface<C>
{
    public static C New() => new();
}
public char[] chars = new[] { 'A', 'B', 'C' };

[Benchmark]
public IInterface[] GenericNew()
{
    IInterface[] results = new IInterface[3];
    for (int i = 0; i < chars.Length; i++)
    {
        results[i] = chars[i] switch
        {
            'A' => GenericNew<A>(),
            'B' => GenericNew<B>(),
            'C' => GenericNew<C>(),
            _ => throw new InvalidOperationException()
        };
    }

    return results;
}

[Benchmark]
public IInterface[] Factory()
{
    IInterface[] results = new IInterface[3];
    for (int i = 0; i < chars.Length; i++)
    {
        results[i] = chars[i] switch
        {
            'A' => Factory<A>(() => new()),
            'B' => Factory<B>(() => new()),
            'C' => Factory<C>(() => new()),
            _ => throw new InvalidOperationException()
        };
    }

    return results;
}

[Benchmark]
public IInterface[] StaticMemberNew()
{
    IInterface[] results = new IInterface[3];
    for (int i = 0; i < chars.Length; i++)
    {
        results[i] = chars[i] switch
        {
            'A' => StaticMemberNew<A>(),
            'B' => StaticMemberNew<B>(),
            'C' => StaticMemberNew<C>(),
            _ => throw new InvalidOperationException()
        };
    }

    return results;
}
          Method |      Mean |    Error |   StdDev |   Gen0 | Allocated |
---------------- |----------:|---------:|---------:|-------:|----------:|
      GenericNew | 104.30 ns | 59.13 ns | 3.241 ns | 0.0381 |     120 B |
         Factory |  39.08 ns | 25.54 ns | 1.400 ns | 0.0382 |     120 B |
 StaticMemberNew |  32.42 ns | 24.56 ns | 1.346 ns | 0.0382 |     120 B |

new()制約はやはり後れて、生成デリゲート(Func<T>)とインターフェースの静的仮想メンバーによる方法は、後者が速いものの差はnew()制約と比べると小さい、というふうに見える。

簡易なのはデリゲート。
とはいえ、なんにせよインターフェース実装はするのだから、静的仮想メンバーの利用もほとんど手間がかからないように感じる。