我想做的事情如下:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
Run Code Online (Sandbox Code Playgroud)
然后更改未在原始对象中反映的新对象.
我不经常需要这个功能,所以当有必要的时候,我已经使用了创建一个新对象然后单独复制每个属性,但它总是让我觉得有更好或更优雅的处理方式情况.
如何克隆或深度复制对象,以便可以修改克隆对象而不会在原始对象中反映任何更改?
joh*_*hnc 1658
虽然标准的做法是实现ICloneable
界面(这里描述,所以我不会反刍),这是我在The Code Project上发现的一个很好的深度克隆对象复制器,并将其合并到我们的东西中.
如其他地方所述,它确实需要您的对象可序列化.
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
/// <summary>
/// Perform a deep Copy of the object.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T Clone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", nameof(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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我们的想法是将对象序列化,然后将其反序列化为新对象.好处是,当对象过于复杂时,您不必担心克隆所有内容.
并使用扩展方法(也来自最初引用的源):
如果您更喜欢使用C#3.0 的新扩展方法,请将方法更改为具有以下签名:
public static T Clone<T>(this T source)
{
//...
}
Run Code Online (Sandbox Code Playgroud)
现在方法调用就变成了objectBeingCloned.Clone();
.
编辑(2015年1月10日)以为我会重新考虑这一点,提到我最近开始使用(Newtonsoft)Json这样做,它应该更轻,并避免[Serializable]标签的开销.(注意 @atconway在评论中指出私有成员不使用JSON方法克隆)
/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
// initialize inner objects individually
// for example in default constructor some list property initialized with some values,
// but in 'source' these items are cleaned -
// without ObjectCreationHandling.Replace default constructor values will be added to result
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
Run Code Online (Sandbox Code Playgroud)
cra*_*tad 273
我想要一个非常简单的对象,主要是原始和列表的克隆人.如果你的对象是开箱即用的JSON serializable,那么这个方法就可以了.这不需要修改或实现克隆类上的接口,只需要像JSON.NET这样的JSON序列化程序.
public static T Clone<T>(T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
Run Code Online (Sandbox Code Playgroud)
此外,您可以使用此扩展方法
public static class SystemExtension
{
public static T Clone<T>(this T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
}
Run Code Online (Sandbox Code Playgroud)
Rya*_*ndy 171
不使用的原因ICloneable是不是因为它没有一个通用的接口. 不使用它的原因是因为它含糊不清.它不清楚你是否得到浅或副本; 这取决于实施者.
是的,MemberwiseClone
做一个浅的副本,但相反的MemberwiseClone
是不是Clone
; 或许DeepClone
,它可能不存在.当您通过其ICloneable接口使用对象时,您无法知道底层对象执行哪种克隆.(并且XML注释不会说清楚,因为您将获得接口注释而不是对象的Clone方法上的注释.)
我通常做的只是制作一个Copy
完全符合我想要的方法.
cre*_*gox 108
在详细阅读了这里链接的许多选项以及此问题的可能解决方案之后,我相信所有选项都在Ian P的链接中得到了很好的总结(所有其他选项都是这些选项的变体),最佳解决方案由Pedro77关于问题评论的链接.
所以我将在这里复制这两个参考文献的相关部分.这样我们可以:
首先,这些都是我们的选择:
" 表达式树快速复制 "一文 还对序列化,反射和表达式树的克隆进行了性能比较.
Venkat Subramaniam先生(此处的冗余链接)详细解释了原因.
他的所有文章围绕着一个试图适用于大多数情况的例子,使用3个对象:人,大脑和城市.我们想要克隆一个人,它将拥有自己的大脑但是同一个城市.你可以想象上面的任何其他方法可以带来或阅读文章的所有问题.
这是他对他的结论的略微修改版本:
通过指定
New
后跟类名来复制对象通常会导致代码不可扩展.使用clone,原型模式的应用,是实现这一目标的更好方法.但是,使用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);
}
…
}
Run Code Online (Sandbox Code Playgroud)
现在考虑从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();
}
}
Run Code Online (Sandbox Code Playgroud)
您可以尝试运行以下代码:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
产生的输出将是:
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
Run Code Online (Sandbox Code Playgroud)
注意,如果我们保持对象数量的计数,这里实现的克隆将保持对象数量的正确计数.
Nic*_*ick 83
我更喜欢复制构造函数到克隆.意图更清晰.
Kon*_*tov 40
简单的扩展方法来复制所有公共属性.适用于任何对象,并没有要求类是[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 ) } );
}
};
}
Run Code Online (Sandbox Code Playgroud)
小智 32
好吧,我在Silverlight中使用ICloneable时遇到了问题,但我喜欢seralization的想法,我可以将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);
}
}
Run Code Online (Sandbox Code Playgroud)
Mar*_*zek 31
我刚刚创建了CloneExtensions
库项目.它使用Expression Tree运行时代码编译生成的简单赋值操作执行快速,深度克隆.
如何使用它?
使用表达式树,程序不是使用字段和属性之间的分配语音编写自己的方法Clone
或Copy
方法.GetClone<T>()
标记为扩展方法的方法允许您在实例上简单地调用它:
var newInstance = source.GetClone();
Run Code Online (Sandbox Code Playgroud)
您可以选择什么应该被复制source
到newInstance
使用CloningFlags
枚举:
var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
Run Code Online (Sandbox Code Playgroud)
什么可以克隆?
类/ struct成员在内部克隆:
它有多快?
解决方案比反射更快,因为成员信息只需要收集一次,之后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:
}
Run Code Online (Sandbox Code Playgroud)
}
具有与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;
}
Run Code Online (Sandbox Code Playgroud)
这不是很像你如何编写自己的Clone
方法List<int>
吗?
Mic*_*Cox 28
如果您已经使用像一个第三方应用程序ValueInjecter或Automapper,你可以这样做:
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
Run Code Online (Sandbox Code Playgroud)
使用此方法,您不必在对象上实现ISerializable或ICloneable.这在MVC/MVVM模式中很常见,因此创建了这样的简单工具.
Zac*_*ame 21
简短的回答是您从ICloneable接口继承然后实现.clone函数.克隆应该执行成员复制并对需要它的任何成员执行深层复制,然后返回结果对象.这是一个递归操作(它要求您要克隆的类的所有成员都是值类型或实现ICloneable,并且它们的成员是值类型或实现ICloneable,依此类推).
有关使用ICloneable克隆的更详细说明,请查看本文.
该长的答案是"看情况".正如其他人所提到的,ICloneable不受泛型支持,需要对循环类引用进行特殊考虑,并且实际上被某些人视为.NET Framework中的"错误".序列化方法取决于您的对象是可序列化的,它们可能不是,您可能无法控制.社区中仍然存在很多争论,即"最佳"做法.实际上,对于像ICloneable最初被解释为的所有情况,所有解决方案都不是一刀切.
有关更多选项,请参阅此Developer's Corner文章(归功于Ian).
fra*_*kon 21
最好的方法是实现类似的扩展方法
public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }
Run Code Online (Sandbox Code Playgroud)
然后在解决方案的任何地方使用它
var copy = anyObject.DeepClone();
Run Code Online (Sandbox Code Playgroud)
我们可以有以下三种实现:
所有链接的方法都很好,并经过深入测试.
dim*_*ist 18
干杯.
ale*_*a87 18
阅读完所有答案后,我很惊讶没有人提到这个优秀的软件包:
详细介绍一下它的 README,以下是我们在工作中选择它的原因:
- 可以深拷贝也可以浅拷贝
- 在深度克隆中,所有对象图都得到维护。
- 在运行时使用代码生成,因为结果克隆非常快
- 由内部结构复制的对象,没有调用方法或构造函数
- 您不需要以某种方式标记类(例如 Serializable-attribute 或实现接口)
- 不需要为克隆指定对象类型。对象可以转换为接口或抽象对象(例如,您可以将整数数组克隆为抽象数组或 IEnumerable;甚至可以克隆 null,而不会出现任何错误)
- 克隆的对象没有任何能力确定他是克隆的(除非有非常具体的方法)
var deepClone = new { Id = 1, Name = "222" }.DeepClone();
var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
Run Code Online (Sandbox Code Playgroud)
README 包含各种克隆库和方法的性能比较:DeepCloner Performance。
Sta*_*ked 12
保持简单并像其他人一样使用AutoMapper,它是一个简单的小库,可以将一个对象映射到另一个...要将对象复制到另一个具有相同类型的对象,您只需要三行代码:
MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);
Run Code Online (Sandbox Code Playgroud)
目标对象现在是源对象的副本.不够简单?创建一个扩展方法,以便在解决方案中的任
public static T Copy<T>(this T source)
{
T copy = default(T);
Mapper.CreateMap<T, T>();
copy = Mapper.Map<T, T>(source);
return copy;
}
Run Code Online (Sandbox Code Playgroud)
通过使用扩展方法,三行成为一行:
MyType copy = source.Copy();
Run Code Online (Sandbox Code Playgroud)
Dan*_*dor 11
我提出这个来克服.NET必须手动深度复制List <T>的缺点.
我用这个:
static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
foreach (SpotPlacement sp in spotPlacements)
{
yield return (SpotPlacement)sp.Clone();
}
}
Run Code Online (Sandbox Code Playgroud)
在另一个地方:
public object Clone()
{
OrderItem newOrderItem = new OrderItem();
...
newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
...
return newOrderItem;
}
Run Code Online (Sandbox Code Playgroud)
我试图提出这样做的oneliner,但是由于不能在匿名方法块内工作,所以不可能.
更好的是,使用通用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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
换句话说,除非您有需要修复的性能瓶颈,否则请使用其他答案,并且您可以使用分析器来证明它.
以下执行深度克隆的方法是:
要获得最高速度,可以使用嵌套的MemberwiseClone进行深层复制.它与复制值结构的速度几乎相同,并且比(a)反射或(b)序列化快得多(如本页其他答案中所述).
请注意,如果您使用嵌套的MemberwiseClone进行深层复制,则必须为类中的每个嵌套级别手动实现ShallowCopy,并使用DeepCopy调用所有所述的ShallowCopy方法来创建完整的克隆.这很简单:总共只有几行,请参阅下面的演示代码.
以下是代码的输出,显示100,000个克隆的相对性能差异:
在类上使用嵌套的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
Run Code Online (Sandbox Code Playgroud)
要了解如何使用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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后,从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();
}
Run Code Online (Sandbox Code Playgroud)
同样,请注意,如果您使用嵌套的MemberwiseClone进行深层复制,则必须为类中的每个嵌套级别手动实现ShallowCopy,并使用DeepCopy调用所有所述的ShallowCopy方法来创建完整的克隆.这很简单:总共只有几行,请参阅上面的演示代码.
请注意,在克隆对象时," struct "和" class " 之间存在很大差异:
此代码的一个优秀用例是将嵌套类或结构的克隆提供到队列中,以实现生产者/消费者模式.
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);
Run Code Online (Sandbox Code Playgroud)
或者,如果您不介意使用扩展名污染您的对象类型ObjectCloner.Extensions
,请编写:
var clone = original.DeepClone();
Run Code Online (Sandbox Code Playgroud)
克隆类层次结构的简单基准测试显示,性能比使用反射快约 3 倍,比 Newtonsoft.Json 序列化快约 12 倍,比强烈建议的BinaryFormatter
.
通常,您实现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;
}
Run Code Online (Sandbox Code Playgroud)
由于我找不到满足我在不同项目中的所有要求的克隆人,我创建了一个深度克隆者,可以配置和适应不同的代码结构,而不是调整我的代码以满足克隆者的要求.它是通过在应该克隆的代码中添加注释来实现的,或者只是保留代码,因为它具有默认行为.它使用反射,类型缓存并基于更快的反射.对于大量数据和高对象层次结构(与其他基于反射/序列化的算法相比),克隆过程非常快.
https://github.com/kalisohn/CloneBehave
也可作为nuget包提供:https://www.nuget.org/packages/Clone.Behave/1.0.0
例如:以下代码将为deepClone Address,但仅执行_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
Run Code Online (Sandbox Code Playgroud)
这个方法为我解决了这个问题:
private static MyObj DeepCopy(MyObj source)
{
var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);
}
Run Code Online (Sandbox Code Playgroud)
像这样使用它: MyObj a = DeepCopy(b);
创建扩展:
public static T Clone<T>(this T theObject)
{
string jsonData = JsonConvert.SerializeObject(theObject);
return JsonConvert.DeserializeObject<T>(jsonData);
}
Run Code Online (Sandbox Code Playgroud)
并这样称呼它:
NewObject = OldObject.Clone();
Run Code Online (Sandbox Code Playgroud)
我喜欢像这样的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));
}
}
Run Code Online (Sandbox Code Playgroud)
如果您有更多要复制的东西,请添加它们
我们已经看到了从手动实现到反射的序列化的很多想法,我想使用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;
}
}
Run Code Online (Sandbox Code Playgroud)
注意:最新版本有一个更多的空检查,但我把它们留下来以便更好地理解.
这里的解决方案快速而简单,对我有用而无需继续序列化/反序列化.
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;
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:要求
using System.Linq;
using System.Reflection;
Run Code Online (Sandbox Code Playgroud)
这就是我如何使用它
public MyClass Clone(MyClass theObjectIneededToClone)
{
MyClass clonedObj = theObjectIneededToClone.DeepClone();
}
Run Code Online (Sandbox Code Playgroud)
小智 6
我想你可以试试看。
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
Run Code Online (Sandbox Code Playgroud)
最短的方法但需要依赖:
using Newtonsoft.Json;
public static T Clone<T>(T source) =>
JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
Run Code Online (Sandbox Code Playgroud)
C# 9.0 引入了with
需要 a 的关键字record
(感谢 Mark Nading)。这应该允许使用非常少的样板进行非常简单的对象克隆(如果需要的话还可以进行突变),但只能使用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);
}
}
Run Code Online (Sandbox Code Playgroud)
这写的是“再见世界:(”-字符串是通过引用复制的(不需要)。https: //dotnetfiddle.net/w3IJgG
(令人难以置信的是,上面的代码可以正确地使用struct
!https://dotnetfiddle.net/469NJv)
但克隆 arecord
似乎确实可以起到缩进、按值克隆的作用。
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这将返回“Hello World”,该字符串是按值复制的!https://dotnetfiddle.net/MCHGEL
更多信息可以在博客文章中找到:
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/with-expression
ObjectExtensions.cs
在我正在使用的代码库中,我们有来自 GitHub 项目Burtsev-Alexey/net-object-deep-copy的文件副本。今年已经9岁了。它起作用了,尽管我们后来意识到对于较大的对象结构来说它非常慢。
ObjectExtensions.cs
相反,我们在 GitHub 项目jpmikkers/Baksteen.Extensions.DeepCopy中找到了该文件的分支。大型数据结构的深度复制操作以前需要大约 30 分钟,现在感觉几乎是瞬时的。
这个改进版本有以下文档:
用于快速对象克隆的 C# 扩展方法。
这是 Alexey Burtsev 深度复印机的速度优化叉。根据您的用例,这将比原始速度快 2 至 3 倍。它还修复了原始代码中存在的一些错误。与经典的二进制序列化/反序列化深度克隆技术相比,此版本大约快七倍(对象包含的数组越多,加速系数越大)。
加速是通过以下技术实现的:
- 对象反射结果被缓存
- 不要深度复制基元或不可变的结构和类(例如枚举和字符串)
- 为了提高引用的局部性,在内部循环中处理“快速”维度或多维数组
- 使用已编译的lambda表达式来调用MemberwiseClone
如何使用:
Run Code Online (Sandbox Code Playgroud)using Baksteen.Extensions.DeepCopy; ... var myobject = new SomeClass(); ... var myclone = myobject.DeepCopy()!; // creates a new deep copy of the original object
注意:仅当您在项目中启用了可空引用类型时才需要感叹号(空值宽容运算符)
跟着这些步骤:
ISelf<T>
具有只读Self
属性的,该属性返回T
和ICloneable<out T>
,该属性派生自ISelf<T>
并包含方法T Clone()
。CloneBase
类型,该类型实现对传入类型的protected virtual generic VirtualClone
强制转换MemberwiseClone
。 VirtualClone
通过调用基本clone方法来实现,然后执行所需的任何操作以正确克隆父VirtualClone方法尚未处理的派生类型的那些方面。为了最大程度地继承通用性,公开公共克隆功能的类应为sealed
,但派生自基类,除非缺少克隆,否则基类是相同的。而不是传递显式可克隆类型的变量,而应使用type的参数ICloneable<theNonCloneableType>
。这将允许期望的可克隆派生Foo
与的可克隆派生一起使用的例程 DerivedFoo
,但也允许创建的不可克隆派生Foo
。
好吧,这篇文章中有一些明显的反射示例,但是反射通常很慢,直到您开始正确缓存它。
如果你正确地缓存它,那么它将在 4.6 秒内深度克隆 1000000 个对象(由 Watcher 测量)。
static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();
Run Code Online (Sandbox Code Playgroud)
比您获取缓存的属性或向字典添加新属性并简单地使用它们
foreach (var prop in propList)
{
var value = prop.GetValue(source, null);
prop.SetValue(copyInstance, value, null);
}
Run Code Online (Sandbox Code Playgroud)
完整代码请查看我在另一个答案中的帖子
归档时间: |
|
查看次数: |
774668 次 |
最近记录: |