C# 乱数の使い方完全ガイド|Randomで範囲指定・重複なし・安全な生成方法まで解説

はじめに

C#で乱数を扱う場面はとても多くあります。たとえば、ゲームで敵の出現位置を変える、抽選で当選者を選ぶ、テスト用のダミーデータを作る、配列をランダムに並べ替える、認証コードやトークンを生成する、といったケースです。

C#で乱数を生成するときに最もよく使われるのがRandomクラスです。ただし、Random.Next()の最大値は含まれない、ループ内でnew Random()すると問題が起きやすい、パスワード生成には向かないなど、知っておくべき注意点もあります。

この記事では、C#の乱数について、基本的な使い方から範囲指定、重複なしの生成、安全な乱数生成、Unityでの注意点まで実践的に解説します。

1. C#の乱数とは?まず押さえる基本

1-1. 乱数とは何か:プログラムでランダムな値を扱う仕組み

乱数とは、予測しにくい値を一定の範囲から取り出すための仕組みです。C#では、整数・小数・バイト配列など、さまざまな形式のランダムな値を生成できます。

ただし、コンピューターは基本的に決められた処理を正確に実行する機械です。そのため、多くのプログラムで使われる乱数は「完全に偶然」ではなく、計算によってランダムに見える値を作る「疑似乱数」です。

C#のRandomクラスも疑似乱数を生成するクラスです。ゲームや抽選、表示のランダム化など、一般的な用途では十分便利に使えます。

1-2. C#で乱数を使う主な場面:ゲーム・抽選・テストデータ・シャッフル

C#の乱数は、次のような場面でよく使われます。

用途
ゲームサイコロ、敵の出現位置、アイテムドロップ
抽選当選者の選択、ランダムな番号の生成
テストデータランダムな名前、年齢、スコアの生成
シャッフル配列やリストの順番をランダムに並べ替える
表示制御ランダムな広告、メッセージ、画像の表示
セキュリティパスワード、トークン、認証コードの生成

ただし、セキュリティ用途ではRandomではなく、RandomNumberGeneratorを使う必要があります。

1-3. C#で乱数を生成する代表的な方法

C#で乱数を生成する主な方法は次の3つです。

方法用途
Random一般的な乱数生成
Random.Shared.NET 6以降で手軽に使える共有インスタンス
RandomNumberGenerator暗号論的に安全な乱数生成

通常のアプリやゲームであればRandomまたはRandom.Sharedで十分です。一方、パスワードやトークンなど、推測されると困る値にはRandomNumberGeneratorを使います。

1-4. 疑似乱数と本当に安全な乱数の違い

疑似乱数は、内部状態やシード値をもとに計算で生成されます。高速で扱いやすく、ゲームやシミュレーションには向いています。

一方で、セキュリティ用途では「次の値を推測されにくいこと」が重要です。Randomは暗号用途を目的としたクラスではないため、パスワード、APIキー、認証コード、リセットトークンなどには使うべきではありません。

安全性が必要な場面では、System.Security.Cryptography.RandomNumberGeneratorを使います。Microsoftのドキュメントでも、RandomNumberGeneratorは暗号強度の高いランダム値を生成するためのAPIとして説明されています。

2. C#のRandomクラスの基本的な使い方

2-1. Randomクラスとは

Randomクラスは、C#で疑似乱数を生成するための基本的なクラスです。整数、小数、バイト配列のランダム値を生成できます。

基本的には、Randomのインスタンスを作成し、そのインスタンスからNext()NextDouble()などのメソッドを呼び出します。

C#
Random random = new Random();

int number = random.Next();
Console.WriteLine(number);

2-2. Randomインスタンスの作成方法

基本的な作成方法は次のとおりです。

C#
Random random = new Random();

このrandomを使い回して乱数を生成します。

C#
Random random = new Random();

int a = random.Next();
int b = random.Next();
int c = random.Next();

Console.WriteLine($"{a}, {b}, {c}");

初心者がよくやりがちなのが、乱数を作るたびにnew Random()する書き方です。

C#
// 非推奨
int value = new Random().Next(1, 101);

1回だけなら大きな問題になりにくいですが、ループ内で何度も作ると同じような値が出る原因になります。基本は、Randomインスタンスを1つ作って使い回します。

