理解浅拷贝和深拷贝

1、浅拷贝与深拷贝的定义

  什么是拷贝?拷贝即为常说的复制或者克隆一个对象,并且通过拷贝这些源对象创建新的对象。其中拷贝分为浅拷贝和深拷贝。对于拷贝出来的对象,在使用上有很大的差异,特别是在引用类型上。

  浅拷贝:将对象中的所有字段复制到新的对象中。其中,值类型字段被复制到新对象中后,在新对象中的修改不会影响到原先对象的值。而新对象的引用类型则是原先对象引用类型的引用,不是引用自己对象本身。:在新对象中修改引用类型的值会影响到原先对象;理论上String也是引用类型,但是由于由于该类型比较特殊,Object.MemberwiseClone()方法依旧为其新对象开辟了新的内存空间存储String的值,在浅拷贝中把String类型当作'值类型'即可。

   深拷贝:同样也是拷贝,但是与浅拷贝不同的是,深拷贝会对引用类型重新在创新一次(包括值类型),在新对象做的任何修改都不会影响到源对象本身。

   

2、实现浅拷贝与深拷贝

   在 .Net平台开发中,要实现拷贝,微软官方建议继承ICloneable接口,该接口位于System命名空间下,该接口只实现一个Clone方法,我们可以根据具体项目需求在该方法内实现浅拷贝或者深拷贝。先实现一个浅拷贝,具体代码如下:

 

 //Equal探索
        static void Main()
        {
            //创建源对象
            Teacher Source = new Teacher("Fode",18,DateTime.Now,22);
            Source.Print("源对象");

            //浅拷贝对象
            Teacher Target = Source.Clone() as Teacher;
            /*
             理论上String也是引用类型,但是由于由于该类型比较特殊,
             Object.MemberwiseClone()方法依旧为其新对象开辟了新的内存空间存储String的值,
             在浅拷贝中把String类型当作'值类型'即可
             */
            Target.Name = "JJ";
            Target.Student.Count = 11;
            Console.WriteLine("新对象的引用类型的值发生变化");

            Target.Print("新对象");
            Source.Print("源对象");

            Console.ReadKey();
        }

        class Teacher : ICloneable
        {
            public Teacher(String name, Int32 age, DateTime birthday, Int32 count)
            {
                this._name = name;
                this._age = age;
                this._birthday = birthday;
                this.Student = new Student() { Count = count };
            }

            private Int32 _age;
            public Int32 Age { get { return _age; } set { _age = value; } }

            private String _name;
            public String Name { get { return _name; } set { _name = value; } }


            private DateTime _birthday;
            public DateTime Birthday { get { return _birthday; } set { _birthday = value; } }


            public Student Student { get; set; }
            public void Print(String title)
            {
                Console.WriteLine(title);
                Console.WriteLine($"基本信息:姓名:{this.Name},年龄:{this.Age},生日:{this.Birthday.ToString("D")}");
                Console.WriteLine($"引用类型的值{Student.ToString()}");
                Console.WriteLine();
            }

            //实现浅拷贝
            public Object Clone()
            {
                return this.MemberwiseClone();
            }
        }

        class Student
        {
            public Int32 Count { get; set; }
            public override string ToString()
            {
                return Count.ToString();
            }
        }

 其运行结果如下:

可以发现当新对象的引用类型发生改变后,其源对象的引用类型也发生改变(String类型除外),他们共同引用的是Student这个引用类型对象(即使发生变化的是其里面的值类型),而新对象的值类型改变并不会到源类型的值类型。

 

