When IEnumerator.Reset() method is called?

后端 未结 6 563
醉话见心
醉话见心 2021-01-11 14:51

let\'s have this code :

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

    #region IEnumerator Members

    pub         


        
相关标签:
6条回答
  • 2021-01-11 15:29

    Reset is not called by foreach. Looking at your Main method in Reflector confirms this.

    The .NET classes, like ArrayList, actually return a new instance of a class that implements IEnumerator.

    For example ArrayList implements IEnumerable, and its GetEnumerator method looks like this:

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

    so there is no need to worry about calling Reset since every foreach uses a new instance of the enumerator.

    For a complete example showing the implementation of IEnumerable and a separate class implementing IEnumerator, you can look at the documentation for IEnumerable.

    0 讨论(0)
  • 2021-01-11 15:29

    While I agree with the comments regarding vestigial pieces and that Reset() is not used by framework code and generator blocks do indeed throw exceptions, I don't agree that it's completely useless. My understanding as to why generator blocks throw exceptions on reset is because of concerns over any built up state. The very nature of generator blocks make them particularly il-suited for resettable operations, but that doesn't mean a resettable enumerator is bad or poorly designed.

    Consider a complex enumeration, built up by composing hundreds of different 'decorator' enumerators. The cost of constructing such an object graph is non-negligible. Now also consider the source of this complex enumeration is dynamic, but for processing reasons, snapshot semantics are required. In such a scenario, we may create this 'enumerator stack' and upon the first MoveNext() call, a snapshot of the source is taken. The complex enumeration/projection/etc is performed and results obtained. Now we wish to perform this operation again, starting at the beginning. Reset() provides a mechanism for this entire enumerator stack to re-initialize to it's starting state without having to re-construct the entire object graph. Further, it permits us to keep the construction of the object graph separated from the consumer who needs to run this complex enumeration multiple times.

    I've found many uses for using resettable enumerators and it is almost always in relation to some sort of data feed that requires complex/composable enumerator decorators. In many of these decorators, the call to Reset() is simply passed to the wrapped enumerator instance, but in others, some minor work is performed such as zeroing out a running sum, perhaps restarting a start time, or re-fetching the source snapshot of your data.

    Below is an example of a source enumerator that downloads a document an a list and then enumerates over that list. Resetting the enumerator causes the list to be re-downloaded. Further, a decorator enumerator is defined that could be used to wrap the list enumerator (or any enumerator) to project the items of the enumerator. I called it SelectEnumerator since it performs the same role as 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
        }
    }
    
    
    0 讨论(0)
  • 2021-01-11 15:31

    Reset is redundant; so much so that it is a requirement in the language spec for iterator blocks to throw an exception on Reset. The correct thing to do is simply dispose and release the old iterator, and call GetEnumerator again. Or better: avoid having to read it twice, since not all data is repeatable.

    0 讨论(0)
  • 2021-01-11 15:32

    There a many instances where I need to do this, so what I do is call reset in my GetEnumerator method. Here is an example:

    public IEnumerator GetEnumerator()
    {
        this.Reset(); // Reset each time you get the Enumerator
        return (IEnumerator)this;
    }
    
    0 讨论(0)
  • 2021-01-11 15:34

    This also works:

    public bool MoveNext()
    {
            if(i < 5)
            {
                i++;
                return true;
            }
            else
            {
                i = -1;
                return false;
            }
    }
    
    0 讨论(0)
  • 2021-01-11 15:49

    The IEnumerable and IEnumerator should generally be separate classes, and except in the case of enumerators that always return empty or always return the same item, the GetEnumerator method must always return a new instance of an IEnumerator.

    There isn't much point to IEnumerator.Reset; for-each loops don't use it, and consumers of an IEnumerable/IEnumerator can't use it unless they know what the enumerable type is, in which case they could use the actual type rather than the interface.

    0 讨论(0)
提交回复
热议问题