提问人:NakedBrunch 提问时间:9/17/2008 最后编辑:Vivek NunaNakedBrunch 更新时间:6/26/2023 访问量:1005275
深度克隆对象
Deep cloning objects
问:
我想做这样的事情:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
然后对新对象进行未反映在原始对象中的更改。
我通常不需要这个功能,所以当有必要时,我会求助于创建一个新对象,然后单独复制每个属性,但它总是让我觉得有一种更好或更优雅的处理方式。
如何克隆或深拷贝对象,以便可以修改克隆的对象,而不会在原始对象中反映任何更改?
答:
通常,您可以实现 ICloneable 接口并自行实现克隆。 C# 对象具有内置的 MemberwiseClone 方法,该方法执行浅拷贝,可以帮助您解决所有基元的问题。
对于深层拷贝,它无法知道如何自动执行此操作。
评论
- 基本上,你需要实现 ICloneable 接口,然后实现对象结构复制。
- 如果它是所有成员的深层副本,则需要确保(与您选择的解决方案无关)所有子项也是可克隆的。
- 有时你需要注意在这个过程中的一些限制,例如,如果你复制ORM对象,大多数框架只允许一个对象附加到会话,你不能克隆这个对象,或者如果可能的话,你需要关心这些对象的会话附加。
干杯。
评论
我更喜欢复制构造函数而不是克隆。意图更明确。
评论
简短的回答是,从 ICloneable 接口继承,然后实现 .clone 函数。克隆应执行成员复制,并对需要它的任何成员执行深层复制,然后返回生成的对象。这是一个递归操作(它要求要克隆的类的所有成员都是值类型或实现 ICloneable,并且它们的成员是值类型或实现 ICloneable,依此类推)。
有关使用 ICloneable 进行克隆的更详细说明,请查看此文章。
长答案是“视情况而定”。正如其他人所提到的,ICloneable 不受泛型支持,需要对循环类引用进行特殊考虑,并且实际上被某些人视为 .NET Framework 中的“错误”。序列化方法取决于对象是否可序列化,而对象可能不是,并且您可能无法控制。社区中关于哪种是“最佳”做法仍存在很多争论。实际上,没有一个解决方案是针对所有情况的一刀切的最佳实践,就像 ICloneable 最初被解释的那样。
有关更多选项,请参阅此开发人员专区文章(感谢 Ian)。
评论
虽然一种方法是实现 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 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);
}
评论
typeof(T).IsSerializable
[Serializable]
ISerializable
不使用 ICloneable 的原因不是因为它没有泛型接口。不使用它的原因是因为它含糊不清。它不清楚你得到的是浅拷贝还是深拷贝;这取决于实施者。
是的,做一个浅层的副本,但反面不是;也许是不存在的。通过对象的 ICloneable 接口使用对象时,无法知道基础对象执行哪种克隆。(XML 注释不会说得很清楚,因为你会得到接口注释,而不是对象的 Clone 方法上的注释。MemberwiseClone
MemberwiseClone
Clone
DeepClone
我通常做的只是简单地制作一个完全符合我想要的方法。Copy
评论
我想出这个来克服 .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();
}
}
}
好吧,我在Silverlight中使用ICloneable时遇到了问题,但是我喜欢Seralization的想法,我可以Seralize XML,所以我这样做了:
static public class SerializeHelper
{
//Michael White, Holly Springs Consulting, 2009
//[email protected]
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);
}
}
我也看到它通过反思实现。基本上,有一种方法可以遍历对象的成员并将它们适当地复制到新对象中。当它到达引用类型或集合时,我认为它对自己进行了递归调用。反射很昂贵,但效果很好。
复制所有公共属性的简单扩展方法。适用于任何对象,并且不需要类是 。可以扩展到其他访问级别。[Serializable]
public static void CopyTo( this object S, object T )
{
foreach( var pS in S.GetType().GetProperties() )
{
foreach( var pT in T.GetType().GetProperties() )
{
if( pT.Name != pS.Name ) continue;
( pT.GetSetMethod() ).Invoke( T, new object[]
{ pS.GetGetMethod().Invoke( S, null ) } );
}
};
}
评论
下面是一个深层复制实现:
public static object CloneObject(object opSource)
{
//grab the type and create a new instance of that 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;
}
评论
请按照下列步骤操作:
- 定义一个只读属性,该属性返回 和 ,该属性派生自并包含方法。
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
在阅读了大量关于此处链接的许多选项以及此问题的可能解决方案之后,我相信所有选项在 Ian P 的链接中都得到了很好的总结(所有其他选项都是这些选项的变体),并且 Pedro77 在问题评论上的链接提供了最佳解决方案。
因此,我将在此处复制这 2 个参考文献的相关部分。这样我们就可以:
在 C sharp 中克隆对象的最佳方法!
首先,这些都是我们的选择:
- 使用 ICloneable 手动操作,它是浅层的,不是类型安全的
- MemberwiseClone,它使用 ICloneable
- 使用 Activator.CreateInstance 和递归 MemberwiseClone 进行反射
- 序列化,正如 johnc 的首选答案所指出的那样
- 中级语言,我不知道它是如何工作的
- 扩展方法,例如 Havard Straden 的自定义克隆框架
- 表达式树
文章 Fast Deep Copy by Expression Trees 还对 Serialization、Reflection 和 Expression Trees 克隆进行了性能比较。
为什么我选择ICloneable(即手动)
Venkat Subramaniam先生(此处为冗余链接)详细解释了原因。
他的所有文章都围绕着一个试图适用于大多数情况的例子,使用了 3 个对象:人、大脑和城市。我们想克隆一个人,它将有自己的大脑,但同一个城市。您可以想象上述任何其他方法可能带来的所有问题,也可以阅读本文。
这是我对他的结论略有修改的版本:
通过指定后跟类名来复制对象通常会导致代码不可扩展。使用克隆,原型模式的应用,是实现这一目标的更好方法。但是,使用 C#(和 Java)中提供的克隆也可能是相当成问题的。最好提供一个受保护的(非公共)复制构造函数,并从克隆方法调用它。这使我们能够将创建对象的任务委托给类本身的实例,从而提供可扩展性,并且使用受保护的副本构造函数安全地创建对象。
New
希望这个实现可以把事情弄清楚:
public class Person : ICloneable
{
private final Brain brain; // brain is final since I do not want
// any transplant on it once created!
private int age;
public Person(Brain aBrain, int theAge)
{
brain = aBrain;
age = theAge;
}
protected Person(Person another)
{
Brain refBrain = null;
try
{
refBrain = (Brain) another.brain.clone();
// You can set the brain in the constructor
}
catch(CloneNotSupportedException e) {}
brain = refBrain;
age = another.age;
}
public String toString()
{
return "This is person with " + brain;
// Not meant to sound rude as it reads!
}
public Object clone()
{
return new Person(this);
}
…
}
现在考虑让类派生自 Person。
public class SkilledPerson extends Person
{
private String theSkills;
public SkilledPerson(Brain aBrain, int theAge, String skills)
{
super(aBrain, theAge);
theSkills = skills;
}
protected SkilledPerson(SkilledPerson another)
{
super(another);
theSkills = another.theSkills;
}
public Object clone()
{
return new SkilledPerson(this);
}
public String toString()
{
return "SkilledPerson: " + super.toString();
}
}
您可以尝试运行以下代码:
public class User
{
public static void play(Person p)
{
Person another = (Person) p.clone();
System.out.println(p);
System.out.println(another);
}
public static void main(String[] args)
{
Person sam = new Person(new Brain(), 1);
play(sam);
SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
play(bob);
}
}
产生的输出将是:
This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e
请注意,如果我们对对象数量进行计数,则此处实现的克隆将保留正确的对象数量计数。
评论
ICloneable
如果您已经在使用 ValueInjecter 或 Automapper 等第三方应用程序,则可以执行如下操作:
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
使用此方法,您不必在对象上实现或实现。这在 MVC/MVVM 模式中很常见,因此创建了这样的简单工具。ISerializable
ICloneable
请参阅 GitHub 上的 ValueInjecter 深度克隆示例。
我想要一个克隆器,用于非常简单的对象,主要是原始和列表。如果您的对象是开箱即用的 JSON 可序列化,那么此方法将解决问题。这不需要修改或实现克隆类上的接口,只需要像 JSON.NET 这样的 JSON 序列化程序。
public static T Clone<T>(T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
此外,您可以使用此扩展方法
public static class SystemExtension
{
public static T Clone<T>(this T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
}
评论
Newtonsoft.Json.JsonConvert
这会将一个对象的所有可读和可写属性复制到另一个对象。
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);
我刚刚创建了 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>
评论
我创建了一个适用于“[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;
}
}
如何在方法内部重新转换 这基本上应该调用一个自动复制构造函数
T t = new T();
T t2 = (T)t; //eh something like that
List<myclass> cloneum;
public void SomeFuncB(ref List<myclass> _mylist)
{
cloneum = new List<myclass>();
cloneum = (List < myclass >) _mylist;
cloneum.Add(new myclass(3));
_mylist = new List<myclass>();
}
似乎对我有用
评论
若要克隆类对象,可以使用 Object.MemberwiseClone 方法。
只需将此函数添加到您的类中:
public class yourClass
{
// ...
// ...
public yourClass DeepCopy()
{
yourClass othercopy = (yourClass)this.MemberwiseClone();
return othercopy;
}
}
然后要执行深度独立复制,只需调用 DeepCopy 方法:
yourClass newLine = oldLine.DeepCopy();
希望这会有所帮助。
评论
编辑:项目已停产
如果你想真正克隆到未知类型,你可以看看fastclone。
这是基于表达式的克隆,其工作速度比二进制序列化快约 10 倍,并保持完整的对象图完整性。
这意味着:如果您多次引用层次结构中的同一对象,则克隆也将引用单个实例。
无需对要克隆的对象进行接口、属性或任何其他修改。
评论
我喜欢这样的 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));
}
}
如果您有更多内容要复制,请添加它们
如果你的对象树是可序列化的,你也可以使用类似这样的东西
static public MyClass Clone(MyClass myClass)
{
MyClass clone;
XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
using (var ms = new MemoryStream())
{
ser.Serialize(ms, myClass);
ms.Position = 0;
clone = (MyClass)ser.Deserialize(ms);
}
return clone;
}
请注意,此解决方案非常简单,但性能不如其他解决方案。
并确保如果类增长,仍然只有那些被克隆的字段,这些字段也会被序列化。
令人难以置信的是,你可以花多少精力在IClonable接口上 - 特别是如果你有沉重的类层次结构。此外,MemberwiseClone 的工作方式也很奇怪 - 它甚至不能完全克隆普通的 List 类型的结构。
当然,序列化最有趣的困境是序列化回溯引用 - 例如,具有子父关系的.class层次结构。 在这种情况下,我怀疑二进制序列化程序是否能够为您提供帮助。(它最终会得到递归循环 + 堆栈溢出)。
我不知何故喜欢这里提出的解决方案:如何在 .NET(特别是 C#)中对对象进行深度复制?
但是 - 它不支持列表,并补充说,这种支持还考虑了重新父级。 对于我制定的仅父级规则,该字段或属性应命名为“parent”,然后 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
}
}
Q.我为什么会选择这个答案?
- 如果您想要 .NET 能够达到的最快速度,请选择此答案。
- 如果你想要一个非常非常简单的克隆方法,请忽略这个答案。
换句话说,除非您有需要修复的性能瓶颈,否则请选择另一个答案,并且您可以使用分析器来证明这一点。
比其他方法快 10 倍
执行深度克隆的方法是:
- 比任何涉及序列化/反序列化的东西都快 10 倍;
- 非常接近 .NET 能够达到的理论最大速度。
而方法......
为了获得最佳速度,您可以使用 Nested MemberwiseClone 进行深度复制。它的速度几乎与复制值结构的速度相同,并且比 (a) 反射或 (b) 序列化(如本页的其他答案中所述)快得多。
请注意,如果将 Nested MemberwiseClone 用于深层复制,则必须为类中的每个嵌套级别手动实现 ShallowCopy,以及调用所有所述 ShallowCopy 方法以创建完整克隆的 DeepCopy。这很简单:总共只有几行,请参阅下面的演示代码。
以下是代码的输出,显示了 100,000 个克隆的相对性能差异:
- 嵌套结构上的嵌套 MemberwiseClone 为 1.08 秒
- 嵌套类上的嵌套 MemberwiseClone 为 4.77 秒
- 序列化/反序列化 39.93 秒
在类上使用嵌套 MemberwiseClone 的速度几乎与复制结构体一样快,而复制结构体的速度非常接近 .NET 能够达到的理论最大速度。
Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
Create 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 与上述手动编码一样快,甚至更快。我可能不得不看看它们与分析器的比较。
评论
当使用 Marc Gravells protobuf-net 作为序列化程序时,接受的答案需要一些轻微的修改,因为要复制的对象不会被归因,因此不可序列化,并且 Clone 方法将引发异常。
我修改了它以使用 protobuf-net:[Serializable]
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);
}
}
这将检查属性是否存在,并使用 protobufs 自己的格式化程序来序列化对象。[ProtoContract]
好的,这篇文章中有一些明显的反射示例,但是反射通常很慢,直到您开始正确缓存它。
如果您正确缓存它,那么它将在 1000000 秒内深度克隆 4,6 个对象(由 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);
}
在另一个答案中检查我帖子中的完整代码
https://stackoverflow.com/a/34365709/4711853
评论
prop.GetValue(...)
由于我在不同的项目中找不到满足我所有要求的克隆器,因此我创建了一个深度克隆器,可以配置和适应不同的代码结构,而不是调整我的代码以满足克隆器的要求。它是通过向要克隆的代码添加注释来实现的,或者您只需将代码保留为默认行为即可。它使用反射,类型缓存,并基于fasterflect。对于大量数据和高对象层次结构(与其他基于反射/序列化的算法相比),克隆过程非常快。
https://github.com/kalisohn/CloneBehave
也可作为 nuget 包使用:https://www.nuget.org/packages/Clone.Behave/1.0.0
例如:以下代码将深度克隆地址,但仅执行_currentJob字段的浅层复制。
public class Person
{
[DeepClone(DeepCloneBehavior.Shallow)]
private Job _currentJob;
public string Name { get; set; }
public Job CurrentJob
{
get{ return _currentJob; }
set{ _currentJob = value; }
}
public Person Manager { get; set; }
}
public class Address
{
public Person PersonLivingHere { get; set; }
}
Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");
Address adrClone = adr.Clone();
//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);
保持简单,使用AutoMapper,正如其他人提到的,它是一个简单的小库,可以将一个对象映射到另一个对象......若要将一个对象复制到另一个具有相同类型的对象,只需三行代码:
MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);
目标对象现在是源对象的副本。 还不够简单?创建要在解决方案中的任何位置使用的扩展方法:Create an extension method to use anywhere in your solution:
public static T Copy<T>(this T source)
{
T copy = default(T);
Mapper.CreateMap<T, T>();
copy = Mapper.Map<T, T>(source);
return copy;
}
扩展方法可以按如下方式使用:
MyType copy = source.Copy();
评论
代码生成器
我们已经看到了很多想法,从序列化到手动实现到反射,我想提出一种使用 CGbR 代码生成器的完全不同的方法。生成克隆方法具有内存和 CPU 效率,因此比标准 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();
}
最好的方法是实现一个扩展方法,如
public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }
然后通过以下方式在解决方案中的任何位置使用它
var copy = anyObject.DeepClone();
我们可以有以下三种实现:
所有链接的方法都运行良好,并经过了深入测试。
评论
我想你可以试试这个。
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
评论
new MyObject(myObj);
我知道这个问题和答案在这里坐了一段时间,接下来的不是完全的答案,而是观察,我最近在检查是否确实没有克隆私人时遇到了这个问题(如果我没有;)当我愉快地复制粘贴@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 repo,开始挖掘。 结果是:在反序列化恰好只有一个 ctor 的类型并且其参数名称与(不区分大小写)公共属性名称匹配时,它们将作为这些参数传递给 ctor。可以在代码中找到一些线索 这里 和 这里.
底线
我知道这种情况并不常见,示例代码有点滥用,但是嘿!当我检查灌木丛中是否有龙等着跳出来咬我的屁股时,这让我大吃一惊。;)
映射器执行深层复制。对于对象的每个成员,它会创建一个新对象并分配其所有值。它以递归方式处理每个非原始内部成员。
我建议你使用目前最快、最积极开发的之一。 我建议 UltraMapper https://github.com/maurosampietro/UltraMapper
Nuget 包:https://www.nuget.org/packages/UltraMapper/
评论
我找到了一种新的方法,那就是 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 更改它太难了,我做不到。
另一个 JSON.NET 答案。此版本适用于未实现 ISerializable 的类。
public static class Cloner
{
public static T Clone<T>(T source)
{
if (ReferenceEquals(source, null))
return default(T);
var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
}
class ContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => base.CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => base.CreateProperty(f, memberSerialization)))
.ToList();
props.ForEach(p => { p.Writable = true; p.Readable = true; });
return props;
}
}
}
C# 扩展,也支持“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()
通用方法在技术上都是有效的,但我只想补充一点,因为我们实际上很少需要真正的深度复制,我强烈反对在实际的业务应用程序中使用通用的深度复制,因为这样你可能会有很多地方复制对象,然后显式修改, 很容易迷路。
在大多数现实情况下,您还希望对复制过程进行尽可能精细的控制,因为您不仅与数据访问框架耦合,而且在实践中,复制的业务对象很少应该 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
};
}
}
如果有人正在寻找如何构建对象实例化,同时保留对复制过程的完全控制,这是我个人非常成功的解决方案。受保护的构造函数也使它成为现实,其他开发人员被迫使用工厂方法,该方法提供了一个简洁的单点对象实例化,将构造逻辑封装在对象内部。如有必要,您还可以重载该方法,并在不同位置使用多个克隆逻辑。
由于这个问题的几乎所有答案都不令人满意,或者在我的情况下显然不起作用,因此我编写了 AnyClone,它完全通过反思实现并解决了这里的所有需求。我无法让序列化在具有复杂结构的复杂场景中工作,并且不太理想 - 事实上,它甚至不应该是必需的。IClonable
使用 、 支持标准忽略属性。支持复杂的集合、不带 setter 的属性、只读字段等。[IgnoreDataMember]
[NonSerialized]
我希望它能帮助那些遇到与我相同的问题的其他人。
评论
AnyClone
深度克隆是关于复制状态的。对于状态表示字段。.net
假设一个人有一个层次结构:
static class RandomHelper
{
private static readonly Random random = new Random();
public static int Next(int maxValue) => random.Next(maxValue);
}
class A
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";
}
class B : A
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";
}
class C : B
{
private readonly int random = RandomHelper.Next(100);
public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";
}
克隆可以做到:
static class DeepCloneExtension
{
// consider instance fields, both public and non-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
请注意,所有新对象都有 field 的随机值,但与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
请注意,事件支持字段也被复制,客户端也订阅了克隆的事件。
评论
用: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 中不需要 like,因为它将默认替换集合值。您现在应该忘记 Json.NET,因为一切都将被新的官方 API 取代。ObjectCreationHandling.Replace
我不确定这是否适用于私有字段。
DeepCloner:快速、简单、有效的 NuGet 包,用于解决克隆问题
在阅读了所有答案后,我很惊讶没有人提到这个优秀的包:
详细阐述一下它的自述文件,以下是我们在工作中选择它的原因:
- 它可以深拷贝或浅拷贝
- 在深度克隆中,所有对象图都会被维护。
- 在运行时使用代码生成,因为结果克隆速度极快
- 对象由内部结构复制,未调用任何方法或 ctor
- 您不需要以某种方式标记类(如 Serializable-attribute,或实现接口)
- 无需指定克隆的对象类型。对象可以转换为接口或作为抽象对象(例如,您可以将 int 数组克隆为 abstract Array 或 IEnumerable;甚至可以克隆 null 而不会出现任何错误)
- 被克隆的对象没有任何能力来确定他是克隆的(除非使用非常具体的方法)
用法:
var deepClone = new { Id = 1, Name = "222" }.DeepClone();
var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
性能:
README 包含各种克隆库和方法的性能比较:DeepCloner 性能。
要求:
- .NET 4.0 或更高版本或 .NET Standard 1.3 (.NET Core)
- 需要“完全信任”权限集或“反射”权限 (MemberAccess)
评论
免责声明:我是上述软件包的作者。
令我惊讶的是,2019 年这个问题的顶级答案仍然使用序列化或反射。
序列化是有限制的(需要属性、特定的构造函数等),并且非常慢
BinaryFormatter
需要属性,需要无参数构造函数或属性,两者都不能很好地处理只读字段或接口,并且两者都比必要的慢 10-30 倍。Serializable
JsonConverter
表达式树
您可以改用表达式树或 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 倍,比强烈建议的 .BinaryFormatter
评论
ObjectCloner
ObjectCloner.ObjectCloner.DeepClone(someObject)
最短的方法,但需要依赖:
using Newtonsoft.Json;
public static T Clone<T>(T source) =>
JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
对于克隆过程,可以先将对象转换为字节数组,然后再转换回对象。
public static class Extentions
{
public static T Clone<T>(this T obj)
{
byte[] buffer = BinarySerialize(obj);
return (T)BinaryDeserialize(buffer);
}
public static byte[] BinarySerialize(object obj)
{
using (var stream = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, obj);
return stream.ToArray();
}
}
public static object BinaryDeserialize(byte[] buffer)
{
using (var stream = new MemoryStream(buffer))
{
var formatter = new BinaryFormatter();
return formatter.Deserialize(stream);
}
}
}
必须对对象进行序列化,才能进行序列化过程。
[Serializable]
public class MyObject
{
public string Name { get; set; }
}
用法:
MyObject myObj = GetMyObj();
MyObject newObj = myObj.Clone();
创建扩展:
public static T Clone<T>(this T theObject)
{
string jsonData = JsonConvert.SerializeObject(theObject);
return JsonConvert.DeserializeObject<T>(jsonData);
}
并这样称呼它:
NewObject = OldObject.Clone();
对@Konrad的补充,并使用内置的 @craastadSystem.Text.Json
.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);
}
}
C# 9.0 引入了需要 (感谢 Mark Nading) 的关键字。这应该允许非常简单的对象克隆(如果需要,还可以进行突变),使用很少的样板,但只能使用 .with
record
record
你似乎无法通过将类放入泛型中来克隆(按值)类record
;
using System;
public class Program
{
public class Example
{
public string A { get; set; }
}
public record ClonerRecord<T>(T a)
{
}
public static void Main()
{
var foo = new Example {A = "Hello World"};
var bar = (new ClonerRecord<Example>(foo) with {}).a;
foo.A = "Goodbye World :(";
Console.WriteLine(bar.A);
}
}
这写了“Goodbye World :(”- 字符串是通过引用复制的(不需要的)。https://dotnetfiddle.net/w3IJgG
(令人难以置信的是,以上内容在 !https://dotnetfiddle.net/469NJvstruct
)
但是克隆似乎确实可以缩进,按值克隆。record
using System;
public class Program
{
public record Example
{
public string A { get; set; }
}
public static void Main()
{
var foo = new Example {A = "Hello World"};
var bar = foo with {};
foo.A = "Goodbye World :(";
Console.WriteLine(bar.A);
}
}
这将返回“Hello World”,字符串是按值复制的!https://dotnetfiddle.net/MCHGEL
更多信息可以在博客文章中找到:
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/with-expression
评论
record
class
record
我对当前的答案做了一些基准测试,发现了一些有趣的事实。
使用 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 LegacyJIT
方法 | 意味 着 | 错误 | 标准开发 | 第 0 代 | 分配 |
---|---|---|---|---|---|
BinarySerializer 二进制序列化程序 | 220.69 微秒 | 4.374 微秒 | 9.963 微秒 | 49.8047 | 77 |
XmlSerializer (英语) | 182.72 微秒 | 3.619 微秒 | 9.405 微秒 | 21.9727 | 34 KB |
Activator.CreateInstance | 49.99 微秒 | 0.992 微秒 | 2.861 微秒 | 1.9531 | 3 KB |
找到了这个包,与它相比,它似乎更快,并且没有依赖关系。DeepCloner
https://github.com/AlenToma/FastDeepCloner
我将使用以下简单的方法来实现这一点。 只需创建一个抽象类并实现方法,即可再次序列化和反序列化并返回。
public abstract class CloneablePrototype<T>
{
public T DeepCopy()
{
string result = JsonConvert.SerializeObject(this);
return JsonConvert.DeserializeObject<T>(result);
}
}
public class YourClass : CloneablePrototype< YourClass>
…
…
…
并像这样使用它来创建深度副本。
YourClass newObj = (YourClass)oldObj.DeepCopy();
如果您还需要实现浅拷贝方法,此解决方案也很容易扩展。
只需在抽象类中实现一个新方法即可。
public T ShallowCopy()
{
return (T)this.MemberwiseClone();
}
除了这里的一些精彩答案之外,在 C# 9.0 及更高版本中,您可以执行以下操作(假设您可以将类转换为记录):
record Record
{
public int Property1 { get; set; }
public string Property2 { get; set; }
}
然后,只需使用 with 运算符将一个对象的值复制到新对象。
var object1 = new Record()
{
Property1 = 1,
Property2 = "2"
};
var object2 = object1 with { };
// object2 now has Property1 = 1 & Property2 = "2"
我希望这对:)有所帮助
基于 @craastad 的答案,适用于派生类。
在原始答案中,如果调用方正在调用基类对象,则克隆的对象属于基类。但以下代码将返回派生类。DeepCopy
using Newtonsoft.Json;
public static T DeepCopy<T>(this T source)
{
return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source), source.GetType());
}
如果使用 net.core 并且对象是可序列化的,则可以使用
var jsonBin = BinaryData.FromObjectAsJson(yourObject);
然后
var yourObjectCloned = jsonBin.ToObjectFromJson<YourType>();
BinaryData 位于 dotnet 中,因此不需要第三方库。它还可以处理类上的属性是 Object 类型的情况(属性中的实际数据仍然需要可序列化)
在我正在使用的代码库中,我们有一个来自 GitHub 项目 Burtsev-Alexey/net-object-deep-copy 的文件副本。它已经 9 岁了。它奏效了,尽管我们后来意识到对于较大的对象结构来说,它非常慢。ObjectExtensions.cs
相反,我们在 GitHub 项目 jpmikkers/Baksteen.Extensions.DeepCopy 中找到了该文件的分支。以前需要大约 30 分钟才能完成大型数据结构的深度复制操作,现在感觉几乎是瞬间完成的。ObjectExtensions.cs
此改进版本包含以下文档:
用于快速对象克隆的 C# 扩展方法。
这是阿列克谢·布尔采夫(Alexey Burtsev)的深度复印机的速度优化分叉。根据您的用例,这将比原始速度快 2 到 3 倍。它还修复了原始代码中存在的一些错误。与经典的二进制序列化/反序列化深度克隆技术相比,此版本的速度大约快七倍(对象包含的数组越多,加速系数就越大)。
加速是通过以下技术实现的:
- 缓存对象反射结果
- 不要深度复制原语或不可变的结构和类(例如枚举和字符串)
- 为了提高参考的局部性,在内部循环中处理“快速”维度或多维数组
- 使用已编译的 lamba 表达式调用 MemberwiseClone
如何使用:
using Baksteen.Extensions.DeepCopy; ... var myobject = new SomeClass(); ... var myclone = myobject.DeepCopy()!; // creates a new deep copy of the original object
注意:仅当在项目中启用了可为 null 的引用类型时,才需要感叹号(空容错运算符)
这是一种使用匿名或对象实例进行克隆的方法
public static T CloneJson<T>(T source)
{
if (ReferenceEquals(source, null))
{
return default;
}
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
}
private object Clone(object instance)
{
Type type = instance.GetType();
MethodInfo genericMethod = this.GetType().GetMethod(nameof(CloneJson));
MethodInfo cloneMethod = genericMethod.MakeGenericMethod(type);
object clone = cloneMethod.Invoke(null, new object[] { instance });
return clone;
}
评论
clone
this