使用英语阅读

通过


System.Random 类

本文提供了此 API 参考文档的补充说明。

Random 类表示伪随机数生成器,该生成器是一种算法,该算法生成满足特定随机性统计要求的数字序列。

从有限的一组数字中选择伪随机数,概率相等。 所选的数字不是完全随机的,因为数学算法用于选择它们,但它们足够随机,以实现实际目的。 该类的 Random 实现基于 Donald E. Knuth 的减数随机数生成器算法的修改版本。 有关详细信息,请参阅 D. E. Knuth。 计算机编程的艺术,第 2 卷:半数值算法。 艾迪森-韦斯利,雷丁,MA,第三版,1997年。

若要生成加密安全的随机数(例如适合创建随机密码的随机数字),请使用类中的 System.Security.Cryptography.RandomNumberGenerator 一种静态方法。

实例化随机数生成器

通过将种子值(伪随机数生成算法的起始值) Random 提供给类构造函数来实例化随机数生成器。 可以显式或隐式提供种子值:

  • 构造 Random(Int32) 函数使用你提供的显式种子值。
  • 构造 Random() 函数使用默认种子值。 这是实例化随机数生成器的最常用方法。

在 .NET Framework 中,默认种子值依赖于时间。 在 .NET Core 中,默认种子值由线程静态伪随机数生成器生成。

如果同一种子用于单独的 Random 对象,它们将生成相同的随机数系列。 这可用于创建处理随机值的测试套件,或者用于重播从随机数派生其数据的游戏。 但是,请注意 Random ,在不同版本的 .NET Framework 下运行的进程中的对象可能会返回随机数的不同系列,即使它们使用相同的种子值实例化也是如此。

若要生成不同的随机数序列,可以使种子值与时间相关,从而生成具有每个新实例 Random的不同序列。 参数化 Random(Int32) 构造函数可以根据当前时间的刻度数获取值 Int32 ,而无 Random() 参数构造函数使用系统时钟生成其种子值。 但是,仅在 .NET Framework 上,因为时钟具有有限的分辨率,因此使用无参数构造函数在紧密连续中创建不同的 Random 对象会创建生成相同随机数序列的随机数生成器。 以下示例演示了 .NET Framework 应用程序中连续实例化的两 Random 个对象如何生成相同的随机数系列。 在大多数 Windows 系统上, Random 在彼此 15 毫秒内创建的对象可能具有相同的种子值。

C#
byte[] bytes1 = new byte[100];
byte[] bytes2 = new byte[100];
Random rnd1 = new Random();
Random rnd2 = new Random();

rnd1.NextBytes(bytes1);
rnd2.NextBytes(bytes2);

Console.WriteLine("First Series:");
for (int ctr = bytes1.GetLowerBound(0);
     ctr <= bytes1.GetUpperBound(0);
     ctr++) {
   Console.Write("{0, 5}", bytes1[ctr]);
   if ((ctr + 1) % 10 == 0) Console.WriteLine();
}

Console.WriteLine();

Console.WriteLine("Second Series:");
for (int ctr = bytes2.GetLowerBound(0);
     ctr <= bytes2.GetUpperBound(0);
     ctr++) {
   Console.Write("{0, 5}", bytes2[ctr]);
   if ((ctr + 1) % 10 == 0) Console.WriteLine();
}

// The example displays output like the following:
//       First Series:
//          97  129  149   54   22  208  120  105   68  177
//         113  214   30  172   74  218  116  230   89   18
//          12  112  130  105  116  180  190  200  187  120
//           7  198  233  158   58   51   50  170   98   23
//          21    1  113   74  146  245   34  255   96   24
//         232  255   23    9  167  240  255   44  194   98
//          18  175  173  204  169  171  236  127  114   23
//         167  202  132   65  253   11  254   56  214  127
//         145  191  104  163  143    7  174  224  247   73
//          52    6  231  255    5  101   83  165  160  231
//
//       Second Series:
//          97  129  149   54   22  208  120  105   68  177
//         113  214   30  172   74  218  116  230   89   18
//          12  112  130  105  116  180  190  200  187  120
//           7  198  233  158   58   51   50  170   98   23
//          21    1  113   74  146  245   34  255   96   24
//         232  255   23    9  167  240  255   44  194   98
//          18  175  173  204  169  171  236  127  114   23
//         167  202  132   65  253   11  254   56  214  127
//         145  191  104  163  143    7  174  224  247   73
//          52    6  231  255    5  101   83  165  160  231

若要避免此问题,请创建一 Random 个对象而不是多个对象。 请注意, Random .NET Core 中的类没有此限制。

避免多个实例化

在 .NET Framework 上,在紧密循环中初始化两个随机数生成器或快速连续创建两个随机数生成器,这些随机数生成器可以生成相同的随机数序列。 在大多数情况下,这不是开发人员的意图,可能会导致性能问题,因为实例化和初始化随机数生成器是一个相对昂贵的过程。

为了提高性能并避免无意中创建生成相同数字序列的单独随机数生成器,我们建议创建一个 Random 对象来生成一段时间内的多个随机数,而不是创建新的 Random 对象来生成一个随机数。

但是,该 Random 类不是线程安全的。 如果从多个线程调用 Random 方法,请按照下一部分中讨论的准则进行操作。

线程安全性

建议创建单个Random实例来生成应用所需的所有随机数,而不是实例化单个Random对象。 但是, Random 对象不是线程安全的。 如果应用从多个线程调用 Random 方法,则必须使用同步对象来确保一次只能有一个线程访问随机数生成器。 如果不确保 Random 以线程安全的方式访问对象,则调用返回随机数的方法将返回 0。

