深度克隆对象

我想做这样的事情:

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

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

我不经常需要这个功能,所以当有必要的时候,我求助于创建一个新对象,然后单独复制每个属性,但它总是让我觉得有更好或更优雅的方式来处理这种情况。

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

961593 次浏览

一般来说,您可以实现IClone接口并自己实现Clone。C#对象有一个内置的MemberwiseClone方法,它执行一个浅拷贝,可以帮助您完成所有原语。

对于深度复制,它不可能知道如何自动执行。

  1. 基本上你需要实现ICLonable接口,然后实现对象结构复制。
  2. 如果它是所有成员的深层副本,您需要确保(与您选择的解决方案无关)所有子级也是可克隆的。
  3. 有时你需要在这个过程中意识到一些限制,例如,如果你复制ORM对象,大多数框架只允许一个对象附加到会话,你不能克隆这个对象,或者如果可能的话,你需要关心这些对象的会话附加。

干杯。

我更喜欢复制构造函数而不是克隆。意图更清晰。

简短的回答是你从IClonable接口继承,然后实现. clone函数。Clone应该进行成员复制,并对任何需要它的成员执行深度复制,然后返回结果对象。这是一个递归操作(它要求你要克隆的类的所有成员要么是值类型,要么是实现IClonable,并且它们的成员要么是值类型,要么是实现IClonable,依此类推)。

要了解有关使用IClONBLE进行克隆的更详细说明,请查看这篇文章

长期的答案是“这取决于”。正如其他人所提到的,泛型不支持IC的,循环类引用需要特殊考虑,并且实际上被一些人视为. NET Framework中的“错误”。序列化方法取决于你的对象是可序列化的,而它们可能不是,你可能无法控制。社区中关于哪个是“最佳”实践仍然有很多争论。实际上,没有一个解决方案是适用于所有情况的一刀切的最佳实践,就像IC的最初解释那样。

看到这个开发者角文章更多的选择(信贷伊恩)。

一种方法是实现#0接口(描述了这里,所以我不会重复),这是我在代码项目上找到的一个不错的深度克隆对象复印机,并将其合并到我们的代码中。如前所述,它要求您的对象是可序列化的。

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 via serialization./// </summary>/// <typeparam name="T">The type of object being copied.</typeparam>/// <param name="source">The object instance to copy.</param>/// <returns>A deep copy of the object.</returns>public static T Clone<T>(T source){if (!typeof(T).IsSerializable){throw new ArgumentException("The type must be serializable.", nameof(source));}
// Don't serialize a null object, simply return the default for that objectif (ReferenceEquals(source, null)) return default;
using var Stream stream = new MemoryStream();IFormatter formatter = new BinaryFormatter();formatter.Serialize(stream, source);stream.Seek(0, SeekOrigin.Begin);return (T)formatter.Deserialize(stream);}}

这个想法是它序列化你的对象,然后反序列化成一个新的对象。好处是,当对象变得太复杂时,你不必担心克隆所有东西。

如果您更喜欢使用C#3.0的新扩展方法,请更改方法以具有以下签名:

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

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

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

/// <summary>/// Perform a deep Copy of the object, using Json as a serialization 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 objectif (ReferenceEquals(source, null)) return default;
// 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 resultvar deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);}

不使用冰封的的原因是没有,因为它没有通用接口。不使用它的原因是因为它很模糊。它不清楚你得到的是浅拷贝还是深拷贝;这取决于实现者。

是的,MemberwiseClone做了一个浅拷贝,但MemberwiseClone的对立面不是Clone;它可能是DeepClone,它不存在。当你通过一个对象的IClone接口使用它时,你不知道底层对象执行的是哪种克隆。(而且XML注释不会让它更清楚,因为你会得到接口注释,而不是对象的Clone方法上的注释。)

我通常做的是简单地创建一个Copy方法,它完全符合我的要求。

我想出这个来克服一个. NET的缺点,必须手动深度复制List

我用这个:

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,但这是不可能的,因为在匿名方法块中不工作。

更好的是,使用泛型List克隆器:

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中使用IClonable时遇到了问题,但我喜欢序列化的想法,我可以序列化XML,所以我这样做了:

static public class SerializeHelper{//Michael White, Holly Springs Consulting, 2009//michael@hollyspringsconsulting.compublic 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);}}

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

复制所有公共属性的简单扩展方法。适用于任何对象,不要要求类为[Serializable]。可以扩展为其他访问级别。

public static void CopyTo( this object S, object T ){foreach( var pS in S.GetType().GetProperties() ){foreach( var pT in T.GetType().GetProperties() ){if( pT.Name != pS.Name ) continue;( pT.GetSetMethod() ).Invoke( T, new object[]{ pS.GetGetMethod().Invoke( S, null ) } );}};}

这是一个深度复制实现:

public static object CloneObject(object opSource){//grab the type and create a new instance of that typeType opSourceType = opSource.GetType();object opTarget = CreateInstanceOfType(opSourceType);
//grab the propertiesPropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
//iterate over the properties and if it has a 'set' method assign it from the source TO the targetforeach (PropertyInfo item in opPropertyInfo){if (item.CanWrite){//value types can simply be 'set'if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String))){item.SetValue(opTarget, item.GetValue(opSource, null), null);}//object/complex types need to recursively call this method until the end of the tree is reachedelse{object opPropertyValue = item.GetValue(opSource, null);if (opPropertyValue == null){item.SetValue(opTarget, null, null);}else{item.SetValue(opTarget, CloneObject(opPropertyValue), null);}}}}//return the new itemreturn opTarget;}

遵循这些步骤:

  • 定义一个ISelf<T>,它具有只读的Self属性,返回TICloneable<out T>,它派生自ISelf<T>并包含一个方法T Clone()
  • 然后定义一个CloneBase类型,它实现了protected virtual generic VirtualClone到传入类型的转换MemberwiseClone
  • 每个派生类型都应该通过调用基克隆方法来实现VirtualClone,然后执行任何需要执行的操作来正确克隆父VirtualClone方法尚未处理的派生类型的那些方面。

为了获得最大的继承多功能性,公开公共克隆功能的类应该是sealed,但派生自除了缺乏克隆之外其他相同的基类。不要传递显式可克隆类型的变量,而是采用类型ICloneable<theNonCloneableType>的参数。这将允许期望Foo的可克隆派生与DerivedFoo的可克隆派生一起工作的例程,但也允许创建Foo的不可克隆派生。

在大量阅读了这里链接的许多选项以及这个问题的可能解决方案之后,我相信所有选项都在Ian P的链接中很好地总结了(所有其他选项都是这些选项的变体),最好的解决方案是由Pedro77的链接在问题评论中提供的。

所以我将把这两个参考文献的相关部分复制在这里。这样我们就可以有:

在C锐化中克隆对象的最佳方法!

首先,这些都是我们的选择:

通过表达式树进行快速深度复制还通过序列化、反射和表达式树对克隆进行了性能比较。

为什么我选择冰封的(即手动)

Venkat Subramanam先生(这里的冗余链接)详细解释了为什么

他所有的文章都围绕着一个试图适用于大多数情况的例子,使用了3个对象:大脑城市。我们想克隆一个人,它将有自己的大脑,但相同的城市。你可以描绘出上面任何其他方法可以带来的所有问题,也可以阅读这篇文章。

这是我对他结论的略微修改版本:

通过指定New后跟类名来复制对象通常会导致代码不可扩展。使用原型模式的应用clone是实现这一目标的更好方法。然而,使用C#(和Java)中提供的clone也可能有很大问题。最好提供一个受保护的(非公共)复制构造函数并从clone方法调用该构造函数。这使我们能够将创建对象的任务委托给类本身的实例,从而提供可扩展性,并使用受保护的复制构造函数安全地创建对象。

希望这个实现可以让事情变得清晰:

public class Person : ICloneable{private final Brain brain; // brain is final since I do not want// any transplant on it once created!private int age;public Person(Brain aBrain, int theAge){brain = aBrain;age = theAge;}protected Person(Person another){Brain refBrain = null;try{refBrain = (Brain) another.brain.clone();// You can set the brain in the constructor}catch(CloneNotSupportedException e) {}brain = refBrain;age = another.age;}public String toString(){return "This is person with " + brain;// Not meant to sound rude as it reads!}public Object clone(){return new Person(this);}…}

现在考虑从Person派生一个类。

public class SkilledPerson extends Person{private String theSkills;public SkilledPerson(Brain aBrain, int theAge, String skills){super(aBrain, theAge);theSkills = skills;}protected SkilledPerson(SkilledPerson another){super(another);theSkills = another.theSkills;}
public Object clone(){return new SkilledPerson(this);}public String toString(){return "SkilledPerson: " + super.toString();}}

您可以尝试运行以下代码:

public class User{public static void play(Person p){Person another = (Person) p.clone();System.out.println(p);System.out.println(another);}public static void main(String[] args){Person sam = new Person(new Brain(), 1);play(sam);SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");play(bob);}}

产出将是:

This is person with Brain@1fcc69This is person with Brain@253498SkilledPerson: This is person with SmarterBrain@1fef6fSkilledPerson: This is person with SmarterBrain@209f4e

注意,如果我们保持对象数量的计数,这里实现的克隆将保持对象数量的正确计数。

如果您已经在使用像值注入器Automapper这样的第三方应用程序,您可以这样做:

MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();newObj.InjectFrom(oldObj); // Using ValueInjecter syntax

使用此方法,您不必在对象上实现ISerializableICloneable。这在MVC/MVVM模式中很常见,因此创建了这样的简单工具。

GitHub上的ValueInject ter深度克隆示例

我想要一个克隆器,用于非常简单的对象,主要是原语和列表。如果你的对象是开箱即用的JSON序列化,那么这个方法就可以做到。这不需要修改或实现克隆类的接口,只需像JSON.NET.这样的JSON序列化器

public static T Clone<T>(T source){var serialized = JsonConvert.SerializeObject(source);return JsonConvert.DeserializeObject<T>(serialized);}

此外,您可以使用此扩展方法

public static class SystemExtension{public static T Clone<T>(this T source){var serialized = JsonConvert.SerializeObject(source);return JsonConvert.DeserializeObject<T>(serialized);}}

这将把一个对象的所有可读和可写属性复制到另一个对象。

 public class PropertyCopy<TSource, TTarget>where TSource: class, new()where TTarget: class, new(){public static TTarget Copy(TSource src, TTarget trg, params string[] properties){if (src==null) return trg;if (trg == null) trg = new TTarget();var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();if (properties != null && properties.Count() > 0)fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();if (fulllist == null || fulllist.Count() == 0) return trg;
fulllist.ForEach(c =>{c.SetValue(trg, c.GetValue(src));});
return trg;}}

这就是你如何使用它:

 var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,"Creation","Description","IdTicketStatus","IdUserCreated","IdUserInCharge","IdUserRequested","IsUniqueTicketGenerated","LastEdit","Subject","UniqeTicketRequestId","Visibility");

或复制一切:

var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);

我刚刚创建了#0库项目。它使用表达式树运行时代码编译生成的简单赋值操作执行快速、深度克隆。

如何使用它?

而不是用字段和属性之间的赋值基调编写自己的CloneCopy方法,让程序自己做,使用表达式树。标记为扩展方法的GetClone<T>()方法允许您简单地在实例上调用它:

var newInstance = source.GetClone();

您可以使用CloningFlags枚举选择应该从source复制到newInstance的内容:

var newInstance= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);

什么可以克隆?

  • 原语(int、uint、byte、double、char等),已知不可变类型(DateTime、TimeSpan、String)和委托(包括动作,函数等)
  • 不为空
  • T[]数组
  • 自定义类和结构,包括泛型类和结构。

以下类/结构成员在内部克隆:

  • 公共字段的值,而不是只读字段的值
  • 具有get和set访问器的公共属性的值
  • 实现ICollection的类型的集合项

它有多快?

解决方案比反射更快,因为成员信息只需要收集一次,在给定类型T第一次使用GetClone<T>之前。

当您克隆更多相同类型T的实例时,它也比基于序列化的解决方案更快。

还有更多…

阅读有关留档上生成表达式的更多信息。

List<int>的示例表达式调试列表:

.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(System.Collections.Generic.List`1[System.Int32] $source,CloneExtensions.CloningFlags $flags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {.Block(System.Collections.Generic.List`1[System.Int32] $target) {.If ($source == null) {.Return #Label1 { null }} .Else {.Default(System.Void)};.If (.Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))) {$target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]).Invoke((System.Object)$source)} .Else {$target = .New System.Collections.Generic.List`1[System.Int32]()};.If (((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) {.Default(System.Void)} .Else {.Default(System.Void)};.If (((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) {.Block() {$target.Capacity = .Call CloneExtensions.CloneFactory.GetClone($source.Capacity,$flags,$initializers)}} .Else {.Default(System.Void)};.If (((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) {.Block(System.Collections.Generic.IEnumerator`1[System.Int32] $var1,System.Collections.Generic.ICollection`1[System.Int32] $var2) {$var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();$var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;.Loop  {.If (.Call $var1.MoveNext() != False) {.Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone($var1.Current,$flags,

$initializers))} .Else {.Break #Label2 { }}}.LabelTarget #Label2:}} .Else {.Default(System.Void)};.Label$target.LabelTarget #Label1:}

}

与以下c#代码具有相同含义的内容:

(source, flags, initializers) =>{if(source == null)return null;
if(initializers.ContainsKey(typeof(List<int>))target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);elsetarget = new List<int>();
if((flags & CloningFlags.Properties) == CloningFlags.Properties){target.Capacity = target.Capacity.GetClone(flags, initializers);}
if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems){var targetCollection = (ICollection<int>)target;foreach(var item in (ICollection<int>)source){targetCollection.Add(item.Clone(flags, initializers));}}
return target;}

这不是很像你为List<int>编写自己的Clone方法吗?

我已经创建了一个适用于'[Serializable]'和'[Data合同]'的已接受答案的版本。自从我写它以来已经有一段时间了,但是如果我没记错的话[Data合同]需要一个不同的序列化程序。

需要System.IOSystem. Xml

public static class ObjectCopier{
/// <summary>/// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'/// </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 == true){return CloneUsingSerializable<T>(source);}
if (IsDataContract(typeof(T)) == true){return CloneUsingDataContracts<T>(source);}
throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");}

/// <summary>/// Perform a deep Copy of an object that is marked with '[Serializable]'/// </summary>/// <remarks>/// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp/// Uses code found on CodeProject, which allows free use in third party apps/// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx/// </remarks>/// <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 CloneUsingSerializable<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 objectif (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);}}

/// <summary>/// Perform a deep Copy of an object that is marked with '[DataContract]'/// </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 CloneUsingDataContracts<T>(T source){if (IsDataContract(typeof(T)) == false){throw new ArgumentException("The type must be a data contract.", "source");}
// ** Don't serialize a null object, simply return the default for that objectif (Object.ReferenceEquals(source, null)){return default(T);}
DataContractSerializer dcs = new DataContractSerializer(typeof(T));using(Stream stream = new MemoryStream()){using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream)){dcs.WriteObject(writer, source);writer.Flush();stream.Seek(0, SeekOrigin.Begin);using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max)){return (T)dcs.ReadObject(reader);}}}}

/// <summary>/// Helper function to check if a class is a [DataContract]/// </summary>/// <param name="type">The type of the object to check.</param>/// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>public static bool IsDataContract(Type type){object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);return attributes.Length == 1;}
}

在方法内部重铸如何这基本上应该调用一个自动复制构造函数

T t = new T();T t2 = (T)t;  //eh something like that
List<myclass> cloneum;public void SomeFuncB(ref List<myclass> _mylist){cloneum = new List<myclass>();cloneum = (List < myclass >) _mylist;cloneum.Add(new myclass(3));_mylist = new List<myclass>();}

似乎对我有用

要克隆你的类对象,你可以使用Object. MemberwiseClone方法,

只需将此函数添加到您的类中:

public class yourClass{// ...// ...
public yourClass DeepCopy(){yourClass othercopy = (yourClass)this.MemberwiseClone();return othercopy;}}

然后要执行深度独立复制,只需调用DeepCopy方法:

yourClass newLine = oldLine.DeepCopy();

希望这有帮助。

编辑:项目已停止

如果你想真正的克隆到未知的类型,你可以看看快速克隆.

这是基于表达式的克隆,比二进制序列化快10倍,并保持完整的对象图完整性。

这意味着:如果你多次引用层次结构中的同一个对象,克隆也将引用一个实例。

不需要对被克隆的对象进行接口、属性或任何其他修改。

我喜欢这样的复制构造函数:

    public AnyObject(AnyObject anyObject){foreach (var property in typeof(AnyObject).GetProperties()){property.SetValue(this, property.GetValue(anyObject));}foreach (var field in typeof(AnyObject).GetFields()){field.SetValue(this, field.GetValue(anyObject));}}

如果您有更多内容要复制,请添加它们

如果你的对象树是可序列化的,你也可以使用这样的东西

static public MyClass Clone(MyClass myClass){MyClass clone;XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);using (var ms = new MemoryStream()){ser.Serialize(ms, myClass);ms.Position = 0;clone = (MyClass)ser.Deserialize(ms);}return clone;}

请注意,此解决方案非常简单,但性能不如其他解决方案。

并确保如果类增长,仍然只有那些被克隆的字段,它们也会被序列化。

令人难以置信的是,你可以在ICLonable接口上花费多少精力-特别是如果你有很重的类层次结构。此外,MemberwiseClone的工作方式有些奇怪-它甚至不能完全克隆正常的List类型的结构。

当然,序列化最有趣的困境是序列化反向引用-例如,具有子父关系的类层次结构。我怀疑二进制序列化器在这种情况下能否帮助你。(它最终会导致递归循环+堆栈溢出)。

我喜欢这里提出的解决方案:如何在. NET(特别是C#)中对对象进行深度复制?

然而,它不支持列表,补充说,支持,也考虑到重新养育。对于父级规则,我已经将该字段或属性命名为“父级”,那么它将被DeepClone忽略。你可能想决定自己的反向引用规则-对于树层次结构,它可能是“左/右”,等等…

以下是完整的代码片段,包括测试代码:

using System;using System.Collections;using System.Collections.Generic;using System.Diagnostics;using System.Linq;using System.Reflection;using System.Text;
namespace TestDeepClone{class Program{static void Main(string[] args){A a = new A();a.name = "main_A";a.b_list.Add(new B(a) { name = "b1" });a.b_list.Add(new B(a) { name = "b2" });
A a2 = (A)a.DeepClone();a2.name = "second_A";
// Perform re-parenting manually after deep copy.foreach( var b in a2.b_list )b.parent = a2;

Debug.WriteLine("ok");
}}
public class A{public String name = "one";public List<String> list = new List<string>();public List<String> null_list;public List<B> b_list = new List<B>();private int private_pleaseCopyMeAsWell = 5;
public override string ToString(){return "A(" + name + ")";}}
public class B{public B() { }public B(A _parent) { parent = _parent; }public A parent;public String name = "two";}

public static class ReflectionEx{public static Type GetUnderlyingType(this MemberInfo member){Type type;switch (member.MemberType){case MemberTypes.Field:type = ((FieldInfo)member).FieldType;break;case MemberTypes.Property:type = ((PropertyInfo)member).PropertyType;break;case MemberTypes.Event:type = ((EventInfo)member).EventHandlerType;break;default:throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");}return Nullable.GetUnderlyingType(type) ?? type;}
/// <summary>/// Gets fields and properties into one array./// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)/// </summary>/// <param name="type">Type from which to get</param>/// <returns>array of fields and properties</returns>public static MemberInfo[] GetFieldsAndProperties(this Type type){List<MemberInfo> fps = new List<MemberInfo>();fps.AddRange(type.GetFields());fps.AddRange(type.GetProperties());fps = fps.OrderBy(x => x.MetadataToken).ToList();return fps.ToArray();}
public static object GetValue(this MemberInfo member, object target){if (member is PropertyInfo){return (member as PropertyInfo).GetValue(target, null);}else if (member is FieldInfo){return (member as FieldInfo).GetValue(target);}else{throw new Exception("member must be either PropertyInfo or FieldInfo");}}
public static void SetValue(this MemberInfo member, object target, object value){if (member is PropertyInfo){(member as PropertyInfo).SetValue(target, value, null);}else if (member is FieldInfo){(member as FieldInfo).SetValue(target, value);}else{throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");}}
/// <summary>/// Deep clones specific object./// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically/// This is now improved version (list support added)/// </summary>/// <param name="obj">object to be cloned</param>/// <returns>full copy of object.</returns>public static object DeepClone(this object obj){if (obj == null)return null;
Type type = obj.GetType();
if (obj is IList){IList list = ((IList)obj);IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);
foreach (object elem in list)newlist.Add(DeepClone(elem));
return newlist;} //if
if (type.IsValueType || type == typeof(string)){return obj;}else if (type.IsArray){Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));var array = obj as Array;Array copied = Array.CreateInstance(elementType, array.Length);
for (int i = 0; i < array.Length; i++)copied.SetValue(DeepClone(array.GetValue(i)), i);
return Convert.ChangeType(copied, obj.GetType());}else if (type.IsClass){object toret = Activator.CreateInstance(obj.GetType());
MemberInfo[] fields = type.GetFieldsAndProperties();foreach (MemberInfo field in fields){// Don't clone parent back-reference classes. (Using special kind of naming 'parent'// to indicate child's parent class.if (field.Name == "parent"){continue;}
object fieldValue = field.GetValue(obj);
if (fieldValue == null)continue;
field.SetValue(toret, DeepClone(fieldValue));}
return toret;}else{// Don't know that type, don't know how to clone it.if (Debugger.IsAttached)Debugger.Break();
return null;}} //DeepClone}
}

问:我为什么会选择这个答案?

  • 如果您想要. NET能够提供的最快速度,请选择此答案。
  • 如果你想要一个非常非常简单的克隆方法,请忽略这个答案。

换句话说,除非您有需要修复的性能瓶颈,并且您可以使用分析器来证明它,否则请使用其他答案

比其他方法快10倍

执行深度克隆的以下方法是:

  • 比任何涉及序列化/反序列化的东西快10倍;
  • 相当接近理论上的最大速度。NET能够。

而方法…

对于最终的速度,您可以使用嵌套MemberwiseClone进行深度复制。它与复制值结构的速度几乎相同,并且比(a)反射或(b)序列化(如本页上的其他答案所述)快得多。

请注意,如果你使用嵌套MemberwiseClone用于深度复制,你必须为类中的每个嵌套级别手动实现一个浅表复制,以及一个调用所有所述浅表复制方法的DeepCopy来创建一个完整的克隆。这很简单:总共只有几行,请参阅下面的演示代码。

以下是显示100,000个克隆的相对性能差异的代码输出:

  • 嵌套结构上的嵌套MemberwiseClone 1.08秒
  • 嵌套类上的嵌套MemberwiseClone为4.77秒
  • 序列化/反序列化39.93秒

在类上使用嵌套MemberwiseClone几乎与复制结构一样快,并且复制结构非常接近理论上的最大速度。NET能够。

Demo 1 of shallow and deep copy, using classes and MemberwiseClone:Create BobBob.Age=30, Bob.Purchase.Description=LamborghiniClone Bob >> BobsSonAdjust BobsSon detailsBobsSon.Age=2, BobsSon.Purchase.Description=Toy carProof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:Bob.Age=30, Bob.Purchase.Description=LamborghiniElapsed time: 00:00:04.7795670,30000000
Demo 2 of shallow and deep copy, using structs and value copying:Create BobBob.Age=30, Bob.Purchase.Description=LamborghiniClone Bob >> BobsSonAdjust BobsSon details:BobsSon.Age=2, BobsSon.Purchase.Description=Toy carProof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:Bob.Age=30, Bob.Purchase.Description=LamborghiniElapsed time: 00:00:01.0875454,30000000
Demo 3 of deep copy, using class and serialize/deserialize:Elapsed time: 00:00:39.9339425,30000000

要了解如何使用MemberwiseCopy进行深度复制,以下是用于生成上述时间的演示项目:

// Nested MemberwiseClone example.// Added to demo how to deep copy a reference class.[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.public class Person{public Person(int age, string description){this.Age = age;this.Purchase.Description = description;}[Serializable] // Not required if using MemberwiseClonepublic class PurchaseType{public string Description;public PurchaseType ShallowCopy(){return (PurchaseType)this.MemberwiseClone();}}public PurchaseType Purchase = new PurchaseType();public int Age;// Add this if using nested MemberwiseClone.// This is a class, which is a reference type, so cloning is more difficult.public Person ShallowCopy(){return (Person)this.MemberwiseClone();}// Add this if using nested MemberwiseClone.// This is a class, which is a reference type, so cloning is more difficult.public Person DeepCopy(){// Clone the root ...Person other = (Person) this.MemberwiseClone();// ... then clone the nested class.other.Purchase = this.Purchase.ShallowCopy();return other;}}// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)public struct PersonStruct{public PersonStruct(int age, string description){this.Age = age;this.Purchase.Description = description;}public struct PurchaseType{public string Description;}public PurchaseType Purchase;public int Age;// This is a struct, which is a value type, so everything is a clone by default.public PersonStruct ShallowCopy(){return (PersonStruct)this;}// This is a struct, which is a value type, so everything is a clone by default.public PersonStruct DeepCopy(){return (PersonStruct)this;}}// Added only for a speed comparison.public class MyDeepCopy{public static T DeepCopy<T>(T obj){object result = null;using (var ms = new MemoryStream()){var formatter = new BinaryFormatter();formatter.Serialize(ms, obj);ms.Position = 0;result = (T)formatter.Deserialize(ms);ms.Close();}return (T)result;}}

然后,从main调用demo:

void MyMain(string[] args)\{\{Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");var Bob = new Person(30, "Lamborghini");Console.Write("  Create Bob\n");Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);Console.Write("  Clone Bob >> BobsSon\n");var BobsSon = Bob.DeepCopy();Console.Write("  Adjust BobsSon details\n");BobsSon.Age = 2;BobsSon.Purchase.Description = "Toy car";Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);Debug.Assert(Bob.Age == 30);Debug.Assert(Bob.Purchase.Description == "Lamborghini");var sw = new Stopwatch();sw.Start();int total = 0;for (int i = 0; i < 100000; i++){var n = Bob.DeepCopy();total += n.Age;}Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);}{Console.Write("Demo 2 of shallow and deep copy, using structs:\n");var Bob = new PersonStruct(30, "Lamborghini");Console.Write("  Create Bob\n");Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);Console.Write("  Clone Bob >> BobsSon\n");var BobsSon = Bob.DeepCopy();Console.Write("  Adjust BobsSon details:\n");BobsSon.Age = 2;BobsSon.Purchase.Description = "Toy car";Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);Debug.Assert(Bob.Age == 30);Debug.Assert(Bob.Purchase.Description == "Lamborghini");var sw = new Stopwatch();sw.Start();int total = 0;for (int i = 0; i < 100000; i++){var n = Bob.DeepCopy();total += n.Age;}Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);}{Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");int total = 0;var sw = new Stopwatch();sw.Start();var Bob = new Person(30, "Lamborghini");for (int i = 0; i < 100000; i++){var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);total += BobsSon.Age;}Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);}Console.ReadKey();}

同样,请注意,如果你使用嵌套MemberwiseClone用于深度复制,你必须为类中的每个嵌套级别手动实现一个浅表复制,以及一个调用所有上述浅表复制方法的DeepCopy来创建一个完整的克隆。这很简单:总共只有几行,参见上面的演示代码。

值类型与引用类型

请注意,在克隆对象时,“struct”和“”之间有很大的区别:

  • 如果你有一个“struct”,它是一个值类型,所以你可以复制它,内容将被克隆(但它只会做一个浅克隆,除非你使用这篇文章中的技术)。
  • 如果你有一个“”,它是一个引用类型,所以如果你复制它,你所做的就是复制指向它的指针。要创建一个真正的克隆,你必须更有创造力,并使用值类型和引用类型之间的区别在内存中创建原始对象的另一个副本。

值类型和引用类型之间的区别

帮助调试的校验和

  • 错误地克隆对象会导致非常难以确定的错误。在生产代码中,我倾向于实现校验和来双重检查对象是否已正确克隆,并且没有被对它的另一个引用损坏。可以在发布模式下关闭此校验和。
  • 我发现这种方法非常有用:通常,您只想克隆对象的一部分,而不是整个对象。

对于将许多线程与许多其他线程解耦非常有用

此代码的一个很好的用例是将嵌套类或结构的克隆提供给队列,以实现生产者/消费者模式。

  • 我们可以让一个(或多个)线程修改它们拥有的类,然后将该类的完整副本推送到ConcurrentQueue中。
  • 然后我们有一个(或多个)线程提取这些类的副本并处理它们。

这在实践中非常有效,并允许我们将许多线程(生产者)与一个或多个线程(消费者)解耦。

这种方法也非常快:如果我们使用嵌套结构,它比序列化/反序列化嵌套类快35倍,并允许我们利用机器上所有可用的线程。

更新

显然,ExpressMapper与上述手动编码一样快,如果不是更快的话。我可能得看看它们与分析器的比较。

当使用Marc Gravell作为序列化器时,接受的答案需要一些轻微的修改,因为要复制的对象不会被归因于[Serializable],因此不可序列化,Clone方法会抛出异常。
我修改了它以使用原型网络:

public static T Clone<T>(this T source){if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))== null){throw new ArgumentException("Type has no ProtoContract!", "source");}
if(Object.ReferenceEquals(source, null)){return default(T);}
IFormatter formatter = ProtoBuf.Serializer.CreateFormatter<T>();using (Stream stream = new MemoryStream()){formatter.Serialize(stream, source);stream.Seek(0, SeekOrigin.Begin);return (T)formatter.Deserialize(stream);}}

这将检查是否存在[ProtoContract]属性,并使用原型自己的格式化程序来序列化对象。

好吧,在这篇文章中有一些明显的反射例子,但是反射通常很慢,直到你开始正确地缓存它。

如果您正确缓存它,那么它将通过4,6s(由Watcher测量)深度克隆1000000对象。

static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

而不是将缓存属性或新属性添加到字典中并简单地使用它们

foreach (var prop in propList){var value = prop.GetValue(source, null);prop.SetValue(copyInstance, value, null);}

在另一个答案中检查我的帖子中的完整代码

https://stackoverflow.com/a/34365709/4711853

由于我在不同的项目中找不到满足我所有要求的克隆器,我创建了一个深度克隆器,它可以配置和适应不同的代码结构,而不是调整我的代码来满足克隆器的要求。它通过向应被克隆的代码添加注释来实现,或者你只需保持代码具有默认行为。它使用反射、类型缓存并基于快速反射。对于大量数据和高对象层次结构(与其他基于反射/序列化的算法相比),克隆过程非常快。

https://github.com/kalisohn/CloneBehave

也可以作为nuget包使用:https://www.nuget.org/packages/Clone.Behave/1.0.0

例如:下面的代码将深度克隆地址,但只执行_currentJob字段的浅拷贝。

public class Person{[DeepClone(DeepCloneBehavior.Shallow)]private Job _currentJob;
public string Name { get; set; }
public Job CurrentJob{get{ return _currentJob; }set{ _currentJob = value; }}
public Person Manager { get; set; }}
public class Address{public Person PersonLivingHere { get; set; }}
Address adr = new Address();adr.PersonLivingHere = new Person("John");adr.PersonLivingHere.BestFriend = new Person("James");adr.PersonLivingHere.CurrentJob = new Job("Programmer");
Address adrClone = adr.Clone();
//RESULTadr.PersonLivingHere == adrClone.PersonLivingHere //falseadr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //falseadr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //trueadr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true

这个方法为我解决了这个问题:

private static MyObj DeepCopy(MyObj source){
var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);
}

像这样使用它:MyObj a = DeepCopy(b);

保持简单并使用AutoMapper,就像其他人提到的那样,它是一个简单的小库,可以将一个对象映射到另一个对象……要将一个对象复制到另一个具有相同类型的对象,您只需要三行代码:

MyType source = new MyType();Mapper.CreateMap<MyType, MyType>();MyType target = Mapper.Map<MyType, MyType>(source);

目标对象现在是源对象的副本。还不够简单?创建一个扩展方法以在解决方案中的任何地方使用:

public static T Copy<T>(this T source){T copy = default(T);Mapper.CreateMap<T, T>();copy = Mapper.Map<T, T>(source);return copy;}

扩展方法可以使用如下:

MyType copy = source.Copy();

代码生成器

我们已经看到了从串行化到手动实现再到反射的很多想法,我想提出一种使用CGbR代码生成器的完全不同的方法。生成克隆方法具有内存和CPU效率,因此比标准DataCompltSerializer快300倍。

你所需要的只是一个带有ICloneable的部分类定义,其余的由生成器完成:

public partial class Root : ICloneable{public Root(int number){_number = number;}private int _number;
public Partial[] Partials { get; set; }
public IList<ulong> Numbers { get; set; }
public object Clone(){return Clone(true);}
private Root(){}}
public partial class Root{public Root Clone(bool deep){var copy = new Root();// All value types can be simply copiedcopy._number = _number;if (deep){// In a deep clone the references are clonedvar tempPartials = new Partial[Partials.Length];for (var i = 0; i < Partials.Length; i++){var value = Partials[i];value = value.Clone(true);tempPartials[i] = value;}copy.Partials = tempPartials;var tempNumbers = new List<ulong>(Numbers.Count);for (var i = 0; i < Numbers.Count; i++){var value = Numbers[i];tempNumbers.Add(value);}copy.Numbers = tempNumbers;}else{// In a shallow clone only references are copiedcopy.Partials = Partials;copy.Numbers = Numbers;}return copy;}}

备注:最新版本有更多的空检查,但为了更好地理解,我把它们省略了。

这里有一个快速简单的解决方案,无需中继序列化/反序列化即可为我工作。

public class MyClass{public virtual MyClass DeepClone(){var returnObj = (MyClass)MemberwiseClone();var type = returnObj.GetType();var fieldInfoArray = type.GetRuntimeFields().ToArray();
foreach (var fieldInfo in fieldInfoArray){object sourceFieldValue = fieldInfo.GetValue(this);if (!(sourceFieldValue is MyClass)){continue;}
var sourceObj = (MyClass)sourceFieldValue;var clonedObj = sourceObj.DeepClone();fieldInfo.SetValue(returnObj, clonedObj);}return returnObj;}}

编辑:需要

