Случайное число в C# проще всего получить через класс Random из пространства имен System: он выдает псевдослучайную последовательность, которой хватает для игр, тестов, симуляций и выборки. Для паролей, токенов и ключей нужен другой инструмент — RandomNumberGenerator из System.Security.Cryptography.
Содержание
Ниже разберем, как сгенерировать число в нужном диапазоне, чем различаются конструкторы Random, почему один экземпляр нельзя делить между потоками и когда обычного генератора недостаточно. Все примеры даны под .NET 8; ожидаемый вывод показан рядом с кодом.
Быстрый старт: одно случайное число
Минимальная программа, которую можно скопировать и запустить. Она бросает кубик (целое от 1 до 6) и берет дробное число из полуинтервала от 0 до 1:
using System;
class Program
{
static void Main()
{
Random rnd = new Random();
int dice = rnd.Next(1, 7); // целое от 1 до 6
double unit = rnd.NextDouble(); // дробное [0.0, 1.0), 1.0 не входит
Console.WriteLine($"Бросок кубика: {dice}");
Console.WriteLine($"Дробное [0,1): {unit}");
}
}
Вывод меняется от запуска к запуску, например:
Бросок кубика: 4
Дробное [0,1): 0.7302194564101349
Разделитель дробной части (точка или запятая) зависит от системной культуры: при русской локали NextDouble выведется через запятую. Чтобы вывод был стабильным, форматируйте число с CultureInfo.InvariantCulture.
Класс Random и его конструкторы
Random — это генератор псевдослучайных чисел. «Псевдо» значит, что числа выдает детерминированный алгоритм: он выглядит случайным, но полностью определяется начальным состоянием — сидом (seed). У класса два конструктора.
new Random()— без сида. Начальное состояние берется автоматически, каждый экземпляр дает свою последовательность.new Random(int seed)— с явным сидом. Один и тот же сид всегда задает одну и ту же последовательность.
Важное уточнение про поведение без сида: в .NET Framework new Random() брал сид из системного таймера, поэтому два генератора, созданные в пределах ~15 мс, получали одинаковый сид и выдавали одинаковые числа. В .NET Core 2.0 и новее сид берется из потокобезопасного глобального источника, и эта коллизия ушла. С .NET 6 бессидовый Random вдобавок использует более быстрый и качественный алгоритм (xoshiro256**), а сеяный конструктор сохранил прежний алгоритм ради обратной совместимости вывода.
Методы генерации
| Метод | Что возвращает | Границы |
|---|---|---|
Next() |
неотрицательное целое | [0, int.MaxValue) |
Next(max) |
целое | [0, max), max не входит |
Next(min, max) |
целое | [min, max), max не входит |
NextInt64(...) |
64-битное целое (long) | как у Next, с .NET 6 |
NextDouble() |
дробное double | [0.0, 1.0) |
NextSingle() |
дробное float | [0.0, 1.0), с .NET 6 |
NextBytes(buffer) |
заполняет массив или Span байтами | 0-255 в каждый байт |
Число в заданном диапазоне
Главная практическая задача — число из диапазона. Здесь легко ошибиться на границе: у Next(min, max) нижняя граница входит, а верхняя — нет. Значит, для кубика нужен Next(1, 7), а не Next(1, 6).
Random rnd = new Random();
int dice = rnd.Next(1, 7); // 1..6 включительно
int percent = rnd.Next(101); // 0..100 включительно
double price = rnd.NextDouble() * 10 + 5; // дробное в [5, 15)
Типичная ошибка — забыть про исключенную верхнюю границу:
int dice = rnd.Next(1, 6); // хотели 1..6, а получаем только 1..5
Результат: 6 не выпадет никогда, кубик «сломан». Исправление — поднять верхнюю границу на единицу: rnd.Next(1, 7). То же правило работает для NextInt64 и для RandomNumberGenerator.GetInt32, о котором ниже.
Воспроизводимость: один сид — одна последовательность
Сид нужен, когда результат должен повторяться: отладка, юнит-тесты, воспроизведение бага. Два генератора с одинаковым сидом внутри одного запуска выдают идентичные последовательности — это легко проверить:
using System;
class Program
{
static void Main()
{
Random a = new Random(42);
Random b = new Random(42);
bool same = true;
for (int i = 0; i < 1000; i++)
if (a.Next() != b.Next())
same = false;
Console.WriteLine($"Последовательности совпали: {same}");
}
}
Вывод:
Последовательности совпали: True
Поэтому в тестах фиксируют сид (new Random(42)), чтобы «случайные» данные были одинаковыми при каждом прогоне. Оговорка: конкретные числа воспроизводимы в пределах одной версии .NET; между мажорными версиями рантайма алгоритм сеяного генератора Microsoft менять не гарантирует, поэтому одинаковый сид может дать другую серию на другой версии .NET. В боевом коде, где нужна настоящая непредсказуемость, сид, наоборот, не задают.
Потокобезопасность и Random.Shared
Экземпляр Random не потокобезопасен. Если один и тот же объект вызывать из нескольких потоков без синхронизации, его внутреннее состояние повреждается, и генератор может начать выдавать одни нули. Отдельная классическая ошибка — создавать новый Random в цикле:
// Плохо: новый Random на каждой итерации
for (int i = 0; i < 3; i++)
{
Random r = new Random();
Console.WriteLine(r.Next(100));
}
В .NET Framework это давало повтор одного значения, потому что генераторы получали одинаковый сид из таймера:
41
41
41
Исправление — один экземпляр на всю работу, а для многопоточного кода — статическое свойство Random.Shared (появилось в .NET 6), которое потокобезопасно по определению:
// Хорошо: общий потокобезопасный генератор
int x = Random.Shared.Next(1, 7);
double y = Random.Shared.NextDouble();
Random.Shared не требует создавать объект и корректно работает из разных потоков, поэтому в новом коде для несекретных задач это вариант по умолчанию.
Криптостойкие числа: RandomNumberGenerator
Random предсказуем: зная или подобрав сид, можно восстановить всю последовательность. Для паролей, токенов, соли и ключей это недопустимо — там нужен RandomNumberGenerator из System.Security.Cryptography. Класс абстрактный, но у него есть готовые статические методы, и создавать экземпляр не нужно.
using System;
using System.Security.Cryptography;
class Program
{
static void Main()
{
// криптостойкое целое из [0, 100)
int secure = RandomNumberGenerator.GetInt32(0, 100);
// 16 случайных байтов, например для токена
byte[] token = RandomNumberGenerator.GetBytes(16);
Console.WriteLine($"Число: {secure}");
Console.WriteLine($"Токен: {Convert.ToHexString(token)}");
}
}
Вывод непредсказуем и различается при каждом запуске, например:
Число: 73
Токен: 9F3A1C08B7E24D5106AABF77CC13920E
Старый класс RNGCryptoServiceProvider использовать не нужно: он помечен устаревшим (предупреждение SYSLIB0023) и заменен статическими методами RandomNumberGenerator. Криптогенератор надежнее, но медленнее обычного Random, поэтому брать его стоит именно под задачи безопасности, а не под игры и симуляции.
| Критерий | Random / Random.Shared | RandomNumberGenerator |
|---|---|---|
| Назначение | игры, симуляции, выборка, тесты | токены, пароли, ключи, соль |
| Предсказуемость | предсказуем при известном сиде | криптостойкий, непредсказуем |
| Скорость | быстрый | медленнее |
| Потоки | Random.Shared — да, обычный экземпляр — нет |
статические методы потокобезопасны |
| Namespace | System |
System.Security.Cryptography |
Выводы
- Случайное число в C# дает класс
Random:Next(min, max)— для целого в диапазоне (верхняя граница не входит),NextDouble()— для дробного из [0, 1). - Конструктор без сида дает новую последовательность;
new Random(seed)повторяет одну и ту же — это для тестов и отладки. - Экземпляр
Randomне потокобезопасен и не должен пересоздаваться в цикле; в .NET 6+ для общего доступа естьRandom.Shared. - Для паролей, токенов и ключей нужен криптостойкий
RandomNumberGenerator, а неRandom; устаревшийRNGCryptoServiceProviderне применять.
Где применяется и что учить дальше
Генерация случайных чисел встречается почти в любом проекте: раздача карт и бросок кубика в играх, перемешивание списка, случайная выборка данных, тестовые наборы с фиксированным сидом, а на стороне безопасности — генерация паролей, токенов сессий и криптографической соли. Разница между обычным и криптостойким генератором здесь принципиальна: перепутать их — это готовая уязвимость.
Освойте тему на практике
Если хочется освоить C# системно — от синтаксиса и типов до работы с библиотеками .NET и практических задач, посмотрите курс C# Basic. Оценить формат и уровень до старта помогают бесплатные открытые уроки Otus — живые занятия с преподавателями.
Смежные темы: Генерируем случайные числа в JavaScript, Квадрат числа в математике и программировании.
FAQ
Как перемешать список случайным образом? Пройдите массив с конца к началу и на каждом шаге меняйте текущий элемент с элементом на случайной позиции из Random.Shared.Next(0, i + 1) — это алгоритм Фишера-Йейтса. Сортировка «по случайному ключу» дает смещенный результат, так делать не стоит.
Можно ли получить случайное дробное число в произвольном диапазоне? Да: Random.Shared.NextDouble() дает значение из [0, 1), а линейное преобразование NextDouble() * (max - min) + min переносит его в нужный полуинтервал [min, max).
Почему нельзя брать Random для генерации пароля? Потому что последовательность Random восстанавливается по сиду, а сид угадывается или подбирается. Пароль или токен, построенный так, предсказуем; для этого есть криптостойкий RandomNumberGenerator.



