我知道值类型应该是不可变的,但这只是一个建议,而不是规则,对吧? 那么为什么我不能这样做:
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迭代变量'
为什么?
答案 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)
我认为您可以在下面找到答案。
答案 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中调整对象。