C#でのランダム数生成
[[academy-video-youtube({"vid": "GFLPncbEi-w", "start_time": "0", "title": "C#でランダムな数を生成する", "creator": "Tim Corey", "length": "10m 05s"})]]
C#でランダムな数値を生成することは、ワンライナーであるべきだと考えられますが、多くの場合そうです。 しかし、言語はランダムな値を生成するための複数の方法を提供しており、スレッドセーフ性、再現可能性、使用ケースを考慮に入れるとその違いが重要になります。 誤ったアプローチを選ぶと、マルチスレッドのコードで微妙なバグを引き起こしたり、報告された欠陥を再現不可能にすることがあります。
彼のビデオ"C#でランダムな数字を生成する"では、Tim Corey が古典的なRandom.Sharedを紹介します。 各アプローチとその背後にある理由を扱いますので、推測することなく正しいものを選ぶことができます。 なぜそんなに単純に聞こえるものに複数の道があるのか不思議に思ったことがあるならば、この記事はそれを解き明かします。
デモの設定
[0:11 - 0:59] TimはVisual Studio 2026(現在プレビュー中)で.NET 10を使って実行しているコンソールアプリケーション内で作業しています。彼は、ここで示されたすべてが.NET 9およびVisual Studio 2022に同様に適用されることを指摘していますので、既にインストールされているツールを使用してフォローすることができます。
デモのレイアウトは、各イテレーションで2つのランダムな値を並べて出力するforループです。 平行して実行される2つの出力を持つことで、両方のジェネレーターが独立して動作するか、同じ結果をもたらすかを観察するのが簡単になりますが、シード値が関与するとその区別が重要になります。
クラシックなRandomクラス
[0:59 - 3:28] C#でランダムな整数を生成する元のアプローチは、Randomクラスのインスタンスを作成することです。
Random rng1 = new Random();
Random rng2 = new Random();Random rng1 = new Random();
Random rng2 = new Random();各インスタンスは独自の内部状態を維持します。 どちらかに.Next(1, 101)を呼び出すと、1から100の間の整数が生成されます。Timは初心者がつまずきやすい詳細を強調します: 最小値は含まれますが、最大値は含まれません。1から100までの値が欲しい場合は、100ではありません。
int output1 = rng1.Next(1, 101);
int output2 = rng2.Next(1, 101);int output1 = rng1.Next(1, 101);
int output2 = rng2.Next(1, 101);アプリケーションを実行すると、両方のインスタンスが異なるシーケンスを生成することが確認されます。 その結果は直感的に感じられますが、両方のインスタンスが同じ開始点を共有すると何が起こるかは別の話を語ります。
このアプローチに関する重要な注意点: 個々のRandomインスタンスはスレッドセーフではありません。 アプリケーションが並列処理を行い、複数のスレッドが同じインスタンスにアクセスする場合、内部状態が破損し、0や繰り返しの値を生成する可能性があります。 安全な方法はスレッドごとに1つのインスタンスを作成することです。 この制約が、後に言語がより良い代替案を導入した理由の一つです。
シード値と再現可能なシーケンス
[3:28 - 6:00] Timは次に、両方のコンストラクターに明示的なシードを渡します:
Random rng1 = new Random(25);
Random rng2 = new Random(25);Random rng1 = new Random(25);
Random rng2 = new Random(25);出力が劇的に変わります。 両方のジェネレーターが今や同一のシーケンスを生成します:79, 16, 25, 90, 50, 41など。 シードを知らない場合、数値は依然として個々には予測不可能ですが、同じ開始値を考慮に入れると、進行は決定的です。
なぜ誰かこれを望むのでしょうか? Timは実践的な例を示します。 あるゲームがセッション全体でランダムなイベントを生成すると想像してください。 プレイヤーがバグを報告しますが、結果がランダム化されているため再現が不可能に感じられます。 ゲームがそのセッションに使用したシードをログに記録していれば、開発者は同じ値で新しいRandomインスタンスを初期化することで、正確な決定の連鎖を再現できます。 同様の論理が、一貫した出力を必要とし、ランダム化された動作に対して信頼性のあるアサーションを書く必要がある単体テストのシナリオにも適用されます。
シードされたインスタンスは、制御されたランダム性を提供します:一見予測不可能に見えるが、要求に応じてリプレイできるシーケンス。 この機能は、シードを受け入れる古典的なRandomコンストラクタが、よりシンプルなAPIが存在しても廃止されていない理由です。
Random.Shared: モダンなデフォルト
[7:36 - 9:01] .NET 6以降、ほとんどのランダム数生成における推奨されるアプローチはRandom.Sharedです。
int output1 = Random.Shared.Next(1, 101);
int output2 = Random.Shared.Next(1, 101);int output1 = Random.Shared.Next(1, 101);
int output2 = Random.Shared.Next(1, 101);インスタンス化は不要です。 Randomクラスの他のメソッド)を呼び出すと、オブジェクトのライフタイムや同時実行性を気にせずに値を受け取ることができます。
Timはその証明のためにデモを2回実行します。 最初の実行は94と91から始まります。 2番目のものは42と70から始まります。シードがあるインスタンスとは異なり、Random.Sharedはプロセスが開始されるたびに異なる開始状態から引き出します。 シードを設定することはできません。つまり、このAPIを介して再現可能なシーケンスを生成することはできません。 それがトレードオフです:決定的なリプレイを放棄する代わりに、シンプルさと安全性。
Random.Sharedは倍精度数を生成し、バイト配列を埋め、コレクションをシャッフルするためのメソッドを公開します。 迅速なランダム値を必要とするアプリケーションコードの大部分にとって、この単一の静的プロパティは、独自のインスタンスを管理するボイラープレートを置き換えます。
正しいアプローチの選択
[9:01 - 9:30] Timは簡潔な決定フレームワークで締めくくります。 日常のランダム性(値を選択する、リストをシャッフルする、ランダム要素を選択する)に関しては、Random.Sharedが正解です。 セットアップ不要で、同時実行を処理し、スレッド全体で正しく動作します。
デバッグ、テスト、またはシミュレーションの再生用に繰り返し可能な出力シーケンスが必要な場合は、既知のシードで専用のRandomインスタンスを作成します。 これらのインスタンスをスレッド間で共有することは安全ではないことを忘れないでください。
そして、セキュリティに関わる何か(トークン、キー、パスワードの塩分)には、どちらのアプローチも適していません。 Timは視聴者をSystem.Security.Cryptographyの暗号ライブラリに案内し、それがランダムであるだけでなく、予測に対して耐性がある値を生成することを示します。
締めくくり:シンプルなAPI、意味深い違い
[9:30 - 9:50] このトピックを誤解しやすいのは、どのアプローチでもそれほど多くのコードがかからないからです。 1行だけでランダムな数を生成することができます。 複雑さは構文ではなく、各メソッドが提供する保証、つまりスレッドセーフ性、再現可能性、または暗号強度を理解することにあります。
結論
[9:50 - 10:05] 要約すると: Random.Sharedはゼロセットアップと組み込みのスレッドセーフを備え、ほとんどのニーズをカバーします。 シード付きのRandomインスタンスは、デバッグやテストで特定のシーケンスを再現する必要がある場合に役立ちます。 暗号生成は予測可能性が脆弱性であり、機能ではないセキュリティに敏感なコードに属します。
次回C#でランダムな数を求めるとき、その決定は1つの質問にかかっています:このシーケンスを後で再生する必要がありますか? 答えがいいえであれば、Random.Sharedがあなたが必要とするすべてです。
例のヒント: 101を渡す必要があります。 このオフバイワン境界はシードされたインスタンスにも適用されます。

