Mastering Design Patterns in C# and ASP.NET Core
Part 4.4 – Iterator Design Pattern
Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Behavioral Design Pattern
Difficulty: ⭐⭐⭐☆☆ Intermediate
Prerequisites: C#, OOP, Interfaces, Collections, Generics, LINQ, IEnumerable<T>, IEnumerator<T>
Table of Contents
Introduction
What is the Iterator Design Pattern?
Why Do We Need the Iterator Pattern?
Iterator and Aggregate Concepts
Iterator Pattern Structure
UML Class Diagram
Components of the Iterator Pattern
IEnumerable<T>andIEnumerator<T>How
foreachWorks InternallyComplete C# Console Application
Custom Iterator Implementation
Understanding
yield returnIterator vs
yield returnASP.NET Core Implementation
Database and Repository Example
Lazy Iteration
Streaming Large Datasets
Pagination
Real-World Enterprise Examples
Advantages
Disadvantages
Best Practices
Common Mistakes
Iterator vs
IEnumerable<T>Iterator vs
yield returnIterator vs
foreachInterview Questions
Summary
Coming Up Next
1. Introduction
In almost every C# application, we work with collections:
List<Customer>
List<Order>
List<Product>
Dictionary<int, Employee>
A common requirement is to traverse these objects one by one.
For example:
foreach (var customer in customers)
{
Console.WriteLine(customer.Name);
}
The interesting question is:
What actually happens internally when we use
foreach?
The C# language and .NET collection infrastructure use the concepts of:
IEnumerable<T>IEnumerator<T>GetEnumerator()MoveNext()Currentyield return
These concepts are closely related to the Iterator Design Pattern.
The Iterator Pattern provides a standardized way to traverse a collection without exposing its internal representation.
2. What is the Iterator Design Pattern?
Definition
The Iterator Design Pattern is a behavioral design pattern that provides a way to sequentially access elements of a collection without exposing the collection's underlying representation.
In simple terms:
Iterator separates the process of traversing a collection from the collection itself.
For example:
Collection
↓
Iterator
↓
Item 1
↓
Item 2
↓
Item 3
↓
Item 4
The client does not need to know whether the data is stored in:
Array
List
Linked List
Tree
Database result
File
Network stream
It only needs to know how to iterate through the data.
3. Why Do We Need the Iterator Pattern?
Consider a collection:
List<Product> products;
We could expose the internal collection directly.
But this creates unnecessary coupling.
The client might become dependent on:
List
Array
Dictionary
LinkedList
Instead, we can expose an abstraction:
IEnumerable<Product>
The client simply says:
foreach (var product in products)
{
// Process product
}
The client doesn't care how the collection is implemented.
This provides:
Encapsulation
Flexibility
Reusability
Separation of responsibilities
Consistent traversal
4. Iterator and Aggregate Concepts
Two important terms are:
Aggregate
An Aggregate is the collection or object containing multiple elements.
For example:
CustomerCollection
OrderCollection
ProductCollection
EmployeeCollection
Iterator
The Iterator is responsible for moving through the collection.
For example:
Iterator
|
+-- MoveNext()
|
+-- Current
|
+-- Reset()
Conceptually:
+-------------------+
| Aggregate |
+-------------------+
| CreateIterator() |
+---------+---------+
|
↓
+-------------------+
| Iterator |
+-------------------+
| Current |
| MoveNext() |
| Reset() |
+-------------------+
5. Iterator Pattern Structure
The basic structure looks like:
+----------------+
| Client |
+-------+--------+
|
↓
+---------------+
| Iterator |
+---------------+
| MoveNext() |
| Current |
+-------+-------+
|
↓
+---------------+
| Aggregate |
+---------------+
| Items |
| CreateIterator|
+---------------+
The client uses the iterator instead of directly depending on the internal data structure.
6. UML Class Diagram
A traditional Iterator Pattern UML diagram looks like this:
+----------------------+
| Client |
+----------+-----------+
|
↓
+----------------------+
| <<interface>> |
| Iterator<T> |
+----------------------+
| + Current : T |
| + MoveNext() : bool |
| + Reset() : void |
+----------^-----------+
|
|
+----------------------+
| ConcreteIterator<T> |
+----------------------+
| - currentIndex |
| - collection |
+----------------------+
| + Current |
| + MoveNext() |
| + Reset() |
+----------------------+
+----------------------+
| <<interface>> |
| Aggregate<T> |
+----------------------+
| + CreateIterator() |
+----------^-----------+
|
|
+----------------------+
| ConcreteAggregate<T> |
+----------------------+
| - items |
+----------------------+
| + CreateIterator() |
+----------------------+
7. Components of the Iterator Pattern
1. Iterator
Defines how to traverse elements.
Typical operations:
MoveNext()
Current
Reset()
2. Concrete Iterator
Implements the iterator behavior.
It maintains the current position.
For example:
Index = 0
Index = 1
Index = 2
3. Aggregate
Represents the collection.
It provides a mechanism for creating an iterator.
4. Concrete Aggregate
Contains the actual collection.
For example:
List<Product>
5. Client
Uses the iterator to traverse the collection.
8. IEnumerable<T> and IEnumerator<T>
This is one of the most important concepts for a .NET developer.
C# provides:
IEnumerable<T>
and:
IEnumerator<T>
They play different roles.
IEnumerable<T>
IEnumerable<T> represents something that can provide an enumerator.
Conceptually:
public interface IEnumerable<out T>
{
IEnumerator<T> GetEnumerator();
}
IEnumerator<T>
The enumerator is responsible for actually moving through the collection.
Conceptually:
public interface IEnumerator<out T>
{
T Current { get; }
bool MoveNext();
void Reset();
}
It also implements IDisposable.
9. How foreach Works Internally
Consider:
var numbers = new List<int>
{
10,
20,
30
};
foreach (var number in numbers)
{
Console.WriteLine(number);
}
It looks simple.
But conceptually, the compiler transforms the loop into something similar to:
var enumerator = numbers.GetEnumerator();
while (enumerator.MoveNext())
{
var number = enumerator.Current;
Console.WriteLine(number);
}
And in a more complete conceptual form:
var enumerator = numbers.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
int number = enumerator.Current;
Console.WriteLine(number);
}
}
finally
{
enumerator.Dispose();
}
So:
foreach
↓
GetEnumerator()
↓
MoveNext()
↓
Current
↓
MoveNext()
↓
Current
↓
...
This is the core of iteration in .NET.
10. What Does MoveNext() Do?
Suppose our collection contains:
10
20
30
Initially:
Position = Before First Item
Calling:
MoveNext()
moves to:
10
Then:
Current
returns:
10
Next:
MoveNext()
moves to:
20
Then:
Current
returns:
20
Eventually:
MoveNext()
returns:
false
The iteration ends.
11. Complete C# Console Application
Let's create a custom Iterator Pattern implementation.
Step 1 – Product
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
Step 2 – Iterator Interface
public interface IProductIterator
{
Product Current { get; }
bool MoveNext();
void Reset();
}
Step 3 – Concrete Iterator
public class ProductIterator : IProductIterator
{
private readonly List<Product> _products;
private int _currentIndex = -1;
public ProductIterator(List<Product> products)
{
_products = products;
}
public Product Current
{
get
{
if (_currentIndex < 0 ||
_currentIndex >= _products.Count)
{
throw new InvalidOperationException();
}
return _products[_currentIndex];
}
}
public bool MoveNext()
{
if (_currentIndex + 1 >= _products.Count)
{
return false;
}
_currentIndex++;
return true;
}
public void Reset()
{
_currentIndex = -1;
}
}
12. Product Collection
Now create our Aggregate.
public class ProductCollection
{
private readonly List<Product> _products = new();
public void Add(Product product)
{
_products.Add(product);
}
public IProductIterator CreateIterator()
{
return new ProductIterator(_products);
}
}
13. Client Code
var products = new ProductCollection();
products.Add(new Product
{
Id = 1,
Name = "Laptop",
Price = 1200
});
products.Add(new Product
{
Id = 2,
Name = "Monitor",
Price = 400
});
products.Add(new Product
{
Id = 3,
Name = "Keyboard",
Price = 100
});
var iterator = products.CreateIterator();
while (iterator.MoveNext())
{
var product = iterator.Current;
Console.WriteLine(
$"{product.Id} - {product.Name} - ${product.Price}");
}
Output:
1 - Laptop - $1200
2 - Monitor - $400
3 - Keyboard - $100
Notice that the client does not access the internal _products list.
It simply uses:
MoveNext()
and:
Current
14. Implementing the Iterator with IEnumerable<T>
Modern C# applications usually don't need to manually implement the traditional pattern.
We can use:
IEnumerable<T>
For example:
public class ProductCollection
{
private readonly List<Product> _products = new();
public void Add(Product product)
{
_products.Add(product);
}
public IEnumerable<Product> GetProducts()
{
return _products;
}
}
Now the client can write:
foreach (var product in collection.GetProducts())
{
Console.WriteLine(product.Name);
}
This is one reason the Iterator Pattern is so deeply integrated into .NET.
15. Understanding yield return
One of the most useful C# features related to iteration is:
yield return
Example:
public IEnumerable<int> GetNumbers()
{
yield return 10;
yield return 20;
yield return 30;
}
Usage:
foreach (var number in GetNumbers())
{
Console.WriteLine(number);
}
Output:
10
20
30
The important point is that the method does not need to create and return a complete list.
16. How yield return Works
Consider:
public IEnumerable<int> GetNumbers()
{
yield return 10;
yield return 20;
yield return 30;
}
Conceptually, the compiler generates a state machine that remembers where execution should resume.
The flow is approximately:
GetNumbers()
↓
yield return 10
↓
Pause
↓
MoveNext()
↓
yield return 20
↓
Pause
↓
MoveNext()
↓
yield return 30
This makes iterator methods useful for lazy evaluation.
17. Custom Iterator Using yield return
For example:
public IEnumerable<int> GetEvenNumbers(
int start,
int end)
{
for (int i = start; i <= end; i++)
{
if (i % 2 == 0)
{
yield return i;
}
}
}
Usage:
foreach (var number in GetEvenNumbers(1, 10))
{
Console.WriteLine(number);
}
Output:
2
4
6
8
10
18. Why Lazy Evaluation Matters
Consider:
var numbers = Enumerable.Range(1, 1_000_000);
If we only want the first five values:
var result = numbers.Take(5);
we don't need to process the entire sequence first.
This is one of the major benefits of deferred execution in LINQ.
The general idea is:
Large Data Source
↓
Iterator
↓
Request one item
↓
Process
↓
Request next item
Instead of:
Large Data Source
↓
Load Everything
↓
Store in Memory
↓
Process
19. ASP.NET Core Implementation
Let's create an ASP.NET Core Web API that exposes products.
Model
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
Repository Interface
public interface IProductRepository
{
IEnumerable<Product> GetProducts();
}
Repository Implementation
public class ProductRepository : IProductRepository
{
private readonly List<Product> _products =
new()
{
new Product
{
Id = 1,
Name = "Laptop",
Price = 1200
},
new Product
{
Id = 2,
Name = "Monitor",
Price = 400
},
new Product
{
Id = 3,
Name = "Keyboard",
Price = 100
}
};
public IEnumerable<Product> GetProducts()
{
foreach (var product in _products)
{
yield return product;
}
}
}
20. Register Dependency Injection
In Program.cs:
builder.Services.AddScoped<IProductRepository,
ProductRepository>();
21. Controller
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly IProductRepository _repository;
public ProductsController(
IProductRepository repository)
{
_repository = repository;
}
[HttpGet]
public IActionResult GetProducts()
{
var products = _repository.GetProducts();
return Ok(products);
}
}
The architecture becomes:
Angular / Client
↓
ProductsController
↓
IProductRepository
↓
ProductRepository
↓
IEnumerable<Product>
↓
Iterator
22. Database and Repository Example
In enterprise applications, repositories often retrieve data from databases.
For example:
public IEnumerable<Product> GetProducts()
{
foreach (var product in _dbContext.Products)
{
yield return product;
}
}
However, there is an important consideration here.
yield return does not automatically make database access efficient.
You need to understand:
Database query execution
EF Core query translation
Materialization
Connection lifetime
Tracking
Pagination
Streaming behavior
For Entity Framework Core applications, IQueryable<T> and async APIs such as IAsyncEnumerable<T> can be more appropriate depending on the scenario.
23. IEnumerable<T> vs IQueryable<T>
This is an important interview topic.
IEnumerable<T>
Generally works with data already available to the application process.
Example:
IEnumerable<Product> products =
productsList.Where(x => x.Price > 500);
The filtering occurs in application memory.
IQueryable<T>
Represents a query that can be translated by a provider.
Example:
IQueryable<Product> products =
dbContext.Products
.Where(x => x.Price > 500);
With Entity Framework Core, the provider can translate the expression into SQL.
Conceptually:
IQueryable
↓
Expression Tree
↓
EF Core Provider
↓
SQL
↓
Database
This distinction is extremely important when working with large datasets.
24. Lazy Iteration
Lazy iteration means:
Produce each item only when it is requested.
Example:
public IEnumerable<int> GetNumbers()
{
for (int i = 1; i <= 1000000; i++)
{
yield return i;
}
}
Calling:
var numbers = GetNumbers();
does not necessarily generate all one million values immediately.
Values are produced as the iterator is consumed.
For example:
foreach (var number in numbers.Take(5))
{
Console.WriteLine(number);
}
Only the required portion of the sequence is consumed.
25. Streaming Large Datasets
Suppose an application needs to process millions of records.
A naive approach might be:
var customers = dbContext.Customers.ToList();
This materializes the entire result set into memory.
For very large datasets, that may be expensive.
A more scalable design can use:
Pagination
Streaming
Async iteration
Database-side filtering
Projection
Batching
For asynchronous streaming, modern C# provides:
IAsyncEnumerable<T>
For example:
public async IAsyncEnumerable<Product> GetProductsAsync()
{
await foreach (var product in GetProductStreamAsync())
{
yield return product;
}
}
The exact implementation should be designed carefully around the database provider and connection lifetime.
26. IAsyncEnumerable<T>
For asynchronous data sources, .NET provides:
IAsyncEnumerable<T>
and:
IAsyncEnumerator<T>
Usage:
await foreach (var product in products)
{
Console.WriteLine(product.Name);
}
The conceptual flow becomes:
IAsyncEnumerable<T>
↓
GetAsyncEnumerator()
↓
MoveNextAsync()
↓
Current
↓
MoveNextAsync()
↓
Current
This is particularly useful when consuming asynchronous streams.
27. Pagination
Pagination is another practical application of iterator-style processing.
Suppose there are:
1,000,000 records
Instead of returning all records:
Page 1 → 1–100
Page 2 → 101–200
Page 3 → 201–300
An API could accept:
GET /api/products?pageNumber=2&pageSize=100
The server processes only the requested page.
A simple implementation:
[HttpGet]
public IActionResult GetProducts(
int pageNumber = 1,
int pageSize = 20)
{
var products = _repository
.GetProducts()
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
return Ok(products);
}
For very large database tables, keyset/seek pagination can often be more efficient than large Skip() offsets.
28. Real-World Enterprise Examples
1. E-Commerce
Iterate through:
Products
Orders
Cart Items
Customers
2. Banking
Process:
Transactions
Accounts
Statements
Payment Records
3. Healthcare
Iterate through:
Patient Records
Appointments
Medical Claims
Billing Transactions
4. Reporting
Process:
Millions of database records
without unnecessarily loading everything into memory.
5. File Processing
Read files sequentially:
Line 1
Line 2
Line 3
...
rather than loading the entire file into memory.
6. Log Processing
Process application logs one record at a time.
7. Message Processing
Iterate through messages from:
Message Queue
Event Stream
Service Bus
8. API Pagination
Iterate through large datasets in manageable pages.
29. Advantages
1. Encapsulation
The internal structure of the collection is hidden.
2. Separation of Responsibilities
The collection stores data.
The iterator handles traversal.
3. Consistent Traversal
Different collections can expose a common iteration interface.
4. Lazy Evaluation
Iterators can produce data on demand.
5. Memory Efficiency
Large sequences don't always need to be materialized completely.
6. Supports Multiple Traversals
Different iterators can represent different traversal strategies.
For example:
Forward Iterator
Reverse Iterator
Filtered Iterator
Sorted Iterator
7. Excellent .NET Integration
The pattern is deeply integrated into:
IEnumerable<T>
IEnumerator<T>
foreach
LINQ
yield return
IAsyncEnumerable<T>
30. Disadvantages
1. Additional Abstraction
For simple collections, introducing custom iterator classes may be unnecessary.
2. Debugging Can Be More Complex
Lazy execution can make it less obvious when code actually runs.
3. Deferred Execution Can Surprise Developers
For example:
var query = products.Where(p => p.Price > 500);
The filtering may not execute until the query is enumerated.
4. Multiple Enumeration
This can be inefficient:
var query = products.Where(p => p.Price > 500);
var count = query.Count();
var first = query.First();
Depending on the source, the sequence may be evaluated more than once.
5. Resource Lifetime Issues
An iterator that depends on a database connection, file, or stream must not outlive the resource it needs.
31. Best Practices
1. Prefer IEnumerable<T> for Read-Only Enumeration
Expose:
IEnumerable<Product>
instead of:
List<Product>
when callers only need to enumerate.
2. Use yield return for Simple Custom Iterators
Instead of manually implementing:
IEnumerator
MoveNext
Current
Reset
consider:
yield return
when appropriate.
3. Avoid Unnecessary Materialization
Be careful with:
ToList()
ToArray()
because they materialize the sequence.
4. Filter as Early as Possible
Instead of:
var products = db.Products.ToList();
var expensiveProducts =
products.Where(p => p.Price > 1000);
prefer database-side filtering:
var expensiveProducts =
db.Products.Where(p => p.Price > 1000);
This allows the provider to perform filtering closer to the data source.
5. Use Pagination for Large Datasets
Don't return millions of records through a normal API response.
6. Consider IAsyncEnumerable<T> for Asynchronous Streams
For asynchronous streaming scenarios:
await foreach
can be more appropriate than synchronous enumeration.
7. Be Careful with Multiple Enumeration
If a sequence is expensive to generate, materialize it once when appropriate:
var products = query.ToList();
Then reuse the materialized result.
32. Common Mistakes
Mistake 1 – Confusing IEnumerable with IEnumerator
Remember:
IEnumerable<T>
↓
Provides Enumerator
IEnumerator<T>
↓
Performs Traversal
Mistake 2 – Assuming yield return Loads Everything
It generally enables deferred, state-machine-based iteration rather than requiring the complete sequence to be built up front.
Mistake 3 – Calling ToList() Too Early
For example:
var result = db.Products
.ToList()
.Where(p => p.Price > 1000);
This can cause all rows to be loaded before filtering.
Better:
var result = db.Products
.Where(p => p.Price > 1000)
.ToList();
Mistake 4 – Returning Huge Collections from APIs
Use:
Filtering
Pagination
Projection
Streaming
where appropriate.
Mistake 5 – Ignoring Resource Lifetime
An iterator that lazily reads from a database or stream requires the underlying resource to remain available during enumeration.
33. Iterator vs IEnumerable<T>
These terms are related but aren't exactly the same thing.
| Iterator Pattern | IEnumerable<T> |
|---|---|
| Design pattern | .NET interface |
| General software concept | Framework abstraction |
| Defines traversal concept | Provides enumerator |
| Language/framework independent | C#/.NET |
| Can be manually implemented | Built into .NET |
The Iterator Pattern is the design concept.
IEnumerable<T> is one of the major .NET implementations/abstractions that supports it.
34. Iterator vs yield return
| Iterator Pattern | yield return |
|---|---|
| Design pattern | C# language feature |
| General concept | Compiler-supported implementation technique |
| Can be manually implemented | Simplifies iterator creation |
| Works beyond C# | Specific to C#/.NET |
yield return is not itself a design pattern.
It is a convenient language feature for creating iterator methods.
35. Iterator vs foreach
foreach is the syntax used by the client to consume an enumerable sequence.
For example:
foreach (var product in products)
{
Console.WriteLine(product.Name);
}
Conceptually:
foreach
↓
GetEnumerator()
↓
MoveNext()
↓
Current
So:
Iterator Pattern
↓
IEnumerable / IEnumerator
↓
foreach
36. Iterator vs Strategy
These are behavioral patterns but solve different problems.
| Iterator | Strategy |
|---|---|
| Traverses a collection | Selects an algorithm |
| Focuses on iteration | Focuses on behavior |
MoveNext() / Current | Different algorithm implementations |
IEnumerable<T> | Strategy interface |
Example Iterator:
Products
↓
Iterator
↓
Product 1
Product 2
Product 3
Example Strategy:
Payment
├── CreditCardStrategy
├── BankTransferStrategy
└── WalletStrategy
37. Interview Questions
Beginner Questions
1. What is the Iterator Design Pattern?
It provides a standard mechanism for traversing a collection without exposing its internal representation.
2. What type of pattern is Iterator?
It is a Behavioral Design Pattern.
3. What is IEnumerable<T>?
IEnumerable<T> represents a sequence that can provide an enumerator.
4. What is IEnumerator<T>?
It represents the mechanism used to traverse the sequence.
5. What does MoveNext() do?
It advances the enumerator to the next element and returns true if an element exists.
6. What does Current do?
It returns the element at the enumerator's current position.
7. How does foreach work internally?
Conceptually, it obtains an enumerator and repeatedly calls:
MoveNext()
and accesses:
Current
until MoveNext() returns false.
38. Intermediate Interview Questions
8. What is yield return?
It allows a method to produce an iterator sequence incrementally, with the compiler generating the necessary state-machine implementation.
9. What is lazy evaluation?
It means values are produced or computed when requested rather than necessarily being computed all at once.
10. What is deferred execution in LINQ?
The query is generally not executed when it is defined; execution happens when the sequence is enumerated or materialized.
11. Difference between IEnumerable<T> and IQueryable<T>?
IEnumerable<T> generally operates over in-process data.
IQueryable<T> represents a query that can be translated by a query provider, such as EF Core's database provider.
12. Why should we avoid unnecessary ToList()?
Because it materializes the entire sequence, which can increase memory usage and cause unnecessary work.
13. What is IAsyncEnumerable<T>?
It provides asynchronous iteration using:
await foreach
and asynchronous enumeration operations.
14. Is yield return the same as the Iterator Pattern?
No.
yield return is a C# language feature that makes implementing iterator behavior easier.
39. Advanced Interview Questions
15. How can Iterator improve memory efficiency?
It can enable lazy processing where items are generated and consumed one at a time instead of materializing the complete sequence.
16. Can Iterator be used with database queries?
Yes, but developers must understand the difference between:
IEnumerable
IQueryable
and how the underlying database provider executes the query.
17. What happens if an iterator depends on a disposed resource?
Enumeration may fail because the iterator still needs access to the underlying resource.
This is particularly important with:
Database connections
Streams
Files
Network resources
18. Why is Iterator useful for large datasets?
It allows applications to process data incrementally and can reduce memory pressure when the underlying source supports streaming or incremental access.
19. How does LINQ relate to the Iterator Pattern?
LINQ relies heavily on IEnumerable<T> and iterator-based sequence processing.
Many LINQ operators use deferred execution and produce sequences that are consumed through enumeration.
20. Is List<T> an Iterator?
No.
List<T> is a collection.
It provides an enumerator through:
GetEnumerator()
The enumerator performs the traversal.
40. Practical Enterprise Architecture
A typical enterprise application might look like:
Angular Application
|
↓
ASP.NET Core API
|
↓
Controller
|
↓
Service Layer
|
↓
Repository Layer
|
↓
EF Core
|
↓
Database
For large result sets:
Database
↓
Query
↓
IQueryable<T>
↓
Filtering / Projection
↓
Enumeration
↓
IEnumerable<T>
↓
API Response / Stream
For asynchronous streaming:
Database / Service
↓
IAsyncEnumerable<T>
↓
await foreach
↓
Process one item
↓
Next item
41. Complete Conceptual Flow
The entire Iterator Pattern can be remembered using this diagram:
COLLECTION
|
|
GetEnumerator()
|
↓
ENUMERATOR
|
+-------+-------+
| |
MoveNext() Current
| |
↓ ↓
Next Item Current Item
|
↓
Continue Until
MoveNext() = false
And in C#:
foreach (var item in collection)
{
Process(item);
}
Conceptually:
var enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Process(item);
}
42. Key Takeaways
The most important concepts from this article are:
1. Iterator is a Behavioral Design Pattern
It focuses on how objects are traversed.
2. IEnumerable<T> provides an enumeration abstraction
IEnumerable<T>
allows a client to obtain an enumerator.
3. IEnumerator<T> performs traversal
The main concepts are:
Current
MoveNext()
Reset()
4. foreach uses enumeration
Conceptually:
GetEnumerator()
→ MoveNext()
→ Current
→ MoveNext()
→ Current
5. yield return simplifies iterator implementation
It allows C# developers to write iterator methods without manually implementing all iterator state-management code.
6. Lazy iteration can reduce memory usage
Instead of:
Load Everything
↓
Process
we can often use:
Request Item
↓
Process
↓
Request Next Item
7. Large datasets require careful design
Use appropriate combinations of:
Filtering
Projection
Pagination
Streaming
IEnumerable<T>IQueryable<T>IAsyncEnumerable<T>
Conclusion
The Iterator Design Pattern is one of the most important behavioral patterns for C# developers because its concepts are deeply embedded into the .NET ecosystem.
When you write:
foreach (var customer in customers)
{
Console.WriteLine(customer.Name);
}
you are using a mechanism built around enumeration and iteration.
The key concepts are:
IEnumerable<T>
↓
GetEnumerator()
↓
IEnumerator<T>
↓
MoveNext()
↓
Current
C# makes iterator-based programming even easier through:
yield return
and modern .NET extends these ideas with:
IAsyncEnumerable<T>
This becomes especially important in enterprise applications where we process:
Large database result sets
Millions of records
Files
Logs
API results
Message streams
Paginated data
Asynchronous streams
The key lesson is:
The Iterator Pattern allows clients to traverse a collection without needing to know how that collection stores or manages its data.
For modern .NET developers, understanding the Iterator Pattern also means understanding how foreach, IEnumerable<T>, IEnumerator<T>, LINQ, yield return, lazy evaluation, and asynchronous iteration fit together.
🚀 Coming Up Next: Part 4.5 – Mediator Design Pattern
In the next article, we'll explore the Mediator Design Pattern, including:
What is the Mediator Pattern?
Why do we need it?
Mediator vs direct object communication
Colleague and Mediator concepts
UML Class Diagram
Complete C# Console Application
ASP.NET Core implementation
Dependency Injection
Request/Response communication
CQRS and Mediator
MediatR-style architecture
Command and Query handlers
Pipeline behaviors
Validation
Logging
Authorization
Transaction handling
Banking example
E-commerce example
Real-world enterprise scenarios
Advantages and disadvantages
Best practices
Common mistakes
Mediator vs Observer
Mediator vs Facade
Mediator vs Command
Interview questions
The Mediator Pattern is particularly important for modern .NET developers because it provides a foundation for understanding loosely coupled communication, CQRS, command/query handlers, pipeline behaviors, and clean application-layer architecture.
