迭代器模式
大约 11 分钟约 3237 字
迭代器模式
简介
迭代器(Iterator)提供一种方法顺序访问聚合对象中的元素,而不暴露其底层表示。C# 通过 IEnumerable/IEnumerator 将迭代器模式内置于语言中,yield return 更是简化了自定义迭代器的编写。
迭代器模式是 GoF 设计模式中应用最广泛的一个 —— 几乎所有的现代编程语言都内置了对迭代器的支持。在 C# 中,foreach 循环就是迭代器模式的语法糖,IEnumerable<T> 和 IEnumerator<T> 是标准的迭代器接口。
yield return 关键字是 C# 对迭代器模式的革命性简化。编译器会将包含 yield return 的方法自动转换为一个实现了 IEnumerable<T> 的状态机类,开发者无需手动实现 IEnumerator 的 MoveNext()、Current 和 Reset() 方法。
特点
结构分析
UML 类图
+--------------------+
| IEnumerable<T> |
+--------------------+
| +GetEnumerator() |
+--------------------+
|
v
+--------------------+
| IEnumerator<T> |
+--------------------+
| +Current: T |
| +MoveNext(): bool |
| +Reset() |
| +Dispose() |
+--------------------+
+--------------------+
| foreach (var x |
| in collection) | <-- 语法糖
+--------------------+yield return 状态机
编译器生成的状态机 (简化):
+--------+
| State |
+--------+
| -1: 初始 |
| 0: yield return item1 |
| 1: yield return item2 |
| 2: yield return item3 |
| -2: 结束 |
+--------+
MoveNext() 流程:
当前状态 -> 执行到下一个 yield -> 更新状态和 Current实现
yield return 自定义集合
// 自定义树形结构遍历
public class TreeNode<T>
{
public T Value { get; }
public List<TreeNode<T>> Children { get; } = new();
public TreeNode(T value) => Value = value;
public void Add(TreeNode<T> child) => Children.Add(child);
// 深度优先遍历
public IEnumerable<TreeNode<T>> DepthFirst()
{
yield return this;
foreach (var child in Children)
foreach (var desc in child.DepthFirst())
yield return desc;
}
// 广度优先遍历
public IEnumerable<TreeNode<T>> BreadthFirst()
{
var queue = new Queue<TreeNode<T>>();
queue.Enqueue(this);
while (queue.Count > 0)
{
var node = queue.Dequeue();
yield return node;
foreach (var child in node.Children) queue.Enqueue(child);
}
}
}
// 使用
var root = new TreeNode<string>("根");
var a = new TreeNode<string>("A");
var b = new TreeNode<string>("B");
a.Add(new TreeNode<string>("A1"));
a.Add(new TreeNode<string>("A2"));
b.Add(new TreeNode<string>("B1"));
root.Add(a); root.Add(b);
Console.WriteLine("深度优先:");
foreach (var node in root.DepthFirst()) Console.Write($"{node.Value} "); // 根 A A1 A2 B B1
Console.WriteLine("\n广度优先:");
foreach (var node in root.BreadthFirst()) Console.Write($"{node.Value} "); // 根 A B A1 A2 B1自定义迭代器类
// 二叉搜索树迭代器
public class BSTNode<T> where T : IComparable<T>
{
public T Value { get; }
public BSTNode<T>? Left { get; set; }
public BSTNode<T>? Right { get; set; }
public BSTNode(T value) => Value = value;
}
public class BSTInOrderIterator<T> : IEnumerator<T> where T : IComparable<T>
{
private readonly BSTNode<T>? _root;
private readonly Stack<BSTNode<T>> _stack = new();
private BSTNode<T>? _current;
public BSTInOrderIterator(BSTNode<T>? root)
{
_root = root;
PushLeftBranch(root);
}
public T Current => _current!.Value;
object IEnumerator.Current => Current!;
public bool MoveNext()
{
if (_stack.Count == 0) return false;
_current = _stack.Pop();
PushLeftBranch(_current.Right);
return true;
}
private void PushLeftBranch(BSTNode<T>? node)
{
while (node != null) { _stack.Push(node); node = node.Left; }
}
public void Reset() { _stack.Clear(); PushLeftBranch(_root); _current = null; }
public void Dispose() { }
}
// 使用
var bstRoot = new BSTNode<int>(5);
bstRoot.Left = new BSTNode<int>(3);
bstRoot.Right = new BSTNode<int>(8);
bstRoot.Left.Left = new BSTNode<int>(1);
bstRoot.Left.Right = new BSTNode<int>(4);
var iterator = new BSTInOrderIterator<int>(bstRoot);
while (iterator.MoveNext()) Console.Write($"{iterator.Current} "); // 1 3 4 5 8分页迭代器
public class PagedEnumerator<T> : IEnumerable<List<T>>
{
private readonly IEnumerable<T> _source;
private readonly int _pageSize;
public PagedEnumerator(IEnumerable<T> source, int pageSize)
{
_source = source; _pageSize = pageSize;
}
public IEnumerator<List<T>> GetEnumerator()
{
var page = new List<T>(_pageSize);
foreach (var item in _source)
{
page.Add(item);
if (page.Count >= _pageSize)
{
yield return page;
page = new List<T>(_pageSize);
}
}
if (page.Count > 0) yield return page;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
// 使用 — 流式分页处理大数据
var allRecords = Enumerable.Range(1, 1000);
foreach (var page in new PagedEnumerator<int>(allRecords, pageSize: 50))
{
Console.WriteLine($"处理第 {page.First()}-{page.Last()} 条记录");
// BatchWriteToDb(page);
}过滤与转换迭代器
// 链式迭代器 — 不依赖 LINQ 的自定义管道
public static class IteratorExtensions
{
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
foreach (var item in source)
if (predicate(item)) yield return item;
}
public static IEnumerable<TResult> Select<TSource, TResult>(
this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
foreach (var item in source) yield return selector(item);
}
public static IEnumerable<T> Take<T>(this IEnumerable<T> source, int count)
{
int i = 0;
foreach (var item in source)
{
if (i++ >= count) yield break;
yield return item;
}
}
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector)
{
var seen = new HashSet<TKey>();
foreach (var item in source)
if (seen.Add(keySelector(item))) yield return item;
}
}实战:交错合并多个有序序列
// 合并多个有序迭代器(类似归并排序的合并阶段)
public static IEnumerable<T> MergeSorted<T>(IEnumerable<IEnumerator<T>> enumerators)
where T : IComparable<T>
{
var heap = new PriorityQueue<IEnumerator<T>, T>();
foreach (var e in enumerators)
{
if (e.MoveNext()) heap.Enqueue(e, e.Current);
}
while (heap.Count > 0)
{
heap.TryDequeue(out var enumerator, out var value);
yield return value;
if (enumerator.MoveNext()) heap.Enqueue(enumerator, enumerator.Current);
}
}
// 使用
var list1 = new[] { 1, 4, 7, 10 }.AsEnumerable().GetEnumerator();
var list2 = new[] { 2, 3, 8, 11 }.AsEnumerable().GetEnumerator();
var list3 = new[] { 0, 5, 6, 9 }.AsEnumerable().GetEnumerator();
// 需要手动调用 MoveNext,此处简化演示
Console.WriteLine(string.Join(", ", MergeSorted(new[] { list1, list2, list3 })));实战:带超时的迭代器
public static IEnumerable<T> WithTimeout<T>(this IEnumerable<T> source, TimeSpan timeout)
{
var sw = Stopwatch.StartNew();
foreach (var item in source)
{
if (sw.Elapsed > timeout) yield break;
yield return item;
}
}
// 使用
var data = GetData().WithTimeout(TimeSpan.FromSeconds(5));
foreach (var item in data) Console.WriteLine(item);
static IEnumerable<int> GetData()
{
for (int i = 0; i < 1000000; i++)
{
Thread.Sleep(1); // 模拟慢数据源
yield return i;
}
}异步迭代器 — IAsyncEnumerable
基本异步迭代器
// C# 8+ 支持 IAsyncEnumerable<T> 和 await foreach
public static async IAsyncEnumerable<string> ReadLinesAsync(string filePath)
{
using var reader = new StreamReader(filePath);
while (await reader.ReadLineAsync() is { } line)
{
yield return line;
}
}
// 异步分页查询
public static async IAsyncEnumerable<T> PaginatedQueryAsync<T>(
IHttpClient client, string url, int pageSize = 100)
{
int page = 0;
bool hasMore = true;
while (hasMore)
{
var response = await client.GetFromJsonAsync<PagedResult<T>>(
$"{url}?page={page}&size={pageSize}");
foreach (var item in response.Items)
yield return item;
hasMore = response.HasMore;
page++;
}
}
// 使用
await foreach (var line in ReadLinesAsync("large_file.txt"))
{
Console.WriteLine(line);
}
await foreach (var order in PaginatedQueryAsync<Order>(client, "/api/orders"))
{
ProcessOrder(order);
}异步迭代器进阶用法
// 异步流式处理 — 逐条处理不阻塞
public static async IAsyncEnumerable<T> FilterAsync<T>(
IAsyncEnumerable<T> source,
Func<T, Task<bool>> predicate)
{
await foreach (var item in source)
{
if (await predicate(item))
yield return item;
}
}
public static async IAsyncEnumerable<TResult> SelectAsync<TSource, TResult>(
IAsyncEnumerable<TSource> source,
Func<TSource, Task<TResult>> selector)
{
await foreach (var item in source)
{
yield return await selector(item);
}
}
// 并行处理异步流
public static async Task<List<TResult>> ParallelProcessAsync<TSource, TResult>(
IAsyncEnumerable<TSource> source,
Func<TSource, Task<TResult>> processor,
int maxConcurrency = 10)
{
var results = new List<TResult>();
var tasks = new List<Task<TResult>>();
await foreach (var item in source)
{
tasks.Add(processor(item));
if (tasks.Count >= maxConcurrency)
{
results.AddRange(await Task.WhenAll(tasks));
tasks.Clear();
}
}
if (tasks.Count > 0)
results.AddRange(await Task.WhenAll(tasks));
return results;
}带取消的异步迭代器
// 异步迭代器支持取消令牌
public static async IAsyncEnumerable<T> WithCancellation<T>(
IAsyncEnumerable<T> source,
[EnumeratorCancellation] CancellationToken ct = default)
{
await foreach (var item in source.WithCancellation(ct))
{
ct.ThrowIfCancellationRequested();
yield return item;
}
}
// 使用 — 支持超时和取消
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{
await foreach (var item in GetDataAsync().WithCancellation(cts.Token))
{
Console.WriteLine(item);
}
}
catch (OperationCanceledException)
{
Console.WriteLine("数据加载已取消");
}
static async IAsyncEnumerable<int> GetDataAsync(
[EnumeratorCancellation] CancellationToken ct = default)
{
for (int i = 0; i < 1000000; i++)
{
ct.ThrowIfCancellationRequested();
await Task.Delay(10, ct);
yield return i;
}
}自定义集合实现
泛型自定义集合
// 实现完整的自定义集合 — 支持迭代器
public class ObservableList<T> : IEnumerable<T>
{
private readonly List<T> _items = new();
public event Action<T>? ItemAdded;
public event Action<T>? ItemRemoved;
public event Action? Cleared;
public void Add(T item)
{
_items.Add(item);
ItemAdded?.Invoke(item);
}
public bool Remove(T item)
{
if (_items.Remove(item))
{
ItemRemoved?.Invoke(item);
return true;
}
return false;
}
public void Clear()
{
_items.Clear();
Cleared?.Invoke();
}
public int Count => _items.Count;
// 实现 IEnumerable<T> — 支持 foreach
public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
// 只读集合包装器
public class ReadOnlyCollection<T> : IEnumerable<T>
{
private readonly IEnumerable<T> _source;
public ReadOnlyCollection(IEnumerable<T> source) => _source = source;
public IEnumerator<T> GetEnumerator() => _source.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}矩阵迭代器
// 矩阵(二维数组)的多种遍历方式
public class Matrix<T>
{
private readonly T[,] _data;
public Matrix(int rows, int cols) => _data = new T[rows, cols];
public T this[int row, int col]
{
get => _data[row, col];
set => _data[row, col] = value;
}
public int Rows => _data.GetLength(0);
public int Cols => _data.GetLength(1);
// 行优先遍历
public IEnumerable<T> RowMajor()
{
for (int r = 0; r < Rows; r++)
for (int c = 0; c < Cols; c++)
yield return _data[r, c];
}
// 列优先遍历
public IEnumerable<T> ColumnMajor()
{
for (int c = 0; c < Cols; c++)
for (int r = 0; r < Rows; r++)
yield return _data[r, c];
}
// 螺旋遍历
public IEnumerable<T> Spiral()
{
int top = 0, bottom = Rows - 1, left = 0, right = Cols - 1;
while (top <= bottom && left <= right)
{
// 从左到右
for (int c = left; c <= right; c++) yield return _data[top, c];
top++;
// 从上到下
for (int r = top; r <= bottom; r++) yield return _data[r, right];
right--;
if (top <= bottom)
{
// 从右到左
for (int c = right; c >= left; c--) yield return _data[bottom, c];
bottom--;
}
if (left <= right)
{
// 从下到上
for (int r = bottom; r >= top; r--) yield return _data[r, left];
left++;
}
}
}
// 对角线遍历
public IEnumerable<T> Diagonal()
{
for (int d = 0; d < Rows + Cols - 1; d++)
{
int r = d < Cols ? 0 : d - Cols + 1;
int c = d < Cols ? d : Cols - 1;
while (r < Rows && c >= 0)
{
yield return _data[r, c];
r++; c--;
}
}
}
}
// 使用
var matrix = new Matrix<int>(3, 3);
int val = 1;
for (int r = 0; r < 3; r++)
for (int c = 0; c < 3; c++)
matrix[r, c] = val++;
Console.WriteLine("行优先: " + string.Join(", ", matrix.RowMajor()));
Console.WriteLine("螺旋: " + string.Join(", ", matrix.Spiral()));
Console.WriteLine("对角线: " + string.Join(", ", matrix.Diagonal()));yield return 的注意事项
延迟执行 (Deferred Execution):
var query = GetData().Where(x => x > 0); // 此时不执行任何代码
foreach (var x in query) { ... } // 此时才开始执行
多次枚举问题:
var data = GetData(); // 每次枚举都会重新执行
data.Count(); // 第一次执行
data.First(); // 第二次执行
// 解决: 使用 .ToList() 或 .ToArray() 缓存
闭包陷阱:
var actions = new List<Action>();
for (int i = 0; i < 5; i++)
actions.Add(() => Console.Write(i + " ")); // 捕获的是变量 i,不是值
// 输出: 5 5 5 5 5(不是 0 1 2 3 4)最佳实践
- 优先使用 yield return:相比手动实现 IEnumerator,yield return 更简洁、更不容易出错。
- 注意延迟执行:迭代器中的代码在枚举时才执行,副作用(如日志、计数)可能不符合预期。
- 缓存结果避免重复枚举:如果需要多次遍历,使用
ToList()或ToArray()缓存。 - 实现 IDisposable:如果迭代器持有非托管资源,在 Dispose 中释放。
- 使用 IAsyncEnumerable 处理异步数据:C# 8+ 支持
await foreach和IAsyncEnumerable<T>。
优点
缺点
总结
C# 通过 IEnumerable<T>/IEnumerator<T> 内置迭代器模式。yield return 简化自定义迭代器编写,自动生成状态机。自定义集合应实现 IEnumerable<T> 以支持 foreach 和 LINQ。分页迭代器实现流式处理大数据。建议优先使用 yield return 而非手动实现 IEnumerator。
迭代器模式的本质价值在于:它提供了一种统一的方式来遍历任何集合,无论集合的内部结构如何(数组、链表、树、图),客户端都可以使用相同的 foreach 语法。这种封装不仅简化了客户端代码,也使得集合的实现可以自由更换。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来"高级"而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《迭代器模式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕"为了模式而模式",尤其是在简单业务里。
复盘问题
- 如果把《迭代器模式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《迭代器模式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《迭代器模式》最大的收益和代价分别是什么?
