我想做类似这样的事情:
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
然后对新对象进行更改,这些更改不会反映在原始对象中。
我很少需要此功能,因此在必要时,我经常会创建一个新对象,然后逐个复制每个属性,但这总让我感觉有更好或更优雅的方法来处理这种情况。
如何克隆或深度复制一个对象,以便可以修改克隆的对象,而不会反映在原始对象中?
我想做类似这样的事情:
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);
}
}
public static T Clone<T>(this T source)
{
// ...
}
objectBeingCloned.Clone();
。
编辑(2015年1月10日)我想重新访问一下这个问题,提到我最近开始使用(Newtonsoft)Json来做这个,它应该更轻便,并避免了[Serializable]标记的开销。(NB @atconway在评论中指出,使用JSON方法无法克隆私有成员)/// <summary>
/// Perform a deep Copy of the object, using Json as a serialization method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (ReferenceEquals(source, null)) return default;
// initialize inner objects individually
// for example in default constructor some list property initialized with some values,
// but in 'source' these items are cleaned -
// without ObjectCreationHandling.Replace default constructor values will be added to result
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
[Serializable]
,那么typeof(T).IsSerializable
也会返回true,不一定要实现ISerializable
接口。 - Daniel Gehriger我需要一个克隆器,可以用于大部分基本数据类型和列表。如果你的对象可以直接转化为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
,但效果一样。 - PierreMemberwiseClone
会进行浅层复制,但相反的MemberwiseClone
不是Clone
;也许应该是DeepClone
,但这种方法不存在。当您通过其ICloneable接口使用对象时,您无法知道基础对象执行哪种克隆。 (XML注释也无法清楚地说明此点,因为您将获得接口注释而不是对象的Clone方法上的注释。)Copy
方法,以完全满足我的需求。经过大量阅读这里提供的许多选项和解决方案,我相信Ian P 的链接已经很好地总结了所有选项(其它所有选项都是那些选项的变体),而最佳的解决方案由问题评论中Pedro77的链接提供。
所以我只会在这里复制那两个参考文献的相关部分。这样我们就可以得到:
首先,这些是我们的全部选项:
Fast Deep Copy by Expression Trees文章还进行了克隆性能比较,包括使用序列化、反射和表达式树的克隆。
Venkat Subramaniam先生(这里是冗余链接)详细解释了为什么。
他的所有文章都围绕着一个例子展开,试图适用于大多数情况,使用3个对象:Person、Brain和City。我们想要克隆一个人,这个人将有自己的大脑但城市相同。您可以想象任何其他上面提到的方法可能
public class Person : ICloneable
{
private final Brain brain; // brain is final since I do not want
// any transplant on it once created!
private int age;
public Person(Brain aBrain, int theAge)
{
brain = aBrain;
age = theAge;
}
protected Person(Person another)
{
Brain refBrain = null;
try
{
refBrain = (Brain) another.brain.clone();
// You can set the brain in the constructor
}
catch(CloneNotSupportedException e) {}
brain = refBrain;
age = another.age;
}
public String toString()
{
return "This is person with " + brain;
// Not meant to sound rude as it reads!
}
public Object clone()
{
return new Person(this);
}
…
}
现在考虑让一个类继承自Person。
public class SkilledPerson extends Person
{
private String theSkills;
public SkilledPerson(Brain aBrain, int theAge, String skills)
{
super(aBrain, theAge);
theSkills = skills;
}
protected SkilledPerson(SkilledPerson another)
{
super(another);
theSkills = another.theSkills;
}
public Object clone()
{
return new SkilledPerson(this);
}
public String toString()
{
return "SkilledPerson: " + super.toString();
}
}
您可以尝试运行以下代码:
public class User
{
public static void play(Person p)
{
Person another = (Person) p.clone();
System.out.println(p);
System.out.println(another);
}
public static void main(String[] args)
{
Person sam = new Person(new Brain(), 1);
play(sam);
SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
play(bob);
}
}
输出的结果将是:
This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e
需要注意的是,如果我们保持对对象数量的计数,那么这里实现的克隆将会正确地保留对象数量的计数。
ICloneable
用于公共成员。"因为调用方无法依赖 Clone
方法执行可预测的克隆操作,我们建议不要在公共 API 中实现 ICloneable
。" 但是,根据您链接文章中Venkat Subramaniam 的解释,我认为在这种情况下使用 ICloneable
是有意义的,只要创建 ICloneable
对象的人深刻理解哪些属性应该进行深拷贝和浅拷贝(例如深拷贝 Brain,浅拷贝 City)。 - BateTech我更喜欢使用拷贝构造函数而非克隆。这样意图更加清晰。
这是一个简单的扩展方法,可以复制所有公共属性。适用于任何对象,不需要将类标记为[Serializable]
。也可以扩展到其他访问级别。
public static void CopyTo( this object S, object T )
{
foreach( var pS in S.GetType().GetProperties() )
{
foreach( var pT in T.GetType().GetProperties() )
{
if( pT.Name != pS.Name ) continue;
( pT.GetSetMethod() ).Invoke( T, new object[]
{ pS.GetGetMethod().Invoke( S, null ) } );
}
};
}
CloneExtensions
library的项目。它使用表达式树运行时代码编译生成简单的赋值操作来执行快速、深度克隆。
如何使用?
不需要自己编写大量的字段和属性之间的赋值语句来定义Clone
或Copy
方法,可以使用Expression Tree让程序自动完成。标记为扩展方法的GetClone<T>()
方法允许您在实例上轻松调用它:var newInstance = source.GetClone();
CloningFlags
枚举来选择从源
复制到newInstance
的内容:var newInstance
= source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
可以克隆什么?
以下类/结构成员在内部进行克隆:
速度如何?
该解决方案比反射快,因为成员信息只需要在第一次为给定类型 T
使用 GetClone<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;
}
这不是很像你为 List<int>
编写自己的 Clone
方法吗?
如果您已经使用第三方应用程序,如ValueInjector或Automapper,则可以执行以下操作:
MyObject oldObj; // The existing object to clone
MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
使用这种方法,您无需在对象上实现ISerializable
或ICloneable
。这在MVC/MVVM模式中很常见,因此已经创建了像这样的简单工具。
请参阅GitHub上的ValueInjecter深拷贝示例。
我在使用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);
}
}