views:

381

answers:

10

Why are assignment operators (=) invalid in a foreach loop? I'm using C#, but I would assume that the argument is the same for other languages that support foreach (e.g. PHP). For example, if I do something like this:

string[] sArray = new string[5];

foreach (string item in sArray)
{
   item = "Some assignment.\r\n";
}

I get an error, "Cannot assign to 'item' because it is a 'foreach iteration variable'."

+2  A: 

Because an IEnumerable is readonly.

Darin Dimitrov
I'm not sure that really explains why the "current item variable" is read-only.
Kirk Woll
@Kirk: because the current item isn't copied when read off the `IEnumerable` object.
BoltClock
@Kirk Woll, you cannot modify values using `IEnumerator`. The `IEnumerator.Current` property is readonly, it doesn't have a setter.
Darin Dimitrov
Any reason for the downvote?
Darin Dimitrov
@Darin: I did not downvote, but Kirk Woll's comment is probably the root of the reason why.
Billy ONeal
@Darin: I also did not downvote, but I think what's misleading about your answer is that you're conflating the local variable and the `Current` property. See Mehrdad's or my answer to understand what I mean.
Dan Tao
+4  A: 

Because you can't use a foreach loop to modify an array you're looping through. The loop iterates through the array, so if you try to modify what it's iterating through then unexpected behavior may occur. Furthermore, as Darin and DMan have pointed out, you're iterating through an IEnumerable which is itself read-only.

PHP makes a copy of the array in its foreach loop and iterates through that copy, unless you use references, in which case you'll modify the array itself.

BoltClock
Well, you can modify it, as long as you don't assign to it. For example, if you are "foreaching" over a bunch of objects, you can change the internals of the object, you just can't actually assign to the object itself.
Robaticus
+3  A: 

The foreach loop is designed to iterate through objects in a collection, not to assign things- it's simply design of the language.

Also, from MSDN:

"This error occurs when an assignment to variable occurs in a read- only context. Read-only contexts include foreach iteration variables, using variables, and fixed variables. To resolve this error, avoid assignments to a statement variable in using blocks, foreach statements, and fixed statements."

The foreach keyword just enumerates IEnumerable instances (getting an IEnumerator instances by calling the GetEnumerator() method). IEnumerator is read-only, therefore values can't be changed using IEnumerator =can't be changed using the foreach context.

DMan
+4  A: 

Because the language specification says so.

But seriously, not all sequences are arrays or things that can be logically modified or written to. For instance:

foreach (var i in Enumerable.Range(1, 100)) {
   // modification of `i` will not make much sense here.
}

While it would've been technically possible to have i = something; modify a local variable, it can be misleading (you may think it really changes something under the hood and it wouldn't be the case).

To support these kind of sequences, IEnumerable<T> doesn't require a set accessor for its Current property, making it read-only. Thus, foreach cannot modify the underlying collection (if one exists) using the Current property.

Mehrdad Afshari
This and the yield keyword were the first thing that came to mind as the real thing that we're being protected from. "unexpected behaviour" may occur, but C# rarely protects us from unexpected behaviour, your example though is worth protecting us from.
Jimmy Hoffa
A: 

You cannot modify an array that you are foreach'ing through. Use The following code instead:

string[] sArray = new string[5]; 

for (int i=0;i<sArray.Length;i++)
{
    item[i] = "Some Assignment.\r\n";
}
icemanind
You can reduce the whole thing to a single statement using `Enumerable.Repeat`.
Billy ONeal
A: 

In general, if you're trying to do this, you need to think long and hard about your design, because you're probably not using the best construction. In this case, the best answer would probably be

string[] sArray = Enumerable.Repeat("Some assignment.\r\n", 5).ToArray();

Higher level constructions are almost always usable instead of this kind of loop in C#. (And C++, but that's a whole other topic)

Billy ONeal
Strings are not value types. More importantly, assigning a local variable to a new value would **never** change the value of a reference elsewhere, unless it's a `ref` parameter (in which case, it's actually the same reference). Maybe you should say, "Because `item` has local scope..."?
Dan Tao
@Dan Tao: I have removed that portion of my answer; other answers here did a better job of explaining what I meant anyway.
Billy ONeal
@Billy: I figured you had just mistyped. Everybody's always in a mad rush to get their answer in first -- I know I am ;)
Dan Tao
+30  A: 

Here's your code:

foreach (string item in sArray)
{
   item = "Some assignment.\r\n";
}

Here's a rough approximation of what the compiler does with this:

using (var enumerator = sArray.GetEnumerator())
{
    string item;
    while (enumerator.MoveNext())
    {
        item = enumerator.Current;

        // Your code gets put here
    }
}

The IEnumerator<T>.Current property is read-only, but that's not actually relevant here, as you are attempting to assign the local item variable to a new value. The compile-time check preventing you from doing so is in place basically to protect you from doing something that isn't going to work like you expect (i.e., changing a local variable and having no effect on the underlying collection/sequence).

If you want to modify the internals of an indexed collection such as a string[] while enumerating, the traditional way is to use a for loop instead of a foreach:

for (int i = 0; i < sArray.Length; ++i)
{
    sArray[i] = "Some assignment.\r\n";
}
Dan Tao
+1 for dissecting the syntactic sugar :)
Billy ONeal
Great answer! Finally one that properly explains what's going on.
Kirk Woll
I'll never understand them compilers :/
BoltClock
Although you're correct in that `foreach` is just syntactic sure, you're a little bit incorrect in what the compiler actually transforms it into. There's a more accurate explanation of what happens on [Eric Lippert's blog](http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx). The post talks about closures but it also explains what the compiler does. One of the very important distinctions is that the temporary variable is declared **outside** the loop.
R0MANARMY
@R0MANARMY: Thanks for pointing that out; obviously in most cases these two constructs are not so different, but I've changed my answer anyway so as not to be misleading in those cases where they are. I still opted to go with `using` rather than the `try`/`finally` block since, after all, that's what `using` gets converted to by the compiler anyway (my main concern here was readability).
Dan Tao
You forgot to dispose of the enumerator. Depending upon how the enumerator is implemented, that could cause a severe memory leak.
supercat
@supercat: I did not forget! That's what the `using` statement does.
Dan Tao
A: 

The foreach is designed to interate through the array once, without repeating or skipping (though you can skip some action within the foreach construct by using the continue keyword). If you want to modify the current item, consider using a for loop instead.

Andy
A: 

You cannot modify a list that is being looped through via a "ForEach".

The best option is to simply create a temporary list to store the items you wish to use.

Jamie Keeling
No, the best option is to use an off the shelf component that lets you avoid using the loop in the first place, or writing a loop using subscripting instead of enumerators.
Billy ONeal
A: 

It would be perfectly possible to let it be altered. However, what does this then mean? It would read like the underlying enumeration was modified, which it isn't (it would be possible to allow that too, but that has its own downsides).

So you'd have code that people would naturally read as indicating something other than what has actually happened. Considering that the purpose of a computer language is primarily to be understood by people (compilers deal with the balance being set against them, unless you use assembly, machine code, or the appropriately named Brainf**k) this would indicate a flaw in the language.

Jon Hanna