HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Implementing Peek to IEnumerator and IEnumerator<T>

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
andimplementingpeekienumerator

Problem

Many of you might have come to the point and wished to have a Peek for IEnumerator and IEnumerator. I tried to implement it by cheating a bit and looking up the next element before the actual MoveNext call. So I ended up with some kind of wrapper.

First of the extensions to convert default enumerators:

public static class PeekableEnumeratorExtension
{
    public static PeekableEnumerator ToPeekable(this IEnumerator enumerator)
    {
        return new PeekableEnumerator(enumerator);
    }

    public static PeekableEnumerator ToPeekable(this IEnumerator enumerator)
    {
        return new PeekableEnumerator(enumerator);
    }
}


And here is the non-generic PeekableEnumerator:

```
public class PeekableEnumerator : IEnumerator
{
protected enum Status { Uninitialized, Starting, Started, Ending, Ended }

protected IEnumerator enumerator;

protected Status status;

protected object current;

protected object peek;

public PeekableEnumerator(IEnumerator enumerator)
{
this.enumerator = enumerator;
status = Status.Uninitialized;
MoveNext();
}

public object Current
{
get
{
if (Status.Starting == status)
throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
if (Status.Ended == status)
throw new InvalidOperationException("Enumeration already finished.");

return current;
}
}

public object Peek
{
get
{
if (Status.Ending == status || Status.Ended == status)
throw new InvalidOperationException("Enumeration already finished.");

return peek;
}
}

public bool MoveNext()
{
current = peek;
switch (status)
{
case Status.Uninitialized:
case Status.Starting:
if (enumerator.MoveNext())
{
status++;

Solution

I think your implementation is too complicated, and what nagged me was that you start enumerate in constructor. Here is my implementation which fix that. The state reduced to a boolean telling that the peek value has been fetched from the underlying enumerator or not.

public class PeekEnumerator : IEnumerator
{
    private IEnumerator _enumerator;
    private T _peek;
    private bool _didPeek;

    public PeekEnumerator(IEnumerator enumerator)
    {
        if (enumerator == null)
            throw new ArgumentNullException("enumerator");
        _enumerator = enumerator;
    }

    #region IEnumerator implementation
    public bool MoveNext()
    {
        return _didPeek ? !(_didPeek = false) : _enumerator.MoveNext();
    }

    public void Reset()
    {
        _enumerator.Reset();
        _didPeek = false;
    }

    object IEnumerator.Current { get { return this.Current; } }
    #endregion

    #region IDisposable implementation
    public void Dispose()
    {
        _enumerator.Dispose();
    }
    #endregion

    #region IEnumerator implementation
    public T Current
    {
        get { return _didPeek ? _peek : _enumerator.Current; }
    }
    #endregion

    private void TryFetchPeek() {
        if (!_didPeek && (_didPeek = _enumerator.MoveNext()))
        {
            _peek = _enumerator.Current;
        }
    }

    public T Peek
    {
        get { 
            TryFetchPeek();
            if (!_didPeek)
                throw new InvalidOperationException("Enumeration already finished.");

            return _peek;
        }
    }
}


My test to make sure it complies to your needed behaviour:

var a = new PeekEnumerator(new [] { 1, 2, 3 }.AsEnumerable().GetEnumerator());
Console.WriteLine(a.Peek); // 1
Console.WriteLine(a.MoveNext()); // true
Console.WriteLine(a.Current); // 1
Console.WriteLine(a.Peek); // 2
Console.WriteLine(a.MoveNext()); // true
Console.WriteLine(a.Current); // 2
Console.WriteLine(a.Peek); // 3
Console.WriteLine(a.MoveNext()); // true
Console.WriteLine(a.Current); // 3

try {
    Console.WriteLine(a.Peek); // InvalidOperationException
}
catch (Exception e) {
    Console.WriteLine(e.GetType());
}

Console.WriteLine(a.MoveNext()); // false

try {
    Console.WriteLine(a.Current); // InvalidOperationException
}
catch (Exception e) {
    Console.WriteLine(e.GetType());
}

try {
    Console.WriteLine(a.Peek); // InvalidOperationException
}
catch (Exception e) {
    Console.WriteLine(e.GetType());
}

Code Snippets

public class PeekEnumerator<T> : IEnumerator<T>
{
    private IEnumerator<T> _enumerator;
    private T _peek;
    private bool _didPeek;

    public PeekEnumerator(IEnumerator<T> enumerator)
    {
        if (enumerator == null)
            throw new ArgumentNullException("enumerator");
        _enumerator = enumerator;
    }

    #region IEnumerator implementation
    public bool MoveNext()
    {
        return _didPeek ? !(_didPeek = false) : _enumerator.MoveNext();
    }

    public void Reset()
    {
        _enumerator.Reset();
        _didPeek = false;
    }

    object IEnumerator.Current { get { return this.Current; } }
    #endregion

    #region IDisposable implementation
    public void Dispose()
    {
        _enumerator.Dispose();
    }
    #endregion

    #region IEnumerator implementation
    public T Current
    {
        get { return _didPeek ? _peek : _enumerator.Current; }
    }
    #endregion

    private void TryFetchPeek() {
        if (!_didPeek && (_didPeek = _enumerator.MoveNext()))
        {
            _peek = _enumerator.Current;
        }
    }

    public T Peek
    {
        get { 
            TryFetchPeek();
            if (!_didPeek)
                throw new InvalidOperationException("Enumeration already finished.");

            return _peek;
        }
    }
}
var a = new PeekEnumerator<int>(new [] { 1, 2, 3 }.AsEnumerable().GetEnumerator());
Console.WriteLine(a.Peek); // 1
Console.WriteLine(a.MoveNext()); // true
Console.WriteLine(a.Current); // 1
Console.WriteLine(a.Peek); // 2
Console.WriteLine(a.MoveNext()); // true
Console.WriteLine(a.Current); // 2
Console.WriteLine(a.Peek); // 3
Console.WriteLine(a.MoveNext()); // true
Console.WriteLine(a.Current); // 3

try {
    Console.WriteLine(a.Peek); // InvalidOperationException
}
catch (Exception e) {
    Console.WriteLine(e.GetType());
}

Console.WriteLine(a.MoveNext()); // false

try {
    Console.WriteLine(a.Current); // InvalidOperationException
}
catch (Exception e) {
    Console.WriteLine(e.GetType());
}

try {
    Console.WriteLine(a.Peek); // InvalidOperationException
}
catch (Exception e) {
    Console.WriteLine(e.GetType());
}

Context

StackExchange Code Review Q#32857, answer score: 7

Revisions (0)

No revisions yet.