12

I want to make a deep copy of an object so I could change the the new copy and still have the option to cancel my changes and get back the original object.

My problem here is that the object can be of any type, even from an unknown assembly. I can not use BinaryFormatter or XmlSerializer, because the object unnecessarily have [Serializable] attribute.

I have tried to do this using the Object.MemberwiseClone() method:

public object DeepCopy(object obj)
{
    var memberwiseClone = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);

    var newCopy = memberwiseClone.Invoke(obj, new object[0]);

    foreach (var field in newCopy.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
    {
        if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
        {
            var fieldCopy = DeepCopy(field.GetValue(newCopy));
            field.SetValue(newCopy, fieldCopy);
        }
    }
    return newCopy;
}

The problem in this is that it's not working on an enumerable (array, list etc.), an not on dictionary.

So, how can I make a deep copy of an unknown object in C#?

TNX a lot!

Orad SA
  • 1,885
  • 5
  • 16
  • 16
  • 1
    Invoking `MemberwiseClone` on an arbitrary object is _evil_. – SLaks Sep 05 '10 at 17:47
  • Ive tried this on arrays and dictionaries and they both seem to work. I added a check at the beginning for null. This seems like a general purpose solution for basic objects. As Slaks mentioned its not a good idea for filestreams and such as it will copy the same handle to the file stream. They will both be dependent the stream closing or seeking. If you are aware that its not always a deep clone its an interesting solution. –  Sep 05 '10 at 20:00
  • I have also read a solution where you can check for the ICloneable Interface. If ICloneable is implemented invoke it otherwise just create an empty instance. –  Sep 05 '10 at 20:09
  • I think its not completely understood what I want to do. I am trying to create a DEEP copy, meaning copy all the objects hierarchy that the object contains in properties etc. Because of that, the method I showed up not working on an array of classes, just try to change one of the classes and you will find out the value was changed in the 'copied' array too. I want the array (or any object whatsoever), to be completely separate, so changing value in the source object will not change the copy I created. – Orad SA Sep 06 '10 at 06:38
  • We understand what you're trying to do. However, it is **completely impossible** to deep-copy an arbitrary object. – SLaks Sep 06 '10 at 11:58
  • This [blog](http://developerscon.blogspot.in/2008/06/c-object-clone-wars.html) is very handy and covers all possible ways for cloning of objects in C#. I found it really useful. Have a loook! – RBT Mar 26 '15 at 02:44

9 Answers9

14

It is completely impossible to deep-copy an arbitrary object.

For example, how would you handle a Control or a FileStream or an HttpWebResponse?

Your code cannot know how the object works and what its fields are supposed to contain.

Do not do this.
It's a recipe for disaster.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • I do not see what you meant here. Why can't I create a deep copy of a Control? What I am trying to do is just to create the exact same instance I have, but in a new 'place' on the memory, so if I will change the one, the second will not be effected. If I could, what I would do is take the bytes where the object 'sits' at the memory and copy those to a new location in the memory, then create a pointer so I can use it as any other object in .net. Can I do taht somehow? – Orad SA Sep 06 '10 at 06:47
  • About FileStream or an HttpWebResponse, I can be quiet certain that the object is not one of those. The object must be a simple object ('data object' or something like that). – Orad SA Sep 06 '10 at 06:52
  • 1
    @Orad: Controls involve native interop (they contain a window handle). If you attempt to deep-copy one, you'll get two controls wrapping the same window handle. – SLaks Sep 06 '10 at 12:04
4

Making a deep copy of an arbitrary object is quite difficult. What if the object contains access to a resource such as an open file with write capabilities, or a network connection? Without knowing what type of information the object holds, I would be hard to make a copy of an object, and have it function exactly the same way. You might be able to use reflection to do this, but it would get quite difficult. For starters you'd have to have some kind of list to keep all the objects you copied, otherwise, if A references B and B references A, then you might end up in an endless loop.

Kibbee
  • 65,369
  • 27
  • 142
  • 182
4

Agree with SLaks. You allways need custom copying semantics just to distingush between weather you want to have a deep copy or a flat copy. (What is a reference, what a contained reference in a sens of composite reference.)

The pattern you are talking about is the memento pattern.

Read the article on how to implement it. But basically it turns out to create a custom copy facitlity. Either internal in the class or external in a "copy factory".

schoetbi
  • 12,009
  • 10
  • 54
  • 72
  • I don't see how I can use any pattern. I don't know what is the object I'm going to copy. Can you give some example? – Orad SA Sep 06 '10 at 07:17
  • The Memento pattern is very good described here:http://www.business-patterns.com/DesignPatterns/Behavioural/Memento.pdf. – schoetbi Sep 06 '10 at 09:49
  • I still don't get how can I use it in my case. In the Memento pattern, I will have to create a specific memento to any class I need to save its state; but in this case I need to save the state of any object (even classes from an assembly that is not referenced to my assembly). Also, I need to save only one state, the memento pattern (form my understanding) is mostly for saving many states the object passed. – Orad SA Sep 06 '10 at 10:33
  • Just to make it clear. The memenot pattern is not the answer to the question. As I said: You need a custom copy facitlity for your objects. – schoetbi Sep 06 '10 at 11:28
3

To answer your question, you can deep-copy an array by calling Array.CreateInstance and copying the contents of the array by calling GetValue and SetValue.

However, you should not do this for arbitrary objects.

For example:

  • What about event handlers?
  • Some objects have unique IDs; you will end up creating duplicate IDs
  • What about objects that reference their parents?
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
2

As other have said, deep-copying an arbitrary object can be disastrous. However, if you're pretty certain about the objects you are trying to clone, you can still attempt this.

Two things about your original method:

  • In case of circular references you will fall into an infinite loop;
  • The only special case you need to worry about is copying members that are arrays. Everything else (Lists, Hashsets, Dictionaries, etc.) will boil down to that eventually (or in case of trees and linked lists will be deep-copy-able the standard way).

Note also that there was a method which allowed to create an arbitrary class object without invoking any of its constructors. The BinaryFormatter used it and it was publicly available. Unfortunately I do not remember what it was called and where it lived. Something about runtimes or marshalling.

Update: System.Runtime.Serialization.FormatterServices.GetUninitializedObject Note that

You cannot use the GetUninitializedObject method to create instances of types that derive from the ContextBoundObject class.

Vilx-
  • 104,512
  • 87
  • 279
  • 422
  • 2
    http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatterservices.getuninitializedobject.aspx – SLaks Sep 05 '10 at 18:17
  • Thanks a lot, you have shown me some things I Haven't seen before. I will try to upgrade my method so it will take care of the points you wrote. – Orad SA Sep 06 '10 at 07:29
2

Ok, I modified your routine a little bit. You'll need to clean it up but it should accomplish what you want. I did not test this against controls or filestreams, and care should be taken with those instances.

I avoided Memberwise clone for Activator.CreateInstance. This will create new Instances of reference types and copy value types. If you use objects with multi-dimensional arrays you will need to use the Array Rank and iterate for each rank.

    static object DeepCopyMine(object obj)
    {
        if (obj == null) return null;

        object newCopy;
        if (obj.GetType().IsArray)
        {
            var t = obj.GetType();
            var e = t.GetElementType();
            var r = t.GetArrayRank();
            Array a = (Array)obj;
            newCopy = Array.CreateInstance(e, a.Length);
            Array n = (Array)newCopy;
            for (int i=0; i<a.Length; i++)
            {
                n.SetValue(DeepCopyMine(a.GetValue(i)), i);
            }
            return newCopy;
        }
        else
        {
            newCopy = Activator.CreateInstance(obj.GetType(), true);
        }

        foreach (var field in newCopy.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
        {
            if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
            {
                var fieldCopy = DeepCopyMine(field.GetValue(obj));
                field.SetValue(newCopy, fieldCopy);
            }
            else
            {
                field.SetValue(newCopy, field.GetValue(obj));
            }
        }
        return newCopy;
    }
  • Thanks you, errant developer - almost had this verbatim and then I stumbled on it for that last piece... grr!... but thanks! – Code Jockey Aug 01 '14 at 12:21
1

Another reason not to copy arbitrary objects: it is impossible to know, without examining the code behind an object, how that object will relate to other objects in the system, or whether certain values are magical. For example, two objects may both hold references to arrays that hold a single integer which equals five. Both of those arrays are used elsewhere in the program. What matter might not be that either array happens to hold the value five, but rather the effect that writing to the array may have on other program execution. There's no way a serializer whose author doesn't know what the arrays are used for will be able to preserve that relationship.

supercat
  • 77,689
  • 9
  • 166
  • 211
1

For a deep copy, I used Newtonsoft and create and generic method such as:

public T DeepCopy<T>(T objectToCopy){
   var objectSerialized = JsonConvert.SerializeObject(objectToCopy);
   return JsonConvert.DeserializeObject<T>(objectSerialized);
}

The best solution I can use for that problem.

Enrique Reyes
  • 11
  • 1
  • 1
0

Here is an elaboration of the answer by @schoetbi. You need to tell the class how to clone itself. C# does not distinguish between aggregation and composition and uses object references for both.

If you had a class for storing information about a car, it could have instances fields, like engine, wheels, etc. (composition), but also manufacturer (aggregation). Both are stored as references.

If you cloned the car, you would expect the engine and wheels to be cloned as well, but you certainly would not want the manufacturer to be cloned as well.

Community
  • 1
  • 1
R. Schreurs
  • 8,587
  • 5
  • 43
  • 62