4

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!

8
  • 1
    There's a reason you don't see general purpose deep clone methods. To be done properly they realistically need to be implemented based on the specific types in question. Given that you should very rarely need to actually use such implementations, this is generally not an issue. That you would need something like this is in fact an indication that something might be wrong. Commented Feb 6, 2014 at 0:24
  • Check cloneextensions.codeplex.com Commented Feb 6, 2014 at 0:26
  • You can try this code: code.msdn.microsoft.com/CSEFDeepCloneObject-12a5cb95 Commented Feb 6, 2014 at 0:27
  • I understand. But it's a practicum, so I have to do it somehow. I don't know, maybe to solve the problem I should add some storage for objects tha has already been cloned so as not to clone them again. But would that provide deep copy for all chain of objects? I'm really desperate here Commented Feb 6, 2014 at 0:28
  • 1
    You have to keep a dictionary of every object cloned (keyed by reference or hash) with a reference to its clone. Before cloning a new object, check that it hasn't already been cloned. Commented Feb 6, 2014 at 1:38

2 Answers 2

8

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Doesn't serialization work only with Serializable objects as well as serializable members? I have used binary serialization in the example as you can seen. But that doesn't solve the problem.
Good point. Typically I work with DTOs, which are all public. I've updated the example to include private fields too.
1

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.

1 Comment

Thanks, will try this . And thanks for bugs noticing - they're the reesult of 3 a.m. coding.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.