2-3. Next()で整数の乱数を生成する

Next()は、0以上の整数をランダムに返します。

C#
Random random = new Random();

int value = random.Next();

Console.WriteLine(value);

範囲を指定することもできます。

C#
Random random = new Random();

int value = random.Next(100); // 0以上100未満

Console.WriteLine(value);

Next(100)の場合、生成される値は0から99までです。100は含まれません。Microsoftのドキュメントでも、Random.Next(maxValue)maxValueは排他的上限、つまり含まれない最大値として説明されています。

2-4. NextDouble()で小数の乱数を生成する

NextDouble()を使うと、0.0以上1.0未満のdouble型の乱数を生成できます。

C#
Random random = new Random();

double value = random.NextDouble();

Console.WriteLine(value);

NextDouble()の戻り値は、0.0 <= value < 1.0の範囲です。1.0は含まれません。

たとえば、0.0以上100.0未満の小数が欲しい場合は、次のようにします。

C#
Random random = new Random();

double value = random.NextDouble() * 100.0;

Console.WriteLine(value);

2-5. NextBytes()でバイト配列の乱数を生成する

NextBytes()を使うと、バイト配列にランダムな値を入れられます。

C#
Random random = new Random();

byte[] bytes = new byte[10];
random.NextBytes(bytes);

Console.WriteLine(string.Join(", ", bytes));

画像処理、バイナリデータのテスト、ランダムなバイト列の生成などに使えます。ただし、暗号用途のバイト列を生成したい場合はRandom.NextBytes()ではなくRandomNumberGeneratorを使いましょう。Random.NextBytes()はバイト配列をランダム値で埋めるメソッドですが、暗号用途には暗号学的に安全なAPIを使う必要があります。

2-6. Random.Sharedの使い方

.NET 6以降では、Random.Sharedを使うと手軽です。

C#
int value = Random.Shared.Next(1, 101);

Console.WriteLine(value);

Random.Sharedは、どこからでも使える共有のRandomインスタンスです。Microsoftのドキュメントでは、任意のスレッドから同時に使用できるスレッドセーフなRandomインスタンスとして説明されています。

簡単なアプリケーションであれば、次のように書けます。

C#
int dice = Random.Shared.Next(1, 7);
double rate = Random.Shared.NextDouble();

Console.WriteLine($"dice: {dice}");
Console.WriteLine($"rate: {rate}");

.NET 6以降で、特別なシード値が不要な場合はRandom.Sharedを使うとシンプルです。

3. C#で範囲を指定して乱数を生成する方法

3-1. 0以上n未満の乱数を生成する

0以上n未満の整数乱数を生成するには、Next(n)を使います。

C#
Random random = new Random();

int value = random.Next(10); // 0〜9

Console.WriteLine(value);

この場合、生成される値は次の範囲です。

0, 1, 2, 3, 4, 5, 6, 7, 8, 9

10は含まれません。

3-2. min以上max未満の乱数を生成する

最小値と最大値を指定する場合は、Next(minValue, maxValue)を使います。

C#
Random random = new Random();

int value = random.Next(10, 20); // 10〜19

Console.WriteLine(value);

この場合、10は含まれますが、20は含まれません。

10以上20未満

C#の乱数で範囲指定をするときは、「下限は含む、上限は含まない」と覚えておくとミスを防げます。

3-3. 1から10までの乱数を作るときの注意点

1から10までの乱数を作りたい場合、次のように書くのは間違いです。

C#
// 1〜9になってしまう
int value = Random.Shared.Next(1, 10);

Next(1, 10)1以上10未満なので、生成されるのは1から9です。

1から10まで、つまり10を含めたい場合は、最大値に11を指定します。

C#
int value = Random.Shared.Next(1, 11); // 1〜10

3-4. 最大値を含めたい場合の書き方

最大値を含めたい場合は、max + 1を指定します。

C#
int min = 1;
int max = 100;

int value = Random.Shared.Next(min, max + 1); // 1〜100

ただし、maxint.MaxValueの場合はmax + 1がオーバーフローするため注意が必要です。通常の範囲であればこの書き方で問題ありません。

