【问题标题】:Why are assignment operators (=) invalid in a foreach loop?为什么赋值运算符 (=) 在 foreach 循环中无效?
【发布时间】:2011-04-02 18:15:33
【问题描述】:

为什么赋值运算符 (=) 在 foreach 循环中无效?我正在使用 C#,但我认为该参数对于支持 foreach 的其他语言(例如 PHP)是相同的。例如,如果我这样做:

string[] sArray = new string[5];

foreach (string item in sArray)
{
   item = "Some assignment.\r\n";
}

我收到一个错误,“无法分配给 'item',因为它是 'foreach 迭代变量'。”

【问题讨论】:

  • 一般来说,PHP 可以让您做各种使软件维护变得困难的事情。更重要的是,它与大多数其他编程语言并不真正相似。像这样在其他语言中使用 PHP 习语可能不是最好的主意。

标签: c# foreach variable-assignment


【解决方案1】:

这是你的代码:

foreach (string item in sArray)
{
   item = "Some assignment.\r\n";
}

下面是a rough approximation 编译器对此做了什么:

using (var enumerator = sArray.GetEnumerator())
{
    string item;
    while (enumerator.MoveNext())
    {
        item = enumerator.Current;

        // Your code gets put here
    }
}

IEnumerator<T>.Current 属性是只读的,但这实际上并不相关,因为您正试图将本地 item 变量分配给一个新值。防止您这样做的编译时检查基本上是为了保护您免于做一些不会像您期望的那样工作的事情(即,更改局部变量并且对基础集合/序列没有影响)。

如果您想在枚举时修改索引集合的内部结构,例如string[],传统的方法是使用for 循环而不是foreach

for (int i = 0; i < sArray.Length; ++i)
{
    sArray[i] = "Some assignment.\r\n";
}

【讨论】:

  • 很好的答案!最后一个正确解释发生了什么的。
  • 我永远不会理解他们的编译器:/
  • 虽然你是对的,foreach 只是句法上的肯定,但你在编译器实际将它转换成什么方面有点不正确。对Eric Lippert's blog 上发生的事情有更准确的解释。这篇文章讨论了闭包,但它也解释了编译器的作用。一个非常重要的区别是临时变量是在循环外部声明的。
  • @R0MANARMY:感谢您指出这一点;显然在大多数情况下,这两种结构并没有太大的不同,但无论如何我已经改变了我的答案,以免在它们所在的情况下产生误导。我仍然选择使用using 而不是try/finally 块,因为毕竟这是using 被编译器转换为的内容(我主要关心的是可读性)。
  • @supercat:我没有忘记!这就是using 语句的作用。
【解决方案2】:

完全有可能让它被改变。然而,这又意味着什么呢?它读起来像是底层枚举被修改了,但实际上并没有(也可以允许这样做,但这有其自身的缺点)。

因此,您的代码自然会被人们解读为指示实际发生的事情以外的事情。考虑到计算机语言的目的主要是为了让人们理解(编译器处理对他们设置的平衡,除非您使用汇编、机器代码或适当命名的 Brainf**k),这表明在语言。

