Q1:

首先定义一个结构

public struct MyStruct { public int T; }

定义一个泛型List来存放结构体,然后访问第一个元素去修改T,输出T:

List<MyStruct> arrLis =new List<MyStruct>(){new MyStruct()};

arrLis[0].T = 100;

Console.WriteLine(arrLis[0].T);

大家猜是什么结果?

很遗憾不是100,arrLis[0].T = 100;VS提示该语句有错误。Cannot modify the expression because it is not a variable.

说修改的不是一个变量。

这是为什么呢?

关于这个问题我们首先来看一下List的源码

其实List[]被称做索引器。索引的实现其实类似属性,靠一对Get,Set方法来实现的。索引器其实只是C#的语法糖而已。那么很明显我们上面的语句其实只是调用了get_Item方法而已,且返回值MyStruct是个值类型。所以get_Item方法返回的是一个值(value)。你也许会说,那又怎么样,我为什么就不能修改这个值。很不辛,在.NET中值(value)是不能被修改的,只有变量(variable)才能够被修改,这就是为什么变量称之为”变量”了:)。

Q2:

再看下面的代码,我们修改一下,把泛型List改为Array数组。

MyStruct[] arrStr =new MyStruct[1]{new MyStruct()};

arrStr[0].T = 100;

Console.WriteLine(arrStr[0].T);

你是否觉得这次赋值语句也会报错?

其实不然,代码顺利通过编译,运行成功。

结果输出:100

这太奇怪啦,为什么把List改成Array就没有问题了呢。

让我们继续查看一下源码

看到没,对于一维数组的访问其实是访问到了这个GetValue方法。该方法的意思是使用typeReference去取到位于index位置的对象的引用,然后转换为Object返回。看来原因就在这里了,对于数组的[]索引器其实是返回了对象的一个引用(地址),也就是相当于我们使用Array[0]访问的是得到的是一个变量(variable),所以可以直接给内部的成员变量赋值。

对于这段源码也许不是那么好理解,不妨看看IL。

ldelema:将位于指定数组索引的数组元素的地址作为 & 类型(托管指针)加载到计算堆栈的顶部。

这就很清楚了,在IL里面也清楚的显示,操作的是对象的地址。

到这里,Array跟List索引访问的区别出来了,Array是返回了对象的引用,而List返回的就是对象的值(值类型对象就是内部的值,引用类型对象是引用的地址)。

Q3:

还没完,既然直接给赋值不行,那我用一个Set方法包装起来,去设置内部变量的值如何?

public struct MyStruct {

public int T;

public void SetT(int t)

  { T = t; }

}

改造一下,加了一个SetT方法。

把List初始化语句也改一下,去掉一些语法糖,因为我们要查IL,语法糖会影响我们的判断。

A:

List<MyStruct> arrLis = new List<MyStruct>();

var myStruct = new MyStruct();

arrLis.Add(myStruct);

arrLis[0].SetT(100);

Console.WriteLine(arrLis[0].T);

以上代码顺利通过。

输出:0

那为什么直接访问方法就可以呢。其实arrLis[0].SetT(100); 这也可以算是一个语法糖。上面A段代码到了IL层面其实就相当于下面B段代码,IL还是会用一个局部变量去接arrLis[0]返回的值。

B:

List<MyStruct> arrLis = new List<MyStruct>();

var myStruct = new MyStruct();

arrLis.Add(myStruct);

var temp = arrLis[0];

temp.SetT(100);

Console.WriteLine(arrLis[0].T);

不信我们查一下IL:

左边是A段代码,右边是B段代码:

这2段IL只有红线画出来的地方不一样,其实就是一个变量命名不一样而已。

Q4:

那上面A段代码输出为什么是0呢?

这个也很好理解,既然arrLis[0].SetT(100); 相当于var temp = arrLis[0]; 那么值类型赋值操作,其实是把右边的值(副本)赋值给了左边的变量,我们用SetT来修改T的时候只是在修改temp里面的T而已。这个不用多解释吧。

总结:

当我们在List里面使用值类型的时候一定要格外小心,特别是使用结构体的时候,因为从表象上来说更像一个引用类型(结构可以定义方法,成员变量等),不知不觉你就会用引用类型对象的惯用法去处理问题,说不定就掉坑了。所以结构体最好定义为不可变的。

参考:

why-can-struct-change-their-own-fields

what-is-the-difference-between-listt-and-array-indexers

internals-of-array