C#
int score = Random.Shared.Next(0, 101); // 0〜100
int dice = Random.Shared.Next(1, 7); // 1〜6

3-5. 負の数を含む範囲の乱数を生成する

Random.Next(min, max)では、負の数を含む範囲も指定できます。

C#
int value = Random.Shared.Next(-10, 11); // -10〜10

Console.WriteLine(value);

この例では、-10から10までの整数が生成されます。最大値の11は含まれないため、結果として10までが対象になります。

3-6. 小数の範囲を指定して乱数を生成する

小数の範囲を指定したい場合は、NextDouble()を使って計算します。

C#
double min = 10.0;
double max = 20.0;

double value = min + Random.Shared.NextDouble() * (max - min);

Console.WriteLine(value);

このコードでは、10.0以上20.0未満の小数が生成されます。

汎用的なメソッドにするなら、次のように書けます。

C#
static double NextDouble(double min, double max)
{
return min + Random.Shared.NextDouble() * (max - min);
}

double value = NextDouble(1.5, 3.5);
Console.WriteLine(value);

4. C#で重複なしの乱数を生成する方法

4-1. 重複なしの乱数が必要になるケース

重複なしの乱数は、次のような場面で必要になります。

場面
抽選同じ人を2回当選させない
席替え同じ席を複数人に割り当てない
カードゲーム同じカードを2回引かない
クイズ同じ問題を繰り返し出さない
ランキング表示ランダムに重複なしで表示する

重複なしにする方法はいくつかあります。少数の乱数を作るだけならHashSet、候補リストから選ぶならList、全体をランダムに並べ替えるならシャッフルが便利です。

4-2. HashSetを使って重複を防ぐ方法

HashSet<T>は、同じ値を重複して保持しないコレクションです。重複なしの乱数を作るときに便利です。

C#
HashSet<int> numbers = new HashSet<int>();

while (numbers.Count < 5)
{
int value = Random.Shared.Next(1, 11); // 1〜10
numbers.Add(value);
}

Console.WriteLine(string.Join(", ", numbers));

このコードでは、1から10までの中から重複なしで5個の整数を生成します。

ただし、生成したい個数が範囲より多い場合は無限ループになります。

C#
// 1〜10の範囲から11個は作れない

そのため、事前にチェックを入れると安全です。

C#
int min = 1;
int max = 10;
int count = 5;

if (count > max - min + 1)
{
throw new ArgumentException("指定範囲より多い個数は生成できません。");
}

HashSet<int> numbers = new HashSet<int>();

while (numbers.Count < count)
{
numbers.Add(Random.Shared.Next(min, max + 1));
}

Console.WriteLine(string.Join(", ", numbers));

4-3. Listから取り出して重複なしにする方法

候補をリストに入れておき、選んだ要素を削除していく方法もあります。

C#
List<int> candidates = Enumerable.Range(1, 10).ToList();
List<int> results = new List<int>();

for (int i = 0; i < 5; i++)
{
int index = Random.Shared.Next(candidates.Count);
results.Add(candidates[index]);
candidates.RemoveAt(index);
}

Console.WriteLine(string.Join(", ", results));

この方法では、選ばれた値を候補から削除するため、同じ値が再び選ばれることはありません。

抽選アプリやカードを引く処理では、この考え方がわかりやすく実用的です。

4-4. 配列やリストをシャッフルして重複なしにする方法

最初に全候補をシャッフルし、先頭から必要な数だけ取り出す方法もあります。

C#
List<int> numbers = Enumerable.Range(1, 10).ToList();

for (int i = numbers.Count - 1; i > 0; i--)
{
int j = Random.Shared.Next(i + 1);
(numbers[i], numbers[j]) = (numbers[j], numbers[i]);
}

List<int> results = numbers.Take(5).ToList();

Console.WriteLine(string.Join(", ", results));

この方法は、候補全体をランダムな順番に並べ替えたい場合に向いています。

4-5. Fisher-Yatesシャッフルで効率よくランダムに並べ替える

Fisher-Yatesシャッフルは、配列やリストを効率よくランダムに並べ替える定番アルゴリズムです。

C#
static void Shuffle<T>(IList<T> list)
{
for (int i = list.Count - 1; i > 0; i--)
{
int j = Random.Shared.Next(i + 1);
(list[i], list[j]) = (list[j], list[i]);
}
}

