Description
EDIT @stephentoub 02/24/2021 to updated proposal:
namespace System
{
public class Random
{
public static Random Shared { get; } // instance is thread-safe, can be used from any thread
...
}
}
Background and Motivation
The Random
class is a very frequently used API from even non experienced developers - it's used anywhere from creating simple programs for beginners ("let's create a guessing game"), to generating random data for unit tests and benchmarks, to all sorts of other applications (eg. shuffling a music playlist). Not everyone is aware of some of the specific details of this class, and the current API surface doesn't help in this aspect, leading a number of developers to use the type incorrectly and stumbling upon seemingly weird bugs ("why are these random numbers all zeros?!").
In particular:
- The
Random
class doesn't have static APIs, which make is less intuitive to use. Most of the time when a developer just needs a one-shot usage of an API of some class, they'd just type the type name (pun not intended) and look at the available static APIs, rather than trying to instantiate a new throwaway instance to use in that single line of code. I believe this is the same rationale that led to Add static hash helper methods #17590. - To work around 1., I've seen a number of developers just storing some
Random
instance into a static field. This will inevitably make the code both more verbose (need to define a field) as well as more error prone, becauseRandom
is not thread safe and will just break down if used concurrently. I've seen a few developers discovering this issue, and I'd be lying if I said I didn't make this same exact mistake as well a few years ago 😄
The proposal is to add a new static, thread-static Random
instance that devs could access. This would simply return some Random
instance that they could use without worrying about concurrently or creating an instance. Making the instance thread-static would allow the BCL to reuse the same Random
class without the need to actually come up with a new thread-safe implementation that could be shared across threads.
Proposed API
namespace System
{
public class Random
{
public static Random Current { get; }
}
}
Here I used the Current
name from conceptually similar APIs such as SynchronizationContext.Current
, which specifically refers to an instance that's directly related to the current thread. Other examples can be found in APIs such as Application.Current
and Window.Current
, or also a similar naming scheme is also used by APIs such as DispatcherQueue.GetForCurrentThread
.
Usage Examples
For one-liners:
// BEFORE
int number = new Random().Next();
// AFTER
int number = Random.Current.Next();
For cases where multiple usages would be needed:
// BEFORE
var numbers = new int[100];
var random = new Random();
foreach (ref var x in numbers.AsSpan())
{
// Can't use the batched API if we want a given range
x = random.Next(100);
}
// AFTER
var numbers = new int[100];
foreach (ref var x in numbers.AsSpan())
{
x = Random.Current.Next(100);
}
Note how in the second example we no longer need to also have the extra verbosity of storing a Random
instance in a local.
Alternative Designs
I had two other possible ideas:
- Expose static versions of the various APIs. That won't work since you can't overload just between static/instance methods in C#, and just having different names for the static versions would be extremely awkward, so no.
- Expose a thread-safe
Shared
instance (à laArrayPool<T>.Shared
). That would make the implementation unnecessarily more complex, as a whole newRandom
type would have to be written.
Risks
Technically, devs could still just get Random.Current
and store it in a field, then use it concurrently. But I mean, they could already do (and in fact, they do) that today anyway. Having a static field would effectively nudge them towards not doing this at all anymore, so I think the API addition would actually reduce the overall risks related to the usage of this type.