开发者

Why is the System.Random class not static?

开发者 https://www.devze.com 2023-02-09 19:54 出处:网络
When you use the System.Random class, you must make an instance of it. Why is it not static? Because if I want a random number between 0 and 9, I can开发者_如何转开发 use the static method, System.Ran

When you use the System.Random class, you must make an instance of it. Why is it not static? Because if I want a random number between 0 and 9, I can开发者_如何转开发 use the static method, System.Random.Next(int, int):

int ourRandomNumber = Random.Next(0,9);

So why isn't the class just static?


You wouldn't be able to use different seeds if it were static - the Random instance keeps track of that state. By default Random uses the current time as seed, but re-using a particular seed (i.e. new Random(42)) allows you to exactly repeat the sequence of random numbers - they will always be the same for the same seed. This aspect is very important in some applications. For example, Minecraft.


Random is not thread-safe. It's fine to have one instance of Random per thread, but you shouldn't use one instance from multiple threads concurrently. So you can't just have one instance of Random in a static variable and use that from the static method.

Also, making it static would remove the opportunity to give a specific seed, as mentioned by BrokenGlass.

Of course, it wouldn't be too hard to create static methods which took care of the thread safety when you don't need to specify a seed, but still leave the instance methods for when you want to use a particular instance. Personally I find it appropriate to treat "a source of random numbers" as a dependency to be injected where appropriate.

I have an article which covers some of this and which you may find useful.


Sometimes you want "something random", and you don't care about how that random value is arrived at. Having a static method for that could work.

However, sometimes you want to be able to repeatbly obtain the same random sequence. For that, you use the overload of the constructor that takes a seed value, and in that case, you don't want any other code that's using random numbers to consume one of the numbers from your sequence. In that case, you definitely need an instance of the class


Having a repeatable 'random' sequence is useful in testing scenarios.

For example, you could use it in testing a game engine to ensure that an AI was correctly picking targets, or paths - even if it has a random path evaluation.

Here is a very simplistic example. No matter how many times you run this test, it will always pick the same three cards when given the same base random number generator. This can be useful to ensure that the random number generator being used is the one supplied. And, for some reason if a new random number generator were introduced without altering the test, then the test would fail.

[TestMethod]
public void TestRandomPicking()
{
    Random random = new Random(1);
    Deck deck = new Deck(random);


    Assert.AreEqual(3, deck.PickCard().Value);
    Assert.AreEqual(1, deck.PickCard().Value);
    Assert.AreEqual(5, deck.PickCard().Value);

}

public class Deck
{
    public Deck()
    {
        _randomizer = new Random();
    }

    public Deck(Random randomizer)
    {
        _randomizer = randomizer; 
    }

    Random _randomizer;

    private List<Card> _cards = new List<Card>
                                    {
                                        new Card {Value = 1},
                                        new Card {Value = 2},
                                        new Card {Value = 3},
                                        new Card {Value = 4},
                                        new Card {Value = 5},
                                        new Card {Value = 6},
                                        new Card {Value = 7},
                                        new Card {Value = 8},
                                        new Card {Value = 9},
                                        new Card {Value = 10}
                                    };

    private List<Card> Cards { get { return _cards; } }

    public Card PickCard()
    {
        return Cards[_randomizer.Next(0, Cards.Count - 1)];
    }
}

public class Card
{
    public int Value { get; set; }
}


Often when one is debugging a program, improper behavior at one step may not have visible symptoms until many more steps have executed, by which time the original cause may have been obscured. In such cases, it can be very useful to be able to restart from scratch a program which malfunctioned on e.g. step 1,000,000 and have it run the first 999,990 or so steps exactly as it did the first time and then pause to let the programmer examine its state. Such debugging won't be possible if a program generates truly "random" numbers, but will be if it instead uses a pseudo-random generator which can be reloaded on the second run with the same seed as was used on the first run.


Why? It was a design mistake.

This late-1990s design it not an easy thing to use correctly. It pushes developers into the of failure. It is amazing how many developers are forced to invent their own horrific solutions:

new Random().Next(1, 100);

Not even counting the mental energy needed on the part of every new developer to needing a random number in C#.

The design was well-meaning at the time, using logical arguments why it shouldn't have a static method (you can implement the static class easily yourself using our provided building blocks). The problem is that the design leads itself to excessive memory use, as well as poor security for those who needed to depend on randomness.

Those developers who truly needed a specifically seeded RNG for testing: they could have instantiated the class, and build the functionality using the building blocks provided by the .NET team.

What's odd today is that some will take the design itself as the gospel only way to do it, then go on to justify the design when instead they're justifying our code has to be the way it has to be.

This design would not have survived the late 2000s design review process that went into WinRT - where they focused on the "pit of success". The team would have recognized the fundamental design flaws in the API as it is, and fixed it. Which is what they did - and that WinRT API is easy to call correctly:

  • public static IBuffer GenerateRandom(uint length);
  • public static uint GenerateRandomNumber();
0

精彩评论

暂无评论...
验证码 换一张
取 消