    using System.Linq;using System.Reflection;

我就是这么用的

public MyClass Clone(MyClass theObjectIneededToClone){MyClass clonedObj = theObjectIneededToClone.DeepClone();}

最好的是实现一个延拓法,比如

public static T DeepClone<T>(this T originalObject){ /* the cloning code */ }

然后在溶液中的任何地方使用它

var copy = anyObject.DeepClone();

我们可以有以下三种实现:

  1. 按序列化(最短的代码)
  2. 通过反思-快5倍
  3. 按表达式树-快20倍

所有相关的方法都运行良好,并经过了深入的测试。

我想你可以试试这个。

MyObject myObj = GetMyObj(); // Create and fill a new objectMyObject newObj = new MyObject(myObj); //DeepClone it

我知道这个问题和回答在这里坐了一会儿,下面不是答案,而是观察,我最近在检查私人是否确实没有被克隆时遇到了这个问题(如果我没有,我就不会是我自己;)当我高兴地复制粘贴@johnc更新的答案时。

我简单地做了自己的扩展方法(这几乎是上述答案的复制粘贴形式):

public static class CloneThroughJsonExtension{private static readonly JsonSerializerSettings DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
public static T CloneThroughJson<T>(this T source){return ReferenceEquals(source, null) ? default(T) : JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), DeserializeSettings);}}

并像这样天真地放弃了课程(事实上还有更多,但它们是不相关的):

public class WhatTheHeck{public string PrivateSet { get; private set; } // matches ctor param name
public string GetOnly { get; } // matches ctor param name
private readonly string _indirectField;public string Indirect => $"Inception of: {_indirectField} "; // matches ctor param namepublic string RealIndirectFieldVaule => _indirectField;
public WhatTheHeck(string privateSet, string getOnly, string indirect){PrivateSet = privateSet;GetOnly = getOnly;_indirectField = indirect;}}

和这样的代码:

var clone = new WhatTheHeck("Private-Set-Prop cloned!", "Get-Only-Prop cloned!", "Indirect-Field clonned!").CloneThroughJson();Console.WriteLine($"1. {clone.PrivateSet}");Console.WriteLine($"2. {clone.GetOnly}");Console.WriteLine($"3.1. {clone.Indirect}");Console.WriteLine($"3.2. {clone.RealIndirectFieldVaule}");

结果是:

1. Private-Set-Prop cloned!2. Get-Only-Prop cloned!3.1. Inception of: Inception of: Indirect-Field cloned!3.2. Inception of: Indirect-Field cloned!

我整个就像:什么F…所以我抓起Newtonsoft. Json Github存储库并开始挖掘。结果是:当反序列化一个恰好只有一个ctor并且其参数名称匹配(不区分大小写)公共属性名称的类型时,它们将作为这些参数传递给ctor。一些线索可以在代码这里这里中找到。

底线是

我知道这不是常见的情况,示例代码有点滥用,但是嘿!当我检查是否有龙在灌木丛中等待跳出来咬我的屁股时,它让我大吃一惊。;)

映射器执行深度复制。Foreach对象的成员它创建一个新对象并分配其所有值。它在每个非原始内部成员上递归工作。

我推荐你一个最快的,目前正在积极开发的。我建议UltraMapperhttps://github.com/maurosampietro/UltraMapper

Nuget包:https://www.nuget.org/packages/UltraMapper/

我找到了一种新的方法来做到这一点,那就是发射。

我们可以使用Emit将IL添加到应用程序并运行它。但我认为这不是一个好方法,因为我想完善这个我写我的答案。

Emit可以看到正式文件指南

你应该学习一些IL来阅读代码。我将在课堂上编写可以复制属性的代码。

public static class Clone{// ReSharper disable once InconsistentNamingpublic static void CloneObjectWithIL<T>(T source, T los){//see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/if (CachedIl.ContainsKey(typeof(T))){((Action<T, T>) CachedIl[typeof(T)])(source, los);return;}var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });ILGenerator generator = dynamicMethod.GetILGenerator();
foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite)){//do not copy static that will exceptif (temp.GetAccessors(true)[0].IsStatic){continue;}
generator.Emit(OpCodes.Ldarg_1);// losgenerator.Emit(OpCodes.Ldarg_0);// sgenerator.Emit(OpCodes.Callvirt, temp.GetMethod);generator.Emit(OpCodes.Callvirt, temp.SetMethod);}generator.Emit(OpCodes.Ret);var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));CachedIl[typeof(T)] = clone;clone(source, los);}
private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();}

代码可以是深度复制,但它可以复制属性。如果你想让它成为深度复制,你可以改变它,因为IL太难了,我做不到。

还有一个JSON.NET的答案。这个版本适用于不实现ISerializable的类。

public static class Cloner{public static T Clone<T>(T source){if (ReferenceEquals(source, null))return default(T);
var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);}
class ContractResolver : DefaultContractResolver{protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization){var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Select(p => base.CreateProperty(p, memberSerialization)).Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Select(f => base.CreateProperty(f, memberSerialization))).ToList();props.ForEach(p => { p.Writable = true; p.Readable = true; });return props;}}}

C#扩展也将支持“非ISerializable”类型。

 public static class AppExtensions{public static T DeepClone<T>(this T a){using (var stream = new MemoryStream()){var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
serializer.Serialize(stream, a);stream.Position = 0;return (T)serializer.Deserialize(stream);}}}

用法

       var obj2 = obj1.DeepClone()

泛型方法在技术上都是有效的,但我只是想添加一个自己的注意事项,因为我们很少真正需要一个真正的深度复制,我强烈反对在实际的业务应用程序中使用泛型深度复制,因为这使得你可能有很多地方复制对象,然后显式修改,很容易丢失。

在大多数现实生活中的情况下,你也希望对复制过程有尽可能多的细粒度控制,因为你不仅耦合到数据访问框架,而且在实践中复制的业务对象很少应该是100%相同的。想想ORM用来识别对象引用的一个例子,一个完整的深度复制也会复制这个id,所以在内存中的对象会不同,一旦你提交到数据存储,它就会抱怨,所以无论如何你都必须在复制后手动修改这个属性,如果对象发生变化,你需要在所有使用通用深度复制的地方调整它。

扩展@cregox的答案与IClONABLE,什么是深度副本?它只是堆上一个新分配的对象,与原始对象相同,但占用不同的内存空间,因此,而不是使用通用的克隆器功能,为什么不创建一个新对象呢?

我个人在我的域对象上使用静态工厂方法的想法。

