深度克隆对象

时间:2020-03-05 18:57:33  来源:igfitidea点击:

我想做类似的事情:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

然后对未反映在原始对象中的新对象进行更改。

我通常不需要此功能,因此在必要时我会先创建一个新对象,然后分别复制每个属性,但是它总是让我感到有更好或者更优雅的处理方式情况。

如何克隆或者深度复制对象,以便可以修改克隆的对象而不会在原始对象中反映任何更改?

解决方案

回答

通常,我们可以实现ICloneable接口并自己实现Clone。
Cobject具有内置的MemberwiseClone方法,该方法执行浅表复制,可以解决所有原语。

对于深层副本,它无法知道如何自动执行。

回答

  • 基本上,我们需要实现ICloneable接口,然后实现对象结构复制。
  • 如果是所有成员的完整副本,则需要确保(与选择的解决方案无关)所有子代也都是可克隆的。
  • 有时我们需要在此过程中意识到一些限制,例如,如果我们复制ORM对象,则大多数框架仅允许将一个对象添加到会话,并且我们不得对该对象进行克隆,或者是否有可能需要注意关于这些对象的会话添加。

干杯。

回答

与克隆相比,我更喜欢复制构造函数。意图更加明确。

回答

简短的答案是我们从IClo​​neable接口继承,然后实现.clone函数。克隆应该执行成员级复制,并在需要复制的任何成员上执行深层复制,然后返回结果对象。这是一个递归操作(它要求我们要克隆的类的所有成员均为值类型或者实现ICloneable,并且其成员为值类型或者实现ICloneable,依此类推)。

有关使用ICloneable进行克隆的更详细说明,请参阅本文。

长的答案是"取决于"。正如其他人提到的那样,泛型不支持ICloneable,ICloneable需要对循环类引用进行特殊考虑,并且在某些情况下,ICloneable实际上在.NET Framework中被视为"错误"。序列化方法取决于对象是否可序列化,它们可能不是,并且我们可能无法控制。关于"最佳"实践,社区中仍有许多争议。实际上,所有解决方案都不是一种适用于所有情况的最佳解决方案,例如最初解释为ICloneable的情况。

有关更多选择(请参阅Ian),请参见此开发人员专栏文章。

回答

虽然标准做法是实现" ICloneable"接口(在此进行了描述,所以我不会反驳),但我还是在一段时间前在The Code Project上发现了一个不错的深克隆对象复印机,并将其合并到我们的资料中。

如在其他地方提到的,它确实要求对象可序列化。

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep Copy of the object.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "source");
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }
}

这个想法是,它序列化对象,然后将其反序列化为一个新的对象。这样做的好处是,当对象变得太复杂时,我们不必担心克隆所有内容。

并使用扩展方法(也来自最初引用的源):

如果我们更喜欢使用C3.0的新扩展方法,请将方法更改为具有以下签名:

public static T Clone<T>(this T source)
{
   //...
}

现在,方法调用简单地变成了" objectBeingCloned.Clone();"。

编辑(2015年1月10日)以为我会重新审视这一点,我最近提到开始使用(Newtonsoft)Json来做到这一点,它应该更轻巧,并且避免了[Serializable]标签的开销。 (注意:@atconway在注释中指出,不使用JSON方法克隆私有成员)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}

回答

不使用ICloneable的原因不是因为它没有通用接口。不使用它的原因是因为它含糊不清。不清楚是要浅拷贝还是要深拷贝。这取决于实施者。

是的,MemberwiseClone会进行浅表复制,但与MemberwiseClone相反的不是Clone;可能是" DeepClone",它不存在。通过对象的ICloneable接口使用对象时,我们不知道基础对象执行哪种克隆。 (而且XML注释也不清楚,因为我们将获得接口注释,而不是对象的Clone方法中的注释。)

我通常所做的只是简单地制作一个完全符合我想要的Copy方法。

回答

我想出了这个方法来克服.NET的缺点,即必须手动深复制List <T>。

我用这个:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

在另一个地方:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

我试图提出一个做到这一点的oneliner,但是这是不可能的,因为yield无法在匿名方法块中工作。

更好的是,使用通用List <T>克隆器:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}

回答

我在Silverlight中使用ICloneable时遇到了问题,但是我喜欢序列化的想法,因为我可以序列化XML,所以我这样做了:

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //[email protected]
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

        TextReader tr = new StringReader(xmlData);
        T DocItms = new T();
        XmlSerializer xms = new XmlSerializer(DocItms.GetType());
        DocItms = (T)xms.Deserialize(tr);

        return DocItms == null ? default(T) : DocItms;
    }

    public static string SeralizeObjectToXML<T>(T xmlObject)
    {
        StringBuilder sbTR = new StringBuilder();
        XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
        XmlWriterSettings xwsTR = new XmlWriterSettings();

        XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
        xmsTR.Serialize(xmwTR,xmlObject);

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}

回答

我也看到它是通过反射实现的。基本上,有一种方法可以遍历对象的成员并将其适当地复制到新对象。当它到达引用类型或者集合时,我认为它对自己进行了递归调用。反射很昂贵,但是效果很好。