深度克隆对象
我想做类似的事情:
MyObject myObj = GetMyObj(); // Create and fill a new object MyObject newObj = myObj.Clone();
然后对未反映在原始对象中的新对象进行更改。
我通常不需要此功能,因此在必要时我会先创建一个新对象,然后分别复制每个属性,但是它总是让我感到有更好或者更优雅的处理方式情况。
如何克隆或者深度复制对象,以便可以修改克隆的对象而不会在原始对象中反映任何更改?
解决方案
回答
通常,我们可以实现ICloneable接口并自己实现Clone。
Cobject具有内置的MemberwiseClone方法,该方法执行浅表复制,可以解决所有原语。
对于深层副本,它无法知道如何自动执行。
回答
- 基本上,我们需要实现ICloneable接口,然后实现对象结构复制。
- 如果是所有成员的完整副本,则需要确保(与选择的解决方案无关)所有子代也都是可克隆的。
- 有时我们需要在此过程中意识到一些限制,例如,如果我们复制ORM对象,则大多数框架仅允许将一个对象添加到会话,并且我们不得对该对象进行克隆,或者是否有可能需要注意关于这些对象的会话添加。
干杯。
回答
与克隆相比,我更喜欢复制构造函数。意图更加明确。
回答
简短的答案是我们从ICloneable接口继承,然后实现.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); } }
回答
我也看到它是通过反射实现的。基本上,有一种方法可以遍历对象的成员并将其适当地复制到新对象。当它到达引用类型或者集合时,我认为它对自己进行了递归调用。反射很昂贵,但是效果很好。