C#において、乱数を生成する際には一般的にSystem.Randomクラスが利用されます。
プログラム内で「ランダムな結果」が求められる場面は多いですが、開発のフェーズによってはそのランダム性を制御し、意図的に同じ結果を再現したい場合があります。
例えば、バグの調査中に特定の乱数パターンで不具合が発生した際、実行のたびに結果が変わってしまうと原因の特定が困難になります。
このような課題を解決するのが、「シード(種)値」の固定という手法です。
この記事では、C#でRandomクラスのシード値を固定し、再現性のある乱数を生成するための具体的な実装方法と活用シーンについて詳しく紹介します。
C#におけるRandomクラスとシード値の仕組み
C#のSystem.Randomクラスは、実際には「疑似乱数生成器」と呼ばれる仕組みで動作しています。
疑似乱数とは、完全に無作為な数値ではなく、特定の数式に基づいて計算によって導き出される数値の列です。
この計算の出発点となる値が「シード値」であり、同じシード値を与えれば、必ず同じ順番で同じ数値が生成されるという性質を持っています。
デフォルトの挙動
引数なしでnew Random()を呼び出した場合、シード値には現在のシステム時刻(Environment.TickCountなど)が自動的に使用されます。
ミリ秒単位で値が変化するため、通常の実行では毎回異なる乱数列が得られるようになっています。
しかし、2026年現在のモダンな.NET環境(.NET 8や.NET 9以降)でも、この基本的なアルゴリズムの性質は変わっていません。
シード値を固定して乱数を生成する方法
シード値を固定する方法は非常に簡単で、Randomクラスのコンストラクタに任意の整数(int型)を渡すだけです。
以下のコード例では、固定のシード値を使用して乱数を生成する方法を示しています。
using System;
public class Program
{
public static void Main()
{
// シード値として「12345」を指定
int seed = 12345;
Random rand = new Random(seed);
Console.WriteLine("1回目の生成:");
for (int i = 0; i < 5; i++)
{
Console.WriteLine(rand.Next(1, 101)); // 1から100の範囲で生成
}
// 再度同じシード値でインスタンスを作成
Random rand2 = new Random(seed);
Console.WriteLine("\n2回目の生成(同じシード):");
for (int i = 0; i < 5; i++)
{
Console.WriteLine(rand2.Next(1, 101));
}
}
}
1回目の生成:
23
84
71
38
92
2回目の生成(同じシード):
23
84
71
38
92
この実行結果からわかるように、インスタンスが異なってもシード値が同じであれば、生成される数値のパターンは完全に一致します。
シード値固定が必要な主なユースケース
どのような場面でシード値の固定が役立つのか、代表的な例を整理しました。
1. ユニットテストの自動化
プログラムのテストにおいて、入力値がランダムだと結果の期待値を固定することができません。
シード値を固定することで、常に同じランダムデータが入力される状態を作り出し、テストの妥当性を検証できるようになります。
2. ゲームのマップ生成(プロシージャル生成)
オープンワールドゲームなどで、特定の「ワールドシード」を入力すると全員が同じ地形を生成できる仕組みがあります。
これは、地形生成アルゴリズムのRandomクラスにユーザー入力のシード値を渡すことで実現されています。
3. デバッグの再現性確保
「100回に1回だけ発生するエラー」のような稀な現象を追跡する際、その時のシード値をログに記録しておきます。
修正作業時にそのシード値を再利用すれば、エラーが発生した状況を何度でも再現することが可能になります。
シード値の指定方法による違い
以下の表は、シード値の指定方法とその特徴を比較したものです。
| 指定方法 | 特徴 | 主な用途 |
|---|---|---|
new Random() | 時刻依存。実行のたびに変化する。 | 一般的なアプリケーションの演出。 |
new Random(int) | 固定値。常に同じ結果。 | テスト、デバッグ、マップ共有。 |
Random.Shared | スレッドセーフだがシード固定不可。 | 並列処理でのパフォーマンス重視。 |
実装上の注意点とベストプラクティス
シード値を固定する際に、開発者が気をつけるべきポイントがいくつかあります。
Randomインスタンスの寿命に注意する
乱数を生成するたびにnew Random(seed)を繰り返すと、同じシード値が何度もリセットされるため、毎回同じ数字しか出力されないというミスが起こりやすいです。
基本的には、1つのプロセスや特定の処理単位で1つのインスタンスを使い回すことが推奨されます。
マルチスレッド環境での取り扱い
System.Randomはスレッドセーフではありません。
複数のスレッドから同時に同じRandomインスタンスへアクセスすると、内部状態が壊れてしまい、再現性が失われる可能性があります。
マルチスレッド環境で再現性を確保したい場合は、スレッドごとに異なる固定シード値を持つインスタンスを割り当てるか、ロック機構を検討してください。
暗号学的な用途には使用しない
System.Randomはあくまで疑似乱数であり、予測が可能です。
セキュリティが重要なパスワード生成や暗号化キーの作成には、System.Security.Cryptography.RandomNumberGeneratorを使用してください。
ただし、これらの暗号用乱数生成器には「シード値を固定して再現させる」という機能は通常備わっていないため注意が必要です。
高度な再現性のためのテクニック
2026年現在の開発現場では、より複雑なシミュレーションを行うために、独自のシード管理クラスを実装することも一般的です。
例えば、メインのシード値から複数のサブシード値を派生させ、それぞれの機能(天候、敵の配置、アイテムドロップなど)に割り当てる設計です。
public class SeedManager
{
private readonly int _masterSeed;
public SeedManager(int masterSeed)
{
_masterSeed = masterSeed;
}
public Random GetWeatherRandom() => new Random(_masterSeed + 1);
public Random GetEnemyRandom() => new Random(_masterSeed + 2);
}
このように、マスターシードを基点に各要素の乱数を管理することで、ゲーム全体の状態を一つのシード値で保存・共有することが可能になります。
まとめ
C#のRandomクラスでシード値を固定することは、プログラムに再現性を持たせるための極めて強力な手法です。
コンストラクタに整数を渡すだけで、誰が実行しても同じ結果が得られるようになり、テストやデバッグ、特殊なコンテンツ生成に大きく貢献します。
現代のC#開発においては、Random.Sharedによる利便性と、new Random(seed)による制御可能性を適切に使い分けることが求められます。
まずはシンプルな数値固定から始め、再現性のあるコード設計のメリットを実感してみてください。
今回紹介したコツを意識することで、予測不能なバグに悩まされる時間を減らし、より堅牢なプログラムを構築できるはずです。
