C#中结构体数组索引器是否返回副本?含Vector3/Quaternion场景
数组索引器与可变结构体:行为差异与陷阱解析
核心结论
数组的索引器不会返回副本,直接修改数组中可变结构体的成员(比如quaternions[1,1].X = 1.0f)会直接作用于原数组元素,没有额外的CPU开销,这是C#对数组的特殊处理,和Dictionary、List等集合的索引器行为完全不同。
1. 数组索引器的特殊本质
根据C#规范中「Array access」章节的定义:
计算索引表达式指定的数组元素的位置,该位置即为数组访问的结果。
这意味着数组访问返回的是元素的直接引用(左值),而非值类型的副本。所以你测试代码中的修改操作是直接对内存中的数组元素进行的,不需要先复制到临时变量、修改再赋值回去,完全没有额外的性能损耗。
2. 普通集合索引器的行为(Dictionary/List等)
像Dictionary、List这类集合的索引器本质是类的属性(包含get访问器),当你访问mydictionary[123]时,get访问器会返回值类型的副本。此时如果尝试直接修改副本的成员(比如mydictionary[123].blah = 456),修改只会作用在临时副本上,不会影响字典中的原元素——编译器会直接报错阻止这种无效操作,这和Eric Lippert 2008年的描述完全一致,至今没有变化。
正确的做法确实是:
var temp = mydictionary[123]; temp.blah = 456; mydictionary[123] = temp;
3. 数组转IList后的行为变化
当你把数组转为IList<T>接口后,调用索引器时会触发接口的get访问器,它的行为和普通集合索引器一致:返回值类型的副本。所以此时修改成员只会改变副本,原数组元素不会受到影响——这就是为什么转成IList后操作失效的原因。
4. 可变结构体的其他注意事项
虽然数组场景下你可以安全修改成员,但可变结构体仍有其他常见陷阱需要规避:
- 只读字段中的可变结构体:如果类中包含
readonly修饰的可变结构体字段,修改其成员会导致修改副本,原字段不会变化(这正是Eric Lippert博客的核心场景)。 - 值传递场景:将可变结构体作为参数传递给方法(默认值传递)、作为方法返回值时,都会产生副本,修改副本不会影响原变量。
内容的提问来源于stack exchange,提问作者Edward Ned Harvey
相关产品推荐
相关产品推荐

