30

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'."

Carl Manaster
  • 39,912
  • 17
  • 102
  • 155
Jim Fell
  • 13,750
  • 36
  • 127
  • 202
  • 8
    Generally speaking, PHP lets you do all sorts of things that make maintaining software hard. More importantly, it isn't really similar to most other programming languages. Using PHP idioms in other languages like this is probably not the best idea to begin with. – Billy ONeal Aug 23 '10 at 16:54
  • Duplicate question: http://stackoverflow.com/questions/776430/why-is-the-iteration-variable-in-a-c-foreach-statement-read-only – chilltemp Aug 23 '10 at 21:14

10 Answers10

70

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
  • 125,917
  • 54
  • 300
  • 447
  • 2
    Great answer! Finally one that properly explains what's going on. – Kirk Woll Aug 23 '10 at 16:53
  • I'll never understand them compilers :/ – BoltClock Aug 23 '10 at 16:58
  • 2
    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. – Roman Aug 23 '10 at 17:22
  • @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 Aug 23 '10 at 17:34
  • You forgot to dispose of the enumerator. Depending upon how the enumerator is implemented, that could cause a severe memory leak. – supercat Aug 23 '10 at 18:19
  • 1
    @supercat: I did not forget! That's what the `using` statement does. – Dan Tao Aug 23 '10 at 18:28
6

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
  • 414,610
  • 91
  • 852
  • 789
  • 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 Aug 23 '10 at 16:54
5

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.

Dominic K
  • 6,975
  • 11
  • 53
  • 62
4

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
  • 700,868
  • 160
  • 1,392
  • 1,356
  • 1
    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 Aug 23 '10 at 16:55
2

Because an IEnumerable is readonly.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    I'm not sure that really explains why the "current item variable" is read-only. – Kirk Woll Aug 23 '10 at 16:49
  • 1
    @Kirk: because the current item isn't copied when read off the `IEnumerable` object. – BoltClock Aug 23 '10 at 16:51
  • @Kirk Woll, you cannot modify values using `IEnumerator`. The `IEnumerator.Current` property is readonly, it doesn't have a setter. – Darin Dimitrov Aug 23 '10 at 16:52
  • @Darin: I did not downvote, but Kirk Woll's comment is probably the root of the reason why. – Billy ONeal Aug 23 '10 at 16:58
  • @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 Aug 23 '10 at 17:02
2

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
  • 104,103
  • 58
  • 317
  • 552
  • 1
    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 Aug 23 '10 at 16:54
  • @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 Aug 23 '10 at 16:57
  • 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 Aug 23 '10 at 16:59
  • I like this answer!!! it is a lot neater than for-loop. I know this is not very common, but sometimes (in test cases) you need to generate an array of 100 things. – John Henckel Jun 05 '15 at 22:24
1

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
  • 47,519
  • 50
  • 171
  • 296
0

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
  • 856
  • 9
  • 26
0

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
  • 9,806
  • 17
  • 65
  • 102
  • 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 Aug 23 '10 at 16:57
0

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
  • 110,372
  • 10
  • 146
  • 251