Domanda

I have to implement generic extention deepclone method which can be used with any reference type instance to get its deep copy. I implement it as the following

static class ClassCopy
{
    static public T DeepClone<T> (this T instance)
    {
        if (instance == null) return null;
        var type = instance.GetType();
        T copy;
        var flags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic |
                    BindingFlags.Instance;

        var fields = type.GetFields(flags);

        // If type is serializable - create instance copy using BinaryFormatter
        if (type.IsSerializable)
        {
            using (var stream = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(stream, instance);
                stream.Position = 0;
                copy = (T) formatter.Deserialize(stream);
            }

            // Copy all fiels  which are not marked as serializable 
            foreach (var field in fields)
            {
                if (!field.IsNotSerialized) continue;
                var value = field.GetValue(instance);

                //Recursion!!!
                //for each embedded object also create deep copy
                value = value != null  ? value.DeepClone() : value;
                field.SetValue(copy, value);
            }
        }
        else
        {
            // If type is not serializable - create instance copy using Activator
            //(if there is default constructor)
            // or FormatterServices ( if there is no constractor)

            copy = CreateInstance<T>(type);
            foreach (var field in fields)
            {
                var value = field.GetValue(instance);

                //Recursion!!!
                value = value != null  ? value.DeepClone() : value;
                field.SetValue(copy, value);
            }
        }

        //Copy all properties 
        //In order to copy all backing fields  for auto-implemented properties

        var properties = type.GetProperties(flags|BindingFlags.SetProperty);
        foreach (var property in properties)
        {
            if (property.CanWrite)
            {
                var value = property.GetValue(instance);

                //Recursion!!!
                value = value != null ? value.DeepClone() : null;
                property.SetValue(copy, value);
            }
        }
        return copy;
    }

    private static T CreateInstance<T>(Type t) where T: class
    {
        T instance;
        var constructor = t.GetConstructor(Type.EmptyTypes);
        if (constructor != null)
        {
            instance = Activator.CreateInstance(t) as T;
            return instance;
        }
        instance = FormatterServices.GetUninitializedObject(t) as T;
        return instance;
    }
}

It works well. But if object to be cloned and its reference type fields have mutual references this code leads to infinite loops. e.g.

private static void Main(string[] args)
{
    var parent = new Parent();
    parent.Child = new Child();
    parent.Child.Parent = parent;
    //Infinite Loop!!!
    var parent1 = parent.DeepClone();
}

class Parent
{
    public Child Child { get; set; }
}
class Child
{
    public Parent Parent { get; set; }
}

Does anyone has any idea how to implement this task? It should be implemented literally and no variations are allowed (it's a practicum). Thanks a lot for any tips!

È stato utile?

Soluzione 2

What you can do is pass around a Dictionary of items mapped to their clones. Now the method will look like this:

static private T DeepClone<T> (this T instance, IDictionary<object, object> originalToAlreadyCloned) where T: class

Now the first thing you do after if (instance == null) return null;, is check if instance is present in originalToAlreadyCloned and if so, return it.

In order to populate it, after

  1. copy = (T) formatter.Deserialize(stream);
  2. copy = CreateInstance<T>(type);

call originalToAlreadyCloned.Add(instance, copy);

Finally, provide a new top-level method:

static private T DeepClone<T> (this T instance) where T: class that simply calls DeepClone(instance, new Dictionary<object, object>());

By the way, value = value != null && value.GetType().IsClass? value.DeepClone() : null; seems wrong. What you're saying is, if value is not a class, set it to null. But if it's not a class, you can't generally set it to null. I'm not sure why you don't just clone these items too, and why DeepClone is restricted to only classes.

Altri suggerimenti

An old trick for deep-cloning objects, is to serialize and de-serialize them, thereby creating new instances.

public T deepClone<T>(T toClone) where T : class
{
    string tmp = JsonConvert.SerializeObject(toClone);
    return JsonConvert.DeserializeObject<T>(tmp);            
}

I've worked extensively with Newtonsoft.Json and it has a built in solution to your problem. By default, it detects if an object has already be serialized and throws an exception. However, you can configure it to serialize references to object for getting around circular references. Instead of serializing objects in-line, it serializes a reference to that object and guarantees that every reference to an object is only serialized once.

Further, the default is to only serialize public fields/properties. There is an additional setting for serializing the private fields too.

public T deepClone<T>(T toClone) where T : class
{
    JsonSerializerSettings settings = new JsonSerializerSettings();
    settings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;

    DefaultContractResolver dcr = new DefaultContractResolver();
    dcr.DefaultMembersSearchFlags |= System.Reflection.BindingFlags.NonPublic;
    settings.ContractResolver = dcr;

    string tmp = JsonConvert.SerializeObject(toClone, settings);
    return JsonConvert.DeserializeObject<T>(tmp);
}

So you could either "cheat" and use code like this or copy how it works to implement a clone that preserves references. The example you gave of parent / child is just 1 way cloning is difficult. 1 to many is another.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top