C#中"静态"Random.Next的正确使用方法是什么?

33

如果我想创建一个 1 到 100 之间的随机数,为什么需要创建 Random 类的实例?

Random rand = new Random();
rand.Next(1,100);

Random类中是否有任何静态函数可以做同样的事情?如...

Random.Next(1,100);
我不想不必要地创建一个实例。

16
最后总结一下,不要过早优化。 :-) (注:此处英文表情符号的意思是微笑) - user166390
可能是为什么System.Random类不是静态的?的重复问题。 - Michael Freidgeim
11个回答

36

最佳实践是创建单个Random实例并在整个程序中使用它,否则结果可能不太随机。通过不创建静态函数来鼓励这种行为。

您不必担心“不必要地创建实例”,最好情况下影响可以忽略不计 - 这是框架的工作方式。


13
如果Random是静态的,这个目标是否不能实现?在构建时生成器将被种子化并且可以贯穿使用。 - Asher
2
@Asher - 那需要一个内部实例,并且需要状态。我认为它不应该作为框架的一部分,但是可以很容易地在您的静态类上实现。 - Kobi
2
@Kobi 或许我应该发布自己的问题 :). 抛开内存使用和性能不谈,我确实觉得实例化这个类很烦人。如果你只需要用一个类,为什么不把它设为静态呢?这是我没理解到的静态类的问题吗?无论如何,我离题了。 - Asher
2
考虑可重用性 - 觉得你只调用它一次,但是有一天可能会有人调用您的函数1000次,导致失去随机性。如果实例化类令人讨厌,也许C#并不太适合您 :) - Kobi
7
@Asher :但Random不是线程安全的,因此如果你想从多个线程调用Random,那么这将无法工作...另外,如果您想要为Random类使用多个种子怎么办?例如,我希望使用多个线程在每次执行时产生相同的结果,这就要求我使用具有相同种子的多个实例,使用静态方法会导致竞争。 - Ian
显示剩余5条评论

33
//Function to get random number
private static readonly Random random = new Random();
private static readonly object syncLock = new object();
public static int RandomNumber(int min, int max)
{
    lock(syncLock) { // synchronize
        return random.Next(min, max);
    }
}

直接复制自


5
这个做得很好,避免了像 Evgeny 回答中所提到的误用类的情况。 +1 引用代码,功劳要归于原作者 :) - Codesleuth

7

这并不是“不必要的”,因为Random类在内部存储了一些状态。它这样做是为了确保如果您在非常短的时间内(在同一毫秒或滴答声等)多次调用.Next(),您仍然不会得到相同的数字。

当然,如果在您的情况下这不是问题,您可以将这两行代码合并成一行:

new Random().Next(1, 100);

9
这样使用随机数很可能会有问题……即使现在没有问题,如果有人实现了每50毫秒调用一次的随机数生成器,并且因为它能正常工作而使用它,那么一旦硬件变得更快,它将很快出问题,这将导致大量的调试时间消耗 :) - Marek
真实,所以我永远不会在循环中使用这种代码 - 但如果你知道你的程序只需要生成一个随机数,那么它就足够安全。 - EMP
5
好的,使用这个来获取单个物品。不要使用其他方式。有诱惑就会点踩。 - nawfal

6

你已经在这里得到了答案。再重申一下正确的解决方案

namespace mySpace
{
    public static class Util
    {
        private static Random rnd = new Random();
        public static int GetRandom()
        {
            return rnd.Next();
        }
    }
}

所以你可以调用:

var i = Util.GetRandom();

在整个过程中,如果你需要一个真正的无状态静态方法来生成随机数,可以依赖于 Guid

public static class Util
{
    public static int GetRandom()
    {
        return Guid.NewGuid().GetHashCode();
    }
}

虽然可能会稍微慢一些,但是根据我的经验,它比Random.Next更加随机。

但是不是

new Random(Guid.NewGuid().GetHashCode()).Next();

不必要的对象创建会使代码变慢,特别是在循环中。

而且绝不能

new Random().Next();

在循环内使用它不仅会变慢,而且我认为它的随机性也不是很好。


