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

Gia*_*971 50 c# iteration foreach accessor

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

        foreach (var item in MyObjectList)
        {
            item = Value;
        }
Run Code Online (Sandbox Code Playgroud)

但是以下将编译并运行:

        foreach (var item in MyObjectList)
        {
            item.Value = Value;
        }
Run Code Online (Sandbox Code Playgroud)

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

Moh*_*bed 36

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

  • "_foreach是一个只读迭代器,它动态地迭代实现IEnumerable_的类."虽然正确,但这并不严格.编译器只需要你迭代的类型有`T GetEnumerator()`,`bool MoveNext()`方法和`T Current`属性(假设你想创建自己的枚举器).你实际上并不需要实现`IEnumerable`. (6认同)
  • @JosephWoodward:*“虽然正确,但严格来说这不是真的。”* 约瑟夫,这是尤达级别的声明,肯定会进入我的历史前 10 名!:D (2认同)
  • 当您可以修改迭代变量引用的对象时,将其设置为只读的原因是什么?如果需要,您还可以通过删除语法糖并使用 while 循环并调用 MoveNext() 等来修改变量的引用。 (2认同)

Jon*_*eet 34

第二个是完全没有做同样的事情.它不会改变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");
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请参阅有关参考和值的文章.


Jam*_*son 22

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

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

for (int i = 0; i < MyObjectList.Count; i++)
{
    MyObjectList[i] = new MyObject();
}
Run Code Online (Sandbox Code Playgroud)


Wou*_*ort 11

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

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

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

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

  • 据我所知,`v`将保持当前元素引用,而`e`保存迭代器引用.所以编译器可以只分配我们想要的任何东西,而```和`e`仍然会持有迭代器.这样,foreach元素变量可以被赋予新的引用,因为它们被转换为`v`变量,而不是`e`. (3认同)

Jon*_*nna 5

有可能使item可变的。我们可以更改代码的生成方式,以便:

foreach (var item in MyObjectList)
{
  item = Value;
}
Run Code Online (Sandbox Code Playgroud)

等效于:

using(var enumerator = MyObjectList.GetEnumerator())
{
  while(enumerator.MoveNext())
  {
    var item = enumerator.Current;
    item = Value;
  }
}
Run Code Online (Sandbox Code Playgroud)

然后它将进行编译。但是,它不会影响收集。

还有摩擦。代码:

foreach (var item in MyObjectList)
{
  item = Value;
}
Run Code Online (Sandbox Code Playgroud)

人类有两种合理的方式去考虑它。一个就是那item只是一个占位符,更改它与更改位置没有什么不同item

for(int item = 0; item < 100; item++)
    item *= 2; //perfectly valid
Run Code Online (Sandbox Code Playgroud)

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

在前一种情况下,我们可以将项目分配给另一个变量,然后进行处理,因此不会有任何损失。在后一种情况下,这是既禁止(或者至少,你不能指望改变的集合,而通过它迭代,虽然它不具备对所有普查员执行),并在许多情况下无法提供(视的性质)。

即使我们认为前一种情况是“正确的”实现,人类也可以以两种不同的方式对其进行合理解释这一事实足以避免这种情况,尤其是考虑到在任何情况下我们都可以轻松解决这一问题。