C#の乱数生成を完全理解:Randomクラスの使い方・範囲指定・同じ値になる原因まで解説

はじめに

C#で乱数を生成する基本的な方法は、System.Randomクラスを使うことです。整数、小数、バイト列などを簡単に生成できるため、ゲームの抽選、テストデータの作成、表示順のシャッフル、確率判定など、さまざまな場面で利用できます。

ただし、C#の乱数では「最大値が範囲に含まれない」「短時間にRandomを何度も生成すると同じ値が続くことがある」「パスワード生成にRandomを使ってはいけない」といった注意点があります。

この記事では、C#における乱数生成の基本から、範囲指定、同じ値になる原因、重複なしの抽選、シャッフル、暗号学的に安全な乱数まで、サンプルコードを交えて解説します。

1. C#の乱数生成でできることと基本の考え方

1-1. C#で乱数を使う主な場面

C#の乱数は、次のような処理で利用されます。

  • サイコロやくじ引きの結果を決める

  • ゲーム内の敵、アイテム、マップをランダムに出現させる

  • 配列やリストから要素をランダムに選ぶ

  • データの順番をシャッフルする

  • 一定確率で処理を実行する

  • テスト用の数値や文字列を生成する

  • シミュレーションを行う

一般的なアプリケーションではRandomクラスで十分です。一方、パスワード、認証トークン、秘密鍵など、予測されると危険な値にはRandomNumberGeneratorを使用します。

1-2. Randomクラスとは何か

Randomは、C#で疑似乱数を生成するためのクラスです。名前空間はSystemなので、通常は次のように記述します。

C#
using System;

Random random = new Random();
int value = random.Next();

Console.WriteLine(value);

Randomクラスは、数学的なアルゴリズムによって乱数らしい数値の並びを生成します。生成できる代表的な値は次のとおりです。

  • int型の整数

  • double型の小数

  • ランダムなバイト列

Microsoftの公式ドキュメントでも、Randomは統計的なランダム性の要件を満たす数列を生成する疑似乱数ジェネレーターとして説明されています。Microsoft Learn+1

1-3. 疑似乱数と本当の乱数の違い

コンピューターが計算によって生成する乱数は、厳密には「疑似乱数」です。一定のアルゴリズムと初期値から数値を生成するため、同じ条件を再現すると同じ数列を生成できます。

一方、物理現象など予測困難な外部要因を利用するものは、一般に真性乱数と呼ばれます。

Randomが生成する疑似乱数には、次の特徴があります。

  • 高速に生成できる

  • ゲームやシミュレーションに使いやすい

  • シード値を固定すると結果を再現できる

  • 暗号用途には十分な予測困難性がない

そのため、「ランダムに見えればよい処理」と「予測されてはいけない処理」を区別することが重要です。

1-4. まず覚えるべきRandomクラスの主要メソッド

最初に覚えておきたいメソッドは、次の3つです。

メソッド用途
Next()整数の乱数を生成する
NextDouble()0以上1未満の小数を生成する
NextBytes()バイト配列を乱数で埋める

特に使用頻度が高いのはNext()です。

C#
Random random = new Random();

int number1 = random.Next();
int number2 = random.Next(10);
int number3 = random.Next(1, 11);

それぞれの範囲は次のようになります。

random.Next()       : 0以上 int.MaxValue未満
random.Next(10) : 0以上10未満
random.Next(1, 11) : 1以上11未満、つまり1~10

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

2-1. Randomインスタンスを作成する

Randomを使用するには、最初にインスタンスを作成します。

C#
Random random = new Random();

最近の.NETを使用している場合は、共有インスタンスであるRandom.Sharedも利用できます。

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

Random.Sharedはスレッドセーフな共有インスタンスとして提供されており、単純な乱数生成のたびに自分でインスタンスを管理する必要がありません。Microsoft Learn+1

従来の.NET環境や、シード値を明示的に指定したい場合は、new Random()を使用します。

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

引数なしのNext()は、0以上の非負整数を返します。

C#
Random random = new Random();

int value = random.Next();

Console.WriteLine(value);

上限を指定する場合は、引数を1つ渡します。

C#
int value = random.Next(100);

このコードで生成される値は、0~99です。100は含まれません。

下限と上限を指定する場合は、引数を2つ渡します。

C#
int value = random.Next(10, 20);

