Deep clone of System.Random

问题内容:

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

Not Serializable

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.

问题评论:

    
I dont get an exception (.NET 4.6.2)
3  
Why not write your own version of System.Random and use that instead? There are plenty of options for writing a PRNG available. You could even directly copy the relevant parts of underlying C# source code.
    
I’ve now copied the Random class from referencesource.microsoft.com/#mscorlib/system/… and it seems to work, just had to comment out some Environment.GetResourceString errors. Why doesn’t it work from the bundled System.Random?
– xareth
5 hours ago
1  
Because in .NET Core it’s not marked as serializable
    
@Evk I was having a hard time believing it but you’re right. Took me forever to find the source on Github
– xareth
4 hours ago

答案:

答案1:

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

答案评论:

    
This worked for my test program above. As a bonus, I’m already using JSON.NET in my application. Unfortunately, I’ve run straight into another issue there. But I’ve got a serialized Random class now.
– xareth
3 hours ago

答案2:

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();
        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
        {
            R = new Random()
        };
    }
}

[Serializable]
public class ObjectType : ISerializable
{

    internal Random R;

    public ObjectType() { }
    protected ObjectType(SerializationInfo info, StreamingContext context)
    {
        R = info.GetString("Random").DeserializeRandom();

    }       

    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {

        info.AddValue("Random",R.SerializeRandom());          
    }


}

public static class RandomExtensions
{
    public static string SerializeRandom(this Random random)
    {
        var binaryFormatter = new BinaryFormatter();

        var arr = random.GetType().GetField("_seedArray", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

        using (var temp = new MemoryStream())
        {
            binaryFormatter.Serialize(temp, arr.GetValue(random));

            return Encoding.BigEndianUnicode.GetString(temp.ToArray());
        }
    }

    public static Random DeserializeRandom(this string randomStr)
    {
        Random r = new Random();
        var binaryFormatter = new BinaryFormatter();
        using (var temp = new MemoryStream(Encoding.BigEndianUnicode.GetBytes(randomStr)))
        {
            var arr = (int[])binaryFormatter.Deserialize(temp);
            r.GetType().GetField("_seedArray", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(r, arr);
            return r;
        }

    }

}

答案评论:

    
Note that you should also serialize other fields of Random, not just _seedArray.

原文地址:

https://stackoverflow.com/questions/47750409/deep-clone-of-system-random

添加评论