• 浅谈C#浅拷贝和深拷贝


    近来爱上一本书《编写高质量代码,改善C#程序的157个建议》,我想很多人都想编写高质量的代码,因为我们不仅仅是码农,更是一名程序员。

    从今天开始,我将每天和大家分享这本书中的内容,并加上自己的理解,希望可以帮助到更多和我一样盲目的程序员们。

    今天我们谈谈C#中的对象拷贝问题;

    所谓的对象拷贝,其实就是为对象创建副本,C#中将拷贝分为两种,分别为浅拷贝和深拷贝;

    所谓浅拷贝就是将对象中的所有字段复制到新的副本对象中;浅拷贝对于值类型与引用类型的方式有区别,值类型字段的值被复制到副本中后,在副本中的修改不会影响源对象对应的值;然而对于引用类型的字段被复制到副本中的却是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值被修改后,源对象的值也将被修改。

    深拷贝也同样是将对象中的所有字段复制到副本对象中,但是,无论对象的值类型字段或者引用类型字段,都会被重新创建并复制,对于副本的修改,不会影响到源对象的本身;

    当然,无论是哪种拷贝,微软都建议使用类型继承ICloneable接口的方式明确告诉调用者,该对象是否可用被拷贝。当然了,ICloneable接口只提供了一个声明为Clone的方法,我们可以根据需求在Clone的方法内实现浅拷贝或者是深拷贝,下面我们进行一段浅拷贝的案例,代码如下

     1     class Student : ICloneable
     2     {
     3         public string IDCode { get; set; }
     4 
     5         public int Age { get; set; }
     6 
     7         public Grent Grent { get; set; }
     8 
     9         #region 拷贝主体
    10         public object Clone()
    11         {
    12             return this.MemberwiseClone();
    13             //throw new NotImplementedException();
    14         }
    15         #endregion
    16 
    17     }
    18 
    19     class Grent
    20     {
    21         public string Name { get; set; }
    22 
    23         public override string ToString()
    24         {
    25             return this.Name;
    26         }
    27     }
    View Code

    调用

    Student stu1 = new Student()
                {
                    IDCode = "lily",
                    Age = 24,
                    Grent = new Grent() { Name="五年三班"}
                };
                Student stu2 = stu1.Clone() as Student;
                if (stu2 == null) {
                    Console.WriteLine("转换失败");
                    Console.ReadKey();
                    return;
                }
    
                Console.WriteLine(stu2.IDCode);
                Console.WriteLine(stu2.Age);
                Console.WriteLine(stu2.Grent.ToString());
    
                Console.WriteLine("重新为stu1赋值");
                stu1.IDCode = "Anagle";
                stu1.Age += 10;
                stu1.Grent.Name = "六年二班";
                Console.WriteLine(stu2.IDCode);
                Console.WriteLine(stu2.Age);
                Console.WriteLine(stu2.Grent.ToString());
                Console.ReadKey();
    View Code

    输出结果为:

    lily

    24

    五年三班

    重新为stu1赋值

    lily

    24

    六年二班

    这里我们需要注意一点Student中的IDCode属性是string类型,理论上string类型是引用类型,但是由于该引用类型的特殊性,Object.MemberwiseClone方法仍旧为他创建了副本,也就是说,在浅拷贝过程中,我们应该将字符串看成值类型;

    因为Student中的Grent是引用类型,所以在stu1中的Grent的Name被改变的同时,副本stu2中的Grent的Name也同样被改变。

    Student的深拷贝有多钟实现方法,最简单的方法是手动对字段诼个赋值,但是这种方法容易出错,也就是说,如果类型的字段发生变化或有增减时,那么该拷贝方法也就要发生相应的变化,所以,建议使用序列化的形式来进行深拷贝。实现代码如下

        [Serializable]
        class Student : ICloneable
        {
            public string IDCode { get; set; }
    
            public int Age { get; set; }
    
            public Grent Grent { get; set; }
    
            #region 拷贝主体
            /// <summary>
            /// 深度拷贝
            /// </summary>
            /// <returns></returns>
            public Student DeepClone() {
                using (Stream objectStream = new MemoryStream()) {
                    IFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(objectStream, this);
                    objectStream.Seek(0,SeekOrigin.Begin);
                    return formatter.Deserialize(objectStream) as Student;
                }
            }
    
            public object Clone()
            {
                return this.MemberwiseClone();
            }
            #endregion
    
        }
    View Code

    调用DeepClone方法

    Student stu1 = new Student()
                {
                    IDCode = "lily",
                    Age = 24,
                    Grent = new Grent() { Name="五年三班"}
                };
                Student stu2 = stu1.DeepClone() as Student;
                if (stu2 == null) {
                    Console.WriteLine("转换失败");
                    Console.ReadKey();
                    return;
                }
    
                Console.WriteLine(stu2.IDCode);
                Console.WriteLine(stu2.Age);
                Console.WriteLine(stu2.Grent.ToString());
    
                Console.WriteLine("重新为stu1赋值");
                stu1.IDCode = "Anagle";
                stu1.Age += 10;
                stu1.Grent.Name = "六年二班";
                Console.WriteLine(stu2.IDCode);
                Console.WriteLine(stu2.Age);
                Console.WriteLine(stu2.Grent.ToString());
                Console.ReadKey();
    View Code

    输出结果为:

    lily

    24

    五年三班

    重新为stu1赋值

    lily

    24

    五年三班

    这次我们看到的结果已经很明显了,深拷贝后,源对象的重新赋值与修改将不再导致副本对象被修改,这样将很好的控制住引用类型的拷贝问题。

    DEMO源码下载

  • 相关阅读:
    jenkins集成python时出现"Non-ASCII character 'xe6' in file"错误解决方法
    【转】/bin/bash^M: bad interpreter: No such file or directory
    解决Jenkins中执行jmeter脚本后不能发报告(原报告被覆盖、新报告无法保存)的问题
    【转】shell脚本中如何传入参数
    Hibernate学习笔记
    struts2学习笔记
    oracle表空间相关SQL语句
    javaMail
    Mysql 5.7.7
    设计模式之享元模式
  • 原文地址:https://www.cnblogs.com/xufei/p/copyDeep.html
Copyright © 2020-2023  润新知