目录

C#-枚举器与迭代器.md

一、从根本理解:为什么需要枚举器?

核心问题:如何统一遍历不同的数据结构?

// 数组、链表、树、数据库结果集……它们的内部结构完全不同
// 但我们希望用统一的方式去遍历它们,这就是枚举器模式(Iterator Pattern)要解决的问题

int[] array = { 1, 2, 3 };
List<int> list = new List<int> { 4, 5, 6 };
HashSet<int> set = new HashSet<int> { 7, 8, 9 };

// 统一的遍历方式 —— foreach
foreach (var item in array) Console.Write(item);
foreach (var item in list) Console.Write(item);
foreach (var item in set) Console.Write(item);

二、两大核心接口

2.1接口定义

// ====== 非泛型版本 (实际开发多用以下的泛型版本) ======
public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

public interface IEnumerator
{
    object Current { get; } 		// 获取当前元素
    bool MoveNext();				// 移动到下一个元素,返回是否成功
    void Reset();					// 重置到初始位置
}


// ======= 泛型版本 ======
public interface IEnumerable<out T> : IEnumerable
{
	new IEnumerator<T> GetEnumerator();    
}

public interface IEnumerator<out T> : IEnumerator, IDisposable
{
    new T Current { get; }
}

2.2 关系图解

┌─────────────────────────────────────────────────┐
│              IEnumerable<T>                      │
│  "我是一个可以被遍历的集合"                        │
│                                                  │
│  职责:提供一个枚举器                              │
│  方法:IEnumerator<T> GetEnumerator()             │
│                                                  │
│  类比:图书馆(可以给你一个导览员)                  │
└─────────────────┬───────────────────────────────┘
                  │ 创建
┌─────────────────────────────────────────────────┐
│              IEnumerator<T>                      │
│  "我是负责遍历的游标/指针"                         │
│                                                  │
│  职责:维护遍历状态,逐个返回元素                    │
│  属性:T Current        → 当前指向的元素            │
│  方法:bool MoveNext()  → 移到下一个               │
│  方法:void Reset()     → 重置                    │
│  方法:void Dispose()   → 释放资源                 │
│                                                  │
│  类比:导览员(记住当前位置,带你看下一个展品)        │
└─────────────────────────────────────────────────┘

三、手动实现枚举器(理解原理)

3.1 最基础实现

/// <summary>
/// 自定义集合:存储一组整数
/// </summary>
public class IntCollection : IEnumerable<int>
{
    private readonly int[] _data;
    
    public IntCollection(params int[] data)
    {
        _data = data ?? Array.Empty<int>();
    }
    
    // 返回一个枚举器
    public IEnumerator<int> GetEnumerator()
    {
        return new IntEnumerator(_data);
    }
    
    // 显示实现非泛型版本(为了兼容旧接口)
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    
    /// <summary>
    /// 枚举器:维护遍历状态的对象
    /// </summary>
    private class IntEnumerator : IEnumerator<int>
    {
        private readonly int[] _data;
        private int _index = -1;	// 初始位置在第一个元素"之前"
        
        public IntEnmerator(int[] data)
        {
            _data = data;
        }
        
        // 当前元素
        public int Current
        {
            get
            {
                if (_index < 0 || _index >= _data.Length)
                    throw new InvalidOperationException("枚举器位于无效位置");
                return _data[_index];
            }
        }
        
        // 非泛型版本
        object IEnumerator.Current => Current;
        
        // 移动到下一个元素
        public bool MoveNext()
        {
            _index++;
            return _index < _data.Length;
        }
        
        // 重置(通常不实现,很少用到)
        public void Reset() => _index = -1;
        
        // 释放资源
        public void Dispose(){}
    }
}

void Main()
{
    var collection = new InCollection(10, 20, 30, 40 ,50);
    foreach(var item in collection)
        Console.WriteLine(item);
}

3.2 foreach的编译器展开

foreach 就是对枚举器模式的语法糖

foreach (var item in collection)
    Console.WriteLine(item);

// 编译器实际生成的代码 (等价形式)
IEnumerator<int> enumerator = collection.GetEnumerator();
try
{
    while(enumerator.MoveNext())  // 尝试移动到下一个
    {
        int item = enumerator.Current(); // 获取当前值
        Console.WriteLine(item);
    }
}
finally
{
    enumerator?.Dispose();	// 确保资源释放
}

3.3 枚举器状态机图解

         初始状态
         index = -1
    ┌─ MoveNext() ─┐
    │  index = 0    │
    │  return true  │
    └───────┬───────┘
            │  Current → data[0]
    ┌─ MoveNext() ─┐
    │  index = 1    │
    │  return true  │
    └───────┬───────┘
            │  Current → data[1]
          ......
    ┌─ MoveNext() ─┐
    │  index = N    │
    │  return false │ ← 没有更多元素了
    └───────┬───────┘
        遍历结束
        Dispose()

四、迭代器(yield) – 编译器帮你写枚举器

4.1 yield 的诞生动机

// 手动写枚举器太繁琐了!每次都要:
// 1. 定义一个枚举器类
// 2. 维护 _index 状态
// 3. 实现 MoveNext / Current / Dispose
// 
// C# 2.0 引入了 yield 关键字,让编译器自动生成这些代码

4.2 基本用法

public class SmartCollection : IEnumerable<int>
{
    private readonly int[] _data;
    
    public SmartCollection(param int[] data) => _data = data;
    
    // 使用 yield return —— 编译器自动生成枚举器类
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i < _data.Length; i ++)
        {
            Console.WriteLine($"[即将yield第{i}个元素]");
            yield return _data[i];  // "产出" 一个值,然后暂停
            Console.WriteLine($"[yield第{i}个元素后恢复执行");
        }
        Console.WirteLine("[迭代器执行完毕]");
    }
    
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

void Main()
{
    var sc = new SmartCollection(100, 200, 300);
    foreach(var item in sc)
        Console.WriteLine($"收到:{item}");
}

/* 输出:
  [即将yield第0个元素]
收到: 100
  [yield第0个元素后恢复执行]
  [即将yield第1个元素]
收到: 200
  [yield第1个元素后恢复执行]
  [即将yield第2个元素]
收到: 300
  [yield第2个元素后恢复执行]
  [迭代器执行完毕]
*/

4.3 yield 的核心原理:延迟执行与状态机

// ======= yield 的两个关键特性 ======

// 1. 延迟执行(Lazy Evaluation) —— 不调用MoveNext就不执行
static IEnumerable<int> GenerateNumbers()
{
    Console.WriteLine("开始生成");		// 什么时候打印?
    yield return 1;
    Console.WriteLine("生成第二个");
    yield return 2;
    Console.WriteLine("生成第三个");
    yield return 3;
    Console.WriteLine("全部生成完毕");
}

// 调用方法时, 方法体根本不执行
IEnumerable<int> numbers = GenerateNumners();	//  没有任何输出	
Console.WriteLine("还没开始遍历呢");

// 开始遍历时才执行,而且是按需执行
foreach (var n in numbers)
{
    Console.WriteLine($"  消费: {n}");
}

/* 输出:
还没开始遍历呢
开始生成
  消费: 1
生成第二个
  消费: 2
生成第三个
  消费: 3
全部生成完毕
*/
// 2. yield break —— 提前终止迭代
static IEnumerable<int> GetNumbersUntilNegative(int[] source)
{
    foreach(var num in source)
    {
        if(num < 0)
            yield break;	// 立即终止迭代器,不再产出任何值
        
        yield return num;
    }
}

void Main()
{
    var data = new[] { 1, 3, 5, -1, 7, 9 };
    foreach(var n in GetNumbersUntilNegative(data))
    {
        Console.Write($"{n}"); // 1 3 5
    }
}

4.4 编译器对 yield 做了什么

// 示例代码
IEnumerable<int> GetNumners()
{
    yield return 1;
    yield return 2;
    yield return 3;	
}

