The problem: a loop that can't change the array
The task is to add one to every even number in nums and subtract one from every odd number, so that {1, 2, 3, 4, 5} becomes {0, 3, 2, 5, 4}. The starting code uses a foreach loop:
int[] nums = {1, 2, 3, 4, 5};
foreach (int num in nums) {
if (num % 2 == 0) {
num++;
} else {
num--;
}
}
It doesn't even compile. The compiler stops at num++ and num-- with an error like this:
error CS1656: Cannot assign to 'num' because it is a 'foreach iteration variable'
That error is C# protecting you from a mistake. Even if the assignment were allowed, it would have no effect on the array, and the next section explains why.
The loop variable is a copy
With an int[], each pass of a foreach loop copies the current element into the loop variable. The variable num is a separate value that happens to start out equal to nums[0], then nums[1], and so on. Changing it can't reach back into the array, in the same way that changing one variable doesn't change the variable you copied it from:
int number = 100;
int numberCopy = number;
numberCopy++;
Console.WriteLine(numberCopy); // 101
Console.WriteLine(number); // 100
C# makes a foreach iteration variable read-only. Many languages let you modify it silently, and the bug only shows up as a loop that "does nothing". In C# the compiler points at the exact line instead.
What you can still change through a copy
If the array holds objects (instances of a class), the loop variable is a copy of the reference, not of the object. You still can't assign a new object to the loop variable, but you can change the object's members through it, and the change is visible in the array. That is a different situation from the one in this lesson: an int is a plain value, so the copy is all there is.
The fix: loop over the indices
To change an element, you have to write to the array slot itself. The expression nums[i] is that slot, so the loop needs the position i and not just the value. A for loop provides it.
- Replace
foreachwith a counting loop. Startiat 0, continue whilei < nums.Length, and add one after each pass. Arrays useLength, a property with no parentheses. - Read the element through its index. The test becomes
nums[i] % 2 == 0. Reading by index gives the same value the old loop variable held. - Write the element through its index.
nums[i]++andnums[i]--change the array slot in place, which is what the original lines were trying to do.
The fixed code in C#
int[] nums = {1, 2, 3, 4, 5};
for (int i = 0; i < nums.Length; i++) {
if (nums[i] % 2 == 0) {
nums[i]++;
} else {
nums[i]--;
}
}
Tracing the first three passes shows what happens:
i = 0:nums[0]is 1, which is odd, so it becomes 0.i = 1:nums[1]is 2, which is even, so it becomes 3.i = 2:nums[2]is 3, which is odd, so it becomes 2.
After all five passes, the array holds 0, 3, 2, 5, 4. Each element is visited exactly once and changed at most once, so the order of the checks can't cause one change to confuse another.
To see the result, print the array after the loop with Console.WriteLine(string.Join(", ", nums));, which shows 0, 3, 2, 5, 4.
Common mistakes
- Using
<=instead of<. The last valid index isnums.Length - 1. Withi <= nums.Length, the final pass readsnums[5]and throws anIndexOutOfRangeException. - Writing
nums.Length()ornums.length. C# is case-sensitive, andLengthis a property, not a method. Code ported from Java often has this slip. - Copying the Java or Python loop syntax. C# writes
foreach (int num in nums)with the keywordin. A colon, as infor (int num : nums), doesn't compile. - Working with a copy of the whole array. Assigning
int[] other = nums;doesn't copy the elements. Both names refer to the same array, so changes through either one are visible through the other. - Growing or shrinking a
List<int>insideforeach. Adding or removing items while aforeachis running makes the next step throw anInvalidOperationException. Use an index loop for that kind of work as well.
Key takeaways
- A
foreachvariable holds a copy of each element, and C# makes it read-only. - To change array elements in place, loop over the indices and assign to
nums[i]. - Use
foreachwhen you only need to read values, and aforloop with an index when you need to write them.