如何使用IEnumerator.Reset()?

36

如何正确调用IEnumerator.Reset方法?

文档中说明:

Reset方法是为COM互操作性提供的。它不一定需要被实现; 相反,实现者可以简单地抛出一个NotSupportedException

好了,那么这是否意味着我不应该调用它?

使用异常来控制流程是如此诱人:

using (enumerator = GetSomeExpensiveEnumerator())
{
    while (enumerator.MoveNext()) { ... }

    try { enumerator.Reset(); } //Try an inexpensive method
    catch (NotSupportedException)
    { enumerator = GetSomeExpensiveEnumerator(); } //Fine, get another one

    while (enumerator.MoveNext()) { ... }
}

这就是我们应该如何使用它吗?还是说我们根本不应该从托管代码中使用它?


4
请绝不要将异常用于控制流程... - Enigmativity
3个回答

59

永远不要这样做。多次迭代序列的正确方法是再次调用.GetEnumerator(),即再次使用foreach。如果您的数据不可重复(或成本很高),请通过.ToList()或类似方法缓冲它。

在语言规范中,迭代器块必须为此方法抛出异常。因此,您不能依赖它正常工作。永远不要这样做。


1
+1 我不知道那部分是语言规范的要求... 那肯定是个错误。有点遗憾哈哈,有时候直接调用会更容易。 - user541686
2
@Mark Gravell:并非所有枚举器都是迭代器块。如果您正在使用已知 IEnumerator 实现且其 Reset 方法已知可用的情况下,您可以依赖于 Reset 的工作。例如,System.Collections.Generic.List<T>。 - phoog
2
@phoog 这个是否有明确的文档说明它可以工作...?然后你限制自己只使用 List<T>,而不是接口 - 这有点麻烦。如果你已经知道你有一个 T 列表,那么两次迭代它同样容易... - Marc Gravell
1
是的,它明确记录为有效(http://msdn.microsoft.com/en-us/library/bb335884.aspx)。我只是选择List<T>作为支持该方法的库类型的快速示例。 - phoog
1
@springy76 在现实中,“大多数”并不足以成为有用的;要么它是合同的有用部分,要么就是无用的。如果没有办法测试它是否被支持(例如bool ResetSupported {get;}),那么它就是无用的。我回忆起编译器规范实际上要求迭代块在调用 Reset() 时抛出 NotSupportedException - Marc Gravell
显示剩余4条评论

8

我建议不要使用它。许多现代的IEnumerable实现只会抛出异常。

获取枚举器通常并不是“昂贵”的。完全枚举它们可能是昂贵的。


1
根据问题的不同,相对于其他正常操作,它可能是昂贵的。例如:假设您正在包装对FindFirstFile/FindNextFile的调用--这些调用会获取系统句柄,与诸如数组枚举之类的操作相比,这是一种相对较昂贵的操作。 - user541686
1
@MMehrdad:但是要获取数组一次,然后迭代该数组。当您需要重新启动时,请为已有的同一数组获取新的迭代器。 - jalf
2
@Mehrdad:在这种情况下,定义枚举器,使其仅在列举第一项时调用“FindFirstFile”。这样,它的行为就像其他所有枚举器一样。 - Stephen Cleary
1
@Mehrdad:如果是这种情况怎么办?听起来唯一能满足您要求的解决方案就是魔法。无法两全其美。您可以选择将数组存储在内存中,使得重复迭代变得便宜,或者您可以按需加载每个元素,使得重新开始变得昂贵。无论如何,这两个选项都是您拥有的全部。如果Reset()可以安全使用,那么它仍然必须执行其中的一个操作。 - jalf
1
枚举器可以将整个数组存储在内存中,或者在您调用“Next”时调用“FindNextFile”,如果您调用“Reset”,则调用“FindFirstFile”。没有其他选项。 - jalf
显示剩余7条评论

0
public class PeopleEnum : IEnumerator
{
    public Person[] _people;

    // Enumerators are positioned before the first element 
    // until the first MoveNext() call. 
    int position = -1;

    public PeopleEnum(Person[] list)
    {
        _people = list;
    }

    public bool MoveNext()
    {
        position++;
        return (position < _people.Length);
    }

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

    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }

    public Person Current
    {
        get
        {
            try
            {
                return _people[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接