生成される値は、10~19です。下限の10は含まれますが、上限の20は含まれません。Next()の上限が排他的であることは、範囲指定を行ううえで最も重要なルールです。Microsoft Learn

2-3. 小数の乱数を生成する:NextDouble()

NextDouble()は、0.0以上1.0未満のdouble型の値を生成します。

C#
Random random = new Random();

double value = random.NextDouble();

Console.WriteLine(value);

結果の例は次のとおりです。

0.384729105

0.0は範囲に含まれますが、1.0は含まれません。Microsoft Learn

0~100未満の小数が必要な場合は、100を掛けます。

C#
double value = random.NextDouble() * 100;

2-4. バイト配列に乱数を入れる:NextBytes()

NextBytes()を使うと、バイト配列の各要素を0~255のランダムな値で埋められます。

C#
Random random = new Random();

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

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

NextBytes()は画像処理、疑似データ作成、アルゴリズムのテストなどで利用できます。ただし、暗号学的に安全なバイト列が必要な場合は、RandomNumberGenerator.GetBytes()を使用してください。Microsoft Learn+1

2-5. サンプルコードで確認する基本パターン

整数、小数、バイト列をまとめて生成する例です。

C#
using System;

class Program
{
static void Main()
{
Random random = new Random();

int integerValue = random.Next(1, 101);
double doubleValue = random.NextDouble();

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

Console.WriteLine($"1~100の整数: {integerValue}");
Console.WriteLine($"0以上1未満の小数: {doubleValue}");
Console.WriteLine($"バイト列: {string.Join(", ", bytes)}");
}
}

同じRandomインスタンスを使い回している点が重要です。

3. C#で乱数の範囲を指定する方法

3-1. 0以上の整数を生成する

引数なしのNext()を使用します。

C#
Random random = new Random();

int value = random.Next();

返される値は0以上int.MaxValue未満です。

3-2. 0以上・指定値未満の整数を生成する

0以上100未満の乱数を生成する場合は、次のように記述します。

C#
int value = random.Next(100);

生成範囲は次のとおりです。

0~99

配列やリストのインデックスを選ぶ場合にも、この形式が便利です。

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

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

LengthCountは要素数を表し、有効な最大インデックスは要素数より1小さいため、上限を含まないNext()と相性がよい設計になっています。

3-3. 最小値以上・最大値未満の整数を生成する

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

C#
int value = random.Next(20, 50);

生成範囲は20~49です。

一般化すると、次の関係になります。

minValue <= 生成値 < maxValue

3-4. 1〜10など任意の範囲で乱数を生成する

1~10をすべて含めたい場合は、上限に11を指定します。

C#
int value = random.Next(1, 11);

同様に、50~100を生成する場合は次のようにします。

C#
int value = random.Next(50, 101);

「最大値を含めたい場合は、通常は最大値に1を加える」と覚えると分かりやすいでしょう。

ただし、最大値がint.MaxValueの場合は、単純に1を加えるとオーバーフローします。非常に大きな範囲を扱う場合は、NextInt64()を利用する方法も検討してください。

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

NextDouble()に計算を加えると、任意の範囲の小数を生成できます。

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

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

この場合、生成範囲は10.0以上20.0未満です。

計算式は次のようになります。

最小値 + 0以上1未満の乱数 ×(最大値 - 最小値)

再利用する場合は、メソッドにまとめると便利です。

C#
static double NextDouble(Random random, double min, double max)
{
if (min > max)
{
throw new ArgumentException("minはmax以下にしてください。");
}

return min + random.NextDouble() * (max - min);
}

3-6. マイナス値を含む範囲で乱数を生成する

Next()には負の最小値も指定できます。

C#
int value = random.Next(-10, 11);

生成範囲は-10~10です。

小数の場合も、通常の範囲指定と同じ計算式を使えます。

C#
double value = -5.0 + random.NextDouble() * 10.0;

生成範囲は-5.0以上5.0未満です。

4. Randomで同じ値が出る原因と対策

4-1. 毎回同じ乱数になる主な原因

乱数が毎回同じになる、または同じような値が繰り返される主な原因は次のとおりです。

  • 同じシード値を明示的に指定している

  • 短時間にRandomインスタンスを何度も作成している

  • メソッドを呼び出すたびにnew Random()している

  • テスト用に固定したシード値を変更していない

  • 偶然同じ値が出ただけなのに異常だと判断している

乱数には重複する可能性があります。サイコロを2回振って同じ目が出るのと同じで、同じ数値が連続しても、それだけで不具合とは限りません。

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

次のようなコードは避けましょう。

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

特に古い.NET Frameworkでは、短時間に作成された複数のRandomが同じ、または近いシードで初期化され、同じ数列を返すことがありました。

現在の.NETでは既定のシード生成方法が改善されていますが、乱数を1つ生成するたびにインスタンスを作る利点はありません。Microsoftも、複数の乱数を生成するときは1つのRandomオブジェクトを繰り返し使用する方法を推奨しています。Microsoft Learn+1

正しくは、ループの外でインスタンスを作成します。

C#
Random random = new Random();

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

4-3. シード値とは何か

シード値とは、疑似乱数の数列を生成するための初期値です。

C#
Random random = new Random(12345);

同じシード値を使い、同じ順序でメソッドを呼び出すと、基本的には同じ数列が生成されます。

シード値の固定は、次のような場面で役立ちます。

  • 単体テストの結果を再現する

  • ゲームのマップを再生成する

  • シミュレーションの条件を比較する

  • 不具合発生時の状況を再現する

4-4. 同じシード値を指定した場合の挙動

次の2つのインスタンスは、同じシード値で初期化されています。

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

for (int i = 0; i < 5; i++)
{
Console.WriteLine($"{random1.Next()} / {random2.Next()}");
}

同じ順番でNext()を呼び出すと、それぞれ同じ値が返されます。

ただし、Randomの内部実装は.NETのメジャーバージョンをまたいで同一であることが保証されていません。そのため、同じシードを指定しても、異なる.NETバージョンで完全に同じ数列になるとは限りません。Microsoft Learn

長期間にわたって完全な再現性が必要な場合は、使用するランタイムのバージョンを固定するか、仕様が固定された独自の乱数アルゴリズムを採用します。

4-5. 同じ値を避けるための正しいRandomの使い回し方

クラス内でRandomを共有する基本形は次のとおりです。

C#
public class Dice
{
private readonly Random _random = new Random();

public int Roll()
{
return _random.Next(1, 7);
}
}

静的メンバーとして保持する方法もあります。

C#
public static class RandomHelper
{
private static readonly Random Random = new Random();

public static int Next(int min, int max)
{
return Random.Next(min, max);
}
}

ただし、同じRandomインスタンスを複数のスレッドから同時に操作する場合は注意が必要です。通常のRandomインスタンスは、複数スレッドからの同時アクセスが保証された設計ではありません。Microsoft Learn

4-6. Random.Sharedを使う方法

利用可能な.NET環境では、Random.Sharedを使うと簡潔に記述できます。

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

ループ内でも、インスタンスを作り直す必要はありません。

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

Random.Sharedは複数のスレッドから利用できるスレッドセーフな共有インスタンスです。通常のアプリケーションでシード値を固定する必要がなければ、扱いやすい選択肢です。Microsoft Learn

5. よく使う乱数生成の実装パターン

5-1. サイコロのように1〜6の乱数を生成する

サイコロの目は1~6なので、上限に7を指定します。

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

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

2個のサイコロを振る場合は、次のようになります。

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

Console.WriteLine($"合計: {dice1 + dice2}");

5-2. 配列やListからランダムに1つ選ぶ

配列からランダムに1つ選ぶ場合は、配列の長さをNext()の上限に指定します。

C#
string[] colors = { "赤", "青", "緑", "黄" };

string selected = colors[Random.Shared.Next(colors.Length)];

Console.WriteLine(selected);

List<T>でも考え方は同じです。

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

string selected = names[Random.Shared.Next(names.Count)];

空の配列やリストに対して実行すると要素を取得できないため、事前に確認します。

C#
if (names.Count == 0)
{
throw new InvalidOperationException("リストが空です。");
}

string selected = names[Random.Shared.Next(names.Count)];

5-3. 文字列からランダムな文字を選ぶ

文字列もインデックスでアクセスできるため、配列と同じ方法で選べます。

C#
string text = "ABCDEFG";

char selected = text[Random.Shared.Next(text.Length)];

Console.WriteLine(selected);

空文字列を受け取る可能性がある場合は、事前に検証します。

C#
static char GetRandomChar(string text)
{
if (string.IsNullOrEmpty(text))
{
throw new ArgumentException("文字列を指定してください。", nameof(text));
}

return text[Random.Shared.Next(text.Length)];
}

5-4. ランダムな英数字文字列を生成する

テストデータなど、セキュリティを必要としない英数字文字列は次のように作成できます。

C#
static string CreateRandomString(int length)
{
const string chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"0123456789";

char[] result = new char[length];

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

return new string(result);
}

使用例は次のとおりです。

C#
string value = CreateRandomString(12);

Console.WriteLine(value);

この方法はダミーデータや一時的な表示用IDには使えますが、パスワード、認証コード、セッショントークンには適していません。

5-5. 確率判定を実装する

NextDouble()が返す0以上1未満の値を利用すると、確率判定を実装できます。

C#
double probability = 0.3;

bool isHit = Random.Shared.NextDouble() < probability;

この例では、約30%の確率でisHittrueになります。

メソッド化する場合は、確率の範囲も検証します。

C#
static bool CheckProbability(double probability)
{
if (probability < 0.0 || probability > 1.0)
{
throw new ArgumentOutOfRangeException(
nameof(probability),
"確率は0以上1以下で指定してください。");
}

return Random.Shared.NextDouble() < probability;
}

パーセント単位で指定したい場合は、100で割ります。

C#
static bool CheckPercent(double percent)
{
if (percent < 0.0 || percent > 100.0)
{
throw new ArgumentOutOfRangeException(nameof(percent));
}

return Random.Shared.NextDouble() < percent / 100.0;
}

5-6. 重複しない乱数を生成する

1~100から重複なしで5個選ぶ場合は、候補をシャッフルして先頭から取得する方法が分かりやすく、安全です。

C#
static List<int> GetUniqueRandomNumbers(
int min,
int max,
int count)
{
if (min > max)
{
throw new ArgumentException("minはmax以下にしてください。");
}

int rangeSize = max - min + 1;

if (count < 0 || count > rangeSize)
{
throw new ArgumentOutOfRangeException(nameof(count));
}

List<int> numbers = Enumerable.Range(min, rangeSize).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]);
}

return numbers.Take(count).ToList();
}

使用例です。

C#
List<int> result = GetUniqueRandomNumbers(1, 100, 5);

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

候補範囲が非常に大きく、選ぶ個数が少ない場合は、HashSet<int>を使う方法もあります。

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

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

ただし、選択数が候補数に近づくほど重複が増え、ループ回数が多くなります。

5-7. リストをランダムにシャッフルする

リストのシャッフルには、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> cards = new()
{
"A",
"B",
"C",
"D",
"E"
};

Shuffle(cards);

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

OrderBy(x => Random.Shared.Next())で並べ替えるコードも見かけますが、シャッフル専用のアルゴリズムではありません。処理効率や意図の明確さを考えると、Fisher–Yates方式が適しています。

6. Randomクラスを使うときの注意点

6-1. Next()の最大値は含まれない

Next(min, max)の最大値は含まれません。

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

このコードの結果は1~9です。10を含めるには、上限を11にします。

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

下限は含み、上限は含まないというルールを覚えておきましょう。

下限 <= 生成値 < 上限

6-2. Randomは暗号用途には向かない

Randomは高速で扱いやすい一方、暗号学的な予測困難性を目的としたクラスではありません。

次の用途では使用しないでください。

  • パスワード

  • パスワードリセット用コード

  • APIキー

  • 認証トークン

  • セッションID

  • 暗号鍵

  • 本人確認用のワンタイムコード

Microsoftも、ランダムなパスワードなど暗号学的に安全な値には、System.Security.Cryptography.RandomNumberGeneratorを使うよう案内しています。Microsoft Learn+1

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

通常のRandomインスタンスを複数スレッドで共有するときは、排他制御が必要です。

C#
private static readonly Random Random = new();
private static readonly object LockObject = new();

static int NextThreadSafe(int min, int max)
{
lock (LockObject)
{
return Random.Next(min, max);
}
}

より簡単な方法は、Random.Sharedを使うことです。

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

Random.Sharedは複数スレッドから利用できるよう設計されています。Microsoft Learn

6-4. テストで再現性を持たせたい場合のシード指定

テストでは、乱数によって結果が毎回変わると、失敗した条件を再現しにくくなります。その場合は、シード値を固定します。

C#
Random random = new Random(1234);

int value1 = random.Next();
int value2 = random.Next();

依存性注入を使い、テスト対象にRandomを渡す方法もあります。

C#
public class NumberGenerator
{
private readonly Random _random;

public NumberGenerator(Random random)
{
_random = random;
}

public int Generate()
{
return _random.Next(1, 101);
}
}