以下示例使用 C# lock 语句、F# 锁函数 和 Visual Basic SyncLock 语句 来确保以线程安全的方式访问 11 个线程访问单个随机数生成器。 每个线程生成 200 万个随机数,对生成的随机数进行计数并计算其总和,然后在完成执行时更新所有线程的总和。

C#
using System;
using System.Threading;

public class Example13
{
    [ThreadStatic] static double previous = 0.0;
    [ThreadStatic] static int perThreadCtr = 0;
    [ThreadStatic] static double perThreadTotal = 0.0;
    static CancellationTokenSource source;
    static CountdownEvent countdown;
    static Object randLock, numericLock;
    static Random rand;
    double totalValue = 0.0;
    int totalCount = 0;

    public Example13()
    {
        rand = new Random();
        randLock = new Object();
        numericLock = new Object();
        countdown = new CountdownEvent(1);
        source = new CancellationTokenSource();
    }

    public static void Main()
    {
        Example13 ex = new Example13();
        Thread.CurrentThread.Name = "Main";
        ex.Execute();
    }

    private void Execute()
    {
        CancellationToken token = source.Token;

        for (int threads = 1; threads <= 10; threads++)
        {
            Thread newThread = new Thread(this.GetRandomNumbers);
            newThread.Name = threads.ToString();
            newThread.Start(token);
        }
        this.GetRandomNumbers(token);

        countdown.Signal();
        // Make sure all threads have finished.
        countdown.Wait();
        source.Dispose();

        Console.WriteLine("\nTotal random numbers generated: {0:N0}", totalCount);
        Console.WriteLine("Total sum of all random numbers: {0:N2}", totalValue);
        Console.WriteLine("Random number mean: {0:N4}", totalValue / totalCount);
    }

    private void GetRandomNumbers(Object o)
    {
        CancellationToken token = (CancellationToken)o;
        double result = 0.0;
        countdown.AddCount(1);

        try
        {
            for (int ctr = 0; ctr < 2000000; ctr++)
            {
                // Make sure there's no corruption of Random.
                token.ThrowIfCancellationRequested();

                lock (randLock)
                {
                    result = rand.NextDouble();
                }
                // Check for corruption of Random instance.
                if ((result == previous) && result == 0)
                {
                    source.Cancel();
                }
                else
                {
                    previous = result;
                }
                perThreadCtr++;
                perThreadTotal += result;
            }

            Console.WriteLine("Thread {0} finished execution.",
                              Thread.CurrentThread.Name);
            Console.WriteLine("Random numbers generated: {0:N0}", perThreadCtr);
            Console.WriteLine("Sum of random numbers: {0:N2}", perThreadTotal);
            Console.WriteLine("Random number mean: {0:N4}\n", perThreadTotal / perThreadCtr);

            // Update overall totals.
            lock (numericLock)
            {
                totalCount += perThreadCtr;
                totalValue += perThreadTotal;
            }
        }
        catch (OperationCanceledException e)
        {
            Console.WriteLine("Corruption in Thread {1}", e.GetType().Name, Thread.CurrentThread.Name);
        }
        finally
        {
            countdown.Signal();
        }
    }
}
// The example displays output like the following:
//       Thread 6 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 1,000,491.05
//       Random number mean: 0.5002
//
//       Thread 10 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,329.64
//       Random number mean: 0.4997
//
//       Thread 4 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 1,000,166.89
//       Random number mean: 0.5001
//
//       Thread 8 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,628.37
//       Random number mean: 0.4998
//
//       Thread Main finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,920.89
//       Random number mean: 0.5000
//
//       Thread 3 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,370.45
//       Random number mean: 0.4997
//
//       Thread 7 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,330.92
//       Random number mean: 0.4997
//
//       Thread 9 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 1,000,172.79
//       Random number mean: 0.5001
//
//       Thread 5 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 1,000,079.43
//       Random number mean: 0.5000
//
//       Thread 1 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,817.91
//       Random number mean: 0.4999
//
//       Thread 2 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,930.63
//       Random number mean: 0.5000
//
//
//       Total random numbers generated: 22,000,000
//       Total sum of all random numbers: 10,998,238.98
//       Random number mean: 0.4999

该示例通过以下方式确保线程安全:

  • ThreadStaticAttribute 属性用于定义线程局部变量,用于跟踪生成的随机数总数以及每个线程的总和。
  • 锁( lock C# 中的语句、 lock F# 中的函数和 SyncLock Visual Basic 中的语句)保护对所有线程上生成的所有随机数总数和变量的访问。
  • 信号灯( CountdownEvent 对象)用于确保主线程阻塞,直到所有其他线程完成执行。
  • 该示例通过确定对随机数生成方法的两次连续调用是否返回 0 来检查随机数生成器是否已损坏。 如果检测到损坏,该示例将使用 CancellationTokenSource 对象来指示应取消所有线程。
  • 在生成每个随机数之前,每个线程都会检查对象的状态 CancellationToken 。 如果请求取消,该示例将调用 CancellationToken.ThrowIfCancellationRequested 该方法来取消线程。

以下示例与第一个示例相同,只不过它使用 Task 对象和 lambda 表达式而不是 Thread 对象。

C#
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example15
{
    static Object randLock, numericLock;
    static Random rand;
    static CancellationTokenSource source;
    double totalValue = 0.0;
    int totalCount = 0;

    public Example15()
    {
        rand = new Random();
        randLock = new Object();
        numericLock = new Object();
        source = new CancellationTokenSource();
    }

    public static async Task Main()
    {
        Example15 ex = new Example15();
        Thread.CurrentThread.Name = "Main";
        await ex.Execute();
    }

    private async Task Execute()
    {
        List<Task> tasks = new List<Task>();

        for (int ctr = 0; ctr <= 10; ctr++)
        {
            CancellationToken token = source.Token;
            int taskNo = ctr;
            tasks.Add(Task.Run(() =>
               {
                   double previous = 0.0;
                   int taskCtr = 0;
                   double taskTotal = 0.0;
                   double result = 0.0;

                   for (int n = 0; n < 2000000; n++)
                   {
                       // Make sure there's no corruption of Random.
                       token.ThrowIfCancellationRequested();

                       lock (randLock)
                       {
                           result = rand.NextDouble();
                       }
                       // Check for corruption of Random instance.
                       if ((result == previous) && result == 0)
                       {
                           source.Cancel();
                       }
                       else
                       {
                           previous = result;
                       }
                       taskCtr++;
                       taskTotal += result;
                   }

                   // Show result.
                   Console.WriteLine("Task {0} finished execution.", taskNo);
                   Console.WriteLine("Random numbers generated: {0:N0}", taskCtr);
                   Console.WriteLine("Sum of random numbers: {0:N2}", taskTotal);
                   Console.WriteLine("Random number mean: {0:N4}\n", taskTotal / taskCtr);

                   // Update overall totals.
                   lock (numericLock)
                   {
                       totalCount += taskCtr;
                       totalValue += taskTotal;
                   }
               },
            token));
        }
        try
        {
            await Task.WhenAll(tasks.ToArray());
            Console.WriteLine("\nTotal random numbers generated: {0:N0}", totalCount);
            Console.WriteLine("Total sum of all random numbers: {0:N2}", totalValue);
            Console.WriteLine("Random number mean: {0:N4}", totalValue / totalCount);
        }
        catch (AggregateException e)
        {
            foreach (Exception inner in e.InnerExceptions)
            {
                TaskCanceledException canc = inner as TaskCanceledException;
                if (canc != null)
                    Console.WriteLine("Task #{0} cancelled.", canc.Task.Id);
                else
                    Console.WriteLine("Exception: {0}", inner.GetType().Name);
            }
        }
        finally
        {
            source.Dispose();
        }
    }
}
// The example displays output like the following:
//       Task 1 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 1,000,502.47
//       Random number mean: 0.5003
//
//       Task 0 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 1,000,445.63
//       Random number mean: 0.5002
//
//       Task 2 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 1,000,556.04
//       Random number mean: 0.5003
//
//       Task 3 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 1,000,178.87
//       Random number mean: 0.5001
//
//       Task 4 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,819.17
//       Random number mean: 0.4999
//
//       Task 5 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 1,000,190.58
//       Random number mean: 0.5001
//
//       Task 6 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,720.21
//       Random number mean: 0.4999
//
//       Task 7 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,000.96
//       Random number mean: 0.4995
//
//       Task 8 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,499.33
//       Random number mean: 0.4997
//
//       Task 9 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 1,000,193.25
//       Random number mean: 0.5001
//
//       Task 10 finished execution.
//       Random numbers generated: 2,000,000
//       Sum of random numbers: 999,960.82
//       Random number mean: 0.5000
//
//
//       Total random numbers generated: 22,000,000
//       Total sum of all random numbers: 11,000,067.33
//       Random number mean: 0.5000

它与以下方法中的第一个示例不同:

  • 用于跟踪生成的随机数及其每个任务中的总和的变量是任务的本地变量,因此无需使用该 ThreadStaticAttribute 属性。
  • 静态 Task.WaitAll 方法用于确保在完成所有任务之前主线程未完成。 不需要对象 CountdownEvent
  • 方法中 Task.WaitAll 显示了任务取消结果的异常。 在前面的示例中,它由每个线程处理。

生成不同类型的随机数

随机数生成器提供的方法可用于生成以下类型的随机数:

  • 一系列 Byte 值。 通过将初始化的数组传递给希望该方法返回到 NextBytes 方法的元素数来确定字节值的数目。 以下示例生成 20 个字节。

    C#
    Random rnd = new Random();
    Byte[] bytes = new Byte[20];
    rnd.NextBytes(bytes);
    for (int ctr = 1; ctr <= bytes.Length; ctr++)
    {
        Console.Write("{0,3}   ", bytes[ctr - 1]);
        if (ctr % 10 == 0) Console.WriteLine();
    }
    
    // The example displays output like the following:
    //       141    48   189    66   134   212   211    71   161    56
    //       181   166   220   133     9   252   222    57    62    62
    
  • 单个整数。 你可以通过调用Next()方法、通过调用方法将介于 0 和特定值之间的整数,或通过调用Next(Int32)该方法来选择从 0 到最大值Int32.MaxValue(- 1)的整数,还是通过调用Next(Int32, Int32)该方法在值范围内的整数。 在参数化重载中,指定的最大值是独占的;也就是说,生成的实际最大数比指定值少一个。

    以下示例调用 Next(Int32, Int32) 该方法以在 -10 和 10 之间生成 10 个随机数。 请注意,该方法的第二个参数指定方法返回的随机值范围的独占上限。 换句话说,该方法可以返回的最大整数小于此值。

    C#
    Random rnd = new Random();
    for (int ctr = 0; ctr < 10; ctr++)
    {
        Console.Write("{0,3}   ", rnd.Next(-10, 11));
    }
    
    // The example displays output like the following:
    //    2     9    -3     2     4    -7    -3    -8    -8     5
    
  • 通过调用 NextDouble 方法,从 0.0 到小于 1.0 的单个浮点值。 方法返回的随机数的排他上限为 1,因此其实际上限为 0.9999999999999999999978。 以下示例生成 10 个随机浮点数。

    C#
    Random rnd = new Random();
    for (int ctr = 0; ctr < 10; ctr++)
    {
        Console.Write("{0,-19:R}   ", rnd.NextDouble());
        if ((ctr + 1) % 3 == 0) Console.WriteLine();
    }
    
    // The example displays output like the following:
    //    0.7911680553998649    0.0903414949264105    0.79776258291572455
    //    0.615568345233597     0.652644504165577     0.84023809378977776
    //    0.099662564741290441   0.91341467383942321  0.96018602045261581
    //    0.74772306473354022
    

重要

此方法 Next(Int32, Int32) 允许指定返回的随机数的范围。 但是, maxValue 指定返回的上限数字的参数是独占值,而不是非非独占值。 这意味着方法调用 Next(0, 100) 返回一个介于 0 和 99 之间的值,而不是介于 0 和 100 之间。

还可以将Random类用于生成随机布尔值、在指定范围内生成随机浮点值、生成随机 64 位整数以及从数组或集合检索唯一元素等任务。

替换自己的算法

可以通过从 Random 类继承并提供随机数生成算法来实现自己的随机数生成器。 若要提供自己的算法,必须重写 Sample 实现随机数生成算法的方法。 还应重写Next()Next(Int32, Int32)NextBytes方法,以确保它们调用重写Sample的方法。 无需重写 Next(Int32)NextDouble 方法。

有关派生自 Random 该类并修改其默认伪随机数生成器的示例,请参阅 Sample 参考页。

检索相同的随机值序列

有时,你想要在软件测试方案和游戏中生成相同的随机数序列。 使用相同随机数序列进行测试,可以检测回归并确认 bug 修复。 在游戏中使用相同的随机数序列,可以重播以前的游戏。

可以通过向构造函数提供相同的种子值 Random(Int32) 来生成相同的随机数序列。 种子值为伪随机数生成算法提供起始值。 以下示例使用 100100 作为任意种子值来实例化 Random 对象,显示 20 个随机浮点值,并保留种子值。 然后,它会还原种子值,实例化新的随机数生成器,并显示相同的 20 个随机浮点值。 请注意,如果在不同的版本的 .NET 上运行,该示例可能会生成不同的随机数序列。

C#
using System;
using System.IO;

public class Example12
{
    public static void Main()
    {
        int seed = 100100;
        ShowRandomNumbers(seed);
        Console.WriteLine();

        PersistSeed(seed);

        DisplayNewRandomNumbers();
    }

    private static void ShowRandomNumbers(int seed)
    {
        Random rnd = new Random(seed);
        for (int ctr = 0; ctr <= 20; ctr++)
            Console.WriteLine(rnd.NextDouble());
    }

    private static void PersistSeed(int seed)
    {
        FileStream fs = new FileStream(@".\seed.dat", FileMode.Create);
        BinaryWriter bin = new BinaryWriter(fs);
        bin.Write(seed);
        bin.Close();
    }

    private static void DisplayNewRandomNumbers()
    {
        FileStream fs = new FileStream(@".\seed.dat", FileMode.Open);
        BinaryReader bin = new BinaryReader(fs);
        int seed = bin.ReadInt32();
        bin.Close();

        Random rnd = new Random(seed);
        for (int ctr = 0; ctr <= 20; ctr++)
            Console.WriteLine(rnd.NextDouble());
    }
}
// The example displays output like the following:
//       0.500193602172748
//       0.0209461245783354
//       0.465869495396442
//       0.195512794514891
//       0.928583675496552
//       0.729333720509584
//       0.381455668891527
//       0.0508996467343064
//       0.019261200921266
//       0.258578445417145
//       0.0177532266908107
//       0.983277184415272
//       0.483650274334313
//       0.0219647376900375
//       0.165910115077118
//       0.572085966622497
//       0.805291457942357
//       0.927985211335116
//       0.4228545699375
//       0.523320379910674
//       0.157783938645285
//
//       0.500193602172748
//       0.0209461245783354
//       0.465869495396442
//       0.195512794514891
//       0.928583675496552
//       0.729333720509584
//       0.381455668891527
//       0.0508996467343064
//       0.019261200921266
//       0.258578445417145
//       0.0177532266908107
//       0.983277184415272
//       0.483650274334313
//       0.0219647376900375
//       0.165910115077118
//       0.572085966622497
//       0.805291457942357
//       0.927985211335116
//       0.4228545699375
//       0.523320379910674
//       0.157783938645285

检索随机数的唯一序列

向类实例 Random 提供不同的种子值会导致每个随机数生成器生成不同的值序列。 可以通过调用 Random(Int32) 构造函数显式提供种子值,也可以通过调用 Random() 构造函数隐式提供种子值。 大多数开发人员调用使用系统时钟的无参数构造函数。 以下示例使用此方法实例化两 Random 个实例。 每个实例显示一系列 10 个随机整数。

C#
using System;
using System.Threading;

public class Example16
{
    public static void Main()
    {
        Console.WriteLine("Instantiating two random number generators...");
        Random rnd1 = new Random();
        Thread.Sleep(2000);
        Random rnd2 = new Random();

        Console.WriteLine("\nThe first random number generator:");
        for (int ctr = 1; ctr <= 10; ctr++)
            Console.WriteLine("   {0}", rnd1.Next());

        Console.WriteLine("\nThe second random number generator:");
        for (int ctr = 1; ctr <= 10; ctr++)
            Console.WriteLine("   {0}", rnd2.Next());
    }
}
// The example displays output like the following:
//       Instantiating two random number generators...
//
//       The first random number generator:
//          643164361
//          1606571630
//          1725607587
//          2138048432
//          496874898
//          1969147632
//          2034533749
//          1840964542
//          412380298
//          47518930
//
//       The second random number generator:
//          1251659083
//          1514185439
//          1465798544
//          517841554
//          1821920222
//          195154223
//          1538948391
//          1548375095
//          546062716
//          897797880

但是,由于其有限分辨率,系统时钟不会检测小于 15 毫秒的时间差。 因此,如果代码调用 Random() .NET Framework 上的重载来连续实例化两 Random 个对象,则可能无意中为对象提供相同的种子值。 Random(.NET Core 中的类没有此限制。若要查看上一示例中的Thread.Sleep这一点,请注释掉方法调用,然后再次编译并运行该示例。

为防止这种情况发生,我们建议实例化单个 Random 对象而不是多个对象。 但是,由于 Random 不是线程安全的,如果从多个线程访问 Random 实例,则必须使用一些同步设备;有关详细信息,请参阅 “线程安全 ”部分。 或者,可以使用延迟机制(如 Sleep 上一示例中使用的方法),以确保实例化相隔 15 毫秒以上。

检索指定范围内的整数

可以通过调用 Next(Int32, Int32) 方法来检索指定范围内的整数,这样就可以指定希望随机数生成器返回的数字的下限和上限。 上限是独占值,而不是非独占值。 也就是说,该方法返回的值范围中不包含它。 以下示例使用此方法在 -10 和 10 之间生成随机整数。 请注意,它指定 11,即大于所需值的 11,作为方法调用中参数的值 maxValue

C#
Random rnd = new Random();
for (int ctr = 1; ctr <= 15; ctr++)
{
    Console.Write("{0,3}    ", rnd.Next(-10, 11));
    if (ctr % 5 == 0) Console.WriteLine();
}

// The example displays output like the following:
//        -2     -5     -1     -2     10
//        -3      6     -4     -8      3
//        -7     10      5     -2      4

检索具有指定数字数的整数

可以调用 Next(Int32, Int32) 该方法来检索具有指定数字数的数字。 例如,若要检索包含四个数字的数字(即介于 1000 到 9999 的数字),请调用Next(Int32, Int32)值为 1000 且maxValue值为 10000 的方法minValue,如以下示例所示。

C#
Random rnd = new Random();
for (int ctr = 1; ctr <= 50; ctr++)
{
    Console.Write("{0,3}    ", rnd.Next(1000, 10000));
    if (ctr % 10 == 0) Console.WriteLine();
}

// The example displays output like the following:
//    9570    8979    5770    1606    3818    4735    8495    7196    7070    2313
//    5279    6577    5104    5734    4227    3373    7376    6007    8193    5540
//    7558    3934    3819    7392    1113    7191    6947    4963    9179    7907
//    3391    6667    7269    1838    7317    1981    5154    7377    3297    5320
//    9869    8694    2684    4949    2999    3019    2357    5211    9604    2593

检索指定区域中的浮点值

该方法 NextDouble 返回从 0 到小于 1 的随机浮点值。 但是,通常需要在其他一些范围内生成随机值。

如果最小值和最大所需值之间的间隔为 1,则可以将所需起始间隔与 0 之间的差值添加到方法返回 NextDouble 的数字。 以下示例执行该操作以在 -1 和 0 之间生成 10 个随机数。

C#
Random rnd = new Random();
for (int ctr = 1; ctr <= 10; ctr++)
    Console.WriteLine(rnd.NextDouble() - 1);

// The example displays output like the following:
//       -0.930412760437658
//       -0.164699016215605
//       -0.9851692803135
//       -0.43468508843085
//       -0.177202483255976
//       -0.776813320245972
//       -0.0713201854710096
//       -0.0912875561468711
//       -0.540621722368813
//       -0.232211863730201

若要生成下限为 0 但上限大于 1 的随机浮点数(或者,如果负数(其下限小于 -1 且上限为 0),请将随机数乘以非零边界。 以下示例将生成 2000 万个随机浮点数,范围为 0 到 Int64.MaxValue。 此外,还显示方法生成的随机值的分布。

C#
const long ONE_TENTH = 922337203685477581;

Random rnd = new Random();
double number;
int[] count = new int[10];

// Generate 20 million integer values between.
for (int ctr = 1; ctr <= 20000000; ctr++)
{
    number = rnd.NextDouble() * Int64.MaxValue;
    // Categorize random numbers into 10 groups.
    count[(int)(number / ONE_TENTH)]++;
}
// Display breakdown by range.
Console.WriteLine("{0,28} {1,32}   {2,7}\n", "Range", "Count", "Pct.");
for (int ctr = 0; ctr <= 9; ctr++)
    Console.WriteLine("{0,25:N0}-{1,25:N0}  {2,8:N0}   {3,7:P2}", ctr * ONE_TENTH,
                       ctr < 9 ? ctr * ONE_TENTH + ONE_TENTH - 1 : Int64.MaxValue,
                       count[ctr], count[ctr] / 20000000.0);

// The example displays output like the following:
//                           Range                            Count      Pct.
//
//                            0-  922,337,203,685,477,580  1,996,148    9.98 %
//      922,337,203,685,477,581-1,844,674,407,370,955,161  2,000,293   10.00 %
//    1,844,674,407,370,955,162-2,767,011,611,056,432,742  2,000,094   10.00 %
//    2,767,011,611,056,432,743-3,689,348,814,741,910,323  2,000,159   10.00 %
//    3,689,348,814,741,910,324-4,611,686,018,427,387,904  1,999,552   10.00 %
//    4,611,686,018,427,387,905-5,534,023,222,112,865,485  1,998,248    9.99 %
//    5,534,023,222,112,865,486-6,456,360,425,798,343,066  2,000,696   10.00 %
//    6,456,360,425,798,343,067-7,378,697,629,483,820,647  2,001,637   10.01 %
//    7,378,697,629,483,820,648-8,301,034,833,169,298,228  2,002,870   10.01 %
//    8,301,034,833,169,298,229-9,223,372,036,854,775,807  2,000,303   10.00 %

若要在两个任意值之间生成随机浮点数,如 Next(Int32, Int32) 该方法对整数执行的方法,请使用以下公式:

C#
Random.NextDouble() * (maxValue - minValue) + minValue

以下示例生成 100 万个随机数,范围为 10.0 到 11.0,并显示其分布。

C#
Random rnd = new Random();
int lowerBound = 10;
int upperBound = 11;
int[] range = new int[10];
for (int ctr = 1; ctr <= 1000000; ctr++)
{
    Double value = rnd.NextDouble() * (upperBound - lowerBound) + lowerBound;
    range[(int)Math.Truncate((value - lowerBound) * 10)]++;
}

for (int ctr = 0; ctr <= 9; ctr++)
{
    Double lowerRange = 10 + ctr * .1;
    Console.WriteLine("{0:N1} to {1:N1}: {2,8:N0}  ({3,7:P2})",
                      lowerRange, lowerRange + .1, range[ctr],
                      range[ctr] / 1000000.0);
}

// The example displays output like the following:
//       10.0 to 10.1:   99,929  ( 9.99 %)
//       10.1 to 10.2:  100,189  (10.02 %)
//       10.2 to 10.3:   99,384  ( 9.94 %)
//       10.3 to 10.4:  100,240  (10.02 %)
//       10.4 to 10.5:   99,397  ( 9.94 %)
//       10.5 to 10.6:  100,580  (10.06 %)
//       10.6 to 10.7:  100,293  (10.03 %)
//       10.7 to 10.8:  100,135  (10.01 %)
//       10.8 to 10.9:   99,905  ( 9.99 %)
//       10.9 to 11.0:   99,948  ( 9.99 %)

生成随机布尔值

Random 类不提供生成 Boolean 值的方法。 但是,可以定义自己的类或方法来执行此操作。 以下示例使用单个方法NextBoolean定义类BooleanGenerator。 该 BooleanGenerator 类将 Random 对象存储为私有变量。 该方法 NextBoolean 调用该方法 Random.Next(Int32, Int32) 并将结果传递给 Convert.ToBoolean(Int32) 该方法。 请注意,2 用作指定随机数上限的参数。 由于这是独占值,因此方法调用返回 0 或 1。

C#
using System;

public class Example1
{
    public static void Main()
    {
        // Instantiate the Boolean generator.
        BooleanGenerator boolGen = new BooleanGenerator();
        int totalTrue = 0, totalFalse = 0;

        // Generate 1,0000 random Booleans, and keep a running total.
        for (int ctr = 0; ctr < 1000000; ctr++)
        {
            bool value = boolGen.NextBoolean();
            if (value)
                totalTrue++;
            else
                totalFalse++;
        }
        Console.WriteLine("Number of true values:  {0,7:N0} ({1:P3})",
                          totalTrue,
                          ((double)totalTrue) / (totalTrue + totalFalse));
        Console.WriteLine("Number of false values: {0,7:N0} ({1:P3})",
                          totalFalse,
                          ((double)totalFalse) / (totalTrue + totalFalse));
    }
}

public class BooleanGenerator
{
    Random rnd;

    public BooleanGenerator()
    {
        rnd = new Random();
    }

    public bool NextBoolean()
    {
        return rnd.Next(0, 2) == 1;
    }
}
// The example displays output like the following:
//       Number of true values:  500,004 (50.000 %)
//       Number of false values: 499,996 (50.000 %)

该示例只能定义单个方法,而不是创建单独的类来生成随机 Boolean 值。 但是,在这种情况下, Random 应将对象定义为类级变量,以避免在每个方法调用中实例化新 Random 实例。 在 Visual Basic 中,随机实例可以定义为方法中的NextBoolean静态变量。 以下示例提供实现。

C#
Random rnd = new Random();

int totalTrue = 0, totalFalse = 0;

// Generate 1,000,000 random Booleans, and keep a running total.
for (int ctr = 0; ctr < 1000000; ctr++)
{
    bool value = NextBoolean();
    if (value)
        totalTrue++;
    else
        totalFalse++;
}
Console.WriteLine("Number of true values:  {0,7:N0} ({1:P3})",
                  totalTrue,
                  ((double)totalTrue) / (totalTrue + totalFalse));
Console.WriteLine("Number of false values: {0,7:N0} ({1:P3})",
                  totalFalse,
                  ((double)totalFalse) / (totalTrue + totalFalse));

bool NextBoolean()
{
    return rnd.Next(0, 2) == 1;
}

// The example displays output like the following:
//       Number of true values:  499,777 (49.978 %)
//       Number of false values: 500,223 (50.022 %)

生成随机 64 位整数

方法的 Next 重载返回 32 位整数。 但是,在某些情况下,你可能想要使用 64 位整数。 你可以按如下所示进行操作:

  1. NextDouble调用该方法以检索双精度浮点值。

  2. 将该值乘以 Int64.MaxValue

以下示例使用此方法生成 2000 万个随机长整数,并将其分类为 10 个相等组。 然后,它通过将每个组中的数字计数从 0 计算为 0 来 Int64.MaxValue计算随机数的分布。 如示例中的输出所示,这些数字通过长整数的范围以相同或多或少的分布方式分布。

C#
const long ONE_TENTH = 922337203685477581;

Random rnd = new Random();
long number;
int[] count = new int[10];

// Generate 20 million long integers.
for (int ctr = 1; ctr <= 20000000; ctr++)
{
    number = (long)(rnd.NextDouble() * Int64.MaxValue);
    // Categorize random numbers.
    count[(int)(number / ONE_TENTH)]++;
}
// Display breakdown by range.
Console.WriteLine("{0,28} {1,32}   {2,7}\n", "Range", "Count", "Pct.");
for (int ctr = 0; ctr <= 9; ctr++)
    Console.WriteLine("{0,25:N0}-{1,25:N0}  {2,8:N0}   {3,7:P2}", ctr * ONE_TENTH,
                       ctr < 9 ? ctr * ONE_TENTH + ONE_TENTH - 1 : Int64.MaxValue,
                       count[ctr], count[ctr] / 20000000.0);

// The example displays output like the following:
//                           Range                            Count      Pct.
//
//                            0-  922,337,203,685,477,580  1,996,148    9.98 %
//      922,337,203,685,477,581-1,844,674,407,370,955,161  2,000,293   10.00 %
//    1,844,674,407,370,955,162-2,767,011,611,056,432,742  2,000,094   10.00 %
//    2,767,011,611,056,432,743-3,689,348,814,741,910,323  2,000,159   10.00 %
//    3,689,348,814,741,910,324-4,611,686,018,427,387,904  1,999,552   10.00 %
//    4,611,686,018,427,387,905-5,534,023,222,112,865,485  1,998,248    9.99 %
//    5,534,023,222,112,865,486-6,456,360,425,798,343,066  2,000,696   10.00 %
//    6,456,360,425,798,343,067-7,378,697,629,483,820,647  2,001,637   10.01 %
//    7,378,697,629,483,820,648-8,301,034,833,169,298,228  2,002,870   10.01 %
//    8,301,034,833,169,298,229-9,223,372,036,854,775,807  2,000,303   10.00 %

使用位操作的替代技术不会生成真正的随机数。 此方法调用 Next() 生成两个整数,左移一个 32 位,并将它们一起发出 OR。 此方法有两个限制:

  1. 由于位 31 是符号位,因此生成的长整数的位 31 中的值始终为 0。 这可以通过生成随机 0 或 1,将它左移 31 位,并使用原始随机长整数进行 ORing 来解决此问题。

  2. 更严重的是,由于返回 Next() 的值的概率为 0,因此,如果范围中的任何随机数0x0-0x00000000FFFFFFFF,则很少。

检索指定范围内的字节数

方法的 Next 重载允许指定随机数的范围,但 NextBytes 该方法不指定。 以下示例实现一个 NextBytes 方法,用于指定返回的字节的范围。 它定义 Random2 派生自 Random 其方法并重载的 NextBytes 类。

C#
using System;

public class Example3
{
    public static void Main()
    {
        Random2 rnd = new Random2();
        Byte[] bytes = new Byte[10000];
        int[] total = new int[101];
        rnd.NextBytes(bytes, 0, 101);

        // Calculate how many of each value we have.
        foreach (var value in bytes)
            total[value]++;

        // Display the results.
        for (int ctr = 0; ctr < total.Length; ctr++)
        {
            Console.Write("{0,3}: {1,-3}   ", ctr, total[ctr]);
            if ((ctr + 1) % 5 == 0) Console.WriteLine();
        }
    }
}

public class Random2 : Random
{
    public Random2() : base()
    { }

    public Random2(int seed) : base(seed)
    { }

    public void NextBytes(byte[] bytes, byte minValue, byte maxValue)
    {
        for (int ctr = bytes.GetLowerBound(0); ctr <= bytes.GetUpperBound(0); ctr++)
            bytes[ctr] = (byte)Next(minValue, maxValue);
    }
}
// The example displays output like the following:
//         0: 115     1: 119     2: 92      3: 98      4: 92
//         5: 102     6: 103     7: 84      8: 93      9: 116
//        10: 91     11: 98     12: 106    13: 91     14: 92
//        15: 101    16: 100    17: 96     18: 97     19: 100
//        20: 101    21: 106    22: 112    23: 82     24: 85
//        25: 102    26: 107    27: 98     28: 106    29: 102
//        30: 109    31: 108    32: 94     33: 101    34: 107
//        35: 101    36: 86     37: 100    38: 101    39: 102
//        40: 113    41: 95     42: 96     43: 89     44: 99
//        45: 81     46: 89     47: 105    48: 100    49: 85
//        50: 103    51: 103    52: 93     53: 89     54: 91
//        55: 97     56: 105    57: 97     58: 110    59: 86
//        60: 116    61: 94     62: 117    63: 98     64: 110
//        65: 93     66: 102    67: 100    68: 105    69: 83
//        70: 81     71: 97     72: 85     73: 70     74: 98
//        75: 100    76: 110    77: 114    78: 83     79: 90
//        80: 96     81: 112    82: 102    83: 102    84: 99
//        85: 81     86: 100    87: 93     88: 99     89: 118
//        90: 95     91: 124    92: 108    93: 96     94: 104
//        95: 106    96: 99     97: 99     98: 92     99: 99
//       100: 108

该方法 NextBytes(Byte[], Byte, Byte) 包装对 Next(Int32, Int32) 方法的调用,并指定要在字节数组中返回的最小值和大于最大值(在本例中为 0 和 101)。 由于我们确信该方法返回 NextByte 整数值在数据类型范围内,因此可以安全地转换它们(在 C# 和 F# 中),或将它们(在 Visual Basic 中)从整数转换为字节。

从数组或集合中随机检索元素

随机数通常用作索引,用于从数组或集合中检索值。 若要检索随机索引值,可以调用 Next(Int32, Int32) 该方法,并使用数组的下限作为其 minValue 参数的值,将大于数组上限的数组作为其 maxValue 参数的值。 对于从零开始的数组,这等效于其 Length 属性,或一个大于方法返回 Array.GetUpperBound 的值。 以下示例从一组城市随机检索美国中的城市名称。

C#
String[] cities = { "Atlanta", "Boston", "Chicago", "Detroit",
                    "Fort Wayne", "Greensboro", "Honolulu", "Indianapolis",
                    "Jersey City", "Kansas City", "Los Angeles",
                    "Milwaukee", "New York", "Omaha", "Philadelphia",
                    "Raleigh", "San Francisco", "Tulsa", "Washington" };
Random rnd = new Random();
int index = rnd.Next(0, cities.Length);
Console.WriteLine("Today's city of the day: {0}",
                  cities[index]);

// The example displays output like the following:
//   Today's city of the day: Honolulu

从数组或集合中检索唯一元素

随机数生成器始终可以返回重复值。 当数字范围变小或生成的值数变大时,重复项的概率就会增加。 如果随机值必须是唯一的,则生成更多的数字来补偿重复值,从而导致性能越来越差。

有许多技术可以处理此方案。 一种常见解决方案是创建包含要检索的值的数组或集合,以及包含随机浮点数的并行数组。 第二个数组在创建第一个数组时使用随机数填充,该方法 Array.Sort(Array, Array) 用于使用并行数组中的值对第一个数组进行排序。

例如,如果要开发 Solitaire 游戏,则希望确保每个卡片只使用一次。 可以创建可用于对卡片进行排序的并行随机数数组,而不是生成随机数来检索卡片并跟踪该卡是否已被处理。 对幻灯片进行排序后,你的应用可以保留一个指针,以指示幻灯片上的下一张卡片的索引。

下面的示例阐释了这种方法。 它定义一个 Card 类,该类表示一张打牌和一个 Dealer 处理一组混排牌的类。 Dealer类构造函数填充两个数组:一个deck具有类范围且表示甲板中的所有卡片的数组;以及一个与数组具有相同数量的元素deck且填充有随机生成的Double值的本地order数组。 Array.Sort(Array, Array)然后调用该方法以基于数组中的order值对数组进行排序deck

C#
using System;

// A class that represents an individual card in a playing deck.
public class Card
{
    public Suit Suit;
    public FaceValue FaceValue;

    public override String ToString()
    {
        return String.Format("{0:F} of {1:F}", this.FaceValue, this.Suit);
    }
}

public enum Suit { Hearts, Diamonds, Spades, Clubs };

public enum FaceValue
{
    Ace = 1, Two, Three, Four, Five, Six,
    Seven, Eight, Nine, Ten, Jack, Queen,
    King
};

public class Dealer
{
    Random rnd;
    // A deck of cards, without Jokers.
    Card[] deck = new Card[52];
    // Parallel array for sorting cards.
    Double[] order = new Double[52];
    // A pointer to the next card to deal.
    int ptr = 0;
    // A flag to indicate the deck is used.
    bool mustReshuffle = false;

    public Dealer()
    {
        rnd = new Random();
        // Initialize the deck.
        int deckCtr = 0;
        foreach (var suit in Enum.GetValues(typeof(Suit)))
        {
            foreach (var faceValue in Enum.GetValues(typeof(FaceValue)))
            {
                Card card = new Card();
                card.Suit = (Suit)suit;
                card.FaceValue = (FaceValue)faceValue;
                deck[deckCtr] = card;
                deckCtr++;
            }
        }

        for (int ctr = 0; ctr < order.Length; ctr++)
            order[ctr] = rnd.NextDouble();

        Array.Sort(order, deck);
    }

    public Card[] Deal(int numberToDeal)
    {
        if (mustReshuffle)
        {
            Console.WriteLine("There are no cards left in the deck");
            return null;
        }

        Card[] cardsDealt = new Card[numberToDeal];
        for (int ctr = 0; ctr < numberToDeal; ctr++)
        {
            cardsDealt[ctr] = deck[ptr];
            ptr++;
            if (ptr == deck.Length)
                mustReshuffle = true;

            if (mustReshuffle & ctr < numberToDeal - 1)
            {
                Console.WriteLine("Can only deal the {0} cards remaining on the deck.",
                                  ctr + 1);
                return cardsDealt;
            }
        }
        return cardsDealt;
    }
}

public class Example17
{
    public static void Main()
    {
        Dealer dealer = new Dealer();
        ShowCards(dealer.Deal(20));
    }

    private static void ShowCards(Card[] cards)
    {
        foreach (var card in cards)
            if (card != null)
                Console.WriteLine("{0} of {1}", card.FaceValue, card.Suit);
    }
}
// The example displays output like the following:
//       Six of Diamonds
//       King of Clubs
//       Eight of Clubs
//       Seven of Clubs
//       Queen of Clubs
//       King of Hearts
//       Three of Spades
//       Ace of Clubs
//       Four of Hearts
//       Three of Diamonds
//       Nine of Diamonds
//       Two of Hearts
//       Ace of Hearts
//       Three of Hearts
//       Four of Spades
//       Eight of Hearts
//       Queen of Diamonds
//       Two of Clubs
//       Four of Diamonds
//       Jack of Hearts