开发者

Changing foreach iteration variable and implementation differences between C# and C++/CLI

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.


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.


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.)


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