C# List<T>复制

//--------------忙忙碌碌,从代码的搬运工做起--------------//

  在C#中,我们会使用List<T>存储数据,而后,当我们在别处对List<T>中的数据轮询并做逻辑处理时,有时却会发现数据被意外改动了,尤其是在多线程中,这就会引发一系列错误。因此,我们就需要对List<T>做进一步的了解,当然如果你不想了解,那就直接拖到下方看方法代码

  首先呢,我们要有个概念,值类型和引用类型。值类型存放的是实际的值,引用类型存放的是值的引用地址。

  啥意思呢,举个不知道恰不恰当的栗子,张三去买厕纸,掏出了一张纸币付款,纸币上写着10元,这个是值类型;张三去买车子,掏出了一张黑不溜秋的银行卡付款,卡上写着银行卡号,这张卡就是引用类型,通过卡号这个地址,可以划扣掉张三账户里的一百万。

//-----------------------------分割线-----------------------------//

  • 值类型  

  测试如下:

byte bValue1 = 0;
//打印bValue1 
byte bValue2 = bValue1;
//打印bValue1 、bValue2 
bValue1 = 1;
//打印bValue1 、bValue2 
bValue2 = 2;
//打印bValue1 、bValue2 

  输出如下:

bValue1-0
***********************
bValue1-0,bValue2-0
***********************
bValue1-1,bValue2-0
***********************
bValue1-1,bValue2-2

  结论:

   将一个值类型变量的值赋给另一个值类型变量,它传递的是实际的值,对两个变量的操作,结果互不干扰。

  • 引用类型

  测试如下:

List<byte> lsClone1 = new List<byte>();
lsClone1.Add(1);
//打印lsClone1 
List<byte> lsClone2 = lsClone1;
//打印lsClone1、lsClone2 
lsClone1.Add(2);
//打印lsClone1、lsClone2 
lsClone2.Add(3);
//打印lsClone1、lsClone2 

  输出如下:

ls1-01 
***********************
ls1-01 ,ls2-01 
***********************
ls1-01 02 ,ls2-01 02 
***********************
ls1-01 02 03 ,ls2-01 02 03 

  结论:

   将一个引用类型变量直接赋给另一个引用类型变量,那么它传递的是引用地址,对不同变量的操作,实际上指向的是同个地址,因此两个变量的存储值保持一致。

  • 再举个栗子

  值类型:发工资这天,老板王五从金库里拿出了1W元给员工张三,又从金库里拿出了一样多的钱给员工李四,他们两个拿了钱各自消费,大家其乐融融。

  引用类型:过了一段时间,老板宣布换一种方式发工资,他先给张三发了一把钥匙,说这是金库的钥匙,张三确认了金库里存有1W元,然后再把李四叫进来,给了他一把跟张三一样的钥匙,李四也确认了金库里存有1W元,但是过了不久,张三把金库里的钱取走了,等到李四想去取钱的时候,只能空手而归,于是矛盾产生了,说不定还得打一架。

  实际上,方式2可以看作是对引用类型的错误使用。

 //-----------------------------分割线-----------------------------//

  言归正传,List<T>就属于引用类型,对它的操作就得考虑引用类型的特性。

  一般而言,对于List<T>的复制可以分为浅拷贝和深拷贝两种。

  浅拷贝就是将变量A的引用地址复制给变量B,变量A与变量B指向同个地址,因此A与B对实际内容的修改是同步的。

  深拷贝是给变量B重新分配一个地址,把变量A地址指向的内容复制给B,因此A与B对实际内容的修改是互不干扰的。

  深复制的方式可以有反射、序列化等很多种,就个人理解而言,为了达到重新分配地址的目的,可以将引用类型转换成值类型,再重新转换回引用类型,比如利用Json的序列化与反序列化,或者是遍历List<T>,在值类型的层次将所有项重新赋值。

  • 方法代码:

  在百度上搜了一下,找了几种方法,如下所示:

 方法1:利用Json的序列化与反序列化,需要引用Newtonsoft.Json

     public static List<T> Clone_ByJson<T>(this List<T> list) where T : new()
        {
            var str = JsonConvert.SerializeObject(list);
            return JsonConvert.DeserializeObject<List<T>>(str);
        }

方法2:利用反射

     public static List<T> Clone_ByProperties<T>(this List<T> list) where T : new()
        {
            List<T> items = new List<T>();
            foreach (var m in list)
            {
                var model = new T();
                var ps = model.GetType().GetProperties();
                var properties = m.GetType().GetProperties();
                foreach (var p in properties)
                {
                    foreach (var pm in ps)
                    {
                        if (pm.Name == p.Name)
                        {
                            pm.SetValue(model, p.GetValue(m));
                        }
                    }
                }
                items.Add(model);
            }
            return items;
        }

方法3:利用IFormatter的序列化与反序列化,需要引用System.Runtime.Serialization等,需要实体类有[Serializable]特性

     public static List<T> Clone_ByStream<T>(this List<T> list)
        {
            using (Stream objectStream = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(objectStream, list);
                objectStream.Seek(0, SeekOrigin.Begin);
                return (List<T>)formatter.Deserialize(objectStream);
            }
        }

测试:

        [Serializable]//方法3需要
        public class TestClone
        {
            public TestClone()
            {
                ID = 0;
                Items = new byte[] { 0, 1 };
                Remark = "";
            }
            public int ID { get; set; }
            public byte[] Items { get; set; }
            public string Remark { get; set; }
        }

        private void button_Click(object sender, EventArgs e)
        {
            List<TestClone> clone = new List<TestClone>();
            int i = 0;
            TestClone c = new TestClone();
            i = 1;
            c.ID = i; c.Items = new byte[] { 1, 2, 3 }; c.Remark = "No.1";
            clone.Add(c);
            //打印clone:count-1,index-0,1-[01 02 03 ]-No.1
            i = 2;
            c.ID = i; c.Items = new byte[] { 2, 2, 2 }; c.Remark = "No.2";
            clone.Add(c);
            //打印clone count-2,index-0,2-[02 02 02 ]-No.2 //可以看到元素0的Items项被改变了,元素0跟元素1指向的地址是相同的
              count-2,index-1,2-[02 02 02 ]-No.2
c = new TestClone(); i = 3; c.ID = i; c.Items = new byte[] { 3, 3, 3 }; c.Remark = "No.3"; clone.Add(c); //打印clone:count-3,index-0,2-[02 02 02 ]-No.2
              count-3,index-1,2-[02 02 02 ]-No.2
              count-3,index-2,3-[03 03 03 ]-No.3 //新加的元素2是将c重新new了,所以不会影响前两个元素的Items项
TestClone c1 = new TestClone(); i = 4; c1.ID = i; c1.Items = new byte[] { 4, 4, 4 }; c1.Remark = "No.4"; clone.Add(c1); //打印clone:count-4,index-0,2-[02 02 02 ]-No.2
              count-4,index-1,2-[02 02 02 ]-No.2
              count-4,index-2,3-[03 03 03 ]-No.3
              count-4,index-3,4-[04 04 04 ]-No.4 //新定义的c1,自然也不会影响前面的元素

       List<TestClone> clone1 = clone; List<TestClone> clone2 = StaticLogic.Clone_ByJson(clone); List<TestClone> clone3 = StaticLogic.Clone_ByProperties(clone); List<TestClone> clone4 = StaticLogic.Clone_ByStream(clone); clone[0].Items = new byte[] { 0, 0, 0 }; clone[0].Remark = "No.1.1.1";
clone,count-4,index-0,2-[00 00 00 ]-No.1.1.1
clone,count-4,index-1,2-[00 00 00 ]-No.1.1.1
clone,count-4,index-2,3-[03 03 03 ]-No.3
clone,count-4,index-3,4-[04 04 04 ]-No.4
***********************
//直接将clone赋给clone1,则clone与clone1的变化是同步的
clone1,count-4,index-0,2-[00 00 00 ]-No.1.1.1
clone1,count-4,index-1,2-[00 00 00 ]-No.1.1.1
clone1,count-4,index-2,3-[03 03 03 ]-No.3
clone1,count-4,index-3,4-[04 04 04 ]-No.4
***********************
//使用方法1将clone复制给clone2,可以看到对clone的修改不影响clone2
clone2,count-4,index-0,2-[02 02 02 ]-No.2
clone2,count-4,index-1,2-[02 02 02 ]-No.2
clone2,count-4,index-2,3-[03 03 03 ]-No.3
clone2,count-4,index-3,4-[04 04 04 ]-No.4
***********************
//使用方法2将clone复制给clone3,可以看到对clone的修改不影响clone3
clone3,count-4,index-0,2-[02 02 02 ]-No.2
clone3,count-4,index-1,2-[02 02 02 ]-No.2
clone3,count-4,index-2,3-[03 03 03 ]-No.3
clone3,count-4,index-3,4-[04 04 04 ]-No.4
***********************
//使用方法3将clone复制给clone4,可以看到对clone的修改不影响clone4
clone4,count-4,index-0,2-[02 02 02 ]-No.2
clone4,count-4,index-1,2-[02 02 02 ]-No.2
clone4,count-4,index-2,3-[03 03 03 ]-No.3
clone4,count-4,index-3,4-[04 04 04 ]-No.4
//打印clone、clone1、clone2、clone3、clone4
       clone2[0].Items = new byte[] { 0, 0, 0 };
            clone2[0].Remark = "No.1.1.2";
            //打印clone2:count-4,index-0,2-[00 00 00 ]-No.1.1.2
               count-4,index-1,2-[02 02 02 ]-No.2 //对元素0的修改不会影响元素1,此时元素0跟元素1指向的地址已经不同
               count-4,index-2,3-[03 03 03 ]-No.3
               count-4,index-3,4-[04 04 04 ]-No.4
clone3[0].Items = new byte[] { 0, 0, 0 }; clone3[0].Remark = "No.1.1.3"; //打印clone3:count-4,index-0,2-[00 00 00 ]-No.1.1.3
               count-4,index-1,2-[02 02 02 ]-No.2 //对元素0的修改不会影响元素1,此时元素0跟元素1指向的地址已经不同
               count-4,index-2,3-[03 03 03 ]-No.3
               count-4,index-3,4-[04 04 04 ]-No.4
clone4[0].Items = new byte[] { 0, 0, 0 }; clone4[0].Remark = "No.1.1.4"; //打印clone4:count-4,index-0,2-[00 00 00 ]-No.1.1.4
               count-4,index-1,2-[00 00 00 ]-No.1.1.4 //对元素0的修改会影响元素1,此时元素0与元素1指向的地址是相同的
               count-4,index-2,3-[03 03 03 ]-No.3
               count-4,index-3,4-[04 04 04 ]-No.4
}

 结论:

  抛开性能的角度不讲,三种方法都可以实现List<T>的深复制,其中,方法1与方法2类似,会将原来指向同一地址的不同元素重新分配成不同地址,方法3则保留跟原来List<T>一致的特征。

 

//--------------勤勤恳恳,做一只搬运知识的蚂蚁--------------//

posted @ 2021-07-30 13:57  MaQaQ  阅读(750)  评论(0编辑  收藏  举报