Прочитать на английском

Поделиться через


Класс System.Random

В этой статье приводятся дополнительные замечания к справочной документации по этому API.

Класс Random представляет генератор псевдослучайного числа, который представляет собой алгоритм, который создает последовательность чисел, удовлетворяющих определенным статистическим требованиям для случайности.

Псевдослучайные числа выбираются с равной вероятностью из конечного набора чисел. Выбранные числа не являются совершенно случайными, так как математический алгоритм используется для их выбора, но они достаточно случайны для практических целей. Реализация Random класса основана на измененной версии подтрактивного алгоритма генератора случайных чисел Дональда E. Knuth. Дополнительные сведения см. в статье D. E. Knuth. Искусство компьютерного программирования, том 2: полунумерические алгоритмы. Addison-Wesley, Reading, 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 объектов в близком последовательности, создает генераторы случайных чисел, которые создают идентичные последовательности случайных чисел. В следующем примере показано, как два Random объекта, созданные в близком последовательности в приложении платформа .NET Framework, создают идентичный ряд случайных чисел. В большинстве систем Windows объекты, созданные в пределах 15 миллисекундах друг друга, Random скорее всего, имеют идентичные начальные значения.

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#, функция блокировки F# и инструкция Visual Basic SyncLock, чтобы обеспечить доступ к одному генератору случайных чисел 11 потоков в потокобезопасном режиме. Каждый поток создает 2 миллиона случайных чисел, подсчитывает количество созданных случайных чисел и вычисляет их сумму, а затем обновляет итоги для всех потоков после завершения выполнения.

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 объект и лямбда-выражение вместо 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
    
  • Одно целое число. Можно выбрать, требуется ли целое число от 0 до максимального значения (Int32.MaxValue –1), вызвав Next() метод, целое число от 0 до определенного значения, вызвав Next(Int32) метод или целое число в диапазоне значений путем вызова 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
    
  • Одно значение с плавающей запятой от 0,0 до меньше 1,0 путем вызова NextDouble метода. Монопольная верхняя граница случайного числа, возвращаемого методом, составляет 1, поэтому его фактическая верхняя граница составляет 0,99999999999999978. В следующем примере создается 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()методы и NextBytes Next(Int32, Int32)методы, чтобы убедиться, что они вызывают переопределенный Sample метод. Вам не нужно переопределять Next(Int32) методы и NextDouble методы.

Пример, производный от Random класса и изменяющий генератор псевдо случайных чисел по умолчанию, см. на Sample странице ссылки.

Получение той же последовательности случайных значений

Иногда требуется создать ту же последовательность случайных чисел в сценариях тестирования программного обеспечения и в игре. Тестирование с той же последовательностью случайных чисел позволяет обнаруживать регрессии и проверять исправления ошибок. Использование той же последовательности случайного числа в играх позволяет воспроизводить предыдущие игры.

Вы можете создать ту же последовательность случайных чисел, указав одно и то же начальное значение конструктору 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, которое больше требуемого значения в качестве значения аргумента 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) со minValue значением 1000 и maxValue значением 10000, как показано в следующем примере.

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 . В следующем примере создается 10 случайных чисел от -1 до 0.

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), умножьте случайное число на ненулевое. В следующем примере это делается для создания 20 миллионов случайных чисел с плавающей запятой, которые варьируются от 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

В следующем примере создается 1 миллион случайных чисел, которые варьируются от 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 значения. Однако для этого можно определить собственный класс или метод. В следующем примере определяется класс, BooleanGeneratorс одним методом NextBoolean. Класс 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.

В следующем примере этот метод используется для создания 20 миллионов случайных целых чисел и классифицирует их в 10 равных группах. Затем он оценивает распределение случайных чисел, подсчитывая число в каждой группе от 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), которое мы хотим вернуть в массив байтов. Так как мы уверены, что целые значения, возвращаемые Next методом, находятся в диапазоне Byte типа данных, мы можем безопасно привести их (в 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 массив с областью класса и представляет все карточки в колоде; и локальный order массив, имеющий одинаковое количество элементов, что deck и массив и заполняется случайным образом созданными Double значениями. Затем Array.Sort(Array, Array) метод вызывается для сортировки deck массива на основе значений в массиве order .

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