我想做的事情如下:
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
界面(描述 这里,所以我不会反刍),这是我发现的一个很好的深度克隆对象复印机 代码项目 前一段时间,并将其纳入我们的东西。
正如其他地方所提到的,它确实需要您的对象可序列化。
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
/// <summary>
/// Perform a deep Copy of the object.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T Clone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
}
这个想法是它序列化你的对象,然后将它反序列化为一个新的对象。好处是,当对象过于复杂时,您不必担心克隆所有内容。
并使用扩展方法(也来自最初引用的源):
如果您更喜欢使用新的 扩展方法 在C#3.0中,将方法更改为具有以下签名:
public static T Clone<T>(this T source)
{
//...
}
现在方法调用就变成了 objectBeingCloned.Clone();
。
编辑 (2015年1月10日)以为我会重新审视这一点,提到我最近开始使用(Newtonsoft)Json这样做,它 应该 更轻,并避免[Serializable]标签的开销。 (NB @atconway在评论中指出私有成员不是使用JSON方法克隆的)
/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
// initialize inner objects individually
// for example in default constructor some list property initialized with some values,
// but in 'source' these items are cleaned -
// without ObjectCreationHandling.Replace default constructor values will be added to result
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
我想要一个非常简单的对象,主要是原始和列表的克隆人。如果你的对象是开箱即用的JSON serializable,那么这个方法就可以了。这不需要修改或实现克隆类上的接口,只需要像JSON.NET这样的JSON序列化程序。
public static T Clone<T>(T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
不使用的原因 ICloneable 是 不 因为它没有通用接口。 不使用它的原因是因为它含糊不清。它不清楚你是否得到浅拷贝或深拷贝;这取决于实施者。
是, MemberwiseClone
做一个浅的副本,但相反 MemberwiseClone
不 Clone
;它可能是, DeepClone
,这是不存在的。通过其ICloneable接口使用对象时,您无法知道底层对象执行哪种克隆。 (并且XML注释不会说清楚,因为您将获得接口注释而不是对象的Clone方法上的注释。)
我通常做的只是做一个 Copy
完全符合我想要的方法。
我相信,在阅读了很多与此处相关的选项以及此问题的可能解决方案之后 所有的选项总结得很好 伊恩P.的链接 (所有其他选项都是这些选项的变体),最佳解决方案由提供 Pedro77的链接 关于问题评论。
所以我将在这里复制这两个参考文献的相关部分。这样我们可以:
首先,这些都是我们的选择:
该 文章快速深层复制表达式树 还有序列化,反射和表达树克隆的性能比较。
Venkat Subramaniam先生(此处的冗余链接)详细解释了原因。
他的所有文章都围绕着一个试图适用于大多数情况的例子,使用了3个对象: 人, 脑 和 市。我们想要克隆一个人,它将拥有自己的大脑但是同一个城市。你可以想象上面的任何其他方法可以带来或阅读文章的所有问题。
这是他对他的结论的略微修改版本:
通过指定复制对象
New
后跟类名通常会导致代码无法扩展。使用clone,原型模式的应用,是实现这一目标的更好方法。但是,使用C#(和Java)中提供的克隆也很成问题。最好提供受保护(非公共)的复制构造函数,并从克隆方法中调用它。这使我们能够将创建对象的任务委托给类本身的实例,从而提供可扩展性,并使用受保护的拷贝构造函数安全地创建对象。
希望这个实现可以使事情变得清晰:
public class Person : ICloneable
{
private final Brain brain; // brain is final since I do not want
// any transplant on it once created!
private int age;
public Person(Brain aBrain, int theAge)
{
brain = aBrain;
age = theAge;
}
protected Person(Person another)
{
Brain refBrain = null;
try
{
refBrain = (Brain) another.brain.clone();
// You can set the brain in the constructor
}
catch(CloneNotSupportedException e) {}
brain = refBrain;
age = another.age;
}
public String toString()
{
return "This is person with " + brain;
// Not meant to sound rude as it reads!
}
public Object clone()
{
return new Person(this);
}
…
}
现在考虑从Person派生一个类。
public class SkilledPerson extends Person
{
private String theSkills;
public SkilledPerson(Brain aBrain, int theAge, String skills)
{
super(aBrain, theAge);
theSkills = skills;
}
protected SkilledPerson(SkilledPerson another)
{
super(another);
theSkills = another.theSkills;
}
public Object clone()
{
return new SkilledPerson(this);
}
public String toString()
{
return "SkilledPerson: " + super.toString();
}
}
您可以尝试运行以下代码:
public class User
{
public static void play(Person p)
{
Person another = (Person) p.clone();
System.out.println(p);
System.out.println(another);
}
public static void main(String[] args)
{
Person sam = new Person(new Brain(), 1);
play(sam);
SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
play(bob);
}
}
产生的输出将是:
This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e
注意,如果我们保持对象数量的计数,这里实现的克隆将保持对象数量的正确计数。
我更喜欢复制构造函数到克隆。意图更清晰。
简单的扩展方法来复制所有公共属性。适用于任何物体和 才不是 要求上课 [Serializable]
。可以扩展为其他访问级别。
public static void CopyTo( this object S, object T )
{
foreach( var pS in S.GetType().GetProperties() )
{
foreach( var pT in T.GetType().GetProperties() )
{
if( pT.Name != pS.Name ) continue;
( pT.GetSetMethod() ).Invoke( T, new object[]
{ pS.GetGetMethod().Invoke( S, null ) } );
}
};
}
好吧,我在Silverlight中使用ICloneable时遇到了问题,但是我喜欢seralization的想法,我可以seralize 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);
}
}