简体   繁体   中英

Why is The Iteration Variable in a C# foreach statement read-only?

As I understand it, C#'s foreach iteration variable is immutable.

Which means I can't modify the iterator like this:

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

     Plot(Location);
}

I can't modify the iterator variable directly and instead, I have to use a for loop

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

     Plot(Location);        
     i = Location;
}

Coming from a C++ background, I see foreach as an alternative to the for loop. But with the above restriction, I usually fallback to using the for loop.

I'm curious, what is the rationale behind making the iterator immutable?


Edit:

This question is more of a curiousity question and not as a coding question. I appreciated the coding answers but I can't mark them as answers.

Also, the example above was over-simplified. Here is a C++ example of what I want to do:

// 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 can't do the above in C#'s foreach because the iterator i is immutable. I think (correct me if I'm wrong), this is specific to the design of foreach in languages.

I'm interested in why the foreach iterator is immutable.

Lets start out with a silly but illustrative example:

Object o = 15;
o = "apples";

At no point do we get the impression that we just turned the number 15 into a string of apples. We know that o is simply a pointer. Now lets do this in iterator form.

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

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

Again, this really accomplishes nothing. Or at least it would accomplish nothing were it to compile. It certainly wouldn't insert our string into the int array -- that's not allowed, and we know that o is just a pointer anyway.

Let's take your example:

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

     Plot(Location);
}

Were this to compile, the Location in your example stars out referring to a value in Map , but then you change it to refer to a new Position (implicitly created by the addition operator). Functionally it's equivalent to this (which DOES compile):

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

So, why does Microsoft prohibit you from re-assigning the pointer used for iteration? Clarity for one thing -- you don't want people assigning to it thinking they've changed your position within the loop. Ease of implementation for another: The variable might hide some internal logic indicating the state of the loop in progress.

But more importantly, there is no reason for you to want to assign to it. It represents the current element of the looping sequence. Assigning a value to it breaks the "Single Responsibility Principle" or Curly's Law if you follow Coding Horror. A variable should mean one thing only.

If the variable were mutable, that might give an incorrect impression. For example:

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

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

Some people might think that would change the array contents. It's reaching a bit, but it might be a reason. I'll look it up in my annotated spec tonight...

I think its artificial limitation, no really need to do it. To prove the point, take this code into considiration, and a possible solution to your problem. The problem is to asign, but internal changes of objects don't present a problem:

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

I didn't know about this phenomena, because I was always modifying the objects the way I described.

When you modify the collection, the modification might have unpredictable side effects. The enumerator has no way of knowing how to deal correctly with these side effects, so they made the collections immutable.

See also this question: What is the best way to modify a list in a foreach

The foreach statement works with Enumerable. The thing that is different with an Enumerable is that it doesn't know about the collection as a whole, it's almost blind.

This means that it doesn't know what's coming next, or indeed if there is anything until the next iteration cycle. That why there you see .ToList() a lot so people can grab a count of the Enumerable.

For some reason that I'm not sure about, this means that you need to ensure your collection isn't changing as you try to move the enumerator along.

The condition to work with IEnumerable objects is that the underlying collection must not change while you are accessing it with Enumerable. You can presume that the Enumerable object is a snap shot of the original collection. So if you tries to change the collection while enumerating it'll throw an exception. However the fetched objects in Enumeration is not immutable at all.

Since the variable used in foreach loop is local to the loop block this variable is however not available outside the block.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM