Question
In C#, I find the yield keyword confusing and I am not fully confident about when it should be used.
Given the following two implementations, which approach is preferred, and why?
Version 1: Using yield return
public static IEnumerable<Product> GetAllProducts()
{
using (AdventureWorksEntities db = new AdventureWorksEntities())
{
var products = from product in db.Product
select product;
foreach (Product product in products)
{
yield return product;
}
}
}
Version 2: Returning a materialized list
public static IEnumerable<Product> GetAllProducts()
{
using (AdventureWorksEntities db = new AdventureWorksEntities())
{
var products = from product in db.Product
select product;
return products.ToList();
}
}
Short Answer
By the end of this page, you will understand what yield return does in C#, how it creates lazy sequences, how it differs from ToList(), and why this matters when working with IEnumerable<T> and database contexts such as Entity Framework. You will also learn when yield return is useful, when it is risky, and which approach is safer in database-backed methods.
Concept
yield return is a C# feature for creating an iterator method. Instead of building a full collection in memory and returning it all at once, the method returns items one at a time as the caller asks for them.
This is called deferred execution or lazy evaluation.
When a method uses yield return:
- C# transforms it into a state machine behind the scenes.
- The method does not run all at once.
- It pauses each time it reaches
yield return. - It resumes when the caller asks for the next item.
That behavior can be very useful when:
- the result set is large,
- values are expensive to compute,
- you may not need all values,
- you want to stream items instead of buffering everything first.
However, with databases there is an important detail: the data source often depends on an open connection or active context. In your example, the AdventureWorksEntities object is wrapped in a using block. If enumeration happens after the context is disposed, the sequence may fail.
That is why this topic matters in real programming: lazy sequences are powerful, but they must not outlive the resources they depend on.
In this case:
yield returnstreams the products one by one.ToList()executes the query immediately and stores the results in memory before the context is disposed.
So the main tradeoff is:
Mental Model
Think of yield return like serving plates of food one at a time from a kitchen.
- The caller says, "Give me the next plate."
- The method goes back into the kitchen, prepares the next plate, and hands it out.
- It does not prepare the whole banquet in advance.
By contrast, ToList() is like preparing every plate, putting them all on a large table, and then handing over the full set at once.
Now add one more detail: the kitchen closes when the using block ends.
- If you use
yield return, the kitchen must still be open while people keep asking for plates. - If the kitchen closes too soon, you cannot serve the remaining plates.
- If you use
ToList(), all plates are prepared before the kitchen closes, so callers can safely use them later.
That is the core idea behind your example.
Syntax and Examples
The basic syntax for yield return looks like this:
public static IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
When used:
foreach (var number in GetNumbers())
{
Console.WriteLine(number);
}
Output:
1
2
3
The numbers are produced one at a time.
Example with computed values
public static IEnumerable<int> GetEvenNumbers(int max)
{
for (int i = 0; i <= max; i++)
{
(i % == )
{
i;
}
}
}
Step by Step Execution
Consider this iterator:
public static IEnumerable<string> GetNames()
{
Console.WriteLine("Start");
yield return "Alice";
Console.WriteLine("Middle");
yield return "Bob";
Console.WriteLine("End");
}
And this code:
var names = GetNames();
Console.WriteLine("Iterator created");
foreach (var name in names)
{
Console.WriteLine(name);
}
What happens step by step
GetNames()is called.- The method does not run fully yet.
namesnow holds an iterator object.Console.WriteLine("Iterator created")runs first.- The
foreachloop asks for the first item. - The iterator starts executing.
- It prints
Start. - It reaches
yield return "Alice";and gives back .
Real World Use Cases
yield return is useful when you want to stream data or compute items on demand.
Common practical uses
- Reading large files line by line
- Avoid loading the whole file into memory.
- Generating sequences
- Dates, IDs, test data, number ranges.
- Filtering pipelines
- Return only matching items as they are found.
- Tree or graph traversal
- Yield each node during recursive traversal.
- Custom parsers
- Produce tokens one at a time.
Example: file streaming
public static IEnumerable<string> ReadImportantLines(string path)
{
foreach (var line in File.ReadLines(path))
{
if (line.Contains("ERROR"))
{
yield return line;
}
}
}
Where ToList() is better
- Returning query results from a method that disposes its data source
Real Codebase Usage
In real projects, developers choose between lazy and eager execution based on resource lifetime, performance, and API expectations.
Common patterns
1. Materialize before leaving a repository or service method
public List<Product> GetProducts()
{
using (var db = new AdventureWorksEntities())
{
return db.Product.Where(p => p.IsActive).ToList();
}
}
Why:
- avoids returning a query tied to a disposed context
- makes the method behavior predictable
2. Use yield return for in-memory transformations
public static IEnumerable<Product> FilterExpensiveProducts(IEnumerable<Product> products)
{
foreach (var product in products)
{
if (product.ListPrice > 1000)
{
yield return product;
}
}
}
Why:
- no database context involved
- clean and memory-efficient
3. Guard clauses before yielding
Common Mistakes
1. Using yield return with a disposed database context
Broken pattern:
public static IEnumerable<Product> GetAllProducts()
{
using (var db = new AdventureWorksEntities())
{
foreach (var product in db.Product)
{
yield return product;
}
}
}
Why it is risky:
- enumeration may happen after the method returns
- the context may already be disposed
- this can cause runtime errors
How to avoid it:
public static List<Product> GetAllProducts()
{
using (var db = new AdventureWorksEntities())
{
return db.Product.ToList();
}
}
2. Assuming yield return runs immediately
Broken assumption:
var data = GetNumbers();
// Expecting the iterator code to have already executed here
Comparisons
| Approach | Execution style | Memory usage | Safe after DB context is disposed? | Best for |
|---|---|---|---|---|
yield return | Deferred/lazy | Usually lower | Usually no, if it depends on the context | Streaming, in-memory iterators |
ToList() | Immediate/eager | Higher | Yes, because data is materialized first | Repository results, snapshots |
Return raw LINQ query (IQueryable<T> or deferred IEnumerable<T>) | Deferred | Lower at first | Risky if context is disposed | Query composition inside same context scope |
yield return vs
Cheat Sheet
yield return quick reference
public static IEnumerable<int> GetValues()
{
yield return 1;
yield return 2;
}
Key rules
yield returnreturns one item at a time.- Execution is deferred until enumeration begins.
- The method pauses at each
yield return. - The method resumes on the next iteration request.
- The return type is usually
IEnumerable<T>orIEnumerator<T>.
Use yield return when
- you want lazy evaluation
- you want to stream large results
- you are iterating over in-memory data
- you are writing custom iterators
Use ToList() when
- you need a snapshot now
- the source depends on a disposable resource
- you will iterate multiple times
- you need list operations like indexing or
Count
FAQ
What does yield return do in C#?
It creates a lazy iterator that returns items one at a time instead of building the full result immediately.
Is yield return faster than ToList()?
Not always. It can reduce memory use and avoid unnecessary work, but it may be slower if you enumerate multiple times or if the source depends on expensive operations.
Why can yield return be dangerous with Entity Framework?
Because the query may execute while iterating, and if the DbContext has already been disposed, enumeration can fail.
Should I always use ToList() in repository methods?
Not always, but it is often safer when the method creates and disposes the database context internally.
Can I return IEnumerable<T> even if I use ToList()?
Yes. The method signature can still be IEnumerable<T>, even though the data has already been materialized into a list.
When is yield return a good choice?
It is a good choice for custom iterators, file processing, sequence generation, and in-memory filtering where resource lifetime is not a problem.
Does yield return load everything into memory?
Mini Project
Description
Build a small product filtering utility that demonstrates the difference between lazy iteration with yield return and eager materialization with ToList(). The project uses an in-memory list of products so you can safely focus on iterator behavior without database lifetime issues.
Goal
Create methods that filter products lazily and eagerly, then compare how and when each approach executes.
Requirements
- Create a
Productclass withName,Price, andIsActiveproperties. - Create a sample list with at least five products.
- Write one method that uses
yield returnto return active products. - Write one method that uses
ToList()to return expensive products. - Print the results of both methods and observe the execution order.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Array Initialization Syntaxes Explained
Learn all common C# array initialization syntaxes with examples, rules, comparisons, and mistakes beginners often make.