虽然我同意 cmets 关于残留片段的观点,并且框架代码不使用 Reset() 并且生成器块确实会引发异常,但我不同意它完全没用。我对生成器块为何在重置时抛出异常的理解是因为对任何构建状态的担忧。生成器块的本质使它们特别不适合可重置操作,但这并不意味着可重置枚举器不好或设计不佳。
考虑一个复杂的枚举,它由数百个不同的“装饰器”枚举器组成。构建这样一个对象图的成本是不可忽略的。现在还要考虑这个复杂枚举的来源是动态的,但是出于处理的原因,需要快照语义。在这种情况下,我们可以创建这个“枚举器堆栈”,并在第一次调用MoveNext() 时拍摄源快照。执行复杂的枚举/投影/等并获得结果。现在我们希望再次执行此操作,从头开始。 Reset() 为整个枚举器堆栈提供了一种机制,可以重新初始化到它的起始状态,而无需重新构建整个对象图。此外,它允许我们将对象图的构建与需要多次运行此复杂枚举的消费者分开。
我发现使用可重置枚举器有很多用途,而且它几乎总是与某种需要复杂/可组合枚举器装饰器的数据馈送相关。在许多装饰器中,对Reset() 的调用只是传递给包装的枚举器实例,但在其他装饰器中,会执行一些次要工作,例如将运行总和归零,可能重新启动开始时间,或重新获取源数据快照。
下面是一个源枚举器示例,它下载一个列表中的文档,然后在该列表上进行枚举。重置枚举器会导致重新下载列表。此外,定义了一个装饰器枚举器,可用于包装列表枚举器(或任何枚举器)以投影枚举器的项目。我称它为SelectEnumerator,因为它的作用与Enumerable.Select相同
// excuse the poorly named types
public class ListDownloaderEnumerator<T> : IEnumerator<T>
{
private int index = -1;
private readonly string url;
private IReadOnlyList<T> items;
public ListDownloaderEnumerator(string url)
{
this.url = url;
}
public bool MoveNext()
{
// downloading logic removed for brevity
if (items == null) download(url);
index = index + 1;
return index < items.Count;
}
public void Reset()
{
index = -1;
items = null;
}
// other parts of IEnumerator<T>, such as Current
}
public class SelectEnumerator<T, TResult> : IEnumerator<T>
{
private readonly IEnumerator<T> enumerator;
private readonly Func<T, TResult> projection;
public SelectEnumerator(IEnumerator<T> enumerator, Func<T, TResult> projection)
{
this.enumerator = enumerator;
this.projection = projection;
}
public bool MoveNext()
{
return enumerator.MoveNext();
}
public void Reset()
{
enumerator.Reset();
}
// other parts of IEnumerator<T>, such as Current
}
// somewhere else in the application
// we can now write processing code without concern for sourcing
// and perhaps projecting the data. this example is very simple,
// but using decorator enumerator you can accomplish very complex
// processing of sequences while maintaining small, testable, and
// composable classes. it also allows for highly configurable
// processing, since the decorators become building blocks.
public class DownloadedDataProcessor
{
private readonly IEnumerator<MyProjectedListItem> enumerator;
public DownloadedDataProcessor(IEnumerator<MyProjectedListItem> enumerator)
{
this.enumerator = enumerator;
}
public void ProcessForever()
{
while (true)
{
while (enumerator.MoveNext())
{
Process(enumerator.Current);
}
enumerator.Reset();
}
}
private void Process(MyProjectedListItem item)
{
// top secret processing
}
}