// 编译器生成的代码(简化版, 实际更复杂)
private sealed class <GetNumbers>d_0 : IEnumerable<int>, IEnumerator<int>
{
    private int _state;		// 状态变量
    private int _current;	// 当前值
    
    public int Current => _current;
    
    
    public bool MoveNext()
    {
        switch(_state)
        {
            case 0;						// 初始状态
                _current = 1;
                _state = 1;				// 记住下次从哪里继续
                return true;
                
            case 1:						// 第一次yield return之后
                _current = 2;
                _state = 2;
                return true;
                
            case 2:						// 第二次yield return之后
                _current = 3;
                _state = 3;
                return true;
                
            case 3:						// 第三次yield return之后
                _state = -1;			// 结束 
                return false;
              
            default:
                return false;
        }
    }
    // …… 其它接口实现
}
┌──────────────────────────────────────────────────────────┐
│                    yield 状态机                           │
│                                                          │
│  State 0 ──MoveNext()──→ yield return 1 → State 1       │
│  State 1 ──MoveNext()──→ yield return 2 → State 2       │
│  State 2 ──MoveNext()──→ yield return 3 → State 3       │
│  State 3 ──MoveNext()──→ return false (结束)             │
│                                                          │
│  每次 MoveNext() 从上次暂停的位置恢复执行                   │
│  所有局部变量都被"提升"为状态机类的字段                      │
└──────────────────────────────────────────────────────────┘

五、实际应用场景

5.1 无限序列

/// <summary>
/// 生成无限的斐波那契数列 —— 只有yield才能优雅实现
/// </summary>
static IEnumerable<long> Fibonacci()
{
    long a = 0, b = 1;
    while (true)  // 无限循环!但因为是延迟执行,完全没问题
    {
        yield return a;
        (a, b) = (b, a + b);  // 元组解构赋值
    }
}

// 取前20个
foreach (var fib in Fibonacci().Take(20))
{
    Console.Write($"{fib} ");
}
// 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181

// 找到第一个大于1000的斐波那契数
long result = Fibonacci().First(f => f > 1000);
Console.WriteLine(result); // 1597

5.2 管道式数据处理

/// <summary>
/// 每个方法都是一个处理管道,数据按需流动
/// </summary>
static IEnumerable<string> ReadLines(string filePath)
{
    using var reader = new StreamReader(filePath);
    string? line;
    while ((line = reader.ReadLine()) != null)
    {
        yield return line;
    }
    // 退出时自动调用 Dispose → reader 被关闭
}

static IEnumerable<string> FilterNonEmpty(IEnumerable<string> lines)
{
    foreach (var line in lines)
    {
        if (!string.IsNullOrWhiteSpace(line))
            yield return line;
    }
}

static IEnumerable<string> ToUpperCase(IEnumerable<string> lines)
{
    foreach (var line in lines)
    {
        yield return line.ToUpper();
    }
}

// 组合使用 —— 数据流式处理,内存中同一时间只有一行
var processed = ToUpperCase(FilterNonEmpty(ReadLines("data.txt")));

foreach (var line in processed.Take(10))
{
    Console.WriteLine(line);
}
// 即使文件有100GB,内存占用也极小!

5.3 树的遍历

public class TreeNode<T>
{
    public T Value { get; set; }
    public List<TreeNode<T>> Children { get; set; } = new();

    public TreeNode(T value) => Value = value;

    /// <summary>
    /// 深度优先遍历 —— 递归 + yield
    /// </summary>
    public IEnumerable<T> DepthFirst()
    {
        yield return Value;  // 先返回自己

        foreach (var child in Children)
        {
            foreach (var descendant in child.DepthFirst())  // 递归
            {
                yield return descendant;
            }
        }
    }

