如何在C#中做deep copy?
介绍
System.Object是所有类,结构,枚举和委托的基类。我们可以说它是类型层次结构的根。System.Object有一个称为MemberwiseClone的方法,该方法有助于创建当前对象实例的克隆副本。
问题陈述
System.Object的MemberwiseClone方法创建一个新对象的浅表副本,并将当前对象实例的非静态字段复制到一个新对象。复制对象是按属性逐个执行的,如果属性是值类型,则它会一点一点地复制数据,如果属性是引用类型,则它将复制原始对象的引用。这意味着MemberwiseClone方法不会创建该对象的深层副本。
解
有多种方法可以实现深层复制类对象。在这里用示例描述了其中两个。
- 使用序列化反序列化对象实现深度克隆
- 使用反射实现深度克隆
1.使用序列化反序列化对象实现深度克隆
ICloneable接口使我们能够提供定制的实现,以使用“Clone”方法创建现有对象的副本。通常,“ Object.MemberwiseClone”方法可帮助我们创建现有对象的副本,但会创建该对象的浅表副本。
序列化是将对象状态存储到字节流中的过程。反序列化是从字节流转换为原始对象的过程。在.NET中,有许多执行序列化和反序列化的方法,例如二进制序列化,XML序列化,数据协定序列化等。
使用序列化和反序列化,我们可以创建任何对象的深层副本。请注意,要执行序列化和反序列化,所有类型都必须标记为“可序列化”。
例
假设有一个Employee类,它包含Department类类型作为属性。现在,使用ICloneable接口实现了Employee类,并且实现了ICloneable接口的“Clone”方法。使用二进制格式化程序,我只是序列化了当前对象并将其反序列化为一个新对象。
[Serializable]
public class Department
{
public int DepartmentId { get; set; }
public string DepartmentName { get; set; }
}
[Serializable]
public class Employee : ICloneable
{
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public Department Department { get; set; }
public object Clone()
{
using (MemoryStream stream = new MemoryStream())
{
if (this.GetType().IsSerializable)
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Position = 0;
return formatter.Deserialize(stream);
}
return null;
}
}
}
我们也可以使用扩展方法来实现.
public static class ObjectExtension
{
public static T CopyObject<T>(this object objSource)
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, objSource);
stream.Position = 0;
return (T)formatter.Deserialize(stream);
}
}
}
2.使用反射实现深度克隆反射
用于在运行时获取对象的元信息。System.Reflection命名空间具有一些类,这些类使我们可以在运行时获取对象信息,还可以从现有对象创建对象类型的实例,还可以访问其属性并调用其方法。要对对象进行深层复制,可以使用反射。考虑下面的代码。在这里,我创建了一个静态方法,该方法可以接受任何对象,并返回具有新引用的相同类型的对象。
public class Utility
{
public static object CloneObject(object objSource)
{
//步骤1:获取源对象的类型并创建该类型的新实例
Type typeSource = objSource.GetType();
object objTarget = Activator.CreateInstance(typeSource);
//步骤2:获取源对象类型的所有属性
PropertyInfo[] propertyInfo = typeSource.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
//步骤3:将所有源属性分配给标记对象的属性
foreach (PropertyInfo property in propertyInfo)
{
//检查属性是否可以写入
if (property.CanWrite)
{
//步骤4:检查属性类型是值类型,枚举类型还是字符串类型
if (property.PropertyType.IsValueType || property.PropertyType.IsEnum || property.PropertyType.Equals(typeof(System.String)))
{
property.SetValue(objTarget, property.GetValue(objSource, null), null);
}
//其他属性类型是对象/复杂类型,因此需要递归调用此方法,直到到达树的末尾
else
{
object objPropertyValue = property.GetValue(objSource, null);
if (objPropertyValue == null)
{
property.SetValue(objTarget, null, null);
}
else
{
property.SetValue(objTarget, CloneObject(objPropertyValue), null);
}
}
}
}
return objTarget;
}
}
这也可以通过下面描述的对象扩展方法来实现。
使用对象扩展方法
public static class ObjectExtension
{
public static object CloneObject(this object objSource)
{
//获取源对象的类型并创建该类型的新实例
Type typeSource = objSource.GetType();
object objTarget = Activator.CreateInstance(typeSource);
//获取源对象类型的所有属性
PropertyInfo[] propertyInfo = typeSource.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
//将所有源属性分配给taget对象的属性
foreach (PropertyInfo property in propertyInfo)
{
//检查属性是否可以写入
if (property.CanWrite)
{
//检查属性类型是值类型,枚举类型还是字符串类型
if (property.PropertyType.IsValueType || property.PropertyType.IsEnum || property.PropertyType.Equals(typeof(System.String)))
{
property.SetValue(objTarget, property.GetValue(objSource, null), null);
}
//其他属性类型是对象/复杂类型,因此需要递归调用此方法,直到到达树的末尾
else
{
object objPropertyValue = property.GetValue(objSource, null);
if (objPropertyValue == null)
{
property.SetValue(objTarget, null, null);
}
else
{
property.SetValue(objTarget, objPropertyValue.CloneObject(), null);
}
}
}
}
return objTarget;
}
}
示例代码并输出
class Program
{
static void Main(string[] args)
{
Employee emp = new Employee();
emp.EmployeeId = 1000;
emp.EmployeeName = "Jignesh";
emp.Department = new Department { DepartmentId = 1, DepartmentName = "Examination" };
Employee empClone = emp.Clone() as Employee;
Employee empClone1 = Utility.CloneObject(emp) as Employee;
//现在更改原始对象的值
emp.EmployeeName = "Tejas";
emp.Department.DepartmentName = "Admin";
//打印origianl以及克隆对象的属性值。
Console.WriteLine("Original Employee Name : " + emp.EmployeeName);
Console.WriteLine("Original Department Name : " + emp.Department.DepartmentName);
Console.WriteLine("");
Console.WriteLine("Clone Object Employee Name (Clone Method) : " + empClone.EmployeeName);
Console.WriteLine("Clone Object Department Name (Clone Method) : " + empClone.Department.DepartmentName);
Console.WriteLine("");
Console.WriteLine("Clone Object Employee Name (Static Method) : " + empClone1.EmployeeName);
Console.WriteLine("Clone Object Department Name (Static Method) : " + empClone1.Department.DepartmentName);
Console.WriteLine("");
Console.WriteLine("Clone Object Employee Name (Extension Method) : " + empClone2.EmployeeName);
Console.WriteLine("Clone Object Department Name (Extension Method) : " + empClone2.Department.DepartmentName);
Console.ReadKey();
}
}
结论
使用序列化和反射,我们可以创建对象的深层副本。使用序列化的深层副本的唯一缺点是,我们必须将对象标记为“可序列化”。
推荐阅读