一、概念
把一个对象复制为另一个对象,有深挎贝(也叫深表复制,deep copy)和浅挎贝(也叫浅表复制,shallow copy)之分,而两种方式在值类型的对象和引用类型的对象中又有所区别。对于值类型对象而言,无论是深挎贝还是浅挎贝,效果都是同样的——即复制对象的值本身到新对象中,对原对象的值的修改不会影响到新对象。而对于引用对象而言,如果是浅挎贝,则只是复制对象的引用,因此,对原对象的值的修改,将会影响到新对象的值,因为新对象和原对象的引用都指向同一个内存位置;而如果是深挎贝,则是将原对象的值(不是引用)复制一份,并使新对象的引用指向它,因此,对原对象值的修改,不会影响到新对象中的值,因为两者已经指向不同的位置。
二、实现
对于值对象来说,深挎贝和浅挎贝可以通过赋值操作(“=”运算符)来实现。对于值对象而言,浅挎贝的实现一般通过Object类的MemberwiseClone方法来完成;深挎贝则有两种不同的方法:
(1)创建一个新的对象,并对这个对象的所有成员依次赋值;
(2)使用内存流来序列化和反序列化对象。
以下是第(2)种示例的代码:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleApplication1
{
[Serializable]
class A : ICloneable
{
public int i = 0;
public int[] arr = { 1, 2, 3 };
/// <summary>
/// 使用MemberwiseClone执行浅挎贝
/// </summary>
/// <returns></returns>
public A Clone1()
{
//调用MemberwiseClone创建一个浅表副本,具体来说就是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。
//如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象
return this.MemberwiseClone() as A;
}
/// <summary>
/// 执行深挎贝
/// </summary>
/// <returns></returns>
public A Clone2()
{
//创建内存存储区
MemoryStream memoryStream = new MemoryStream();
//以二进制的格式序列化对象为流
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, this);
memoryStream.Position = 0;
//反序列化
return binaryFormatter.Deserialize(memoryStream) as A;
//其他深挎贝的方式,是创建一个新的对象,并依次给这个对象赋值
}
public object Clone()
{
return this.MemberwiseClone();
}
}
class Program
{
static void Main(string[] args)
{
A a = new A();
a.i = 10;
a.arr = new int[]{8,9,10};
A b1 = a.Clone1();
A b2 = a.Clone2();
Console.WriteLine("a.i={0},b1.i={1},b2.i={2}", a.i.ToString(), b1.i.ToString(), b2.i.ToString());
Console.WriteLine("a.arr[0]={0},b1.arr[0]={1},b2.arr[0]={2}", a.arr[0].ToString(), b1.arr[0].ToString(), b2.arr[0].ToString());
//修改原始类a的值
a.i = 20;
a.arr[0] = 11;
//对于a修改后,浅挎贝后,b1类中,值类型i的值未变,值为10;引用类型的arr的值发生改变,为11;
//深挎贝后,b2类中值类型的值未变,i-10;引用类值的值也未改变,为arr[0]=8。
Console.WriteLine("shallow copy,a.i={0},b1.i={1}", a.i.ToString(), b1.i.ToString());
Console.WriteLine("shallow copy,a.arr[0]={0},b1.arr[0]={1}", a.arr[0].ToString(), b1.arr[0].ToString());
Console.WriteLine("deep copy,a.i={0},b2.i={1}", a.i.ToString(), b2.i.ToString());
Console.WriteLine("deep copy,a.arr[0]={0},b2.arr[0]={1}", a.arr[0].ToString(), b2.arr[0].ToString());
Console.ReadLine();
}
}
}
三、应用
1、对于值类型来讲,建议都不需要支持ICloneable接口,使用默认的赋值操作就可以了;
2、对于引用类型来说,如果要选择实现ICloneable接口,则要明确实现类中是实现了深挎贝还是浅挎贝;
3、当在一个类中实现ICloneable接口时,此类的每一个派生类(包括派生类的派生类)都必须显式地实现Clone方法。