而对于要实现深拷贝则有很多中方法了,比如在拷贝方法里面 直接一个个属性字段赋值,但是一旦为源对象新增属性或者字段的时候,容易忘了修改拷贝方法中的值,最好使用序列化的方法进行深拷贝。深拷贝简单代码如下,与浅拷贝同样的案例,只是重写了Clone()方法,并在类加了[Serializable]序列化特性标签:

     [Serializable]
        class Teacher : ICloneable
        {
            public Teacher(String name, Int32 age, DateTime birthday, Int32 count)
            {
                this._name = name;
                this._age = age;
                this._birthday = birthday;
                this.Student = new Student() { Count = count };
            }

            private Int32 _age;
            public Int32 Age { get { return _age; } set { _age = value; } }

            private String _name;
            public String Name { get { return _name; } set { _name = value; } }


            private DateTime _birthday;
            public DateTime Birthday { get { return _birthday; } set { _birthday = value; } }


            public Student Student { get; set; }
            public void Print(String title)
            {
                Console.WriteLine(title);
                Console.WriteLine($"基本信息:姓名:{this.Name},年龄:{this.Age},生日:{this.Birthday.ToString("D")}");
                Console.WriteLine($"引用类型的值{Student.ToString()}");
                Console.WriteLine();
            }

            //实现深拷贝拷贝
            public Object Clone()
            {
                System.IO.Stream stream = new MemoryStream();
                try
                {
                    System.Runtime.Serialization.IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                    formatter.Serialize(stream, this);
                    stream.Seek(0, SeekOrigin.Begin);
                    return formatter.Deserialize(stream);
                }
                finally {
                    stream.Close();
                    stream.Dispose();
                }

            }
        }

        [Serializable]
        class Student
        {
            public Int32 Count { get; set; }
            public override string ToString()
            {
                return Count.ToString();
            }
        }
        static void Main()
        {
            //创建源对象
            Teacher Source = new Teacher("Fode", 18, DateTime.Now, 22);
            Source.Print("源对象");

            //深拷贝对象
            Teacher Target = Source.Clone() as Teacher;
            Target.Name = "JJ";
            Target.Student.Count = 11;
            Console.WriteLine("新对象的引用类型的值发生变化");

            Target.Print("新对象");
            Source.Print("源对象");

            Console.ReadKey();
        }

 其结果如下:

可以发现,此时拷贝后的Target对象与源对象没有任何关系。修改源对象的引用类型并不会影响对应新对象的值。最后在把代码优化一下,在一个类中同时实现深拷贝和浅拷贝:

            //实现浅拷贝
            public Object Clone()
            {
                return this.MemberwiseClone();
            }

            /// <summary>
            /// 获得浅拷贝对象
            /// </summary>
            /// <returns></returns>
            public Teacher ShallowClone()
            {
                return this.Clone() as Teacher;
            }

            /// <summary>
            /// 获得深拷贝对象
            /// </summary>
            /// <returns></returns>
            public Teacher DeepClone()
            {
                System.IO.Stream stream = new MemoryStream();
                try
                {
                    System.Runtime.Serialization.IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                    formatter.Serialize(stream, this);
                    stream.Seek(0, SeekOrigin.Begin);
                    return formatter.Deserialize(stream) as Teacher;
                }
                finally
                {
                    stream.Close();
                    stream.Dispose();
                }
            }

注:ICloneable接口适用于

.NET Core

2.2 2.1 2.0 1.1 1.0

.NET Framework

4.8 4.7.2 4.7.1 4.7 4.6.2 4.6.1 4.6 4.5.2 4.5.1 4.5 4.0 3.5 3.0 2.0 1.1

.NET Standard

2.0 1.6 1.5 1.4 1.3 1.2 1.1 1.0

Xamarin.Android

7.1

Xamarin.iOS

10.8

Xamarin.Mac

3.0
 
误区:要区别赋值操作,当对象采用赋值操作"="其实是引用源对象在堆中的地址,他们两个对象引用的是同一个地址,所以说,当源对象(或新对象)的值类型改变后都会影响到其新对象(或源对象)。具体代码如下:
            Teacher Source = new Teacher("Fode", 18, DateTime.Now, 22);
            Teacher Target = Source;
            Source.Print("源对象");
            Console.WriteLine("源对象的值类型发生改变");
            Source.Name = "JJ"; Source.Age = 22;
            Source.Print("源对象");
            Target.Print("新对象");

            Console.WriteLine("新对象的值类型发生改变");
            Target.Name = "范冰冰"; Target.Age = 18;
            Source.Print("源对象");
            Target.Print("新对象");
            Console.ReadKey();    

输出结果如下:

 

 

 

 

posted @ 2018-12-05 21:51  Fode  阅读(1961)  评论(0编辑  收藏  举报