C# 浅复制和深复制

来源:互联网 发布:薛之谦的淘宝女装店 编辑:程序博客网 时间:2024/06/06 23:56

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



 




0 0