为什么C#foreach语句中的迭代变量是只读的?

时间:2009-04-22 09:48:21

标签: c# language-design

据我了解,C#的foreach迭代变量是不可变的。

这意味着我不能像这样修改迭代器:

foreach (Position Location in Map)
{
     //We want to fudge the position to hide the exact coordinates
     Location = Location + Random();     //Compiler Error

     Plot(Location);
}

我不能直接修改迭代器变量,而是必须使用for循环

for (int i = 0; i < Map.Count; i++)
{
     Position Location = Map[i];
     Location = Location + Random();

     Plot(Location);        
     i = Location;
}

来自C ++背景,我认为foreach是for循环的替代品。但是由于上述限制,我通常会回退使用for循环。

我很好奇,使迭代器不可变的原理是什么?


编辑:

这个问题更多的是一个好奇的问题,而不是一个编码问题。我很感激编码答案,但我不能将它们标记为答案。

此外,上述示例过于简化。这是我想要做的C ++示例:

// The game's rules: 
//   - The "Laser Of Death (tm)" moves around the game board from the
//     start area (index 0) until the end area (index BoardSize)
//   - If the Laser hits a teleporter, destroy that teleporter on the
//     board and move the Laser to the square where the teleporter 
//     points to
//   - If the Laser hits a player, deal 15 damage and stop the laser.

for (int i = 0; i < BoardSize; i++)
{
    if (GetItem(Board[i]) == Teleporter)
    {
        TeleportSquare = GetTeleportSquare(Board[i]);
        SetItem(Board[i], FreeSpace);
        i = TeleportSquare;
    }

    if (GetItem(Board[i]) == Player)
    {
        Player.Life -= 15;
        break;
    }
}

我无法在C#的foreach中执行上述操作,因为迭代器是不可变的。我想(如果我错了,请纠正我),这是针对foreach语言的设计。

我对为什么foreach迭代器是不可变的感兴趣。

6 个答案:

答案 0 :(得分:25)

让我们从一个愚蠢但说明性的例子开始:

Object o = 15;
o = "apples";

在任何时候我们都不会觉得我们只是把数字15变成了一串苹果。我们知道o只是一个指针。现在让我们以迭代器的形式执行此操作。

int[] nums = { 15, 16, 17 };

foreach (Object o in nums) {
     o = "apples";
}

同样,这真的没有成就。或者至少它 什么都没有完成编译。它肯定不会将我们的字符串插入到int数组中 - 这是不允许的,我们知道o无论如何都只是一个指针。

我们举个例子:

foreach (Position Location in Map)
{
     //We want to fudge the position to hide the exact coordinates
     Location = Location + Random();     //Compiler Error

     Plot(Location);
}

如果要进行编译,示例中的Location会引用Map中的值,但随后您将其更改为引用新的Position(由foreach (Position Location in Map) { //We want to fudge the position to hide the exact coordinates Position Location2 = Location + Random(); //No more Error Plot(Location2); } 隐式创建加法运算符)。在功能上它等同于此(编译):

{{1}}

那么,为什么Microsoft会禁止您重新分配用于迭代的指针?清楚一件事 - 你不希望人们认为他们已经改变了你在循环中的位置。易于实现另一个:变量可能隐藏一些内部逻辑,指示正在进行的循环的状态。

但更重要的是,您没有理由想要分配给它。它表示循环序列的当前元素。如果您遵循Coding Horror,为其分配值会破坏“单一责任原则”或Curly's Law。变量应该仅仅意味着一件事。

答案 1 :(得分:14)

如果变量是可变的,那可能会给人一种不正确的印象。例如:

string[] names = { "Jon", "Holly", "Tom", "Robin", "William" };

foreach (string name in names)
{
    name = name + " Skeet";
}

有些人可能认为会改变数组内容。它达到了一点,但这可能是一个原因。我今晚会在我的注释规范中查一查......

答案 2 :(得分:10)

我认为它是人为的限制,不需要这么做。为证明这一点,请将此代码纳入考虑范围,并为您的问题提供可能的解决方案。问题是asign,但对象的内部更改不会出现问题:

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {

            List<MyObject> colection =new List<MyObject>{ new MyObject{ id=1, Name="obj1" }, 
                                                    new MyObject{ id=2, Name="obj2"} };

            foreach (MyObject b in colection)
            {
             // b += 3;     //Doesn't work
                b.Add(3);   //Works
            }
        }

        class MyObject
        {
            public static MyObject operator +(MyObject b1, int b2)
            {
                return new MyObject { id = b1.id + b2, Name = b1.Name };
            }

          public void Add(int b2)
          {
              this.id += b2;
          }
            public string Name;
            public int id;
        }
    }
}

我不知道这个现象,因为我总是按照我描述的方式修改对象。

答案 3 :(得分:2)

foreach语句适用于Enumerable。与Enumerable不同的是它不了解整个集合,它几乎是盲目的。

这意味着它不知道接下来会发生什么,或者确实在下一个迭代周期之前有什么东西。这就是为什么你看到.ToList()很多,所以人们可以抓住Enumerable的数量。

出于某种原因,我不确定,这意味着您需要确保在尝试移动枚举器时收集不会发生变化。

答案 4 :(得分:2)

修改集合时,修改可能会产生不可预测的副作用。枚举器无法知道如何正确处理这些副作用,因此它们使集合不可变。

另见这个问题: What is the best way to modify a list in a foreach

答案 5 :(得分:1)

使用IEnumerable对象的条件是,在使用Enumerable访问它时,底层集合不得更改。您可以假设Enumerable对象是原始集合的快照。因此,如果您在枚举时尝试更改集合,则会抛出异常。但是,Enumeration中的获取对象根本不是不可变的。

由于foreach循环中使用的变量是循环块的本地变量,因此该变量在块外不可用。