How do you do a deep copy of an object in .NET? [duplicate]
Asked Answered
V

10

701

I want a true deep copy. In Java, this was easy, but how do you do it in C#?

Vintager answered 24/9, 2008 at 19:39 Comment(9)
What does a Deep Copy do? Does it copy the bitstream?Jessamine
A deep copy is something that copies EVERY field of an object. A shallow copy will only create a new object and point all the fields to the original.Coulter
A framework for copying/cloning .NET objects: github.com/havard/copyablePawsner
A deep copy creates a second instance of the object with the same values. A shallow copy (oversimplified) is like creating a second reference to an object.Willena
Use a Mapper, I suggest UltraMapper github.com/maurosampietro/UltraMapperTylertylosis
check this post : https://mcmap.net/q/10195/-is-this-a-good-way-to-copy-a-class-closedMasaryk
The BinaryFormatter is insecure, take a look on official docs: learn.microsoft.com/en-us/dotnet/api/…Byrnie
If perf is not an issue, newtonsoft to the rescue... JsonConvert.DeserializeObject<MyType>(JsonConvert.SerializeObject(MyInstance));Conlin
See the Object.MemberwiseClone Method page on Microsoft.com.Chowchow
S
725

Important Note

BinaryFormatter has been deprecated, and will no longer be available in .NET after November 2023. See BinaryFormatter Obsoletion Strategy


I've seen a few different approaches to this, but I use a generic utility method as such:

public static T DeepClone<T>(this T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

   return (T) formatter.Deserialize(ms);
 }
}

Notes:

  • Your class MUST be marked as [Serializable] for this to work.

  • Your source file must include the following code:

     using System.Runtime.Serialization.Formatters.Binary;
     using System.IO;
    
Swingle answered 24/9, 2008 at 19:40 Comment(18)
What happen if the object have event, Do they lost everything because of the serialization?Thermotaxis
Event subscribes are included into serialization graph, since BinaryFormatter uses fields via reflection, and events are just fields of delegate types plus add/remove/invoke methods. You can use [field: NonSerialized] on event to avoid this.Swingletree
What is that undeclared "stream" variable? Or is it something just in C# and not VB.NET? I converted everything but that variable.Linet
ms.Location or ms.Position? or is this something to do with the Framework version?Cambogia
How to mark something as [Serializable] ??Ella
@Sean87: above the class declaration, add [Serializable]. so [Serializable]public class Foo { } will make Foo marked as serializable.Plenary
Recursive MemberwiseClone will do deep copy too, it works 3 times faster then BinaryFormatter, doesn't require default constructor or any attributes. See my answer: https://mcmap.net/q/9314/-how-do-you-do-a-deep-copy-of-an-object-in-net-duplicateAstounding
This is creating a curious exception "assembly not found" while using this Utility code within the UserControlTestContainer. Its really weird because the assembly is loaded...Bozeman
To address your first note programmatically, you can handle the exception a little better by using the following: if (!typeof(T).IsSerializable) { throw new ArgumentException("Type {0} is not serializable",typeof(T).Name); }Valuate
how would you respond to -->this apparent memory leak issue with Deserialize()?Plod
It's more helpful to use var formatter = new BinaryFormatter {Context = new StreamingContext(StreamingContextStates.Clone)};Manger
while this is working in some scenarios - it seem like an overkill to serialize everything and deserialize everything just to create a copy of an object. I feel like this should be a example on how to do when you have to finish your project in the next 5 minutes.Bala
It is also extremly slow.Gymnasiarch
For those who wonder, see this easy solution: #223098Redhot
ms.Position = 0 saved the day for me! Without it really weird stuff was happening.Terminator
I know this post is old but it still comes up as a top hit when searching for deep cloning. Take note that according to Microsoft (aka.ms/binaryformatter) this is no longer a recommended solution as it is insecure.Monserratemonsieur
I have worked on .net core 2.2. I'm using BinaryFormatter for Deepclone. Does BinaryFormatter not work for .net core 2.2 as mentioned here?Spandrel
I switched mine over to this implementation and it works. public static T DeepClone<T>(this T obj) { using (MemoryStream memoryStream = new MemoryStream()) { DataContractSerializer serializer = new DataContractSerializer(typeof(T)); serializer.WriteObject(memoryStream, obj); memoryStream.Seek(0, SeekOrigin.Begin); return (T)serializer.ReadObject(memoryStream); } }Companionship
A
426

I wrote a deep object copy extension method, based on recursive "MemberwiseClone". It is fast (three times faster than BinaryFormatter), and it works with any object. You don't need a default constructor or serializable attributes.

Source code:

using System.Collections.Generic;
using System.Reflection;
using System.ArrayExtensions;

namespace System
{
    public static class ObjectExtensions
    {
        private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);

        public static bool IsPrimitive(this Type type)
        {
            if (type == typeof(String)) return true;
            return (type.IsValueType & type.IsPrimitive);
        }

        public static Object Copy(this Object originalObject)
        {
            return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()));
        }
        private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited)
        {
            if (originalObject == null) return null;
            var typeToReflect = originalObject.GetType();
            if (IsPrimitive(typeToReflect)) return originalObject;
            if (visited.ContainsKey(originalObject)) return visited[originalObject];
            if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
            var cloneObject = CloneMethod.Invoke(originalObject, null);
            if (typeToReflect.IsArray)
            {
                var arrayType = typeToReflect.GetElementType();
                if (IsPrimitive(arrayType) == false)
                {
                    Array clonedArray = (Array)cloneObject;
                    clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
                }

            }
            visited.Add(originalObject, cloneObject);
            CopyFields(originalObject, visited, cloneObject, typeToReflect);
            RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
            return cloneObject;
        }

        private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
        {
            if (typeToReflect.BaseType != null)
            {
                RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
                CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
            }
        }

        private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
        {
            foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
            {
                if (filter != null && filter(fieldInfo) == false) continue;
                if (IsPrimitive(fieldInfo.FieldType)) continue;
                var originalFieldValue = fieldInfo.GetValue(originalObject);
                var clonedFieldValue = InternalCopy(originalFieldValue, visited);
                fieldInfo.SetValue(cloneObject, clonedFieldValue);
            }
        }
        public static T Copy<T>(this T original)
        {
            return (T)Copy((Object)original);
        }
    }

    public class ReferenceEqualityComparer : EqualityComparer<Object>
    {
        public override bool Equals(object x, object y)
        {
            return ReferenceEquals(x, y);
        }
        public override int GetHashCode(object obj)
        {
            if (obj == null) return 0;
            return obj.GetHashCode();
        }
    }

    namespace ArrayExtensions
    {
        public static class ArrayExtensions
        {
            public static void ForEach(this Array array, Action<Array, int[]> action)
            {
                if (array.LongLength == 0) return;
                ArrayTraverse walker = new ArrayTraverse(array);
                do action(array, walker.Position);
                while (walker.Step());
            }
        }

        internal class ArrayTraverse
        {
            public int[] Position;
            private int[] maxLengths;

            public ArrayTraverse(Array array)
            {
                maxLengths = new int[array.Rank];
                for (int i = 0; i < array.Rank; ++i)
                {
                    maxLengths[i] = array.GetLength(i) - 1;
                }
                Position = new int[array.Rank];
            }

            public bool Step()
            {
                for (int i = 0; i < Position.Length; ++i)
                {
                    if (Position[i] < maxLengths[i])
                    {
                        Position[i]++;
                        for (int j = 0; j < i; j++)
                        {
                            Position[j] = 0;
                        }
                        return true;
                    }
                }
                return false;
            }
        }
    }

}
Astounding answered 3/7, 2012 at 10:20 Comment(44)
I tried this, but got an error saying I needed to have serializable attributes in my class I am cloning...Shelleyshellfire
@Shelleyshellfire I think you have called Clone, instead of Copy, there is a Clone method that uses BinaryFormatter as described in accepted answer for benchmark comparissonAstounding
Thanks Alex, yes I needed to call copy instead and that worked!Shelleyshellfire
For ReferenceEqualityComparer.GetHashCode(object obj) you should be using RuntimeHelpers.GetHashCode(obj), otherwise it will be using the objects real hashcode. See https://mcmap.net/q/10197/-what-does-runtimehelpers-gethashcode-doPolacca
Regarding IsPrimitive: what is the reason you return true for a string. Also, is there any reason you use the single & rather than && in the statement: return (type.IsValueType & type.IsPrimitive);?Polacca
Based on reading the code, I assume this doesn't work for types containing Delegates. Do you know if the other approaches do/don't work with Delegates as well? I would assume they do not.Polacca
@MattSmith You are absolutely right about GetHashCode, checked it, yep StackOverflow exception - gist.github.com/Burtsev-Alexey/11227277Astounding
@MattSmith It was working for delegates, but I intently disabled it (by setting null), see github.com/Burtsev-Alexey/net-object-deep-copy/issues/7, the subscribers were cloned, in the end if you had two object A and B connected (by event subscription) you would get objects A' and B' connected, this is correct but that's not what most people want when the clone objects.Astounding
@AlexBurtsev, Regarding the delegates--okay, I changed it to not copy delegates, but copy the reference (similar to how you treat strings), since (like strings) delegates are immutable. The serialization approach does handle delegates, but I'm not sure if they have a similar problem to what you experienced with delegates.Polacca
@MattSmith Strings are treated as primitives because 1: they are immutable, 2: calling protected MemberwiseClone on string will result in memory corruption, string data will turn into random chars, and soon .NET runtime will crash with internal error saying theer is a but in .NET :-)Astounding
@MattSmith I believe you do understand that by reusing same delegate in original and cloned object they will be using the same objects which were referenced in delegate, like A has delegate which modifies B, by cloning A you would get A' which modifies the same B, if you want to have the true copy (snapshot) of object graphs (memory), then simply remove the line which sets delegate to null. You would get then A modifying B, and A' modifying B'Astounding
@AlexBurtsev, Good points. What you want somewhat depends on what objects are being accessed within the delegate. If you're accessing objects that are being copied, then you probably want the copied delegate. If you're accessing object external to the copied object, then you probably want the delegate to not be copied. That said, I don't have a use case for delegates, so perhaps it's best to leave it as unimplemented for now (i.e. I'll throw an exception).Polacca
BTW, When I enable Delegates for my simple test case it stackoverflows. Here's my sample object I attempt to copy: shar.es/T86JRPolacca
I've forked and updated your code for compatibility with the new TypeInfo-based reflection API (used in Windows Phone and Windows Store applications, and their respective PCL profiles). github.com/Gameleon12/net-object-deep-copy/blob/master/…Thyroid
Please add an example of how to use your extension method, it will help flesh out your answer.Cyclotron
Even faster (for me) is to use JSON.NET and serialize and then deserialize. Might not work for everyone (I'm thinking private fields and such).Merger
@AlexBurtsev, your code using namespace System.ArrayExtensions, where can I get it?Obstinacy
@Obstinacy - just encountered the same puzzlement. All the relevant code is in the referenced file, there's an ArrayExtensions namespace lower down.Outfitter
This is a very clever and powerful implementation, however you have to consider a few things before deciding whether it’s right for your data model. Memberwiseclone() is so fast because it does not invoke constructors. So if your constructors are doing heavy lifting such as event subscription, you are out of luck. It relies on copying private fields of the object, bypassing the business logic in the properties and methods. For example, I saw hashCode field being copied in a HashSet collection, even though all instances have changed.Sticktight
I'm getting a StackOverflowException when cloning objects that mutually reference each other... Anybody know of a workaround?Mcwhirter
@Mcwhirter cyclic references are suported and works fine. Your problem is somewhere else. If you can share source code of the class you are trying to clone, open an issue at project website. Cccasionally I run into SO Exceptions, when cloning what is not supposed to be cloned, like .NET internal infrastructure, that might get referenced accidently, especially pointers and their reflections. As an example trying to clone NHibernate entity containing collection that is attached to session, will result in big mess, because NH collection implementing IList<T> contains DbConnection field inside.Astounding
@AlexBurtsev I was trying to clone a collection objects mirroring objects in a different application. The access is done via com calls, so maybe I have a reference in there that is causing the thing to blow up. The .Copy() method worked fine if I set a breakpoint and inspected the list content in the watch before resuming execution. Weird. I found that calling .ToList() actually achieves what I want for now, so it's all good. Thanks for the reply :)Mcwhirter
Is there anyway I can use this but still ensure that a certain field is only copied by reference?Graphology
@Graphology Not out the box.Astounding
Can you explain how this is deep vs. shallow? My understanding is that memberwiseClone is shallow, and that's what you invoke in here. Unless I am missing something.Anzovin
Hi Alex, Thanks for your extension. Does this also "DeepCopy" events/delegates?Eulaheulalee
Seems to have issues with type PropertyInfo. Is there any way to mark properties of this type as excluded or allowed to reference?Specter
Not a single Comment in the whole thing. WOW.Amenable
Still able to mutate nested lists :(Crinkle
you dont copy arrays if its primitive+string, but one can modify one copy then other copy sees that change which is not deep copy meant to be.Colligate
Warning: this approach brakes XDocuments. I can no longer access attributes by name, even thou there is no namespace defined.Unity
It seems that List<BsonElement> is not properly reflected by fieldInfo. Out of: List<BsonElement> { BsonElement ("status=Connected"), BsonElement("lastConnectedTimeUtc=2019-05-15T10:50:40.5570932Z") } it makes: BsonElement[] { BsonElement ("status=Connected"), BsonElement("lastConnectedTimeUtc=2019-05-15T10:50:40.5570932Z"), BsonElement("="), BsonElement("=") }Undaunted
I used this in conjunction with fluentassertions to make a clone of an object and later validate originalObject.Should().BeEquivalentTo(objectClone). However this fails if the object contains Noda Time structs. The solution was to add a line like this to InternalCopy: if (typeToReflect == typeof(LocalDate) || typeToReflect == typeof(OffsetDateTime)) return originalObject;Ladanum
@Anzovin - "how this is deep vs. shallow". If it stopped after calling MemberwiseClone, then it would be shallow. It uses that as a first step, then uses reflection info to do some recursive changes to the clone. That converts the shallow clone into a deep clone. (I assume the reason for initial MemberwiseClone, is to avoid the need to process fields that are in fact shallow - field types that don't need any recursion.)Fransen
Note that this approach can be easily optimized to use a custom interface that you implement to speed up your own classes. interface IDeepCopy { object DeepCopy(IDictionary<object, object> visited); } Each implementor does object DeepCopy(visited) { ..check-visited-add-to-visited..; MyType clone = MemberwiseClone; ... }, where "..." recurses as needed. E.g. clone.myField1 = myField1.DeepCopy(visited); or for fields that aren't IDeepCopy, do the InternalCopy call shown in answer.Fransen
I know this code is very old and not continuously managed, but it has serious problems. If you deep copy an object which has a Type object as a member, Type object itself will be copied too. And the new one seems to be the same with the old one, but type comparison between two Types will get fail, because Type.Equals(Type) use reference comparison. And one more, if an object 'A' has a user-defined struct member 'B', and 'B' has a reference to an object 'C' as a member again, object 'C' will not get deep copied with 'A'. Please use this code with care.Dictation
@Amenable - If it was hard to write, it should be hard to understand.Anthropology
@Alic W When you are explaining to some one how to do something the idea is to take something that is hard and try to make it easier. It's hard and it should be hard is not helpful, this is a reason Documentation is used. Its also why Stack Overflow exists.Amenable
@AlexBurtsev, after 9 years still best & only (as I'm aware) solution. Thanks a lot !!!!!Logging
It seems to not work on the objects wit the cross-links. The very first attempt to copy one of my objects ended up in indefinite loop :( And my problem is that the objects that I need to copy I don't control. I'd love to implement extra functionality on them to support the deep copy approach, except I can't.Fallingout
@Fallingout probably a bit late for you, but this is because the visited.Add() statement needs to be directly before the if (typeToReflect.IsArray) block. This issue and more were fixed in my fork of Alex's project on github.Gelid
I'm getting a Could not get function from a frame. The code is currently unavailable. The error code is CORDBG_E_CODE_NOT_AVAILABLE, or0x80131309. error on the line var cloneObject = CloneMethod.Invoke(originalObject, null);Rugby
You can get some nasty seemingly-random System.InvalidCastExceptions if you use this to deep copy ExpandoObjects, see https://mcmap.net/q/10198/-weakreference-returns-wrong-object and https://mcmap.net/q/21228/-system-invalidcastexception-unable-to-cast-object-of-type-39-system-data-sqlclient-sqltransaction-39-to-type-39-system-transactions-bucketset-39/8479. Still great on plainer objects but as always, beware when copying objects you don't fully understand. Or code.Jurisdiction
If this method clones a Thread, it appears to cause memory corruption (which typically causes a crash from FatalExecutionEngineError at some random later time).Cherey
C
183

Building on Kilhoffer's solution...

With C# 3.0 you can create an extension method as follows:

public static class ExtensionMethods
{
    // Deep clone
    public static T DeepClone<T>(this T a)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, a);
            stream.Position = 0;
            return (T) formatter.Deserialize(stream);
        }
    }
}

which extends any class that's been marked as [Serializable] with a DeepClone method

MyClass copy = obj.DeepClone();
Corrientes answered 31/7, 2009 at 16:51 Comment(13)
To that add "public static T DeepClone<T>(this T a) where T : ISerializable"Sebaceous
@Amir - it isn't necessary for the class to implement ISerializable, Marking with SerializableAttribute is sufficient. The attribute uses reflection to perform serialization, while the interface allows you to write a custom serializerCorrientes
I agree with your statement, but I like Amir's suggestion b/c it provides compile-time checking. Is there any way to reconcile the two?Willena
Passed unit test var stringbuilder = new StringBuilder("TestData"); var copy = stringbuilder.DeepClone(); Assert.IsFalse(Equals(stringbuilder,copy)); Thanks a lot.Distend
@Corrientes This method is 10x slower than the NestedMemberwiseClone method, see my post on this page.Spelling
Hi @Gravitas. It's no surprise that hand-coded methods can out-perform a solution that uses reflection. It would be interesting to compare the performance of your solution to a hand-coded serialization method (i.e. implementing ISerializable)Corrientes
@Gravitas, hand-coded serialization was no better. Your hand-coded clone method is much faster for simple classes (maybe more like 100x)Corrientes
+1 @Corrientes - Thanks this is eactly what I was looking for!Encore
780 rep just for adding a this.. Power of extension methods :)Qualify
Hi @Qualify - well at the time there was also a bug in Kilhoffer's code that I didn't have enough karma to fix. But yes - my best contribution so far really is this trivialCorrientes
this trivial. no pun intended hehehehGraphology
I wrote a DeepClone method based on Neil's answer. It worked great, until I called DeepClone on an object whose class was defined in a 'plug-in' ... an assembly that I loaded programmatically. That caused a SerializationException to be thrown with an error of 'Unable to find assembly xxx'. I solved that problem with a solution I posted hereWiltonwiltsey
Important Note BinaryFormatter has been deprecated and will no longer be available in .NET after November 2023. See BinaryFormatter Obsoletion StrategyHudibrastic
S
59

You can use Nested MemberwiseClone to do a deep copy. Its almost the same speed as copying a value struct, and its an order of magnitude faster than (a) reflection or (b) serialization (as described in other answers on this page).

Note that if you use Nested MemberwiseClone for a deep copy, you have to manually implement a ShallowCopy for each nested level in the class, and a DeepCopy which calls all said ShallowCopy methods to create a complete clone. This is simple: only a few lines in total, see the demo code below.

Here is the output of the code showing the relative performance difference (4.77 seconds for deep Nested MemberwiseClone vs. 39.93 seconds for Serialization). Using Nested MemberwiseClone is almost as fast as copying a struct, and copying a struct is pretty darn close to the theoretical maximum speed .NET is capable of, which is probably quite close to the speed of the same thing in C or C++ (but would have to run some equivalent benchmarks to check this claim).

    Demo of shallow and deep copy, using classes and MemberwiseClone:
      Create Bob
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Clone Bob >> BobsSon
      Adjust BobsSon details
        BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
      Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Elapsed time: 00:00:04.7795670,30000000
    Demo of shallow and deep copy, using structs and value copying:
      Create Bob
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Clone Bob >> BobsSon
      Adjust BobsSon details:
        BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
      Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Elapsed time: 00:00:01.0875454,30000000
    Demo of deep copy, using class and serialize/deserialize:
      Elapsed time: 00:00:39.9339425,30000000

To understand how to do a deep copy using Nested MemberwiseClone, here is the demo project:

// Nested MemberwiseClone example. 
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
    public Person(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    [Serializable] // Not required if using MemberwiseClone
    public class PurchaseType
    {
        public string Description;
        public PurchaseType ShallowCopy()
        {
            return (PurchaseType)this.MemberwiseClone();
        }
    }
    public PurchaseType Purchase = new PurchaseType();
    public int Age;
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person ShallowCopy()
    {
        return (Person)this.MemberwiseClone();
    }
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person DeepCopy()
    {
            // Clone the root ...
        Person other = (Person) this.MemberwiseClone();
            // ... then clone the nested class.
        other.Purchase = this.Purchase.ShallowCopy();
        return other;
    }
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
    public PersonStruct(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    public struct PurchaseType
    {
        public string Description;
    }
    public PurchaseType Purchase;
    public int Age;
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct ShallowCopy()
    {
        return (PersonStruct)this;
    }
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct DeepCopy()
    {
        return (PersonStruct)this;
    }
}
// Added only for a speed comparison.
public class MyDeepCopy
{
    public static T DeepCopy<T>(T obj)
    {
        object result = null;
        using (var ms = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, obj);
            ms.Position = 0;
            result = (T)formatter.Deserialize(ms);
            ms.Close();
        }
        return (T)result;
    }
}

Then, call the demo from main:

    void MyMain(string[] args)
    {
        {
            Console.Write("Demo of shallow and deep copy, using classes and Nested MemberwiseClone:\n");
            var Bob = new Person(30, "Lamborghini");
            Console.Write("  Create Bob\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
            Console.Write("  Clone Bob >> BobsSon\n");
            var BobsSon = Bob.DeepCopy();
            Console.Write("  Adjust BobsSon details\n");
            BobsSon.Age = 2;
            BobsSon.Purchase.Description = "Toy car";
            Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
            Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
            Debug.Assert(Bob.Age == 30);
            Debug.Assert(Bob.Purchase.Description == "Lamborghini");
            var sw = new Stopwatch();
            sw.Start();
            int total = 0;
            for (int i = 0; i < 100000; i++)
            {
                var n = Bob.DeepCopy();
                total += n.Age;
            }
            Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
        }
        {               
            Console.Write("Demo of shallow and deep copy, using structs:\n");
            var Bob = new PersonStruct(30, "Lamborghini");
            Console.Write("  Create Bob\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
            Console.Write("  Clone Bob >> BobsSon\n");
            var BobsSon = Bob.DeepCopy();
            Console.Write("  Adjust BobsSon details:\n");
            BobsSon.Age = 2;
            BobsSon.Purchase.Description = "Toy car";
            Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
            Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);                
            Debug.Assert(Bob.Age == 30);
            Debug.Assert(Bob.Purchase.Description == "Lamborghini");
            var sw = new Stopwatch();
            sw.Start();
            int total = 0;
            for (int i = 0; i < 100000; i++)
            {
                var n = Bob.DeepCopy();
                total += n.Age;
            }
            Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
        }
        {
            Console.Write("Demo of deep copy, using class and serialize/deserialize:\n");
            int total = 0;
            var sw = new Stopwatch();
            sw.Start();
            var Bob = new Person(30, "Lamborghini");
            for (int i = 0; i < 100000; i++)
            {
                var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
                total += BobsSon.Age;
            }
            Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
        }
        Console.ReadKey();
    }

Again, note that if you use Nested MemberwiseClone for a deep copy, you have to manually implement a ShallowCopy for each nested level in the class, and a DeepCopy which calls all said ShallowCopy methods to create a complete clone. This is simple: only a few lines in total, see the demo code above.

Note that when it comes to cloning an object, there is is a big difference between a "struct" and a "class":

  • If you have a "struct", it's a value type so you can just copy it, and the contents will be cloned.
  • If you have a "class", it's a reference type, so if you copy it, all you are doing is copying the pointer to it. To create a true clone, you have to be more creative, and use a method which creates another copy of the original object in memory.
  • Cloning objects incorrectly can lead to very difficult-to-pin-down bugs. In production code, I tend to implement a checksum to double check that the object has been cloned properly, and hasn't been corrupted by another reference to it. This checksum can be switched off in Release mode.
  • I find this method quite useful: often, you only want to clone parts of the object, not the entire thing. It's also essential for any use case where you are modifying objects, then feeding the modified copies into a queue.

Update

It's probably possible to use reflection to recursively walk through the object graph to do a deep copy. WCF uses this technique to serialize an object, including all of its children. The trick is to annotate all of the child objects with an attribute that makes it discoverable. You might lose some performance benefits, however.

Update

Quote on independent speed test (see comments below):

I've run my own speed test using Neil's serialize/deserialize extension method, Contango's Nested MemberwiseClone, Alex Burtsev's reflection-based extension method and AutoMapper, 1 million times each. Serialize-deserialize was slowest, taking 15.7 seconds. Then came AutoMapper, taking 10.1 seconds. Much faster was the reflection-based method which took 2.4 seconds. By far the fastest was Nested MemberwiseClone, taking 0.1 seconds. Comes down to performance versus hassle of adding code to each class to clone it. If performance isn't an issue go with Alex Burtsev's method. – Simon Tewsi

Spelling answered 30/12, 2011 at 19:5 Comment(15)
Good post. Any idea why serialization is so much slower? Also, how would your checksum work? Why not just have an equality checker?Crapulent
@Crapulent The checksum works by manually converting all of the parameters within the class into ints, then adding up said ints to create a checksum. Its useful if you are feeding copies of objects into a queue in one thread, and reading out with another thread. You would have to methods: ChecksumWrite and ChecksumVerify.Spelling
@Crapulent Could use an equality checker as well - but only if you had something else to compare it to. If you are feeding things into a queue, how do you work out if the items popping out the other end are valid or not? An equality checker will not work, whereas a checksum will verify internal consistency. Once the code has been proven to work with say 100 million items in the queue over a couple of weeks of deployment, you can be pretty sure that the code is solidly written, and you can remove the checksum.Spelling
I can confirm that this is much faster than the serialization method. The cost is: writing more code; the maintenance risk of adding a field without adding it to the clone method; need to write helper classes for any 3rd party classes (such as Dictionary<>)Corrientes
@Gravitas: Ah ok. I guess I had envisioned item1 compared to item2, so it seemed excessive to do item1.GetChecksum() only to compare it to item2.GetChecksum(), which would require more operations than a simple equality comparison. But if you're only comparing it to itself at a later time..., i don't know that sounds like an impossible task. My ChecksumVerify would probably call GetChecksum(), so the two would be almost certainly guaranteed to match.Crapulent
@Gravitas: I guess you're concerned about the integrity of the data over some sort of noisy transmission line, right?Crapulent
You can create an extension method that works on any object, see my answer https://mcmap.net/q/9314/-how-do-you-do-a-deep-copy-of-an-object-in-net-duplicateAstounding
@user420667. No, I am more concerned with cloning objects incorrectly which leads to bugs (see my second to last bullet point in my answer above).Spelling
It's too bad neither Java nor .NET distinguishes among references that encapsulate identity, mutable state, both, or neither. Conceptually, there should only be one type of "clone": a new object where each reference encapsulates the same thing as in the corresponding reference in the original. If a reference encapsulates identity, the clone's reference must refer to the same object. If it encapsulates mutable state but not identity, the clone must receive a reference to a different object with the same state [otherwise both references would erroneously...Aggregate
...encapsulate identity as well as state]. An object reference that encapsulates both identity and state cannot be cloned except by copying everything else which holds a reference to that object--a feat which is often difficult or impossible. While references to some types of object will usually be used to encapsulate identity, and references to others will usually encapsulate mutable state, knowing the type of an object is not sufficient to the purpose for which a reference is held.Aggregate
I've run my own speed test using Neil's serialize/deserialize extension method, Contango's Nested MemberwiseClone, Alex Burtsev's reflection-based extension method and AutoMapper, 1 million times each. Serialize-deserialize was slowest, taking 15.7 seconds. Then came AutoMapper, taking 10.1 seconds. Much faster was the reflection-based method which took 2.4 seconds. By far the fastest was Nested MemberwiseClone, taking 0.1 seconds. Comes down to performance versus hassle of adding code to each class to clone it. If performance isn't an issue go with Alex Burtsev's method.Coexecutor
How does NestedMemberwiseClone handle circular references?Levirate
@Levirate There are no circular references if one designs a data structure with none. Generally, POCO data structures simple, nested 2 (or perhaps 3) levels deep.Spelling
this.Purchase.ShallowCopy(). This makes no sense, if Purchase itself needs a DeepCopy. The correct pattern for a true DeepCopy, is to call DeepCopy on each non-primitive field. Any type that contains only primitives, simply implements DeepCopy as a ShallowCopy (Person)this.MemberwiseClone(). In a DeepCopy, the parent does not know whether each non-primitive child is deep or shallow, so it must call DeepCopy on the child. It is child's responsibility to continue deep, or do shallow. To avoid mistakes, its best to always call DeepCopy. Let the child be responsible for itself.Fransen
@Fransen Correct. Also, please keep in mind that this is demo code only, mainly aimed at showing the difference between Deep and Shallow copies. In production code, would also recommend everything is named DeepCopy right down the stack, even if it only executes a shallow copy. Fortunately, this is a self-limiting issue: having a ShallowCopy method is unlikely to produce any bugs, as the name makes it clear what it does, and it can be refactored to add a DeepCopy on demand. Fnally, this code is absolutely rock solid, and battle tested in many successful projects over the last 10 years.Spelling
G
21

I believe that the BinaryFormatter approach is relatively slow (which came as a surprise to me!). You might be able to use ProtoBuf .NET for some objects if they meet the requirements of ProtoBuf. From the ProtoBuf Getting Started page (http://code.google.com/p/protobuf-net/wiki/GettingStarted):

Notes on types supported:

Custom classes that:

  • Are marked as data-contract
  • Have a parameterless constructor
  • For Silverlight: are public
  • Many common primitives, etc.
  • Single dimension arrays: T[]
  • List<T> / IList<T>
  • Dictionary<TKey, TValue> / IDictionary<TKey, TValue>
  • any type which implements IEnumerable<T> and has an Add(T) method

The code assumes that types will be mutable around the elected members. Accordingly, custom structs are not supported, since they should be immutable.

If your class meets these requirements you could try:

public static void deepCopy<T>(ref T object2Copy, ref T objectCopy)
{
    using (var stream = new MemoryStream())
    {
        Serializer.Serialize(stream, object2Copy);
        stream.Position = 0;
        objectCopy = Serializer.Deserialize<T>(stream);
    }
}

Which is VERY fast indeed...

Edit:

Here is working code for a modification of this (tested on .NET 4.6). It uses System.Xml.Serialization and System.IO. No need to mark classes as serializable.

public void DeepCopy<T>(ref T object2Copy, ref T objectCopy)
{
    using (var stream = new MemoryStream())
    {
        var serializer = new XS.XmlSerializer(typeof(T));

        serializer.Serialize(stream, object2Copy);
        stream.Position = 0;
        objectCopy = (T)serializer.Deserialize(stream);
    }
}
Garrow answered 3/1, 2012 at 5:21 Comment(2)
Wonder how fast it is compared to the Nested MemberwiseClone answer above?Spelling
this won't work if your class has a Dictionary which has to be copied, as IDictionary cannot be serializedWidthwise
B
9

You can try this

    public static object DeepCopy(object obj)
    {
        if (obj == null)
            return null;
        Type type = obj.GetType();

        if (type.IsValueType || type == typeof(string))
        {
            return obj;
        }
        else if (type.IsArray)
        {
            Type elementType = Type.GetType(
                 type.FullName.Replace("[]", string.Empty));
            var array = obj as Array;
            Array copied = Array.CreateInstance(elementType, array.Length);
            for (int i = 0; i < array.Length; i++)
            {
                copied.SetValue(DeepCopy(array.GetValue(i)), i);
            }
            return Convert.ChangeType(copied, obj.GetType());
        }
        else if (type.IsClass)
        {

            object toret = Activator.CreateInstance(obj.GetType());
            FieldInfo[] fields = type.GetFields(BindingFlags.Public |
                        BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (FieldInfo field in fields)
            {
                object fieldValue = field.GetValue(obj);
                if (fieldValue == null)
                    continue;
                field.SetValue(toret, DeepCopy(fieldValue));
            }
            return toret;
        }
        else
            throw new ArgumentException("Unknown type");
    }

Thanks to DetoX83 article on code project.

Blush answered 1/4, 2012 at 3:59 Comment(2)
It works only if your obj has default constructor!Surround
It does not deep copy value types, but those can have fields with references to mutable objects. Also, it cannot handle object graphs with cycles.Gelid
U
4

The best way is:

    public interface IDeepClonable<T> where T : class
    {
        T DeepClone();
    }

    public class MyObj : IDeepClonable<MyObj>
    {
        public MyObj Clone()
        {
            var myObj = new MyObj();
            myObj._field1 = _field1;//value type
            myObj._field2 = _field2;//value type
            myObj._field3 = _field3;//value type

            if (_child != null)
            {
                myObj._child = _child.DeepClone(); //reference type .DeepClone() that does the same
            }

            int len = _array.Length;
            myObj._array = new MyObj[len]; // array / collection
            for (int i = 0; i < len; i++)
            {
                myObj._array[i] = _array[i];
            }

            return myObj;
        }

        private bool _field1;
        public bool Field1
        {
            get { return _field1; }
            set { _field1 = value; }
        }

        private int _field2;
        public int Property2
        {
            get { return _field2; }
            set { _field2 = value; }
        }

        private string _field3;
        public string Property3
        {
            get { return _field3; }
            set { _field3 = value; }
        }

        private MyObj _child;
        private MyObj Child
        {
            get { return _child; }
            set { _child = value; }
        }

        private MyObj[] _array = new MyObj[4];
    }
Unskilled answered 22/4, 2012 at 11:23 Comment(1)
Using CGbR Clone Generator you get the same result without manually writing the code.Gymnasiarch
D
3

Maybe you only need a shallow copy, in that case use Object.MemberWiseClone().

There are good recommendations in the documentation for MemberWiseClone() for strategies to deep copy: -

http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx

Demars answered 1/12, 2010 at 3:0 Comment(2)
Good try, but he specifically asked for a deep clone.Afebrile
You can do a deep clone with MemberwiseClone, all you do is add nesting. See answer from @Gravitas above.Spelling
C
1

The MSDN documentation seems to hint that Clone should perform a deep copy, but it is never explicitly stated:

The ICloneable interface contains one member, Clone, which is intended to support cloning beyond that supplied by MemberWiseClone… The MemberwiseClone method creates a shallow copy…

You can find my post helpful.

http://pragmaticcoding.com/index.php/cloning-objects-in-c/

Cascio answered 13/2, 2013 at 13:50 Comment(1)
The problem with ICloneable is that the Clone method does not explicitly specify whether it is performing a shallow or deep copy, so callers can never be sure. Hence, there is some [discussion|blogs.msdn.com/brada/archive/2004/05/03/125427.aspx] about making ICloneable obsolete in the .NET Framework.Greek
B
0
    public static object CopyObject(object input)
    {
        if (input != null)
        {
            object result = Activator.CreateInstance(input.GetType());
            foreach (FieldInfo field in input.GetType().GetFields(Consts.AppConsts.FullBindingList))
            {
                if (field.FieldType.GetInterface("IList", false) == null)
                {
                    field.SetValue(result, field.GetValue(input));
                }
                else
                {
                    IList listObject = (IList)field.GetValue(result);
                    if (listObject != null)
                    {
                        foreach (object item in ((IList)field.GetValue(input)))
                        {
                            listObject.Add(CopyObject(item));
                        }
                    }
                }
            }
            return result;
        }
        else
        {
            return null;
        }
    }

This way is a few times faster than BinarySerialization AND this does not require the [Serializable] attribute.

Breeden answered 4/7, 2011 at 13:57 Comment(4)
You're not continuing the deep copy down your non-IList branch and I think you would have issues with ICollection/IEnumerable.Heger
Using the "Nested MemberwiseClone" technique is an order of magnitude faster again (see my post under @Gravitas).Spelling
What is Consts.AppConsts.FullBindingList?Ronnaronnholm
Poor quality answer. Includes an unknown constant Consts.AppConsts.FullBindingListHandsaw

© 2022 - 2024 — McMap. All rights reserved.