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

时间:2011-10-20 15:11:56

标签: c# foreach iteration accessor

我只是对此感到好奇:以下代码无法编译,因为我们无法修改foreach迭代变量:

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

但是以下内容将编译并运行:

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

为什么第一个无效,而第二个可以在下面做同样的事情(我正在为此寻找正确的英语表达,但我不记得了。在...下面??^^)

9 个答案:

答案 0 :(得分:33)

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

答案 1 :(得分:31)

第二个根本没有做同样的事情。它不会更改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");

有关详细信息,请参阅我的article on references and values

答案 2 :(得分:17)

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

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

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

答案 3 :(得分:11)

如果你看一下语言规范,就可以看出为什么这不起作用了:

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

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

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

答案 4 :(得分:3)

可以使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

另一个是更改项目实际上会改变集合。

在前一种情况下,我们可以将项目分配给另一个变量,然后使用该变量,因此没有任何损失。在后一种情况下,这是禁止的(或者至少,你不能期望在迭代它时改变一个集合,尽管它不会 强制执行所有调查员)并且在许多情况下无法提供(取决于可枚举的性质)。

即使我们认为前一种情况是“正确的”实施,但人类可以通过两种不同的方式对其进行合理解释的事实是避免允许它的充分理由,特别是考虑到我们可以轻松地解决这个问题。无论如何。

答案 5 :(得分:2)

因为第一个基本没有多大意义。变量项由迭代器控制(在每次迭代时设置)。你不应该改变它 - 只需使用另一个变量:

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

    ....
}

至于第二个,那里有一些有效的用例 - 你可能想要迭代汽车的枚举并在每个汽车上调用.Drive(),或者设置car.gasTank = full;

答案 6 :(得分:1)

关键是你在迭代它时不能修改集合本身。修改迭代器产生的对象是绝对合法和通用的。

答案 7 :(得分:1)

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

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

答案 8 :(得分:1)

使用for循环代替foreach循环并赋值。它会起作用

object = $(this).parent();