views:

601

answers:

4

With Java Iterators, I have used the hasNext method to determine whether an iteration has more elements (without consuming an element) -- thus, hasNext is like a "Peek" method.

My question: is there anything like a "hasNext" or "Peek" method with C#'s generic IEnumerators?

+4  A: 

No, unfortunately there isn't.

The IEnumerator<T> interface only exposes the following members:

Methods:

Dispose
MoveNext
Reset

Properties:

Current

Andrew Hare
Thank you, Andrew.
JaysonFix
We're taking about IEnumerator, instead of IEnumerable here, right? And * should be on Dispose, instead of MoveNext.
Even Mien
@Even - Yikes, that post was rife with errors! Thanks for pointing them out.
Andrew Hare
A: 

Nope, just MoveNext, Reset and Current.

Matt Howells
+9  A: 

No, but in C# you can repeatedly ask for the current element without moving to the next one. It's just a different way of looking at it.

It wouldn't be too hard to write a C# class to take a .NET-style IEnumerator and return a Java-style Iterator. Personally I find the .NET style easier to use in most cases, but there we go :)

EDIT: Okay, this is completely untested, but I think it will work. It does at least compile :)

using System;
using System.Collections;
using System.Collections.Generic;

// // Mimics Java's Iterable<T> interface
public interface IIterable<T>
{
    IIterator<T> Iterator();
}

// Mimics Java's Iterator interface - but
// implements IDisposable for the sake of
// parity with IEnumerator.
public interface IIterator<T> : IDisposable
{
    bool HasNext { get; }
    T Next();
    void Remove();
}

public sealed class EnumerableAdapter<T> : IIterable<T>
{
    private readonly IEnumerable<T> enumerable;

    public EnumerableAdapter(IEnumerable<T> enumerable)
    {
        this.enumerable = enumerable;
    }

    public IIterator<T> Iterator()
    {
        return new EnumeratorAdapter<T>(enumerable.GetEnumerator());
    }
}

public sealed class EnumeratorAdapter<T> : IIterator<T>
{
    private readonly IEnumerator<T> enumerator;

    private bool fetchedNext = false;
    private bool nextAvailable = false;
    private T next;

    public EnumeratorAdapter(IEnumerator<T> enumerator)
    {
        this.enumerator = enumerator;
    }

    public bool HasNext
    {
        get
        {
            CheckNext();
            return nextAvailable;
        } 
    }

    public T Next()
    {
        CheckNext();
        if (!nextAvailable)
        {
            throw new InvalidOperationException();
        }
        fetchedNext = false; // We've consumed this now
        return next;
    }

    void CheckNext()
    {
        if (!fetchedNext)
        {
            nextAvailable = enumerator.MoveNext();
            if (nextAvailable)
            {
                next = enumerator.Current;
            }
            fetchedNext = true;            
        }
    }

    public void Remove()
    {
        throw new NotSupportedException();
    }

    public void Dispose()
    {
        enumerator.Dispose();
    }
}

public sealed class IterableAdapter<T> : IEnumerable<T>
{
    private readonly IIterable<T> iterable;

    public IterableAdapter(IIterable<T> iterable)
    {
        this.iterable = iterable;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new IteratorAdapter<T>(iterable.Iterator());
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public sealed class IteratorAdapter<T> : IEnumerator<T>
{
    private readonly IIterator<T> iterator;

    private bool gotCurrent = false;
    private T current;

    public IteratorAdapter(IIterator<T> iterator)
    {
        this.iterator = iterator;
    }

    public T Current
    {
        get
        {
            if (!gotCurrent)
            {
                throw new InvalidOperationException();
            }
            return current;
        }
    }

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

    public bool MoveNext()
    {
        gotCurrent = iterator.HasNext;
        if (gotCurrent)
        {
            current = iterator.Next();
        }
        return gotCurrent;
    }

    public void Reset()
    {
        throw new NotSupportedException();
    }

    public void Dispose()
    {
        iterator.Dispose();
    }
}
Jon Skeet
+1 It really is just a different take on the same pattern.
Andrew Hare
(I'm happy to code up the adapter if anyone's interested, but I won't do it otherwise...)
Jon Skeet
I'd be interested in seeing it, Jon.
JaysonFix
Wow, that was fast! Thank you, Jon!
JaysonFix
@Andrew, it places responsibility a little different. I find the dotNet version easier to work with.
Dykam
The major failing of Java's iterators is that they don't have the equivalent of IDisposable, making them impossible to use for anything which might iterator over a resource (e.g. lines in a file).
Jon Skeet
+2  A: 

Enumerators are often lazily evaluated so HasNext makes little sense.

Ray