1. 概念
1)深拷贝
深拷贝:指的是拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。这样进行深拷贝后的拷贝对象就和源对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人叫张三,然后使用克隆技术以张三来克隆另外一个人叫李四,这样张三和李四就是相互独立的,不管张三缺胳膊还是李四少腿了都不会影响另外一个人。在.NET领域,值类型对象就是典型的例子,值类型赋值内部执行深拷贝,如int, Double以及结构体和枚举等。具体例子如下所示:
int source = 123;
// 值类型赋值内部执行深拷贝
int copy = source;
// 对拷贝对象进行赋值不会改变源对象的值
copy = 234;
// 同样对源对象赋值也不会改变拷贝对象的值
source = 345;
2)浅拷贝
浅拷贝:指的是拷贝一个对象时,仅仅将对象的引用进行拷贝,但是拷贝对象和源对象还是引用同一份地址。此时,其中一个对象的改变都会影响到另一个对象。例如,一个人一开始叫张三,后来改名字为张老三了,可是他们还是同一个人,不管张三缺胳膊还是张老三少腿,都反应在同一个人身上。在.NET中引用类型就是一个例子。如类的类型。具体例子如下示:
该例子内类的属性只有引用类型,因此等号赋值与浅拷贝的效果相同,即对象的改变影响到源对象。
public class Person
{
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
Person sourceP = new Person() { Name = "张三"};
Person copyP = sourceP; // 浅拷贝(等号赋值)
copyP.Name = "张老三"; // 拷贝对象改变Name值
// 结果都是"张老三",因为实现的是浅拷贝,一个对象的改变都会影响到另一个对象
Console.WriteLine("Person.Name: [SourceP: {0}] [CopyP:{1}]",sourceP.Name, copyP.Name);
Console.Read();
}
}
3)应用场景
当一个类内既有值类型属性,又有引用类型属性时;
A.等号赋值实现:
等号赋值相当于把引用地址赋给新对象,此时两个对象共用同一份地址指向的值,任意一个对象改变指向的值,源数据都会受到影响(即值类型的值和引用类型的值都会被改变)。
等号结果,改变对象属性值都会反映到源数据上;
B.浅拷贝实现:
通过实现ICloneable 接口:支持克隆,即用与现有实例相同的值创建类的新实例。
使用MemberwiseClone 方法:创建当前 System.Object 的浅表副本。
class p : ICloneable
{
public object Clone()
{
return this.MemberwiseClone();
}
}
浅拷贝的效果是:对于内部的引用类型的属性(Class 对象和数组),则Copy 一份地址。[ 改变B 时,A也被改变了 ]
而对于其它内置值类型的属性(int / string / enum / struct /object 类型),则Copy 一份值。[ 改变B 时,A不会被改变]
总结:因此可以看出,浅拷贝的实现会对值类型的属性进行深拷贝(两个对象的属性值相互独立),而引用类型(除string类型外)执行浅拷贝,对于一个对象中全是除String类型外的引用类型时,此时等号和浅拷贝是一样的效果,虽然结果一样,但是本质不一样,浅拷贝是有对属性拷贝的过程的,而等号完全没有对属性的拷贝过程
注:string效果类似值类型,表现上被.NET优化为值类型,可查看专题【string不变特性】内容。
4)案例探究
问:浅拷贝对引用类型来说就是拷贝它的引用地址,那么直接用一个等号就完成了,为什么还需要那么麻烦的去实现接口呢?
答:其实这个问题自己试下就知道了,用等号引用的是同一个对象,引用对象的改变都会反映到源对象中,而浅拷贝的实现会对值类型的属性进行深拷贝,而引用类型(除string类型外)执行浅拷贝,对于一个对象中全是除String类型外的引用类型时,此时等号和浅拷贝是一样的效果,虽然结果一样,但是本质不一样,浅拷贝是有对属性拷贝的过程的,而等号完全没有对属性的拷贝过程
classProgram
{
class浅拷贝与等号赋值
{
staticvoid Main(string[] args)
{
DemoClass A = newDemoClass();
//创建实例A的副本 --> 新对象实例B
DemoClass B = (DemoClass)A.Clone();【浅拷贝】
DemoClass B = A;【等号赋值】
//更改对象各个属性值
B._int = 2;
Console.WriteLine(" int\t\t A:{0} B:{1}",A._int, B._int);
B._string = "2";
Console.WriteLine(" string\t A:{0} B:{1}", A._string, B._string);
B._enum = myEnum._2;
Console.WriteLine(" enum\t\t A:{0} B:{1}", (int)A._enum, (int)B._enum);
B._struct._int = 2;
Console.WriteLine(" struct\t A:{0} B:{1}",
A._struct._int, B._struct._int);
B._class._string = "2";
Console.WriteLine(" class\t\t A:{0} B:{1}",
A._class._string, B._class._string);
B.arrInt[0] = 2;
Console.WriteLine(" intArray\t A:{0} B:{1}",
A.arrInt[0],B.arrInt[0]);
B.arrString[0] = "2";
Console.WriteLine(" stringArray\t A:{0} B:{1}",
A.arrString[0], B.arrString[0]);
Console.ReadKey();
}
}
//枚举
publicenummyEnum
{ _1 = 1, _2 = 2 }
//结构体
publicstructmyStruct
{
publicint _int;
public myStruct(int i)
{ _int = i; }
}
//类
classmyClass
{
publicstring _string;
public myClass(string s)
{ _string = s; }
}
//ICloneable:创建作为当前实例副本的新对象。
classDemoClass : ICloneable
{
publicint _int = 1;
publicstring _string = "1";
publicmyEnum _enum = myEnum._1;
publicmyStruct _struct = newmyStruct(1);
publicmyClass _class = newmyClass("1");
//数组
publicint[] arrInt = newint[] { 1 };
publicstring[] arrString = newstring[] { "1" };
//返回此实例副本的新对象
publicobject Clone()
{
//MemberwiseClone:返回当前对象的浅表副本(它是Object对象的基方法)
returnthis.MemberwiseClone();
}
}
}
DemoClass B = A;【等号赋值】
源数据改变,值类型的属性值和引用类型属性值也都全部改变。(等号赋值,指向的源数据,一变全变,没有例外)
DemoClass B = (DemoClass)A.Clone();【浅拷贝】
浅拷贝实现,值类型属性值不变保持相互独立,引用类型(除了string)其他引用的源数据改变,属性值也全部改变(浅拷贝,值类型保持独立,引用类型一变全变,string不可变性)
。