欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何在C#中做deep copy?

程序员文章站 2022-05-06 09:29:49
...

介绍

System.Object是所有类,结构,枚举和委托的基类。我们可以说它是类型层次结构的根。System.Object有一个称为MemberwiseClone的方法,该方法有助于创建当前对象实例的克隆副本。
 
问题陈述

System.Object的MemberwiseClone方法创建一个新对象的浅表副本,并将当前对象实例的非静态字段复制到一个新对象。复制对象是按属性逐个执行的,如果属性是值类型,则它会一点一点地复制数据,如果属性是引用类型,则它将复制原始对象的引用。这意味着MemberwiseClone方法不会创建该对象的深层副本。
 

有多种方法可以实现深层复制类对象。在这里用示例描述了其中两个。

  1. 使用序列化反序列化对象实现深度克隆
  2. 使用反射实现深度克隆

1.使用序列化反序列化对象实现深度克隆

ICloneable接口使我们能够提供定制的实现,以使用“Clone”方法创建现有对象的副本。通常,“ Object.MemberwiseClone”方法可帮助我们创建现有对象的副本,但会创建该对象的浅表副本。
 
序列化是将对象状态存储到字节流中的过程。反序列化是从字节流转换为原始对象的过程。在.NET中,有许多执行序列化和反序列化的方法,例如
二进制序列化,XML序列化,数据协定序列化等。
 
使用序列化和反序列化,我们可以创建任何对象的深层副本。
请注意,要执行序列化和反序列化,所有类型都必须标记为“可序列化”
 

假设有一个Employee类,它包含Department类类型作为属性。现在,使用ICloneable接口实现了Employee类,并且实现了ICloneable接口的“Clone”方法。使用二进制格式化程序,我只是序列化了当前对象并将其反序列化为一个新对象。

如何在C#中做deep copy?

[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();
        }
    }

如何在C#中做deep copy?

结论

使用序列化和反射,我们可以创建对象的深层副本。使用序列化的深层副本的唯一缺点是,我们必须将对象标记为“可序列化”。