使い方は次のとおりです。

C#
List<string> names = new List<string>
{
"佐藤",
"鈴木",
"高橋",
"田中",
"伊藤"
};

Shuffle(names);

Console.WriteLine(string.Join(", ", names));

リスト全体の順番をランダムにしたいときは、この方法を使うのがおすすめです。

4-6. 重複なし乱数で無限ループを避ける注意点

重複なし乱数で最も注意すべきなのは、生成したい個数が候補数を超えるケースです。

たとえば、1から10までの範囲には10個の値しかありません。その範囲から重複なしで11個の値を生成することはできません。

悪い例は次のようなコードです。

C#
HashSet<int> numbers = new HashSet<int>();

while (numbers.Count < 11)
{
numbers.Add(Random.Shared.Next(1, 11));
}

このコードは永久に終わりません。

安全にするには、必ず事前チェックを入れます。

C#
int min = 1;
int max = 10;
int count = 11;

int capacity = max - min + 1;

if (count > capacity)
{
throw new ArgumentException("重複なしで生成できる個数を超えています。");
}

5. Randomクラスでよくある失敗と対処法

5-1. 毎回同じ乱数が出る原因

毎回同じ乱数が出る原因として多いのは、同じシード値を指定しているケースです。

C#
Random random = new Random(123);

Console.WriteLine(random.Next(1, 101));
Console.WriteLine(random.Next(1, 101));
Console.WriteLine(random.Next(1, 101));

シード値を指定すると、同じシードからは同じ乱数列が生成されます。これはバグではなく、再現性を持たせるための機能です。

同じ結果にしたくない場合は、通常はシード値を指定せずに作成します。

C#
Random random = new Random();

または、.NET 6以降ならRandom.Sharedを使います。

C#
int value = Random.Shared.Next(1, 101);

5-2. ループ内でnew Random()してはいけない理由

次のようなコードは避けるべきです。

C#
for (int i = 0; i < 10; i++)
{
Random random = new Random();
Console.WriteLine(random.Next(1, 101));
}

ループ内で毎回Randomを作ると、環境やタイミングによっては同じような乱数が出やすくなります。また、毎回インスタンスを作る必要もありません。

正しくは、ループの外で1回だけ作ります。

C#
Random random = new Random();

for (int i = 0; i < 10; i++)
{
Console.WriteLine(random.Next(1, 101));
}

.NET 6以降なら、次のように簡潔に書けます。

C#
for (int i = 0; i < 10; i++)
{
Console.WriteLine(Random.Shared.Next(1, 101));
}

5-3. 乱数に偏りがあるように見えるときの確認ポイント

乱数は、短い回数だけ見ると偏っているように見えることがあります。

たとえば、サイコロを10回振って「1」が一度も出ないことは普通にあります。乱数が正しく動いているか確認するには、試行回数を増やして分布を見る必要があります。

C#
int[] counts = new int[6];

for (int i = 0; i < 100000; i++)
{
int dice = Random.Shared.Next(1, 7);
counts[dice - 1]++;
}

for (int i = 0; i < counts.Length; i++)
{
Console.WriteLine($"{i + 1}: {counts[i]}");
}

また、次のような処理は偏りの原因になります。

C#
// よくない例
int value = Random.Shared.Next() % 10;

範囲を指定したい場合は、Next(10)Next(min, max)を使いましょう。

C#
int value = Random.Shared.Next(10);

5-4. シード値を指定した場合の動作

シード値とは、乱数列の初期値のようなものです。

同じシード値を指定すると、同じ順番で乱数が生成されます。

C#
Random random1 = new Random(100);
Random random2 = new Random(100);

Console.WriteLine(random1.Next());
Console.WriteLine(random2.Next());

この2つは同じ値になります。

ゲームのステージ生成、テスト、シミュレーションなどで「同じ条件なら同じ結果を再現したい」場合に便利です。

5-5. 再現性のある乱数を使いたい場合の実装例

再現性のある乱数を使う場合は、シード値を固定します。

C#
int seed = 12345;
Random random = new Random(seed);

for (int i = 0; i < 5; i++)
{
Console.WriteLine(random.Next(1, 101));
}

このコードは、何度実行しても同じ結果になります。

テストコードでは、ランダム性があると結果が不安定になることがあります。そのような場合は、シード値を固定すると検証しやすくなります。

C#
static List<int> CreateRandomScores(int seed, int count)
{
Random random = new Random(seed);
List<int> scores = new List<int>();

for (int i = 0; i < count; i++)
{
scores.Add(random.Next(0, 101));
}

return scores;
}

5-6. マルチスレッドでRandomを使うときの注意点

マルチスレッドで1つのRandomインスタンスを共有すると、スレッドセーフではない問題が起きる可能性があります。

.NET 6以降であれば、スレッドセーフなRandom.Sharedを使うのが簡単です。

C#
Parallel.For(0, 10, i =>
{
int value = Random.Shared.Next(1, 101);
Console.WriteLine(value);
});

古い.NET環境でスレッドごとに乱数を扱う場合は、スレッドごとにRandomを持たせる、ロックする、または設計を見直す必要があります。

C#
object lockObj = new object();
Random random = new Random();

Parallel.For(0, 10, i =>
{
int value;

lock (lockObj)
{
value = random.Next(1, 101);
}

Console.WriteLine(value);
});

ただし、ロックはパフォーマンスに影響する場合があります。可能であれば、.NET 6以降ではRandom.Sharedを使いましょう。

6. C#で安全な乱数を生成する方法

6-1. Randomクラスがセキュリティ用途に向かない理由

Randomは、一般的な疑似乱数を生成するためのクラスです。ゲームや抽選、テストデータ生成には便利ですが、セキュリティ用途には向いていません。

理由は、Randomの出力が暗号論的な安全性を目的としていないためです。攻撃者に値の傾向や内部状態を推測されるリスクがあります。

次のような用途ではRandomを使わないでください。

用途Randomの使用
パスワード生成NG
リセットトークンNG
APIキーNG
認証コードNG
セッションIDNG
ゲームのサイコロOK
テストデータOK

6-2. RandomNumberGeneratorとは

RandomNumberGeneratorは、暗号論的に安全な乱数を生成するためのクラスです。

名前空間は次のとおりです。

C#
using System.Security.Cryptography;

整数の乱数を生成する場合は、RandomNumberGenerator.GetInt32()が便利です。

C#
using System.Security.Cryptography;

int value = RandomNumberGenerator.GetInt32(1, 101);

Console.WriteLine(value);

このコードでは、1以上101未満、つまり1から100までの安全な整数乱数を生成できます。RandomNumberGenerator.GetInt32(min, max)も、下限は含み、上限は含まない形式です。

6-3. 暗号論的に安全な整数乱数を生成する

認証コードのような値を作る場合は、次のように書けます。

C#
using System.Security.Cryptography;

int code = RandomNumberGenerator.GetInt32(100000, 1000000);

Console.WriteLine(code);

この例では、6桁の数値を生成します。

100000〜999999

サイコロのような値でも、安全性が必要な場面ではRandomNumberGeneratorを使えます。

C#
int dice = RandomNumberGenerator.GetInt32(1, 7);

6-4. パスワード・トークン・認証コードに使う乱数の考え方

パスワードやトークンに使う乱数では、次の点が重要です。

ポイント内容
推測されにくい攻撃者が次の値を予測できない
十分な長さがある短すぎる値は総当たりされやすい
文字種が適切英数字・記号などを用途に応じて使う
暗号論的に安全RandomではなくRandomNumberGeneratorを使う

ランダムな文字列を生成する例は次のとおりです。

C#
using System.Security.Cryptography;

static string GenerateSecureString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
char[] result = new char[length];

for (int i = 0; i < length; i++)
{
int index = RandomNumberGenerator.GetInt32(chars.Length);
result[i] = chars[index];
}

return new string(result);
}

string token = GenerateSecureString(32);
Console.WriteLine(token);

6-5. RandomとRandomNumberGeneratorの使い分け

RandomRandomNumberGeneratorは、目的に応じて使い分けます。

目的おすすめ
ゲームのランダム処理RandomまたはRandom.Shared
抽選アプリRandomまたはRandom.Shared
テストデータRandom
再現性のある乱数シード付きRandom
パスワード生成RandomNumberGenerator
トークン生成RandomNumberGenerator
認証コードRandomNumberGenerator

