42

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.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
MrValdez
  • 8,515
  • 10
  • 56
  • 79

7 Answers7

26

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.

tylerl
  • 30,197
  • 13
  • 80
  • 113
  • 3
    Tyler, I may be wrong but I don't agree with your assertion that "Postion Location" or "Object O" are iterators. Instead, they are iteration variables which have iterators that operate on them. If that is indeed the case, then I think your reasoning may be a little skewed. I do agree that changing the value of the iteration variable can cause clarity issues but I do wish Microsoft had left that for the developer to decide. I have found a number of instances where it would have been useful to change the value of a foreach iteration variable. – Anthony Gatlin Oct 06 '11 at 23:22
  • @AnthonyGatlin I don't agree that they should let the developer decide in this case. There's a very real cost to confusing code, and Microsoft has enough people coding in C# that they have a very real incentive to create a language that, as much as possible, prevents the developer from creating bugs. – tylerl Nov 18 '11 at 19:46
  • "*A variable should mean one thing only*". Good concept. In my head, though, it'd be better if it simply meant "*current element*" rather than a "*reference*" or a "*pointer*". Or, in the op's instance `Map[i]`, which is exactly what we intuitively expect. – cregox May 04 '12 at 17:04
  • I also do not agree with you. C#'s `foreach` kills the entire concept of mutable iterators. I am forced to make a copy while iterating with foreach and assign the copy afterwards which is indeed, ineficient. Or to abandon using syntax sugar like `foreach` in favor of good old for(i;i – Петър Петров Jan 26 '17 at 21:15
  • `foreach (Object o in nums) {` - isn't that basically the same as casting a [`List`](https://msdn.microsoft.com/en-us/library/6sh2ey19(v=vs.110).aspx) into [`IList`](https://msdn.microsoft.com/en-us/library/system.collections.ilist(v=vs.110).aspx)? That's perfectly feasible. However, assigning an arbitrary `object` via [`IList`'s indexer](https://msdn.microsoft.com/en-us/library/bb346281(v=vs.110).aspx) is not possible: If the assigned value does not match the strongly-typed list item type, an exception will be thrown. `o = "apples";` could easily be treated the same way. – O. R. Mapper Feb 05 '17 at 20:47
  • Other than that, concerning "you don't want people assigning to it thinking they've changed your position within the loop" - as I've pointed out [in another comment here](http://stackoverflow.com/questions/776430/why-is-the-iteration-variable-in-a-c-sharp-foreach-statement-read-only#comment71286578_776450), I don't currently see any technical reason why the iteration variable in a `foreach` loop couldn't work like that. – O. R. Mapper Feb 05 '17 at 20:52
14

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
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    That would be the point Jon is trying to illustrate I guess. Notice the "If". – Brian Rasmussen Apr 22 '09 at 10:26
  • 1
    Brian, was your comment in response to another comment which has now been deleted? – Jon Skeet Apr 22 '09 at 10:29
  • 3
    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 Jun 18 '10 at 11:47
  • Is this you and your family, Jon? – Dio F Feb 13 '13 at 21:17
  • "Some people *might* think that would change the array contents." - I'm not sure what's the issue here. Obviously, the compiler could compile any access to what appears as a simple iteration variable in source code into something like a property getter/setter call that can modify the underlying array as needed. – O. R. Mapper Feb 05 '17 at 20:41
  • @O.R.Mapper: That would be very surprising behaviour, IMO. And while it could work for arrays, it couldn't work for other forms of `foreach`, leading to a very strange design IMO. I'm glad it's the way it is. – Jon Skeet Feb 05 '17 at 21:36
  • @JonSkeet: "That would be very surprising behaviour, IMO." - given that it's not surprising when properties behave the same way, our opinions seem to differ on that. In particular, I'm not convinced that being unable to assign to the iteration variable is any less surprising in seemingly simple situations (such as when iterating over an array). "And while it could work for arrays, it couldn't work for other forms of `foreach`" - the compiler allows `foreach` on things that implement `IEnumerable`. I see no reason why it cannot also allow assignment to the iteration variable within ... – O. R. Mapper Feb 05 '17 at 21:53
  • ... `foreach` when another, similar condition (e.g. a `IModifyableEnumerable` interface) is met. Ultimately, though, one gets used to whichever way is defined to be the way a language works. – O. R. Mapper Feb 05 '17 at 21:53
  • @O.R.Mapper: Properties and local variables act very differently in all kinds of ways. You're suggesting that something should be changed to make one particular kind of local variable behave differently to others. Note that the "surprise" of not being able to assign to the iteration variable leads to a compile-time error - whereas the surprise of it compiling but behaving differently to expectations would (IMO) lead to subtle bugs. – Jon Skeet Feb 05 '17 at 21:59
  • @JonSkeet: "to make one particular kind of local variable behave differently to others" - it already does behave differently to others. It cannot be assigned to. Arguably, I am suggesting to make this particular kind of local variable behave *more* similarly to others. – O. R. Mapper Feb 05 '17 at 22:05
  • @O.R.Mapper: But that's a safer and simpler difference than the one you were proposing. Anyway, I don't think either of us is going to persuade the other. I strongly support the language design here. – Jon Skeet Feb 05 '17 at 22:06
  • @O.R.Mapper: With `ref` local variables in C# 7, I can see a use case for an iteration variable to be declared as `ref` as well for arrays. That would be reasonable - but not just doing so implicitly, IMO. – Jon Skeet Feb 05 '17 at 22:08
11

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
  • 8,730
  • 9
  • 54
  • 72
4

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

Community
  • 1
  • 1
Rik
  • 28,507
  • 14
  • 48
  • 67
  • 1
    Can you name at least one concrete example of such a side-effect that the enumerator would have to handle (that cannot occur when the iteration variable is immutable)? Otherwise, this answer sounds extremely vague. – O. R. Mapper Feb 05 '17 at 20:33
  • 1
    I agree with @O.R.Mapper. I can understand why adding/removing elements from a collection is bad (would the foreach have to go back a loop to access the one you just inserted?) but updating elements in the collection should be fine. – David Klempfner Sep 18 '19 at 10:01
1

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

Not anymore it's not! Since C# 7.3 (September 2018) we have ref return, allowing Enumerator.Current to return a reference to an iterator value.

This prevents copying a value (or reference), and allows you to overwrite array elements in-place using the foreach (ref var v ...) syntax:

var arr = new[] { "foo", "bar" };

foreach (ref var v in arr.AsSpan())
{
    v = "baz";
    Console.WriteLine(v);
}

Console.WriteLine(string.Join(", ", arr));

This outputs:

baz
baz
baz, baz

References (hah!):

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
1

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
  • 33,605
  • 26
  • 118
  • 198
  • 3
    But changing the *variable* wouldn't change the enumerator anyway. It's just a local copy of the current value. – Jon Skeet Apr 22 '09 at 10:16
  • 1
    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 Apr 22 '09 at 11:29
  • Surely, this is not a "hard" reason to not allow modification of the iterator variable. The compiler is already hard-coded to look for `IEnumerable`/`IEnumerable` to decide whether or not to allow a `foreach` loop in the first place. *If* the language designers had chosen to allow modification of the iteration variable, the compiler could have been made to accept such modifications in cases where the `foreach` loop is used to iterate over an `IList`/`IList`. – O. R. Mapper Feb 05 '17 at 20:37
  • @Ian it doesn't matter if it's a reference or value type. If you assign a new value to a value type variable, or a new reference to a reference type variable, either way, you are only updating that local variable, not the element in the collection. – David Klempfner Sep 18 '19 at 09:59
1

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
  • 4,423
  • 7
  • 25
  • 35