__注意: C#初心者なので、とんちんかんなことをいっているかもしれません。__呼び出すたびに5桁のランダムな整数からなるパスワードを生成するCreatePassword
というメソッドを作成。5回連続でこのメソッドを呼び出したところ、5回とも同じパスワードが生成されるという事象が発生しました(´・ω・`)
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(CreatePassword()); // => 83740
Console.WriteLine(CreatePassword()); // => 83740
Console.WriteLine(CreatePassword()); // => 83740
Console.WriteLine(CreatePassword()); // => 83740
Console.WriteLine(CreatePassword()); // => 83740
}
static string CreatePassword()
{
var random = new Random();
List<int> digits = new List<int>();
while (digits.Count < 5)
{
digits.Add(random.Next(10));
}
return string.Join("", digits);
}
}
そこでインターネットの叡智にすがってみました。そもそもSystem.Random
のコンストラクタはRandom()
とRandom(int32)
の2種類があります。後者は引数としてシード値を与えているのですが、引数を省略した場合(つまり前者の場合)、シード値はPCが起動してからの時間を保持するSystem.Environment.TickCount
が使用されます。要するにnew Random()
は内部的にはnew Random(System.Environment.TickCount)
と同じということになります。
問題はこのTickCount
プロパティがミリ秒単位であるということ。つまり1ミリ秒以内に複数のRandom
インスタンスがコンストラクタRandom()
により生成された場合、すべて同じシード値になり、結果として同じ乱数列が生成されてしまいます。
そこで以下のようにsleep
を利用して、Random()
のシード値であるTickCount
の値を無理やり変えてやると、sleep
の前と後ろで乱数列が異なる、つまりシード値が変化したような挙動を見せています。前3つと後3つはそれぞれ1ミリ秒以内に生成され、同じシード値を共有しているのでしょう。(コンパイラの最適化なども「悪さ」をしているような気がしますが、そこまでは追いきれませんでした……)
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(CreatePassword()); // => 38067
Console.WriteLine(CreatePassword()); // => 38067
Console.WriteLine(CreatePassword()); // => 38067
System.Threading.Thread.Sleep(1000); // sleep in 1 sec.
Console.WriteLine(CreatePassword()); // => 72012
Console.WriteLine(CreatePassword()); // => 72012
Console.WriteLine(CreatePassword()); // => 72012
}
static string CreatePassword()
{
var random = new Random();
List<int> digits = new List<int>();
while (digits.Count < 5)
{
digits.Add(random.Next(10));
}
return string.Join("", digits);
}
}
__原因が分かったとして、では冒頭のコードを想定通り動作させるにはどう修正すればよいかですが、不用意にSystem.Random
インスタンスを生成せずに使いまわすというのが手っ取り早そうです。__繰り返すようにRandom()
の乱数らしさは「時間」に依存していますが、この「時間」をプログラム上でコントロールするのは容易ではない。だとすれば「時間」の流れによって変化しないロジック、すなわち初期化された状態のままのインスタンスを使いまわすロジックを採用するのは、そう悪くない戦略だと思います。
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(CreatePassword()); // => 60926
Console.WriteLine(CreatePassword()); // => 54621
Console.WriteLine(CreatePassword()); // => 62504
Console.WriteLine(CreatePassword()); // => 67956
Console.WriteLine(CreatePassword()); // => 91760
}
static Random random = new Random();
static string CreatePassword()
{
List<int> digits = new List<int>();
while (digits.Count < 5)
{
digits.Add(random.Next(10));
}
return string.Join("", digits);
}
}
確かに修正後はCreatePassword
をコールするたびに、ランダムなパスワードが生成されていますね(`・ω・´)シャキーン
参考: