Skip to content

Proposal: add a static, thread-static Random.Current instance #43887

Closed

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:

  1. 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.
  2. 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, because Random 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 (à la ArrayPool<T>.Shared). That would make the implementation unnecessarily more complex, as a whole new Random 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions