koudenpaのブログ

趣味のブログです。株式会社はてなでWebアプリケーションエンジニアをやっています。職業柄IT関連の記事が多いと思います。

ASP.NET Core で System.Random を使う

ASP.NET CoreC#)で System.Random を使った際の覚書記事です。

アプリケーションを作っていると乱数を使いたくなる場面は多々ある。各言語では概ね疑似乱数の実装が提供されている。

.NET Core でも System.Random クラスが提供されている。

疑似乱数の生成器は内部に状態をもって、次々に疑似乱数値を生成していくものだ。したがって、疑似乱数クラスのインスタンスは乱数が欲しい場面ごとにインスタンスを生成するのではなく、使いまわしすることが望ましい。

これで済むのでは?

// シングルトンインスタンスとしてDIコンテナに登録
services.AddSingleton<Random>(new Random());

と思ったけれど、ドキュメントを読むとこんなことが書いてあった。

クラスRandomはスレッドセーフではありません。 複数のスレッドRandomからメソッドを呼び出す場合は、次のセクションで説明するガイドラインに従ってください。

仕方ないのでガイドラインを参考にスレッドセーフになるようなRandomラッパーを書いて、それをDIコンテナに登録することにした。

// Randomのラッパーを作る。ここでは今回使うメソッドだけラップしている。
public interface IRandomizer
{
    int Next(int maxValue);
}
public class Randomizer : IRandomizer
{
    private Random random;
    private object syncRoot = new object();

    public Randomizer()
    {
        random = new Random();
    }

    public int Next(int maxValue)
    {
        lock (syncRoot)
        {
            return random.Next(maxValue);
        }
    }
}
// あとは Startup.cs でシングルトンに登録して
services.AddSingleton<IRandomizer>(new Randomizer());

// 使いたいところでコンストラクタインジェクションしてやればよい
public class HogeController : Controller
{
    private readonly ApplicationDbContext _context;
    private readonly IRandomizer _randomizer;
    public HogeController(
        ApplicationDbContext context,
        IRandomizer randomizer)
    {
        _context = context;
        _randomizer = randomizer;
    }
    // ...
}

パッと見動いていそうだった。

以下余談。

スレッドセーフなRandom実装を提供してくれれば楽なのだけれどなぁ。ConcurrentRandom のような感じで。

自分はC#Javaをたまに比較するのだけれど、JavaではRandomの実装はスレッドセーフらしい。ただ、スレッドセーフにするためにそれなりにオーバヘッドがあるようで、それを回避するならスレッド毎に乱数を生成する実装を使うようするのがいいそうだ。

この事情はC#だろうが変わらないだろうけれど、今回はアプリケーション内で唯一の乱数生成器を使うことを優先した。