浅谈C#浅拷贝和深拷贝

近来爱上一本书《编写高质量代码,改善C#程序的157个建议》,我想很多人都想编写高质量的代码,因为我们不仅仅是码农,更是一名程序员。

从今天开始,我将每天和大家分享这本书中的内容,并加上自己的理解,希望可以帮助到更多和我一样盲目的程序员们。

今天我们谈谈C#中的对象拷贝问题;

所谓的对象拷贝,其实就是为对象创建副本,C#中将拷贝分为两种,分别为浅拷贝和深拷贝;

所谓浅拷贝就是将对象中的所有字段复制到新的副本对象中;浅拷贝对于值类型与引用类型的方式有区别,值类型字段的值被复制到副本中后,在副本中的修改不会影响源对象对应的值;然而对于引用类型的字段被复制到副本中的却是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值被修改后,源对象的值也将被修改。

深拷贝也同样是将对象中的所有字段复制到副本对象中,但是,无论对象的值类型字段或者引用类型字段,都会被重新创建并复制,对于副本的修改,不会影响到源对象的本身;

当然,无论是哪种拷贝,微软都建议使用类型继承ICloneable接口的方式明确告诉调用者,该对象是否可用被拷贝。当然了,ICloneable接口只提供了一个声明为Clone的方法,我们可以根据需求在Clone的方法内实现浅拷贝或者是深拷贝,下面我们进行一段浅拷贝的案例,代码如下

     class Student : ICloneable
{
public string IDCode { get; set; } public int Age { get; set; } public Grent Grent { get; set; } #region 拷贝主体
public object Clone()
{
return this.MemberwiseClone();
//throw new NotImplementedException();
}
#endregion } class Grent
{
public string Name { get; set; } public override string ToString()
{
return this.Name;
}
}

调用

Student stu1 = new Student()
{
IDCode = "lily",
Age = ,
Grent = new Grent() { Name="五年三班"}
};
Student stu2 = stu1.Clone() as Student;
if (stu2 == null) {
Console.WriteLine("转换失败");
Console.ReadKey();
return;
} Console.WriteLine(stu2.IDCode);
Console.WriteLine(stu2.Age);
Console.WriteLine(stu2.Grent.ToString()); Console.WriteLine("重新为stu1赋值");
stu1.IDCode = "Anagle";
stu1.Age += ;
stu1.Grent.Name = "六年二班";
Console.WriteLine(stu2.IDCode);
Console.WriteLine(stu2.Age);
Console.WriteLine(stu2.Grent.ToString());
Console.ReadKey();

输出结果为:

lily

24

五年三班

重新为stu1赋值

lily

24

六年二班

aaarticlea/png;base64,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" alt="" />

这里我们需要注意一点Student中的IDCode属性是string类型,理论上string类型是引用类型,但是由于该引用类型的特殊性,Object.MemberwiseClone方法仍旧为他创建了副本,也就是说,在浅拷贝过程中,我们应该将字符串看成值类型;

因为Student中的Grent是引用类型,所以在stu1中的Grent的Name被改变的同时,副本stu2中的Grent的Name也同样被改变。

Student的深拷贝有多钟实现方法,最简单的方法是手动对字段诼个赋值,但是这种方法容易出错,也就是说,如果类型的字段发生变化或有增减时,那么该拷贝方法也就要发生相应的变化,所以,建议使用序列化的形式来进行深拷贝。实现代码如下

    [Serializable]
class Student : ICloneable
{
public string IDCode { get; set; } public int Age { get; set; } public Grent Grent { get; set; } #region 拷贝主体
/// <summary>
/// 深度拷贝
/// </summary>
/// <returns></returns>
public Student DeepClone() {
using (Stream objectStream = new MemoryStream()) {
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(objectStream, this);
objectStream.Seek(,SeekOrigin.Begin);
return formatter.Deserialize(objectStream) as Student;
}
} public object Clone()
{
return this.MemberwiseClone();
}
#endregion }

调用DeepClone方法

Student stu1 = new Student()
{
IDCode = "lily",
Age = ,
Grent = new Grent() { Name="五年三班"}
};
Student stu2 = stu1.DeepClone() as Student;
if (stu2 == null) {
Console.WriteLine("转换失败");
Console.ReadKey();
return;
} Console.WriteLine(stu2.IDCode);
Console.WriteLine(stu2.Age);
Console.WriteLine(stu2.Grent.ToString()); Console.WriteLine("重新为stu1赋值");
stu1.IDCode = "Anagle";
stu1.Age += ;
stu1.Grent.Name = "六年二班";
Console.WriteLine(stu2.IDCode);
Console.WriteLine(stu2.Age);
Console.WriteLine(stu2.Grent.ToString());
Console.ReadKey();

输出结果为:

lily

24

五年三班

重新为stu1赋值

lily

24

五年三班

aaarticlea/png;base64,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" alt="" />

这次我们看到的结果已经很明显了,深拷贝后,源对象的重新赋值与修改将不再导致副本对象被修改,这样将很好的控制住引用类型的拷贝问题。

DEMO源码下载

上一篇:LeetCode(83): 删除排序链表中的重复元素


下一篇:java基础(十七)----- 浅谈Java中的深拷贝和浅拷贝 —— 面试必问