36

How exactly is the right way to call IEnumerator.Reset?

The documentation says:

The Reset method is provided for COM interoperability. It does not necessarily need to be implemented; instead, the implementer can simply throw a NotSupportedException.

Okay, so does that mean I'm not supposed to ever call it?

It's so tempting to use exceptions for flow control:

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

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

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

Is that how we're supposed to use it? Or are we not meant to use it from managed code at all?

user541686
  • 205,094
  • 128
  • 528
  • 886

3 Answers3

59

never; ultimately this was a mistake. The correct way to iterate a sequence more than once is to call .GetEnumerator() again - i.e. use foreach again. If your data is non-repeatable (or expensive to repeat), buffer it via .ToList() or similar.

It is a formal requirement in the language spec that iterator blocks throw exceptions for this method. As such, you cannot rely on it working. Ever.

Jeff Yates
  • 61,417
  • 20
  • 137
  • 189
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    +1 I didn't know that part was a requirement in the language spec... then yeah, that's definitely a mistake. Kind of a bummer haha, it's sometimes so much easier to call it. – user541686 May 11 '11 at 18:42
  • 2
    @Mark Gravell: Not all enumerators are iterator blocks. You can rely on Reset working if you are using a known IEnumerator implementation whose Reset method is known to work. For example, System.Collections.Generic.List. – phoog May 11 '11 at 18:47
  • @phoog: Yeah I know what iterator blocks are, I just didn't know they had that *requirement*. :-) – user541686 May 11 '11 at 18:48
  • 2
    @phoog is it explicitly documented to work...? And then you limit yourself to `List`, not the interface - which is a bit of a pain. If you already know you have list-of-T, you also know that iterating it twice is just as easy... – Marc Gravell May 11 '11 at 18:54
  • 1
    Yes, it is explicitly documented to work (http://msdn.microsoft.com/en-us/library/bb335884.aspx). I just chose List as a quick example of a library type supporting the method. – phoog May 11 '11 at 19:01
  • @phoog but as mentioned: if you are tying into very specific implementations, you aren't *really* using IEnumerable as a general interface. It sucks that a method exists that is unusable... but; there it is – Marc Gravell May 11 '11 at 19:20
  • @Marc Gravell: Agreed, I'm just in a particularly pedantic mood today for some reason :=/ – phoog May 11 '11 at 19:43
  • To my findings most classes implementing `ICollection` (T[], List, HashSet, ...) return enumerators which implement `Reset()` in a working way. – springy76 Aug 25 '16 at 07:59
  • 1
    @springy76 reality is that "most" simply isn't enough to be useful; either it is a useful part of the contract, or it isn't. With no way of testing whether it is supported (`bool ResetSupported {get;}`, for example), it is useless. IIRC, the compiler specification actually *requires* iterator blocks to throw `NotSupportedException` when `Reset()` is called. – Marc Gravell Aug 25 '16 at 09:25
8

I recommend not using it. A lot of modern IEnumerable implementations will just throw an exception.

Getting enumerators is hardly ever "expensive". It is enumerating them all (fully) that can be expensive.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    Depending on the issue, though, it *can* be expensive, at least relative to other normal operations. E.g.: Let's say you're wrapping calls to `FindFirstFile`/`FindNextFile` -- those acquire system handles, which is a relatively expensive operation compared to something like array enumeration. – user541686 May 11 '11 at 18:38
  • 1
    @MMehrdad: but then get the array *once*, and iterate over that. And when you need to restart, get a new iterator for the same array you already had. – jalf May 11 '11 at 18:40
  • @Stephen: Depends. If you're over the network, it can be expensive. – user541686 May 11 '11 at 18:40
  • @Jalf: What if the array is very big though? You can't always save your data. – user541686 May 11 '11 at 18:41
  • 2
    @Mehrdad: In that case, define the enumerator so that it only calls `FindFirstFile` when the first item is enumerated. That way, it acts just like every other enumerator. – Stephen Cleary May 11 '11 at 18:42
  • @Mehrdad if the data is very big, the cost of GetEnumerator() is insignificant noise – Marc Gravell May 11 '11 at 18:42
  • @Marc: Not if you're doing something like iterating lots of files over an intermittent network connection. @Stephen: Yeah, that's a good *workaround*, though I'm not sure if it's a great *solution* per se. – user541686 May 11 '11 at 18:45
  • 1
    @Mehrdad: what if that was the case? It sounds like the only solution that would satisfy your requirements is *magic*. It's not possible to have it both ways. You can *either* have the array in memory, making it cheap to iterate again and again, or you can load each element on demand, making it expensive to start over. No matter how those two options are wrapped, they're still all you have. If `Reset()` could be safely used, then it would still have to do one of those two – jalf May 11 '11 at 18:48
  • 1
    Either the enumerator could store the full array in memory, or it could call `FindNextFile` when you call `Next`, and `FindFirstFile` if you called `Reset`. There are no other options. – jalf May 11 '11 at 18:49
  • Most likely, depending on the implementation, either the enumerator is going to require a lot of memory (same as if you cached your very big list of files yourself) or calling Reset will be expensive (same as if you called GetSomeExpensiveEnumerator() again). – phoog May 11 '11 at 18:50
  • @Mehrdad: It's not a workaround. It's the expected way that enumerators work, especially in the current world of LINQ. `GetEnumerator` is expected to be a fast operation that captures its parameters and the first enumeration initiates the real work (network call, establishing db connection, etc). – Stephen Cleary May 11 '11 at 18:51
  • @Mehrdad - then te API should be written to support that :) but not (IMO) by using Reset on the iterator – Marc Gravell May 11 '11 at 18:54
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();
            }
        }
    }
}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Pitka
  • 525
  • 5
  • 12