Skip to content

Commit 58f431b

Browse files
authored
Fix Random.GetItems observably breaking change in produced sequence (#108017)
Developers, often in tests, rely on seeded Random instances producing the same sequence of values on every use. We made a change in .NET 9, though, that changed the sequence GetItems produces, due to employing a different algorithm. This fixes that special-case to only be used when the developer couldn't rely on the results being deterministic, namely when using either `new Random()` or `Random.Shared`. If a seed is provided or if a custom derived implementation is used, it falls back to the old behavior.
1 parent 4d37f40 commit 58f431b

File tree

1 file changed

+6
-1
lines changed
  • src/libraries/System.Private.CoreLib/src/System

1 file changed

+6
-1
lines changed

src/libraries/System.Private.CoreLib/src/System/Random.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,16 @@ public void GetItems<T>(ReadOnlySpan<T> choices, Span<T> destination)
199199

200200
// The most expensive part of this operation is the call to get random data. We can
201201
// do so potentially many fewer times if:
202+
// - the instance was constructed as `new Random()` or is `Random.Shared`, such that it's not seeded nor is it
203+
// a custom derived type. We don't want to observably change the deterministically-produced sequence from previous releases.
202204
// - the number of choices is <= 256. This let's us get a single byte per choice.
203205
// - the number of choices is a power of two. This let's us use a byte and simply mask off
204206
// unnecessary bits cheaply rather than needing to use rejection sampling.
205207
// In such a case, we can grab a bunch of random bytes in one call.
206-
if (BitOperations.IsPow2(choices.Length) && choices.Length <= 256)
208+
ImplBase impl = _impl;
209+
if ((impl is null || impl.GetType() == typeof(XoshiroImpl)) &&
210+
BitOperations.IsPow2(choices.Length) &&
211+
choices.Length <= 256)
207212
{
208213
Span<byte> randomBytes = stackalloc byte[512]; // arbitrary size, a balance between stack consumed and number of random calls required
209214
while (!destination.IsEmpty)

0 commit comments

Comments
 (0)