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、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>一致的特征。
//--------------勤勤恳恳,做一只搬运知识的蚂蚁--------------//
本文来自博客园,作者:MaQaQ,转载请注明原文链接:https://www.cnblogs.com/magicMaQaQ/p/15001715.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。


浙公网安备 33010602011771号