views:

2393

answers:

8

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.

+6  A: 

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...

Jon Skeet
That would be the point Jon is trying to illustrate I guess. Notice the "If".
Brian Rasmussen
Brian, was your comment in response to another comment which has now been deleted?
Jon Skeet
Did you ever look this up in your spec? I assume that the designers made the value readonly for clarity as you suggest, but it'd be interesting to know whether there were other considerations!
Jon Artus
+1  A: 

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.

Ian
But changing the *variable* wouldn't change the enumerator anyway. It's just a local copy of the current value.
Jon Skeet
Surely that depends on if its a reference or value type? But yeah, I understand you're looking at the value, and maybe not adjusting the enumerator itself... hmm
Ian
+1  A: 

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

Rik
A: 

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.

S M Kamran
+3  A: 

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.

majkinetor
+1 for a decent work-around.
ashes999
A: 

Iterators (foreach) were in C# 1.0, but maybe somebody was thinking of functional programming (and the resulting immutability) back then.

Dan
+7  A: 

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 returned in the iterator? 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 iterator 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 the iterator. 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.

tylerl
A: 

Some research shows that the variable in the iteration is known as the iteration variable. The book "The C# Programming Language, Third Edition" says:

The iteration variable corresponds to a read-only local variable with a scope that extends over the embedded statement.

John Saunders