    /// <summary>
    /// 广度优先遍历
    /// </summary>
    public IEnumerable<T> BreadthFirst()
    {
        var queue = new Queue<TreeNode<T>>();
        queue.Enqueue(this);

        while (queue.Count > 0)
        {
            var node = queue.Dequeue();
            yield return node.Value;

            foreach (var child in node.Children)
            {
                queue.Enqueue(child);
            }
        }
    }
}

// 使用
var root = new TreeNode<string>("CEO")
{
    Children =
    {
        new TreeNode<string>("CTO")
        {
            Children =
            {
                new TreeNode<string>("Dev Lead"),
                new TreeNode<string>("QA Lead")
            }
        },
        new TreeNode<string>("CFO")
        {
            Children =
            {
                new TreeNode<string>("Accountant")
            }
        }
    }
};

Console.WriteLine("深度优先:");
foreach (var name in root.DepthFirst())
    Console.Write($"{name} → ");
// CEO → CTO → Dev Lead → QA Lead → CFO → Accountant →

Console.WriteLine("\n广度优先:");
foreach (var name in root.BreadthFirst())
    Console.Write($"{name} → ");
// CEO → CTO → CFO → Dev Lead → QA Lead → Accountant →

5.4 分页查询

/// <summary>
/// 自动分页获取所有数据 —— 调用方无需关心分页逻辑
/// </summary>
static IEnumerable<User> GetAllUsers(IUserRepository repo, int pageSize = 100)
{
    int page = 0;
    List<User> batch;

    do
    {
        batch = repo.GetUsers(page, pageSize);  // 每次取一页
        
        foreach (var user in batch)
        {
            yield return user;  // 逐个产出
        }
        
        page++;
    } while (batch.Count == pageSize);  // 如果不满一页,说明是最后一页
}

// 调用方完全不用关心分页
foreach (var user in GetAllUsers(userRepo))
{
    ProcessUser(user);
}

// 或者只取前50个
var first50 = GetAllUsers(userRepo).Take(50).ToList();

5.5 自定义 LINQ 操作符

public static class EnumerableExtensions
{
    /// <summary>
    /// 批量分组:将序列按指定大小分批
    /// [1,2,3,4,5,6,7] → [[1,2,3],[4,5,6],[7]]
    /// </summary>
    public static IEnumerable<IReadOnlyList<T>> Batch<T>(
        this IEnumerable<T> source, int batchSize)
    {
        if (batchSize <= 0)
            throw new ArgumentOutOfRangeException(nameof(batchSize));

        var batch = new List<T>(batchSize);

        foreach (var item in source)
        {
            batch.Add(item);
            if (batch.Count == batchSize)
            {
                yield return batch.AsReadOnly();
                batch = new List<T>(batchSize);
            }
        }

        if (batch.Count > 0)
            yield return batch.AsReadOnly();
    }

    /// <summary>
    /// 滑动窗口:每次返回连续的N个元素
    /// [1,2,3,4,5], window=3 → [[1,2,3],[2,3,4],[3,4,5]]
    /// </summary>
    public static IEnumerable<IReadOnlyList<T>> SlidingWindow<T>(
        this IEnumerable<T> source, int windowSize)
    {
        var window = new Queue<T>(windowSize);

        foreach (var item in source)
        {
            window.Enqueue(item);
            if (window.Count == windowSize)
            {
                yield return window.ToList().AsReadOnly();
                window.Dequeue();
            }
        }
    }

    /// <summary>
    /// 交替合并两个序列
    /// [1,3,5] + [2,4,6] → [1,2,3,4,5,6]
    /// </summary>
    public static IEnumerable<T> Interleave<T>(
        this IEnumerable<T> first, IEnumerable<T> second)
    {
        using var e1 = first.GetEnumerator();
        using var e2 = second.GetEnumerator();

        bool has1 = true, has2 = true;

        while (true)
        {
            has1 = has1 && e1.MoveNext();
            if (has1) yield return e1.Current;

            has2 = has2 && e2.MoveNext();
            if (has2) yield return e2.Current;

            if (!has1 && !has2) yield break;
        }
    }

    /// <summary>
    /// 去重(根据指定键)
    /// </summary>
    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;
            }
        }
    }
}

