如何在C#中保存Random Generator的状态?

出于测试目的,我使用给定的种子创建随机数(即不基于当前时间)。

因此整个计划是确定性的。

如果发生了什么事情,我希望能够在“事件发生前不久”迅速恢复一个点。

因此,我需要能够将System.Random恢复到以前的状态。

有没有办法提取种子,我可以用来重建随机生成器?

根据这里给出的答案 ,我写了一个小class来帮助保存和恢复状态。

 void Main() { var r = new Random(); Enumerable.Range(1, 5).Select(idx => r.Next()).Dump("before save"); var s = r.Save(); Enumerable.Range(1, 5).Select(idx => r.Next()).Dump("after save"); r = s.Restore(); Enumerable.Range(1, 5).Select(idx => r.Next()).Dump("after restore"); s.Dump(); } public static class RandomExtensions { public static RandomState Save(this Random random) { var binaryFormatter = new BinaryFormatter(); using (var temp = new MemoryStream()) { binaryFormatter.Serialize(temp, random); return new RandomState(temp.ToArray()); } } public static Random Restore(this RandomState state) { var binaryFormatter = new BinaryFormatter(); using (var temp = new MemoryStream(state.State)) { return (Random)binaryFormatter.Deserialize(temp); } } } public struct RandomState { public readonly byte[] State; public RandomState(byte[] state) { State = state; } } 

您可以在LINQPad中测试此代码。

这就是我提出的:

基本上它提取私有种子数组。 您只需要小心恢复“非共享”数组。

 var first = new Random(100); // gain access to private seed array of Random var seedArrayInfo = typeof(Random).GetField("SeedArray", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); var seedArray = seedArrayInfo.GetValue(first) as int[]; var other = new Random(200); // seed doesn't matter! var seedArrayCopy = seedArray.ToArray(); // we need to copy since otherwise they share the array! seedArrayInfo.SetValue(other, seedArrayCopy); for (var i = 10; i < 1000; ++i) { var v1 = first.Next(i); var v2 = other.Next(i); Debug.Assert(v1 == v2); } 

System.Random没有密封,其方法是虚拟的,因此您可以创建一个类来计算为跟踪状态而生成的数字的数量,如:

 class StateRandom : System.Random { Int32 _numberOfInvokes; public Int32 NumberOfInvokes { get { return _numberOfInvokes; } } public StateRandom(int Seed, int forward = 0) : base(Seed) { for(int i = 0; i < forward; ++i) Next(0); } public override Int32 Next(Int32 maxValue) { _numberOfInvokes += 1; return base.Next(maxValue); } } 

用法示例:

 void Main() { var a = new StateRandom(123); a.Next(100); a.Next(100); a.Next(100); var state = a.NumberOfInvokes; Console.WriteLine(a.Next(100)); Console.WriteLine(a.Next(100)); Console.WriteLine(a.Next(100)); // use 'state - 1' to be in the previous state instead var b = new StateRandom(123, state); Console.WriteLine(b.Next(100)); Console.WriteLine(b.Next(100)); Console.WriteLine(b.Next(100)); } 

输出:

 81 73 4 81 73 4 

我知道这个问题已经得到了解答,但是,我想提供我自己的实现,目前正用于我正在创建的游戏。 基本上,我使用.NET的Random.cs代码创建了自己的Random类。 我不仅添加了更多function,而且还添加了一种方法来保存当前生成器状态并将其加载到仅有59个索引的数组中。 最好这样做,而不是其他一些评论建议“迭代x次以手动恢复状态。这是一个坏主意,因为在RNG重游戏中你的随机生成器状态理论上可以进入数十亿的调用,这意味着你会 – 根据他们 – 需要迭代十亿次以恢复每次启动期间最后一次播放会话的状态。当然,这可能只需要一秒钟,但我认为它仍然太脏了,尤其是当你可以简单地提取随机生成器的当前状态并在需要时重新加载它,并且只占用1个数组(59个内存索引)。

这只是一个想法,所以从我的代码中得到你想要的。

这是完整的来源,这个太大了,不能在这里发布:

GrimoireRandom.cs

对于那些只想要实现问题的人,我会在这里发布。

  public int[] GetState() { int[] state = new int[59]; state[0] = _seed; state[1] = _inext; state[2] = _inextp; for (int i = 3; i < this._seedArray.Length; i++) { state[i] = _seedArray[i - 3]; } return state; } public void LoadState(int[] saveState) { if (saveState.Length != 59) { throw new Exception("GrimoireRandom state was corrupted!"); } _seed = saveState[0]; _inext = saveState[1]; _inextp = saveState[2]; _seedArray = new int[59]; for (int i = 3; i < this._seedArray.Length; i++) { _seedArray[i - 3] = saveState[i]; } } 

除了DiceType枚举和OpenTK Vector3结构之外,我的代码是完全独立的。 这两个function都可以删除,它将适合您。

有一种替代解决方案,(1)避免需要记住所有先前生成的数字; (2)不涉及访问Random的私有字段; (3)不需要序列化; (4)不需要循环回通过随机调用的次数; (5)不需要为内置的Random类创建替换。

诀窍是通过生成随机数来获取状态,然后将随机数生成器重新设置为此值。 然后,将来,通过将随机数发生器重新接种到该值,可以始终返回到该状态。 换句话说,为了节省状态和重新种植,我们在随机数序列中“刻录”一个数字。

实施如下。 请注意,可以访问Generator属性以实际生成数字。

 public class RestorableRandom { public Random Generator { get; private set; } public RestorableRandom() { Generator = new Random(); } public RestorableRandom(int seed) { Generator = new Random(seed); } public int GetState() { int state = Generator.Next(); Generator = new Random(state); return state; } public void RestoreState(int state) { Generator = new Random(state); } } 

这是一个简单的测试:

 [Fact] public void RestorableRandomWorks() { RestorableRandom r = new RestorableRandom(); double firstValueInSequence = r.Generator.NextDouble(); int state = r.GetState(); double secondValueInSequence = r.Generator.NextDouble(); double thirdValueInSequence = r.Generator.NextDouble(); r.RestoreState(state); r.Generator.NextDouble().Should().Be(secondValueInSequence); r.Generator.NextDouble().Should().Be(thirdValueInSequence); } 

存储随机数发生器运行的次数,如Xi Huan所写。

然后简单地循环以恢复旧状态。

 Random rand= new Random(); int oldRNGState = 439394; for(int i = 1; i < oldRNGState-1; i++) { rand.Next(1) } 

现在就做

 int lastOldRNGValue = rand.Next(whateverValue); 

没有办法解决这个问题,你必须循环回到你离开的地方。