代码之家  ›  专栏  ›  技术社区  ›  MrValdez

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

  •  34
  • MrValdez  · 技术社区  · 15 年前

    据我所知,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++背景来看,我认为FrACH是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;
        }
    }
    

    因为迭代器i是不可变的,所以我不能在c的foreach中执行上述操作。我认为(如果我错了,请纠正我),这是针对语言中foreach的设计的。

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

    6 回复  |  直到 10 年前
        1
  •  25
  •   tylerl    10 年前

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

    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);
    }
    

    那么,为什么微软禁止你重新分配用于迭代的指针呢?有一点很清楚——你不想让别人认为他们改变了你在圈内的位置。另一种实现的简单性:变量可能隐藏一些内部逻辑,指示正在进行的循环的状态。

    但更重要的是,你没有理由 希望 分配给它。它表示循环序列的当前元素。为其赋值违反了“单一责任原则”或 Curly's Law 如果你遵循恐怖编码。变量应该只意味着一件事。

        2
  •  14
  •   Jon Skeet    15 年前

    如果变量是可变的,那可能会给人错误的印象。例如:

    string[] names = { "Jon", "Holly", "Tom", "Robin", "William" };
    
    foreach (string name in names)
    {
        name = name + " Skeet";
    }
    

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

        3
  •  10
  •   majkinetor    15 年前

    我认为这是人为的限制,不需要真的去做。为了证明这一点,请将此代码考虑在内,并为您的问题提供可能的解决方案。问题在于设计,但对象的内部更改并不存在问题:

    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;
            }
        }
    }
    

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

        4
  •  2
  •   Ian    15 年前

    foreach语句与Enumerable一起使用。与可枚举的不同之处在于它不知道整个集合,几乎是盲目的。

    这意味着它不知道接下来会发生什么,或者在下一个迭代周期之前是否有任何事情。所以你经常看到.tolist(),这样人们就可以获取一个可枚举的计数。

    由于某些原因,我不确定,这意味着您需要确保在尝试移动枚举器时集合不会发生更改。

        5
  •  2
  •   Community Reversed Engineer    7 年前

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

    另请参见此问题: What is the best way to modify a list in a foreach

        6
  •  1
  •   S M Kamran    15 年前

    使用IEnumerable对象的条件是,使用Enumerable访问基础集合时,基础集合不得更改。可以假定可枚举对象是原始集合的快照。因此,如果在枚举时尝试更改集合,则会引发异常。然而,枚举中获取的对象一点也不可变。

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