Генерация случайных чисел на C# или класс Random под микроскопом

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

Скачать

бесплатно

После оплаты Вы получите работу на электронную почту.
csharp random numbers In depth.zip
34359
Оцени работу

рейтинг

Поделись работой с друзьями

Комментарии (1)

dmytro

/ /

Оставить комментарий

Ты не можешь комментировать

Только зарегестрированые пользователи имеют возможность комментировать работы
Генерация случайных чисел на C# или класс Random под микроскопом
В этой статье можно найти ответы на следующие вопросы: как правильно инициализировать класс Random, как получить случайный enum, как генерировать случайную строку, как генерировать простой тип bool, как сделать Random потокобезопасным
Категория: Образование
Стоимость: Бесплатно