C# 迭代器模式(Iterator Pattern)全面解析:从原理到高级应用

一、迭代器模式概述

迭代器模式是一种行为设计模式,它提供了一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。在C#中,迭代器模式通过IEnumerableIEnumerator接口得到了原生支持,使得这种模式在.NET生态中应用极为广泛。

1.1 核心价值

迭代器模式的核心价值在于:

  1. 职责分离:将集合对象的存储职责和遍历职责分离
  2. 统一接口:为不同的集合结构提供统一的遍历接口
  3. 多态迭代:支持对同一集合实施多种遍历方式
  4. 并行遍历:允许同时进行多个独立的遍历过程

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#编译器会执行以下转换:

  1. 生成一个实现了IEnumerator<T>的嵌套类(状态机)
  2. 将方法体转换为状态机代码
  3. 将局部变量提升为状态机的字段
  4. 实现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 迭代器性能特点

  1. 延迟执行:迭代器代码在第一次MoveNext()调用时才开始执行
  2. 状态保持:每次yield return都会保存当前执行状态
  3. 内存效率:不需要一次性加载所有数据

4.2 优化建议

  1. 避免装箱:始终使用泛型版本IEnumerable<T>IEnumerator<T>
  2. 短路操作:与LINQ的Take()First()等方法配合使用
  3. 结构体迭代器:对于性能关键路径,可使用ref struct实现迭代器
  4. 避免嵌套过深:深度嵌套的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

解决方案

  1. 迭代前创建集合副本
  2. 使用线程安全集合
  3. 延迟修改直到迭代完成

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 }

七、最佳实践总结

  1. 优先使用yield:在大多数情况下,yield是最简洁的实现方式
  2. 考虑线程安全:多线程环境下要确保迭代的安全性
  3. 注意资源释放:实现IDisposable正确释放资源
  4. 避免副作用:迭代器方法应该是幂等的
  5. 合理使用LINQ:LINQ本身就是基于迭代器模式的强大实现
  6. 性能关键路径优化:对于性能敏感场景考虑手动实现或结构体迭代器

通过深入理解和合理应用迭代器模式,可以大大提升C#程序的灵活性、可维护性和性能表现。这种模式是.NET集合体系和LINQ查询的基础,掌握它将使你能够编写出更优雅高效的C#代码。

猜你喜欢

转载自blog.csdn.net/weixin_48916144/article/details/146915265