0

.NET 框架的ICloneable接口通常提供一种支持克隆类实例的方法。

但是,如果我有多个 3rd 方类,并且不想单独关心每个属性,如何有效地克隆这些类的对象?(这些类的源代码不可用)。有没有使用泛型扩展方法的方法

我需要的是一个深度克隆,它创建一个包含所有属性和(子)对象的精确副本。


示例:假设您要在LinqPadUserQuery中克隆一个对象:

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

我正在寻找的是一个CreateCopy()扩展,它允许创建副本而无需处理此类的细节,因为我不拥有UerQuery.

(请注意,这UserQuery只是显示我需要的示例,它也可以是 PDF 文档类、用户控件类、ADO.NET 类或其他任何东西)。

4

2 回答 2

7

我目前有两种不同的解决方案,一种使用反射,一种不使用反射:


1.)使用通用扩展方法(加上反射),您可以在 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

这非常强大,因为现在您可以克隆每个对象,不仅是您编写的类中的对象,还包括所有类中的对象,包括 .NET Framework 的系统类。并且由于反射,您不需要知道它的属性,它们会自动复制。

例子

要使用 method CreateCopy(),假设您有一个Customer类和一个Order类,并且您需要创建副本(不是引用)但具有新的 ID。然后您可以执行以下操作:

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;
}

毫不奇怪,对于 Customer 类,它看起来是一样的:

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;
}

请注意,示例类中定义的所有属性的值都会自动复制。如果您不拥有源代码,您甚至可以克隆第 3 方程序集的对象。权衡是,反射方法更慢。


2.)受这个问题的启发,还有另一种无需反思的方法。一个优点是它甚至能够克隆实体框架的对象(例如,将实体对象附加和重新附加到不同的数据上下文):

// 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);
    }
}

为了不破坏上面的例子,你可以改变扩展方法CreateCopy如下:

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

注意:虽然Cloneris using System.Runtime.Serialization,但被克隆的对象不需要是可序列化的。这可能是一个优势,我见过的其他解决方案只能克隆可序列化对象。

于 2012-10-16T08:50:05.780 回答
1

如果你实现了ICloneable你就不必关心每个属性!你的回答很好,但对我来说,它的代码和反射性能都很差(!),我更喜欢使用

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

    #region ICloneable-Member

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

    #endregion
}
于 2012-10-16T08:57:59.517 回答