拷贝(复制)为对象创建副本,即将对象中的所有字段复制到新的对象(副本中)。拷贝有两种:浅拷贝和深拷贝,微软建议用类型继承ICloneable接口的方式明确该类型是可以被拷贝的,ICloneable接口只提供了一个Clone方法,需要根据需要在Clone方法内实现浅拷贝或深拷贝。
1、浅拷贝:把源对象中的值类型字段的值和引用类型字段的引用复制到副本中。在源对象(副本)中,修改值类型字段的值不会影响到副本(源对象),而修改引用类型字段的值会影响到副本(源对象)。
注意:string类型除外,虽然string类型是引用类型,但是由于该引用类型的特殊性,在浅拷贝过程,副本中会创建新的字符串并把对应的值复制过来,字符串应被看成值类型。
浅拷贝声明代码,使用Object.MeberwiseClone方法进行浅拷贝:
1 class Employee : ICloneable 2 { 3 public string ID { get; set; } 4 public int Age { get; set; } 5 public Department DepartmentName { get; set; } 6 7 //实现ICloneable接口的Clone方法 8 public object Clone() 9 { 10 return this.MemberwiseClone();//浅拷贝 11 } 12 } 13 class Department 14 { 15 public string DepartmentName { get; set; } 16 public Department(string value) 17 { 18 DepartmentName = value; 19 } 20 public override string ToString() 21 { 22 return DepartmentName.ToString(); 23 } 24 }
调用浅拷贝代码:
1 Employee emp1 = new Employee() 2 { 3 ID = "NO1", 4 Age = 20, 5 DepartmentName = new Department("Technology") 6 }; 7 Employee emp2 = emp1.Clone() as Employee;//浅拷贝 8 9 Console.WriteLine("-------初始化赋值------"); 10 Console.WriteLine(string.Format("[emp1] id:{0}\tage:{1}\tdepartment:{2}", emp1.ID, emp1.Age, emp1.DepartmentName)); 11 Console.WriteLine(string.Format("[emp2] id:{0}\tage:{1}\tdepartment:{2}", emp2.ID, emp2.Age, emp2.DepartmentName)); 12 13 Console.WriteLine("\n-------改变emp1的值-------"); 14 emp1.ID = "NO2"; 15 emp1.Age = 22; 16 emp1.DepartmentName.DepartmentName = "sales"; 17 Console.WriteLine(string.Format("[emp1] id:{0}\tage:{1}\tdepartment:{2}", emp1.ID, emp1.Age, emp1.DepartmentName)); 18 Console.WriteLine(string.Format("[emp2] id:{0}\tage:{1}\tdepartment:{2}", emp2.ID, emp2.Age, emp2.DepartmentName)); 19 20 Console.WriteLine("\n-------改变emp2的值-------"); 21 emp2.ID = "NO3"; 22 emp2.Age = 24; 23 emp2.DepartmentName.DepartmentName = "personnel"; 24 Console.WriteLine(string.Format("[emp1] id:{0}\tage:{1}\tdepartment:{2}", emp1.ID, emp1.Age, emp1.DepartmentName)); 25 Console.WriteLine(string.Format("[emp2] id:{0}\tage:{1}\tdepartment:{2}", emp2.ID, emp2.Age, emp2.DepartmentName));
运行结果:
-------初始化赋值------ [emp1] id:NO1 age:20 department:Technology [emp2] id:NO1 age:20 department:Technology -------改变emp1的值------- [emp1] id:NO2 age:22 department:sales [emp2] id:NO1 age:20 department:sales -------改变emp2的值------- [emp1] id:NO2 age:22 department:personnel [emp2] id:NO3 age:24 department:personnel
从结果可以看出,Age是值类型,ID是string类型这里被当做值类型处理,所以ID和Age修改了对另一个对象没有影响;Department属性是引用类型,浅拷贝emp1和emp2引用的是同一个Department对象,其中一个修改了DepartmentName的值会影响另一个。
2、深拷贝:把源对象的值类型字段和引用类型字段,重新创建并赋值。在源对象(副本)中,修改值类型字段的值或者引用类型字段的值都不会影响到副本(源对象)。
建议使用序列化的形式来进行深拷贝
深拷贝代码:
[Serializable]//标记可序列化 class Employee : ICloneable { public string ID { get; set; } public int Age { get; set; } public Department DepartmentName { get; set; } //实现ICloneable接口的Clone方法 public object Clone() { using (MemoryStream stream = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, this); stream.Seek(0, SeekOrigin.Begin); return formatter.Deserialize(stream) as Employee; } } } [Serializable]//标记可序列化 class Department { public string DepartmentName { get; set; } public Department(string value) { DepartmentName = value; } public override string ToString() { return DepartmentName.ToString(); } }
跟上面调用浅拷贝代码一样调用深拷贝,运行结果:
-------初始化赋值------ [emp1] id:NO1 age:20 department:Technology [emp2] id:NO1 age:20 department:Technology -------改变emp1的值------- [emp1] id:NO2 age:22 department:sales [emp2] id:NO1 age:20 department:Technology -------改变emp2的值------- [emp1] id:NO2 age:22 department:sales [emp2] id:NO3 age:24 department:personnel
拷贝以后,无论是修改值类型还是引用类型,都对另一个对象没有影响。
3、要同时实现深拷贝和浅拷贝,可以在Clone方法外,额外实现两个方法,声明为DeepClone和Shallow:
1 [Serializable]//标记可序列化 2 class Employee : ICloneable 3 { 4 public string ID { get; set; } 5 public int Age { get; set; } 6 public Department DepartmentName { get; set; } 7 8 //实现ICloneable接口的Clone方法 9 public object Clone() 10 { 11 return this.MemberwiseClone(); 12 } 13 14 //深拷贝 15 public Employee DeepClone() 16 { 17 using (MemoryStream stream = new MemoryStream()) 18 { 19 BinaryFormatter formatter = new BinaryFormatter(); 20 formatter.Serialize(stream, this); 21 stream.Seek(0, SeekOrigin.Begin); 22 return formatter.Deserialize(stream) as Employee; 23 } 24 } 25 26 //浅拷贝 27 public Employee Shallow() 28 { 29 return this.Clone() as Employee; 30 } 31 }
参考:《编写高质量代码改善C#程序的157个建议》陆敏技