Question
In C#, I saw this method used in an answer about exposing only part of an IList<>:
IEnumerable<object> FilteredList()
{
foreach (object item in FullList)
{
if (IsItemInPartialList(item))
yield return item;
}
}
What does the yield keyword do in this method?
I have seen yield mentioned in a few places, but I do not yet understand what it actually does. I am familiar with the word yield in the sense of one thread yielding to another, but that does not seem to apply here. How does yield return work in C#?
Short Answer
By the end of this page, you will understand that yield in C# is used to create iterators—methods that produce values one at a time instead of building and returning a whole collection immediately. You will learn how yield return works, why it enables lazy evaluation, how enumeration resumes from where it left off, and when to use it in real code.
Concept
In C#, the yield keyword is used inside a method, property, or operator to return elements of a sequence one at a time.
The most common form is:
yield return value;
When a method contains yield return, it becomes an iterator method. Instead of running all the code at once and returning a fully built collection, C# transforms the method into something that can be paused and resumed during enumeration.
That means:
- The method returns an
IEnumerable<T>orIEnumerator<T>sequence. - Each
yield returnproduces one item. - Execution pauses after returning that item.
- The next time the caller asks for another item, execution continues from the same place.
For example, this method:
IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
behaves like a sequence that produces 1, then 2, then 3 as it is iterated.
This matters because it lets you:
- avoid creating temporary lists
- work efficiently with large data sets
- filter data on demand
- express custom iteration logic clearly
In your example:
IEnumerable<object> FilteredList()
{
foreach (object item in FullList)
{
if (IsItemInPartialList(item))
yield return item;
}
}
FilteredList() does not build a new list immediately. Instead, it creates a sequence that checks each item in FullList as the caller iterates, and only yields the matching ones.
There is also:
yield break;
which stops the sequence early.
Mental Model
Think of yield return like a librarian handing you books one at a time from a shelf.
- A normal
returnis like putting all requested books into a box and handing you the whole box at once. - A
yield returnis like giving you one book, waiting until you ask again, then continuing from the exact place on the shelf where the librarian stopped.
So the method does not finish all at once. It pauses, remembers its position, and continues later.
That is the key idea behind iterators in C#:
return= give back one final result and end the methodyield return= give back one element of a sequence and pauseyield break= stop producing more elements
Syntax and Examples
The basic syntax looks like this:
IEnumerable<int> GetValues()
{
yield return 10;
yield return 20;
yield return 30;
}
You can use it with foreach:
foreach (int value in GetValues())
{
Console.WriteLine(value);
}
Output:
10
20
30
Example: filtering values
IEnumerable<int> GetEvenNumbers(List<int> numbers)
{
foreach (int number in numbers)
{
if (number % 2 == 0)
{
yield return number;
}
}
}
Usage:
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
foreach (int even in GetEvenNumbers(numbers))
{
Console.WriteLine(even);
}
Output:
2
4
6
Why this is useful
Without yield, you might write:
List<int> GetEvenNumbers(List<int> numbers)
{
List<int> result = new List<int>();
foreach (int number in numbers)
{
if (number % 2 == 0)
{
result.Add(number);
}
}
return result;
}
This works, but it builds the whole list before returning. The yield version produces values only when needed.
Example with yield break
IEnumerable<int> CountUpTo(int max)
{
for (int i = 1; i <= max; i++)
{
if (i > 5)
yield break;
yield return i;
}
}
This stops the sequence once i becomes greater than 5.
Step by Step Execution
Consider this method:
IEnumerable<string> GetShortWords()
{
string[] words = { "cat", "elephant", "dog", "giraffe" };
foreach (string word in words)
{
if (word.Length <= 3)
{
yield return word;
}
}
}
And this code:
foreach (string word in GetShortWords())
{
Console.WriteLine(word);
}
Here is what happens step by step:
GetShortWords()is called.- The method does not immediately loop through all words and print anything.
- It returns an enumerable object that knows how to produce values later.
- The
foreachstarts iterating. - Execution enters the iterator method.
word = "cat"- length is
3 yield return word;returns"cat"- execution pauses here
- length is
foreachprintscatforeachasks for the next item- Execution resumes after the previous
yield return word = "elephant"- length is greater than
3 - nothing is yielded
- length is greater than
word = "dog"- length is
3 yield return word;returns"dog"- execution pauses again
- length is
foreachprintsdog- Iteration resumes again
word = "giraffe"- skipped
- End of method is reached
- Enumeration finishes
Output:
cat
dog
The important detail is that the method resumes from the point where it last yielded a value.
Real World Use Cases
yield is useful anywhere you want to produce a sequence gradually.
Filtering collections
Your example is a classic case: expose only items that match a rule.
IEnumerable<Order> GetOpenOrders(IEnumerable<Order> orders)
{
foreach (var order in orders)
{
if (!order.IsClosed)
yield return order;
}
}
Reading large files line by line
Instead of loading everything into memory at once, you can yield each line.
IEnumerable<string> ReadImportantLines(string path)
{
foreach (var line in File.ReadLines(path))
{
if (line.Contains("ERROR"))
yield return line;
}
}
Generating computed data
IEnumerable<int> Squares(int count)
{
for (int i = 1; i <= count; i++)
{
yield return i * i;
}
}
Tree or graph traversal
IEnumerable<Node> Traverse(Node root)
{
yield return root;
foreach (var child in root.Children)
{
foreach (var node in Traverse(child))
{
yield return node;
}
}
}
API and data processing pipelines
Developers use iterators to create sequences that can later be filtered, mapped, or consumed by LINQ.
Real Codebase Usage
In real projects, yield often appears in helper methods that expose data as IEnumerable<T> without forcing immediate allocation.
Common patterns
1. Guard clauses before iteration
IEnumerable<string> GetNames(List<User> users)
{
if (users == null)
yield break;
foreach (var user in users)
{
if (!string.IsNullOrWhiteSpace(user.Name))
yield return user.Name;
}
}
This avoids errors and cleanly returns an empty sequence when input is invalid.
2. Validation and filtering
IEnumerable<Product> GetAvailableProducts(IEnumerable<Product> products)
{
foreach (var product in products)
{
if (product.Stock > 0 && product.IsActive)
yield return product;
}
}
3. Wrapping existing collections with custom rules
Instead of exposing a raw list, teams often expose an enumerable with conditions, ordering, or transformation.
IEnumerable<string> GetVisibleMessages(IEnumerable<Message> messages)
{
foreach (var message in messages)
{
if (!message.IsDeleted)
yield return message.Text;
}
}
4. Replacing manual enumerator classes
Without yield, implementing IEnumerable<T> manually can be verbose. yield makes custom iteration much simpler.
5. Working with LINQ-style pipelines
Iterator methods fit naturally with LINQ because both work with IEnumerable<T>.
var result = GetAvailableProducts(products)
.Where(p => p.Price < 100)
.Select(p => p.Name);
Important practical note
Because yield is lazy, the underlying data is read when the sequence is enumerated, not when the method is called. That can be useful, but it also means results may change if the source collection changes before enumeration.
Common Mistakes
1. Expecting the method to run immediately
Beginners often think this code executes everything at call time:
var result = GetEvenNumbers(numbers);
Usually, the body does not fully run until you iterate:
foreach (var n in result)
{
Console.WriteLine(n);
}
2. Confusing yield return with normal return
Broken idea:
IEnumerable<int> GetNumbers()
{
return 1;
}
This is invalid because the method must return a sequence, not a single int.
Correct:
IEnumerable<int> GetNumbers()
{
yield return 1;
}
3. Assuming the result is a stored list
This may surprise you:
var sequence = GetEvenNumbers(numbers);
numbers.Add(8);
If the sequence has not been enumerated yet, 8 may appear during iteration because the method runs later.
If you need a fixed snapshot, materialize it:
var list = GetEvenNumbers(numbers).ToList();
4. Modifying the collection during enumeration
Broken example:
foreach (var item in numbers)
{
numbers.Add(99);
}
This can throw an exception. Be careful when the iterator reads from a collection that may change while being enumerated.
5. Forgetting that multiple enumeration reruns the iterator
var sequence = GetEvenNumbers(numbers);
foreach (var n in sequence) { }
foreach (var n in sequence) { }
The iterator logic runs again each time. If the work is expensive, store the results:
var cached = GetEvenNumbers(numbers).ToList();
6. Using yield when a simple list is clearer
yield is useful, but not always necessary. If you already need all results immediately and will reuse them often, returning a list may be simpler.
Comparisons
| Concept | What it does | When to use it |
|---|---|---|
return | Ends the method and returns one final result | When you already have the complete result |
yield return | Produces one item of a sequence and pauses | When returning items one at a time |
yield break | Stops an iterator early | When no more items should be produced |
yield vs building a list
| Approach | Behavior | Pros | Cons |
|---|---|---|---|
Build and return List<T> | Computes everything first | Simple, reusable, fixed snapshot | Uses more memory, does all work up front |
Use yield return | Computes items as needed | Lazy, memory-friendly, expressive | Can rerun logic, depends on source state at iteration time |
yield vs LINQ
| Approach | Best for |
|---|---|
yield return | Writing your own custom sequence logic |
LINQ (Where, Select) | Applying common filtering and projection operations |
Example:
IEnumerable<int> GetEvenNumbersManual(IEnumerable<int> numbers)
{
foreach (var n in numbers)
{
if (n % 2 == 0)
yield return n;
}
}
Similar LINQ version:
IEnumerable<int> GetEvenNumbersLinq(IEnumerable<int> numbers)
{
return numbers.Where(n => n % 2 == 0);
}
Both are lazy. yield is more flexible when the logic is more complex.
Cheat Sheet
IEnumerable<T> MethodName()
{
yield return item1;
yield return item2;
yield break;
}
Rules
yield returnreturns one element at a time.yield breakstops the sequence.- Iterator methods usually return
IEnumerable<T>orIEnumerator<T>. - Execution is lazy: it happens during enumeration.
- Each new enumeration starts the iterator again from the beginning.
Typical pattern
IEnumerable<int> Filter(IEnumerable<int> values)
{
foreach (var value in values)
{
if (value > 0)
yield return value;
}
}
Remember
returnends the method completely.yield returnpauses and resumes later.- Use
.ToList()if you need a concrete list right now. - Source collection changes can affect future enumeration.
Common use cases
- filtering
- generating sequences
- reading data lazily
- custom traversal logic
FAQ
What does yield return mean in C#?
It means the method is returning one item of a sequence and then pausing, so it can continue later when the next item is requested.
Does yield return create a list?
No. It creates a lazy sequence. Items are usually produced only when you iterate over them.
What is the difference between return and yield return?
return sends back one final result and ends the method. yield return sends back one sequence element and pauses the method.
When does a yield method actually execute?
Usually when the returned sequence is enumerated, such as in a foreach loop.
What does yield break do in C#?
It stops the iterator immediately, producing no more items.
Is yield return efficient?
Often yes, especially for large sequences, because it avoids building a whole collection up front.
Can I use yield for filtering data?
Yes. Filtering is one of the most common uses of yield return.
Why would results change between calls?
Because iterator methods are lazy. If the underlying data changes before enumeration, the produced items may also change.
Mini Project
Description
Build a small iterator method that filters a collection of names and returns only valid entries. This demonstrates how yield return can expose a clean, lazy sequence without creating a new list manually.
Goal
Create a method that lazily returns only non-empty names with at least three characters, then iterate over the results.
Requirements
- Create a C# method that returns
IEnumerable<string>. - Loop through a collection of names.
- Use
yield returnto return only valid names. - Skip
null, empty, or too-short names. - Print the filtered names with
foreach.
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# Access Modifiers and static: public, private, protected, and Defaults
Learn how C# public, private, protected, and default access control visibility, and how static differs from instance members.