81

I was just curious about this: the following code will not compile, because we cannot modify a foreach iteration variable:

        foreach (var item in MyObjectList)
        {
            item = Value;
        }

But the following will compile and run:

        foreach (var item in MyObjectList)
        {
            item.Value = Value;
        }

Why is the first invalid, whereas the second can do the same underneath (I was searching for the correct english expression for this, but I don't remember it. Under the...? ^^ )

GianT971
  • 4,385
  • 7
  • 34
  • 46
  • 5
    I think you mean "under the hood" - but it's not actually the same thing at all. – Jon Skeet Oct 20 '11 at 15:14
  • @GianT971 Could you describe how you would 'do the same' in your setter? `this = value`? – jv42 Oct 20 '11 at 15:17
  • @JonSkeet Yess Under the hood ^^. Ok so item is actually a reference to the object, and not the object itself, I got it now – GianT971 Oct 20 '11 at 15:29
  • 1
    @jv42 you are totally right, I forgot that two objects with the same values for all properties don't mean that they are equals – GianT971 Oct 20 '11 at 15:41

9 Answers9

51

foreach is a read only iterator that iterates dynamically classes that implement IEnumerable, each cycle in foreach will call the IEnumerable to get the next item, the item you have is a read only reference, you can not re-assign it, but simply calling item.Value is accessing it and assigning some value to a read/write attribute yet still the reference of item a read only reference.

Mohamed Abed
  • 5,025
  • 22
  • 31
  • 6
    "_foreach is a read only iterator that iterates dynamically classes that implement IEnumerable_" Whilst correct, this is not strictly true. The compiler only requires the type you're iterating over to have `T GetEnumerator()`, `bool MoveNext()` methods and a `T Current` property (assuming you want to create your own enumerator). You don't actually need to implement `IEnumerable`. – Joseph Woodward Nov 25 '16 at 11:13
  • 3
    @JosephWoodward: *"Whilst correct, this is not strictly true."* Joseph, this is a Yoda-level statement that will definitely make it into my all time top 10! :D – Jpsy Dec 07 '18 at 09:23
  • 2
    What is the reasoning behind making the iteration variable read only, when you can modify the object it refers to? You can also modify the variable's reference if you want to by removing the syntactic sugar and using a while loop and calling MoveNext() etc. – David Klempfner Sep 18 '19 at 06:52
  • because you can manage the access level of the properties individually such as by making something only have a getter, but no setter. It allows granular access to properties inside an object – Bon Jul 23 '20 at 06:24
38

The second isn't doing the same thing at all. It's not changing the value of the item variable - it's changing a property of the object to which that value refers. These two would only be equivalent if item is a mutable value type - in which case you should change that anyway, as mutable value types are evil. (They behave in all kinds of ways which the unwary developer may not expect.)

It's the same as this:

private readonly StringBuilder builder = new StringBuilder();

// Later...
builder = null; // Not allowed - you can't change the *variable*

// Allowed - changes the contents of the *object* to which the value
// of builder refers.
builder.Append("Foo");

See my article on references and values for more information.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
32

You can't modify a collection while it's being enumerated. The second example only updates a property of the object, which is entirely different.

Use a for loop if you need to add/remove/modify elements in a collection:

for (int i = 0; i < MyObjectList.Count; i++)
{
    MyObjectList[i] = new MyObject();
}
James Johnson
  • 45,496
  • 8
  • 73
  • 110
  • 1
    But if the code did compile, it wouldn't modify the collection anyway since the iteration variable is a local variable and you'd just be updating the reference pointed to by that local variable. – David Klempfner Sep 14 '19 at 06:57
12

If you look at the language specification you can see why this is not working:

The specs say that a foreach is expanded to the following code:

 E e = ((C)(x)).GetEnumerator();
   try {
      V v;
      while (e.MoveNext()) {
         v = (V)(T)e.Current;
                  embedded-statement
      }
   }
   finally {
      … // Dispose e
   }

As you can see the current element is used to call MoveNext() on. So if you change the current element the code is 'lost' and can't iterate over the collection. So changing the element to something else doesn't make any sense if you see what code the compiler is actually producing.

Wouter de Kort
  • 39,090
  • 12
  • 84
  • 103
  • 3
    As I understand `v`will hold the current element reference and `e` holds the iterator reference. So the compiler could just assign whatever we wanted o `v` and `e` will still be holding the iterator. This way foreach element variables could be assigned new references as they are converted to the `v`variable, not `e`. – Michel Feinstein Sep 16 '16 at 02:51
5

It would be possible to make item mutable. We could change the way the code is produced so that:

foreach (var item in MyObjectList)
{
  item = Value;
}

Became equivalent to:

using(var enumerator = MyObjectList.GetEnumerator())
{
  while(enumerator.MoveNext())
  {
    var item = enumerator.Current;
    item = Value;
  }
}

And it would then compile. It would not however affect the collection.

And there's the rub. The code:

foreach (var item in MyObjectList)
{
  item = Value;
}

Has two reasonable ways for a human to think about it. One is that item is just a place-holder and changing it is no different to changing item in:

for(int item = 0; item < 100; item++)
    item *= 2; //perfectly valid

The other is that changing item would actually change the collection.

In the former case, we can just assign item to another variable, and then play with that, so there's no loss. In the latter case this is both prohibited (or at least, you can't expect to alter a collection while iterating through it, though it doesn't have to be enforced by all enumerators) and in many cases impossible to provide (depending on the nature of the enumerable).

Even if we considered the former case to be the "correct" implementation, the fact that it could be reasonably interpreted by humans in two different ways is a good enough reason to avoid allowing it, especially considering we can easily work around that in any case.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • While this seems to be the _actual_ answer, I don't find the latter interpretation reasonable at all. – Zeus Jun 24 '20 at 07:04
1

Use for loop instead of foreach loop and assign value. it will work

foreach (var a in FixValue)
{
    for (int i = 0; i < str.Count(); i++)
    {
       if (a.Value.Contains(str[i]))
           str[i] = a.Key;
    }
 }
SPnL
  • 338
  • 4
  • 15
1

Because the first one doesn't make much sense, basically. The variable item is controlled by the iterator (set on each iteration). You shouldn't need to change it- just use another variable:

foreach (var item in MyObjectList)
{
    var someOtherItem = Value;

    ....
}

As for the second, there are valid use cases there- you might want to iterate over an enumeration of cars and call .Drive() on each one, or set car.gasTank = full;

Chris Shain
  • 50,833
  • 6
  • 93
  • 125
1

The point is that you cannot modify the collection itself while iterating over it. It is absolutely legal and common to modify the objects the iterator yields.

Florian Greinacher
  • 14,478
  • 1
  • 35
  • 53
1

Because the two are not the same. When doing the first, you might expect to change the value that is in the collection. But the way foreach works, there is no way that could have been done. It can only retrieve items from the collection, not set them.

But once you have the item, it's an object like any other and you can modify it in any (allowed) way you can.

svick
  • 236,525
  • 50
  • 385
  • 514