C#中Object的深拷贝
简介:
C#中System.Object 是所有类类型、结构类型、枚举类型和委托类型的基类。可以说它是类型继承的基础。System.Object包括一个用于创建当前对象实例的一份拷贝的MemberwiseClone的成员方法。
问题描述:
System.Object的MemberwiseClone方法创建一个新对象的浅拷贝,并把当前对象实例的非静态字段拷贝至新对象实例中。通过属性,对象拷贝能够正确执行:如果属性是值类型,那么将按位拷贝数据,如果属性是引用类型,那么将拷贝原始对象的引用,也就是说,克隆对象指向同一个对象实例。这就意味着MemberwiseClone方法并未创建一个对象的深拷贝。
解决方法:
有许多方法实现类对象的深拷贝,下面我将通过示例介绍其中的两种:
1.通过序列化、反序列化实现深拷贝
2.通过反射实现深拷贝
1、通过序列化、反序列化实现对象的深拷贝
ICloneable接口使得开发者定制实现Clone方法用以创建一个已存对象的深拷贝。通常Object.MemberwiseClone方法帮助开发者创建一份已存对象的拷贝,但是创建的是这个对象的浅拷贝。序列化是指将对象状态存储为二进制流的过程,而反序列化是指将而二进制流转换为原始对象的过程。在.Net中有许多方法可以实现序列化和反序列化,例如二进制序列化、XML序列化、数据约定序列化等等。二进制序列化比XML序列化快速,且二进制序列化使用私有、公有字段,因而二进制序列化是实现序列化和反序列化的不错选择。
通过序列化和反序列化,可以创建对象的深拷贝。需要注意的是,所有类型只有标记为[serializable]特性才能实现序列化和反序列化。
示例程序:
首先创建Employee类,它包含Department类型的属性,Employee类继承ICloneable接口并且实现Clone方法。运用二进制格式化器(binary formater),实现对象的序列化和反序列化为一个新对象。
1 [Serializable] 2 public class Department 3 { 4 private Int32 _DepartmentID; 5 private String _DepartmentName; 6 public Int32 DepartmentID 7 { 8 get {return _DepartmentID;} 9 set { _DepartmentID = value;} 10 } 11 public String DepartmentName 12 { 13 get {return _DepartmentName;} 14 set {_DepartmentName = value;} 15 } 16 } 17 [Serializable] 18 public class Employee : ICloneable 19 { 20 private Int32 _EmployeeID; 21 private String _EmployeeName; 22 private Department _Dempartment; 23 public Int32 EmployeeID 24 { 25 get {return _EmployeeID;} 26 set {_EmployeeID = value; } 27 } 28 public String EmployeeName 29 { 30 get { return _EmployeeName; } 31 set { _EmployeeName = value;} 32 } 33 public Department Department 34 { 35 get{ return _Dempartment; } 36 set{ _Dempartment= value;} 37 } 38 39 public object Clone() 40 { 41 using (MemoryStream stream = new MemoryStream()) 42 { 43 if (this.GetType().IsSerializable) 44 { 45 BinaryFormatter formatter = new BinaryFormatter(); 46 formatter.Serialize(stream, this); 47 stream.Position = 0; 48 return formatter.Deserialize(stream); 49 } 50 return null; 51 } 52 } 53 }
也可以通过扩展方法实现:
1 public static class ObjectExtension 2 { 3 public static T CopyObject<T>(this object objSource) 4 { 5 using (MemoryStream stream = new MemoryStream()) 6 { 7 BinaryFormatter formatter = new BinaryFormatter(); 8 formatter.Serialize(stream, objSource); 9 stream.Position = 0; 10 return (T)formatter.Deserialize(stream); 11 } 12 } 13 }
2.通过反射实现深拷贝
反射用于获取运行时对象原始信息。运用System.Reflection名字空间的类可以获取运行史对象时的信息,从已存对象创建类型实例,并访问其属性及调用方法。考虑一下代码,我创建了一个接受Object参数的静态方法,并且返回同样类型的一个新实例。
1 public class Utility 2 { 3 public static object CloneObject(object objSource) 4 { 5 //Get the type of source object and create a new instance of that type 6 Type typeSource = objSource.GetType(); 7 object objTarget = Activator.CreateInstance(typeSource); 8 9 //Get all the properties of source object type 10 PropertyInfo[] propertyInfo = typeSource.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 11 12 //Assign all source property to taget object 's properties 13 foreach (PropertyInfo property in propertyInfo) 14 { 15 //Check whether property can be written to 16 if (property.CanWrite) 17 { 18 //check whether property type is value type, enum or string type 19 if (property.PropertyType.IsValueType || property.PropertyType.IsEnum || property.PropertyType.Equals(typeof(System.String))) 20 { 21 property.SetValue(objTarget, property.GetValue(objSource, null), null); 22 } 23 //else property type is object/complex types, so need to recursively call this method until the end of the tree is reached 24 else 25 { 26 object objPropertyValue = property.GetValue(objSource, null); 27 if (objPropertyValue == null) 28 { 29 property.SetValue(objTarget, null, null); 30 } 31 else 32 { 33 property.SetValue(objTarget, CloneObject(objPropertyValue), null); 34 } 35 } 36 } 37 } 38 return objTarget; 39 } 40 }
这也可以通过以下的扩展方法实现。
1 public static class ObjectExtension 2 { 3 public static object CloneObject(this object objSource) 4 { 5 //Get the type of source object and create a new instance of that type 6 Type typeSource = objSource.GetType(); 7 object objTarget = Activator.CreateInstance(typeSource); 8 9 //Get all the properties of source object type 10 PropertyInfo[] propertyInfo = typeSource.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 11 12 //Assign all source property to taget object 's properties 13 foreach (PropertyInfo property in propertyInfo) 14 { 15 //Check whether property can be written to 16 if (property.CanWrite) 17 { 18 //check whether property type is value type, enum or string type 19 if (property.PropertyType.IsValueType || property.PropertyType.IsEnum || property.PropertyType.Equals(typeof(System.String))) 20 { 21 property.SetValue(objTarget, property.GetValue(objSource, null), null); 22 } 23 //else property type is object/complex types, so need to recursively call this method until the end of the tree is reached 24 else 25 { 26 object objPropertyValue = property.GetValue(objSource, null); 27 if (objPropertyValue == null) 28 { 29 property.SetValue(objTarget, null, null); 30 } 31 else 32 { 33 property.SetValue(objTarget, objPropertyValue.CloneObject(), null); 34 } 35 } 36 } 37 } 38 return objTarget; 39 } 40 }
以下是示例代码及输出:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Employee emp = new Employee(); 6 emp.EmployeeID = 1000; 7 emp.EmployeeName = "Cacotopia"; 8 emp.Department = new Department { DepartmentID = 1, DepartmentName = "development center" }; 9 10 Employee empClone = emp.Clone() as Employee; 11 12 Employee empClone1 = Utility.CloneObject(emp) as Employee; 13 Employee empClone2 = emp.CloneObject() as Employee; 14 15 Employee empClone3 = emp.CopyObject<Employee>(); 16 //now Change Original Object Value 17 emp.EmployeeName = "24/7"; 18 emp.Department.DepartmentName = "Admin"; 19 20 //Print origianl as well as clone object properties value. 21 22 Console.WriteLine("Original Employee Name : " + emp.EmployeeName); 23 Console.WriteLine("Original Department Name : " + emp.Department.DepartmentName); 24 25 Console.WriteLine(""); 26 27 Console.WriteLine("Clone Object Employee Name (Clone Method) : " + empClone.EmployeeName); 28 Console.WriteLine("Clone Object Department Name (Clone Method) : " + empClone.Department.DepartmentName); 29 30 Console.WriteLine(""); 31 32 Console.WriteLine("Clone Object Employee Name (Static Method) : " + empClone1.EmployeeName); 33 Console.WriteLine("Clone Object Department Name (Static Method) : " + empClone1.Department.DepartmentName); 34 35 Console.WriteLine(""); 36 37 Console.WriteLine("Clone Object Employee Name (Extension Method) : " + empClone2.EmployeeName); 38 Console.WriteLine("Clone Object Department Name (Extension Method) : " + empClone2.Department.DepartmentName); 39 Console.WriteLine("press any key to exit..."); 40 Console.ReadKey(); 41 } 42 }
结束语:
通过序列化和反射我们可以实现对象的深拷贝。运用序列化实现深拷贝的唯一不足之处在于必须将对象标记为Serializable。