为什么我们不能分配 foreach 迭代变量,而我们可以用访问器完全修改它?

我只是对此感到好奇: 下面的代码不能编译,因为我们不能修改 foreach 迭代变量:

        foreach (var item in MyObjectList)
{
item = Value;
}

但是下面的代码将编译并运行:

        foreach (var item in MyObjectList)
{
item.Value = Value;
}

为什么第一个是无效的,而第二个可以做同样的下面(我正在为此搜索正确的英语表达式,但我不记得它。在... ?^^ )

95624 次浏览

第二个根本不是这么回事。它不会改变 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");

有关详细信息,请参阅我的 关于参考文献和价值的文章

因为第一个基本上没什么意义。变量项由迭代器控制(在每次迭代时设置)。你不需要改变它——只需要使用另一个变量:

foreach (var item in MyObjectList)
{
var someOtherItem = Value;


....
}

至于第二种情况,那里有一些有效的用例——您可能希望迭代汽车的枚举并调用。驱动()对每一个,或设置 car.gasTank = full;

关键是在迭代 收藏品本身时不能修改它。修改迭代器产生的对象是绝对合法和常见的。

因为两个 是不一样的。执行第一个操作时,您可能希望更改集合中的值。但以 foreach的工作方式,这是不可能做到的。它只能从集合中检索项,而不能设置它们。

但是一旦你有了这个项目,它就是一个对象,就像其他对象一样,你可以用任何(允许的)方式修改它。

如果你看一下语言规范,你就会明白为什么这个不起作用:

规范说 foreach 扩展为以下代码:

 E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}

如您所见,当前元素用于调用 MoveNext ()。因此,如果更改当前元素,代码将“丢失”并且无法在集合上迭代。 因此,如果您看到编译器实际生成的代码,那么将元素改为其他元素是没有意义的。

在枚举集合时不能修改它。第二个示例只更新对象的属性,这是完全不同的。

如果需要在集合中添加/删除/修改元素,请使用 for循环:

for (int i = 0; i < MyObjectList.Count; i++)
{
MyObjectList[i] = new MyObject();
}

Foreach 是一个只读迭代器,它动态迭代实现 IEnumable 的类,foreach 中的每个循环都会调用 IEnumable 来获取下一个条目,你拥有的条目是一个只读引用,你不能重新分配它,但是简单地调用 item.Value就是访问它,并为一个读/写属性分配一些值,但是条目的引用仍然是一个只读引用。

我们可以改变代码的生成方式,以便:

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 赋值给另一个变量,然后使用它,这样就不会有损失。在后一种情况下,这是被禁止的(或者至少,你不能 期待改变一个集合,而迭代通过它,虽然它没有 强制所有枚举) ,在许多情况下不可能提供(取决于可枚举的性质)。

即使我们认为前一种情况是“正确的”实现,但它可以被人类以两种不同的方式合理地解释这一事实也是一个足够好的理由来避免它,特别是考虑到我们可以在任何情况下轻易地绕过它。

使用 for 循环代替 foreach 循环并赋值

foreach (var a in FixValue)
{
for (int i = 0; i < str.Count(); i++)
{
if (a.Value.Contains(str[i]))
str[i] = a.Key;
}
}