Array,List,Struct可能被大家忽略的问题的更多相关文章

  1. Spark存储Parquet数据到Hive,对map、array、struct字段类型的处理

    利用Spark往Hive中存储parquet数据,针对一些复杂数据类型如map.array.struct的处理遇到的问题? 为了更好的说明导致问题的原因.现象以及解决方案,首先看下述示例: -- 创建 ...

  2. hive复杂格式array,map,struct使用

    -- 创建数据库表,以array作为数据类型 drop table if exists person; create table person( name string ,work_locations ...

  3. Adding a struct into an array(stackoverflow)

    Question: So lets say I have a struct like this: struct example_structure { int thing_one; int thing ...

  4. struct:二进制数据结构的打包与解包

    介绍 struct模块包括一些函数,这些函数可以完成字节串与原生Python数据类型(如数字和字符串)之间的转换 函数与Struct类 struct提供了一组处理结构值的模块级函数,另外还有一个Str ...

  5. [转]struct 用法深入探索

    struct用法深入探索 作者: Cloudward 1. struct的巨大作用 面对一个人的大型C/C++程序时,只看其对struct的使用情况我们就可以对其编写者的编程经验进行评估.因为一个大型 ...

  6. Swift Array copy 的线程安全问题

    Swift Array copy 的线程安全问题 NSArray 继承自 NSObject,属于对象,有 copy 方法.Swift 的 Array 是 struct,没有 copy 方法.把一个 A ...

  7. MaxCompute 2.0复杂数据类型之array

    1. 含义 类似于Java中的array.有序.可重复. 2. 场景 什么样的数据,适合使用array类型来存储呢?这里列举了几个我在开发中实际用到的场景. 2.1 标签类的数据 为什么说标签类数据适 ...

  8. CC+语言 struct 深层探索——CC + language struct deep exploration

    1        struct 的巨大作用 面对一个人的大型C/C++程序时,只看其对struct 的使用情况我们就可以对其编写者的编程经验进行评估.因为一个大型的C/C++程序,势必要涉及一些(甚至 ...

  9. Spark SQL 官方文档-中文翻译

    Spark SQL 官方文档-中文翻译 Spark版本:Spark 1.5.2 转载请注明出处:http://www.cnblogs.com/BYRans/ 1 概述(Overview) 2 Data ...

随机推荐

  1. git .gitignore 文件 解决二进制文件冲突问题

    .gitignore  主要是添加 忽略文件 .最近团队开发经常出现 UserInterfaceState.xcuserstate 冲突,打开发现是二进制文件 ,没法解决冲突. 只好 rm -rf 之 ...

  2. iOS解析Server端返回JSON数据

    在做quhao APP架构时,后台Server端使用了Java,提供WebService,而iOS和Android作为移动客户端.在做数据交互时,Server端返回JSON格式数据.由于iOS SDK ...

  3. WebSocket桌面客户端工具

    考虑到WebSocket的诸多优点和未来的趋势,去年底把服务端通讯全部由HTTP改成WebSocket,期间为了方便测试,做了这个小工具.共享出来以方便有同样需求的程序员. 下载的压缩包里含有源代码和 ...

  4. Codeforces Round #292 (Div. 1) C. Drazil and Park 线段树

    C. Drazil and Park 题目连接: http://codeforces.com/contest/516/problem/C Description Drazil is a monkey. ...

  5. Flink 剖析

    1.概述 在如今数据爆炸的时代,企业的数据量与日俱增,大数据产品层出不穷.今天给大家分享一款产品—— Apache Flink,目前,已是 Apache 顶级项目之一.那么,接下来,笔者为大家介绍Fl ...

  6. C2C,B2C,F2C三种电商运营模式的比较

      第三方模式(C2C) 销售商模式(B2C) 生产商模式(F2C) 概念及简介 第三方平台提供商模式是电子商务的最原始也是最自然的形式.这种模式一般都是由信息技术开发商负责建立平台,利用平台扩展电子 ...

  7. 在Windows/Ubuntu下安装OpenGL环境(GLUT/freeglut)与跨平台编译(mingw/g++)

    GLUT/freeglut 是什么? OpenGL 和它们有什么关系? OpenGL只是一个标准,它的实现一般自带在操作系统里,只要确保显卡驱动足够新就可以使用.如果需要在程序里直接使用OpenGL, ...

  8. SQL 解锁表

    http://www.cnblogs.com/chjf2008/archive/2012/11/21/2780787.html 最主要是找到最近使用工具或者应用连接过数据库的进程,关掉它就可以了.

  9. mysql中int、bigint、smallint 和 tinyint的区别详细介绍

    1 bytes = 8 bit ,一个字节最多可以代表的数据长度是2的8次方 11111111 在计算机中也就是 -128到127 1.BIT[M] 位字段类型,M表示每个值的位数,范围从1到64,如 ...

  10. Javascript倒计时组件new TimeSpan(hours, minutes, minutes)

    function TimeSpan(h, m, s) { this.h = Number(h); this.m = Number(m); this.s = Number(s); } TimeSpan. ...