C#类型基础----对象复制
有的时候,创建一个对象可能会非常耗时,比如对象需要从远程数据库中获取数据来填充,又或者创建对象需要读取磁盘文件.此时,如果已经有了一个对象,再创建新对象时,可能会采用复制现有对象的方法,而不是重新建一个新的对象.本次内容,咱们就一起探讨一下关于对象的复制.
浅度复制和深度复制是以如何复制对象的成员来区分的.对象的成员有可能是值类型,有可能是引用类型.当对对象进行一个浅度复制的时候,对于值类型成员,会赋值其本身(值类型变量本身包含了所有数据,复制时进行按位复制);对于引用类型成员(注意它实际上只是一个对象引用,指向了堆上的对象实例),仅仅复制引用,而不是在堆上重新创建对象.因此,浅度复制结果就是:新对象的引用成员和复制对象的引用成员指向了同一个对象.
继续上面的例子,如果想要进行复制的对象(RefLine)是这样定义的,(为了避免翻看前面的代码,这里把代码再贴过来):
public class RefPoint
{
public int x;
public RefPoint(int x)
{
this.x = x;
}
}
public struct ValPoint
{
public int x;
public ValPoint(int x)
{
this.x = x;
}
}
public class RefLine
{
public RefPoint rPoint;
public ValPoint vPoint;
public RefLine(RefPoint rPoint, ValPoint vPoint)
{
this.rPoint = rPoint;
this.vPoint = vPoint;
}
}
先创建一个想要复制的对象
RefPoint rPoint = new RefPoint(1);
ValPoint vPoint = new ValPoint(1);
RefLine line = new RefLine(rPoint, vPoint);
该对象的实例效果如下所示:
那么当对此对象进行复制时,就会向下图这样(newLine是指向新复制的对象的指针,在代码中体现为一个引用类型的变量).
按照这个定义,在会议上面讲到的内容,可以推出一个结论:当复制一个结构类型成员的时候,直接创建一个新的结构类型变量,然后对它赋值,就相当于进行了一个浅度复制,也可以认为结构类型隐式地实现了浅度复制.如果将上面的RefLine定义为一个结构(Struct),结构类型为ValLine,而不是一个类,那么对它进行浅度复制就可以这样:
ValLine newLine=line;
如下图所示:
现在差不多应该能清楚啥叫浅度复制了吧,知道了如何对结构进行浅度复制.那么如何对一个引用类型实现浅度复制呢?在.NET Framework中,有一个ICloneable接口,我们可以实现这个接口来进行浅度复制.这个接口只要求实现一个Clone(),它返回当前对象的副本.我们并不需要自己实现这个方法,在System.Object基类中,有一个保护的MemberwiseClone()方法,它便用于进行浅度复制.所以,对于引用类型,要想实现浅度复制,只需要调用这个方法就可以了:
public object Clone()
{
return MemberwiseClone();
}
其实到现在大概可能已经想到啥叫深度复制了,深度复制就是将引用成员指向的对象也进行复制.实际的过程是创建新的引用成员指向的对象,然后复制对象中所包含的数据.
深度复制可能会变得非常复杂,因为引用成员指向的对象可能包含另一个引用类型成员,最简单的例子就是一个线性链表.
如果一个对象的成员包含了对于线性链表结构的一个引用,浅度复制只复制了对头节点的引用,深度复制则会复制链表本身,并复制每个节点上的数据.
浅拷贝就是成员数据之间的一一赋值:把值一一赋给要拷贝的值。但是可能会有这样的情况:对象还包含资源,这里的资源可以值堆资源,或者一个文件...当值拷贝的时候,两个对象就有用共同的资源,同时对资源可以访问,这样就会出问题。深拷贝就是用来解决这样的问题的,它把资源也赋值一次,使对象拥有不同的资源,但资源的内容是一样的。对于堆资源来说,就是在开辟一片堆内存,把原来的内容拷贝。
如果你拷贝的对象中引用了某个外部的内容(比如分配在堆上的数据),那么在拷贝这个对象的时候,让新旧两个对象指向同一个外部的内容,就是浅拷贝;如果在拷贝这个对象的时候为新对象制作了外部对象的独立拷贝,就是深拷贝
引用和指针的语义是相似的,引用是不可改变的指针,指针是可以改变的引用。其实都是实现了引用语义。
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。