Random
These methods extend System.Random so you can pick an item, deal a hand, shuffle a list, or make a random price, date, string, enum value or GUID in one call. Use them for test data, samples, demos and games. They are not for security: none of them is cryptographically secure.
All methods live in the System namespace, so you do not need a using directive.
At a glance
| Method | What it does |
|---|---|
NextItem | Picks one item from a list, or one of two or more values. |
NextItems | Picks a number of distinct items (no repeats), in random order. |
NextBool | Flips a fair coin, or returns true with a given probability. |
NextDouble | Returns a double in [min, max). |
NextDecimal | Returns a decimal in [min, max). |
NextGaussian | Returns a normally distributed value (mean 0 and standard deviation 1, or your own). |
NextString | Returns a string of random letters and digits, or of characters from your alphabet. |
NextEnum | Picks one declared member of an enum. |
NextGuid | Returns a version-4-shaped Guid built from the Random, repeatable for a seed. |
NextDateTime | Returns a DateTime in [min, max) with tick precision, keeping the Kind of min. |
NextTimeSpan | Returns a TimeSpan in [min, max) with tick precision. |
Shuffle | Shuffles any IList<T> in place (Fisher–Yates). |
Common rules
- Every method draws only from the
Randomyou call it on. Passnew Random(seed)and you get the same results on every run (see Determinism with a seed). - A
nullreceiver throwsArgumentNullExceptionwith the parameter namerandom. Anulllist, array or alphabet throwsArgumentNullExceptionwith its own parameter name. - Ranges are half-open: the lower bound is included and the upper bound is excluded. When the two bounds are equal, the method returns the lower bound and does not throw. One exception:
NextDecimalwith two equal bounds that use all 28–29 digits ofdecimalcan return a value one unit below the bound, and atdecimal.MaxValueordecimal.MinValueit can throwOverflowException(see NextDecimal). - An upper bound below the lower bound throws
ArgumentOutOfRangeExceptionwith the parameter namemaxExclusive. - The example values on this page come from the stated seed on .NET 10. A different seed, runtime or library build gives different values.
System.Random is predictable. Do not use these methods for passwords, tokens, reset codes or ids that must be hard to guess. Use System.Security.Cryptography.RandomNumberGenerator for those.
Picking items
NextItem
NextItem picks one item, with each item equally likely. Pass an IReadOnlyList<T> (an array, a List<T> and most collections qualify), or pass two or more values directly.
string[] reviewers = { "Alice", "Bob", "Carmen", "Dmitri" };
string reviewer = new Random(42).NextItem(reviewers); // → "Carmen"
string status = new Random(42).NextItem("Pending", "Paid", "Shipped"); // → "Shipped"
The value overload has two named parameters before the params array: NextItem(T first, T second, params T[] others). This is on purpose. With a single params T[], the call random.NextItem(list) would bind with T set to the list type and return the list itself.
An empty list throws ArgumentException with the parameter name items ("There are no items to pick from."). In the value overload, a null others array throws ArgumentNullException with the parameter name others.
NextItems
NextItems(items, count) picks count different items: no item is picked twice. The result is in random order. Use it to deal cards, choose a sample of orders for an audit, or pick winners. The source list is not changed.
List<string> skus = new List<string> { "SKU-100", "SKU-200", "SKU-300", "SKU-400", "SKU-500" };
IReadOnlyList<string> featured = new Random(42).NextItems(skus, 3); // → ["SKU-400", "SKU-200", "SKU-300"]
// skus is unchanged: ["SKU-100", "SKU-200", "SKU-300", "SKU-400", "SKU-500"]
A count of 0 returns an empty list. A count equal to the list size returns every item in a random order.
"Distinct" means distinct positions. If the source list holds the same value twice, the result can hold it twice.
A negative count, or a count greater than the number of items, throws ArgumentOutOfRangeException with the parameter name count.
Booleans and numbers
NextBool
NextBool() flips a fair coin. NextBool(probabilityOfTrue) returns true with the given chance, from 0 (never) to 1 (always). Use it to turn a feature on for a share of users, or to mark about one order in ten as discounted.
Random random = new Random(42);
bool[] flips = Enumerable.Range(0, 5).Select(_ => random.NextBool()).ToArray();
// → [true, false, false, true, false]
Random weighted = new Random(42);
int discountedOrders = Enumerable.Range(0, 1000).Count(_ => weighted.NextBool(0.1)); // → 111
bool never = new Random(42).NextBool(0); // → false
bool always = new Random(42).NextBool(1); // → true
A probability below 0, above 1, or double.NaN throws ArgumentOutOfRangeException with the parameter name probabilityOfTrue.
NextDouble
NextDouble(minInclusive, maxExclusive) returns a uniformly distributed double in [minInclusive, maxExclusive). The built-in Random.NextDouble() without arguments still returns a value in [0, 1).
double latitude = new Random(42).NextDouble(-90.0, 90.0); // → 30.25916386407762
double fixedValue = new Random(42).NextDouble(3.5, 3.5); // → 3.5
The result never equals maxExclusive. If floating-point rounding would land on it in a very small range, the method returns minInclusive.
A minInclusive that is NaN or infinite throws ArgumentOutOfRangeException with the parameter name minInclusive. A maxExclusive that is less than minInclusive, NaN or infinite throws ArgumentOutOfRangeException with the parameter name maxExclusive.
NextDecimal
NextDecimal(minInclusive, maxExclusive) returns a uniformly distributed decimal in [minInclusive, maxExclusive). Use it for prices, totals and balances.
decimal total = new Random(42).NextDecimal(5m, 500m); // → 335.712700626213290m
decimal price = Math.Round(new Random(42).NextDecimal(5m, 500m), 2); // → 335.71m
The randomness comes from one double, so the result has about 15 significant digits of resolution, not the full 28–29 digits of decimal. Round the result to the scale you need, as in the example.
The method does not overflow on very wide ranges. NextDecimal(decimal.MinValue, decimal.MaxValue) works. Equal bounds are the weak spot, because the result is computed by blending the two bounds and rounding. When both bounds use all 28–29 significant digits (for example 12345678901234567890123456789m), some draws return a value one unit below the bound. When both bounds are decimal.MaxValue, or both are decimal.MinValue, rounding can push the result past the limit, and the call throws OverflowException for some draws.
A maxExclusive less than minInclusive throws ArgumentOutOfRangeException with the parameter name maxExclusive.
NextGaussian
NextGaussian() returns a sample from the standard normal distribution: mean 0, standard deviation 1. NextGaussian(mean, standardDeviation) shifts and scales it. Use it for values that cluster around a typical value, such as response times, heights or basket sizes. It uses the Box–Muller transform.
double z = new Random(42).NextGaussian(); // → 0.940176481011304
double height = new Random(42).NextGaussian(170, 10); // → 179.40176481011304
double latencyMs = new Random(42).NextGaussian(mean: 500, standardDeviation: 120); // → 612.8211777213564
double constant = new Random(42).NextGaussian(42, 0); // → 42
A standard deviation of 0 always returns the mean.
A normal sample can be any real number. NextGaussian(500, 120) can return a negative latency. Clamp the result yourself, for example with Math.Max(0, value), when negative values make no sense.
A negative or NaN standardDeviation throws ArgumentOutOfRangeException with the parameter name standardDeviation.
Text, enums and ids
NextString
NextString(length) returns a string of ASCII letters and digits (A–Z, a–z, 0–9). NextString(length, alphabet) draws each character from your own alphabet instead.
string couponCode = new Random(42).NextString(8); // → "pIHgKQsf"
string pin = new Random(42).NextString(6, "0123456789"); // → "611512"
string empty = new Random(42).NextString(0); // → ""
Each character of the alphabet is equally likely. A character that appears twice in the alphabet is twice as likely.
A negative length throws ArgumentOutOfRangeException with the parameter name length. An empty alphabet throws ArgumentException and a null alphabet throws ArgumentNullException, both with the parameter name alphabet.
Anyone who can guess or recover the seed can predict a code made by NextString. Use it for test data and display codes, not for anything a user must keep secret.
NextEnum
NextEnum<T>() picks one of the members declared by the enum T, each declaration equally likely.
DayOfWeek deliveryDay = new Random(42).NextEnum<DayOfWeek>(); // → DayOfWeek.Thursday
The method picks from declarations, not from distinct values. If two members share a value, that value is picked twice as often. For a [Flags] enum, it returns only declared members; a combination such as Read | Write comes back only if the enum declares it as a member.
An enum that declares no members throws InvalidOperationException ("Enum '...' declares no members.").
NextGuid
NextGuid() fills 16 bytes from the Random and sets the version and variant bits of a version 4 GUID. The result looks like a Guid.NewGuid() value, but a seeded Random gives the same id on every run. Use it for stable ids in test fixtures and snapshots.
Guid customerId = new Random(42).NextGuid(); // → 96ba173e-04ae-4bcd-9986-9e56f0adbf3a
bool sameEveryRun = new Random(42).NextGuid() == new Random(42).NextGuid(); // → true
In the "D" format, the character at index 14 is always 4 and the character at index 19 is always one of 8, 9, a or b.
These GUIDs are only as unpredictable as the Random that made them. Do not use them for session ids, API keys or links that grant access. Use Guid.NewGuid() for real ids.
Dates and durations
NextDateTime
NextDateTime(minInclusive, maxExclusive) returns a DateTime in [minInclusive, maxExclusive) with tick precision. The result has the DateTimeKind of minInclusive.
DateTime yearStart = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime yearEnd = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime placedAt = new Random(42).NextDateTime(yearStart, yearEnd);
// → 2024-03-22T18:47:14.0731989Z (Kind = Utc)
The method does not read the system clock. If you want "some time in the last 30 days", compute the bounds from a now value you pass in.
A maxExclusive earlier than minInclusive throws ArgumentOutOfRangeException with the parameter name maxExclusive. The comparison uses ticks and ignores Kind, so pass both bounds in the same kind.
NextTimeSpan
NextTimeSpan(minInclusive, maxExclusive) returns a TimeSpan in [minInclusive, maxExclusive) with tick precision. Use it for simulated delays, session lengths or retry jitter in tests.
TimeSpan sessionLength = new Random(42).NextTimeSpan(TimeSpan.FromMinutes(5), TimeSpan.FromHours(1));
// → 00:12:11.2764937
A maxExclusive less than minInclusive throws ArgumentOutOfRangeException with the parameter name maxExclusive. Negative durations are allowed as long as the bounds are in order.
Shuffling
Shuffle
Shuffle(list) reorders an IList<T> in place with the Fisher–Yates algorithm, so every order is equally likely. It returns nothing. Use it on a List<T>, an ObservableCollection<T> or any other mutable list.
List<string> questions = new List<string> { "Q1", "Q2", "Q3", "Q4", "Q5" };
new Random(7).Shuffle(questions);
// questions → ["Q3", "Q1", "Q5", "Q4", "Q2"]
An empty list or a one-item list is left as it is. A null list throws ArgumentNullException with the parameter name list. A read-only list, such as a ReadOnlyCollection<T> with two or more items, throws NotSupportedException from its indexer.
If you need a shuffled copy and want to keep the original, use InRandomOrder instead.
Arrays and the .NET 8 Random.Shuffle
.NET 8 added the instance methods Random.Shuffle<T>(T[]) and Random.Shuffle<T>(Span<T>). C# always prefers an applicable instance method over an extension method. So on .NET 8 and later, random.Shuffle(array) calls the built-in method, and random.Shuffle(list) calls this library's method. Both shuffle in place and give a uniform result.
The two algorithms walk the list in different directions, so the same seed gives a different order for an array and for a list:
int[] array = { 1, 2, 3, 4, 5 };
List<int> list = new List<int> { 1, 2, 3, 4, 5 };
new Random(42).Shuffle(array); // built-in Random.Shuffle(T[]) on .NET 8+
new Random(42).Shuffle(list); // this library's Shuffle(IList<T>)
// array → [4, 2, 3, 5, 1]
// list → [3, 2, 5, 1, 4]
To force the library's method on an array, cast it: random.Shuffle((IList<int>)array). On runtimes before .NET 8 there is no built-in method, so arrays also use this library's Shuffle.
Shuffle here.NET 10 adds a LINQ Shuffle operator for sequences. This library does not define a sequence method with that name, so the two do not clash. The library's copy-and-shuffle method for sequences is InRandomOrder.
Determinism with a seed
Every method on this page draws only from the Random you pass. Two Random instances created with the same seed give the same results, as long as you make the same calls in the same order.
string first = new Random(42).NextString(16);
string second = new Random(42).NextString(16);
bool same = first == second; // → true
new Random() without a seed is seeded randomly, so its results change on every run. Use it when you want variety and do not need to reproduce a run.
Keep these limits in mind:
- Call order matters. Each call moves the generator forward. Adding a field to a generator, or calling the methods in a different order, changes every value that follows.
- The library build matters.
NextDateTimeandNextTimeSpanuseRandom.NextInt64in the .NET 10 build and a byte-based method in thenetstandard2.1build (which a .NET 8 app uses). The same seed gives different dates and durations on the two builds, and every later call draws different random numbers, so later values can differ too. - The runtime matters. .NET does not promise that a seeded
Randomgives the same sequence on every future version. Do not store seeds as a long-term way to rebuild data. Randomis not thread-safe. Do not share one seeded instance across threads.Random.Shared(.NET 6+) is thread-safe but cannot be seeded, so it is not repeatable.
Seed your test data with a fixed number, or log a randomly chosen seed in the test output. When a test fails, run it again with the same seed to get the same data.
Example: a test-data generator
This generator builds orders for tests and demos. One seeded Random drives every field, so the same seed always gives the same orders on the same runtime and library build.
public enum OrderStatus
{
Pending,
Paid,
Shipped,
Cancelled,
}
public sealed record Order(
Guid Id,
string Number,
string Customer,
OrderStatus Status,
decimal Total,
DateTime PlacedAt,
bool IsGift);
public sealed class OrderGenerator
{
private static readonly string[] Customers = { "Alice Martin", "Bob Chen", "Carmen Diaz", "Dmitri Ivanov" };
private readonly Random _random;
public OrderGenerator(int seed)
{
_random = new Random(seed);
}
public IReadOnlyList<Order> Generate(int count)
{
DateTime from = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime to = new DateTime(2024, 7, 1, 0, 0, 0, DateTimeKind.Utc);
List<Order> orders = new List<Order>(count);
for (int i = 0; i < count; i++)
{
orders.Add(new Order(
Id: _random.NextGuid(),
Number: $"ORD-{_random.NextString(6, "0123456789")}",
Customer: _random.NextItem(Customers),
Status: _random.NextEnum<OrderStatus>(),
Total: Math.Round(_random.NextDecimal(5m, 500m), 2),
PlacedAt: _random.NextDateTime(from, to),
IsGift: _random.NextBool(0.2)));
}
return orders;
}
}
Printing is the point here, so this snippet writes each order to the console:
using System.Globalization;
OrderGenerator generator = new OrderGenerator(seed: 2024);
foreach (Order order in generator.Generate(count: 4))
{
string placedAt = order.PlacedAt.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
Console.WriteLine(
$"{order.Number} {order.Customer,-13} {order.Status,-9} {order.Total,7} {placedAt} gift={order.IsGift}");
}
ORD-684074 Alice Martin Pending 36.44 2024-05-11 05:57 gift=False
ORD-875958 Bob Chen Shipped 321.09 2024-03-22 06:42 gift=False
ORD-398854 Alice Martin Paid 139.51 2024-03-05 14:55 gift=False
ORD-748363 Bob Chen Pending 192.95 2024-05-13 23:34 gift=False
On .NET 8 the first order's id, number, customer, status and total are the same, but its PlacedAt differs, and so do the orders after it, because a .NET 8 app uses the netstandard2.1 build and NextDateTime draws differently there. See Determinism with a seed.
In a test, pin the seed and compare two runs:
[Fact]
public void Generate_WhenSeedIsTheSame_ShouldReturnTheSameOrders()
{
// Arrange
OrderGenerator first = new OrderGenerator(seed: 2024);
OrderGenerator second = new OrderGenerator(seed: 2024);
// Act
IReadOnlyList<Order> firstRun = first.Generate(count: 50);
IReadOnlyList<Order> secondRun = second.Generate(count: 50);
// Assert
secondRun.Should().Equal(firstRun);
}
See also
- Querying sequences:
InRandomOrder, a shuffled copy of any sequence. - Numbers: helpers for the numbers you generate.
- Enums: names, descriptions and parsing for the values
NextEnumreturns. - Dates: helpers for the dates
NextDateTimereturns.