基本的な判断基準は、「推測されても困らないか」です。

推測されても大きな問題がないならRandom、推測されると危険ならRandomNumberGeneratorを使いましょう。

6-6. セキュリティ用途で避けるべき実装例

セキュリティ用途で次のような実装は避けてください。

C#
// NG: パスワード生成にRandomを使っている
static string GeneratePassword(int length)
{
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();

return new string(
Enumerable.Range(0, length)
.Select(_ => chars[random.Next(chars.Length)])
.ToArray()
);
}

一見ランダムに見えますが、Randomは暗号用途に適していません。

安全な実装にするなら、次のようにRandomNumberGeneratorを使います。

C#
using System.Security.Cryptography;

static string GeneratePassword(int length)
{
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
char[] result = new char[length];

for (int i = 0; i < length; i++)
{
result[i] = chars[RandomNumberGenerator.GetInt32(chars.Length)];
}

return new string(result);
}

7. 実践サンプルで学ぶC#乱数の使い方

7-1. サイコロの目をランダムに出す

サイコロは1から6までなので、Next(1, 7)を使います。

C#
int dice = Random.Shared.Next(1, 7);

Console.WriteLine($"サイコロの目: {dice}");

7は含まれないため、結果は1から6になります。

7-2. ランダムな文字列を生成する

一般用途のランダム文字列なら、Random.Sharedで作れます。

C#
static string GenerateRandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
char[] result = new char[length];

for (int i = 0; i < length; i++)
{
int index = Random.Shared.Next(chars.Length);
result[i] = chars[index];
}

return new string(result);
}

string text = GenerateRandomString(12);
Console.WriteLine(text);

ただし、パスワードやトークンとして使う場合はRandomNumberGeneratorを使いましょう。

7-3. 配列からランダムに1件選ぶ

配列からランダムに1件選ぶ場合は、インデックスを乱数で作ります。

C#
string[] fruits = { "りんご", "みかん", "バナナ", "ぶどう" };

int index = Random.Shared.Next(fruits.Length);
string selected = fruits[index];

Console.WriteLine(selected);

Random.Shared.Next(fruits.Length)は、0以上fruits.Length未満の値を返すため、配列のインデックス指定にそのまま使えます。

7-4. リストをランダムに並べ替える

リストをランダムに並べ替えるには、Fisher-Yatesシャッフルを使います。

C#
static void Shuffle<T>(IList<T> list)
{
for (int i = list.Count - 1; i > 0; i--)
{
int j = Random.Shared.Next(i + 1);
(list[i], list[j]) = (list[j], list[i]);
}
}

List<int> numbers = Enumerable.Range(1, 10).ToList();

Shuffle(numbers);

Console.WriteLine(string.Join(", ", numbers));

抽選順、表示順、カードの山札などに使えます。

7-5. 抽選アプリを作る

名前リストから重複なしで当選者を選ぶサンプルです。

C#
List<string> members = new List<string>
{
"佐藤",
"鈴木",
"高橋",
"田中",
"伊藤"
};

int winnerCount = 2;

if (winnerCount > members.Count)
{
throw new ArgumentException("当選者数が参加者数を超えています。");
}

List<string> winners = new List<string>();

for (int i = 0; i < winnerCount; i++)
{
int index = Random.Shared.Next(members.Count);
winners.Add(members[index]);
members.RemoveAt(index);
}

Console.WriteLine("当選者:");
Console.WriteLine(string.Join(", ", winners));

選ばれた人をリストから削除しているため、同じ人が2回当選することはありません。

7-6. テスト用のダミーデータをランダム生成する

テスト用にランダムなユーザーデータを作る例です。

C#
string[] names = { "佐藤", "鈴木", "高橋", "田中", "伊藤" };

for (int i = 0; i < 10; i++)
{
string name = names[Random.Shared.Next(names.Length)];
int age = Random.Shared.Next(18, 66);
int score = Random.Shared.Next(0, 101);

Console.WriteLine($"{name}, {age}歳, {score}点");
}

