В этой статье я хочу показать решения проблем и задач, которые часто возникают при работе с классом 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, который даст тебе "самые" случайные числа. Спасибо, что ты дочитал до конца.
Телеграм
-