一、迭代器模式概述
迭代器模式是一种行为设计模式,它提供了一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。在C#中,迭代器模式通过IEnumerable
和IEnumerator
接口得到了原生支持,使得这种模式在.NET生态中应用极为广泛。
1.1 核心价值
迭代器模式的核心价值在于:
- 职责分离:将集合对象的存储职责和遍历职责分离
- 统一接口:为不同的集合结构提供统一的遍历接口
- 多态迭代:支持对同一集合实施多种遍历方式
- 并行遍历:允许同时进行多个独立的遍历过程
1.2 基本组成
在C#中,迭代器模式主要涉及以下组件:
- IEnumerable/IEnumerable<T>:可枚举接口,定义获取迭代器的方法
- IEnumerator/IEnumerator<T>:迭代器接口,定义遍历集合的基本操作
- yield关键字:C#语法糖,简化迭代器实现
二、C#迭代器实现机制
2.1 接口定义分析
// 非泛型版本
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
// 泛型版本
public interface IEnumerable<T> : IEnumerable
{
new IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<T> : IEnumerator, IDisposable
{
new T Current { get; }
}
2.2 yield关键字原理
当使用yield关键字实现迭代器时,C#编译器会执行以下转换:
- 生成一个实现了
IEnumerator<T>
的嵌套类(状态机) - 将方法体转换为状态机代码
- 将局部变量提升为状态机的字段
- 实现
MoveNext()
方法,根据状态跳转到相应的yield return语句
例如以下代码:
public IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
编译器会生成类似如下的状态机类:
private sealed class <GetNumbers>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator
{
private int <>1__state;
private int <>2__current;
int IEnumerator<int>.Current => <>2__current;
object IEnumerator.Current => <>2__current;
bool MoveNext()
{
switch (<>1__state)
{
case 0:
<>1__state = -1;
<>2__current = 1;
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
<>2__current = 2;
<>1__state = 2;
return true;
case 2:
<>1__state = -1;
<>2__current = 3;
<>1__state = 3;
return true;
case 3:
<>1__state = -1;
return false;
}
return false;
}
}
2.3 手动实现与yield实现的对比
特性 | 手动实现 | yield实现 |
---|---|---|
代码量 | 多 | 少 |
复杂度 | 高 | 低 |
性能 | 稍好 | 稍差 |
可读性 | 差 | 好 |
维护性 | 差 | 好 |
适用场景 | 需要特殊控制时 | 大多数情况 |
三、高级应用场景
3.1 树形结构遍历
public class TreeNode<T>
{
public T Value { get; }
public List<TreeNode<T>> Children { get; } = new();
public TreeNode(T value) => Value = value;
// 深度优先遍历
public IEnumerable<TreeNode<T>> DepthFirst()
{
yield return this;
foreach (var child in Children)
{
foreach (var descendant in child.DepthFirst())
{
yield return descendant;
}
}
}
// 广度优先遍历
public IEnumerable<TreeNode<T>> BreadthFirst()
{
var queue = new Queue<TreeNode<T>>();
queue.Enqueue(this);
while (queue.Count > 0)
{
var current = queue.Dequeue();
yield return current;
foreach (var child in current.Children)
{
queue.Enqueue(child);
}
}
}
}
3.2 线程安全迭代器
public class ConcurrentList<T> : IEnumerable<T>
{
private readonly List<T> _items = new();
private readonly ReaderWriterLockSlim _lock = new();
public void Add(T item)
{
_lock.EnterWriteLock();
try { _items.Add(item); }
finally { _lock.ExitWriteLock(); }
}
public IEnumerator<T> GetEnumerator()
{
_lock.EnterReadLock();
try
{
// 创建副本以保证线程安全
foreach (var item in _items.ToArray())
{
yield return item;
}
}
finally
{
_lock.ExitReadLock();
}
}
}
3.3 大数据分页迭代
public IEnumerable<DataBatch> QueryLargeData(string connectionString, int batchSize)
{
using var connection = new SqlConnection(connectionString);
var command = new SqlCommand("SELECT * FROM LargeTable", connection);
connection.Open();
using var reader = command.ExecuteReader();
var buffer = new object[reader.FieldCount];
var batch = new List<object[]>(batchSize);
while (reader.Read())
{
reader.GetValues(buffer);
batch.Add(buffer.Clone() as object[]);
if (batch.Count >= batchSize)
{
yield return new DataBatch(batch);
batch.Clear();
}
}
if (batch.Count > 0)
yield return new DataBatch(batch);
}
四、性能优化策略
4.1 迭代器性能特点
- 延迟执行:迭代器代码在第一次MoveNext()调用时才开始执行
- 状态保持:每次yield return都会保存当前执行状态
- 内存效率:不需要一次性加载所有数据
4.2 优化建议
- 避免装箱:始终使用泛型版本
IEnumerable<T>
和IEnumerator<T>
- 短路操作:与LINQ的
Take()
、First()
等方法配合使用 - 结构体迭代器:对于性能关键路径,可使用ref struct实现迭代器
- 避免嵌套过深:深度嵌套的yield return会影响性能
4.3 结构体迭代器示例
public ref struct StructEnumerator<T>
{
private readonly T[] _array;
private int _index;
public StructEnumerator(T[] array)
{
_array = array;
_index = -1;
}
public T Current => _array[_index];
public bool MoveNext() => ++_index < _array.Length;
}
五、常见问题与解决方案
5.1 迭代过程中修改集合
问题:在foreach循环中修改集合会抛出InvalidOperationException
解决方案:
- 迭代前创建集合副本
- 使用线程安全集合
- 延迟修改直到迭代完成
5.2 多次迭代IQueryable
问题:每次迭代都会执行数据库查询
解决方案:
// 错误方式:执行两次查询
var query = dbContext.Products.Where(p => p.Price > 100);
var count = query.Count(); // 第一次执行
var list = query.ToList(); // 第二次执行
// 正确方式:只执行一次
var list = dbContext.Products.Where(p => p.Price > 100).ToList();
var count = list.Count;
5.3 递归迭代器性能
问题:深度递归的yield return会导致性能下降
扫描二维码关注公众号,回复:
17553646 查看本文章

解决方案:使用显式栈结构替代递归
// 优化后的深度优先遍历
public IEnumerable<TreeNode<T>> DepthFirstOptimized()
{
var stack = new Stack<TreeNode<T>>();
stack.Push(this);
while (stack.Count > 0)
{
var current = stack.Pop();
yield return current;
// 反向压栈以保证顺序正确
for (int i = current.Children.Count - 1; i >= 0; i--)
{
stack.Push(current.Children[i]);
}
}
}
六、设计模式结合应用
6.1 与策略模式结合
public interface IIterationStrategy<T>
{
IEnumerable<T> Iterate(IEnumerable<T> collection);
}
public class ForwardStrategy<T> : IIterationStrategy<T>
{
public IEnumerable<T> Iterate(IEnumerable<T> collection) => collection;
}
public class ReverseStrategy<T> : IIterationStrategy<T>
{
public IEnumerable<T> Iterate(IEnumerable<T> collection) => collection.Reverse();
}
public class CustomCollection<T>
{
private readonly List<T> _items = new();
private IIterationStrategy<T> _strategy;
public void SetStrategy(IIterationStrategy<T> strategy) => _strategy = strategy;
public IEnumerable<T> GetItems() => _strategy.Iterate(_items);
}
6.2 与工厂模式结合
public static class IteratorFactory
{
public static IEnumerable<T> CreateIterator<T>(IEnumerable<T> source, IteratorType type)
{
return type switch
{
IteratorType.Normal => source,
IteratorType.Reverse => source.Reverse(),
IteratorType.Shuffled => source.OrderBy(x => Guid.NewGuid()),
_ => throw new ArgumentOutOfRangeException()
};
}
}
public enum IteratorType { Normal, Reverse, Shuffled }
七、最佳实践总结
- 优先使用yield:在大多数情况下,yield是最简洁的实现方式
- 考虑线程安全:多线程环境下要确保迭代的安全性
- 注意资源释放:实现
IDisposable
正确释放资源 - 避免副作用:迭代器方法应该是幂等的
- 合理使用LINQ:LINQ本身就是基于迭代器模式的强大实现
- 性能关键路径优化:对于性能敏感场景考虑手动实现或结构体迭代器
通过深入理解和合理应用迭代器模式,可以大大提升C#程序的灵活性、可维护性和性能表现。这种模式是.NET集合体系和LINQ查询的基础,掌握它将使你能够编写出更优雅高效的C#代码。