ダミーデータ生成では、毎回違う値が欲しい場合はRandom.Shared、毎回同じデータを再現したい場合はシード付きRandomを使うと便利です。

C#
Random random = new Random(12345);

8. UnityでC#の乱数を使う場合の注意点

8-1. System.RandomとUnityEngine.Randomの違い

UnityでC#の乱数を使う場合、主に次の2種類があります。

種類特徴
System.RandomC#標準の乱数クラス
UnityEngine.RandomUnityが提供するゲーム向け乱数API

Unityでは、Randomという名前が衝突することがあります。

C#
using System;
using UnityEngine;

この状態でRandomと書くと、System.RandomなのかUnityEngine.Randomなのか曖昧になる場合があります。

その場合は、完全修飾名で書きます。

C#
int value1 = UnityEngine.Random.Range(1, 7);

System.Random random = new System.Random();
int value2 = random.Next(1, 7);

8-2. UnityEngine.Random.Rangeの整数と小数の範囲指定の違い

UnityのRandom.Rangeは、整数と小数で上限の扱いが違います。

整数版は、最小値を含み、最大値を含みません。

C#
int value = UnityEngine.Random.Range(1, 10); // 1〜9

小数版は、最小値と最大値の両方を含む扱いです。

C#
float value = UnityEngine.Random.Range(0.0f, 1.0f); // 0.0〜1.0

Unity公式ドキュメントでも、Random.Rangeは整数版と浮動小数点版で範囲の扱いが異なることが示されています。

C#標準のRandom.Next(min, max)に慣れていると、Unityの小数版で混乱しやすいため注意しましょう。

8-3. ゲームで乱数を再現したい場合の考え方

ゲームでは、同じステージを再現したい、同じランダムマップを生成したい、リプレイを再現したいといった場面があります。

その場合は、シード値を固定します。

System.Randomの場合は次のようにします。

C#
System.Random random = new System.Random(12345);

int value = random.Next(1, 101);

Unity側の乱数では、Random.InitState()を使います。

C#
UnityEngine.Random.InitState(12345);

int value = UnityEngine.Random.Range(1, 101);

ゲーム全体で再現性を持たせたい場合は、どの乱数APIを使うかを統一することが重要です。

8-4. Unityで重複なし抽選を実装する方法

Unityで重複なし抽選をする場合も、基本的な考え方は通常のC#と同じです。

C#
List<string> items = new List<string>
{
"剣",
"盾",
"薬草",
"弓",
"杖"
};

int index = UnityEngine.Random.Range(0, items.Count);
string selected = items[index];
items.RemoveAt(index);

Debug.Log(selected);

items.Countを上限に指定すると、0からitems.Count - 1までのインデックスが選ばれます。

全体をシャッフルしたい場合は、次のようにします。

C#
static void Shuffle<T>(IList<T> list)
{
for (int i = list.Count - 1; i > 0; i--)
{
int j = UnityEngine.Random.Range(0, i + 1);
(list[i], list[j]) = (list[j], list[i]);
}
}

9. C#乱数の目的別おすすめ実装パターン

9-1. 通常のランダム値ならRandomを使う

一般的な乱数生成なら、RandomまたはRandom.Sharedを使います。

C#
int value = Random.Shared.Next(1, 101);

.NET 6以降で、シード値を指定する必要がないならRandom.Sharedが簡単です。

シード値を指定したい場合は、new Random(seed)を使います。

C#
Random random = new Random(12345);
int value = random.Next(1, 101);

9-2. 重複なしならListまたはシャッフルを使う

重複なしで少数を取り出すなら、Listから選んで削除する方法がわかりやすいです。

C#
List<int> candidates = Enumerable.Range(1, 10).ToList();

int index = Random.Shared.Next(candidates.Count);
int selected = candidates[index];

candidates.RemoveAt(index);

全体の順番をランダムにしたいなら、Fisher-Yatesシャッフルを使います。

C#
Shuffle(candidates);

9-3. 再現性が必要ならシード値を指定する

同じ乱数列を再現したい場合は、シード値を指定します。

C#
Random random = new Random(999);

for (int i = 0; i < 5; i++)
{
Console.WriteLine(random.Next(1, 101));
}

テスト、シミュレーション、ランダムマップ生成などに向いています。

