是否有办法获取System.Random的实际状态?
本文关键字:状态 Random System 获取 是否 | 更新日期: 2023-09-27 18:15:13
我希望能够获得系统的实际状态或种子或任何东西。随机,所以我可以关闭一个应用程序,当用户重新启动它,它只是"重新种子"它与存储的一个,并继续像它从未关闭过。
有可能吗?
使用Jon的想法,我想出了这个来测试它;
static void Main(string[] args)
{
var obj = new Random();
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("c:''test.txt", FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, obj);
stream.Close();
for (var i = 0; i < 10; i++)
Console.WriteLine(obj.Next().ToString());
Console.WriteLine();
formatter = new BinaryFormatter();
stream = new FileStream("c:''test.txt", FileMode.Open, FileAccess.Read, FileShare.Read);
obj = (Random)formatter.Deserialize(stream);
stream.Close();
for (var i = 0; i < 10; i++)
Console.WriteLine(obj.Next().ToString());
Console.Read();
}
它是可序列化的,所以你可能会发现你可以只使用BinaryFormatter
并保存字节数组…
示例代码:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public class Program
{
public static void Main(String[] args)
{
Random rng = new Random();
Console.WriteLine("Values before saving...");
ShowValues(rng);
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, rng);
Console.WriteLine("Values after saving...");
ShowValues(rng);
stream.Position = 0; // Rewind ready for reading
Random restored = (Random) formatter.Deserialize(stream);
Console.WriteLine("Values after restoring...");
ShowValues(restored);
}
static void ShowValues(Random rng)
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(rng.Next(100));
}
}
}
样本运行的结果是有希望的:
Values before saving...
25
73
58
6
33
Values after saving...
71
7
87
3
77
Values after restoring...
71
7
87
3
77
无可否认,我并不热衷于内置序列化,但如果这是一个相当快速和肮脏的hack,它应该是好的…
这是一个dotnet 5.0
或更高版本的结构体,可以保存和加载System.Random
实例的当前状态。这当然可以改进,但它应该发挥作用。此外,我很确定它只适用于用Seed
参数(System.Random(int Seed)
)创建的System.Random
实例。在调试时,我注意到使用System.Random
的无参数构造函数会产生另一种名为XoshiroImpl
的ImplBase
类型,该类型缺乏种子支持,因此使我的代码不可用。
可以用System.TEx
Net5CompatSeedImpl
class:https://github.com/dotnet/runtime/blob/f7633f498a8be34bee739b240a0aa9ae6a660cd9/src/libraries/System.Private.CoreLib/src/System/Random.Net5CompatImpl.cs L283
XoshiroImpl
class:https://github.com/dotnet/runtime/blob/4017327955f1d8ddc43980eb1848c52fbb131dfc/src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro128StarStarImpl.cs
using System.Reflection;
public struct RandomState
{
static RandomState()
{
ImplInfo = typeof(Random).GetField("_impl", BindingFlags.Instance | BindingFlags.NonPublic)!;
PrngInfo = Type.GetType(Net5CompatSeedImplName)!.GetField("_prng", BindingFlags.Instance | BindingFlags.NonPublic)!;
Type compatPrngType = Type.GetType(CompatPrngName)!;
seedArrayInfo = compatPrngType.GetField(SeedArrayInfoName, BindingFlags.Instance | BindingFlags.NonPublic)!;
inextInfo = compatPrngType.GetField(InextInfoName, BindingFlags.Instance | BindingFlags.NonPublic )!;
inextpInfo = compatPrngType.GetField(InextpInfoName, BindingFlags.Instance | BindingFlags.NonPublic)!;
}
public const string CompatPrngName = "System.Random+CompatPrng";
public const string Net5CompatSeedImplName = "System.Random+Net5CompatSeedImpl";
public const string SeedArrayInfoName = "_seedArray";
public const string InextInfoName = "_inext";
public const string InextpInfoName = "_inextp";
private static FieldInfo ImplInfo;
private static FieldInfo PrngInfo;
private static FieldInfo seedArrayInfo;
private static FieldInfo inextInfo;
private static FieldInfo inextpInfo;
public int[] seedState { get; set; }
public int inext { get; set; }
public int inextp { get; set; }
public static RandomState GetState(Random random)
{
object o = GetCompatPrng(random);
RandomState state = new RandomState();
state.seedState = (int[])seedArrayInfo.GetValue(o)!;
state.inext = (int)inextInfo.GetValue(o)!;
state.inextp = (int)inextpInfo.GetValue(o)!;
return state;
}
//Random > Impl > CompatPrng
public static object GetImpl(Random random) => ImplInfo.GetValueDirect(__makeref(random))!;
public static object GetCompatPrng(object impl) => PrngInfo.GetValueDirect(__makeref(impl))!;
public static object GetCompatPrng(Random random)
{
object impl = GetImpl(random);
return PrngInfo.GetValueDirect(__makeref(impl))!;
}
public static void SetState(Random random, RandomState state)
{
object impl = GetImpl(random);
TypedReference implref = __makeref(impl);
object prng = PrngInfo.GetValueDirect(implref)!;
seedArrayInfo.SetValue(prng, state.seedState);
inextInfo.SetValue(prng, state.inext);
inextpInfo.SetValue(prng, state.inextp);
PrngInfo.SetValueDirect(implref, prng);
//Testing. can be removed.
/*object o2 = GetCompatPrng(impl);
DehFwk.Debug.Log("orig: " + ((int[])seedArrayInfo.GetValue(prng)!).Length + "| new: " + ((int[])seedArrayInfo.GetValue(o2)!).Length + " vs " + state.seedState.Length);
DehFwk.Debug.Log("orig: " + inextInfo.GetValue(prng)! + " " + "| new: " + inextInfo.GetValue(o2) + " vs " + state.inext);
DehFwk.Debug.Log("orig: " + inextpInfo.GetValue(prng) + "| new: " + inextpInfo.GetValue(o2) + " vs " + state.inextp);*/
}
}
这是我在静态实用程序类中提出的内容:
//* Used for Getting and setting System.Random state *//
private static System.Reflection.FieldInfo[] randomFields;
private static System.Reflection.FieldInfo[] RandomFields { get { if (randomFields == null) {
randomFields = new System.Reflection.FieldInfo[3];
var t = typeof(System.Random);
randomFields[0] = t.GetField("SeedArray", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
randomFields[1] = t.GetField("inext", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
randomFields[2] = t.GetField("inextp", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
}
return randomFields;
} }
/// <summary>
/// Gets <see cref="System.Random"/> current state array and indexes with Reflection.
/// </summary>
/// <param name="rand"></param>
/// <returns></returns>
public static int[] GetSeedArray(this System.Random rand) {
var state = new int[58];
((int[])RandomFields[0].GetValue(rand)).CopyTo(state, 0);
state[56] = (int)RandomFields[1].GetValue(rand);
state[57] = (int)RandomFields[2].GetValue(rand);
return state;
}
/// <summary>
/// Restores saved <see cref="System.Random"/> state and indexes with Reflection. Use with caution.
/// </summary>
/// <param name="rand"></param>
/// <param name="seedArray"></param>
public static void SetSeedArray(this System.Random rand, int[] seedArray) {
if (seedArray.Length != 56 + 2) return;
Array.Copy(seedArray, ((int[])RandomFields[0].GetValue(rand)), 56);
RandomFields[1].SetValue(rand, seedArray[56]);
RandomFields[2].SetValue(rand, seedArray[57]);
}
为了确保在新开始时相同的初始随机数序列(我猜这就是OP测试它的方式),可以用以前的Random
的数字播种Random
的新实例。
我猜f(rnd)
(随机生成器)是相同的随机 f(f(rnd))
。使用后一种方法,你不可能更容易地预测一个数字,分布也不会受到影响。如果有人能证明我错了,我会非常感激。
因此,我建议在进入临界区之前简单地存储最后一个数字,并将该数字用作以后进入临界区的种子,而不是使用丑陋的hack来保存完整的状态。
下面的代码与@Jon的答案产生相同的结果,第二个和第三个输出的数字应该相同:
var rnd = new Random();
Test("Before", rnd);
// only this number is needed to "restore" sequence for critical section
var seed = rnd.Next();
rnd = new(seed);
Test("Critical section", rnd);
rnd = new(seed);
Test("Again critical section", rnd);
static void Test(string header, Random rnd)
{
Console.WriteLine(header);
for (int i = 0; i < 5; i++)
Console.WriteLine(rnd.Next(100));
}
我不是c#人员,所以我没有资源来查看源代码,但是种子必须存储在类中。从它的文档来看,类是不密封的,所以您应该能够创建一个子类。在这个子类中,您可以创建一个函数来返回当前种子。在你的应用程序关闭时,你可以保存种子文件?数据库?),然后在启动新应用程序时重新加载它。
这有一个额外的好处,允许您从任何先前保存的点恢复,用于备份或其他目的。