Back to Blog
C#

C# foreach Loop: Syntax, Behavior, and Performance

c# foreach loop: Learn how to use the C# foreach loop to iterate over collections, understand its runtime behavior, avoid common pitfalls, and know when to choose it o...

C#foreachIEnumerableCollectionsLoop Performance
Illustration of a C# foreach loop iterating over a collection with a cursor moving through elements sequentially.

The c# foreach loop is the most common way to iterate over a collection in C#. It hides index management and works with any type that implements IEnumerable or IEnumerable<T>. This article explains how it behaves, where it fits, and the tradeoffs you should consider when choosing between foreach and other iteration patterns.

Basic foreach Syntax and How It Works

The foreach loop iterates over each element in a collection without requiring an explicit index. The compiler generates code that calls GetEnumerator() and then repeatedly calls MoveNext() and accesses Current.

foreach (var item in collection) { Console.WriteLine(item); }

The var keyword infers the element type from the collection. If the collection is a List<string>, item is string. This works because List<T> implements IEnumerable<T>, which provides the enumerator the loop relies on.

Iterating Over Different Collection Types

foreach works with arrays, lists, dictionaries, and any type that implements IEnumerable or IEnumerable<T>. For arrays, the compiler optimizes the loop to a simple for loop, eliminating enumerator overhead. For dictionaries, each iteration yields a KeyValuePair<TKey, TValue>.

Dictionary<string, int> scores = new() { { "Alice", 90 }, { "Bob", 85 } }; foreach (var pair in scores) { Console.WriteLine($"{pair.Key}: {pair.Value}"); }

If you only need keys or values, use scores.Keys or scores.Values to avoid the pair overhead and make the intent clearer.

Modifying Collections During Iteration

Attempting to add, remove, or replace elements in a collection while iterating with foreach throws an InvalidOperationException. The enumerator tracks the collection's version; any modification invalidates it.

List<int> numbers = new() { 1, 2, 3 }; foreach (var number in numbers) { // This throws InvalidOperationException numbers.Add(number * 2); }

To modify a collection while iterating, iterate over a copy or collect changes and apply them after the loop. For example, you can iterate over numbers.ToList() if you need to add elements based on the original values.

foreach vs for: When to Use Which

foreach is more readable and less error-prone because you don't manage an index. for gives you explicit control over the index, which is useful when you need to skip elements, iterate backward, or modify the collection by index.

Criterionforeachfor
Index accessNot availableFull control
ReadabilityHigherLower
Modifying collectionNot allowedPossible if done carefully
Works with IEnumerableYesNo (needs indexable type)

Use foreach unless you specifically need index-based logic. If you find yourself tracking an index inside a foreach body, a for loop is usually clearer.

Performance Considerations of foreach

For arrays, the C# compiler translates foreach into a for loop, so there is no performance penalty. For List<T>, the enumerator is a struct, so iterating with foreach avoids heap allocation. However, if you manually call GetEnumerator() and cast it to IEnumerator<T>, you introduce boxing and allocation. The foreach construct avoids that by using the concrete type directly.

When performance is critical, measure your specific scenario. The difference between foreach and for on a List<T> is usually negligible, but the enumerator overhead can matter in tight loops over large collections. In such cases, a for loop with an index may be marginally faster, but the readability loss is often not worth it.

Using foreach with Custom Types and IEnumerable

To make your custom type iterable with foreach, implement IEnumerable<T> or IEnumerable. You need to provide an enumerator, either by implementing GetEnumerator() directly or by using an iterator method with yield return.

public class NumberSequence : IEnumerable<int> { public IEnumerator<int> GetEnumerator() { for (int i = 0; i < 10; i++) { yield return i; } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }

Now you can use foreach over NumberSequence just like any built-in collection. This pattern is useful for lazy sequences where you don't want to materialize all elements upfront.

Common Pitfalls and Edge Cases

  • A null collection causes a NullReferenceException when GetEnumerator() is called. Always check for null before iterating if the collection can be null.
  • An empty collection simply skips the loop body, which is usually the desired behavior.
  • If the collection is modified by another thread during iteration, the behavior is undefined; use a synchronization mechanism like a lock or iterate over a snapshot.
  • foreach over a Span<T> or ReadOnlySpan<T> is supported in modern C# and is highly efficient because it avoids allocation entirely.

These are the main points to keep in mind when using foreach in production code. Understanding how the loop interacts with different collection types and the underlying enumerator helps you write predictable, maintainable iteration logic.

c# foreach loop: Practical Usage and Code Examples | RYUSLOG DEV