【讨论】:

    【解决方案3】:

    一般来说,如果您尝试这样做,您需要对您的设计进行长时间而认真的思考,因为您可能没有使用最好的结构。在这种情况下,最好的答案可能是

    string[] sArray = Enumerable.Repeat("Some assignment.\r\n", 5).ToArray();
    

    在 C# 中,几乎总是可以使用更高级别的构造来代替这种循环。 (还有 C++,但那是另一个话题了)

    【讨论】:

    • 字符串不是值类型。更重要的是,将局部变量分配给新值将永远更改其他地方的引用值,除非它是 ref 参数(在这种情况下,它实际上是相同的引用)。也许你应该说,“因为item 有本地范围......”?
    • @Dan Tao:我已经删除了我的那部分答案;这里的其他答案更好地解释了我的意思。
    • 我以为你打错了。每个人总是急于首先得到他们的答案——我知道我是;)
    • 我喜欢这个答案!!!它比 for 循环要整洁得多。我知道这不是很常见,但有时(在测试用例中)你需要生成一个包含 100 个东西的数组。
    【解决方案4】:

    您不能修改通过“ForEach”循环的列表。

    最好的选择是简单地创建一个临时列表来存储您希望使用的项目。

    【讨论】:

    • 不,最好的选择是使用现成的组件,让您避免使用循环,或者使用下标而不是枚举器编写循环。
    【解决方案5】:

    foreach 旨在遍历数组一次,而无需重复或跳过(尽管您可以使用 continue 关键字跳过 foreach 构造中的某些操作)。 如果要修改当前项,请考虑使用 for 循环。

    【讨论】:

      【解决方案6】:

      您不能修改您正在遍历的数组。请改用以下代码:

      string[] sArray = new string[5]; 
      
      for (int i=0;i<sArray.Length;i++)
      {
          item[i] = "Some Assignment.\r\n";
      }
      

      【讨论】:

      • 您可以使用Enumerable.Repeat 将整个事情简化为一条语句。
      【解决方案7】:

      因为语言规范是这么说的。

      但说真的,并不是所有的序列都是数组或可以在逻辑上修改或写入的东西。例如:

      foreach (var i in Enumerable.Range(1, 100)) {
         // modification of `i` will not make much sense here.
      }
      

      虽然让i = something; 修改局部变量在技术上是可行的,但它可能会产生误导(您可能认为它确实改变了某些东西,但事实并非如此)。

      为了支持这些类型的序列,IEnumerable&lt;T&gt;set 属性不需要set 访问器,使其成为只读的。因此,foreach 无法使用 Current 属性修改基础集合(如果存在)。

      【讨论】:

      • 这和 yield 关键字是我们首先想到的真实事物,我们正在受到保护。可能会发生“意外行为”,但 C# 很少能保护我们免受意外行为的影响,尽管您的示例值得保护我们。
      【解决方案8】:

      foreach 循环旨在遍历集合中的对象,而不是分配事物 - 它只是语言的设计。

      另外,来自 MSDN:

      “当一个变量的赋值发生在一个读取- 只有上下文。只读上下文包括 foreach 迭代变量, 使用变量和固定变量。要解决此错误,请避免 在使用块中分配给语句变量,foreach 语句和固定语句。”

      foreach 关键字只是枚举 IEnumerable 实例(获取 IEnumerator 实例通过调用 GetEnumerator() 方法)。 IEnumerator 是只读的,因此值不能 使用 IEnumerator =can't be 更改 使用 foreach 上下文进行更改。

      【讨论】:

        【解决方案9】:

        因为您不能使用foreach 循环来修改您正在循环的数组。循环 迭代 遍历数组,因此如果您尝试修改它正在迭代的内容,则可能会发生意外行为。此外,正如 Darin 和 DMan 所指出的,您正在迭代一个 IEnumerable,它本身就是只读的。

        PHP 在其foreach 循环中创建数组的副本并遍历该副本,除非您使用引用,在这种情况下您将修改数组本身。

        【讨论】:

        • 嗯,你可以修改它,只要你不分配给它。例如,如果你在一堆对象上“foreaching”,你可以改变对象的内部结构,但你实际上不能分配给对象本身。
        【解决方案10】:

        因为IEnumerable 是只读的。

        【讨论】:

        • 我不确定这是否真的解释了为什么“当前项目变量”是只读的。
        • @Kirk:因为在读取IEnumerable 对象时不会复制当前项目。
        • @Kirk Woll,您不能使用 IEnumerator 修改值。 IEnumerator.Current 属性是只读的,它没有设置器。
        • @Darin:我没有投反对票,但 Kirk Woll 的评论可能是根本原因。
        • @Darin:我也没有投反对票,但我认为您的回答具有误导性,因为您将局部变量和 Current 属性混为一谈。请参阅 Mehrdad 或我的回答以了解我的意思。
        猜你喜欢
        • 2013-11-26
        • 1970-01-01
        • 1970-01-01
        • 2014-11-08
        • 2013-09-17
        • 2021-08-15
        • 2021-11-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多