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); // 15975.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的暂停-恢复机制是核心中的核心。