C#で乱数を生成する際、多くの開発者が最初に手に取るのがRandomクラスです。
ゲームのダメージ計算やパスワードの初期化、データのサンプリングなど、特定の範囲内で数値を生成したい場面は非常に多く存在します。
しかし、範囲の指定方法を一歩間違えると、意図しない数値が生成されたり、プログラムが予期せぬ動作をしたりすることもあります。
本記事では、C#のRandomクラスを用いた正確な範囲指定の方法について、初心者から中級者まで役立つ知識を整理して紹介します。
最新の.NET環境における推奨される書き方も含め、実務で役立つテクニックを身につけていきましょう。
C# Randomクラスの基本的な使い方
C#で乱数を扱うための最も標準的な方法は、System.Randomクラスを利用することです。
このクラスを使用するには、まずインスタンスを生成する必要があります。
Randomクラスのインスタンス化
基本的なインスタンス化のコードは以下の通りです。
// Randomクラスのインスタンスを作成する
Random rand = new Random();
従来の.NET Frameworkでは、インスタンスを生成するタイミングによって乱数のパターンが重複する問題がありました。
しかし、現代の.NET(.NET Core以降)では、デフォルトのコンストラクタが内部的に適切なシード値を使用するため、連続してインスタンスを作成しても値が重なりにくくなっています。
Nextメソッドによる範囲指定の基本
整数(int型)の乱数を生成するには、Nextメソッドを使用します。
引数を指定しない場合は、0以上の整数がランダムに返されます。
範囲を指定したい場合は、引数に最小値と最大値を渡します。
Nextメソッドの引数と仕様(最小値・最大値)
Randomクラスで最も重要なルールは、「最小値は範囲に含まれるが、最大値は含まれない」という点です。
この仕様を正しく理解していないと、バグの原因になります。
最小値は「含む」、最大値は「含まない」の法則
例えば、1から10までの数値が欲しい場合、第2引数には「11」を指定する必要があります。
数学的な表現では「最小値 ≦ x < 最大値」という半開区間になります。
この仕様は、配列のインデックス(0から要素数-1まで)を扱う際に非常に便利です。
範囲指定の具体的なコード例
実際に1から100までの乱数を生成する例を見てみましょう。
Random rand = new Random();
// 1から100までの数値を生成する場合
// 第2引数は101を指定する必要がある
int randomNumber = rand.Next(1, 101);
Console.WriteLine($"生成された数値: {randomNumber}");
生成された数値: 42
もしrand.Next(1, 10)と記述した場合、実際に生成される数値は1から9までとなります。
10が生成されることは決してないため、上限の設定には常に注意を払ってください。
浮動小数点数(double, float)の範囲指定
整数の範囲指定は簡単ですが、小数を含む数値を特定の範囲で生成したい場合は少し工夫が必要です。
Random.NextDoubleメソッドは、0.0以上1.0未満の値を返します。
NextDoubleメソッドの使い方
まずは、基本的なNextDoubleの挙動を確認しましょう。
Random rand = new Random();
double d = rand.NextDouble();
Console.WriteLine(d);
0.742859123456789
このメソッドには、Nextメソッドのように引数で範囲を直接指定するオーバーロードが存在しません。
指定した範囲の浮動小数点数を作る計算式
任意の範囲(minからmaxまで)の小数を生成するには、以下の計算式を使用します。
double result = min + (rand.NextDouble() * (max - min));
例えば、10.5から20.5の範囲で値を生成したい場合は次のように記述します。
double min = 10.5;
double max = 20.5;
Random rand = new Random();
// 範囲を指定してdouble型の乱数を生成
double rangeDouble = min + (rand.NextDouble() * (max - min));
Console.WriteLine($"生成された小数: {rangeDouble}");
生成された小数: 15.3284012345
この式を用いることで、自由な範囲の浮動小数点数を取得することが可能になります。
【重要】現代的なC#での書き方:Random.Shared
C# 8.0や.NET 6以降の環境では、より効率的で安全な乱数生成の方法が導入されています。
それがRandom.Sharedプロパティの利用です。
スレッドセーフな乱数生成
従来のnew Random()で作成したインスタンスは、マルチスレッド環境で同時にアクセスすると、内部状態が壊れて常に0を返すようになるなどの問題がありました。
Random.Sharedを使用すると、各スレッドで安全に共有できるインスタンスが提供されます。
これにより、開発者がスレッドセーフを意識して複雑なロック処理を書く必要がなくなりました。
インスタンス生成の手間を省く
Random.Sharedを使えば、わざわざnewする必要もありません。
// インスタンス化不要でどこからでも呼び出せる
int value = Random.Shared.Next(1, 11);
Console.WriteLine(value);
現在のC#開発においては、特別な理由がない限りこのRandom.Sharedを使用するのがベストプラクティスとされています。
暗号学的に安全な乱数が必要な場合
ここまではSystem.Randomを紹介してきましたが、このクラスが生成するのは「擬似乱数」です。
擬似乱数は計算によって求められるため、原理的には次の値を予測されるリスクがあります。
パスワード生成や暗号化キー、金融取引に関わる処理では、Randomクラスを使用してはいけません。
RandomNumberGeneratorクラスの利用
セキュリティが重視される場面では、System.Security.Cryptography.RandomNumberGeneratorクラスを使用します。
using System.Security.Cryptography;
// 安全な乱数を生成する
int secureValue = RandomNumberGenerator.GetInt32(1, 11);
Console.WriteLine($"安全な数値: {secureValue}");
RandomNumberGenerator.GetInt32メソッドは、指定した範囲内で予測不可能な数値を生成します。
用途に応じて、通常のRandomと安全なRandomNumberGeneratorを使い分けることが重要です。
範囲指定時によくあるエラーと対処法
乱数の範囲指定を行う際には、プログラマが陥りやすいミスがいくつか存在します。
特に実行時例外が発生するケースを確認しておきましょう。
最大値が最小値より小さい場合(ArgumentOutOfRangeException)
Next(minValue, maxValue)メソッドにおいて、minValueがmaxValueよりも大きい値を指定すると、プログラムは例外をスローします。
// これはエラーになる例
int errorValue = Random.Shared.Next(100, 10);
変数を引数に渡す場合は、事前に「最小値 < 最大値」という条件を満たしているかチェックするロジックを入れると安全です。
また、負の数を範囲に指定することも可能ですが、その際も大小関係のルールは変わりません。
実践的なサンプル:リストからランダムに要素を取得する
範囲指定の最も一般的な応用例は、リストや配列からランダムに1つの要素を選ぶ処理です。
var fruits = new List<string> { "りんご", "バナナ", "オレンジ", "ぶどう" };
// インデックスは0から要素数(Count)未満
int index = Random.Shared.Next(fruits.Count);
string selected = fruits[index];
Console.WriteLine($"選ばれたフルーツ: {selected}");
リストの要素数が4の場合、Next(4)は0, 1, 2, 3のいずれかを返します。
これはリストのインデックスと完全に一致するため、非常にシンプルに記述できます。
まとめ
C#で数値をランダムに生成する際は、Randomクラスの特性を正しく理解することが不可欠です。
特に、最大値が範囲に含まれないという仕様は、最も間違いやすいポイントの一つです。
また、現代のC#ではRandom.Sharedを活用することで、スレッドセーフで効率的なコードを記述できます。
もしセキュリティが重要な場面であれば、RandomNumberGeneratorを選択することを忘れないでください。
用途に合わせて最適な方法を選び、安全で信頼性の高いプログラムを実装していきましょう。
