IEnumerable.Count()第二次调用返回0的问题排查
IEnumerable两次Count()结果不一致的问题解析
问题现象
编写单元测试对比IEnumerable中的预期值与实际值时,第一次调用Count()两者数量相等,但第二次调用时,expectedValues.Count()返回0,actualValues.Count()却保持不变。
代码复现
失败场景(未使用ToList())
using StreamReader sr = new(filepath); using CsvReader cr = new(sr, CultureInfo.InvariantCulture); IEnumerable<ExpectedT> expectedValues = cr.GetRecords<ExpectedT>(); IEnumerable<ActualT> actualValues = GetActual(); // 第一次断言通过 Assert.AreEqual(expectedValues.Count(), actualValues.Count(), "1st assert"); // 第二次断言失败:expectedValues.Count()返回0,actualValues.Count()正常 Assert.AreEqual(expectedValues.Count(), actualValues.Count(), "2nd assert");
成功场景(使用ToList()转换)
using StreamReader sr = new(filepath); using CsvReader cr = new(sr, CultureInfo.InvariantCulture); List<ExpectedT> expectedValues = cr.GetRecords<ExpectedT>().ToList(); List<ActualT> actualValues = GetActual().ToList(); // 两次断言均通过 Assert.AreEqual(expectedValues.Count, actualValues.Count, "1st assert"); Assert.AreEqual(expectedValues.Count, actualValues.Count, "2nd assert");
原因分析
核心问题在于IEnumerable的延迟执行特性和流的一次性读取限制:
- 延迟执行(Deferred Execution):
IEnumerable<T>本身不存储数据,每次调用Count()、foreach等枚举操作时,才会触发数据的获取逻辑。 - 流的读取特性:
CsvReader.GetRecords<ExpectedT>()依赖StreamReader读取CSV文件,第一次调用Count()时会完整枚举所有记录,此时StreamReader的指针已移动到流的末尾。 - 第二次枚举失效:第二次调用
expectedValues.Count()时,会再次触发枚举逻辑,但流已无剩余数据可读,因此返回0。 - actualValues无问题的原因:
GetActual()返回的IEnumerable是通过内存中直接创建ActualT实例生成的,每次枚举都会重新生成(或读取已存在的内存集合),不受流的限制,因此Count()结果始终一致。
解决方案说明
使用ToList()将IEnumerable转换为List时,会立即执行枚举逻辑,将所有数据加载到内存中存储。后续调用Count时,直接读取List的内置属性,不会再次触发流的读取或数据生成逻辑,因此两次断言都能得到正确结果。
内容的提问来源于stack exchange,提问作者nwpark
相关产品推荐
相关产品推荐

