18.12 数组的浅复制和深复制
在讲到数组的浅复制和深复制之前,首先要了解什么是浅复制和深复制。
❑浅复制:将原对象中的所有字段逐个复制到一个新对象,如果字段是值类型,则只是简单地复制一个副本到新对象,改变新对象中的值类型字段的值不会影响原对象,因为复制的是副本;如果字段是引用类型,则复制的是引用,改变目标对象中引用类型字段的值将会影响原对象,因为引用类型的字段在新对象和原对象中,指向的都是同一个对象。
❑深复制:深复制与浅复制不同的是对于引用类型字段的处理,深复制将会在新对象中创建引用类型字段引用的所有对象,而不仅仅是复制引用。也就是说,此时字段的引用和原始对象对应字段的引用不同,我们可以随意改变新对象中引用的任何对象,不需要担心会影响原对象中对应字段的内容。
了解了浅复制和深复制,还要知道一点,不只是数组才有浅复制和深复制,对象克隆是Object对象提供的功能,也就是说,任何一个对象都可以对自身进行浅复制和深复制,只不过本节学习的只是数组的浅复制和深复制罢了。
那么,回到数组,如果对数组执行浅复制,则仅复制数组的元素,如果元素是值类型,则复制元素的值;如果是引用类型,则只复制元素的引用而不复制这些引用所指向的位于堆中的对象。新数组中的引用与原数组中的引用指向的是相同的对象。比较而言,深复制操作会复制元素以及该元素直接或间接引用的一切对象。
对数组执行浅复制有两种方法,一种是使用CopyTo,另一种是使用Clone。由表18-1可知,它们都属于实例方法,其中Clone方法继承自System.Object对象,返回一个新的数组对象,不过需要显式类型转换,因为Clone方法返回的是Object类型的对象。CopyTo方法可以将一个一维数组的所有元素复制到一个已有的数组实例,这一点是和Clone方法之间的一个重要区别。代码清单18-17演示了使用Clone方法进行浅复制。
代码清单18-17 浅复制示例
namespace ProgrammingCSharp4
{
class Person
{
public int Age{get;set;}
public override string ToString()
{
return string.Format(“年龄:{0}”,Age);
}
}
class ArraySample
{
public static void Main()
{
Person[]array1=new Person[3];
int[]array2=new int[3]{1,2,3};
for(int i=0;i<array1.Length;i++)
{
array1[i]=new Person{Age=10*(i+1)};
}
Print(“数组array1的元素:”);
PrintArray(array1);//打印array1数组的元素内容
//执行浅复制,生成新的数组array3
Person[]array3=(Person[])array1.Clone();
for(int i=0;i<array3.Length;i++)
{
array3[i].Age=20*(i+1);
}
Print(“对新数组array3的元素进行重新赋值后的array 1的元素:”);
PrintArray(array1);//对array3数组的元素数据进行修改后
}
//输出数组的元素内容
public static void PrintArray(Person[]persons)
{
foreach(Person person in persons)
{
System.Console.WriteLine(person);
}
System.Console.WriteLine();
}
//打印字符串到控制台
public static void Print(object obj)
{
System.Console.WriteLine(obj);
System.Console.WriteLine();
}
}
}
上述代码的运行结果为:
数组array1的元素:
年龄:10
年龄:20
年龄:30
对新数组array3的元素进行重新赋值后的array 1的元素:
年龄:20
年龄:40
年龄:60
可见,对于array3数组的修改影响了array1数组的元素数据,这说明array3只是复制了array1数组元素中的引用,而不包括引用所引用的数据。因此修改array1和array3中的任何一个都会影响到另一个,它们的元素引用指向相同,如图18-21所示。
图 18-21 数组的浅复制
.NET Framework没有提供对深复制的原生API支持,要对数组进行深复制,使用“序列化/反序列化”是个不错的选择,如代码清单18-18所示。
代码清单18-18 使用序列化/反序列化对数组进行深复制
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace ProgrammingCSharp4
{
[Serializable]//标识Person类为可序列化
class Person
{
public int Age{get;set;}
public override string ToString()
{
return string.Format(“年龄:{0}”,Age);
}
}
class ArraySample
{
public static void Main()
{
Person[]array1=new Person[3];
int[]array2=new int[3]{1,2,3};
for(int i=0;i<array1.Length;i++)
{
array1[i]=new Person{Age=10*(i+1)};
}
Print(“数组array1的元素:”);
PrintArray(array1);
//序列化
MemoryStream s=new MemoryStream();
BinaryFormatter f=new BinaryFormatter();
f.Serialize(s,array1);
s.Position=0;
//反序列化
Person[]array3=(Person[])f.Deserialize(s);
for(int i=0;i<array3.Length;i++)
{
array3[i].Age=20*(i+1);
}
Print(“对新数组array3的元素进行重新赋值后的array 3的元素:”);
PrintArray(array3);
Print(“对新数组array3的元素进行重新赋值后的array 1的元素:”);
PrintArray(array1);
}
public static void PrintArray(Person[]persons)
{
foreach(Person person in persons)
{
System.Console.WriteLine(person);
}
System.Console.WriteLine();
}
public static void Print(object obj)
{
System.Console.WriteLine(obj);
System.Console.WriteLine();
}
}
}
上述代码的运行结果为:
数组array1的元素:
年龄:10
年龄:20
年龄:30
对新数组array3的元素进行重新赋值后的array 3的元素:
年龄:20
年龄:40
年龄:60
对新数组array3的元素进行重新赋值后的array 1的元素:
年龄:10
年龄:20
年龄:30
可见,对array3数组的修改并没有影响到array1数组,也就是说,array1数组中的所有引用类型的数据元素都被复制到array3数组中。