Skip to content

C# IEnumerable Multiple Enumeration Error Fix

DodaTech Updated 2026-06-24 3 min read

In this tutorial, you'll learn about C# IEnumerable Multiple Enumeration Error Fix. We cover key concepts, practical examples, and best practices.

Your code iterates over an IEnumerable twice but the second loop is empty:

var items = GetItems(); // returns IEnumerable<string>
Console.WriteLine(items.Count()); // first enumeration
foreach (var item in items) { ... } // second enumeration — empty!

IEnumerable uses deferred execution. The first Count() already consumed the enumeration if the source is a forward-only sequence (like a database cursor or a generator). You need to materialize the sequence before multiple iterations.

Step-by-Step Fix

1. Materialize with ToList() or ToArray()

WRONG — enumerating an IEnumerable multiple times:

IEnumerable<string> lines = File.ReadLines("log.txt");
int count = lines.Count();
string first = lines.First(); // re-opens the file, seeks from start if possible

RIGHT — materialize once:

List<string> lines = File.ReadLines("log.txt").ToList();
int count = lines.Count;
string first = lines[0];

2. Understand deferred execution

WRONG — assuming LINQ queries cache results:

IEnumerable<int> evenNumbers = numbers.Where(n => n % 2 == 0);
Console.WriteLine(evenNumbers.Count());
numbers.Add(6);
Console.WriteLine(evenNumbers.Count()); // different result!

RIGHT — force execution when you need a snapshot:

List<int> evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
Console.WriteLine(evenNumbers.Count);
numbers.Add(6);
Console.WriteLine(evenNumbers.Count); // same result — cached

3. Avoid expensive operations per enumeration

WRONG — each enumeration re-executes the expensive query:

IEnumerable<Order> orders = dbContext.Orders.Where(o => o.Amount > 100);
double average = orders.Average(o => o.Amount);
int count = orders.Count(); // executes another SQL query!

RIGHT — compute everything in one pass:

var orders = dbContext.Orders.Where(o => o.Amount > 100).ToList();
double average = orders.Average(o => o.Amount);
int count = orders.Count;

Or use a single database query:

var stats = dbContext.Orders
    .Where(o => o.Amount > 100)
    .GroupBy(o => 1)
    .Select(g => new { Average = g.Average(o => o.Amount), Count = g.Count() })
    .First();

4. Handle forward-only sequences

WRONG — enumerating a forward-only sequence twice:

IEnumerable<string> ReadLines()
{
    using var reader = new StreamReader("data.big");
    while (!reader.EndOfStream) yield return reader.ReadLine();
}

RIGHT — materialize or restructure:

List<string> ReadAllLines()
{
    return File.ReadAllLines("data.big").ToList();
}

Expected output with wrong approach: the second foreach loop yields nothing. With the fix: both loops iterate over all lines.

Prevention

  • Materialize IEnumerable with .ToList() before multiple iterations.
  • Name deferred collections clearly: IEnumerable<T> for single-use, IReadOnlyCollection<T> or IReadOnlyList<T> for reusable.
  • Use IReadOnlyCollection<T> as return type when the caller needs Count/iterations.
  • Run Roslyn analyzer for CA1851: "Possible multiple enumeration of IEnumerable."
  • For database queries, use .ToList() to close the connection and cache results.

Common Mistakes with ienumerable error

  1. Using foldl instead of foldl' causing stack overflow on large lists
  2. Forgetting deriving (Show, Eq) on custom data types needed for debugging
  3. Placing the wildcard pattern first in case expressions, making all subsequent patterns unreachable

These mistakes appear frequently in real-world CSHARP code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### Does foreach also consume the IEnumerable?

Yes. A foreach loop calls GetEnumerator() and MoveNext() until the sequence is exhausted. After the loop completes, the enumerator is disposed and the sequence cannot be replayed unless the underlying source supports it (like a List).

How is IReadOnlyCollection different from IEnumerable?

IReadOnlyCollection extends IEnumerable and adds a Count property. It signals to callers that the collection is already materialized and can be safely iterated multiple times. IEnumerable signals deferred execution.

When should I return IEnumerable vs List?

Return IEnumerable when you want deferred execution (reading large files, database queries). Return List or IReadOnlyCollection when you've already computed the data in memory. The return type communicates your intent to callers.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro