foreach
foreach walks every element of a collection in order without an index, and its loop variable is read-only.
foreach (ElementType element in collection) { ... }foreach runs its body once for every element in a collection, in order, first to last. A collection here is any value that can hand out its items one at a time: an array, a list, even a string, which hands out its characters. You write no counter and you cannot run off the end, because the loop asks the collection for the next item rather than for position 7.
What the collection has to provide
Most collections you meet implement IEnumerable<T>, an agreement that reads "I can produce my items one at a time, and each one is a T". Strictly the compiler asks for less than that interface. It needs a public parameterless GetEnumerator() method whose result has a public Current property and a public parameterless MoveNext() method returning bool. The loop calls MoveNext(), and while that returns true it copies Current into the loop variable and runs the body.
The loop variable is read-only
Assigning to the loop variable is a compile error. That is deliberate. Writing to it would change only that one variable and leave the collection untouched, which looks like an edit but is not one. To transform values, compute a new value inside the body.
What trips people up
Adding or removing elements while a foreach over that same collection is running throws InvalidOperationException on the next pass. Gather the changes during the loop and apply them after it. And if you need the position of each element, keep your own counter or use a for loop.
Example
// An array holds a fixed number of values of one type.
// [ ... ] is the short form of new string[] { ... } (C# 12.0).
string[] names = ["ada", "grace", "alan"];
foreach (string name in names)
{
// name is read-only here: name = "x"; would not compile.
Console.WriteLine(name);
}
// Roughly what the compiler writes for that loop over a collection.
// For an array it emits an index loop instead, which is faster and
// behaves the same.
IEnumerator<string> enumerator = ((IEnumerable<string>)names).GetEnumerator();
while (enumerator.MoveNext())
{
string current = enumerator.Current;
Console.WriteLine($"again: {current}");
}
enumerator.Dispose(); // the real desugaring puts this in a finally block
// Output:
// ada
// grace
// alan
// again: ada
// again: grace
// again: alanWhen to use it
- Print every line read from a file in order.
- Add up the totals of every order in a list.
- Check that each character of a product code is a digit.
- Validate every item in a request and collect the failures.
More examples
When you need the position too
Numbering output, or comparing an element with the one before it, needs a position that foreach does not supply. A counter you keep yourself is fine and reads well; a for loop is better when you index into the collection more than once per pass. Note the two differ by one here because the display starts at 1 and indexes start at 0.
string[] steps = ["unpack", "build", "test"];
// foreach gives you the element, never its position.
// Keep your own counter when the number matters.
int position = 1;
foreach (string step in steps)
{
Console.WriteLine($"{position}. {step}");
position++;
}
// A for loop over the same array, where the index is the point.
for (int i = 0; i < steps.Length; i++)
{
Console.WriteLine($"index {i} is {steps[i]}");
}
// Output:
// 1. unpack
// 2. build
// 3. test
// index 0 is unpack
// index 1 is build
// index 2 is testA string is a sequence of characters
Useful whenever you validate or count characters, as in checking that a code contains only digits. The element type is the detail to notice: letter is a char, written with single quotes, so comparing it with "c" in double quotes is a compile error.
string word = "code";
// string implements IEnumerable<char>, so foreach works on it directly
// and the element type is char, not string.
foreach (char letter in word)
{
Console.WriteLine(letter);
}
// Output:
// c
// o
// d
// eProducing new values instead of assigning the loop variable
This is the error people meet when they try to clean up every entry in a collection in place. The loop variable is a copy of the element, so assigning to it could never have written back. To change the collection, assign through an index in a for loop, or build a new collection from the old one.
string[] names = ["ada", "grace"];
foreach (string name in names)
{
// name = name.ToUpperInvariant(); // error: cannot assign to a
// // foreach iteration variable
string shouted = name.ToUpperInvariant();
Console.WriteLine(shouted);
}
// The array itself is unchanged, which is why the assignment above
// is forbidden rather than merely useless.
Console.WriteLine(names[0]);
// Output:
// ADA
// GRACE
// ada
Discussion