示例:

    public class Client{public string Name { get; set; }
protected Client(){}
public static Client Clone(Client copiedClient){return new Client{Name = copiedClient.Name};}}
public class Shop{public string Name { get; set; }
public string Address { get; set; }
public ICollection<Client> Clients { get; set; }
public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients){var copiedClients = new List<Client>();foreach (var client in copiedShop.Clients){copiedClients.Add(Client.Clone(client));}
return new Shop{Name = copiedShop.Name,Address = newAddress,Clients = copiedClients};}}

如果有人想知道如何在结构化对象实例化的同时保持对复制过程的完全控制,这是我个人非常成功的解决方案。受保护的构造函数也做到了这一点,其他开发人员被迫使用工厂方法,该方法提供了一个整洁的单点对象实例化,将构造逻辑封装在对象内部。你也可以重载该方法,并在必要时为不同的地方有几个克隆逻辑。

由于这个问题的几乎所有答案都不令人满意,或者在我的情况下显然不起作用,我编写了AnyClone,它完全通过反射实现,并解决了这里的所有需求。我无法在结构复杂的复杂场景中进行序列化,IClonable并不理想——事实上它甚至不应该是必要的。

使用[IgnoreDataMember][NonSerialized]支持标准忽略属性。支持复杂的集合、没有设置器的属性、只读字段等。

我希望它能帮助那些遇到和我一样问题的人。

深度克隆是复制。对于.net意味着字段

假设一个人有一个层次结构:

static class RandomHelper{private static readonly Random random = new Random();
public static int Next(int maxValue) => random.Next(maxValue);}
class A{private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";}
class B : A{private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";}
class C : B{private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";}

克隆可以做到:

static class DeepCloneExtension{// consider instance fields, both public and non-publicprivate static readonly BindingFlags bindingFlags =BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
public static T DeepClone<T>(this T obj) where T : new(){var type = obj.GetType();var result = (T)Activator.CreateInstance(type);
do// copy all fieldsforeach (var field in type.GetFields(bindingFlags))field.SetValue(result, field.GetValue(obj));// for every level of hierarchywhile ((type = type.BaseType) != typeof(object));
return result;}}

Demo1

Console.WriteLine(new C());Console.WriteLine(new C());
var c = new C();Console.WriteLine($"{Environment.NewLine}Image: {c}{Environment.NewLine}");
Console.WriteLine(new C());Console.WriteLine(new C());
Console.WriteLine($"{Environment.NewLine}Clone: {c.DeepClone()}{Environment.NewLine}");
Console.WriteLine(new C());Console.WriteLine(new C());

结果:

C.random = 92 B.random = 66 A.random = 71C.random = 36 B.random = 64 A.random = 17
Image: C.random = 96 B.random = 18 A.random = 46
C.random = 60 B.random = 7 A.random = 37C.random = 78 B.random = 11 A.random = 18
Clone: C.random = 96 B.random = 18 A.random = 46
C.random = 33 B.random = 63 A.random = 38C.random = 4 B.random = 5 A.random = 79

请注意,所有新对象对random字段都有随机值,但clone完全匹配image

Demo2

class D{public event EventHandler Event;public void RaiseEvent() => Event?.Invoke(this, EventArgs.Empty);}
// ...
var image = new D();Console.WriteLine($"Created obj #{image.GetHashCode()}");
image.Event += (sender, e) => Console.WriteLine($"Event from obj #{sender.GetHashCode()}");Console.WriteLine($"Subscribed to event of obj #{image.GetHashCode()}");
image.RaiseEvent();image.RaiseEvent();
var clone = image.DeepClone();Console.WriteLine($"obj #{image.GetHashCode()} cloned to obj #{clone.GetHashCode()}");
clone.RaiseEvent();image.RaiseEvent();

结果:

Created obj #46104728Subscribed to event of obj #46104728Event from obj #46104728Event from obj #46104728obj #46104728 cloned to obj #12289376Event from obj #12289376Event from obj #46104728

注意,事件支持字段也被复制,客户端也订阅了克隆的事件。

使用System.Text.Json

https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/

public static T DeepCopy<T>(this T source){return source == null ? default : JsonSerializer.Parse<T>(JsonSerializer.ToString(source));}

新的API使用Span<T>。这应该很快,做一些基准测试会很好。

备注:不需要像Json.NET那样的ObjectCreationHandling.Replace,因为它将默认替换集合值。你现在应该忘记Json.NET,因为所有内容都将被新的官方API替换。

我不确定这是否适用于私人领域。

深度克隆:快速,简单,有效的NuGet包来解决克隆

读完所有的答案后,我很惊讶没有人提到这个优秀的包:

深度克隆GitHub项目

深度克隆的NuGet包

详细介绍一下它的README,以下是我们在工作中选择它的原因:

  • 它可以深或浅复制
  • 在深度克隆中,保持所有对象图。
  • 在运行时使用代码生成,因此克隆非常快
  • 通过内部结构复制的对象,不调用方法或ctor
  • 您不需要以某种方式标记类(如Serializable-属性或实现接口)
  • 不需要指定克隆的对象类型。对象可以转换为接口或抽象对象(例如,您可以将int数组克隆为抽象数组或IENumable;甚至null也可以在没有任何错误的情况下克隆)
  • 克隆对象没有任何能力确定他是克隆的(除非使用非常具体的方法)

用法:

var deepClone = new { Id = 1, Name = "222" }.DeepClone();var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();

性能:

README包含各种克隆库和方法的性能比较:深度克隆性能

职位要求:

  • . NET 4.0或更高版本或. NET Standard 1.3(. NET Core)
  • 需要完全信任权限集或反射权限(MemberAccess)

免责声明:我是上述软件包的作者。

我很惊讶2019年这个问题的顶级答案仍然使用序列化或反射。

序列化具有局限性(需要属性、特定的构造函数等)并且非常慢

BinaryFormatter需要Serializable属性,JsonConverter需要无参数构造函数或属性,两者都不能很好地处理只读字段或接口,并且都比必要的慢10-30倍。

表达式树

您可以改为使用表达式树反射发射仅生成一次克隆代码,然后使用该编译代码而不是慢反射或序列化。

在自己遇到这个问题并且没有看到令人满意的解决方案之后,我决定创建一个这样做的包适用于每种类型,几乎与自定义代码一样快

您可以在GitHub上找到该项目:https://github.com/marcelltoth/ObjectCloner

用法

您可以从NuGet安装它。获取ObjectCloner包并将其用作:

var clone = ObjectCloner.DeepClone(original);

或者,如果您不介意使用扩展污染您的对象类型,也可以获取ObjectCloner.Extensions并编写:

var clone = original.DeepClone();

性能

克隆类层次结构的简单基准测试显示,性能比使用Reflection快约3倍,比Newtonsoft. Json序列化快约12倍,比强烈建议的BinaryFormatter快约36倍。

最短但需要依赖的方式:

using Newtonsoft.Json;public static T Clone<T>(T source) =>JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));

对于克隆过程,可以先将对象转换为字节数组,然后再转换回对象。

public static class Extentions{public static T Clone<T>(this T obj){byte[] buffer = BinarySerialize(obj);return (T)BinaryDeserialize(buffer);}
public static byte[] BinarySerialize(object obj){using (var stream = new MemoryStream()){var formatter = new BinaryFormatter();formatter.Serialize(stream, obj);return stream.ToArray();}}
public static object BinaryDeserialize(byte[] buffer){using (var stream = new MemoryStream(buffer)){var formatter = new BinaryFormatter();return formatter.Deserialize(stream);}}}

该对象必须为序列化过程进行序列化。

[Serializable]public class MyObject{public string Name  { get; set; }}

用法:

MyObject myObj  = GetMyObj();MyObject newObj = myObj.Clone();

创建一个扩展:

public static T Clone<T>(this T theObject){string jsonData = JsonConvert.SerializeObject(theObject);return JsonConvert.DeserializeObject<T>(jsonData);}

并这样称呼它:

NewObject = OldObject.Clone();

@Konrad和@Craastad的补充,使用内置的System.Text.Json用于.NET >5

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?pivots=dotnet-5-0

方法:

public static T Clone<T>(T source){var serialized = JsonSerializer.Serialize(source);return JsonSerializer.Deserialize<T>(serialized);}

扩展方法:

public static class SystemExtension{public static T Clone<T>(this T source){var serialized = JsonSerializer.Serialize(source);return JsonSerializer.Deserialize<T>(serialized);}}

使用System. Text. Json;

public static class CloneExtensions{public static T Clone<T>(this T cloneable) where T : new(){var toJson = JsonSerializer.Serialize(cloneable);return JsonSerializer.Deserialize<T>(toJson);}}

C#9.0引入了with关键字,该关键字需要record(感谢马克纳丁)。这应该允许非常简单的对象克隆(如果需要,还可以进行突变),只需很少的样板,但仅限于record

您似乎无法通过将类放入泛型record来克隆(按值)类;

using System;                
public class Program{public class Example{public string A { get; set; }}    
public record ClonerRecord<T>(T a){}
public static void Main(){var foo = new Example {A = "Hello World"};var bar = (new ClonerRecord<Example>(foo) with {}).a;foo.A = "Goodbye World :(";Console.WriteLine(bar.A);}}

这写着“GoodbyWorld:(”-字符串被引用复制(不需要)。https://dotnetfiddle.net/w3IJgG

(令人难以置信的是,上面的方法在struct

但是克隆record似乎确实可以缩进,按值克隆。

using System;
public class Program{public record Example{public string A { get; set; }}    
public static void Main(){var foo = new Example {A = "Hello World"};var bar = foo with {};foo.A = "Goodbye World :(";Console.WriteLine(bar.A);}}

这返回“Hello World”,字符串是按值复制的!https://dotnetfiddle.net/MCHGEL

更多信息可以在博客文章中找到:

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/with-expression

我对当前的答案做了一些基准测试,发现了一些有趣的事实。

使用BinarySerializer=>https://stackoverflow.com/a/78612/6338072

使用XmlSerializer=>https://stackoverflow.com/a/50150204/6338072

使用Activator. CreateInstance=>https://stackoverflow.com/a/56691124/6338072创建实例

这些是结果

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.18363.1734 (1909/November2019Update/19H2)
英特尔酷睿i5-6200U CPU 2.30GHz(Skylake),1个CPU,4个逻辑内核和2个物理内核[主机]:. NET Framework 4.8(4.8.4400.0),X86 LegacyJIT默认作业:. NET Framework 4.8(4.8.4400.0),X86 LegacyJIT

方法均值错误StdDevGen 0分配
二进制序列化器220.69 us4.374 us9.963 us49.804777 KB
xmlserializer182.72 us3.619 us9.405 us21.972734 KB
激活器。创建实例49.99 us0.992 us2.861 us1.95313 KB

找到这个包,与DeepCloner相比,它似乎更快,并且没有依赖项。

https://github.com/AlenToma/FastDeepCloner

我将使用下面的简单方法来实现它。只需创建一个抽象类并实现方法来再次序列化和反序列化并返回。

public abstract class CloneablePrototype<T>{public T DeepCopy(){string result = JsonConvert.SerializeObject(this);return JsonConvert.DeserializeObject<T>(result);}}public class YourClass : CloneablePrototype< YourClass>………

像这样使用它来创建深度副本。

YourClass newObj = (YourClass)oldObj.DeepCopy();

如果您还需要实现浅拷贝方法,这个解决方案也很容易扩展。

只需在抽象类中实现一个新方法。

public T ShallowCopy(){return (T)this.MemberwiseClone();}

除了这里的一些精彩答案之外,您可以在C#9.0及更高版本中执行以下操作(假设您可以将类转换为记录):

record Record{public int Property1 { get; set; }
public string Property2 { get; set; }}

然后,只需使用与运营商将一个对象的值复制到新对象。

var object1 = new Record(){Property1 = 1,Property2 = "2"};
var object2 = object1 with { };// object2 now has Property1 = 1 & Property2 = "2"

希望对你有帮助:)

基于@crastad的答案,对于派生类。

在最初的答案中,如果调用者对基类对象调用DeepCopy,则克隆的对象是基类。但以下代码将返回派生类。

using Newtonsoft.Json;
public static T DeepCopy<T>(this T source){return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source), source.GetType());}

如果使用net.core并且对象是可序列化的,则可以使用

var jsonBin = BinaryData.FromObjectAsJson(yourObject);

然后

var yourObjectCloned = jsonBin.ToObjectFromJson<YourType>();

BinaryData在dotnet中,因此你不需要第三方库。它还可以处理你的类上的属性是Object类型的情况(你的属性中的实际数据仍然需要是可序列化的)

在我正在使用的代码库中,我们有一个来自GitHub项目Burt的ev-Alexey/net-对象-深度-复制的文件ObjectExtensions.cs的副本。它已经有9年的历史了。它起作用了,尽管我们后来意识到它对于更大的对象结构来说非常慢。

相反,我们在GitHub项目jp mik kers/Bak steen。扩展。深度复制中发现了文件ObjectExtensions.cs的一个分支。以前需要我们大约30分钟的大型数据结构的深度复制操作,现在感觉几乎是瞬时的。

此改进版本具有以下留档:

用于快速对象克隆的C#扩展方法。

这是Alexey Burtsev深度复印机的速度优化分支。根据您的使用场景,这将比原始版本快2-3倍。它还修复了原始代码中存在的一些错误。与经典的二进制序列化/反序列化深度克隆技术相比,这个版本大约快7倍(对象包含的数组越多,加速因子越大)。

加速是通过以下技术实现的:

  • 缓存对象反射结果
  • 不要深度复制原语或不可变的结构和类(例如枚举和字符串)
  • 为了提高引用的局部性,处理内部循环中的“快速”维度或多维数组
  • 使用编译的lamba表达式调用MemberwiseClone

如何使用:

using Baksteen.Extensions.DeepCopy;...var myobject = new SomeClass();...var myclone = myobject.DeepCopy()!;    // creates a new deep copy of the original object

注意:仅当您在项目中启用可空引用类型时,才需要感叹号(空原谅运算符)