理解淺拷貝和深拷貝

一、淺拷貝與深拷貝的定義api

  什麼是拷貝?拷貝即爲常說的複製或者克隆一個對象,而且經過拷貝這些源對象建立新的對象。其中拷貝分爲淺拷貝和深拷貝。對於拷貝出來的對象,在使用上有很大的差別,特別是在引用類型上。
ide

  淺拷貝:將對象中的全部字段複製到新的對象中。其中,值類型字段被複制到新對象中後,在新對象中的修改不會影響到原先對象的值。而新對象的引用類型則是原先對象引用類型的引用,不是引用本身對象自己。:在新對象中修改引用類型的值會影響到原先對象;理論上String也是引用類型,可是因爲因爲該類型比較特殊,Object.MemberwiseClone()方法依舊爲其新對象開闢了新的內存空間存儲String的值,在淺拷貝中把String類型看成'值類型'便可。優化

   深拷貝:一樣也是拷貝,可是與淺拷貝不一樣的是,深拷貝會對引用類型從新在創新一次(包括值類型),在新對象作的任何修改都不會影響到源對象自己。this

   

二、實現淺拷貝與深拷貝spa

   在 .Net平臺開發中,要實現拷貝,微軟官方建議繼承ICloneable接口,該接口位於System命名空間下,該接口只實現一個Clone方法,咱們能夠根據具體項目需求在該方法內實現淺拷貝或者深拷貝。先實現一個淺拷貝,具體代碼以下:3d

 

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

 其運行結果以下:code

能夠發現當新對象的引用類型發生改變後,其源對象的引用類型也發生改變(String類型除外),他們共同引用的是Student這個引用類型對象(即便發生變化的是其裏面的值類型),而新對象的值類型改變並不會到源類型的值類型。orm

 

而對於要實現深拷貝則有不少中方法了,好比在拷貝方法裏面 直接一個個屬性字段賦值,可是一旦爲源對象新增屬性或者字段的時候,容易忘了修改拷貝方法中的值,最好使用序列化的方法進行深拷貝。深拷貝簡單代碼以下,與淺拷貝一樣的案例,只是重寫了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();
        }

 其結果以下:blog

能夠發現,此時拷貝後的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();    

輸出結果以下:

相關文章
相關標籤/搜索