在C#中,为什么我不能在foreach循环中修改值types实例的成员?

我知道值types应该是不变的,但这只是一个build议,而不是一个规则,对吗? 那么为什么我不能这样做:

struct MyStruct { public string Name { get; set; } } public class Program { static void Main(string[] args) { MyStruct[] array = new MyStruct[] { new MyStruct { Name = "1" }, new MyStruct { Name = "2" } }; foreach (var item in array) { item.Name = "3"; } //for (int i = 0; i < array.Length; i++) //{ // array[i].Name = "3"; //} Console.ReadLine(); } } 

代码中的foreach循环不会编译,而for循环可以正常工作。 这是为什么?

由于foreach使用枚举器,枚举器不能更改基础集合,但是可以更改集合中的对象所引用的任何对象。 这就是Value和Reference-type语义发挥作用的地方。

在一个引用types上,也就是一个类,所有正在存储的集合都是对一个对象的引用。 因此,它实际上不会触及任何对象的成员,也不会在意它们。 对象的更改不会触及集合。

另一方面,价值types将其整个结构存储在集合中。 如果不更改集合并使枚举器无效,则无法触摸其成员。

而且,枚举器返回集合中值的一个副本 。 在ref-type中,这没有任何意义。 引用的副本将是相同的引用,并且您可以以任何想要的方式更改引用的对象,并将这些更改散布在范围之外。 另一方面,在值types上,意味着你所得到的只是对象的副本,因此对所述副本的任何改变都不会传播。

从某种意义上说,没有任何东西可以阻止你违反它,但是它应该比“这是一个build议”更加重要。 例如,你在这里看到的原因。

值types将实际值存储在variables中,而不是参考。 这意味着你有你的数组中的值,你有一个在该item中的值的副本 ,而不是一个参考。 如果允许更改item的值,则不会反映到数组中的值,因为它是一个全新的值。 这就是为什么它不被允许。

如果你想这样做,你必须通过索引来遍历数组,而不是使用临时variables。

结构是值types。
类是引用types。

ForEach构造使用IEnumerable数据types元素的IEnumerator 。 当发生这种情况时,variables是只读的,你不能修改它们,因为你有值types,所以你不能修改任何包含的值,因为它共享相同的内存。

C#lang spec部分8.8.4:

迭代variables对应于一个扩展了embedded语句的只读局部variables

为了解决这个使用类结构的问题;

 class MyStruct { public string Name { get; set; } } 

编辑:@CuiPengFei

如果你使用var隐式types,编译器很难帮你。 如果你使用MyStruct它会告诉你在结构的情况下,它是只读的。 在类的情况下,对项目的引用是只读的,所以你不能写item = null; 内循环,但你可以改变它的属性,这是可变的。

你也可以使用(如果你喜欢使用struct ):

  MyStruct[] array = new MyStruct[] { new MyStruct { Name = "1" }, new MyStruct { Name = "2" } }; for (int index=0; index < array.Length; index++) { var item = array[index]; item.Name = "3"; } 

注: 根据亚当的评论,这实际上不是问题的正确答案/原因。 尽pipe如此,仍然值得注意。

来自MSDN 。 在使用枚举器时,不能修改这些值,这基本上是foreach正在做的事情。

枚举器可用于读取集合中的数据,但不能用于修改底层集合。

只要收集保持不变,统计员仍然有效。 如果对集合进行更改(例如添加,修改或删除元素),则枚举器将无法恢复,且其行为未定义。

使MyStruct成为一个类(而不是结构),你就可以做到这一点。

我想你可以find下面的anwser。

foreach struct怪异编译错误在C#

值types按值来调用 。 简而言之,当你评估这个variables的时候,它的一个副本就被制作了。 即使这是允许的,您将编辑一个副本,而不是MyStruct的原始实例。

foreach在C#中是只读的 。 对于引用types(class),这个变化不大,因为只有引用是只读的,所以你仍然可以执行以下操作:

  MyClass[] array = new MyClass[] { new MyClass { Name = "1" }, new MyClass { Name = "2" } }; foreach ( var item in array ) { item.Name = "3"; } 

然而对于值types(struct),整个对象是只读的,导致你正在经历的。 你不能在foreach中调整对象。