首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >实现Peek到IEnumerator和IEnumerator<T>

实现Peek到IEnumerator和IEnumerator<T>
EN

Code Review用户
提问于 2013-10-18 08:06:36
回答 4查看 5.9K关注 0票数 5

你们中的许多人可能已经说到点子上了,并希望对IEnumerator和IEnumerator有一个了解。我试图通过欺骗一点并在实际的MoveNext调用之前查找下一个元素来实现它。所以我最后用了某种包装纸。

首先,要转换默认枚举数的扩展:

代码语言:javascript
复制
public static class PeekableEnumeratorExtension
{
    public static PeekableEnumerator ToPeekable(this IEnumerator enumerator)
    {
        return new PeekableEnumerator(enumerator);
    }

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

下面是非泛型PeekableEnumerator:

代码语言:javascript
复制
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++;
                    peek = enumerator.Current;
                }
                else
                    status = Status.Ending;
                break;
            case Status.Started:
                if (enumerator.MoveNext())
                    peek = enumerator.Current;
                else
                    status++;
                break;
            case Status.Ending:
                status++;
                break;
        }

        return Status.Ended != status;
    }

    public void Reset()
    {
        enumerator.Reset();
        status = Status.Uninitialized;
        MoveNext();
    }
}

非常类似的PeekableEnumerator:

代码语言:javascript
复制
public class PeekableEnumerator<T> : IEnumerator<T>
{
    protected enum Status { Uninitialized, Starting, Started, Ending, Ended }

    protected IEnumerator<T> enumerator;

    protected Status status;

    protected T current;

    protected T peek;

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

    public T 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;
        }
    }

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

    public T 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++;
                    peek = enumerator.Current;
                }
                else
                    status = Status.Ending;
                break;
            case Status.Started:
                if (enumerator.MoveNext())
                    peek = enumerator.Current;
                else
                    status++;
                break;
            case Status.Ending:
                status++;
                break;
        }

        return Status.Ended != status;
    }

    public void Reset()
    {
        enumerator.Reset();
        status = Status.Uninitialized;
        MoveNext();
    }

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

在你问:为什么会有5种状态?它来源于电流和Peek的生命周期:

代码语言:javascript
复制
Status        | Current   | Peek      | Comment
--------------+-----------+-----------+-----------------------------------
Uninitialized | n/a       | n/a       | Internal for constructor and Reset
Starting      | Exception | Available | Before first MoveNext
Started       | Available | Available | After first MoveNext
Ending        | Available | Exception | wrapped MoveNext returned false
Ended         | Exception | Exception | After enumeration finished

示例用法:

代码语言:javascript
复制
var a = new[] { 1, 2, 3 }.GetEnumerator().ToPeekable();

a.Current; // InvalidOperationException
a.Peek; // 1
a.MoveNext(); // true
a.Current; // 1
a.Peek; // 2
a.MoveNext(); // true
a.Current; // 2
a.Peek; // 3
a.MoveNext(); // true
a.Current; // 3
a.Peek; // InvalidOperationException
a.MoveNext(); // false
a.Current; // InvalidOperationException
a.Peek; // InvalidOperationException

更新

多亏了斯维克,这里有一个使用队列的替代版本。它将基本用法从IEnumerator更改为ICollection作为输入,但我可以接受。我需要保留一份原始收藏的副本,以便重新设置。

代码语言:javascript
复制
public class PeekableEnumerator : IEnumerator
{
    protected ICollection collection;

    protected Queue queue;

    protected bool current_set;

    protected object current;

    protected bool peek_set;

    protected object peek;

    public object Current
    {
        get
        {
            if (!current_set)
                if (peek_set)
                    throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
                else
                    throw new InvalidOperationException("Enumeration already finished.");

            return current;
        }
    }

    public object Peek
    {
        get
        {
            if (!peek_set)
                throw new InvalidOperationException("Enumeration already finished.");

            return peek;
        }
    }

    public PeekableEnumerator(ICollection collection)
    {
        this.collection = collection;
        Reset();
    }

    public bool MoveNext()
    {
        current_set = peek_set;
        current = peek;

        if (0 == queue.Count)
        {
            peek_set = false;
            return current_set;
        }
        else
        {
            peek_set = true;
            peek = queue.Dequeue();
            return true;
        }
    }

    public void Reset()
    {
        queue = new Queue(collection);
        MoveNext();
    }
}
EN

回答 4

Code Review用户

回答已采纳

发布于 2013-10-21 17:14:12

我认为您的实现太复杂了,让我烦恼的是您开始在构造函数中枚举。这是我的实现,它解决了这个问题。状态简化为布尔值,说明peek值是否已从基础枚举数中获取。

代码语言:javascript
复制
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;
        }
    }
}

我的测试以确保它符合你所需要的行为:

代码语言:javascript
复制
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());
}
票数 7
EN

Code Review用户

发布于 2013-10-18 08:28:51

不太清楚您的问题是什么,但我有一些您可能需要考虑的问题,也许Peek()不应该在超出界限或有Status.Ending == status || Status.Ended == status时抛出异常,而是返回一个空对象,以便在您决定做某事之前检查它的值。

代码语言:javascript
复制
if(a.Peek != null) { ... }

思想食粮

票数 1
EN

Code Review用户

发布于 2015-03-25 18:51:52

我更改了svick的含义以使用泛型,并将peek方法更改为返回bool,而不是抛出异常。虽加

代码语言:javascript
复制
bool TryPeek(out T value);

对于现有的实现可能更合适。

代码语言:javascript
复制
public class PeekableEnumerator<T> : IEnumerator<T>
{
    protected IEnumerable<T> collection;

    protected Queue<T> queue;

    protected bool current_set;

    protected T current;

    protected bool peek_set;

    protected T peek;

    public T Current
    {
        get
        {
            if (!current_set)
                if (peek_set)
                    throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
                else
                    throw new InvalidOperationException("Enumeration already finished.");

            return current;
        }
    }

    public bool Peek(out T value)
    {
        value = default(T);

        if (!peek_set)
            return false;

        value = peek;
        return true;
    }

    public PeekableEnumerator(IEnumerable<T> collection)
    {
        this.collection = collection;
        Reset();
    }

    public bool MoveNext()
    {
        current_set = peek_set;
        current = peek;

        if (0 == queue.Count)
        {
            peek_set = false;
            return current_set;
        }
        else
        {
            peek_set = true;
            peek = queue.Dequeue();
            return true;
        }
    }

    public void Reset()
    {
        queue = new Queue<T>(collection);
        MoveNext();
    }

    public virtual void Dispose()
    {

    }

    object System.Collections.IEnumerator.Current
    {
        get { return Current; }
    }
}
票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/32857

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档