本番では通常のRandomを渡し、テストでは固定シードのRandomを渡します。

C#
NumberGenerator generator =
new NumberGenerator(new Random(1234));

6-5. UnityでRandomを使う場合の注意点

Unityには、C#標準のSystem.Randomとは別に、UnityEngine.Randomがあります。

C#
int value = UnityEngine.Random.Range(1, 11);

UnityのRandom.Range()は、引数の型によって上限の扱いが異なります。

  • int型では最大値を含まない

  • float型では最小値と最大値の両方を含む

C#
// 1~10
int integerValue = UnityEngine.Random.Range(1, 11);

// 0.0f~1.0f。Unityではfloatの上限も範囲に含まれる
float floatValue = UnityEngine.Random.Range(0.0f, 1.0f);

Unity公式ドキュメントでも、整数版は上限を含まず、浮動小数点版は上限を含む仕様として説明されています。Unityドキュメント+1

両方のRandomを同じファイルで使う場合は、完全修飾名を記述すると混同を防げます。

C#
System.Random systemRandom = new System.Random();
int value1 = systemRandom.Next(1, 11);

int value2 = UnityEngine.Random.Range(1, 11);

7. 暗号用途の乱数にはRandomNumberGeneratorを使う

7-1. RandomとRandomNumberGeneratorの違い

RandomRandomNumberGeneratorの主な違いは、用途と予測困難性です。

項目RandomRandomNumberGenerator
主な用途ゲーム、テスト、シミュレーション認証、暗号、セキュリティ
生成速度高速一般にRandomよりコストが高い
シード指定可能通常は利用者が指定しない
再現性持たせられる再現性を目的としない
予測困難性暗号用途には不十分暗号学的に強い

用途に応じて使い分けることが重要です。

7-2. セキュリティが必要な場面とは

第三者に値を推測されると問題になる処理では、セキュリティが必要です。

たとえば、次の値が予測されると、アカウントの乗っ取りや不正アクセスにつながる可能性があります。

  • ログイン用の一時コード

  • メール認証用トークン

  • パスワードリセット用トークン

  • 招待コード

  • セッション識別子

  • 暗号処理に使う鍵やソルト

このような値は、RandomNumberGeneratorで生成します。

7-3. RandomNumberGeneratorで整数の乱数を生成する

RandomNumberGenerator.GetInt32()を使うと、暗号学的に強い整数乱数を生成できます。

C#
using System.Security.Cryptography;

int value = RandomNumberGenerator.GetInt32(100);

生成範囲は0~99です。

下限と上限も指定できます。

C#
int value = RandomNumberGenerator.GetInt32(1, 11);

生成範囲は1~10です。Random.Next()と同じように、下限は含まれ、上限は含まれません。Microsoft Learn+1

6桁の確認コードを生成する例です。

C#
int code = RandomNumberGenerator.GetInt32(0, 1_000_000);
string formattedCode = code.ToString("D6");

Console.WriteLine(formattedCode);

D6を指定しているため、値が123なら000123として出力されます。

7-4. パスワード・トークン生成で注意すべきこと

ランダムなトークンは、十分なバイト数を確保して生成します。

C#
using System.Security.Cryptography;

byte[] bytes = RandomNumberGenerator.GetBytes(32);
string token = Convert.ToBase64String(bytes);

Console.WriteLine(token);

URLに含めやすい形式にする場合は、Base64URL形式に変換できます。

C#
byte[] bytes = RandomNumberGenerator.GetBytes(32);

string token = Convert.ToBase64String(bytes)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');

パスワードを自動生成するときは、単に長さを増やすだけでなく、次の点も検討します。

  • 十分な文字数を確保する

  • 使用可能な文字集合を適切に選ぶ

  • 人間が入力する場合は紛らわしい文字を除外する

  • 生成後の保存方法にも注意する

  • パスワードは平文保存せず、適切なパスワードハッシュを使用する

また、バイト値を文字数で単純に割った余りで文字を選ぶと、文字数によっては偏りが生じます。整数範囲を安全に生成できるRandomNumberGenerator.GetInt32()を使うと、実装ミスを避けやすくなります。

C#
using System.Security.Cryptography;

static string CreateSecureString(int length)
{
const string chars =
"ABCDEFGHJKLMNPQRSTUVWXYZ" +
"abcdefghijkmnopqrstuvwxyz" +
"23456789";

char[] result = new char[length];

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

return new string(result);
}

7-5. 用途別にRandomとRandomNumberGeneratorを使い分ける

判断基準は、「生成した値が予測されても問題がないか」です。

Randomが適している例は次のとおりです。

  • サイコロ

  • ゲーム内の抽選

  • 敵の行動選択

  • 表示順のシャッフル

  • テストデータ

  • シミュレーション

RandomNumberGeneratorが適している例は次のとおりです。

  • 認証コード

  • パスワード

  • リセットトークン

  • 招待トークン

  • セッションID

  • 暗号鍵やソルト

迷った場合は、セキュリティに関係する値かどうかで判断します。

8. C#の乱数生成でよくあるエラー・疑問

8-1. Next(1, 10)で10が出ないのはなぜか

Next(1, 10)の第2引数は、範囲に含まれない上限だからです。

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

生成される値は1~9です。

10を含めたい場合は、次のように記述します。

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

8-2. 毎回違う結果にするにはどうすればよいか

通常は、シードを指定せずにRandomを1回だけ作成し、そのインスタンスを使い回します。

C#
Random random = new Random();

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

利用可能な環境では、Random.Sharedを使用するとさらに簡単です。

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

ただし、乱数である以上、偶然同じ値が出る可能性はあります。「毎回必ず異なる値」が必要なら、通常の乱数生成ではなく、重複を管理する処理が必要です。

8-3. 毎回同じ結果にするにはどうすればよいか

固定シードを指定します。

C#
Random random = new Random(12345);

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

プログラムを再実行しても、同じ環境で同じ順番にメソッドを呼び出せば、同じ数列を再現できます。

ただし、異なる.NETのメジャーバージョン間では、同じ数列が保証されない点に注意してください。Microsoft Learn

8-4. 乱数が偏って見えるのはなぜか

乱数は、短い回数では均等に見えないことがあります。

たとえば、サイコロを6回振ったからといって、1~6が1回ずつ出るとは限りません。同じ目が続くこともあります。

分布を確認する場合は、十分な回数を試します。

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

for (int i = 0; i < 1_000_000; 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]}回");
}

回数を増やすと、それぞれの出現数はおおむね近づきます。ただし、完全に同じ回数になるわけではありません。

また、次のような実装上の問題も偏りの原因になります。

  • 範囲指定を間違えている

  • 複数のRandomを不適切に生成している

  • 生成後の値に偏った変換を加えている

  • シャッフルのアルゴリズムが適切でない

  • サンプル数が少なすぎる

8-5. double型やfloat型の乱数はどう作るのか

double型はNextDouble()で生成できます。

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

生成範囲は0.0以上1.0未満です。

任意の範囲にする場合は、次の計算を行います。

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

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

float型が必要な場合は、キャストします。

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

任意の範囲にする場合は次のとおりです。

C#
float min = 5.0f;
float max = 10.0f;

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

利用している.NETのバージョンによっては、NextSingle()も使用できます。

C#
float value = Random.Shared.NextSingle();

8-6. 重複なしでランダムに並べ替えるにはどうするのか

要素を重複なしで並べ替える場合は、リスト自体をシャッフルします。

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<int> numbers =
Enumerable.Range(1, 10).ToList();

Shuffle(numbers);

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

もとのリストに重複がなければ、シャッフル後も重複はありません。抽選で一度選ばれた要素を再び選びたくない場合は、シャッフルしたリストを先頭から順番に取り出す方法が分かりやすいでしょう。

まとめ

C#で一般的な乱数を生成するときは、RandomクラスまたはRandom.Sharedを使用します。

整数の範囲指定では、次のルールが基本です。

Random.Next(min, max)
minは含む
maxは含まない

1~10の乱数を生成する場合は、上限を11にします。

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

小数はNextDouble()で生成できます。

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

乱数を繰り返し生成するときは、ループ内で毎回new Random()するのではなく、1つのインスタンスを使い回すか、Random.Sharedを使用します。

また、固定シードを指定すればテスト結果を再現できますが、パスワードや認証トークンなどのセキュリティ用途にはRandomを使ってはいけません。暗号学的に安全な乱数が必要な場合は、RandomNumberGeneratorを使用します。

用途に応じてRandomRandomNumberGeneratorを正しく使い分ければ、ゲーム、抽選、シャッフル、確率判定、テスト、認証処理などを安全かつ分かりやすく実装できます。