views:

287

answers:

5

Consider the following C# code.

string[] stringArray = new string[10];
foreach (string s in stringArray)
    s = "a new string";  // Compiler error - Can't assign to foreach iteration variable

Now consider the following valid C++/CLI code.

array<String^>^ stringArray = gcnew array<String^>(10);
for each(String^% s in stringArray)
    s = "a new string"; 

When foreach is used with array type, compiler translates it into normal for loop. This implementation is same for C# and C++/CLI. So I wonder if C++/CLI can allow this, why not for C# compiler?

This error makes sense when the type is not an array as foreach will be compiled into GetEnumerator call and use the enumerator for iteration. But I think it can be allowed for array types.

Any thoughts?

As a side note, the following is a valid C++/CLI code too but will not produce the expected result.

List<String^>^ stringList = gcnew List<String^>(10);
for each(String^% s in stringList)
    s = "a new string"; // I think this should be prevented by compiler as it makes no sense.
A: 

I think you've answered your own question when you state that for the C++ case:

When foreach is used with array type, compiler translates it into normal for loop.

If you code the C# as a for loop you can do this.

The comments on this MSDN page explain more, but it boils down to the fact that by modifying the string you are changing the indexing of the collection - which is why the loop breaks.

ChrisF
That's not just for C++, it is true for C# as well. C# compiler translates foreach on an array type to for loop not calls to GetEnumerator.
Appu
A: 

This is because C# is using smoke and mirrors (ie magic). The variable you get back in the foreach is not the actual item in the array, it is a copy made by the iteration object... or something else. We don't really know (well we do, but we have to break the abstraction layer and look at the implementation of the iterator object.)

If you want to change the values IN the array, you have to deal directly with the array's interface to access those items. In c++ this happens, but mostly by mistake (a lot of c++ is like this, the original implementations were actually macros and pre-processing). In C# it is explicitly defined not to work -- thus a compiler message. (See section 5.3.3.16 in the spec.)

Hogan
+4  A: 

There seem to be three different questions here:

  1. Why does C++ allow you to assign to a for each iteration variable?
  2. Why doesn't C#?
  3. Why do the C++ and C# compilers behave differently?

The answers are fairly straightforward:

  1. Because the C++ team didn't decide to explicitly disallow it, and technically the iteration variable is just a local variable - it doesn't get special treatment.

  2. Because the C# team did decide to disallow it, because (most likely) they believe it would lead to bugs or incorrect code. Assigning to any loop variable is commonly considered a code smell.

  3. Because the C++ team and the C# team are different teams. C++ has always been a language that allows you to shoot yourself in the foot, if you so choose, and goes so far as to hand you the loaded gun. C# will often try to enforce "correct code" rules.

There might actually be another question here, which is:

  • Why would C# compile a foreach into a for if it doesn't allow assignment? Or, the converse - why doesn't allow this if that's how it gets compiled anyway?

There are actually two answers to this one:

  • Because it's faster. foreach operates on IEnumerable, which requires a new IEnumerator class to be instantiated. Array types are special types recognized by the compiler, so if the compiler already knows that the IEnumerable is actually an Array, then it compiles down to indexed access instead, which is much cheaper.

    This little performance tweak is simply an implementation detail, however; it is not part of the specification, and if you were able to write code that depends on the specific implementation, the C# team would be unable to change that implementation later without breaking existing code. They would certainly want to avoid such a situation.

  • Because it doesn't actually matter the way one might think it does. If you could do the assignment in C#, you would not actually be modifying the array, only the contents of the local variable that initially held something from the array. This, again, falls under the category of "make it difficult to write incorrect code" - if the construct did allow you to assign to the variable, some programmers might think that this would actually change the collection, which would be false.

I think that should explain it pretty well.

Aaronaught
Yes the C# team should worry about being led to an "incorrect dude". :D - Seriously, nice answer despite the funny typo. +1
Hogan
Whoops, I wonder how that happened... corrected!
Aaronaught
A: 

Re Hogan: Yes, a copy gets made by it. In fact, when I was running into it (today) I was banking on it being a modifieable copy:

foreach (string Token in tokenize(Command))
{
foreach (KeyValuePair<string, string> Replacement in TokensToReplace)
    {
    if (Token==Replacement.Key)
        {
        Token = Replacement.Value;
        }
    }
TokenList.Add(Token);
}

I think it's unfortunate that this doesn't work.

Volker Hetzer
It would be insane if this worked - the `foreach` loop can't possibly know how to reverse the process of a one-way function called `tokenize`. If your `Command` object is supposed to have mutable tokens, then it should implement its own API for modifying them.
Aaronaught
A: 

Aaronaught, the loop doesn't have to reverse the function it just has to create a string variable "Token" and initialize it with the value from tokenize. After that it doesn't have anything to do with any internals of tokenize. What I was trying to get at is that the loop variable shouldn't be some magic pointer into a collection but an Object that gets created by "string Token=" just as if it was created in a for or while loop.

I am NOT trying to modify some internal collection value but overwrite a local variable. It's not supposed to have consequences for the data in the collection since the pointer to the old string gets lost after the assignment.

In C++ it works.

This would have made more sense as a comment to the other answer, but anyway... I see what you mean, but it's a trivial 1 line of code to add a `string tempToken = Token` line at the top and operate on the temp token instead - and this expresses the true intent of the code much more clearly.
Aaronaught