Menu

C#の乱数:Random.Next、範囲、シード、シャッフル、安全な乱数

C#のRandomクラスで乱数を生成する方法を解説します。Nextとその上限を含まない範囲、範囲内のランダムなdouble、再現可能な結果のためのシード、ランダムな要素の選択、リストのシャッフル、ループの中のnew Randomの落とし穴、そして暗号的に安全な乱数を扱います。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

System.Random クラスは擬似乱数を生成します。シードから始まる、ランダムに見える決定的な数列です。インスタンスを1つ作ってそのメソッドを呼びます。

出力例:

1294270287
83
You rolled a 5
0.6060

もう一度実行すると数が変わります。上限は含まれません。Next(1, 7) が返しうるのは1、2、3、4、5、6で、7は決して返しません。1から10まで(10を含む)の数なら、Next(1, 11) と書きます。min より小さい max を渡すと ArgumentOutOfRangeException が投げられます。

範囲内の乱数

整数は Next(min, max) でまかなえます。範囲内の double には NextDouble() を拡大縮小し、価格のような decimal には、望む精度になるよう整数から組み立てます。

出力例:

23.6 C
72.42
heads
N
-3

rng.Next(100, 10000) / 100m はセント単位の整数を選んで decimal で割るので、価格は常にちょうど小数点以下2桁になります。double を拡大縮小すると、72.4183のような値になります。

シード:再現可能な数列

整数のシードを渡すと、数列を繰り返せるようになります。同じシードの2つの Random オブジェクトは、まったく同じ数を同じ順序で生成します。

出力:

16 29 91 99 91
16 29 91 99 91
38

シードが役に立つのは、テスト、再実行が必要なシミュレーション、ゲームのリプレイ、手続き的に生成されるレベル(「ワールドシード2026」)です。使ったシードを記録しておけば、意外な結果を後で再現できます。

Microsoftは、あるシードがすべての.NETのバージョンで同じ数列を生成することを約束していません。シードから生成した出力をデータとして保存してはいけません(たとえば、固定のシードから顧客IDを生成し、アップグレード後にも同じものを再生成できると期待すること)。

ランダムな要素を選ぶ

ランダムなインデックスは Next(0, count) です。上限を含まないので、常に有効なインデックスになります。

出力:

Winner 1 gets a sticker pack
Winner 2 gets a mug
Winner 3 gets a sticker pack
common     695
rare       260
legendary  45

重み付きの抽選は、0から99の数を、大きさが重みに等しい範囲に対応付けます。0から69はcommon、70から94はrare、95から99はlegendaryです。1,000回の抽選で、数はそれぞれ700、250、50の近くになります(ここでは695、260、45)。

リストのシャッフル:Fisher-Yates

リストをランダムな順序にするには、末尾から進みながら、各要素をそれ以前(その位置を含む)のランダムな要素と交換します。これがFisher-Yatesのシャッフルで、高速で、すべての順序が等しい確率で現れます。ジェネリックなメソッドとして書くと、任意の要素型の配列やリストに使えます。

出力:

5 A 8 6 4 7 2 3
4 2 1 3 5

ネットでよく見かける2つの近道は、これより劣ります。list.OrderBy(x => rng.Next()) は動きますが、並べ替えを行うので遅く、ランダムなキーが衝突することもあります。rng.Next(i + 1) の代わりに rng.Next(items.Count) と交換するのは似て見えますが、一部の順序が他より現れやすくなります。.NET 8以降では、Random.Shared.Shuffle(array) がその場で正しいシャッフルを行い、rng.GetItems(choices, 5) はランダムな要素を5つ(重複ありで)選びます。

ループの中のnew Random()の落とし穴

典型的なバグは、数が必要になるたびに新しい Random を作ることです。

// Don't do this
int RollDie()
{
    var rng = new Random();   // new instance on every call
    return rng.Next(1, 7);
}

.NET Frameworkでは、シードなしの new Random() はシステムの時計(Environment.TickCount)を使い、これは10から16ミリ秒ごとにしか変わりません。短いループの中の呼び出しは同じシードのインスタンスを作って同じ数を返すので、サイコロを振るループは 4 4 4 4 4 と表示します。.NET Coreと.NET 5以降は、共有のランダムなソースから各インスタンスのシードを作るので症状は隠れますが、数ごとにオブジェクトを作るのは相変わらず無駄で、古いプロジェクトやUnityで再利用するとコードが壊れます。

直し方は、クラスに1つのインスタンスを持たせ、フィールドに格納することです。

出力:

3 6 6 5 6 1 2 1

ここでのシードは、例が毎回同じものを表示するためだけのものです。実際のゲームでは new Random() と書きます。

スレッドとRandom.Shared

Random はスレッドセーフではありません。2つのスレッドが同じインスタンスの Next を同時に呼ぶと、内部の状態が壊れることがあります。よく知られた症状は、それ以降ずっと0を返すインスタンスです。選択肢を簡単なものから挙げます。

static class Dice
{
    // .NET 6 and later: a thread-safe shared instance
    public static int RollShared() => Random.Shared.Next(1, 7);

    // Any version: one instance per thread
    [ThreadStatic] private static Random _local;
    private static Random Local => _local ?? (_local = new Random(Guid.NewGuid().GetHashCode()));
    public static int RollPerThread() => Local.Next(1, 7);

    // Any version: a lock around one shared instance
    private static readonly Random _rng = new Random();
    private static readonly object _sync = new object();
    public static int RollLocked()
    {
        lock (_sync) { return _rng.Next(1, 7); }
    }
}

新しいコードでは Random.Shared が適切な既定の選択です。スレッドセーフで、フィールドが不要で、ランダムにシードされています。シードを指定できないので、再現性が必要なときは独自の new Random(seed) を使います。

暗号的に安全な乱数

System.Random は設計上、予測可能です。シードを知っているか、十分な数の出力を見た人は誰でも、残りを計算できます。ゲームやサンプリングならそれで問題ありませんが、パスワード、リセット用のトークン、セッションID、鍵、くじの抽選には不適切です。それらには、オペレーティングシステムの安全な生成器から読み取る System.Security.Cryptography の RandomNumberGenerator を使います。

出力例:

Verification code: 276062
Reset token: 391e40ab5fa205e6457e48661586d10a
Password: gyv8LWyajJcF

RandomNumberGenerator.GetInt32(.NET Core 3.0以降で使えます)は、範囲内の偏りのない整数を返します。安全なコードを bytes[0] % 10 で作ってはいけません。剰余によって一部の数字が他より出やすくなります。.NET 6以降では、RandomNumberGenerator.GetBytes(16) が新しい配列を直接返し、Convert.ToHexString(bytes) がそれを書式設定します。秘密である必要のない一意の識別子なら、Guid.NewGuid() のほうが簡単です。

よくある質問

C#で乱数を生成するには?

Random オブジェクトを1つ作って Next を呼びます:var rng = new Random(); int roll = rng.Next(1, 7); で1から6が得られます。Next(max) は0から max - 1、NextDouble() は0.0以上1.0未満の double を返します。.NET 6以降では、オブジェクトを作らずに Random.Shared.Next(1, 7) を使えます。

Random.Nextの上限は含まれますか?

含まれません。Next(min, max) は min 以上で max 未満の数を返します。1から10まで(10を含む)の数なら、Next(1, 11) と書きます。そのため Next(0, list.Count) はリストの有効なインデックスになります。

new Random()が同じ数を返すのはなぜですか?

.NET Frameworkでは、new Random() のシードは数ミリ秒ごとにしか変わらないシステムの時計から取られるので、短いループの中で作った複数のインスタンスが同じシードを得て、同じ数列を返します。.NET Coreと.NET 5以降はインスタンスごとに異なるシードを使いますが、直し方はどこでも同じで、Random を1つ作って再利用します。

C#で毎回同じ乱数を得るには?

コンストラクターにシードを渡します:new Random(42)。同じシードの2つのインスタンスは同じ数列を生成するので、テスト、シミュレーション、手続き的な生成を再現できます。あるシードに対する数列は.NETのバージョンによって異なることがあるので、シードから生成した出力を永続的なデータとして保存してはいけません。

System.Randomはパスワードやトークンに使えるほど安全ですか?

安全ではありません。Random は予測可能で、その状態やシードを知った人は誰でも数を再現できます。パスワード、トークン、鍵など、セキュリティに関わるものには System.Security.Cryptography.RandomNumberGenerator を使います。たとえば RandomNumberGenerator.GetInt32(0, 10) や RandomNumberGenerator.GetBytes(32) です。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める