// 使用示例
var numbers = Enumerable.Range(1, 20);

// 批量处理
foreach (var batch in numbers.Batch(6))
{
    Console.WriteLine($"[{string.Join(", ", batch)}]");
}
// [1, 2, 3, 4, 5, 6]
// [7, 8, 9, 10, 11, 12]
// [13, 14, 15, 16, 17, 18]
// [19, 20]

// 滑动平均
var values = new[] { 10.0, 20, 30, 40, 50, 60 };
foreach (var window in values.SlidingWindow(3))
{
    Console.WriteLine($"窗口 [{string.Join(",", window)}] 平均值: {window.Average():F1}");
}

六、常见陷阱与最佳实践

6.1 ⚠️ 参数验证的陷阱

// ❌ 错误:异常会被延迟到遍历时才抛出!
static IEnumerable<int> GetPositiveNumbers_Bad(int[] source)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));  // 延迟执行!
    
    foreach (var n in source)
    {
        if (n > 0) yield return n;
    }
}

// 这里不会抛异常!
var result = GetPositiveNumbers_Bad(null);  // 没有执行方法体

// 这里才抛异常(可能离调用处很远,难以调试)
foreach (var n in result) { }  // 💥 ArgumentNullException

// ✅ 正确:将验证和迭代分离
static IEnumerable<int> GetPositiveNumbers_Good(int[] source)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));  // 立即抛出!
    
    return GetPositiveNumbersCore(source);
}

// 私有迭代器方法
private static IEnumerable<int> GetPositiveNumbersCore(int[] source)
{
    foreach (var n in source)
    {
        if (n > 0) yield return n;
    }
}

// 这里就会立即抛出异常
var result2 = GetPositiveNumbers_Good(null);  // 💥 立即报错

6.2 ⚠️ 多次枚举的陷阱

// ❌ 危险:IEnumerable可能被多次枚举
static void ProcessData_Bad(IEnumerable<int> data)
{
    Console.WriteLine($"总数: {data.Count()}");       // 第1次遍历
    Console.WriteLine($"最大: {data.Max()}");          // 第2次遍历
    Console.WriteLine($"平均: {data.Average()}");      // 第3次遍历
    
    foreach (var item in data)                         // 第4次遍历
    {
        // ...
    }
}

// 如果data来自数据库查询,这会执行4次SQL!
// 如果data来自网络流,第2次遍历会失败!

// ✅ 正确:先物化(Materialize)再使用
static void ProcessData_Good(IEnumerable<int> data)
{
    var materialized = data.ToList();  // 只遍历一次,缓存到内存
    
    Console.WriteLine($"总数: {materialized.Count}");     // O(1)
    Console.WriteLine($"最大: {materialized.Max()}");
    Console.WriteLine($"平均: {materialized.Average()}");
    
    foreach (var item in materialized) { }
}

// ReSharper/Rider 会提示 "Possible multiple enumeration of IEnumerable"

6.3 ⚠️ 在迭代器中使用 try-catch 的限制

// ❌ 编译错误:yield return 不能出现在 try-catch 块中
IEnumerable<int> Bad()
{
    try
    {
        yield return 1;  // CS1626: 编译错误!
    }
    catch (Exception)
    {
        // ...
    }
}

// ✅ yield return 可以在 try-finally 中使用
IEnumerable<int> Good()
{
    try
    {
        yield return 1;  // OK
        yield return 2;
    }
    finally
    {
        Console.WriteLine("清理资源");  // Dispose时执行
    }
}

// ✅ 解决方案:把可能出错的逻辑提取出来
IEnumerable<int> AlsoGood(string[] files)
{
    foreach (var file in files)
    {
        int value;
        try
        {
            value = int.Parse(File.ReadAllText(file));
        }
        catch (Exception ex)
        {
            Console.WriteLine($"跳过文件 {file}: {ex.Message}");
            continue;
        }
        yield return value;  // yield 在 try-catch 外面
    }
}

