C# 淺複製和深複製


Hi all,

           其實最近一直在做WPF的項目,關於UI,MVVM,多線程,devexpress都想寫些東西,可惜忙着,雖然平時感悟挺深,也來不及整理,

也不想隨便寫寫忽悠大家,畢竟不是那種隨便寫寫都能寫出好文的大神,只是想好好的分享下項目過程中遇到的一些容易放錯的地方,俗話說

“知錯能改”嘛。

           嘻嘻,其實這篇也是忽悠,只是今天在寫複雜function時遇到而已,一時沒注意就錯了都不知道。

           

以下僅個人理解加某些專業術語,有不對的地方請原諒。


           “淺複製是對引用類型地址的複製,而深複製是對值類型的複製。”

             這句話有兩個個名詞,

             值類型:派生自System.ValueType的類型,我們常見的 int  char  long  struct  short 等等的數值或結構體或enum,

             引用類型:派生自System.object的類型,如 string   數組array  類class  list 等高級的數據類型。


         文字不好說,還是直接上代碼說明:(一個極簡的CLI程序)

       

            string aaa = "have";
            string bbb = aaa;
            bbb = bbb + "_lin";
            Console.WriteLine(aaa);


           結果是:have。

           這個例子有點特殊,故意舉例這個的,雖說string是引用類型,但是在等號複製的過程中還是“深複製”,具體原因查過資料才知道:

             

      //表示空字符串。此字段爲只讀。
      public static readonly string Empty;

         答案就在於 string 是 readonly 的,當改變 string 類型的數據值時,將重新分配了內存地址。

            我們舉另一個例子List類型:

              

           List<string> arr1 = new List<string>();
            arr1.Add("1");
            arr1.Add("2");
            arr1.Add("3");

            var arr2 = arr1;

            arr2[1] = "lin";

            foreach(string s in arr1)
            {
                Console.WriteLine(s);
            }

  

            結果是:“1”   “lin”  "3"  ,

            上面的程序中,我們修改的是arr2,但arr1也同時被修改了,這說明arr1和arr2是使用一個值的,這就是引用類型的特點。


最後說明注意場景,例子僅自己今天遇到的,還是直接上代碼(有點長):





     小結,在寫長代碼片段是,對臨時變量的管理特重要,在這個例子中,因爲要對新對象處理,對舊對象使用檢索,

所以不能讓新對象影響舊對象,看上去自然的邏輯,其實上錯在僅僅的一行代碼,這是很冤枉的。


best regards


希望能騰點時間來寫wpf的專題。


補充1:

    很不幸,真的只能說是技術沒到家:。

    對於List這樣的集合,提供了支持深複製的方法,GetRange,還是直接上代碼:

 List<string> arr1 = new List<string>();
            arr1.Add("1");
            arr1.Add("2");
            arr1.Add("3");

            var arr2 = arr1;
            changeList(arr2.GetRange(0, arr2.Count));
            arr2[1] = "lin";

            foreach (string s in arr1)
            {
                Console.WriteLine(s);
            }

private static void changeList(List<string> list)
        {
            if (list.Count != 0)
            {
                list[0] = "haha";
            }
        }

最後結果是:‘1’    ‘lin’    ‘3’


代碼裏頭有兩處地方修改List,分別是index 0和1,但最終只有index 1改變,爲此,說明GetRange方法是對arr2對象的一個深複製。



回過頭來寫這一段,也是爲了指出之前犯的錯誤(CTO跟我說你那個函數遲早會有問題),我才痛定思痛的看了不知道多少遍,

直接上代碼說明:


Model層數據

//使用localDB連接數據庫獲取數據      
  public static Player[] GetPlayerByRegionId(Guid regionId)
        {
            using (var dbContext = new LocalModel())
            {
                var players = (from p in dbContext.Players where p.RegionId == regionId select p).ToArray();
                return players;
            }
        }


Control層調用

        private void Sample1(Guid regionId)
        {
            var players1 = LocalDBAccess.GetPlayerByRegionId(regionId);
            List<Player> FirstListObj = new List<Player>(players1);
            //接下來是複製對象部分,先舉錯誤示例,
            //用我上面的方法獲取一個相同的對象,做法是這樣
            var playeres2 = LocalDBAccess.GetPlayerByRegionId(regionId);
            List<Player> LaterListObj = new List<Player>(playeres2);
            //然後各自操作  FirstListObj  ,,LaterListObj
            .......
            .......
       }


這裏想法上是沒有錯誤的,只是在大型一點的工程,流量大的網站,我們就不能保證 FirstListObj 和 LaterListObj 是一樣的。

在沒有訪問鎖(訪問限制)的數據庫中,在user1執行完 GetPlayerByRegionId 方法後,來自user2 給 相關表添加一條數據,

那麼用戶再次執行 GetPlayerByRegionId 方法時,他獲取到的 LaterListObj 就與FirstListObj 不一致,並沒有實現複製的功能。


下面改變Control層調用

private void Sample2(Guid regionId)
        {
            var players1 = LocalDBAccess.GetPlayerByRegionId(regionId);
            List<Player> FirstListObj = new List<Player>(players1);
            //接下來是複製對象部分,正確示例,
            //使用GetRange方法,其他的集合對象也有類似的方法
            var LaterListObj = FirstListObj.GetRange(0, FirstListObj.Count);
            //然後各自操作  FirstListObj  ,,LaterListObj
           ......
           ...... 
        }

這裏我們對數據連接只有一次,所以數據庫的改變並不會馬上反應到變量中,所有操作均是在基礎數據上。


最後的最後,特意挖出來重新舉例,一是承認自己的錯,而勉勵自己繼續努力。


最後修改2017年3月31日11:15:39



 




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章