浅复制,指在创建一个对象或结构的副本时,对其值类型字段直接复制值,引用类型字段只复制引用(地址)。
深复制,与浅复制不同的是,对引用类型字段,会创建新的引用,使用新的引用(地址)。
通常直接赋值操作是浅复制操作,若使用深复制,应该实现ICloneable操作,写Clone方法,利用Object.MemberwiseClone方法浅复制后,需要手动编写对其引用对象进行创建新的对象深复制副本。
结构体也是如此,虽然是值类型,若其中包含引用类型字段,想产生一个深复制版本,必须手动实现。
下面是一个测试结构体浅复制的代码:
- using System;
- using System.Linq;
- class Program
- {
- static void Main()
- {
- //~ 以下测试是使结构体s2=s1,然后修改s2的数据,观察对s1的影响
- var s1=new S1();
- s1.Title="I'm s1";
- s1.firstPoint=new Point(0,0);
- s1.otherPoint=new Point[]{new Point(1,1),new Point(2,2)};
- //~ 直接赋值,使s2=s1
- //~ 显示数据完成相同
- Console.WriteLine("TEST 1:");
- var s2=s1;
- Console.WriteLine("s1: "+s1);
- Console.WriteLine("s2: "+s2);
- Console.WriteLine();
- //~ 直接修改s2的值类型数据(firstPoint)和引用类型数据(otherPoint)
- //~ 值类型数据的改变不影响s1,引用类型数据的改变影响s1
- Console.WriteLine("TEST 2:");
- s2=s1;
- Console.WriteLine("s1: "+s1);
- s2.Title="I'm s2";
- s2.firstPoint.x=50;
- s2.firstPoint.y=50;
- s2.otherPoint[0].x=150;
- s2.otherPoint[0].y=150;
- s2.otherPoint[1].x=250;
- s2.otherPoint[1].y=250;
- Console.WriteLine("s2: "+s2);
- Console.WriteLine("s1: "+s1);
- Console.WriteLine();
- //~ 使用new更改变值类型(firstPoint)和引用类型(otherPoint)
- //~ 值类型改变不影响s1,引用类型改变没影响s1
- Console.WriteLine("TEST 3:");
- s2=s1;
- s2.Title="I'm s2";
- Console.WriteLine("s1: "+s1);
- s2.firstPoint=new Point(-99,-98);
- s2.otherPoint=new Point[]{new Point(-1,-1),new Point(-2,-2)};
- Console.WriteLine("s2: "+s2);
- Console.WriteLine("s1: "+s1);
- Console.WriteLine();
- //~ otherPoint是个数组,s2=s1后,创建这个数组的副本
- //~ s2.otherPoint的改变不再影响s1.otherPoint的改变
- Console.WriteLine("TEST 4:");
- s2=s1;
- s2.otherPoint=s1.otherPoint.ToArray(); //创建一个副本
- s2.Title="I'm s2";
- Console.WriteLine("s1: "+s1);
- s2.firstPoint.x=1;
- s2.firstPoint.y=2;
- s2.otherPoint[0].x=3;
- s2.otherPoint[0].y=4;
- s2.otherPoint[1].x=5;
- s2.otherPoint[1].y=6;
- Console.WriteLine("s2: "+s2);
- Console.WriteLine("s1: "+s1);
- //~ 结论:结构体对象中的引用类型,在结构体所在的栈中只保存了引用对象的引用(指针),
- //~ 使用简单赋值操作(s2=s1),不会在托管堆中创建引用对象的副本,它们将共同使用同一
- //~ 引用对象。即,结构体的简单赋值操作(s2=s1)是对栈中结构体数据进行浅复制。
- //~ 注:字符串对象System.String为特别的引用类型,具有值类型行为,因此,结论中的
- //~ 引用对象不包含字符串类型对象。
- }
- }
- //具有引用类型的结构体
- struct S1
- {
- public string Title; //字符串具体值类型行为
- public Point firstPoint; //结体体是值类型
- public Point[] otherPoint; //数组是引用类型
- public override string ToString()
- {
- string otherPointStr=string.Empty;
- if(otherPoint!=null)
- {
- foreach(var item in otherPoint)
- {
- otherPointStr+=item;
- }
- }
- return string.Format("{0} {1}-{2}",Title,firstPoint,otherPointStr);
- }
- }
- //最简单的结构体
- struct Point
- {
- public int x;
- public int y;
- public Point(int x,int y)
- {
- this.x=x;
- this.y=y;
- }
- public override string ToString()
- {
- return string.Format("({0},{1})",x,y);
- }
- }
显示结果:
- >test
- TEST 1:
- s1: I'm s1 (0,0)-(1,1)(2,2)
- s2: I'm s1 (0,0)-(1,1)(2,2)
- TEST 2:
- s1: I'm s1 (0,0)-(1,1)(2,2)
- s2: I'm s2 (50,50)-(150,150)(250,250)
- s1: I'm s1 (0,0)-(150,150)(250,250)
- TEST 3:
- s1: I'm s1 (0,0)-(150,150)(250,250)
- s2: I'm s2 (-99,-98)-(-1,-1)(-2,-2)
- s1: I'm s1 (0,0)-(150,150)(250,250)
- TEST 4:
- s1: I'm s1 (0,0)-(150,150)(250,250)
- s2: I'm s2 (1,2)-(3,4)(5,6)
- s1: I'm s1 (0,0)-(150,150)(250,250)
- >Exit code: 0 Time: 0.225
测试代码中前三个TEST,使s2=s1操作后,s2得到了s1的一个浅复制版本。
TEST 4利用System.Linq提供的扩展方法ToArray创建了数组的一个副本,
使s2是s1的深复制版本,所以演示中对s2的任何修改不会影响s1。
注意:使用ToArray方法,根据情况,增加源数据对象是否为null的判断。