6.4 ⚠️ 遍历中修改集合

// ❌ 运行时异常
var list = new List<int> { 1, 2, 3, 4, 5 };
foreach (var item in list)
{
    if (item == 3)
        list.Remove(item);  // 💥 InvalidOperationException
}

// ✅ 方案1:反向遍历
for (int i = list.Count - 1; i >= 0; i--)
{
    if (list[i] == 3) list.RemoveAt(i);
}

// ✅ 方案2:LINQ 过滤生成新集合
list = list.Where(x => x != 3).ToList();

// ✅ 方案3:RemoveAll
list.RemoveAll(x => x == 3);

七、高级主题

7.1 IAsyncEnumerable(C# 8.0+)

/// <summary>
/// 异步迭代器 —— 异步版的 yield
/// 适合:数据库查询、HTTP API 分页、流式处理
/// </summary>
static async IAsyncEnumerable<WeatherData> GetWeatherStreamAsync(
    string[] cities,
    [EnumeratorCancellation] CancellationToken ct = default)
{
    using var httpClient = new HttpClient();

    foreach (var city in cities)
    {
        ct.ThrowIfCancellationRequested();

        // 模拟异步API调用
        var response = await httpClient.GetStringAsync(
            $"https://api.weather.com/{city}", ct);

        var data = JsonSerializer.Deserialize<WeatherData>(response);
        
        yield return data;  // 异步yield!
        
        await Task.Delay(100, ct);  // 限流
    }
}

// 使用 await foreach 消费
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

await foreach (var weather in GetWeatherStreamAsync(cities, cts.Token))
{
    Console.WriteLine($"{weather.City}: {weather.Temperature}°C");
    
    if (weather.Temperature > 40)
        break;  // 可以提前退出,资源会正确释放
}

7.2 自定义可枚举类型(鸭子类型)

/// <summary>
/// foreach 其实不需要实现 IEnumerable 接口!
/// 只需要有 GetEnumerator() 方法返回一个有 MoveNext() 和 Current 的对象
/// 这叫"鸭子类型"(Duck Typing)—— 走起路来像鸭子,叫起来像鸭子,就是鸭子
/// </summary>
public class DuckCollection
{
    // 不实现任何接口!
    public DuckEnumerator GetEnumerator() => new DuckEnumerator();
}

public class DuckEnumerator
{
    private int _value = 0;

    public int Current => _value;

    public bool MoveNext()
    {
        _value++;
        return _value <= 5;
    }
}

// 完全可以 foreach!
var duck = new DuckCollection();
foreach (var item in duck)
{
    Console.Write($"{item} "); // 1 2 3 4 5
}

7.3 利用扩展方法使任意类型可 foreach

// 让 int 可以 foreach(范围遍历)
public static class IntExtensions
{
    // 为 Range 类型添加 GetEnumerator
    public static IEnumerator<int> GetEnumerator(this Range range)
    {
        var start = range.Start.Value;
        var end = range.End.Value;

        for (int i = start; i < end; i++)
        {
            yield return i;
        }
    }
}

// 现在可以这样写:
foreach (var i in 0..10)
{
    Console.Write($"{i} "); // 0 1 2 3 4 5 6 7 8 9
}

八、性能考量

8.1 struct 枚举器优化

/// <summary>
/// List<T>.Enumerator 是 struct!避免堆分配
/// 这就是为什么 List<T> 的 GetEnumerator 返回具体的结构体类型
/// </summary>
public class OptimizedCollection<T> : IEnumerable<T>
{
    private T[] _items;
    private int _count;

    // 返回具体的struct类型(而不是IEnumerator<T>接口)
    // foreach 编译器会直接使用这个方法,避免装箱
    public Enumerator GetEnumerator() => new Enumerator(this);

    // 接口的显式实现(只在通过接口引用时使用,会装箱)
    IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
    IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);

    // 结构体枚举器 —— 在栈上分配,零GC压力
    public struct Enumerator : IEnumerator<T>
    {
        private readonly OptimizedCollection<T> _collection;
        private int _index;

        internal Enumerator(OptimizedCollection<T> collection)
        {
            _collection = collection;
            _index = -1;
        }

        public T Current => _collection._items[_index];
        object IEnumerator.Current => Current!;

        public bool MoveNext() => ++_index < _collection._count;
        public void Reset() => _index = -1;
        public void Dispose() { }
    }
}

