Foreach

Intro

foreach is the most common way to iterate a sequence in C#. It works with types that provide an enumerator (typically via IEnumerable / IEnumerable<T>), and the compiler rewrites the loop into an enumerator-based pattern.

You can use foreach with:

Internals (compiler lowering):

Under the hood, foreach compiles into:

var enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
    var item = enumerator.Current;
    // Use item
}

Iterators and yield

yield return and yield break let you write iterator methods: methods that produce a sequence lazily, one element at a time.

An iterator method returns IEnumerable<T> / IEnumerable (or in async scenarios IAsyncEnumerable<T>), but it doesn't execute immediately:

Example:

public static IEnumerable<int> CountNumbers(int start, int end)
{
    for (int i = start; i <= end; i++)
    {
        yield return i;
    }
}

foreach (var number in CountNumbers(1, 5))
{
    Console.WriteLine(number);
}

Pitfalls

Modifying the collection during iteration: Mutating a List<T> or Dictionary<TKey,TValue> inside a foreach over it throws InvalidOperationException. The enumerator tracks an internal version counter and detects the change. Fix by iterating a snapshot (collection.ToList()) or collecting mutations in a separate list and applying them after the loop.

Closure variable capture: Lambdas created inside foreach capture the loop variable by reference unless you shadow it into a local. Before C# 5, all lambdas in a foreach could close over the same item variable and see only the last value. Best practice: always copy to a local inside the lambda body if lifetimes extend beyond the iteration: var current = item; tasks.Add(() => Process(current));.

Tradeoffs

Questions


Whats next