C# Array Loop: for vs foreach and More
c# array loop: Learn how to iterate over C# arrays effectively. Compare for and foreach, handle multi-dimensional arrays, and avoid common pitfalls.
When you write a c# array loop, the first decision is which loop construct to use. The two primary options are for and foreach. Both are valid, but they behave differently in terms of syntax, flexibility, and runtime characteristics. This article explains the tradeoffs and shows you how to choose the right pattern for your specific scenario.
Basic Array Loops: for and foreach
The simplest way to iterate over an array is with foreach. It hides the index management and works with any IEnumerable<T>:
int[] numbers = { 10, 20, 30, 40 }; foreach (int number in numbers) { Console.WriteLine(number); }
The for loop gives you explicit control over the index and the loop condition:
for (int i = 0; i < numbers.Length; i++) { Console.WriteLine(numbers[i]); }
Both produce the same output. The difference becomes important when you need to access the index, modify the array, or control the iteration order.
Choosing Between for and foreach
The decision depends on what you need to do inside the loop.
Use foreach when you only need to read each element and do not need the index. It is more concise and less error-prone because you do not manage the loop counter or the upper bound. The compiler translates foreach on an array into a for loop internally, so there is no performance penalty for using it on arrays specifically.
Use for when you need the index for any reason, such as:
- Writing back to the array at the current position.
- Skipping or repeating elements by adjusting the index.
- Iterating in reverse order.
- Accessing neighboring elements (e.g.,
array[i-1]andarray[i+1]).
Here is a common pattern that requires for: reversing an array in place.
int[] array = { 1, 2, 3, 4, 5 }; for (int i = 0; i < array.Length / 2; i++) { int temp = array[i]; array[i] = array[array.Length - 1 - i]; array[array.Length - 1 - i] = temp; }
foreach cannot modify the collection it iterates over, and it does not expose the index. Attempting to assign to the iteration variable is a compile-time error because it is a read-only local.
Modifying Array Elements During Iteration
Arrays are reference types, and their elements are mutable even when you use foreach. You can change an element's properties if the element is a class, but you cannot replace the element itself. For example:
class Point { public int X; public int Y; } Point[] points = { new Point { X = 1, Y = 2 }, new Point { X = 3, Y = 4 } }; foreach (Point p in points) { p.X = 10; // This works: p references the same object in the array. }
However, p = new Point(); inside the loop would not change the array. To replace elements, use for:
for (int i = 0; i < points.Length; i++) { points[i] = new Point { X = i, Y = i * 2 }; }
This distinction is a frequent source of confusion for developers new to C#.
Performance Characteristics of Array Loops
Both for and foreach compile to nearly identical IL for arrays. The JIT compiler typically eliminates bounds checks in foreach because it knows the array length is fixed. In for, bounds checks are also optimized away when the loop condition is i < array.Length and the index is a local variable. The main performance cost comes from accessing elements that are not in the CPU cache, not from the loop construct itself.
A foreach loop over a List<T> is slower than over an array because the list's enumerator is a struct but still involves virtual calls in some cases. For arrays, the enumerator is a simple struct that the JIT can inline. Therefore, do not avoid foreach on arrays for performance reasons.
If you need maximum performance in a hot path, consider using a for loop with a local copy of the length:
int[] data = GetData(); int length = data.Length; for (int i = 0; i < length; i++) { Process(data[i]); }
This prevents the JIT from re-evaluating the Length property each iteration, though in practice the JIT often hoists it automatically. The measurable difference is negligible in most applications.
Handling Multi-Dimensional and Jagged Arrays
C# has two types of multi-dimensional arrays: rectangular (int[,]) and jagged (int[][]). Looping over them requires different approaches.
For a rectangular array, you can use nested for loops with GetLength:
int[,] matrix = new int[3, 4]; for (int i = 0; i < matrix.GetLength(0); i++) { for (int j = 0; j < matrix.GetLength(1); j++) { matrix[i, j] = i * j; } }
foreach also works on rectangular arrays, but it returns elements in row-major order and does not give you the indices. If you need indices, use nested for loops.
Jagged arrays are arrays of arrays, so you can iterate with a foreach over the outer array and then a foreach over each inner array:
int[][] jagged = new int[3][]; jagged[0] = new int[] { 1, 2 }; jagged[1] = new int[] { 3, 4, 5 }; jagged[2] = new int[] { 6 }; foreach (int[] inner in jagged) { foreach (int value in inner) { Console.WriteLine(value); } }
Jagged arrays are more flexible and often faster than rectangular arrays because each row is a separate array that can be allocated independently.
Using Span<T> for Efficient Array Iteration
Span<T> is a ref struct that provides a safe, allocation-free view over contiguous memory, including arrays. It is useful when you want to avoid creating subarrays or when working with slices.
int[] array = { 1, 2, 3, 4, 5, 6 }; Span<int> span = array.AsSpan(2, 3); // elements 3,4,5 foreach (int value in span) { Console.WriteLine(value); }
Span<T> also supports a for loop with index access, and it has a Length property. Because Span<T> is stack-only, it cannot be used in async methods or as a field in a class. For most array iteration scenarios, a simple for or foreach is sufficient; Span<T> becomes valuable when you need to pass slices without copying or when interoperating with native code.
Common Pitfalls and How to Avoid Them
One frequent mistake is modifying an array while iterating with foreach. As explained earlier, you cannot add, remove, or replace elements without throwing an InvalidOperationException or silently failing. Use for when you need to change the array structure.
Another pitfall is off-by-one errors in for loops. Always use < array.Length rather than <= to avoid IndexOutOfRangeException. For reverse iteration, use i >= 0 with a careful condition:
for (int i = array.Length - 1; i >= 0; i--) { // process array[i] }
When you need to iterate over a portion of an array, avoid creating a new array with Array.Copy or LINQ's Skip/Take if the data is large. Use ArraySegment<T> or Span<T> to reference the original data without copying.
Finally, remember that foreach over an array that is null throws a NullReferenceException. Always check for null before iterating if the array comes from an external source.
Choosing the right array loop pattern comes down to the need for the index, the need to modify elements, and the data structure you are working with. For simple read-only iteration, foreach is clean and efficient. For index-based manipulation, for gives you the control you need. Understanding these differences prevents subtle bugs and keeps your code maintainable.