在C#中实现双向枚举器

时间:2009-01-16 16:46:15

标签: c# iterator ienumerator

有没有办法使用yield块来实现IEnumerator<T>,它可以向后(MoveLast())以及向前?

7 个答案:

答案 0 :(得分:5)

不,C#编译器生成的状态机是严格转发的。

在许多情况下倒退甚至没有意义。想象一下从网络流中读取迭代器 - 要向后移动,它必须记住它曾读过的所有内容,因为它无法回放时间并再次向网络询问数据。

(同样以任何有损方式生成数据的任何东西。想象一下迭代器在每次迭代时为Conway的Life返回一个新的板 - 有多个板可能都是之前的一个,所以为了倒退,你必须记住你已经归还的东西。)

答案 1 :(得分:4)

不直接来自迭代器块,没有。

但是,调用者总是可以缓冲结果,例如缓冲到List<T>,或者只调用Reverse() - 但这并不总是适用。

答案 2 :(得分:3)

我知道这个帖子已经超级老了,但需要注意的是

foreach(var item in someCollection)
{
    // Do something
}

...汇编成:

var enumerator = someCollection.GetEnumerator()
while (enumerator.MoveNext())
{
    var item = enumerator.Current;
    // Do something
}

因此,如果您不介意“MoveNext”语法,您可以轻松实现IEnumerator并添加“MovePrevious”。如果使用“foreach”,则无法反转方向,但如果使用while循环,则可以反转方向。

或者......如果你想反向(不是双向)“预先”列表,你可以利用yield语句。

public static IEnumerable<TItem> Get<TItem>(IList<TItem> list)
{
    if (list == null)
        yield break;

    for (int i = list.Count - 1; i > -1; i--)
        yield return list[i];
}

或者......如果你想通过漫长的路线反向前进,你可以实现自己的IEnumerable / IEnumerator

public static class ReverseEnumerable
{
    public static IEnumerable<TItem> Get<TItem>(IList<TItem> list)
    {
        return new ReverseEnumerable<TItem>(list);
    }
}

public struct ReverseEnumerable<TItem> : IEnumerable<TItem>
{
    private readonly IList<TItem> _list;

    public ReverseEnumerable(IList<TItem> list)
    {
        this._list = list;
    }

    public IEnumerator<TItem> GetEnumerator()
    {
        if (this._list == null)
            return Enumerable.Empty<TItem>().GetEnumerator();

        return new ReverseEnumator<TItem>(this._list);
    }

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

public struct ReverseEnumator<TItem> : IEnumerator<TItem>
{
    private readonly IList<TItem> _list;
    private int _currentIndex;

    public ReverseEnumator(IList<TItem> list)
    {
        this._currentIndex = list.Count;
        this._list = list;
    }

    public bool MoveNext()
    {
        if (--this._currentIndex > -1)
            return true;

        return false;
    }

    public void Reset()
    {
        this._currentIndex = -1;
    }

    public void Dispose() { }

    public TItem Current
    {
        get
        {
            if (this._currentIndex < 0)
                return default(TItem);

            if (this._currentIndex >= this._list.Count)
                return default(TItem);

            return this._list[this._currentIndex];
        }
    }

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

答案 3 :(得分:1)

C5收藏库(http://www.itu.dk/research/c5/) 使用向后枚举实现集合和链表。该项目是OpenSource,所以你应该能够在那里找到答案。

答案 4 :(得分:1)

没有。 IEnumerator的一个限制是它保持当前状态,并且它不记得它的先前状态。因此,IEnumerable是仅向前的。

如果你需要保持先前的状态,请将IEnumerable读入List或LinkedList,然后通过这些对象进行枚举。

答案 5 :(得分:1)

实际上,Accelerated C# 2008中似乎有一种方法。不幸的是,在预览中看不到两个页面,它必须依赖于反射(其结果可以像往常一样缓存),但是你可以获得要点。

答案 6 :(得分:0)

没有。使用yield会产生IEnumerable,这是单向的。