迭代器(IEnumerable) 和 列表(List) 的区别

迭代器(IEnumerable) 和 列表(List) 的区别

一. 迭代器方法

示例方法:

//迭代器方法

private static IEnumerable GetDataItems()

{

for (int i = 0; i < 10; i++)

{

yield return $"DataItem_{i + 1}";

}

yield break;

}

yield 是"现用现做"的生产线模式,适合流式处理但重复使用成本高

优点:

延迟执行(Lazy Evaluation)

数据只在遍历时生成,不会立即占用内存。例如:

var items = GetDataItems(); // 此时尚未生成任何数据

foreach(var item in items) // 开始遍历时才逐个生成

{

// 处理逻辑

}

内存效率高

每次只保留当前迭代项的内存,适合处理大数据集(如百万级数据)

无限序列支持

可以表示理论上无限长的序列(如传感器数据流)

链式处理优化

完美配合LINQ的延迟执行特性:

GetDataItems().Where(x => x.ToString().Contains("2")).Take(5);

缺点:

多次迭代成本高

每次遍历都会重新生成数据:

var items = GetDataItems();

var count = items.Count(); // 第一次完整迭代

var list = items.ToList(); // 第二次完整迭代

调试困难

无法直接查看所有元素(需转换为List)

状态机开销

编译器会生成状态机类,轻微性能损耗

二. 列表方法

示例方法:

//列表方法

private static List GetDataItems()

{

var list = new List();

for (int i = 0; i < 10; i++)

{

list.Add($"DataItem_{i + 1}");

}

return list;

}

List 是"批量预制"的仓库模式,适合高频访问但初始化成本高

优点:

立即执行(Eager Evaluation)

所有数据在方法返回前已生成并存储在内存中

多次访问零成本

数据已物化(materialized),可反复遍历:

var items = GetDataItems();

var count = items.Count; // 直接获取

var first = items[0]; // 随机访问

调试方便

可在调试器中直接查看所有元素

无状态机开销

直接操作集合,无编译器生成的额外代码

缺点:

内存压力大

所有数据同时驻留内存,大数据集可能引发OOM启动延迟

必须等待所有数据生成完毕才能返回灵活性低

无法表示无限序列或延迟计算场景

三. 关键差异对比表

特性yield return版本List版本执行时机延迟执行(按需生成)立即执行(预先生成)内存占用仅当前项全部项多次迭代成本每次重新生成零成本随机访问不支持(必须顺序遍历)支持(通过索引)LINQ兼容性完美配合(延迟链式操作)需要中间转换大数据集适应性优秀(支持流式处理)差(可能内存溢出)代码可调试性较差(无法直接查看所有元素)优秀(完整数据可见)GC压力低(无临时集合)高(生成完整List)四. 使用场景建议

选择 yield return 当:

处理大数据集(避免内存爆炸)需要链式LINQ操作(如.Where().Select())数据生成成本高(按需生成节省资源)表示无限序列(如斐波那契数列)

选择 List 当:

数据量很小且固定需要多次随机访问要求快速获取元素数量调试阶段需要检查完整数据

五. 性能实测对比

测试代码

// 测试代码

void TestPerformance()

{

// yield版本

var sw1 = Stopwatch.StartNew();

var iter = GetDataItemsYield();

for (int i = 0; i < 100000; i++) iter.Count();

sw1.Stop();

// List版本

var sw2 = Stopwatch.StartNew();

var list = GetDataItemsList();

for (int i = 0; i < 100000; i++) list.Count;

sw2.Stop();

Debug.Log($"yield: {sw1.Elapsed} | list: {sw2.Elapsed}");

}

结果(处理10万次计数):

yield版本:~1200ms(每次重新迭代)List版本:~0.3ms(直接访问属性)

1. yield版本的耗时原因(1200ms)

执行流程分析

var iter = GetDataItemsYield(); // 仅创建迭代器,零耗时

for (int i = 0; i < 100000; i++)

iter.Count(); // 每次调用都触发完整迭代

关键瓶颈

重复生成数据

每次调用Count()都会重新执行GetDataItemsYield()中的循环,相当于执行了:

100,000次调用 × 10次迭代 = 1,000,000次循环

状态机开销

编译器为yield生成的IEnumerator状态机(包含MoveNext()等)带来额外调用开销

无缓存

每次迭代都是"重新生产"数据,无法复用之前的结果

2. List版本的极速原因(0.3ms)

执行流程分析

var list = GetDataItemsList(); // 一次性生成所有数据(10次循环)

for (int i = 0; i < 100000; i++)

list.Count; // 直接读取字段值

性能优势

数据预物化

所有数据在GetDataItemsList()中已生成并存储在连续内存中

零成本计数

List.Count是直接返回内部_size字段(本质是内存读取)

csharp

// List源码实现

public int Count => _size; // 简单的字段返回

CPU缓存友好

数据在内存中连续存储,符合空间局部性原理

3.结论

测试结果真实反映了两种模式的本质差异:

yield 是"现用现做"的生产线模式,适合流式处理但重复使用成本高List 是"批量预制"的仓库模式,适合高频访问但初始化成本高

六. 最佳实践

混合模式:对已知小数据集使用ToList()提前物化

var items = GetHugeData().Where(x => x.IsValid).Take(1000).ToList();

// 混合方案:延迟生成+智能缓存

IEnumerable GetData() {

if (_cachedData == null)

_cachedData = GenerateData().ToList(); // 首次访问物化

return _cachedData;

}

避免多重迭代:对yield结果只遍历一次

// 错误做法(两次完整迭代)

var count = GetDataItems().Count();

var list = GetDataItems().ToList();

// 正确做法

var items = GetDataItems().ToList();

var count = items.Count;

相关文章

甩脂机怎么样甩脂机对身体有害吗?如何选购甩脂机
365bet备用投注网址

甩脂机怎么样甩脂机对身体有害吗?如何选购甩脂机

📅 06-28 👁️ 5184
老是有骚扰电话怎么办
手机版office365破解版

老是有骚扰电话怎么办

📅 07-07 👁️ 8178
[颇] 页字旁加一个皮是什么字,怎么读?
手机版office365破解版

[颇] 页字旁加一个皮是什么字,怎么读?

📅 09-17 👁️ 3067