В этой статье я хочу показать решения проблем и задач, которые часто возникают при работе с классом Random в C#. Начнем с самого начала, класс Random мы используем для генерации псевдослучайных чисел. Давай создадим наш собственный класс MyRandom, который мы будем использовать на протяжении всей статьи. Создаем в нем приватную переменную _random типа Random, мы будем использовать её во всех следующих методах. В начальном состоянии наш класс выглядит вот так:
class MyRandom { private Random _random = new Random(); }
Одной из типичных ошибок является постоянное создание нового экземпляра класса Random в цикле. Конструктор Random, не принимающий параметров, принимает значение текущей даты и времени как seed (начальное состояние). Итерации в цикле "пробегают" очень быстро, что время «не успеет измениться» по их окончании; таким образом, все экземпляры Random получат в качестве начального состояния одинаковое значение и поэтому возвратят одинаковое псевдослучайное число. Из этих соображений мы делаем наше поле _random статическим и в качестве параметра конструктора передаем количество миллисекунд после старта системы (Environment.TickCount).
class MyRandom { private static int _seed = Environment.TickCount; // general instance private static Random _random = new Random(_seed); }
Думаешь инициализация на этом закончилась? А вот и нет, класс Random является не потокобезопасным. Это значит, что мы столкнемся с огромной головной болью если экземпляр нашего класса будет использоваться в двух и более потоках. Давай исправим это
class MyRandom { private static int _seed = Environment.TickCount; // thread safety instance private static ThreadLocal<Random> _random = new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref _seed)) ); }
Теперь наш класс выглядит "взрослым" и безопасным. У моих слушателей часто возникает вопрос - "как мне генерировать случайный enum?". Здесь все очень просто, предположим, что у тебя есть enum типа CarBrand
enum CarBrand { BMW, Audi, Bentley }
Метод, который будет генерировать случайный CarBrand должен найти минимальное и максимальное значение этого enum и сгенерировать случайные число (int) в этом диапазоне. Затем с помощью явного преобразования вернуть тип CarBrand
/// <summary> /// Get random value for specific enum /// </summary> /// <returns></returns> public CarBrand NextCarBrand() { int max = (int)Enum.GetValues(typeof(CarBrand)).Cast<CarBrand>().Max(); int min = (int)Enum.GetValues(typeof(CarBrand)).Cast<CarBrand>().Min(); CarBrand randomBrand = (CarBrand)_random.Value.Next(min, max + 1); return randomBrand; }
Я предпочитаю писать гибкие решения, поэтому давай напишем generic метод, который сможет сгенерировать любой enum
/// <summary> /// Generate random, but it should starts from 0 and no gaps /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public T NextGenericEnum<T>() where T : struct, IConvertible { if (!typeof(T).IsEnum) { throw new ArgumentException("T must be an enumerated type"); } T[] values = (T[])(Enum.GetValues(typeof(T))); T randomEnum = values[_random.Value.Next(0, values.Length)]; return randomEnum; }
В этом методе есть несколько ограничений: значения должны начинаться с 0 и между ними не должно быть пробелов (например enum со значениями 1, 2, 5 тебе не подойдет)
Для того, чтоб сгенерировать случайное значение типа bool, мы можем написать такой простой метод
/// <summary> /// Generate TRUE or FALSE /// </summary> /// <returns></returns> public bool NextBool() { // as simple as possible return _random.Value.Next(0, 2) == 1; }
Часто возникает задача сгенерировать случайное имя. Для решения этой задачи нам понадобиться массив заготовленных значений и на основании него мы получим результат. Находим 10-ть самых популярных имен и идем в бой.
/// <summary> /// Generate random string based on predefined values /// </summary> /// <returns></returns> public string NextName() { string[] predefinedNames = new string[] { "Emma","Noah","Mia", "William","Jacob","Liam", "Alice","Asher","Michael" }; int index = _random.Value.Next(0, predefinedNames.Length); return predefinedNames[index]; }
Теперь мы можем использовать наш класс не боясь поток и повторяющихся последовательностей
static void Main(string[] args) { const int MaxGeneratedItems = 3; MyRandom myRandom = new MyRandom(); // random specific enum Console.WriteLine("Random car's brands:"); for (int i = 0; i < MaxGeneratedItems; i++) { CarBrand cb= myRandom.NextCarBrand(); Console.WriteLine(cb); } // random generic enum Console.WriteLine("\nRandom colors:"); for (int i = 0; i < MaxGeneratedItems; i++) { ConsoleColor cc = myRandom.NextGenericEnum<ConsoleColor>(); Console.ForegroundColor = cc; Console.WriteLine(cc); } Console.ForegroundColor = ConsoleColor.Gray; // random bool value Console.WriteLine("\nRandom bool:"); Console.WriteLine(myRandom.NextBool()); // random string based on predefined values Console.WriteLine("\nRandom name:"); string name = myRandom.NextName(); Console.WriteLine(name); Console.ReadKey(); }
Полный исходный код находится в прикрепленном архив
P.S. Если генерация случайных числел, является основных и наиболее важным компонентом твоего приложения, то самый хороший вариант использовать АПИ сайта Random.org, который даст тебе "самые" случайные числа. Спасибо, что ты дочитал до конца.
dmytro