2
Guid.GetHashCode不能保证任何形式的随机性。使用它是一种冒险。我想把它包装在一个漂白函数中,比如new Random... - usr
@usr 我知道 Guid.NewGuid 旨在保证唯一性而非随机性,但其哈希值 (Guid.GetHashCode) 却相当随机。这是因为 Guid.GetHashCode 基于 Guid 的字符串表示形式。如果你说字符串的哈希码实现未被记录,因此无法保证其行为,我同意,但实际上它相当随机,即缺乏可预测的模式。 - nawfal
1
你的第一个例子不是线程安全的。假设有多个线程调用静态方法,那么就会出现问题。.Next() 不是线程安全的,在并发使用下可能会抛出 IndexOutOfRange 异常 - Lilith River

5
最好的方法是拥有一个ThreadStatic Random实例:
[ThreadStatic] static Random random;

Random Get() {
 if (random == null) random = new Random(Guid.NewGuid().GetHashCode());
 return random;
}

这个可以照顾到所有事情

  1. 线程安全性
  2. 性能
  3. 无需种子

我不明白为什么.NET Framework(以及地球上的任何其他框架)不使用类似的东西。


ThreadStatic(或ThreadLocal)本身会产生开销,因此它并不能处理“所有事情”。 - user2864740
1
这是真的...但它比每次创建新的Random实例要便宜得多。这种方法与传递Random引用之间的差异非常小。CoreClr最近进行了更改,以执行此答案中所述的操作,以便为执行new Random()时提供随机种子。@user2864740 - usr
我使用这种方法,它运行良好且易于理解;只是表现出了我对普遍性的追求。 - user2864740
1
我不反对那种行为 :) - usr

5

来自MSDN:Random Class (System)

“随机数生成从种子值开始。如果重复使用相同的种子,则生成相同的数字序列。产生不同序列的一种方法是使种子值依赖于时间,从而在每个新的 Random 实例中产生不同的序列。 默认情况下,Random 类的无参数构造函数使用系统时钟生成其种子值,而其参数化构造函数可以使用当前时间中的滴答数基于一个 Int32 值来生成种子值。 然而,由于时钟具有有限的分辨率,使用无参数构造函数在短时间内创建不同的 Random 对象会创建产生相同随机数序列的随机数生成器。以下示例说明了在短时间内实例化的两个 Random 对象将生成相同的随机数序列…”

Wikipedia 解释PRNGs


3

为什么不呢?

你需要创建一个实例,因为随机数生成的方式是前面的答案会影响后续的答案。默认情况下,new Random()构造函数使用当前系统时间来“种子”序列,但这并不是必须的:如果你愿意,可以传入自己的数字。特别地:

var rand = new Random(1234);
Console.WriteLine(rand.Next(0, 100));
Console.WriteLine(rand.Next(0, 100));
Console.WriteLine(rand.Next(0, 100));
Console.WriteLine(rand.Next(0, 100));

每次都会产生相同的“随机”数序列。

这意味着Random类需要保留实例数据(先前的答案或“种子”)以供后续调用使用。


3
创建一个新的随机实例,然后立即多次调用它,例如:
for (int i = 0; i < 1000; i++)
{
     Random rand = new Random();
     Console.WriteLine(rand.Next(1,100));
}    

我会给你一个偏向于范围低端的分布。

这样做:

Random rand = new Random();
for (int i = 0; i < 1000; i++)
{
     Console.WriteLine(rand.Next(1,100));
}    

将为您提供更好的分发。


谢谢。不知道这个。 - Mariusz

1
在C#中创建一个短暂的实例几乎是免费的。不要浪费时间担心这个问题。你可能有更好的地方来寻找性能或内存增益。

1

随机数生成器必须保持状态才能保证“随机性”。随机数生成器创建的序列是基于随机种子生成的。问题在于计算机中没有真正的随机性。计算机最接近的东西就是系统时钟;这实际上是进程发生的时间。因此,默认情况下使用系统时钟的当前滴答计数。如果您的应用程序足够快,则可能在同一系统滴答下进行许多随机数计算。如果随机数生成器根本不保持状态,则会多次提供相同的随机数(相同的输入产生相同的输出)。这通常不是您想要的。

我知道这个问题已经有答案了,但我必须说我更喜欢在这种情况下使用单例模式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接