何时调用IEnumerator.Reset()方法?

时间:2010-10-16 05:27:40

标签: c# interface

让我们有这个代码:

class MyList : IEnumerable, IEnumerator
{
    int[] A = { 1, 2, 3, 4, 5 };
    int i = -1;

    #region IEnumerator Members

    public object Current
    {
        get { return A[i]; }
    }

    public bool MoveNext()
    {
        i++;
        return i < 5;
    }

    public void Reset()
    {
        i = -1;
    }

    #endregion

    #region IEnumerable Members

    public IEnumerator GetEnumerator()
    {
        return (IEnumerator)this;
    }

    #endregion
}

在主要方法中:

MyList list = new MyList();
foreach (int i in list)
{
    Console.WriteLine(i);
}

foreach (int i in list)
{
    Console.WriteLine(i);
}

为什么第二个foerach不起作用?并且“i”不会再次初始化?

是真的:在执行foreach之前应该自动调用Reset方法吗?

为什么不在这里打电话?

6 个答案:

答案 0 :(得分:9)

重置是多余的;因此,迭代器块的语言规范中的要求会在重置时引发异常。正确的做法是简单地处理和释放旧的迭代器,然后再次调用GetEnumerator。或者更好:避免读取它两次,因为并非所有数据都是可重复的。

答案 1 :(得分:7)

IEnumerable和IEnumerator通常应该是单独的类,除了总是返回空的枚举器或总是返回相同的项之外,GetEnumerator方法必须始终返回IEnumerator的新实例。

IEnumerator.Reset没有多大意义; for-each循环不使用它,并且IEnumerable / IEnumerator的使用者不能使用它,除非他们知道可枚举类型是什么,在这种情况下,他们可以使用实际类型而不是接口。

答案 2 :(得分:3)

foreach未调用重置。查看Reflector中的Main方法确认了这一点。

.NET类,如ArrayList,实际上返回一个实现IEnumerator的类的新实例。

例如ArrayList实现IEnumerable,其GetEnumerator方法如下所示:

public virtual IEnumerator GetEnumerator()
{
    return new ArrayListEnumeratorSimple(this);
}

因此无需担心调用Reset,因为每个foreach都使用枚举器的新实例。

有关显示IEnumerable实现的完整示例以及实现IEnumerator的单独类,您可以查看IEnumerable的文档。

答案 3 :(得分:2)

这也有效:

public bool MoveNext()
{
        if(i < 5)
        {
            i++;
            return true;
        }
        else
        {
            i = -1;
            return false;
        }
}

答案 4 :(得分:0)

虽然我同意有关遗迹部分的评论,并且框架代码未使用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
    }
}

答案 5 :(得分:-1)

我需要执行此操作的许多实例,因此我所做的是在GetEnumerator方法中调用reset。这是一个例子:

public IEnumerator GetEnumerator()
{
    this.Reset(); // Reset each time you get the Enumerator
    return (IEnumerator)this;
}