8.2 性能对比

// 性能从高到低:
// 1. for 循环(直接索引访问)
for (int i = 0; i < array.Length; i++)
    sum += array[i];

// 2. foreach on 具体类型(使用struct枚举器,无GC)
foreach (var item in list)       // List<T>.Enumerator 是 struct
    sum += item;

// 3. foreach on IEnumerable<T>(接口调用,可能装箱)
IEnumerable<int> enumerable = list;
foreach (var item in enumerable)  // 可能导致struct枚举器装箱
    sum += item;

// 4. LINQ(有额外的委托调用开销)
sum = list.Sum();

// 热路径上的建议:
// - 数组优先用 for 或 Span<T>
// - 明确类型时直接 foreach(利用struct枚举器)
// - 避免在热循环中用LINQ

九、综合练习

/// <summary>
/// 综合练习:实现一个惰性求值的整数区间类
/// </summary>
public class LazyRange : IEnumerable<int>
{
    private readonly int _start;
    private readonly int _end;
    private readonly int _step;

    public LazyRange(int start, int end, int step = 1)
    {
        if (step == 0) throw new ArgumentException("步长不能为0", nameof(step));
        if (step > 0 && start > end) throw new ArgumentException("正步长时start必须<=end");
        if (step < 0 && start < end) throw new ArgumentException("负步长时start必须>=end");

        _start = start;
        _end = end;
        _step = step;
    }

    public IEnumerator<int> GetEnumerator()
    {
        for (int i = _start; _step > 0 ? i < _end : i > _end; i += _step)
        {
            yield return i;
        }
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    // 因为实现了 IEnumerable<int>,自动获得所有 LINQ 能力!
    // .Where() .Select() .Take() .Sum() .Average() 等等全部可用
}

// 使用
var range = new LazyRange(0, 1_000_000_000);  // 十亿个数,但不占内存!

var result = range
    .Where(x => x % 7 == 0)    // 7的倍数
    .Select(x => x * x)         // 平方
    .Take(10)                    // 只取前10个
    .ToList();

Console.WriteLine(string.Join(", ", result));
// 0, 49, 196, 441, 784, 1225, 1764, 2401, 3136, 3969

十、总结速查表

┌────────────────────────────────────────────────────────────────┐
│                        速查表                                  │
├─────────────────┬──────────────────────────────────────────────┤
│ IEnumerable<T>  │ "我可以被遍历" — 工厂,生产枚举器             │
│ IEnumerator<T>  │ "我负责遍历" — 游标,维护遍历状态              │
│ foreach         │ GetEnumerator + while(MoveNext) 的语法糖      │
│ yield return    │ 产出一个值并暂停,编译器生成状态机              │
│ yield break     │ 终止迭代器                                    │
│ 延迟执行        │ 方法体在 MoveNext 时才执行                     │
│ await foreach   │ 异步迭代器消费(C# 8.0+)                     │
├─────────────────┼──────────────────────────────────────────────┤
│   最佳实践       │                                              │
├─────────────────┼──────────────────────────────────────────────┤
│ 参数验证         │ 分离为 包装方法(验证)+ 私有迭代器方法        │
│ 避免多次枚举      │ .ToList() / .ToArray() 物化                 │
│ 热路径性能       │ 用 struct 枚举器,避免装箱                    │
│ 资源释放         │ 用 try-finally 或 using,foreach自动Dispose   │
│ 无限序列         │ yield 天然支持,配合 Take/First 使用           │
└─────────────────┴──────────────────────────────────────────────┘

yield 的暂停-恢复机制是核心中的核心。