我想做类似的事情:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
然后对未反映在原始对象中的新对象进行更改。
我并不经常需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但它总是让我觉得有更好或更优雅的处理方式情况。
如何克隆或深度复制对象,以便可以修改克隆的对象而不会在原始对象中反映任何更改?
一种方法是实现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 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 的新扩展方法,请将方法更改为具有以下签名:
public static T Clone<T>(this T source)
{
// ...
}
现在方法调用简单地变成了objectBeingCloned.Clone();
.
编辑(2015 年 1 月 10 日)我想我会重新审视这个,提到我最近开始使用(Newtonsoft)Json 来做这件事,它应该更轻,并且避免了 [Serializable] 标签的开销。(注意@atconway 在评论中指出私有成员不使用 JSON 方法克隆)
/// <summary>
/// Perform a deep Copy of the object, using Json as a 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);
}
}
不使用ICloneable的原因不是因为它没有通用接口。 不使用它的原因是因为它是模糊的。不清楚你得到的是浅拷贝还是深拷贝。这取决于实施者。
是的,MemberwiseClone
做一个浅拷贝,但相反的MemberwiseClone
不是Clone
;它可能是,DeepClone
不存在的。当您通过其 ICloneable 接口使用对象时,您无法知道底层对象执行哪种克隆。(并且 XML 注释不会说清楚,因为您将获得接口注释,而不是对象的 Clone 方法上的注释。)
我通常做的只是简单地制作一个Copy
完全符合我想要的方法。
在大量阅读了此处链接的许多选项以及此问题的可能解决方案之后,我相信所有选项都在Ian P的链接中得到了很好的总结(所有其他选项都是这些选项的变体),并且最佳解决方案由Pedro77的问题评论链接。
所以我将在这里复制这两个参考的相关部分。这样我们就可以拥有:
首先,这些都是我们的选择:
文章Fast Deep Copy by Expression Trees 也有序列化、反射和表达式树克隆的性能比较。
Venkat Subramaniam 先生(此处为冗余链接)详细解释了原因。
他的所有文章都围绕着一个尝试适用于大多数情况的示例,使用 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
请注意,如果我们保持对象数量的计数,则此处实现的克隆将保持对象数量的正确计数。
我更喜欢复制构造函数而不是克隆。意图更明确。
复制所有公共属性的简单扩展方法。适用于任何对象,不需要类是[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 ) } );
}
};
}
如果您已经在使用像ValueInjecter或Automapper这样的 3rd 方应用程序,您可以执行以下操作:
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
使用这种方法,您不必在您的对象上实现ISerializable
或。ICloneable
这在 MVC/MVVM 模式中很常见,因此创建了像这样的简单工具。
我刚刚创建了CloneExtensions
库项目。它使用表达式树运行时代码编译生成的简单赋值操作执行快速、深度克隆。
如何使用它?
使用表达式树,而不是编写自己的方法Clone
或Copy
在字段和属性之间进行分配的方法,而是让程序自己完成。GetClone<T>()
标记为扩展方法的方法允许您在实例上简单地调用它:
var newInstance = source.GetClone();
您可以选择应该复制的内容source
以newInstance
使用CloningFlags
枚举:
var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
什么可以克隆?
以下类/结构成员在内部克隆:
它有多快?
解决方案比反射更快,因为成员信息只需要收集一次,在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>
吗?
好吧,我在 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);
}
}
简短的回答是您从 ICloneable 接口继承,然后实现 .clone 函数。克隆应该进行成员复制并对任何需要它的成员执行深度复制,然后返回结果对象。这是一个递归操作(它要求您要克隆的类的所有成员都是值类型或实现 ICloneable,并且它们的成员是值类型或实现 ICloneable,等等)。
有关使用 ICloneable 进行克隆的更详细说明,请查看这篇文章。
长答案是“这取决于”。正如其他人所提到的,ICloneable 不受泛型支持,需要对循环类引用进行特殊考虑,并且实际上被某些人视为 .NET Framework 中的“错误”。序列化方法取决于您的对象是可序列化的,它们可能不是并且您可能无法控制。社区中仍然存在很多关于哪种是“最佳”实践的争论。实际上,没有一个解决方案是一刀切的最佳实践,适用于 ICloneable 最初被解释为的所有情况。
有关更多选项,请参阅此Developer's Corner 文章(感谢 Ian)。
干杯。
在阅读了所有答案后,我很惊讶没有人提到这个优秀的包:
详细说明它的 README,以下是我们在工作中选择它的原因:
- 它可以深拷贝或浅拷贝
- 在深度克隆中,所有对象图都被维护。
- 在运行时使用代码生成,因为结果克隆非常快
- 由内部结构复制的对象,没有调用方法或ctors
- 您不需要以某种方式标记类(例如 Serializable-attribute 或实现接口)
- 无需为克隆指定对象类型。对象可以转换为接口或抽象对象(例如,您可以将整数数组克隆为抽象数组或 IEnumerable;甚至可以克隆 null 而不会出现任何错误)
- 克隆的对象没有任何能力确定他是克隆的(除了非常特定的方法)
var deepClone = new { Id = 1, Name = "222" }.DeepClone();
var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
自述文件包含各种克隆库和方法的性能比较:DeepCloner Performance。
编辑:项目已停止
如果您想真正克隆到未知类型,可以查看 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 方法,该方法执行浅拷贝,可以帮助您处理所有原语。
对于深拷贝,它无法知道如何自动完成。
我想出这个来克服.NET必须手动深拷贝 List<T> 的缺点。
我用这个:
static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
foreach (SpotPlacement sp in spotPlacements)
{
yield return (SpotPlacement)sp.Clone();
}
}
在另一个地方:
public object Clone()
{
OrderItem newOrderItem = new OrderItem();
...
newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
...
return newOrderItem;
}
我试图想出一个可以做到这一点的oneliner,但这是不可能的,因为 yield 在匿名方法块中不起作用。
更好的是,使用通用 List<T> 克隆器:
class Utility<T> where T : ICloneable
{
static public IEnumerable<T> CloneList(List<T> tl)
{
foreach (T t in tl)
{
yield return (T)t.Clone();
}
}
}
换句话说,除非您有需要修复的性能瓶颈,否则请选择另一个答案,并且您可以使用 profiler 来证明它。
以下执行深度克隆的方法是:
为了获得终极速度,您可以使用Nested MemberwiseClone 进行深度复制。它与复制值结构的速度几乎相同,并且比(a)反射或(b)序列化(如本页其他答案中所述)快得多。
请注意,如果您使用Nested MemberwiseClone 进行深层复制,则必须为类中的每个嵌套级别手动实现一个 ShallowCopy,以及一个调用所有所述 ShallowCopy 方法的 DeepCopy 来创建一个完整的克隆。这很简单:总共只有几行,请参见下面的演示代码。
以下是显示 100,000 个克隆的相对性能差异的代码输出:
在类上使用 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 来创建完整的克隆。这很简单:总共只有几行代码,请参见上面的演示代码。
请注意,在克隆对象时,“结构”和“类”之间存在很大差异:
此代码的一个极好的用例是将嵌套类或结构的克隆提供给队列,以实现生产者/消费者模式。
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();
克隆类层次结构的简单基准显示性能比使用 Reflection 快约 3 倍,比 Newtonsoft.Json 序列化快约 12 倍,比强烈建议的快约 36 倍BinaryFormatter
。
我也看到它是通过反射实现的。基本上有一种方法可以遍历对象的成员并将它们适当地复制到新对象。当它到达引用类型或集合时,我认为它对自身进行了递归调用。反射很昂贵,但效果很好。
这是一个深拷贝实现:
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;
}
由于在不同项目中找不到满足我所有要求的克隆器,我创建了一个深度克隆器,可以配置和适应不同的代码结构,而不是调整我的代码以满足克隆器的要求。它是通过向应克隆的代码添加注释来实现的,或者您只需将代码保留为具有默认行为即可。它使用反射、类型缓存并基于fastflect。对于大量数据和高对象层次结构(与其他基于反射/序列化的算法相比),克隆过程非常快。
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 代码生成器的完全不同的方法。生成克隆方法具有内存和 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();
}
跟着这些步骤:
ISelf<T>
的只读Self
属性,它派生自并包含一个方法。T
ICloneable<out T>
ISelf<T>
T Clone()
CloneBase
实现对传入类型 的protected virtual generic VirtualClone
强制转换的类型。MemberwiseClone
VirtualClone
通过调用基本克隆方法来实现,然后做任何需要做的事情来正确克隆父 VirtualClone 方法尚未处理的派生类型的那些方面。为了最大程度地继承多功能性,公开公共克隆功能的类应该是sealed
,但派生自一个基类,除了缺少克隆之外,该基类是相同的。与其传递显式可克隆类型的变量,不如采用 type 的参数ICloneable<theNonCloneableType>
。这将允许期望 的可克隆衍生物 Foo
与 的可克隆衍生物一起工作的例程DerivedFoo
,但也允许创建 的不可克隆衍生物Foo
。
由于这个问题的几乎所有答案都不令人满意或显然不适用于我的情况,因此我编写了AnyClone,它完全通过反射实现并解决了这里的所有需求。我无法让序列化在具有复杂结构的复杂场景中工作,并且IClonable
不太理想 - 实际上它甚至没有必要。
[IgnoreDataMember]
使用,支持标准忽略属性[NonSerialized]
。支持复杂的集合、没有设置器的属性、只读字段等。
我希望它可以帮助遇到与我相同的问题的其他人。
我创建了一个可接受的答案版本,它适用于“[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;
}
}
好的,这篇文章中有一些明显的反射示例,但是反射通常很慢,直到你开始正确缓存它。
如果你能正确缓存它,它会在 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);
}
在另一个答案中查看我的帖子中的完整代码
我想你可以试试这个。
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
要克隆您的类对象,您可以使用 Object.MemberwiseClone 方法,
只需将此功能添加到您的课程中:
public class yourClass
{
// ...
// ...
public yourClass DeepCopy()
{
yourClass othercopy = (yourClass)this.MemberwiseClone();
return othercopy;
}
}
然后要执行深度独立复制,只需调用 DeepCopy 方法:
yourClass newLine = oldLine.DeepCopy();
希望这可以帮助。
如果您的对象树是可序列化的,您也可以使用类似这样的东西
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;
}
请注意,此解决方案非常简单,但性能不如其他解决方案。
并确保如果类增长,仍然只会克隆那些字段,这些字段也会被序列化。
映射器执行深度复制。对于对象的每个成员,它都会创建一个新对象并分配其所有值。它在每个非原始内部成员上递归地工作。
我建议您使用最快、目前正在积极开发的产品之一。我建议 UltraMapper https://github.com/maurosampietro/UltraMapper
最短的方法但需要依赖:
using Newtonsoft.Json;
public static T Clone<T>(T source) =>
JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
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);
}
}
这写“再见世界:(”-该字符串是通过引用复制的(不需要)。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);
}
}
这将返回“Hello World”,字符串是按值复制的!https://dotnetfiddle.net/MCHGEL
更多信息可以在博客文章中找到:
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/with-expression
使用 IClonable 接口可以花费多少精力是令人难以置信的——尤其是如果您有繁重的类层次结构。MemberwiseClone 的工作方式也有些奇怪——它甚至不能完全克隆普通的 List 类型的结构。
当然,序列化最有趣的困境是序列化反向引用——例如,你有父子关系的类层次结构。我怀疑二进制序列化程序在这种情况下能否为您提供帮助。(最终会出现递归循环+堆栈溢出)。
我有点喜欢这里提出的解决方案:How do you do a deep copy of an object in .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
}
}
另一个 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;
}
}
}
通用方法在技术上都是有效的,但我只是想添加我自己的注释,因为我们实际上很少需要真正的深度复制,我强烈反对在实际业务应用程序中使用通用深度复制,因为这样你可能会有很多对象被复制然后显式修改的地方,很容易丢失。
在大多数实际情况下,您还希望对复制过程进行尽可能精细的控制,因为您不仅与数据访问框架耦合,而且实际上复制的业务对象很少应该 100% 相同。想想ORM用来识别对象引用的示例referenceId,完整的深拷贝也会复制这个id,所以在内存中对象会有所不同,一旦你将它提交到数据存储区,它就会抱怨,所以你会无论如何都必须在复制后手动修改此属性,如果对象发生更改,您需要在所有使用通用深度复制的地方进行调整。
使用 ICloneable 扩展 @cregox 答案,实际上什么是深拷贝?它只是堆上新分配的对象,与原始对象相同,但占用不同的内存空间,因此,为什么不使用通用克隆器功能而不是创建一个新对象呢?
我个人在我的域对象上使用静态工厂方法的想法。
例子:
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
};
}
}
如果有人正在研究如何构建对象实例化同时保留对复制过程的完全控制,这是我个人非常成功的解决方案。受保护的构造函数也做到了这一点,其他开发人员被迫使用工厂方法,它提供了一个简洁的对象实例化单点,将构造逻辑封装在对象内部。如果需要,您还可以重载该方法并为不同的位置设置多个克隆逻辑。
深度克隆是关于复制状态。对于.net
state意味着fields。
假设有一个层次结构:
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-public
private 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 fields
foreach (var field in type.GetFields(bindingFlags))
field.SetValue(result, field.GetValue(obj));
// for every level of hierarchy
while ((type = type.BaseType) != typeof(object));
return result;
}
}
演示1:
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 = 71
C.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 = 37
C.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 = 38
C.random = 4 B.random = 5 A.random = 79
注意,所有新对象都有随机的random
字段值,但clone
完全匹配image
演示2:
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 #46104728
Subscribed to event of obj #46104728
Event from obj #46104728
Event from obj #46104728
obj #46104728 cloned to obj #12289376
Event from obj #12289376
Event from obj #46104728
注意,事件支持字段也被复制,客户端也订阅了克隆的事件。
这会将一个对象的所有可读和可写属性复制到另一个对象。
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);
在一个基本上应该调用自动复制构造函数的方法中进行重铸怎么样
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>();
}
似乎对我有用
当使用 Marc Gravells protobuf-net 作为序列化程序时,接受的答案需要稍作修改,因为要复制的对象不会被赋予属性[Serializable]
,因此不可序列化,并且克隆方法将引发异常。
我修改它以使用 protobuf-net:
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]
属性的存在并使用 protobufs 自己的格式化程序来序列化对象。
我找到了一种新的方法,那就是 Emit。
我们可以使用 Emit 将 IL 添加到应用程序并运行它。但我不认为这是一个好方法,因为我想完善我写下我的答案。
你应该学习一些 IL 来阅读代码。我将编写可以在类中复制属性的代码。
public static class Clone
{
// ReSharper disable once InconsistentNaming
public 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 except
if (temp.GetAccessors(true)[0].IsStatic)
{
continue;
}
generator.Emit(OpCodes.Ldarg_1);// los
generator.Emit(OpCodes.Ldarg_0);// s
generator.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 太难了,我做不到。
C# 扩展也将支持“not 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()
使用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
like,因为它会默认替换集合值。您现在应该忘记 Json.NET,因为一切都将被新的官方 API 取代。
我不确定这是否适用于私有字段。
我对当前答案做了一些基准测试,发现了一些有趣的事实。
使用 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)
Intel Core i5-6200U CPU 2.30GHz (Skylake),1 个 CPU,4 个逻辑和 2 个物理核心 [主机]:.NET Framework 4.8 (4.8.4400.0),X86 LegacyJIT DefaultJob:.NET Framework 4.8 (4.8.4400.0),X86旧版 JIT
方法 | 意思是 | 错误 | 标准差 | 0代 | 已分配 |
---|---|---|---|---|---|
二进制序列化器 | 220.69 美元 | 4.374 我们 | 9.963 我们 | 49.8047 | 77 KB |
XmlSerializer | 182.72 我们 | 3.619 我们 | 9.405 我们 | 21.9727 | 34 KB |
激活器.CreateInstance | 49.99 美元 | 0.992 我们 | 2.861 我们 | 1.9531 | 3 KB |
我将使用以下简单的方法来实现这一点。只需创建一个抽象类并实现方法以再次序列化和反序列化并返回。
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();
}
对于克隆过程,可以先将对象转换为字节数组,然后再转换回对象。
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();
@Konrad 和 @craastad 的补充,使用内置System.Text.Json
for.NET >5
方法:
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);
}
}
DeepCloner
找到了这个包,与它相比,它似乎更快,并且没有依赖关系。
我知道这个问题和答案在这里停留了一段时间,以下不是完全答案,而是观察,我最近在检查是否确实没有克隆私人时遇到了这个问题(如果我没有,我就不会是我自己;) 当我愉快地复制粘贴@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 name
public 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!
我整个人都想:这到底是什么……所以我抓住了 Newtonsoft.Json Github 回购并开始挖掘。结果是:在反序列化恰好只有一个 ctor 且其参数名称匹配(不区分大小写)公共属性名称的类型时,它们将作为这些参数传递给 ctor。可以在此处和此处的代码中找到一些线索。
底线
我知道这并不常见,示例代码有点滥用,但是,嘿!当我检查灌木丛中是否有龙等着跳出来咬我的屁股时,这让我大吃一惊。;)