淺談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源碼下載

 


免責聲明!

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



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