说明:此文根据《编写高质量代码改善C#的57个建议》的建议14而写。在自己根据作者的思路进行代码练习的时候,感觉作者并未把定义说清楚全面,于是在定义中加入了自己的理解部分,如定义中的绿色字体部分。
为对象创建副本的技术成为拷贝(或叫克隆)。我们将拷贝分为浅拷贝和深拷贝。
- 浅拷贝:将对象中的所有字段复制到新的对象(此对象为源对象的一个副本,即新对象)中。其中,值类型字段的值复制到副本中后,在副本中的修改不会影响源对象对应的值,在源对象中的修改不会影响到副本(新对象)对应的值。而引用类型的字段被复制到副本中的是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值做修改会影响到源对象本身,在源对象中对引用类型的字段值做修改会影响到副本(新对象)本身。
- 深拷贝:将对象中的所有字段复制到新的对象中。不过,无论对象的字段是值类型字段还是引用类型字段,都会重新创建并复制,对副本的修改不会影响到源对象本身,对源对象的修改不会影响到副本本身。
浅拷贝实现:
class Employee : ICloneable
{
public string IDCode { get; set; }
public int Age { get; set; }
public Department Department { get ; set ; }
public object Clone()
{
return this.MemberwiseClone();
}
}
class Department
{
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
}
若调用代码为:
static void Main(string [] args)
{
Employee mike = new Employee ()
{
IDCode = "NB123" ,
Age = 30,
Department = new Department () { Name = "Dep1" }
};
Employee rose = mike.Clone() as Employee ;
mike.IDCode = "NB456" ;
mike.Age = 60;
mike.Department.Name = "Dep2" ;
Console .WriteLine("mike :" );
Console .WriteLine(mike.IDCode);
Console .WriteLine(mike.Age);
Console .WriteLine(mike.Department);
Console .WriteLine("rose :" );
Console .WriteLine(rose.IDCode);
Console .WriteLine(rose.Age);
Console .WriteLine(rose.Department);
Console .ReadKey();
}
则输出结果为:
此结果验证了定义中:值类型字段在源对象中的修改不会影响到副本(新对象)对应的值,引用类型的字段在源对象中对引用类型的字段值做修改会影响到副本(新对象)本身
若调用代码为:
static void Main(string [] args)
{
Employee mike = new Employee ()
{
IDCode = "NB123" ,
Age = 30,
Department = new Department () { Name = "Dep1" }
};
Employee rose = mike.Clone() as Employee ;
rose.IDCode = "NB456" ;
rose.Age = 60;
rose.Department.Name = "Dep2" ;
Console .WriteLine("mike :" );
Console .WriteLine(mike.IDCode);
Console .WriteLine(mike.Age);
Console .WriteLine(mike.Department);
Console .WriteLine("rose :" );
Console .WriteLine(rose.IDCode);
Console .WriteLine(rose.Age);
Console .WriteLine(rose.Department);
Console .ReadKey();
}
则输出结果为:
此结果验证了定义中:值类型字段在副本中的修改不会影响源对象对应的值,引用类型的字段在在副本中对引用类型的字段值做修改会影响到源对象本身。
注意到Employee 的IDCode字段是string 类型。理论上来说string类型是引用类型,但是由于该引用类型的特殊性,Object.MemberwiseClone()仍旧为其创建了副本,也就是说在浅拷贝中,我们应将string类型当作是值类型。
深拷贝实现:
深拷贝有多种实现方法,最简单的方法是手动对字段逐个进行赋值,但是这种方法容易出错,也就是说,如果类型的字段发生变化或增减,那么该拷贝方法也要发生相应的变化,所以建议使用序列化的形式来进行深拷贝。实现如下:
需添加如下三个命名空间:
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
[ Serializable]
class Employee : ICloneable
{
public string IDCode { get; set; }
public int Age { get; set; }
public Department Department { get ; set ; }
public object Clone()
{
using( Stream sr= new MemoryStream ())
{
IFormatter formatter = new BinaryFormatter ();
formatter.Serialize(sr, this);
sr.Seek(0, SeekOrigin .Begin);
return formatter.Deserialize(sr) as Employee ;
}
}
}
[ Serializable]
class Department
{
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
}
若调用代码为:
static void Main(string [] args)
{
Employee mike = new Employee ()
{
IDCode = "NB123" ,
Age = 30,
Department = new Department () { Name = "Dep1" }
};
Employee rose = mike.Clone() as Employee ;
Console .WriteLine(mike.IDCode);
Console .WriteLine(mike.Age);
Console .WriteLine(mike.Department);
Console .WriteLine("开始改变rose值:" );
rose.IDCode = "NB456" ;
rose.Age = 60;
rose.Department.Name = "Dep2" ;
Console .WriteLine("mike :" );
Console .WriteLine(mike.IDCode);
Console .WriteLine(mike.Age);
Console .WriteLine(mike.Department);
Console .WriteLine("rose :" );
Console .WriteLine(rose.IDCode);
Console .WriteLine(rose.Age);
Console .WriteLine(rose.Department);
Console .ReadKey();
}
输出结果:
此结果验证了定义中:对副本的修改不会影响到源对象本身
若调用代码为:
static void Main(string [] args)
{
Employee mike = new Employee ()
{
IDCode = "NB123" ,
Age = 30,
Department = new Department () { Name = "Dep1" }
};
Employee rose = mike.Clone() as Employee ;
Console .WriteLine(mike.IDCode);
Console .WriteLine(mike.Age);
Console .WriteLine(mike.Department);
Console .WriteLine("开始改变mike值:" );
mike.IDCode = "NB456" ;
mike.Age = 60;
mike.Department.Name = "Dep2" ;
Console .WriteLine("mike :" );
Console .WriteLine(mike.IDCode);
Console .WriteLine(mike.Age);
Console .WriteLine(mike.Department);
Console .WriteLine("rose :" );
Console .WriteLine(rose.IDCode);
Console .WriteLine(rose.Age);
Console .WriteLine(rose.Department);
Console .ReadKey();
}
则输出结果为:
此结果验证了定义中:对源对象的修改不会影响到副本本身
在同一类中同时实现深拷贝和浅拷贝
修改Employee 类即可,调用深拷贝用方法DeepClone(),调用浅拷贝用方法ShallowClone()
[ Serializable]
class Employee : ICloneable
{
public string IDCode { get; set; }
public int Age { get; set; }
public Department Department { get ; set ; }
public object Clone()
{
return this.MemberwiseClone();
}
public Employee DeepClone()
{
using ( Stream sr = new MemoryStream ())
{
IFormatter formatter = new BinaryFormatter ();
formatter.Serialize(sr, this);
sr.Seek(0, SeekOrigin .Begin);
return formatter.Deserialize(sr) as Employee ;
}
}
public Employee ShallowClone()
{
return Clone() as Employee ;
}
}