为什么不能为foreach迭代变量赋值,而可以用访问器完全修改它?
本文关键字:访问 修改 foreach 不能 迭代 变量 赋值 为什么 | 更新日期: 2023-09-27 18:12:06
我只是对此感到好奇:以下代码将无法编译,因为我们无法修改foreach迭代变量:
foreach (var item in MyObjectList)
{
item = Value;
}
但是下面的代码可以编译并运行:
foreach (var item in MyObjectList)
{
item.Value = Value;
}
为什么第一个无效,而第二个可以在下面做同样的事情(我正在寻找正确的英语表达,但我不记得了。在…?^ ^)
foreach是一个只读迭代器,它动态迭代实现IEnumerable的类,foreach中的每个循环将调用IEnumerable来获取下一个项目,您拥有的项目是只读引用,您不能重新分配它,但简单地调用item.Value
是访问它并为读/写属性分配一些值,但项目的引用仍然是只读引用。
第二个程序完全不做同样的事情。它没有改变item
变量的值-它改变了该值所指向的对象的属性。如果item
是一个可变值类型,这两个只有是等价的——在这种情况下,你应该改变它,因为可变值类型是邪恶的。(它们的行为方式可能是粗心的开发人员意想不到的。)
和下面一样:
private readonly StringBuilder builder = new StringBuilder();
// Later...
builder = null; // Not allowed - you can't change the *variable*
// Allowed - changes the contents of the *object* to which the value
// of builder refers.
builder.Append("Foo");
更多信息请参阅我的文章
不能在枚举集合时修改它。第二个例子只更新对象的一个属性,这是完全不同的。
使用for
循环,如果你需要添加/删除/修改集合中的元素:
for (int i = 0; i < MyObjectList.Count; i++)
{
MyObjectList[i] = new MyObject();
}
如果你看一下语言规范,你就会明白为什么这不起作用了:
规范说明foreach扩展为以下代码:
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
可以看到,当前元素是用来调用moveext()的。因此,如果您更改当前元素,代码将"丢失"并且无法遍历集合。因此,如果你看到编译器实际生成的代码,将元素更改为其他内容是没有任何意义的。
它将有可能使item
可变。我们可以改变生成代码的方式,以便:
foreach (var item in MyObjectList)
{
item = Value;
}
等价于:
using(var enumerator = MyObjectList.GetEnumerator())
{
while(enumerator.MoveNext())
{
var item = enumerator.Current;
item = Value;
}
}
然后编译。但是,它不会影响集合。
这就是问题所在。代码:
foreach (var item in MyObjectList)
{
item = Value;
}
有两种合理的方式供人类思考。一是item
只是一个占位符,改变它和改变:
item
没有什么不同for(int item = 0; item < 100; item++)
item *= 2; //perfectly valid
另一个是更改item实际上会更改集合。
在前一种情况下,我们可以将item赋值给另一个变量,然后使用它,这样就不会有损失。在后一种情况下,这是被禁止的(或者至少,你不能期望在迭代一个集合时改变它,尽管它不要求被所有枚举器强制执行),并且在许多情况下不可能提供(取决于枚举对象的性质)。
即使我们认为前一种情况是"正确的"实现,事实是它可以被人类以两种不同的方式合理地解释,这是一个足够好的理由来避免允许它,特别是考虑到我们可以很容易地在任何情况下绕过它。
因为第一个基本上没有多大意义。变量item由迭代器控制(在每次迭代中设置)。您不需要更改它-只需使用另一个变量:
foreach (var item in MyObjectList)
{
var someOtherItem = Value;
....
}
对于第二个,这里有一些有效的用例—您可能希望遍历汽车枚举并对每个汽车调用.Drive(),或者设置car.gasTank = full;
关键是在迭代集合时不能修改集合本身。修改迭代器生成的对象是绝对合法和常见的。
因为两个不相同。在执行第一个操作时,您可能希望更改集合中的值。但foreach
的工作方式是不可能做到的。它只能从集合中检索项,不能设置项。
但是一旦你有了项目,它就和其他任何对象一样,你可以用任何(允许的)方式修改它
使用for循环而不是foreach循环和assign value。它会工作的
foreach (var a in FixValue)
{
for (int i = 0; i < str.Count(); i++)
{
if (a.Value.Contains(str[i]))
str[i] = a.Key;
}
}