22

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);
    }
}
nawfal
  • 70,104
  • 56
  • 326
  • 368
Allan Chua
  • 9,305
  • 9
  • 41
  • 61

3 Answers3

16

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
    {            
    }
}
sll
  • 61,540
  • 22
  • 104
  • 156
  • 1
    I don't think thats a good solution, you serialize everything in a stream and deserialize it after. Using the reflection and create a copy of the object is much better. – Felix K. Nov 06 '11 at 10:22
  • 1
    @Felix K. : you can add your solution as a new answer and we can take a look – sll Nov 06 '11 at 10:29
  • @sll a test with array type property too would have been a good fit. Never mind i will do it myself – nawfal Apr 25 '13 at 04:51
8

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;
                if (p > -1) indicies[indicies.Length - 1] = -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.

Felix K.
  • 6,201
  • 2
  • 38
  • 71
  • Can you try to clone instance of the class `CustomSerializableType` from my example and see whether all is cloned fine, you can use tests I've provided as well? My assumption - reference types would not be cloned, only references but not memory location – sll Nov 06 '11 at 11:03
  • Added some more code and tested it, it works correct and clones all reference types. – Felix K. Nov 06 '11 at 11:43
  • 1
    @Felix K.: Just found your comment in code `Does not support parameters for subobjects (!!!)`. This mean that it would not be able to clone object if it is encapsulates any object which does not provides default constructor? – sll Nov 11 '11 at 12:56
  • Thats correct, but as far as i know there is also no possibility when using serialization of .NET. Of course you can extend the code above to handle non-default constructors, but this could be a lot of work. If you want to do this you can work with custom attributes and provide informations about the creation process. You can also give the method a delegate which handles types which do not have any default constructor. – Felix K. Nov 11 '11 at 13:16
  • 1
    What is the Increase(...) method? – Tiago Deliberali Santos Mar 20 '12 at 12:50
  • @TiagoDeliberaliSantos Sorry, its increment, changed the spelling. – Felix K. Mar 20 '12 at 13:17
  • Probably the _slowest_ method of cloning because Reflection is slow. But if you aren't doing many copies, it won't significantly affect performance. – Suncat2000 Mar 02 '20 at 14:31
  • @Suncat2000 I doubt it's slower than the `BinaryFormatter`, however there are some ways to improve it, but this would be quite complex. The best performance is definitely possible by using IL-Emit and generating a serialization assembly. However this would be way too much work except you are coping a lot objects in a short time. Not that it would be hard, just a lot of work. – Felix K. Mar 25 '21 at 21:03
  • Increment Method will always Skip the Index "0". This does not work, and cant be well tested. – ThexBasic Nov 21 '22 at 16:13
  • @ThexBasic Well there are certainly a lot of people who used this and just fixed it instead of complaining. Anyway i wrote this on the fly and it wasn't used in a project so there where no unit tests around it at the time. I fixed that. – Felix K. Nov 24 '22 at 14:48
1

The solution using serialization, as suggested by sll, is by-far the simplest but doesn't work if the type you are trying to clone is not serializable.

The code from Felix K. is a good alternative but I found a few issues with it. Here is a revised version that fixes some of the problems I found. I also removed some of the functionality I didn't need (eg. constructor parameters).

/// <summary>
/// A DeepClone method for types that are not serializable.
/// </summary>
public static T DeepCloneWithoutSerialization<T>(this T original)
{
    return original.deepClone(new Dictionary<object, object>());
}

static T deepClone<T>(this T original, Dictionary<object, object> copies)
{
    return (T)original.deepClone(typeof(T), copies);
}

/// <summary>
/// Deep clone an object without using serialisation.
/// Creates a copy of each field of the object (and recurses) so that we end up with
/// a copy that doesn't include any reference to the original object.
/// </summary>
static object deepClone(this object original, Type t, Dictionary<object, object> copies)
{
    // Check if object is immutable or copy on update
    if (t.IsValueType || original == null || t == typeof(string) || t == typeof(Guid)) 
        return original;

    // Interfaces aren't much use to us
    if (t.IsInterface) 
        t = original.GetType();

    object tmpResult;
    // Check if the object already has been copied
    if (copies.TryGetValue(original, out tmpResult))
        return tmpResult;

    object result;
    if (!t.IsArray)
    {
        result = Activator.CreateInstance(t);
        copies.Add(original, result);

        // Maybe you need here some more BindingFlags
        foreach (var field in t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance))
        {
            var fieldValue = field.GetValue(original);
            field.SetValue(result, fieldValue.deepClone(field.FieldType, copies));
        }
    }
    else
    {
        // Handle arrays here
        var originalArray = (Array)original;
        var resultArray = (Array)originalArray.Clone();
        copies.Add(original, resultArray);

        var elementType = t.GetElementType();
        // If the type is not a value type we need to copy each of the elements
        if (!elementType.IsValueType)
        {
            var lengths = new int[t.GetArrayRank()];
            var indicies = new int[lengths.Length];
            // Get lengths from original array
            for (var i = 0; i < lengths.Length; i++)
                lengths[i] = resultArray.GetLength(i);

            var 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))
            {
                var value = resultArray.GetValue(indicies);
                if (value != null)
                    resultArray.SetValue(value.deepClone(elementType, copies), indicies);
            }
        }
        result = resultArray;
    }
    return result;
}

static bool increment(int[] indicies, int[] lengths, int p)
{
    if (p > -1)
    {
        indicies[p]++;
        if (indicies[p] < lengths[p])
            return true;

        if (increment(indicies, lengths, p - 1))
        {
            indicies[p] = 0;
            return true;
        }
    }
    return false;
}
nawfal
  • 70,104
  • 56
  • 326
  • 368
Jason Duffett
  • 3,428
  • 2
  • 23
  • 23
  • +1, simple a cool. Did a minor formatting edit (plus some keyword consistency) – nawfal Apr 25 '13 at 05:02
  • You'll have some issues with the code too. If you check for 't.IsValueType' and return the original the subobjects are not copied! Anyway there is some more work required, you need to implement the special constructors of the serialization ( i did this once, not funny ). – Felix K. Apr 25 '13 at 09:17
  • I'm a novice and not too sure how to use this. Absolutely do need some form of deepClone. Do I simply add it as a method in the class I want to be able to clone? When I do so, many out of scope style errors are flagged. TIA – Tony Martin Mar 17 '15 at 00:06