我想做类似的事情:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
然后对未反映在原始对象中的新对象进行更改。
我并不经常需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的处理方式情况。
如何克隆或深度复制对象,以便可以修改克隆的对象而不会在原始对象中反映任何更改?
clone
方法,然后让它调用一个内部的私有构造函数,然后传递 this
。所以复制是可怕的[原文如此],但仔细复制(这篇文章绝对值得一读)不是。 ;^)
一种方法是实现 ICloneable
接口(描述为 here,因此我不会重复),这是我不久前在 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 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 object
if (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 的新 extension methods,请将方法更改为具有以下签名:
public static T Clone<T>(this T source)
{
// ...
}
现在方法调用简单地变为 objectBeingCloned.Clone();
。
编辑(2015 年 1 月 10 日)我想我会重新审视这一点,提到我最近开始使用 (Newtonsoft) Json 来执行此操作,它should be更轻,并且避免了 [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 object
if (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 result
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
我想要一个克隆器,用于非常简单的对象,主要是基元和列表。如果您的对象是开箱即用的 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);
}
}
Newtonsoft.Json.JsonConvert
但它是一样的
不使用 ICloneable 的原因是 not,因为它没有通用接口。 The reason not to use it is because it's vague。不清楚你得到的是浅拷贝还是深拷贝。这取决于实施者。
是的,MemberwiseClone
进行浅拷贝,但 MemberwiseClone
的反面不是 Clone
;它可能是 DeepClone
,它不存在。当您通过其 ICloneable 接口使用对象时,您无法知道底层对象执行哪种克隆。 (并且 XML 注释不会说清楚,因为您将获得接口注释,而不是对象的 Clone 方法上的注释。)
我通常做的只是创建一个完全符合我要求的 Copy
方法。
在大量阅读了此处链接的许多选项以及此问题的可能解决方案之后,我相信 all the options are summarized pretty well at Ian P's link(所有其他选项都是这些选项的变体),并且Pedro77's link 在问题评论中提供了最佳解决方案。
所以我将在这里复制这两个参考的相关部分。这样我们就可以拥有:
用 C 语言克隆对象的最佳方法!
首先,这些都是我们的选择:
手动使用 ICloneable,它是浅层的,不是类型安全的
MemberwiseClone,它使用 ICloneable
使用 Activator.CreateInstance 和递归 MemberwiseClone 进行反射
序列化,正如 johnc 的首选答案所指出的那样
中级语言,我不知道它是如何工作的
扩展方法,例如 Havard Straden 的这个自定义克隆框架
表达式树
article Fast Deep Copy by Expression Trees 还具有通过序列化、反射和表达式树进行克隆的性能比较。
为什么我选择 ICloneable(即手动)
Mr Venkat Subramaniam (redundant link here) explains in much detail why。
他的所有文章都围绕着一个试图适用于大多数情况的示例,使用 3 个对象:Person、Brain 和 City。我们想克隆一个人,它有自己的大脑,但在同一个城市。您可以描绘上述任何其他方法可能带来的所有问题,也可以阅读本文。
这是我对他的结论稍作修改的版本:
通过指定 New 后跟类名来复制对象通常会导致代码不可扩展。使用克隆,原型模式的应用,是实现这一点的更好方法。但是,使用 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@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e
请注意,如果我们保持对象数量的计数,则此处实现的克隆将保持对象数量的正确计数。
ICloneable
用于公共成员。 “由于 Clone 的调用者不能依赖于执行可预测克隆操作的方法,我们建议不要在公共 API 中实现 ICloneable。” msdn.microsoft.com/en-us/library/… 但是,根据 Venkat Subramaniam 在您的链接文章中给出的解释,我认为在这种情况下使用是有意义的只要 ICloneable 对象的创建者对哪些属性应该深入了解vs. 浅拷贝(即深拷贝Brain,浅拷贝City)
我更喜欢复制构造函数而不是克隆。意图更明确。
复制所有公共属性的简单扩展方法。适用于任何对象并且不要求类为 [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 ) } );
}
};
}
我刚刚创建了 CloneExtensions
library 项目。它使用表达式树运行时代码编译生成的简单赋值操作执行快速、深度克隆。
如何使用它?
无需编写自己的 Clone
或 Copy
方法,在字段和属性之间进行分配,而是使用表达式树让程序自己完成。 GetClone<T>()
标记为扩展方法的方法允许您在实例上简单地调用它:
var newInstance = source.GetClone();
您可以使用 CloningFlags
枚举选择应从 source
复制到 newInstance
的内容:
var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
什么可以克隆?
Primitive(int、uint、byte、double、char 等)、已知的不可变类型(DateTime、TimeSpan、String)和委托(包括 Action、Func 等)
可空的
T[] 数组
自定义类和结构,包括泛型类和结构。
以下类/结构成员在内部克隆:
公共而非只读字段的值
具有 get 和 set 访问器的公共属性的值
实现 ICollection 的类型的集合项
它有多快?
该解决方案比反射更快,因为成员信息只需要收集一次,在 GetClone<T>
首次用于给定类型 T
之前。
当您克隆多个相同类型的实例 T
时,它也比基于序列化的解决方案更快。
和更多...
在 documentation 上阅读有关生成表达式的更多信息。
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);
else
target = 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
方法吗?
如果您已经在使用像 ValueInjecter 或 Automapper 这样的第三方应用程序,您可以执行以下操作:
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
使用此方法,您不必在对象上实现 ISerializable
或 ICloneable
。这在 MVC/MVVM 模式中很常见,因此创建了像这样的简单工具。
见the ValueInjecter deep cloning sample on GitHub。
好吧,我在 Silverlight 中使用 ICloneable 时遇到了问题,但我喜欢序列化的想法,我可以序列化 XML,所以我这样做了:
static public class SerializeHelper
{
//Michael White, Holly Springs Consulting, 2009
//michael@hollyspringsconsulting.com
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);
}
}
最好的方法是实现一个扩展方法,比如
public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }
然后在解决方案中的任何地方使用它
var copy = anyObject.DeepClone();
我们可以有以下三种实现:
通过序列化(最短的代码) 通过反射 - 快 5 倍 通过表达式树 - 快 20 倍
所有链接的方法都运行良好,并经过深入测试。
简短的回答是您从 ICloneable 接口继承,然后实现 .clone 函数。克隆应该进行成员复制并对任何需要它的成员执行深度复制,然后返回结果对象。这是一个递归操作(它要求您要克隆的类的所有成员都是值类型或实现 ICloneable,并且它们的成员是值类型或实现 ICloneable,等等)。
有关使用 ICloneable 进行克隆的更详细说明,请查看 this article。
long 的答案是“视情况而定”。正如其他人所提到的,ICloneable 不受泛型支持,需要对循环类引用进行特殊考虑,并且实际上被某些人视为 .NET Framework 中的 "mistake"。序列化方法取决于您的对象是可序列化的,它们可能不是并且您可能无法控制。社区中仍然存在很多关于哪种是“最佳”实践的争论。实际上,没有一个解决方案是一刀切的最佳实践,适用于 ICloneable 最初被解释为的所有情况。
有关更多选项,请参阅此 Developer's Corner article(感谢 Ian)。
基本上你需要实现ICloneable接口,然后实现对象结构复制。如果它是所有成员的深层副本,您需要确保(与您选择的解决方案无关)所有孩子也是可克隆的。有时您需要注意此过程中的一些限制,例如,如果您复制 ORM 对象,大多数框架只允许将一个对象附加到会话,并且您不得克隆该对象,或者您可能需要注意关于这些对象的会话附加。
干杯。
DeepCloner:解决克隆问题的快速、简单、有效的 NuGet 包
在阅读了所有答案后,我很惊讶没有人提到这个优秀的包:
详细说明它的 README,以下是我们在工作中选择它的原因:
它可以深拷贝或浅拷贝在深度克隆中维护所有对象图。在运行时使用代码生成,因为结果克隆速度非常快 对象由内部结构复制,没有调用方法或 ctor 不需要以某种方式标记类(如可序列化属性或实现接口) 不需要指定对象类型克隆。对象可以转换为接口或抽象对象(例如,您可以将整数数组克隆为抽象数组或 IEnumerable;甚至可以克隆 null 而不会出现任何错误) 克隆对象没有任何能力确定他是克隆对象(除了用非常具体的方法)
用法:
var deepClone = new { Id = 1, Name = "222" }.DeepClone();
var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
表现:
自述文件包含各种克隆库和方法的性能比较:DeepCloner Performance。
要求:
.NET 4.0 或更高版本或 .NET Standard 1.3 (.NET Core)
需要完全信任权限集或反射权限 (MemberAccess)
编辑:项目已停止
如果您想真正克隆到未知类型,可以查看 fastclone。
这是基于表达式的克隆,其工作速度比二进制序列化快 10 倍,并保持完整的对象图完整性。
这意味着:如果您多次引用层次结构中的同一对象,则克隆也将引用单个实例。
不需要对被克隆的对象进行接口、属性或任何其他修改。
保持简单并像其他人提到的那样使用 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();
一般来说,你实现ICloneable接口,自己实现Clone。 C# 对象有一个内置的 MemberwiseClone 方法,该方法执行浅拷贝,可以帮助您处理所有原语。
对于深拷贝,它无法知道如何自动完成。
我想出这个来克服必须手动深拷贝 List<T> 的 .NET 缺点。
我用这个:
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
class Utility<T> where T : ICloneable
{
static public IEnumerable<T> CloneList(List<T> tl)
{
foreach (T t in tl)
{
yield return (T)t.Clone();
}
}
}
问:为什么我会选择这个答案?
如果您想要 .NET 能够提供的最快速度,请选择此答案。
如果您想要一种非常非常简单的克隆方法,请忽略此答案。
比其他方法快 10 倍
以下执行深度克隆的方法是:
比涉及序列化/反序列化的任何东西快 10 倍;
非常接近 .NET 能够达到的理论最大速度。
而且方法...
为了获得终极速度,您可以使用 Nested MemberwiseClone 进行深层复制。它与复制值结构的速度几乎相同,并且比(a)反射或(b)序列化(如本页其他答案中所述)快得多。
请注意,如果您使用 Nested MemberwiseClone 进行深层复制,则必须为类中的每个嵌套级别手动实现一个 ShallowCopy,以及一个调用所有所述 ShallowCopy 方法的 DeepCopy 来创建一个完整的克隆。这很简单:总共只有几行,请参见下面的演示代码。
以下是显示 100,000 个克隆的相对性能差异的代码输出:
嵌套结构上的嵌套 MemberwiseClone 需要 1.08 秒
嵌套类上的嵌套 MemberwiseClone 为 4.77 秒
序列化/反序列化 39.93 秒
在类上使用 Nested MemberwiseClone 几乎与复制结构一样快,并且复制结构非常接近 .NET 能够达到的理论最大速度。
Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:04.7795670,30000000
Demo 2 of shallow and deep copy, using structs and value copying:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details:
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed 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 MemberwiseClone
public 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 调用演示:
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();
}
再次注意,如果您使用 Nested MemberwiseClone 进行深层复制,则必须为类中的每个嵌套级别手动实现一个 ShallowCopy,以及一个调用所有所述 ShallowCopy 方法的 DeepCopy 来创建一个完整的克隆。这很简单:总共只有几行代码,请参见上面的演示代码。
值类型与引用类型
请注意,在克隆对象时,“结构”和“类”之间存在很大差异:
如果你有一个“结构”,它是一个值类型,所以你可以复制它,内容将被克隆(但除非你使用本文中的技术,否则它只会进行浅层克隆)。
如果你有一个“类”,它是一个引用类型,所以如果你复制它,你所做的就是复制指向它的指针。要创建真正的克隆,您必须更具创造性,并利用值类型和引用类型之间的差异,从而在内存中创建原始对象的另一个副本。
请参阅differences between value types and references types。
有助于调试的校验和
不正确地克隆对象会导致非常难以确定的错误。在生产代码中,我倾向于实现校验和来仔细检查对象是否已正确克隆,并且没有被另一个引用损坏。此校验和可以在发布模式下关闭。
我发现这种方法非常有用:通常,您只想克隆对象的一部分,而不是整个对象。
对于将许多线程与许多其他线程解耦非常有用
此代码的一个极好的用例是将嵌套类或结构的克隆提供给队列,以实现生产者/消费者模式。
我们可以让一个(或多个)线程修改他们拥有的类,然后将这个类的完整副本推送到 ConcurrentQueue 中。
然后我们有一个(或多个)线程将这些类的副本拉出并处理它们。
这在实践中非常有效,并且允许我们将许多线程(生产者)与一个或多个线程(消费者)分离。
而且这种方法也快得惊人:如果我们使用嵌套结构,它比序列化/反序列化嵌套类快 35 倍,并且允许我们利用机器上所有可用的线程。
更新
显然,ExpressMapper 与上述手动编码一样快,甚至更快。我可能需要看看他们如何与分析器进行比较。
免责声明:我是上述软件包的作者。
我很惊讶 2019 年这个问题的最佳答案仍然使用序列化或反射。
序列化是有限制的(需要属性、特定的构造函数等)并且非常慢
BinaryFormatter
需要 Serializable
属性,JsonConverter
需要无参数的构造函数或属性,都不能很好地处理只读字段或接口,并且都比必要的慢 10-30 倍。
表达式树
您可以改为使用 Expression Trees 或 Reflection.Emit 只生成一次克隆代码,然后使用该编译代码而不是慢反射或序列化。
我自己遇到了这个问题并且没有看到令人满意的解决方案,我决定创建一个包,它可以做到这一点并且适用于每种类型,并且几乎与自定义编写的代码一样快。
您可以在 GitHub 上找到该项目:https://github.com/marcelltoth/ObjectCloner
用法
您可以从 NuGet 安装它。获取 ObjectCloner
包并将其用作:
var clone = ObjectCloner.DeepClone(original);
或者,如果您不介意使用扩展名污染您的对象类型,请获取 ObjectCloner.Extensions
并编写:
var clone = original.DeepClone();
表现
克隆类层次结构的简单基准显示性能比使用反射快约 3 倍,比 Newtonsoft.Json 序列化快约 12 倍,比强烈建议的 BinaryFormatter
快约 36 倍。
ObjectCloner
的静态方法,尽管使用了指令,但我必须显式地来自命名空间,例如 - ObjectCloner.ObjectCloner.DeepClone(someObject)
。
我也看到它是通过反射实现的。基本上有一种方法可以遍历对象的成员并将它们适当地复制到新对象。当它到达引用类型或集合时,我认为它对自身进行了递归调用。反射很昂贵,但效果很好。
这是一个深拷贝实现:
public static object CloneObject(object opSource)
{
//grab the type and create a new instance of that type
Type opSourceType = opSource.GetType();
object opTarget = CreateInstanceOfType(opSourceType);
//grab the properties
PropertyInfo[] 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 target
foreach (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 reached
else
{
object opPropertyValue = item.GetValue(opSource, null);
if (opPropertyValue == null)
{
item.SetValue(opTarget, null, null);
}
else
{
item.SetValue(opTarget, CloneObject(opPropertyValue), null);
}
}
}
}
//return the new item
return opTarget;
}
由于在不同项目中找不到满足我所有要求的克隆器,我创建了一个深度克隆器,可以配置和适应不同的代码结构,而不是调整我的代码以满足克隆器的要求。它是通过向应克隆的代码添加注释来实现的,或者您只需将代码保留为具有默认行为即可。它使用反射、类型缓存并基于 fasterflect。对于大量数据和高对象层次结构(与其他基于反射/序列化的算法相比),克隆过程非常快。
https://github.com/kalisohn/CloneBehave
也可作为 nuget 包提供:https://www.nuget.org/packages/Clone.Behave/1.0.0
例如:下面的代码将 deepClone 地址,但只执行 _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();
//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true
创建一个扩展:
public static T Clone<T>(this T theObject)
{
string jsonData = JsonConvert.SerializeObject(theObject);
return JsonConvert.DeserializeObject<T>(jsonData);
}
并这样称呼它:
NewObject = OldObject.Clone();
代码生成器
从序列化到手动实现再到反射,我们已经看到了很多想法,我想提出一种使用 CGbR Code Generator 的完全不同的方法。生成克隆方法具有内存和 CPU 效率,因此比标准 DataContractSerializer 快 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 copied
copy._number = _number;
if (deep)
{
// In a deep clone the references are cloned
var 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 copied
copy.Partials = Partials;
copy.Numbers = Numbers;
}
return copy;
}
}
注意:最新版本有更多的空检查,但为了更好地理解,我将它们省略了。
我喜欢这样的 Copyconstructors:
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));
}
}
如果您有更多要复制的内容,请添加
这种方法为我解决了这个问题:
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);
这是一个快速而简单的解决方案,它对我有用,而不需要序列化/反序列化。
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();
}
按着这些次序:
定义一个具有返回 T 的只读 Self 属性的 ISelf
然后定义一个 CloneBase 类型,该类型实现受保护的虚拟泛型 VirtualClone 将 MemberwiseClone 转换为传入的类型。
每个派生类型都应该通过调用基本克隆方法来实现 VirtualClone,然后做任何需要做的事情来正确克隆父 VirtualClone 方法尚未处理的派生类型的那些方面。
为获得最大的继承多功能性,公开公共克隆功能的类应该是 sealed
,但派生自一个基类,除了缺少克隆之外,该基类在其他方面是相同的。与其传递显式可克隆类型的变量,不如采用 ICloneable<theNonCloneableType>
类型的参数。这将允许期望 Foo
的可克隆衍生物的例程与 DerivedFoo
的可克隆衍生物一起工作,但也允许创建 Foo
的不可克隆衍生物。
由于这个问题的几乎所有答案都不令人满意或显然不适用于我的情况,因此我编写了 AnyClone,它完全通过反射实现并解决了这里的所有需求。我无法让序列化在具有复杂结构的复杂场景中工作,并且 IClonable
不太理想 - 实际上它甚至没有必要。
使用 [IgnoreDataMember]
、[NonSerialized]
支持标准忽略属性。支持复杂的集合、没有设置器的属性、只读字段等。
我希望它可以帮助遇到与我相同的问题的其他人。
AnyClone
,我刚刚安装了名为 .Clone() 的 NuGet 包,它在这里的 Blazor 项目中运行良好!
我创建了一个可接受的答案版本,它适用于“[Serializable]”和“[DataContract]”。我写它已经有一段时间了,但如果我没记错的话 [DataContract] 需要一个不同的序列化程序。
需要 System、System.IO、System.Runtime.Serialization、System.Runtime.Serialization.Formatters.Binary、System.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 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);
}
}
/// <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 object
if (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;
}
}
[Serializable]
属性标记,typeof(T).IsSerializable
也是如此。它不必实现ISerializable
接口。