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

时间:2011-04-13 14:15:18

标签: c# foreach immutability value-type

我知道值类型应该是不可变的,但这只是一个建议,而不是规则,对吧? 那么为什么我不能这样做:

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循环工作正常。错误消息:

  

无法修改'item'的成员,因为它是'foreach迭代变量'

为什么?

7 个答案:

答案 0 :(得分:63)

因为foreach使用枚举器,而枚举器无法更改基础集合,但可以,更改集合中对象引用的任何对象。这就是Value和Reference-type语义发挥作用的地方。

在引用类型(即类)上,所有正在存储的集合都是对对象的引用。因此,它实际上从未触及任何对象的成员,并且对它们的关注度不高。对象的更改不会触及该集合。

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

此外,枚举器返回集合中值的副本。在ref-type中,这意味着什么。引用的副本将是相同的引用,您可以以任何方式更改引用的对象,并将更改扩展到范围之外。另一方面,在值类型上,意味着您获得的只是对象的副本,因此对所述副本的任何更改都不会传播。

答案 1 :(得分:16)

这是一个意义上的建议,没有什么可以阻止你违反它,但它应该比“这是一个建议”更重要。例如,由于你在这里看到的原因。

值类型将实际值存储在变量中,而不是引用中。这意味着您拥有数组中的值,并且您在item中拥有该值的副本,而不是引用。如果允许您更改item中的值,则不会将其反映在数组中的值,因为它是一个全新的值。这就是不允许的原因。

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

答案 2 :(得分:9)

结构是价值类型。
类是引用类型。

ForEach构造使用 IEnumerable 数据类型元素的 IEnumerator 。当发生这种情况时,变量在某种意义上是只读的,您无法修改它们,并且由于您具有值类型,因此您无法修改包含的任何值,因为它共享相同的内存。

C#lang spec section 8.8.4:

  

迭代变量对应于只读局部变量,其范围扩展到嵌入语句

修复此结构的使用类;

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

编辑:@CuiPengFei

如果使用var隐式类型,编译器很难帮助您。如果您使用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";
        }

答案 3 :(得分:3)

注意: 根据Adam的评论,这实际上并不是问题的正确答案/原因。不过,它仍然值得记住。

来自MSDN。使用枚举器时无法修改值,这实际上是 foreach 正在执行的操作。

  

可以使用枚举器来阅读   集合中的数据,但他们   不能用来修改   基础集合。

     

只要枚举器仍然有效   该系列保持不变。如果   对集合进行了更改,   例如添加,修改或删除   元素,枚举器是   不可恢复的无效及其   行为未定义。

答案 4 :(得分:1)

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

答案 5 :(得分:1)

我认为您可以在下面找到答案。

Foreach struct weird compile error in C#

答案 6 :(得分:1)

值类型为called by value。简而言之,当您评估变量时,会对其进行复制。即使允许这样做,您也可以编辑副本,而不是MyStruct的原始实例。

foreach is readonly in C#。对于引用类型(类),这不会有太大变化,因为只有引用是只读的,所以仍然允许您执行以下操作:

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

对于值类型(struct),整个对象是只读的,从而导致您遇到的问题。您无法在foreach中调整对象。