I would like to be able to get the actual state or seed or whatever of System.Random so I can close an app and when the user restarts it, it just "reseeds" it with the stored one and continues like it was never closed.
Is it possible?
Using Jon's idea I came up with this to test it;
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();
}
It's serializable, so you may find you can just use BinaryFormatter
and save the byte array...
Sample code:
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));
}
}
}
Results on a sample run are promising:
Values before saving...
25
73
58
6
33
Values after saving...
71
7
87
3
77
Values after restoring...
71
7
87
3
77
Admittedly I'm not keen on the built-in serialization, but if this is for a reasonably quick and dirty hack, it should be okay...
Here's a dotnet 5.0
or later struct that can save and load the current state of a System.Random
instance. This can surely be improved but it works as it should. Also, I'm pretty sure it only works with System.Random
instances that have been created with a Seed
parameter (System.Random(int Seed)
). I noticed while debugging that using the parameterless constructor of System.Random
yields an other type of ImplBase
named XoshiroImpl
that lacks seed support therefore making my code unusable.
This can be serialized with 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);*/
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With