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

Cui*_*崔鹏飞 62 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

代码中的foreach循环不会编译,而注释for循环工作正常.错误消息:

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

这是为什么?

Kyt*_*yte 66

因为foreach使用枚举器,并且枚举器不能更改底层集合,但是可以更改集合中对象引用的任何对象.这就是Value和Reference-type语义发挥作用的地方.

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

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

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


Ada*_*son 16

从某种意义上说,这是一个建议,没有什么可以阻止你违反它,但它确实应该比"这是一个建议"更重要.例如,由于你在这里看到的原因.

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

如果你想这样做,你将不得不通过索引循环数组而不是使用临时变量.


Mar*_*gus 9

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

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

C#lang规范部分8.8.4:

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

修复这个使用类的结构;

class MyStruct {
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

编辑:@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";
        }
Run Code Online (Sandbox Code Playgroud)