List中的值类型无法修改的原因详解

http://blog.csdn.net/wuyazhe/article/details/5676881



[c-sharp]  view plain copy
  1. public struct AA  
  2. {  
  3.     public int value;  
  4.     public AA(int v)  
  5.     {  
  6.         value = v;  
  7.     }  
  8. }  
  9. public class Test  
  10. {  
  11.     public void Run()  
  12.     {  
  13.         List<AA> datas = new List<AA>();  
  14.         datas.Add(new AA(1));  
  15.         datas.Add(new AA(2));  
  16.     }  
  17. }  

我们假设有这样的一个结构体。因为结构体是值类型的,在没有修饰的情况下,我们的方法中,传入,传出都是传递的值,每次传递都进行了一次值的拷贝。

所以,我们这样操作是不可行的。

datas[1].value = 10;

为什么呢,因为datas[1]不是第二个对象,而是第二个对象的副本,你修改副本,当然不会影响原本的值了。正确的写法是这样的

datas[1] = new AA(10);

 

这只是开始。可能有人会问,为什么我用data[i]是这个元素的副本呢?我们来详细的介绍一下。

首先,请先阅读MSDN以增加一些基础的了解

struct(C# 参考)

结构(C# 编程指南)

如何:了解向方法传递结构和向方法传递类引用之间的区别(C# 编程指南)

 

了解过基础,我们看看正题

[c-sharp]  view plain copy
  1. void Foo(StructValue o){}  
  2. //没有人怀疑这里的o是个结构体对象的副本。对吧。这个还有疑问的复习msdn去。  
  3. StructValue Foo()  
  4. {  
  5.   StructValue f;  
  6.   return f;//你认为这里返回的是f还是f的副本呢?显然也是副本。  
  7. }  

这里都明白,返回的f是定义的f的一个副本,没有问题吧。
看看List<T>的索引器
[c-sharp]  view plain copy
  1. public T this[int index]  
  2. {  
  3.     [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]  
  4.     get  
  5.     {  
  6.         if (index >= this._size)  
  7.         {  
  8.             ThrowHelper.ThrowArgumentOutOfRangeException();  
  9.         }  
  10.         return this._items[index];//这里返回的是this._items[index]的副本,能理解了吧。  
  11.     }  
  12.     [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]  
  13.     set  
  14.     {  
  15.         if (index >= this._size)  
  16.         {  
  17.             ThrowHelper.ThrowArgumentOutOfRangeException();  
  18.         }  
  19.         this._items[index] = value;//这里是把你的value副本复制到索引器指定的位置  
  20.         this._version++;  
  21.     }  
  22. }  

 

以上3个例子看懂。这个问题就清晰了。

 

贴的这3个代码已经很明显的告诉你这样一个原因:

data[1]是通过List<T>的索引器访问的值类型数组中的某个元素,返回的是这个元素的副本。而设置也是副本设置,所以可以设置,可以读取,不可以通过副本的修改影响到List<T>中的值类型数组。

为了进一步证明这一点,我来用一个反射的例子来演示一下:

[c-sharp]  view plain copy
  1. private static void TestChangeStructList()  
  2. {  
  3.     //定义一个泛型List  
  4.     List<AA> datas = new List<AA>();  
  5.     //添加2个元素,value分别为1,2  
  6.     datas.Add(new AA(1));  
  7.     datas.Add(new AA(2));  
  8.     //打印出当前元素  
  9.     Console.WriteLine("原始List:");  
  10.     datas.ForEach(o => Console.WriteLine(o.value.ToString()));  
  11.     //使用List的索引器赋值  
  12.     datas.ForEach(o => o.value++);  
  13.     //打印出当前元素  
  14.     Console.WriteLine("使用List的索引器赋值");  
  15.     datas.ForEach(o => Console.WriteLine(o.value.ToString()));  
  16.     //反射List内部的值类型数组赋值  
  17.     AA[] items = (AA[])((FieldInfo)(datas.GetType().GetMember("_items", BindingFlags.NonPublic | BindingFlags.Instance)[0])).GetValue(datas);  
  18.     items[0].value++;  
  19.     items[1].value++;  
  20.     //打印出当前元素  
  21.     Console.WriteLine("反射List内部的值类型数组赋值");  
  22.     datas.ForEach(o => Console.WriteLine(o.value.ToString()));  
  23. }  

 

结果:
原始List:
1
2
使用List的索引器赋值
1
2
反射List内部的值类型数组赋值
2
3

 

 

还没看懂的,最后再讲一次。
List<T>[i]
这是叫做索引器的,索引器是一种属性,属性就是在调用方法,而值类型无法返回一个引用,返回的是值,所以索引器返回的,是你添加进去变量的副本。而因为值类型无法传递引用,所以添加实际也是使用副本的方式添加的。所以对于值类型的List<T>,索引器的结果,可以访问,可以修改,但无法直接存回去,如何保存?可以重新的赋值,例如
List<Point> points = new List<Point>();
points.Add(new Point());//0,0
修改呢,就整个重新复制
points[0] = new Point(1,1);
你不能修改一项points[0].X = 1;
这样不可以的。

希望这样说,各位不明白的能明白。明白的更明白。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值