How can I “deeply” clone the properties of 3rd par

2019-02-11 13:39发布

问题:

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).

回答1:

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.



回答2:

If you implement the ICloneable you dont have to care about each property! Your answer is good but for me its pretty much code and reflections have low performance (!), i prefer to use

class Test : ICloneable
{
    public int A { get; set; }
    public int B { get; set; }

    #region ICloneable-Member

    public object Clone()
    {
        return base.MemberwiseClone();
    }

    #endregion
}