基于Directory.EnumerateFiles实现IEnumerator重复遍历失效问题
问题分析与解决方案
你的代码核心问题是复用了同一个枚举器实例。枚举器(IEnumerator<T>)是带状态的对象,第一次遍历完成后,它的指针已经指向序列末尾,第二次调用MoveNext()会直接返回false,导致遍历结果为空。此外,手动实现的Enumerator结构体也没有解决每次遍历需重新生成序列的问题。
正确实现方式(两种推荐方案)
方案1:直接返回LINQ查询的枚举器(简洁高效)
移除自定义Enumerator结构体,让GetEnumerator()每次返回全新的枚举器实例:
class LocalFileRepository : ILocalRepository { protected readonly DirectoryInfo directory; public LocalFileRepository(DirectoryInfo directory) { this.directory = directory; } DirectoryInfo ILocalRepository.Directory => directory; public IEnumerator<MyFile> GetEnumerator() { // 每次调用都重新生成文件序列的枚举器 return directory .EnumerateFiles($"*{MyFile.Extension}", SearchOption.TopDirectoryOnly) .OrderBy(f => f.Name) .Select(MyFile.Read) .GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); // 其他接口方法实现... }
方案2:使用yield return语法糖(直观易维护)
利用C#的yield关键字自动生成符合规范的枚举器,无需手动实现IEnumerator<T>:
class LocalFileRepository : ILocalRepository { protected readonly DirectoryInfo directory; public LocalFileRepository(DirectoryInfo directory) { this.directory = directory; } DirectoryInfo ILocalRepository.Directory => directory; public IEnumerator<MyFile> GetEnumerator() { // 每次遍历都会从头执行这段逻辑,生成全新的序列 foreach (var file in directory .EnumerateFiles($"*{MyFile.Extension}", SearchOption.TopDirectoryOnly) .OrderBy(f => f.Name)) { yield return MyFile.Read(file); } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); // 其他接口方法实现... }
可选方案:保留自定义枚举器(不推荐)
如果一定要保留自定义的Enumerator结构体,需确保每次GetEnumerator()都返回全新的实例:
- 修改
LocalFileRepository,移除复用的枚举器成员:
class LocalFileRepository : ILocalRepository { protected readonly DirectoryInfo directory; public LocalFileRepository(DirectoryInfo directory) { this.directory = directory; } DirectoryInfo ILocalRepository.Directory => directory; public IEnumerator<MyFile> GetEnumerator() { // 每次都创建新的枚举器实例 return new Enumerator(directory); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); // 其他接口方法实现... }
- 简化自定义
Enumerator结构体(去掉Lazy,因为每次都是新实例):
struct Enumerator : IEnumerator<MyFile> { private readonly IEnumerator<MyFile> enumerator; public Enumerator(DirectoryInfo directory) { enumerator = directory .EnumerateFiles($"*{MyFile.Extension}", SearchOption.TopDirectoryOnly) .OrderBy(f => f.Name) .Select(MyFile.Read) .GetEnumerator(); } MyFile IEnumerator<MyFile>.Current => enumerator.Current; object IEnumerator.Current => enumerator.Current; void IDisposable.Dispose() => enumerator.Dispose(); bool IEnumerator.MoveNext() => enumerator.MoveNext(); void IEnumerator.Reset() => enumerator.Reset(); }
核心原理
IEnumerable<T>接口的规范要求:每次调用GetEnumerator()必须返回一个全新的、处于初始状态的枚举器。这样每次遍历都会重新读取目录文件、生成序列,自然支持多次遍历。
内容的提问来源于stack exchange,提问作者JobaDiniz
相关产品推荐
相关产品推荐

