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.12 数组的浅复制和深复制 - 图1

图 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数组中。