← Back

Traditional List<T> vs yield return in C#

2026-01-10 14:55 · 👁 17675

#c#

Traditional List<T> vs yield return in C#

Sometimes the difference isn’t “style”… it’s memory, latency, and control.


✅ 1) Traditional List<T>: eager execution

You build the whole result up front, then return it.

public static List<int> GetEvenNumbers_List(IEnumerable<int> source)
{
    var result = new List<int>();

    foreach (var n in source)
        if (n % 2 == 0)
            result.Add(n);

    return result;
}

When it shines

  • You need Count, Indexing, multiple enumerations
  • You want to materialize once and reuse
  • You want to avoid re-running expensive logic

Trade-offs

  • Allocates memory for the entire list
  • You pay the cost before the caller can consume anything

✅ 2) yield return: lazy execution (iterator)

You return items as they are produced, one by one.

public static IEnumerable<int> GetEvenNumbers_Yield(IEnumerable<int> source)
{
    foreach (var n in source)
        if (n % 2 == 0)
            yield return n;
}

When it shines

  • Streaming / pipelines
  • Large sequences (or even infinite sequences)
  • You want the first items immediately (lower latency)

Trade-offs

  • Each enumeration re-runs the logic
  • Can surprise you if the source changes later
  • Exceptions happen during enumeration, not at method call time

The “gotchas” you should know

⚠️ Multiple enumeration re-executes

var seq = GetEvenNumbers_Yield(numbers);

seq.ToList(); // runs logic
seq.ToList(); // runs logic AGAIN

If you want lazy generation but reuse the results, materialize once:

var cached = GetEvenNumbers_Yield(numbers).ToList();

⚠️ Exceptions happen later

var seq = GetEvenNumbers_Yield(GetNumbersThatMayThrow());
// no exception yet...

foreach (var n in seq) { } // exception can happen here

That’s great for streaming… but can make debugging feel “delayed”.


⚠️ Resource lifetime matters

This is a classic bug: yield keeps execution alive across foreach.

public static IEnumerable<string> ReadLines(string path)
{
    using var reader = File.OpenText(path);

    while (!reader.EndOfStream)
        yield return reader.ReadLine()!;
}

This is fine because the using stays active while enumerating.
But if you return an iterator built on something disposed too early, you’ll get runtime surprises.


Quick rule of thumb

✅ Use List<T> when you need:

  • random access / Count / multiple passes
  • stability + predictable execution timing
  • caching results

✅ Use yield return when you need:

  • streaming, large data, pipelines
  • early results (low latency)
  • composability with LINQ

One-liner takeaway

List<T> = eager + materialized
yield return = lazy + streamed

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please reload the page.