About Me

My photo
Kozyatağı, İstanbul, Türkiye

Monday, March 25, 2013

Deep Copying Methods for .NET Objects


When we need to make a copy of an instance of a class, we have two different options that end up with different behaviors.

          Shallow Copy:
     Creates a new instance of class and assign values of each field from source object. This can be applied by “MemberwiseClone” method of object class.
For reference type fields, this operation causes “copying reference pointer” of field in source object. After copying, source field and destination field will be pointing to same referenced object in memory.
Code:
 class TestData
    {
        public int Id { get; set; }
        public string Name { get; set; }
        List<int> iList;

        public TestData Clone()
        {
            TestData res = (TestData)this.MemberwiseClone();
            return res;
        }
    }

Deep Copy:
Creates a new instance of object in a totally “memory separated” manner. This operation should create copy of reference types in a way that referenced instance creates a copy of itself. When the creation is complete, there must be no reference in common with source and destination object.

If all the fields of the class are value types or string type, shallow and deep copying will end up with the same result.

In .NET,  there is no direct framework help for deep copying. Deep copying is generally implemented by “Serialization-Deserialization” technique. This technique is quite subtle and easily implemented. To be honest, I was impressed when I saw it for the first time. However it has its own drawbacks, such as:

n  All the reference types that are part of copying must be marked as serializable.
n  Serialization and deserialization are relatively slow methods.

Here is the code for “Serialization-Deserialization” method.
C# Code:
 public static object DeepCopyWithSerialization(object source)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream())
            {
                formatter.Serialize(ms, source);
                ms.Position = 0;
                object res = formatter.Deserialize(ms);
                return res;
            }

        }

I want to suggest another method which is implemented by using System.Reflection library. It is quite faster than serialization-deserialization method.
Main idea of this method is recursively creating copy of reference types until value type or string type is encountered. Here we make an exception for string class because all the strings that have the same value point to same memory area in .NET.
For initial copy of object, we need to use “MemberwiseClone” method of object class. Unfortunately, this method is protected. So we have to invoke this method by reflection over NonPublic binding flag.

C# Code:
static class DeepCopyHelper
    {
        static MethodInfo miMemberwiseClone;
        static Type stringType;
        static DeepCopyHelper()
        {
            miMemberwiseClone = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);
            stringType = typeof(string);
        }


        public static object DeepCopyWithReflection(object objSource)
        {
            object objDest = miMemberwiseClone.Invoke(objSource, null);
            FieldInfo[] fiArr = GetFields(objSource);
            foreach (FieldInfo fi in fiArr)
            {
                object readValue = fi.GetValue(objSource);
                if (readValue != null)
                {
                    Type typeSource = readValue.GetType();
                    if (typeSource.IsClass && !typeSource.Equals(stringType))
                    {
                        readValue = DeepCopyWithReflection(readValue);
                    }
                }
                fi.SetValue(objDest, readValue);
            }
            return objDest;
        }

        static FieldInfo[] GetFields(object obj)
        {
            Type t = obj.GetType();
            FieldInfo[] fiArr = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            return fiArr;
        }

    }

No comments:

Post a Comment