The tasks of copying individual objects and connected graphs are often found in programming. There are several methods for solving them, depending on the initial conditions and requirements. The purpose of the article is to examine key types of solutions, identify the scope, highlight the advantages and disadvantages
Classification of approaches to copying
1) by generalization: routine and generalized
Routine approaches imply the implementation of their own copying logic for each particular class, that is, the creation of a number of special utility methods responsible for copying certain entities. These methods often contain boring and monotonous code in large volumes. Manual writing of such methods is tedious and fraught with errors. However, automatic code generators facilitate this task, although they often impose their own restrictions on objects and the structure of graphs. The advantage of these techniques is their high performance with low memory consumption. They are usually used in protobuf serializers.
Generalized approaches, however, are spared the need to write additional logic of the same type due to a slight decrease in performance, and also apply to objects of various types that meet certain requirements.
')
2) by the possibilities of serialization and deserialization: without support, with accurate and ultra-precise support
Serialization assumes the ability to save information about the state of an object graph into a string or an array of bytes, and deserialization means to restore the graph to its original state using this information, which, in turn, allows using these mechanisms for deep copying. There are many alternative implementations of serializers for various formats, however, even being close to the destination, they are very different in nuances. But one way or another, they can be divided into two classes: exact, which in certain cases bring their own distortions into the copy, and ultra-precise ones, which allow you to restore complex graphs without changes.
Distortions are most often of the following nature:
- violation of the reference structure of the graph
[reasons: several references to one object, closed circular references]
var person = new Person(); var role = new Role(); person.MainRole = role;
var person = new Person(); var role = new Role {Person = person}; person.Roles.Add(role);
- loss of information about the types of objects
[reasons: reference to object with type of base class is used]
- distortion of related primitive types
[reasons: limitations of serialization formats]
[DataMember]public object SingleObject = 12345L;
- loss of properties when serializing collection classes
[reasons: limitations of serializers]
[CollectionDataContract] public class CustomCollection: List<object> {
- individual limitations of serializers
[reasons: for example, multidimensional arrays (object [,,,])]
* the listed disadvantages are inherent even to the standard DataContractJsonSerializerClassification of generalized copying methods
1) according to the structure of the graph: surface and deep
Shallow and deep copying is fundamentally different. Let objects A and B be given, moreover, A contains a reference to B (column A => B). At the surface copying of object A, object A 'will be created, which will also refer to B, that is, in the end, we get two columns A => B and A' => B. They will have a common part B, so when changing object B in the first column, its state will automatically mutate in the second one. Objects A and A 'will remain independent. But the most interesting are the graphs with closed (cyclic) links. Let A refer to B and B refer to A (A <=> B), when copying object A to A, we get a very unusual graph A '=> B <=> A, that is, the original object got into the final graph subjected to cloning. Deep copying involves the cloning of all objects included in the graph. For our case, A <=> B is converted to A '<=> B', as a result, both graphs are completely isolated from each other. In some cases, superficial copying is sufficient, but not always.
2) in terms of the state of the graph: full and partial
As for the state, when copying, it can be reproduced completely, that is, you get a completely identical clone, or partially, limited to only the essential data for solving the problem, for example, copy only public members or those marked with special attributes.
Overview of basic copying techniques
1) MemberwiseClone coupled with reflection
To implement shallow copy [shallow copy] of an object in the .NET platform, a special protected [protected] method MemberwiseClone of the object class is provided, which creates a complete copy of the object by copying all its fields. Using this method in combination with reflection, you can implement a recursive deep copy algorithm.
Pros:
- portable
- works fast
- does not need public and default constructors to create an object
Minuses:
- it is impossible to serialize and deserialize objects
- copies all fields in a row without the possibility of filtering them
Implementing Deep Memberwise Cloning in the Replication Framework Library using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Art.Comparers { public class ReferenceComparer<T> : IEqualityComparer<T> { public static readonly ReferenceComparer<T> Default = new ReferenceComparer<T>(); public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj); public bool Equals(T x, T y) => ReferenceEquals(x, y); } }
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using Art.Comparers; namespace Art { public static class Cloning { public static List<Type> LikeImmutableTypes = new List<Type> {typeof(string), typeof(Regex)}; public static T MemberwiseClone<T>(this T origin, bool deepMode, IEqualityComparer<object> comparer = null) => deepMode ? (T) origin.GetDeepClone(new Dictionary<object, object>(comparer ?? ReferenceComparer<object>.Default)) : (T) MemberwiseCloneMethod.Invoke(origin, null); private static readonly MethodInfo MemberwiseCloneMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance); private static IEnumerable<FieldInfo> EnumerateFields(this Type type, BindingFlags bindingFlags) => type.BaseType?.EnumerateFields(bindingFlags) .Concat(type.GetFields(bindingFlags | BindingFlags.DeclaredOnly)) ?? type.GetFields(bindingFlags); private static bool IsLikeImmutable(this Type type) => type.IsValueType || LikeImmutableTypes.Contains(type); private static object GetDeepClone(this object origin, IDictionary<object, object> originToClone) { if (origin == null) return null; var type = origin.GetType(); if (type.IsLikeImmutable()) return origin; if (originToClone.TryGetValue(origin, out var clone)) return clone; clone = MemberwiseCloneMethod.Invoke(origin, null); originToClone.Add(origin, clone); if (type.IsArray && !type.GetElementType().IsLikeImmutable()) { var array = (Array) clone; var indices = new int[array.Rank]; var dimensions = new int[array.Rank]; for (var i = 0; i < array.Rank; i++) dimensions[i] = array.GetLength(i); for (var i = 0; i < array.Length; i++) { var t = i; for (var j = indices.Length - 1; j >= 0; j--) { indices[j] = t % dimensions[j]; t /= dimensions[j]; } var deepClone = array.GetValue(indices).GetDeepClone(originToClone); array.SetValue(deepClone, indices); } } var fields = type.EnumerateFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); foreach (var field in fields.Where(f => !f.FieldType.IsLikeImmutable())) { var deepClone = field.GetValue(origin).GetDeepClone(originToClone); field.SetValue(origin, deepClone); } return clone; } } }
Application of this extension var person = new Person(); var role = new Role(); person.Roles.Add(role); var deepClone = person.MemberwiseClone(true);
* alternative but slightly non-optimal implementations of this technique are one and two2) Comparison of the functionality of some modern serialization libraries

In terms of functionality, a completely new library, the
Replication Framework , about which a
review publication on Habré appeared not very long ago, provides good expectations. Although it is designed to solve a wider range of tasks, one of the key requirements for its development was the implementation of deep copying and ultra-precise [de] serialization of arbitrarily complex graphs.
The licensed version of Replication Framework is free for non-commercial and educational use and is available
upon request. The trial version
for nuget is functional until September 2017.
Replication Framework performance noteIt may be asked why Replication Framework allows copying objects faster than other serializers, but loses in speed in the serialization and deserialization processes themselves. The point is that for copying the library uses snapshots of objects [Snapshots], and not arrays of bytes or strings, that is, no primitive values are converted, due to which acceleration is achieved. When serializing, a snapshot is first created, and after that it is converted to a json string. It is because of this intermediate step that the speed of serialization and subsequent deserialization decreases (reading a snapshot and then recreating the graph on it).
Implementing C # Deep Copy Methods
BinaryFormatter public static T GetDeepClone<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); } }
DataContractSerializer public static T GetDeepClone<T>(this T obj) { using (var ms = new MemoryStream()) {
DataContractJsonSerializer public static T GetDeepClone<T>(this T obj) { using (var ms = new MemoryStream()) { var serializer = new DataContractJsonSerializer(typeof(T)); serializer.WriteObject(ms, obj); ms.Position = 0; return (T) serializer.ReadObject(ms); } }
Newtonsoft.Json public static T GetDeepClone<T>(this T obj) { var json = JsonConvert.SerializeObject(obj); return JsonConvert.DeserializeObject<T>(json); }
Replication Framework via Memberwise Clone public static T GetShallowClone<T>(this T obj) => obj.MemberwiseClone(false); public static T GetDeepClone<T>(this T obj) => obj.MemberwiseClone(true);
Replication Framework via Snapshot public static T GetDeepClone<T>(this T obj) { var snapshot = obj.CreateSnapshot(); return snapshot.ReplicateGraph<T>(); }
Everything!