Why can't we assign a value to the local variable in a foreach loop?
I understand this would not update the collection because we are just changing what object the local variable refers to.
If you implement the same functionality using a while loop, it allows you to update the local variable.
class Program
{
static void Main(string[] args)
{
IEnumerable<string> enumerable = new List<string> { "a", "b", "c" };
//This foreach loop does not compile:
foreach (string item in enumerable)
{
item = "d"; //"cannot assign to item because it is a foreach iteration variable"
}
//The equivalent C# code does compile and allows assinging a value to item:
var enumerator = enumerable.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
string item = (string)enumerator.Current;
item = "d";
}
}
finally
{
enumerator.Dispose();
}
}
}
EDIT:
This question is different to the possible duplicate because it's asking why we can't modify the iteration variable when behind the scenes it's just a local variable pointing to the same object as enumerator.Current
.
According to Eric Lippert's answer,
The iteration variable is read-only because it is an error to write to it
.Looks like there is some rule in the compiler that stops you from compiling code that attempts to modify the iteration variable, even though it's not marked as
readonly
behind the scenes (it can't anyway because it's a local var).I modified your code so they're actually equivalent of each other:
As you can see, in neither form do we assign to the readonly enumerator.Current
When you write a foreach, the compiler rewrites your code for you, so that it looks like this:
With each passing version, C# evolves in a couple of ways. New processing structures can certainly be added to the language, but often "syntactic sugar" is an evolution achieved by having the compiler able to translate the code you write into a feature that already exists, such as
foreach(x in y)
becomingy.GetEnumerator(); while(..){ x = enumerator.Current; ... }
.Similarly, string interpolation
$"There are {list.Count:N2} items" being transformable to the implemented-first
string.Format("There are {0:N2} items, list.Count")`.Indeed, if one considers the job of the compiler to transform something that is written into something else that is already written, then even syntactic sugaring is an example of a new processing structure, because everything is already written (right the way down to the hardware)
To examine how the compiler rewrites a foreach, I wrote two methods:
I compiled with csc.exe that came with .NET SDK 4.7.2, then used ILDASM to visualize the generated MSIL (screenshot to show the side by side diff). The initial part of the method, setup, variables declare etc are identical but for a no-op:
As are the loop bodies:
The only difference discernible is in the disposing; I wasn't too interested in chasing down the reasons why.
So, it's clear that the compiler rewrites the method in the way shown. Your original question can only really be answered by the person that wrote the compiler, but it seems logical to me that it's an extension of the prohibition against assigning to
enumerator.Current
- it wouldn't make sense to prevent that but allow you to assign to the variable equivalent of it declared in your foreach loop. It's also clear that it's a special case assessed directly, from the way the error message talks abut the foreach loop specifically.I'd also say (opinion part) that preventing assignment to the foreach loop iterator variable prevents harm/unintended consequences/obscure bugs, with no drawback that I can discern. If you want a variable you can reassign; make another one