不忘本~浅拷贝和深拷贝
Clone 分浅拷贝和深拷贝
两者的区别:当对象中的属性是引用类型时,即类product中还有对类category的引用时,浅拷贝与深拷贝才有区别。
浅拷贝:复制的是category的引用,当原对象改变时,会影响新对象,反之,亦然。
深拷贝:复制的是category的值,即,它会new一个新的category,然后将原来的category元素的值复制到新元素,即它与原对象没有任何关系,只是内容相同。
如何实现对象的拷贝功能:
继承接口ICloneable实现Clone方法
ICloneable.Clone本身并不能区分(Deep or Shallow)你可以在Clone中调用MemberwiseClone来实现一个ShallowClone也可以自己来实现一个DeepClone。不过按照microsoft的建议当实现ICloneable的时候是准备用来实现一个DeepClone。
ICloneable.Clone返回类型是object
其中MemberwiseClone的复制原理是值类型按位复制,引用类型复制对象的引用。这里有一个要注意的就是String类型,虽然是引用类型,不过在这里表现上和值类型是一样的,在Clone的时候就当作值类型来看待好了。
实现Clone的方法:
1. 手工克隆
一个能够保证对象完全按照你所想的那样进行克隆的方式是手工克隆对象的每一个域(field)。这种方式的缺点是麻烦而且容易出错:如果你在类中增加了一个域,你很可能会忘记更新Clone方法。还要在克隆引用对象指向原始对象的时候,注意避免无限循环引用。下面是一个进行深拷贝的简单例子:
2. 使用MemberWiseClone方法
MemberWiseClone是 Object类的受保护方法,能够通过创建一个新对象,并把所有当前对象中的非静态域复制到新对象中,从而创建一个浅拷贝。对于值类型的域,进行的是按位拷贝。对于引用类型的域,引用会被赋值而引用的对象则不会。因此,原始对象及其克隆都会引用同一个对象。注意,这种方法对派生类都是有效的,也就是说,你只需在基类中定义一次Clone方法。下面是一个简单的例子:
1 public class Person : ICloneable 2 { 3 public string Name; 4 public Person Spouse; 5 public object Clone() 6 { 7 return this.MemberwiseClone(); 8 } 9 }
3. 用反射进行克隆
用反射进行克隆是使用Activator.CreateInstance方法来创建一个相同类型的新对象,然后用反射对所有域进行浅拷贝。这种方法的优点是它是全自动的,不需要在对象中添加或删除成员的时候修改克隆方法。另外它也能被写成提供深拷贝的方法。缺点是使用了反射,因此会比较慢,而且在部分受信任的环境中是不可用的。示例代码
4. 使用序列化进行克隆
克隆一个对象的最简单的方法是将它序列化并立刻反序列化为一个新对象。和反射方法一样,序列化方法是自动的,无需在对对象成员进行增删的时候做出修改。缺点是序列化比其他方法慢,甚至比用反射还慢,所有引用的对象都必须是可序列化的(Serializable)。另外,取决于你所使用的序列化的类型(XML,SOAP,二进制)的不同,私有成员可能不能像期望的那样被克隆。示例代码在这里,这里和这里。
5. 使用IL进行克隆
一种罕见的解决方案是使用IL(中间语言)来进行对象克隆。这种方式创建一个动态方法(DynamicMethod),获取中间语言生成器(ILGenerator),向方法中注入代码,把它编译成一个委托,然后执行这个委托。委托会被缓存,因此中间语言只在初次克隆的时候才会生成,后续的克隆都不会重新生成一遍。尽管这种方法比使用反射快,但是这种方法难以理解和维护。示例代码
6. 使用扩展方法进行克隆
Havard Stranden用扩展方法(extention method)创建了一个自定义的克隆框架。这个框架能够创建对象及其引用的对象的深拷贝,不管对象结构有多复杂。缺点是,这是一个不提供源代码的自定义框架(更新:现在已经包括源代码了,参见本文评论),并且它不能在不使用无参数构造器的时候,拷贝由私有方法创建的对象。另一个问题,也是所有自动化的深克隆方法共有的问题是,深拷贝通常需要灵活地处理不能进行简单自动化特殊情况(例如未受管理的资源)。