• C# 中的浅拷贝与深拷贝


    Ø  简介

    C# 中分为两种数据类型,值类型和引用类型。我们知道,值类型之间赋值是直接将值赋值给另一个变量,两个变量值的改变都互不影响;而引用类型赋值则是将引用赋值给另一个变量,其中一个变量中的成员的值被改变,会影响到另一个变量。

     

    好,以上论述不是我们今天要讨论的重点,只是起抛砖引玉的作用。现在假设有这样一个需求,我现在有一个对象(别想歪了,不是女朋友~),而我不想使用 new 去创建这个类型的另一个对象,该如何实现呢?这时我们会立马想到使用反射去创建这个对象,然后再逐个属性进行赋值。OK,没问题,其实这一操作正是对象拷贝中深拷贝的一种体现。

     

    那什么是浅拷贝与深拷贝呢?

    首先,我们说一下对象拷贝。对象拷贝,就是将一个现有的对象,克隆为一个全新的对象,它们的引用是相互独立的

    浅拷贝:是指在对象拷贝的过程中,成员中的值类型进行逐一赋值,而引用类型只赋值引用,并不会创建新的对象实例,结果它们指向的是同一个引用

    深拷贝:其实是对引用类型而言的,它除了会对值类型赋值;还可以将引用类型创建新的实例,结果它们指向的是不同引用。说还可以的意思是,其实深拷贝是需要我们写代码去实现的。

     

    好了,理论就是这么多。下面进入实战,主要包括以下几点:

    1.   对象拷贝的用途

    2.   实现浅拷贝

    3.   实现深拷贝

     

    1.   对象拷贝的用途

    我们学习它,当然要知道它能干嘛是吧?

    说实话,对象拷贝用的不多。回忆以下,之前在写 EF 代码时,有接触到将一个对象赋值到另一个对象的场景,只是当时没有对象拷贝这个概念。也仅此一次而已!下面是本人能想到的应用场景:

    1)   当一个对象中的成员较多,而又不想写代码逐个成员去赋值时;

    2)   当需要将一个对象自动完成克隆为一个全新对象时;

    3)   当前已经存在一个对象,而使用 new 关键字去创建该对象比较耗时时。

     

    2.   实现浅拷贝

    我们知道,System.Object 这个基类有一个 MemberwiseClone() 方法,该方法的声明如下:

    [MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]

    protected extern object MemberwiseClone();

    1)   该方法采用外部方法实现;

    2)   该方法是受保护的,采用了 protected 修饰符(所以只能由子类去访问);

    3)   该方法可以翻译为成员逐一克隆

    没错,该方法就是用于实现浅拷贝的,具体实现如下:

    1)   首先,定义一个 CloneBase 抽象基类

    [Serializable]

    public abstract class CloneBase

    {

        /// <summary>

        /// 浅拷贝

        /// </summary>

        public object ShallowCopy()

        {

            return base.MemberwiseClone();

        }

    }

     

    2)   定义实体类

    /// <summary>

    /// 性格

    /// </summary>

    public enum Natures

    {

        /// <summary>

        /// 外向

        /// </summary>

        Extroversion = 1,

     

        /// <summary>

        /// 内向

        /// </summary>

        Introversion = 2

    }

     

    [Serializable]

    /// <summary>

    /// 部门

    /// </summary>

    public class Department

    {

        public int DepId { get; set; }

     

        public string Name { get; set; }

    }

     

    [Serializable]

    /// <summary>

    /// 员工

    /// </summary>

    public class Employee : CloneBase

    {

        //public Employee(int EmpId) { }

     

        public int EmpId { get; set; }

     

        public bool Sex { get; set; }

     

        public double Salary { get; set; }

     

        public DateTime EntryDate { get; set; }

     

        /// <summary>

        /// 性格

        /// </summary>

        public Natures Nature { get; set; }

     

        public string Name { get; set; }

     

        public Department Department { get; set; }

     

        /// <summary>

        /// 业余爱好

        /// </summary>

        public string[] Hobbys { get; set; }

     

        /// <summary>

        /// 技能

        /// </summary>

        public List<string> SkillList { get; set; }

     

        /// <summary>

        /// 工作内容

        /// </summary>

        public Dictionary<int, string> JobContent { get; set; }

     

        /// <summary>

        /// 绩效考核

        /// </summary>

        public DataTable Performance { get; set; }

    }

     

    3)   测试代码

    Employee zhangsan1 = new Employee()

    {

        EmpId = 1,

        Sex = true,

        Salary = 10000,

        EntryDate = DateTime.Parse("2011-01-01"),

        Nature = Natures.Extroversion,

        Name = "张三",

        Department = new Department() { DepId = 1, Name = "研发部" },

        Hobbys = new string[] { "打篮球", "健身", "学习" },

        SkillList = new List<string>() { "SQL Server", "Redis" },

        JobContent = new Dictionary<int, string>()

        {

            { 1, "后端开发" },

            { 2, "移动端开发" }

        },

        Performance = new DataTable("MyTable1")

    };

    zhangsan1.Performance.Columns.AddRange(new DataColumn[]

    {

        new DataColumn("Quarter", typeof(int)),     //季度

        new DataColumn("Score", typeof(double)),    //分数

    });

    zhangsan1.Performance.Rows.Add(1, 80);

     

    Employee zhangsan2 = zhangsan1.ShallowCopy() as Employee;

    zhangsan2.EmpId = 2;

    zhangsan2.Sex = false;

    zhangsan2.Salary = 20000;

    zhangsan2.EntryDate = DateTime.Parse("2012-02-02");

    zhangsan2.Nature = Natures.Introversion;

    zhangsan2.Name = "张山";

    zhangsan2.Department.DepId = 2;

    zhangsan2.Hobbys[0] = "打桌球";

    zhangsan2.SkillList[0] = "Oracle";

    zhangsan2.JobContent[1] = "前端开发";

    zhangsan2.Performance.Rows[0][1] = 90;

     

    Console.WriteLine($"zhangsan1 equal to zhangsan2 : {object.ReferenceEquals(zhangsan1, zhangsan2)}");

    Console.WriteLine("");

    Console.WriteLine($"int type: {zhangsan1.EmpId} -> {zhangsan2.EmpId}");

    Console.WriteLine($"bool type: {zhangsan1.Sex} -> {zhangsan2.Sex}");

    Console.WriteLine($"double type: {zhangsan1.Salary} -> {zhangsan2.Salary}");

    Console.WriteLine($"DateTime type: {zhangsan1.EntryDate} -> {zhangsan2.EntryDate}");

    Console.WriteLine($"enum type: {zhangsan1.Nature} -> {zhangsan2.Nature}");

    Console.WriteLine("");

    Console.WriteLine($"string type: {zhangsan1.Name} -> {zhangsan2.Name}");

    Console.WriteLine($"class type: {zhangsan1.Department.DepId} -> {zhangsan2.Department.DepId}");

    Console.WriteLine($"array type: {zhangsan1.Hobbys[0]} -> {zhangsan2.Hobbys[0]}");

    Console.WriteLine($"List type: {zhangsan1.SkillList[0]} -> {zhangsan2.SkillList[0]}");

    Console.WriteLine($"Dictionary type: {zhangsan1.JobContent[1]} -> {zhangsan2.JobContent[1]}");

    Console.WriteLine($"DataTable type: {zhangsan1.Performance.Rows[0][1]} -> {zhangsan2.Performance.Rows[0][1]}");

     

    4)   运行结果

    clip_image002[6]

     

    3.   实现深拷贝

    深拷贝相比浅拷贝,稍微复杂一点。其实现方式大致分为反射方式序列化方式,而序列化通常可以借助(XmlSerializerDataContractSerializerBinaryFormatter)这三个对象来完成。下面是实现代码:

    1)   首先,在 CloneBase 基类中加入深拷贝的实现代码:

    /// <summary>

    /// 反射方式

    /// </summary>

    private object Reflection<T>(T obj)

    {

        Type sourceType;

        if (obj is string || (sourceType = obj.GetType()).IsValueType)

        {

            return obj;

        }

        else if (sourceType.IsArray)

        {

            Array sourceArr = obj as Array;

            Type arrType = Type.GetType(sourceType.FullName.Replace("[]", string.Empty));   //System.String[] 取数组类型

                                                                                            //创建数组实例,并遍历和递归赋值每个元素

            Array targetArr = Array.CreateInstance(arrType, sourceArr.Length);

            for (int i = 0; i < sourceArr.Length; i++)

            {

                targetArr.SetValue(Reflection(sourceArr.GetValue(i)), i);

            }

            return Convert.ChangeType(targetArr, sourceType);

        }

        else if (sourceType.Assembly.GetName().Name != "mscorlib"

                    && !sourceType.FullName.StartsWith("System"))   //认为是自定义 Class 类型

        {

            //创建当前类型的实例,并遍历成员进行赋值

            object instance = Activator.CreateInstance(sourceType);

            FieldInfo[] fields = sourceType.GetFields(

                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);

            foreach (var field in fields)

            {

                field.SetValue(instance, Reflection(field.GetValue(obj)));  //递归调用(如果是引用类型)

            }

            return instance;

        }

        else

        {

            //到这里可能是 List<T>Dictionary<TKey, TValue>DataTable 等复杂数据类型

            //其实并不好实现克隆,所有这里为了简单,就直接赋值源对象引用了

            return obj;

        }

    }

     

    /// <summary>

    /// 序列化方式

    /// </summary>

    private object Serialize<T>(T obj, int serializeMode)

    {

        //1. 采用XML序列化和反序列化

        if (serializeMode == 1)

        {

            using (MemoryStream ms = new MemoryStream())

            {

                var xmlSerializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());  //无法序列化 System.Collections.Generic.Dictionary`

                xmlSerializer.Serialize(ms, this);

                ms.Seek(0, SeekOrigin.Begin);

                return xmlSerializer.Deserialize(ms);

            }

        }

        //2. 采用数据契约序列化和反序列化

        else if (serializeMode == 2)

        {

            using (MemoryStream ms = new MemoryStream())

            {

                var serializer = new System.Runtime.Serialization.DataContractSerializer(obj.GetType());

                serializer.WriteObject(ms, obj);

                ms.Seek(0, SeekOrigin.Begin);

                return serializer.ReadObject(ms);

            }

        }

        //3. 采用二进制格式序列化和反序列化

        else if (serializeMode == 3)

        {

            using (MemoryStream ms = new MemoryStream())

            {

                var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

                binaryFormatter.Serialize(ms, this);

                ms.Seek(0, SeekOrigin.Begin);

                return binaryFormatter.Deserialize(ms);

            }

        }

        else { throw new ArgumentException("serializeMode 参数不在支持的范围内"); }

    }

     

    /// <summary>

    /// 深拷贝

    /// </summary>

    public virtual object DeepCopy()

    {

        //return Reflection(this);

        return Serialize(this, 3);

    }

     

    2)   修改测试代码

    Employee zhangsan2 = zhangsan1.ShallowCopy() as Employee;

    改为

    Employee zhangsan2 = zhangsan1.DeepCopy() as Employee;

     

    3)   测试结果

    clip_image004[6]

     

    Ø  总结

    以下是两种拷贝方式表现形式:

    拷贝方式

    实现方式

    无参构造函数

    Serializable 特性

    复杂类型(ListDictionaryDataTable等)

    浅拷贝

    MemberwiseClone()

    非必须

    非必须

    支持(引用赋值)

    深拷贝

    反射

    必须

    非必须

    不支持(或比较复杂)

    XmlSerializer

    必须

    非必须

    不支持

    DataContractSerializer

    非必须

    必须

    支持

    BinaryFormatter

    非必须

    必须

    支持

    深拷贝:

    1.   反射,对于泛型集合(ListDictionary等)类型,实现起来比较复杂。不推荐使用。

    2.   XmlSerializer,对(Dictionary等)类型支持不够,会报错:无法序列化 System.Collections.Generic.Dictionary`2。不推荐使用。

    3.   DataContractSerializer,需要额外添加对“System.Runtime.Serialization.dll”程序集的引用,不是很推荐。

    4.   BinaryFormatter,位于“mscorlib.dll”程序集,对象序列化方面支持较好,推荐使用

     

    其他:

    1.   .NET Framework 中提供了用于支持拷贝的接口 System.ICloneable, 该接口定义了一个 object Clone() 方法。但是使用该方法并不好区分是浅拷贝深拷贝,所以个人觉得并不建议去使用它。

    2.   如果非要定义深拷贝由实现类去实现,这时可以考虑定义深拷贝接口 IDeepCopy,具体的拷贝实现由该类来完成;另外,从某种程度上说,深拷贝其实可以看做是一种功能或者能力,也可以考虑定义为帮助方法去实现该功能;而浅拷贝逻辑上不用,因为仅一行代码即可实现,而且父类也能完成该功能。

  • 相关阅读:
    CentOS 7安装配置FTP服务器
    Python验证码
    Eclipse快捷键
    mybatis中的#和$的区别
    spring framework 各版本源码下载地址
    maven的pom.xml文件标签含义
    maven学习笔记
    (转)C#调用非托管Win 32 DLL
    (转)读取XML数据到treeView中
    SpringSecurity学习二----------实现自定义登录界面
  • 原文地址:https://www.cnblogs.com/abeam/p/11919838.html
Copyright © 2020-2023  润新知