The ability to perform a true deep copy of an object is a classic requirement for most software developers. For those who need to understand the key difference between a deep copy and a shallow copy of an object, let's quickly summarize it:
- A Deep Copy is a second instance (B) of the source object (A) with the same values: all (A) properties are also deeply copied as well into (B), meaning that there will be no cross-references between (A) properties and (B) properties: for example, if you alter B.Property , A.Property won't be affected.
- A Shallow Copy, also known as field copy, will also create a second instance (B) of the source object (A), but will copy all the fields values of the source object (A) over to (B) without creating a new instance of them. When the field value is a primitive type there won't be any difference with the previous method: however, whenever the field's value is a reference to an object (e.g., a memory address) such method will copy the reference, hence referring to the same object as A does. The referenced objects are thus shared, so if one of these objects is modified (from A or B), the change is visible in the other.
When it comes to coding a shallow copy is simple and typically easy to pull off, as they can be usually implemented by simply copying the bits exactly: in C# it can even be done with an one-liner, thanks to the native MemberwiseClone object method. Conversely, a deep copy will take additional work and is more resource-intensive, expecially when we're talking about big object (with significative references).
Among the many approaches offered by C# to achieve this, here are two of the most used methods: the BinaryFormatter approach and the Recursive MemberwiseClone way. In this article we'll try our best to briefly review both of them.
Method #1: BinaryFormatter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/// <summary> /// Perform a deep Copy of the object using a BinaryFormatter. /// IMPORTANT: the object class must be marked as [Serializable] and have an parameterless constructor. /// </summary> /// <typeparam name="T">The type of object being copied.</typeparam> /// <param name="source">The object instance to copy.</param> /// <returns>The copied object.</returns> public static T Clone<T>(this T source) { if (!typeof(T).IsSerializable) { throw new ArgumentException("The type must be serializable.", "source"); } // Don't serialize a null object, simply return the default for that object if (Object.ReferenceEquals(source, null)) { return default(T); } IFormatter formatter = new BinaryFormatter(); using (Stream stream = new MemoryStream()) { formatter.Serialize(stream, source); stream.Seek(0, SeekOrigin.Begin); return (T)formatter.Deserialize(stream); } } |
As the comments say, our class must be marked as [Serializable] and have a default, parameter-less constructor in order for this approach to work. Our source file must also reference the following namespaces:
1 2 |
using System.Runtime.Serialization.Formatters.Binary; using System.IO; |
Method #2: Recursive MemberwiseClone
The same result can be achieved using a recursive call to the aforementioned MemberwiseClone native C# function. Such approach that has some performance benefits over the BinaryFormatter one: it's 2x/3x times faster and it doesn't require a default parameter-less constructor or any attributes. The only downside is that the efforts in writing the actula code, because we need to manually iterate through the various members and compare them using a custom-made IComparer.
Luckily enough, there's a great GitHub object extensions class written by Alexey Burtsev that does all that in a neat way: the code is released under a MIT-license, meaning that it can be used in any project as long as the author's credits are mantained.
Here's our slightly modified version of that class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
/// <summary> /// C# extension method for fast object cloning. /// Based upon the great net-object-deep-copy GitHub project by Alexey Burtsev, released under the MIT license. /// /// https://github.com/Burtsev-Alexey/net-object-deep-copy /// </summary> public static partial class ObjectExtensions { /// <summary> /// The Clone Method that will be recursively used for the deep clone. /// </summary> private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance); /// <summary> /// Returns TRUE if the type is a primitive one, FALSE otherwise. /// </summary> public static bool IsPrimitive(this Type type) { if (type == typeof(String)) return true; return (type.IsValueType & type.IsPrimitive); } /// <summary> /// Returns a Deep Clone / Deep Copy of an object using a recursive call to the CloneMethod specified above. /// </summary> public static Object DeepClone(this Object obj) { return DeepClone_Internal(obj, new Dictionary<Object, Object>(new ReferenceEqualityComparer())); } /// <summary> /// Returns a Deep Clone / Deep Copy of an object of type T using a recursive call to the CloneMethod specified above. /// </summary> public static T DeepClone<T>(this T obj) { return (T)DeepClone((Object)obj); } private static Object DeepClone_Internal(Object obj, IDictionary<Object, Object> visited) { if (obj == null) return null; var typeToReflect = obj.GetType(); if (IsPrimitive(typeToReflect)) return obj; if (visited.ContainsKey(obj)) return visited[obj]; if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null; var cloneObject = CloneMethod.Invoke(obj, null); if (typeToReflect.IsArray) { var arrayType = typeToReflect.GetElementType(); if (IsPrimitive(arrayType) == false) { Array clonedArray = (Array)cloneObject; clonedArray.ForEach((array, indices) => array.SetValue(DeepClone_Internal(clonedArray.GetValue(indices), visited), indices)); } } visited.Add(obj, cloneObject); CopyFields(obj, visited, cloneObject, typeToReflect); RecursiveCopyBaseTypePrivateFields(obj, 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 = DeepClone_Internal(originalFieldValue, visited); fieldInfo.SetValue(cloneObject, clonedFieldValue); } } } |
In order for this to work properly we also need to add a couple more pieces of code within the same namespace.
A custom EqualityComparer:
1 2 3 4 5 6 7 8 9 10 11 12 |
internal 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(); } } |
And the following extension method for the Array type, together with an ArrayTraverse internal class that will be used to traverse through the array-types references (if any):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
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; } } |
That's it for now: we sincerely hope that this post will help many C# developers looking for a neat way to deep-clone their objects!
This approach fails for XDocuments
superb! awesomely works.