9-4. セキュリティ用途ならRandomNumberGeneratorを使う

パスワード、トークン、認証コードなどにはRandomNumberGeneratorを使います。

C#
using System.Security.Cryptography;

int code = RandomNumberGenerator.GetInt32(100000, 1000000);

Randomは使わないようにしましょう。

9-5. 大量生成ならパフォーマンスと重複条件を考慮する

大量の乱数を生成する場合は、次の点を考慮します。

ポイント考え方
インスタンス生成毎回new Random()しない
範囲指定Next(min, max)を使う
重複なし候補数と生成数を事前チェックする
大量シャッフルFisher-Yatesを使う
セキュリティ必要な場合だけRandomNumberGeneratorを使う

通常の大量生成なら、Random.Sharedまたは使い回しのRandomで十分です。

C#
for (int i = 0; i < 100000; i++)
{
int value = Random.Shared.Next(1, 101);
}

重複なしで大量に生成する場合は、HashSetで何度も再抽選するより、候補リストをシャッフルして取り出す方が効率的なことがあります。

10. C#乱数に関するよくある質問

10-1. Random.Nextの最大値は含まれる?

含まれません。

C#
int value = Random.Shared.Next(1, 10);

この場合、生成されるのは1から9です。10は含まれません。Random.Nextの上限値は排他的、つまり含まれない値です。

10-2. 0から1未満の小数を作るには?

NextDouble()を使います。

C#
double value = Random.Shared.NextDouble();

結果は0.0以上1.0未満です。

10-3. 1から100までの乱数を作るには?

Next(1, 101)を使います。

C#
int value = Random.Shared.Next(1, 101);

101は含まれないため、結果は1から100になります。

10-4. 重複しないランダムな番号を作るには?

少数ならHashSetが簡単です。

C#
HashSet<int> numbers = new HashSet<int>();

while (numbers.Count < 5)
{
numbers.Add(Random.Shared.Next(1, 11));
}

候補全体から重複なしで選ぶなら、Listから取り出す方法やシャッフルがおすすめです。

C#
List<int> candidates = Enumerable.Range(1, 10).ToList();
List<int> results = new List<int>();

for (int i = 0; i < 5; i++)
{
int index = Random.Shared.Next(candidates.Count);
results.Add(candidates[index]);
candidates.RemoveAt(index);
}

10-5. 毎回違う乱数にするには?

通常は、シード値を指定せずにRandomを作るか、Random.Sharedを使います。

C#
int value = Random.Shared.Next(1, 101);

また、ループ内で毎回new Random()するのではなく、1つのRandomを使い回しましょう。

10-6. 毎回同じ結果にするには?

シード値を指定します。

C#
Random random = new Random(12345);

Console.WriteLine(random.Next(1, 101));
Console.WriteLine(random.Next(1, 101));
Console.WriteLine(random.Next(1, 101));

同じシード値を使えば、同じ順番で乱数が生成されます。

10-7. パスワード生成にRandomを使ってもよい?

使うべきではありません。

パスワード、トークン、認証コードなど、推測されると危険な値にはRandomNumberGeneratorを使います。

C#
using System.Security.Cryptography;

static string GeneratePassword(int length)
{
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
char[] result = new char[length];

for (int i = 0; i < length; i++)
{
int index = RandomNumberGenerator.GetInt32(chars.Length);
result[i] = chars[index];
}

return new string(result);
}

まとめ

C#で乱数を扱う基本は、Randomクラスの使い方を理解することです。整数の乱数はNext()、小数の乱数はNextDouble()、バイト配列の乱数はNextBytes()で生成できます。

範囲指定では、Next(max)0以上max未満、Next(min, max)min以上max未満になります。最大値を含めたい場合は、基本的にmax + 1を指定します。

重複なしの乱数を作る場合は、HashSetListからの取り出し、Fisher-Yatesシャッフルを使い分けます。特に、生成したい個数が候補数を超えないように事前チェックすることが重要です。

また、Randomはセキュリティ用途には向いていません。パスワード、トークン、認証コードなどには、暗号論的に安全なRandomNumberGeneratorを使いましょう。

C#の乱数は、用途に合った方法を選ぶことで、安全で読みやすく、バグの少ない実装にできます。