题
我想做类似的事情:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
然后对新对象进行未反映在原始对象中的更改。
我并不经常需要这个功能,所以当有必要时,我会求助于创建一个新对象,然后单独复制每个属性,但这总是让我感觉有更好或更优雅的处理方式情况。
如何克隆或深度复制对象,以便可以修改克隆的对象而不在原始对象中反映任何更改?
解决方案
虽然标准做法是实施 ICloneable
接口(描述 这里, ,所以我不会反胃),这是我发现的一个很好的深度克隆对象复制器 代码项目 不久前并将其纳入我们的东西中。
正如其他地方提到的,它确实要求您的对象是可序列化的。
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);
}
}
}
这个想法是它序列化你的对象,然后将其反序列化为一个新的对象。这样做的好处是,当对象变得过于复杂时,您不必担心克隆所有内容。
并使用扩展方法(也来自最初引用的来源):
如果您更喜欢使用新的 扩展方法 在 C# 3.0 中,将方法更改为具有以下签名:
public static T Clone<T>(this T source)
{
//...
}
现在方法调用就变成了 objectBeingCloned.Clone();
.
编辑 (2015 年 1 月 10 日)我想我会重新审视这个,提到我最近开始使用 (Newtonsoft) Json 来做到这一点,它 应该 更轻,并且避免了 [Serialized] 标签的开销。(注意 @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);
}
其他提示
我想要一个用于非常简单的对象(主要是基元和列表)的克隆器。如果您的对象是开箱即用的 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);
}
}
不使用的原因 ICloneable 是 不是 因为它没有通用的接口。 不使用它的原因是因为它含糊不清. 。它并不清楚你得到的是浅拷贝还是深拷贝;这取决于实施者。
是的, MemberwiseClone
进行浅拷贝,但相反 MemberwiseClone
不是 Clone
;或许, DeepClone
, ,它不存在。当您通过 ICloneable 接口使用对象时,您无法知道底层对象执行哪种类型的克隆。(XML 注释不会使其变得清晰,因为您将获得接口注释,而不是对象的 Clone 方法上的注释。)
我通常做的只是简单地制作一个 Copy
完全符合我想要的方法。
在大量阅读了此处链接的许多选项以及此问题的可能解决方案后,我相信 所有选项都很好地总结在 伊恩·P的链接 (所有其他选项都是这些选项的变体),最佳解决方案由 佩德罗77的链接 关于问题的评论。
因此,我将在此处复制这两个参考文献的相关部分。这样我们就可以得到:
在 C Sharp 中克隆对象的最佳方法!
首先也是最重要的,这些是我们所有的选择:
- 手动与 ICloneable, ,即 浅的 并不是 类型安全
- 会员克隆, ,它使用 ICloneable
- 反射 通过使用 激活器.CreateInstance 和 递归成员克隆
- 序列化, ,如所指出的 约翰的首选答案
- 中级语言, ,我不知道 如何运作
- 扩展方法, ,比如这个 Havard Straden 的自定义克隆框架
- 表达式树
这 文章通过表达式树进行快速深复制 还有通过序列化、反射和表达式树进行克隆的性能比较。
我为什么选择 ICloneable (IE。手动)
Venkat Subramaniam 先生(此处为多余链接)详细解释了原因.
他的所有文章都围绕一个尝试适用于大多数情况的示例,使用 3 个对象: 人, 脑 和 城市. 。我们想要克隆一个人,它有自己的大脑,但有同一个城市。您可以想象上述任何其他方法可能带来的所有问题,也可以阅读本文。
这是我对他的结论稍加修改的版本:
通过指定复制对象
New
后面跟着类名通常会导致代码不可扩展。使用克隆,即原型模式的应用,是实现这一目标的更好方法。然而,使用 C#(和 Java)中提供的克隆也可能存在很大问题。最好提供一个受保护的(非公共)复制构造函数并从克隆方法调用它。这使我们能够将创建对象的任务委托给类本身的实例,从而提供可扩展性,并且还可以使用受保护的复制构造函数安全地创建对象。
希望这个实现可以让事情变得清晰:
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
请注意,如果我们保留对象数量的计数,则此处实现的克隆将保留对象数量的正确计数。
与克隆相比,我更喜欢复制构造函数。意图更加明确。
复制所有公共属性的简单扩展方法。适用于任何物体和 才不是 要求类是 [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 ) } );
}
};
}
嗯,我在 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);
}
}
我刚刚创建了 CloneExtensions
图书馆 项目。它使用表达式树运行时代码编译生成的简单赋值操作来执行快速、深度克隆。
如何使用它?
而不是自己写 Clone
或者 Copy
在字段和属性之间进行赋值的方法使程序可以使用表达式树自行完成此操作。 GetClone<T>()
标记为扩展方法的方法允许您简单地在实例上调用它:
var newInstance = source.GetClone();
您可以选择要从中复制的内容 source
到 newInstance
使用 CloningFlags
枚举:
var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
什么可以克隆?
- 原始(INT,UINT,BYTE,Double,Char等),已知的不变类型(DateTime,Timespan,String)和代表(包括动作,弹性等)
- 可空
- T[] 数组
- 自定义类和结构,包括通用类和结构。
以下类/结构成员在内部克隆:
- 公共字段的值,而不是只读字段的值
- 具有 get 和 set 访问器的公共属性的值
- 实现 ICollection 的类型的集合项
有多快?
该解决方案比反射更快,因为成员信息只需收集一次,然后 GetClone<T>
第一次用于给定类型 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);
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;
}
这不是很像你自己写的吗 Clone
方法用于 List<int>
?
简短的答案是您继承 ICloneable 接口,然后实现 .clone 函数。克隆应该执行成员复制并对任何需要它的成员执行深层复制,然后返回结果对象。这是一个递归操作(它要求您要克隆的类的所有成员要么是值类型,要么实现 ICloneable,并且它们的成员要么是值类型,要么实现 ICloneable,等等)。
有关使用 ICloneable 进行克隆的更详细说明,请查看 本文.
这 长的 答案是“这取决于”。正如其他人提到的,ICloneable 不受泛型支持,需要特别考虑循环类引用,并且实际上被某些人视为 “错误” 在 .NET 框架中。序列化方法取决于您的对象是否可序列化,但它们可能不是可序列化的,并且您可能无法控制。社区中对于哪种是“最佳”实践仍然存在很多争论。事实上,没有一种解决方案能够像 ICloneable 最初解释的那样,适合所有情况的一刀切的最佳实践。
看这个 开发者角文章 更多选项(归功于 Ian)。
- 基本上你需要实现ICloneable接口,然后实现对象结构复制。
- 如果它是所有成员的深层复制,您需要确保(与您选择的解决方案无关)所有子级也都是可克隆的。
- 有时您需要注意在此过程中的一些限制,例如,如果您复制 ORM 对象,则大多数框架只允许将一个对象附加到会话,并且您不得克隆该对象,或者如果可能您需要关心关于这些对象的会话附加。
干杯。
如果你想真正克隆未知类型,你可以看看快速克隆.
这是基于表达式的克隆,其工作速度比二进制序列化快约 10 倍,并保持完整的对象图完整性。
这意味着:如果您多次引用层次结构中的同一对象,则克隆也将有一个被引用的实例。
不需要对被克隆的对象进行接口、属性或任何其他修改。
保持简单并使用 自动映射器 正如其他人提到的,这是一个简单的小库,用于将一个对象映射到另一个对象......要将一个对象复制到另一个具有相同类型的对象,您只需要三行代码:
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();
我想出这个是为了克服 。网 缺点是必须手动深复制 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();
}
}
}
问。为什么我会选择这个答案?
- 如果您想要 .NET 能够达到的最快速度,请选择此答案。
- 如果您想要一种非常非常简单的克隆方法,请忽略此答案。
换句话说, 除非您有需要修复的性能瓶颈,否则请采用另一个答案,并且您可以使用分析器来证明它.
比其他方法快 10 倍
执行深度克隆的方法如下:
- 比涉及序列化/反序列化的任何操作快 10 倍;
- 非常接近 .NET 的理论最大速度。
还有方法...
为了获得终极速度,您可以使用 嵌套 MemberwiseClone 进行深复制. 。它的速度几乎与复制值结构相同,并且比(a)反射或(b)序列化(如本页其他答案中所述)快得多。
注意 如果 你用 用于深层复制的嵌套 MemberwiseClone, ,您必须为类中的每个嵌套级别手动实现 ShallowCopy,以及调用所有上述 ShallowCopy 方法来创建完整克隆的 DeepCopy。这很简单:总共只有几行,请参见下面的演示代码。
以下是显示 100,000 个克隆的相对性能差异的代码输出:
- 嵌套结构上的 Nested MemberwiseClone 需要 1.08 秒
- 嵌套类上的 Nested 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();
}
再次请注意 如果 你用 用于深层复制的嵌套 MemberwiseClone, ,您必须为类中的每个嵌套级别手动实现 ShallowCopy,以及调用所有上述 ShallowCopy 方法来创建完整克隆的 DeepCopy。这很简单:总共只有几行,参见上面的演示代码。
值类型对比参考文献类型
请注意,当涉及到克隆对象时,“结构体”和一个“班级":
- 如果你有一个“结构体“, 它是 值类型 因此您只需复制它,内容就会被克隆(但除非您使用本文中的技术,否则它只会进行浅层克隆)。
- 如果你有一个“班级“, 它是 参考类型, ,所以如果你复制它,你所做的就是复制指向它的指针。要创建真正的克隆,您必须更具创造力,并使用 值类型和引用类型之间的区别 这会在内存中创建原始对象的另一个副本。
有助于调试的校验和
- 错误地克隆对象可能会导致非常难以确定的错误。在生产代码中,我倾向于实现一个校验和来仔细检查对象是否已正确克隆,并且没有被另一个对其的引用损坏。该校验和可以在发布模式下关闭。
- 我发现这个方法非常有用:通常,您只想克隆对象的一部分,而不是整个对象。
对于将许多线程与许多其他线程解耦非常有用
此代码的一个出色用例是将嵌套类或结构的克隆馈送到队列中,以实现生产者/消费者模式。
- 我们可以让一个(或多个)线程修改它们拥有的类,然后将此类的完整副本推送到
ConcurrentQueue
. - 然后,我们让一个(或多个)线程拉出这些类的副本并处理它们。
这在实践中非常有效,并且允许我们将许多线程(生产者)与一个或多个线程(消费者)解耦。
而且这个方法也快得令人眼花缭乱:如果我们使用嵌套结构,它比序列化/反序列化嵌套类快 35 倍,并且允许我们利用机器上所有可用的线程。
更新
显然,ExpressMapper 与上面的手动编码一样快,甚至更快。我可能需要看看它们与分析器相比如何。
一般来说,您实现ICloneable接口并自己实现Clone。C# 对象有一个内置的 MemberwiseClone 方法,该方法执行浅复制,可以帮助您处理所有基元。
对于深复制,它无法知道如何自动执行此操作。
我也看到过它是通过反射实现的。基本上有一种方法可以迭代对象的成员并将它们适当地复制到新对象。当它到达引用类型或集合时,我认为它对自身进行了递归调用。反射的成本很高,但效果很好。
这是一个深复制实现:
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;
}
由于我无法找到满足不同项目中所有要求的克隆器,因此我创建了一个可以配置和适应不同代码结构的深度克隆器,而不是调整我的代码来满足克隆器的要求。它是通过向要克隆的代码添加注释来实现的,或者您只需保留代码以使其具有默认行为即可。它使用反射、类型缓存并基于 快速反应. 。对于大量数据和高对象层次结构来说,克隆过程非常快(与其他基于反射/序列化的算法相比)。
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
这个方法为我解决了这个问题:
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 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));
}
}
如果您还有更多要复制的内容,请添加它们
代码生成器
我们已经看到了很多从序列化到手动实现再到反射的想法,我想提出一种完全不同的方法,使用 CGbR 代码生成器. 。生成克隆方法具有内存和 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;
}
}
笔记: 最新版本有更多的空检查,但为了更好地理解,我将它们省略了。
这是一个快速、简单的解决方案,对我有用,无需依赖序列化/反序列化。
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();
}
按着这些次序:
- 定义一个
ISelf<T>
具有只读Self
返回的属性T
, , 和ICloneable<out T>
, ,源自ISelf<T>
并包括一个方法T Clone()
. - 然后定义一个
CloneBase
类型实现了protected virtual generic VirtualClone
铸件MemberwiseClone
到传入的类型。 - 每个派生类型应该实现
VirtualClone
通过调用基本克隆方法,然后执行任何需要执行的操作来正确克隆父 VirtualClone 方法尚未处理的派生类型的那些方面。
为了获得最大的继承多功能性,公开公共克隆功能的类应该是 sealed
, ,但派生自基类,该基类除了缺少克隆之外在其他方面都是相同的。不要传递显式可克隆类型的变量,而是采用以下类型的参数 ICloneable<theNonCloneableType>
. 。这将允许一个期望可克隆衍生的例程 Foo
使用可克隆的衍生物 DerivedFoo
, ,但也允许创建不可克隆的衍生物 Foo
.
我想你可以试试这个。
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
我创建了一个可以与“[Serialized]”和“[DataContract]”一起使用的已接受答案的版本。我写它已经有一段时间了,但如果我没记错的话,[DataContract] 需要一个不同的序列化器。
需要 系统、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;
}
}
要克隆类对象,您可以使用 Object.MemberwiseClone 方法,
只需将此函数添加到您的类中:
public class yourClass
{
// ...
// ...
public yourClass DeepCopy()
{
yourClass othercopy = (yourClass)this.MemberwiseClone();
return othercopy;
}
}
然后要执行深度独立复制,只需调用 DeepCopy 方法:
yourClass newLine = oldLine.DeepCopy();
希望这可以帮助。
好吧,这篇文章中有一些明显的反射示例,但是反射通常很慢,直到您开始正确缓存它。
如果你正确地缓存它,那么它将在 4.6 秒内深度克隆 1000000 个对象(由 Watcher 测量)。
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);
}
完整代码请查看我在另一个答案中的帖子
如果您的对象树是可序列化的,您也可以使用类似的东西
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;
}
请注意,该解决方案非常简单,但其性能不如其他解决方案。
并且请确保,如果类增长,仍然只会克隆那些也会被序列化的字段。