是否有办法获取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();
}

是否有办法获取System.Random的实际状态?

它是可序列化的,所以你可能会发现你可以只使用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的无参数构造函数会产生另一种名为XoshiroImplImplBase类型,该类型缺乏种子支持,因此使我的代码不可用。

可以用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#人员,所以我没有资源来查看源代码,但是种子必须存储在类中。从它的文档来看,类是不密封的,所以您应该能够创建一个子类。在这个子类中,您可以创建一个函数来返回当前种子。在你的应用程序关闭时,你可以保存种子文件?数据库?),然后在启动新应用程序时重新加载它。

这有一个额外的好处,允许您从任何先前保存的点恢复,用于备份或其他目的。