If you want a true random number generator, you will want to look beyond System.Random, to the RNGCryptoServiceProvider in the .NET Framework. In fact, there's a good article here that shows how to get a strong password using this method.
But how do you ensure randomness using the System.Random class?
Creating truly random numbers can be tricky, especially if you need to create a series of random numbers repeatedly. It’s easy to misunderstand, in fact, you really can’t even trust the code samples from MSDN Documentation for the System.Random Constructor. Here’s the crux of this issue.
If your application requires different random number sequences,
invoke this constructor repeatedly with different seed values. One way
to produce a unique seed value is to make it time-dependent. For
example, derive the seed value from the system clock.
However,
if your application runs on a fast computer the system clock might not
have time to change between invocations of this constructor; the seed
value might be the same for different instances of Random. In that
case, apply an algorithm to differentiate the seed value in each
invocation.
The documentation goes on to show some code that
supposedly will generate some random numbers, by calling
Thread.Sleep(1). However, this won’t work! It may work in
their example, but in fact, the only reason I think this works in
the MSDN Documentation example is by chance! They just
happen to be using enough cpu cycles in a loop (and thus
getting a new, unique timer tick) to make the example work!
Consider the following code:
static internal string RandomString(int size)
{
Thread.Sleep(1);
Random rand = new System.Random();
StringBuilder builder = new StringBuilder();
for(int i=0; i<size; i++)
{
char ch = Convert.ToChar(Convert.ToInt32(26 * rand.NextDouble() + 65)) ;
if(ch < 0x5B || ch > 0x60) builder.Append(ch);
}
return builder.ToString();
}
If you run this test below, it fails (and not just because of the by
chance occurance of two truly random strings actually being equal), but
because Thread.Sleep(1) isn’t long enough to generate a new random seed
for the default System.Random() constructor.
public class RandomStringObject
{
public RandomStringObject()
{
this.s = RandomString(20);
}
private string s;
public string S
{
get { return s; }
}
}
[Test]
public void TestRandomString()
{
ArrayList randomStringsObjects = new ArrayList();
for(int i = 0; i < 2000; i ++)
{
RandomStringObject rso = new RandomStringObject();
Assert.IsFalse(randomStringsObjects.Contains(rso.S));
randomStringsObjects.Add(rso.S);
}
}
In order to get this test to pass, you have to increase the
Thread.Sleep time, but isn’t really a good solution,
since it’s bound to introduce slowness into your code. A better
solution, is to create the Random object once, and simply use it each
time you generate a new random number, see these posts here and here for
an overview of this. Unfortunately, this isn’t
possible from a static contex (within a constructor for example) since
it won’t have access to the object, unless of course you pass in a
reference to the Random object,
which isn’t really a good solution either.
A perfect solution? Well, I’m not sure if there is one,
but I did some experimentation, and found that the Hash Code of
a Guid (generated
by using Guid.NewGuid().GetHashCode()) creates a fairly
unique integer, and we can use this as our random seed.
static internal string RandomString(int size)
{
Random rand = new System.Random(Guid.NewGuid().GetHashCode());
StringBuilder builder = new StringBuilder();
char ch ;
for(int i=0; i<size; i++)
{
ch = Convert.ToChar(Convert.ToInt32(26 * rand.NextDouble() + 65)) ;
if(ch < 0x5B || ch > 0x60) builder.Append(ch);
}
return builder.ToString();
}
This code above does a much better job generating the random
strings. Is this useful in any way? I’m not quite sure. Like I said, you'll want to use RNGCryptoServiceProvider for security
purposes, like password generation. I guess the big take away from all this is that
you may think you’re code is being random, when it’s actually being
very un-random, so watch out! That is so random!
Brendan