Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a much better way to create deep and shallow clones in C#?

Tags:

I have been creating object for a project and there are some instances that I have to create a deep copy for this objects I have come up with the use of a built in function for C# which is MemberwiseClone(). The problem that bothers me is whenever there is a new class that i created , I would have to write a function like the code below for a shallow copy..Can someone please help me improve this part and give me a shallow copy that is better than the second line of code. thanks :)

SHALLOW COPY:

public static RoomType CreateTwin(RoomType roomType) {     return (roomType.MemberwiseClone() as RoomType); } 

DEEP COPY:

public static T CreateDeepClone<T>(T source) {     if (!typeof(T).IsSerializable)     {         throw new ArgumentException("The type must be serializable.", "source");     }      if (Object.ReferenceEquals(source, null))     {         return default(T);     }      IFormatter formatter = new BinaryFormatter();     Stream stream = new MemoryStream();     using (stream)     {         formatter.Serialize(stream, source);         stream.Seek(0, SeekOrigin.Begin);         return (T)formatter.Deserialize(stream);     } } 
like image 327
Allan Chua Avatar asked Nov 06 '11 08:11

Allan Chua


People also ask

Which is better deep copy or shallow copy?

Shallow Copy stores the copy of the original object and points the references to the objects. Deep copy stores the copy of the original object and recursively copies the objects as well. Shallow copy is faster. Deep copy is comparatively slower.

Why would you want a shallow copy?

Shallow copies are useful when you want to make copies of classes that share one large underlying data structure or set of data.

How can you actually the deep cloning of an object?

To achieve a deep copy, we can serialize an object and then deserialize it to a new object.

What should shallow copying be applied to?

Shallow copies are mostly used for quick copying of objects, without copying the data, but once an objects needs to modify the shared data, a deep copy of it is taken.


2 Answers

MemberwiseClone is not a good choice to do a Deep Copy (MSDN):

The MemberwiseClone method creates a shallow copy by creating a new object, and then copying the nonstatic fields of the current object to the new object. If a field is a value type, a bit-by-bit copy of the field is performed. If a field is a reference type, the reference is copied but the referred object is not; therefore, the original object and its clone refer to the same object.

This mean if cloned object has reference type public fields or properties they would reffer to the same memory location as the original object's fields/properties, so each change in the cloned object will be reflected in the initial object. This is not a true deep copy.

You can use BinarySerialization to create a completely independent instance of the object, see MSDN Page of the BinaryFormatter class for an serialization example.


Example and Test Harness:

Extension method to create a deep copy of a given object:

public static class MemoryUtils {     /// <summary>     /// Creates a deep copy of a given object instance     /// </summary>     /// <typeparam name="TObject">Type of a given object</typeparam>     /// <param name="instance">Object to be cloned</param>     /// <param name="throwInCaseOfError">     /// A value which indicating whether exception should be thrown in case of     /// error whils clonin</param>     /// <returns>Returns a deep copy of a given object</returns>     /// <remarks>Uses BInarySerialization to create a true deep copy</remarks>     public static TObject DeepCopy<TObject>(this TObject instance, bool throwInCaseOfError)         where TObject : class     {         if (instance == null)         {             throw new ArgumentNullException("instance");         }          TObject clonedInstance = default(TObject);          try         {             using (var stream = new MemoryStream())             {                 BinaryFormatter binaryFormatter = new BinaryFormatter();                 binaryFormatter.Serialize(stream, instance);                  // reset position to the beginning of the stream so                 // deserialize would be able to deserialize an object instance                 stream.Position = 0;                  clonedInstance = (TObject)binaryFormatter.Deserialize(stream);             }         }         catch (Exception exception)         {             string errorMessage = String.Format(CultureInfo.CurrentCulture,                             "Exception Type: {0}, Message: {1}{2}",                             exception.GetType(),                             exception.Message,                             exception.InnerException == null ? String.Empty :                             String.Format(CultureInfo.CurrentCulture,                                         " InnerException Type: {0}, Message: {1}",                                         exception.InnerException.GetType(),                                         exception.InnerException.Message));             Debug.WriteLine(errorMessage);              if (throwInCaseOfError)             {                 throw;             }         }          return clonedInstance;     } } 

NUnit tests:

public class MemoryUtilsFixture {     [Test]     public void DeepCopyThrowWhenCopyInstanceOfNonSerializableType()     {         var nonSerializableInstance = new CustomNonSerializableType();         Assert.Throws<SerializationException>(() => nonSerializableInstance.DeepCopy(true));     }      [Test]     public void DeepCopyThrowWhenPassedInNull()     {         object instance = null;         Assert.Throws<ArgumentNullException>(() => instance.DeepCopy(true));     }      [Test]     public void DeepCopyThrowWhenCopyInstanceOfNonSerializableTypeAndErrorsDisabled()     {         var nonSerializableInstance = new CustomNonSerializableType();                     object result = null;          Assert.DoesNotThrow(() => result = nonSerializableInstance.DeepCopy(false));         Assert.IsNull(result);     }      [Test]     public void DeepCopyShouldCreateExactAndIndependentCopyOfAnObject()     {         var instance = new CustomSerializableType                         {                             DateTimeValueType =                                 DateTime.Now.AddDays(1).AddMilliseconds(123).AddTicks(123),                             NumericValueType = 777,                             StringValueType = Guid.NewGuid().ToString(),                             ReferenceType =                                 new CustomSerializableType                                     {                                         DateTimeValueType = DateTime.Now,                                         StringValueType = Guid.NewGuid().ToString()                                     }                         };          var deepCopy = instance.DeepCopy(true);          Assert.IsNotNull(deepCopy);         Assert.IsFalse(ReferenceEquals(instance, deepCopy));         Assert.That(instance.NumericValueType == deepCopy.NumericValueType);         Assert.That(instance.DateTimeValueType == deepCopy.DateTimeValueType);         Assert.That(instance.StringValueType == deepCopy.StringValueType);         Assert.IsNotNull(deepCopy.ReferenceType);         Assert.IsFalse(ReferenceEquals(instance.ReferenceType, deepCopy.ReferenceType));         Assert.That(instance.ReferenceType.DateTimeValueType == deepCopy.ReferenceType.DateTimeValueType);         Assert.That(instance.ReferenceType.StringValueType == deepCopy.ReferenceType.StringValueType);     }      [Serializable]     internal sealed class CustomSerializableType     {                     public int NumericValueType { get; set; }         public string StringValueType { get; set; }         public DateTime DateTimeValueType { get; set; }          public CustomSerializableType ReferenceType { get; set; }     }      public sealed class CustomNonSerializableType     {                 } } 
like image 71
sll Avatar answered Oct 02 '22 06:10

sll


You can also use reflection to create a copy of the object, this should be the fastest way, because the serialization uses the reflection too.

Here some code ( tested ):

public static T DeepClone<T>(this T original, params Object[] args) {     return original.DeepClone(new Dictionary<Object, Object>(), args); }  private static T DeepClone<T>(this T original, Dictionary<Object, Object> copies, params Object[] args) {     T result;     Type t = original.GetType();      Object tmpResult;     // Check if the object already has been copied     if (copies.TryGetValue(original, out tmpResult))     {         return (T)tmpResult;     }     else     {         if (!t.IsArray)         {             /* Create new instance, at this point you pass parameters to                 * the constructor if the constructor if there is no default constructor                 * or you change it to Activator.CreateInstance<T>() if there is always                 * a default constructor */             result = (T)Activator.CreateInstance(t, args);             copies.Add(original, result);              // Maybe you need here some more BindingFlags             foreach (FieldInfo field in t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance))             {                 /* You can filter the fields here ( look for attributes and avoid                     * unwanted fields ) */                  Object fieldValue = field.GetValue(original);                  // Check here if the instance should be cloned                 Type ft = field.FieldType;                  /* You can check here for ft.GetCustomAttributes(typeof(SerializableAttribute), false).Length != 0 to                      * avoid types which do not support serialization ( e.g. NetworkStreams ) */                 if (fieldValue != null && !ft.IsValueType && ft != typeof(String))                 {                     fieldValue = fieldValue.DeepClone(copies);                     /* Does not support parameters for subobjects nativly, but you can provide them when using                         * a delegate to create the objects instead of the Activator. Delegates should not work here                         * they need some more love */                 }                  field.SetValue(result, fieldValue);             }         }         else         {             // Handle arrays here             Array originalArray = (Array)(Object)original;             Array resultArray = (Array)originalArray.Clone();             copies.Add(original, resultArray);              // If the type is not a value type we need to copy each of the elements             if (!t.GetElementType().IsValueType)             {                 Int32[] lengths = new Int32[t.GetArrayRank()];                 Int32[] indicies = new Int32[lengths.Length];                 // Get lengths from original array                 for (int i = 0; i < lengths.Length; i++)                 {                     lengths[i] = resultArray.GetLength(i);                 }                  Int32 p = lengths.Length - 1;                  /* Now we need to iterate though each of the ranks                     * we need to keep it generic to support all array ranks */                 while (Increment(indicies, lengths, p))                 {                     Object value = resultArray.GetValue(indicies);                     if (value != null)                        resultArray.SetValue(value.DeepClone(copies), indicies);                  }             }             result = (T)(Object)resultArray;         }         return result;     } }  private static Boolean Increment(Int32[] indicies, Int32[] lengths, Int32 p) {     if (p > -1)     {         indicies[p]++;         if (indicies[p] < lengths[p])         {             return true;         }         else         {             if (Increment(indicies, lengths, p - 1))             {                 indicies[p] = 0;                 return true;             }             else             {                 return false;             }         }     }     return false; } 

Update

Added some more code, now you can use the method to copy complex objects ( even arrays with multiple dimensions ). Note that delegates are still not implemented.

If you want a complete implementation you need to handle the ISerializable interface which is not really hard but takes some time to reflect over the existing code. Did this once for a remoting implementation.

like image 21
Felix K. Avatar answered Oct 02 '22 05:10

Felix K.