Why Is the Iteration Variable in a C# Foreach Statement Read-Only

Why is foreach loop Read-Only in C#

That is because foreach is meant to iterate over a container, making sure each item is visited exactly once, without changing the container, to avoid nasty side effects.

See: foreach in MSDN

If you meant why would changes to an element like an integer not affect a container of integers, well this is because the variable of iteration in this case would be a value type and is copied, e.g.:

// Warning: Does not compile
foreach (int i in ints)
{
++i; // Would not change the int in ints
}

Even if the variable of iteration was a reference type, whose operations returned a new object, you wouldn't be changing the original collection, you would just be reassigning to this variable most of the time:

// Warning: Does not compile
foreach (MyClass ob in objs)
{
ob=ob+ob; // Reassigning to local ob, not changing the one from the original
// collection of objs
}

The following example has the potential to actually modify the object in the original collection by calling a mutating method:

// Warning: Does not compile
foreach (MyClass ob in objs)
{
ob.ChangeMe(); // This could modify the object in the original collection
}

To avoid confusion with regard to value vs reference types and the scenarios mentioned above (along with some reasons related to optimization), MS chose to make the variable of iteration readonly.

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

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.

How is the iteration variable readonly?

There is special-case code in the compiler which enforces the read-only constraint on the iteration variable in a foreach block. It does not correspond to any modifier which is exposed in the language, so you can't explicitly declare local variables as read-only outside of this particular syntax.

Conceptually, this constraint is applied before the expansion. That is, if there are any assignments to the iteration variable, the compiler generates an error. Otherwise the code is expanded. In the expanded code there is no particular constraints on v since it is just a regular local variable. Therefore the constraint does not exist in the IL either.

So why is there this special-case read-only constraint with the foreach-syntax? Only the language designers can answer that, but I would guess it is just to avoid confusion. If the iterator variable was assignable, you might think you were able to modify the actual collection that way, but nothing would actually happen, since the underlying enumerator is read-only.

Can a foreach iteration variable not change for ANY reason?

Whenever you encounter read only concepts in C#, it's almost always a shallow form of read only - you can't change which object the variable is referring to, but you can make changes to that object's own properties (or call methods on it, etc)

The same applies for readonly fields of a class - you can't change the reference but you can mutate the object.

Cannot assign to item because it is a foreach iteration variable

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 wrote an article with all the questions/answers I came up with while learning about IEnumerable/foreach.

Why can't I modify the loop variable in a foreach?

I'm not sure exactly what you mean by a "readonly loop" but I'm guessing that you want to know why this doesn't compile:

int[] ints = { 1, 2, 3 };
foreach (int x in ints)
{
x = 4;
}

The above code will give the following compile error:


Cannot assign to 'x' because it is a 'foreach iteration variable'

Why is this disallowed? Trying to assigning to it probably wouldn't do what you want - it wouldn't modify the contents of the original collection. This is because the variable x is not a reference to the elements in the list - it is a copy. To avoid people writing buggy code, the compiler disallows this.

Why can't we assign a foreach iteration variable, whereas we can completely modify it with an accessor?

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.

Foreach loop passing the iteration variable to a method with and without ref - dont get why it wont allow it with ref

Ultimately, the notes on CS1657 explains all of this; the l-value of a regular foreach is considered "readonly" - you can't do:

person = new Person();

for example. The ref usage requires the parameter not be read-only, otherwise we have violated the promise of read-only-less, because the method could modify something that is meant to be read-only. In this case, since Person is a class, this only relates to the actual reference itself, not the underlying object.

There is good news, though: we now have in, which is like ref, but for read-only scenarios, so: change public void ChangeName(ref Person person) to public void ChangeName(in Person person) and it should work:

myPeople.ChangeName(in person);

The restriction on this is that you then, in your ChangeName method, can't do things like:

public void ChangeName(in Person person)
{
person = new Person(); // invalid
}

Emphasis: this in usage isn't really intended for classes; it is intended for struct, and in particular readonly struct - to avoid defensive stack copies of complex and large value-types.

In latest versions of C#, there is also a ref readonly concept, by-which the l-value is a ref Foo (for whatever type Foo). This requires a custom iterator with a public ref Foo Current {get;} accessor instead of a public Foo Current {get;} accessor, i.e. it is explicitly tied into ref-return. When the l-value is a ref, you can use it in a method that takes a ref.


Important emphasis, although I realize OP knows this: you don't need ref in this scenario since Person is a class itself; the code would work just fine without the ref, and the object would be updated correctly.

Why can't we change the iteration variable in a foreach loop

Question 1: Why I was not able to change the any attribute of an iteration variable?

From the documentation on Anonymous Types:

Anonymous types provide a convenient way to encapsulate a set of read-only properties

You cannot change the values of the properties in your anonymous type, so

name.Age = 1;
// and
names[i].Age = 1;

are equally invalid.


Question 2. I was only able to assign a new object to the iteration variable in for loop. Not in foreach loop. Why?

From the documentation on IEnumerable:

An enumerator remains valid as long as the collection remains unchanged.

You would invalidate the iterator if you change the backing list in any way. Consider what would happen if the iterator returned the items in a specific order based on the Age field, for example.



Related Topics



Leave a reply



Submit