在 C# 中实现双向枚举器

发布于 2024-07-11 14:41:00 字数 91 浏览 11 评论 0原文

有没有办法使用yield块来实现可以向后(MoveLast())以及向前移动的IEnumerator

Is there a way to use yield blocks to implement an IEnumerator<T> which can go backward (MoveLast()) as well as forward?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

最丧也最甜 2024-07-18 14:41:00

不是直接来自迭代器块,不是。

但是,调用者始终可以缓冲结果,例如缓冲到 List 中,或者仅调用 Reverse() - 但这并不总是适用。

Not directly from the iterator block, no.

However, the caller can always buffer the results, for example into a List<T>, or just call Reverse() - but this doesn't always apply.

韬韬不绝 2024-07-18 14:41:00

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

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

(任何以某种有损方式生成数据的东西也是如此。想象一个迭代器在每次迭代中为康威的生活返回一个新板 - 有多个板可能都是前一个板,所以要倒退你必须再次记住你已经返回的内容。)

No, the state machine generated by the C# compiler is strictly forward.

It doesn't even make sense to go backwards in many cases. Imagine an iterator reading from a network stream - to go backwards, it would have to remember everything that it had ever read, because it couldn't rewind time and ask the network for the data again.

(Ditto anything that generated data in some lossy way. Imagine an iterator which returned a new board for Conway's Life on each iteration - there are multiple boards which could all have been the previous one, so to go backwards you again have to remember what you've already returned.)

葬﹪忆之殇 2024-07-18 14:41:00

我知道这个线程非常旧,但值得注意的是

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 循环,则可以反转方向。

或者...如果您想以相反方向(不是双向)“foreach”列表,您可以利用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];
}

或者...如果您想通过走很长的路线来反向进行 foreach,您可以实现自己的 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; }
    }
}

I know this thread is super old but it is relevant to note that

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

... is get compiled into:

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

So if you don't mind the "MoveNext" syntax, you could easily implement IEnumerator and add a "MovePrevious". You wouldn't be able to reverse direction if you use "foreach" but you'd be able to reverse direction if using a while loop.

Or... if you want to "foreach" a list in reverse direction (not bidirectional) you could take advantage of the yield statement.

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];
}

Or... if you want to foreach in reverse by going the long route you can implement your own 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; }
    }
}
仅此而已 2024-07-18 14:41:00

C5 集合库 (http://www.itu.dk/research/c5/)
使用向后枚举实现集合和链表。 该项目是开源的,因此您应该能够在那里找到答案。

C5 Collections library (http://www.itu.dk/research/c5/)
implements collections and linked list with backwards enumeration. The project is OpenSource so you should be able to find answer there.

提笔书几行 2024-07-18 14:41:00

不可以。IEnumerator 的局限性之一是它保留当前状态,并且不记得之前的状态。 因此,IEnumerable 是只向前的。

如果您需要保留先前的状态,请将 IEnumerable 读入 List 或 LinkedList 并枚举这些对象。

No. One of the limitations of IEnumerator is that it holds its current state, and it doesn't remember its prior state. As a result, IEnumerable is forward-only.

If you need to hold onto prior states, read the IEnumerable into a List or LinkedList and enumerate through those objects instead.

回忆凄美了谁 2024-07-18 14:41:00

Actually, there seems to be an approach described in Accelerated C# 2008. Unfortunately, two pages are not visible in the preview, and it has to rely on reflection (the results of which can be cached, as usual) but you can get the gist.

荒芜了季节 2024-07-18 14:41:00

不会。使用 yield 会导致IEnumerable 这是单向的。

No. Using yield results in an IEnumerable which is unidirectional.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文