理解淺拷貝和深拷貝


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

輸出結果如下:

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM