Search code examples
c#.netclassobjectclone

How can I "deeply" clone the properties of closed source 3rd party classes?


The ICloneable interface of the .NET framework usually provides a way to support cloning of an instance of a class.

But if I have multiple 3rd party classes, and don't want to care about each of the properties individually, how can I clone objects of those classes efficiently? (The source code of those classes is not available). Is there a way using generics and extension methods?

What I need is a deep clone which creates an exact copy including all the properties and (child) objects.


Example: Suppose you want to clone a UserQuery object in LinqPad:

void Main()
{
    UserQuery uc=this;
    var copy=uc.CreateCopy(); // clone it
}

What I am looking for is a CreateCopy() extension that allows to create a copy without having to take care of the details of this class since I don't own the source of UerQuery.

(Note that UserQuery is just an example to show what I require, it could as well be a PDF document class, a user control class, a ADO.NET class or anything else).


Solution

  • I have currently 2 different solutions for this, one with and one without the use of reflection:


    1.) With a generic extension method (plus reflection) you can do it this way in C#:

    public static class Extension
    {
        public static T CreateCopy<T>(this T src)
            where T: new()
        {
            if (src == null) return default(T); // just return null
            T tgt = new T(); // create new instance
            // then copy all properties
            foreach (var pS in src.GetType().GetProperties())
            {
                foreach (var pT in tgt.GetType().GetProperties())
                {
                    if (pT.Name != pS.Name) continue;
                    (pT.GetSetMethod()).Invoke(tgt, new object[] { 
                        pS.GetGetMethod().Invoke(src, null) });
                }
            };
            return tgt;
        } // method
    } // class
    

    This is very powerful, because now you can clone every object, not just objects from the classes you have written, but from all classes including the system classes of the .NET Framework. And thanks to reflection, you don't need to know its properties, they are copied automatically.

    Example

    To use the method CreateCopy(), let's say you have a Customer class and a Order class, and you need to create copies (not references) but with new IDs. Then you can do the following:

    Order CopyOrderWithNewPK(Order item)
    {
        Order newItem = item.CreateCopy(); // use ext. method to copy properties
        newItem.OrderId = new Guid(); // create new primary key for the item
        return newItem;
    }
    

    Not surprisingly, for the Customer class it would look the same:

    Customer CopyCustomerWithNewPK(Customer item)
    {
        Customer newItem = item.CreateCopy(); // use ext. method to copy properties
        newItem.CustomerId = new Guid(); // create new primary key for the item
        return newItem;
    }
    

    Note that the values of all properties defined within the example classes are copied automatically. You can even clone an object of a 3rd party assembly if you don't own the source code. The trade-off is, that the reflection approach is slower.


    2.) There's another another way to do it without reflection, inspired by this question. One advantage is that it even is able to clone the objects of the Entity Framework (for example to attach and re-attach entity objects to a different data context):

    // using System.Runtime.Serialization;
    public class Cloner<T>
    {
        readonly DataContractSerializer _serializer 
                = new DataContractSerializer(typeof(T));
    
        /// <summary>
        /// Clone an object graph
        /// </summary>
        /// <param name="graph"></param>
        /// <returns></returns>
        public T Clone(T graph)
        {
            MemoryStream stream = new MemoryStream();
            _serializer.WriteObject(stream, graph);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)_serializer.ReadObject(stream);
        }
    }
    

    In order to not break the examples above, you can change the extension method CreateCopy as follows:

    public static class Extension
    {   
        public static T CreateCopy<T>(this T src)
            where T: new()
        {
                return (new Cloner<T>()).Clone(src);
        }   
    }   
    

    Note: Although Cloner is using System.Runtime.Serialization, the object being cloned does not need to be serializable. This can be an advantage, other solutions I have seen can only clone serializable objects.