I'm trying to deep clone an object which contains a System.Random variable. My application must be deterministic and so I need to capture the the random object state. My project is based on .Net Core 2.0.
I'm using some deep clone code from here (How do you do a deep copy of an object in .NET (C# specifically)?) which uses serialization.
The documentation for System.Random is mixed:
Serializable
https://msdn.microsoft.com/en-us/library/system.random(v=vs.110).aspx
http://referencesource.microsoft.com/#mscorlib/system/random.cs,bb77e610694e64ca (source code)
Not Serializable
https://docs.microsoft.com/en-us/dotnet/api/system.random?view=netframework-4.7.1
https://docs.microsoft.com/en-us/dotnet/api/system.random?view=netcore-2.0
and I get the following error.
System.Runtime.Serialization.SerializationException HResult=0x8013150C Message=Type 'System.Random' in Assembly 'System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e' is not marked as serializable. Source=System.Runtime.Serialization.Formatters
Can System.Random it be cloned in the way I want?
I created a small program to illustrate.
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace RandomTest
{
class Program
{
static void Main(string[] args)
{
Container C = new Container();
Container CopyC = DeepClone(C);
}
public static T DeepClone<T>(T obj)
{
using(var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj); //<-- error here
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
}
[Serializable]
public class Container
{
public ObjectType AnObject;
public Container()
{
AnObject = new ObjectType();
}
}
[Serializable]
public class ObjectType
{
//[NonSerialized] // Uncommenting here removes the error
internal System.Random R;
}
}
I probably don't need the Container object, but this structure more closely resembles my application.
Making R [NonSerialized] removes the error but I don't get my Random object back after deserialization. I tried re-creating the random object, but it starts a new random sequence and so breaks the deterministic requirement.
You could use JSON.NET
to do this.
Use Project | Manage NuGet Packages to add "Newtonsoft.Json" (latest stable version 10.0.3) to your project.
Then you can write a Cloner
class that uses Json.NET to clone an object:
public static class Cloner
{
public static T Clone<T>(T source)
{
if (ReferenceEquals(source, null))
return default(T);
var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
}
class ContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => base.CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => base.CreateProperty(f, memberSerialization)))
.ToList();
props.ForEach(p => { p.Writable = true; p.Readable = true; });
return props;
}
}
}
Then you can write some code like so:
var inner = new ObjectType {R = new Random(12345)};
var outer = new Container {AnObject = inner};
var clone = Cloner.Clone(outer);
Console.WriteLine(clone.AnObject.R.Next()); // Prints 143337951
Console.WriteLine(outer.AnObject.R.Next()); // Also prints 143337951
It seems that it' is possible to serialize & deserialize random class by writing custom serializer and using reflection. Please Check.
Here is your code sample. It needs some refactoring of course. I just added here to show it' s working.
class Program
{
static void Main(string[] args)
{
Container C = new Container();
Console.WriteLine(C.AnObject.R.Next());
Container CopyC = DeepClone(C);
Console.WriteLine(C.AnObject.R.Next());
Console.WriteLine(CopyC.AnObject.R.Next());
}
public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj); //<-- error here
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
}
[Serializable]
public class Container
{
public ObjectType AnObject;
public Container()
{
AnObject = new ObjectType
{
R = new Random()
};
}
}
[Serializable]
public class ObjectType : ISerializable
{
internal Random R;
public ObjectType() { }
protected ObjectType(SerializationInfo info, StreamingContext context)
{
R = new Random();
var binaryFormatter = new BinaryFormatter();
using (var temp = new MemoryStream(Encoding.BigEndianUnicode.GetBytes(info.GetString("_seedArr"))))
{
var arr = (int[])binaryFormatter.Deserialize(temp);
R.GetType().GetField("_seedArray", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(R, arr);
}
R.GetType().GetField("_inext", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(R, info.GetInt32("_inext"));
R.GetType().GetField("_inextp", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(R, info.GetInt32("_inextp"));
}
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
var binaryFormatter = new BinaryFormatter();
var arr = R.GetType().GetField("_seedArray", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
using (var temp = new MemoryStream())
{
binaryFormatter.Serialize(temp, arr.GetValue(R));
info.AddValue("_seedArr", Encoding.BigEndianUnicode.GetString(temp.ToArray()));
}
info.AddValue("_inext", R.GetType().GetField("_inext", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(R));
info.AddValue("_inextp", R.GetType().GetField("_inextp", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(R));
}
}
User contributions licensed under CC BY-SA 3.0