foreach

foreach walks every element of a collection in order without an index, and its loop variable is read-only.

Syntaxforeach (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

Example · csharp
// 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: alan

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

Example · csharp
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 test

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

Example · csharp
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
// e

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

Example · csharp
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

  • Be the first to comment on this lesson.