List中的值类型无法修改的原因详解
2014-03-03 15:09
417 查看
[c-sharp] view
plaincopy
public struct AA
{
public int value;
public AA(int v)
{
value = v;
}
}
public class Test
{
public void Run()
{
List<AA> datas = new List<AA>();
datas.Add(new AA(1));
datas.Add(new AA(2));
}
}
我们假设有这样的一个结构体。因为结构体是值类型的,在没有修饰的情况下,我们的方法中,传入,传出都是传递的值,每次传递都进行了一次值的拷贝。
所以,我们这样操作是不可行的。
datas[1].value = 10;
为什么呢,因为datas[1]不是第二个对象,而是第二个对象的副本,你修改副本,当然不会影响原本的值了。正确的写法是这样的
datas[1] = new AA(10);
这只是开始。可能有人会问,为什么我用data[i]是这个元素的副本呢?我们来详细的介绍一下。
首先,请先阅读MSDN以增加一些基础的了解
struct(C# 参考)
结构(C# 编程指南)
如何:了解向方法传递结构和向方法传递类引用之间的区别(C#
编程指南)
了解过基础,我们看看正题
[c-sharp] view
plaincopy
void Foo(StructValue o){}
//没有人怀疑这里的o是个结构体对象的副本。对吧。这个还有疑问的复习msdn去。
StructValue Foo()
{
StructValue f;
return f;//你认为这里返回的是f还是f的副本呢?显然也是副本。
}
这里都明白,返回的f是定义的f的一个副本,没有问题吧。
看看List<T>的索引器
[c-sharp] view
plaincopy
public T this[int index]
{
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
get
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return this._items[index];//这里返回的是this._items[index]的副本,能理解了吧。
}
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
set
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
this._items[index] = value;//这里是把你的value副本复制到索引器指定的位置
this._version++;
}
}
以上3个例子看懂。这个问题就清晰了。
贴的这3个代码已经很明显的告诉你这样一个原因:
data[1]是通过List<T>的索引器访问的值类型数组中的某个元素,返回的是这个元素的副本。而设置也是副本设置,所以可以设置,可以读取,不可以通过副本的修改影响到List<T>中的值类型数组。
为了进一步证明这一点,我来用一个反射的例子来演示一下:
[c-sharp] view
plaincopy
private static void TestChangeStructList()
{
//定义一个泛型List
List<AA> datas = new List<AA>();
//添加2个元素,value分别为1,2
datas.Add(new AA(1));
datas.Add(new AA(2));
//打印出当前元素
Console.WriteLine("原始List:");
datas.ForEach(o => Console.WriteLine(o.value.ToString()));
//使用List的索引器赋值
datas.ForEach(o => o.value++);
//打印出当前元素
Console.WriteLine("使用List的索引器赋值");
datas.ForEach(o => Console.WriteLine(o.value.ToString()));
//反射List内部的值类型数组赋值
AA[] items = (AA[])((FieldInfo)(datas.GetType().GetMember("_items", BindingFlags.NonPublic | BindingFlags.Instance)[0])).GetValue(datas);
items[0].value++;
items[1].value++;
//打印出当前元素
Console.WriteLine("反射List内部的值类型数组赋值");
datas.ForEach(o => Console.WriteLine(o.value.ToString()));
}
结果:
原始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;
这样不可以的。
希望这样说,各位不明白的能明白。明白的更明白。
plaincopy
public struct AA
{
public int value;
public AA(int v)
{
value = v;
}
}
public class Test
{
public void Run()
{
List<AA> datas = new List<AA>();
datas.Add(new AA(1));
datas.Add(new AA(2));
}
}
我们假设有这样的一个结构体。因为结构体是值类型的,在没有修饰的情况下,我们的方法中,传入,传出都是传递的值,每次传递都进行了一次值的拷贝。
所以,我们这样操作是不可行的。
datas[1].value = 10;
为什么呢,因为datas[1]不是第二个对象,而是第二个对象的副本,你修改副本,当然不会影响原本的值了。正确的写法是这样的
datas[1] = new AA(10);
这只是开始。可能有人会问,为什么我用data[i]是这个元素的副本呢?我们来详细的介绍一下。
首先,请先阅读MSDN以增加一些基础的了解
struct(C# 参考)
结构(C# 编程指南)
如何:了解向方法传递结构和向方法传递类引用之间的区别(C#
编程指南)
了解过基础,我们看看正题
[c-sharp] view
plaincopy
void Foo(StructValue o){}
//没有人怀疑这里的o是个结构体对象的副本。对吧。这个还有疑问的复习msdn去。
StructValue Foo()
{
StructValue f;
return f;//你认为这里返回的是f还是f的副本呢?显然也是副本。
}
这里都明白,返回的f是定义的f的一个副本,没有问题吧。
看看List<T>的索引器
[c-sharp] view
plaincopy
public T this[int index]
{
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
get
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return this._items[index];//这里返回的是this._items[index]的副本,能理解了吧。
}
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
set
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
this._items[index] = value;//这里是把你的value副本复制到索引器指定的位置
this._version++;
}
}
以上3个例子看懂。这个问题就清晰了。
贴的这3个代码已经很明显的告诉你这样一个原因:
data[1]是通过List<T>的索引器访问的值类型数组中的某个元素,返回的是这个元素的副本。而设置也是副本设置,所以可以设置,可以读取,不可以通过副本的修改影响到List<T>中的值类型数组。
为了进一步证明这一点,我来用一个反射的例子来演示一下:
[c-sharp] view
plaincopy
private static void TestChangeStructList()
{
//定义一个泛型List
List<AA> datas = new List<AA>();
//添加2个元素,value分别为1,2
datas.Add(new AA(1));
datas.Add(new AA(2));
//打印出当前元素
Console.WriteLine("原始List:");
datas.ForEach(o => Console.WriteLine(o.value.ToString()));
//使用List的索引器赋值
datas.ForEach(o => o.value++);
//打印出当前元素
Console.WriteLine("使用List的索引器赋值");
datas.ForEach(o => Console.WriteLine(o.value.ToString()));
//反射List内部的值类型数组赋值
AA[] items = (AA[])((FieldInfo)(datas.GetType().GetMember("_items", BindingFlags.NonPublic | BindingFlags.Instance)[0])).GetValue(datas);
items[0].value++;
items[1].value++;
//打印出当前元素
Console.WriteLine("反射List内部的值类型数组赋值");
datas.ForEach(o => Console.WriteLine(o.value.ToString()));
}
结果:
原始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;
这样不可以的。
希望这样说,各位不明白的能明白。明白的更明白。
相关文章推荐
- List中的值类型无法修改的原因详解
- MySQL 5.7.16 修改密码提示 ERROR 1054 (42S22): Unknown column ''password'' in ''field list''的原因
- 安装版JDK后,修改环境变量,也无法生效的原因和解决办法
- 在源列表 /etc/apt/sources.list.d/google-chrome.list 中第 1 行有误 (类型) E: 无法读取源列表。
- MVC 无法将类型“System.Collections.Generic.List<AnonymousType#1>”隐式转换为“System.Collections.Generic.IList<Mvc3Modeltest.Models.Movie>”。存在一个显式转换(是否缺少强制转换?))
- 为什么我上传了flv或MP4文件到服务器,可输入正确地址通过http协议来访问总是出现“无法找到该页”的404错误呢?这就表明mp4格式文件是服务器无法识别的,其实,这是没有在iis中将相应的MIME类型进行设置的原因。那该怎样设置MIME
- android修改开机动画bootaninmation.zip后无法执行的原因
- redis中list类型详解及常用命令
- Scala 深入浅出实战经典 第81讲:Scala中List的构造是的类型约束逆变、协变、下界详解
- 错误 1 无法将带 [] 的索引应用于“StrigDs.SeqList”类型的表达式 C:/Inetpub/wwwroot/StrigDs/StrigDs/Form5.cs 39 13 StrigDs
- [置顶] REDIS数据类型详解LIST-HASH-SET-STRING
- 如何修改DeDe标签Pagelist分页样式详解
- 安装版JDK后,修改环境变量,也无法生效的原因和解决办法
- List类型对应的jedis操作详解
- java中报错"不兼容类型:java.lang.String无法转换为String"原因
- 无法分析从服务器收到的消息。之所以出现此错误,常见的原因是: 在通过调用 Response.Write() 修改响应时,将启用响应筛选器、HttpModule 或服务器跟踪。
- CS1612: 无法修改“¡°System.Collections.Generic.List.this[int]””的返回值,因为它不是变量。【C# 语言中 struct 的陷阱】
- 无法将类型为“Excel.ApplicationClass”的 COM 对象强制转换为接口类 型“Excel._Application”。此操作失败的原因是对 IID 为“{000208D5 -0000-0000-C000-000000000046}”的接口的 COM 组件调用 QueryInterface 因以下错误而失败: 加载类型库/DLL 时出错。 (异常来 自 HRESULT:
- Intersge无法启动--原因是Message Queues的资源不足,共享内存不足 RedHat如何在系统运行过程中修改内核参数
- win10下修改host后仍无法打开本地网页原因