//--------------忙忙碌碌,从代码的搬运工做起--------------//
在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
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>一致的特征。
//--------------勤勤恳恳,做一只搬运知识的蚂蚁--------------//