我想做一些事情,比如:
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接口并自己实现克隆。C#对象有一个内置的MemberwiseColone方法,该方法执行浅层复制,可以帮助您处理所有原语。
对于深度复制,它无法知道如何自动执行。
其他回答
这个方法为我解决了问题:
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);
简单的答案是从ICloneable接口继承,然后实现.clone函数。克隆应该按成员进行复制,并对任何需要它的成员执行深度复制,然后返回结果对象。这是一个递归操作(它要求您要克隆的类的所有成员都是值类型或实现ICloneable,并且它们的成员是值类型或者实现IClonea,依此类推)。
有关使用ICloneable克隆的详细说明,请参阅本文。
长篇大论的答案是“视情况而定”。正如其他人所提到的,ICloneable不受泛型支持,需要对循环类引用进行特殊考虑,并且实际上被一些人视为.NET Framework中的“错误”。序列化方法取决于对象是否可序列化,而这些对象可能不可序列化,并且您可能无法控制。对于哪种做法是“最佳”做法,社会上仍有很多争论。事实上,没有一个解决方案是适用于所有情况的一刀切的最佳实践,就像ICloneable最初被解释的那样。
请参阅本开发者角文章,了解更多选项(归功于Ian)。
在大量阅读了这里链接的许多选项以及这个问题的可能解决方案之后,我相信所有选项都在Ian P的链接中得到了很好的总结(所有其他选项都是这些选项的变体),Pedro77的问题评论链接提供了最佳解决方案。
所以我将在这里复制这两个参考文献的相关部分。这样我们就可以:
在C sharp中克隆对象的最佳方法!
首先,这些是我们的所有选择:
手动使用ICloneable,它是浅层的,不属于类型安全MemberwiseColone,它使用ICloneable使用Activator.CreateInstance和递归MemberwiseClone进行反射正如johnc的首选答案所指出的序列化中级语言,我不知道它是如何工作的扩展方法,如Havard Straden的自定义克隆框架表达式树
通过表达式树进行快速深度复制一文还对通过序列化、反射和表达式树进行克隆的性能进行了比较。
为什么选择ICloneable(即手动)
Venkat Subramaniam先生(此处为冗余链接)详细解释了原因。
他的所有文章都围绕着一个例子,试图适用于大多数情况,使用三个对象:人、大脑和城市。我们想克隆一个人,这个人将拥有自己的大脑,但同样的城市。你可以想象上面任何其他方法都会带来的所有问题,也可以阅读本文。
这是我对他的结论稍作修改的版本:
通过指定New后跟类名来复制对象通常会导致代码不可扩展。使用克隆(原型模式的应用)是实现这一点的更好方法。然而,使用C#(和Java)中提供的克隆也会有很大的问题。最好提供一个受保护的(非公共的)复制构造函数,并从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
注意,如果我们对对象的数量进行计数,这里实现的克隆将保持正确的对象数量计数。
通常,您实现ICloneable接口并自己实现克隆。C#对象有一个内置的MemberwiseColone方法,该方法执行浅层复制,可以帮助您处理所有原语。
对于深度复制,它无法知道如何自动执行。
不使用ICloneable的原因不是因为它没有通用接口。不使用它的原因是因为它很模糊。它不清楚你得到的是浅拷贝还是深拷贝;这取决于实施者。
是的,MemberwiseColone生成一个浅拷贝,但MemberwiseClone的反面不是克隆;可能是DeepClone,但它并不存在。当您通过对象的ICloneable接口使用对象时,您无法知道底层对象执行哪种克隆。(而且XML注释不会很清楚,因为您将得到接口注释,而不是对象的Clone方法上的注释。)
我通常只做一个Copy方法,它完全符合我的要求。