Thursday, 17 December 2009

Easy, type safe, Object deep cloning

I tend to find amazing the pain that some people go thru to deep clone objects. From non generic methods that copy only one object, to recursive reflection based function calls (these tend to be more generic than the previous), implementing IClonable (so .Net 1.1), to other sightly more obscure methods....

One of the easiest ways to deep clone a object is to (try to) serialize it and immediately desirialize it. But, as explained in this excellent post, this approach seems to also have disadvantages.

.Net 3.5 to the rescue. Using the DataContractSerializer actually gets rid of some of the mentioned problems. The object does not have to be marked as serializable, and you can always mark your object as a DataContract if you feel the need to set non public properties or fields.
Since .Net 3.5, the data contract serializer will accept any POCO, not just DataContracts as MSDN claims.

Ok, 2 problems solved, but can we make type safe and generic? Yes, we can. Extension Methods anyone?
Using extension methods and generics we can easily create a type safe cloner that can attach itself to any type.

This is what we want to do:
Person toClone = new Person()

// Set stuff here.

Person cloned = toClone.DeepClone();

A very simple approach:
using System.IO;
using System.Runtime.Serialization;

namespace Extensions
{
    public static class CloningExtensions
    {
        public static T DeepClone<T>(this T toClone)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                DataContractSerializer serializer = new DataContractSerializer(typeof(T));
                serializer.WriteObject(stream, toClone);
                stream.Position = 0;
                return (T)serializer.ReadObject(stream);
            }
        }
    }
}
The test:
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Extensions;

namespace CloningExtensionsTests
{
    [TestClass]
    public class ExtensionsTests
    {
        public class Person
        {
            public string Name { get; set; }

            public DateTime DateOfBirth { get; set; }

            public List<Person> Siblings { get; set; }
        }

        [TestMethod]
        public void DeepCloneTest()
        {
            Person toClone =
                new Person
                    {
                        Name = "Paulo Serra",
                        DateOfBirth = new DateTime(1972, 4, 2),
                        Siblings = new List<Person>
                                       {
                                           new Person
                                               {
                                                   Name = "Andre Serra",
                                                   DateOfBirth = new DateTime(2002, 3, 27)
                                               }
                                       }
                    };

            Person cloned = toClone.DeepClone();

            // Object is not an object reference
            Assert.AreNotSame(toClone, cloned);

            // Properties were cloned
            Assert.AreEqual(toClone.Name, cloned.Name);
            Assert.AreEqual(toClone.DateOfBirth, cloned.DateOfBirth);

            // Inner object is not an object reference.
            Assert.AreNotSame(toClone.Siblings, cloned.Siblings);

            // Inner object properties were cloned.
            Assert.AreEqual(toClone.Siblings[0].Name, cloned.Siblings[0].Name);
            Assert.AreEqual(toClone.Siblings[0].DateOfBirth, cloned.Siblings[0].DateOfBirth);
        }
    }
}

Hope this post has been useful to somebody, be back shortly for another one ;)

1 comment: