【问题标题】:Implementing a bidirectional enumerator in C#在 C# 中实现双向枚举器
【发布时间】:2010-10-01 20:30:13
【问题描述】:

有没有办法使用 yield 块来实现可以向后 (MoveLast()) 和向前的 IEnumerator<T>

【问题讨论】:

  • 我希望一个名为“MoveLast()”的函数移动到枚举数的最后。我想你想要更像“MovePrev()”的东西
  • 另外,你必须实现自己的枚举器。没那么难,恕我直言。

标签: c# iterator ienumerator


【解决方案1】:

我知道这个帖子太老了,但需要注意的是

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

【讨论】:

  • 问题不是问如何向后迭代序列,而是问如何获取先前产生的项目。那是完全不同的。您可以使用 LINQ Reverse 运算符来反转序列。
  • Servy,我的回答明确提供了有关如何检索先前项目的示例。它并没有简单地解释如何向后迭代。我还应该指出,“保留”缓冲了您使用它的整个 IEnumerable。如果可枚举中没有实际项目,则这是必需的。但是对于不是最优的 IList(T)。我的逆序示例不会创建不必要的副本。
  • 不,它不提供获取序列前一项的方法。它没有提供可以从每个位置向前和向后移动的序列,这就是问题所在。您只需提供一个IEnumerable,它会反向迭代一个序列。是的,Reverse 需要缓冲整个序列;您的答案中的代码需要相同的东西,它只需要将其急切地缓冲到IList 中,而不是推迟它。
  • 阅读我回答的第一段。没有代码示例,是的。但它提供了一种前进和后退的方式。谢谢。
  • 您所说的只是您可以使用MovePrevious 方法创建一个类型。当然,您可以使用该方法创建一个类型,问题是您是否可以让一个迭代器块创建一个双向的迭代器。你甚至没有试图在这个答案中以任何方式解决这个问题。
【解决方案2】:

其实Accelerated C# 2008中似乎有描述的方法。不幸的是,预览中看不到两个页面,它必须依赖反射(其结果可以像往常一样被缓存)但您可以了解要点。

【讨论】:

  • 从 LinkedList 创建一个双向迭代器。这很容易。困难在于仅从 IEnumerable 实现一个。
【解决方案3】:

没有。 IEnumerator 的限制之一是它保持其当前状态,并且不记得其先前状态。因此,IEnumerable 是只进的。

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

【讨论】:

    【解决方案4】:

    C5 集合库 (http://www.itu.dk/research/c5/) 使用反向枚举实现集合和链表。该项目是开源的,所以你应该可以在那里找到答案。

    【讨论】:

      【解决方案5】:

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

      但是,调用者始终可以将结果缓冲到 List&lt;T&gt; 中,或者只是调用 Reverse() - 但这并不总是适用。

      【讨论】:

      • 是的,这是显而易见的解决方案。我希望有一个我错过的替代方案。
      • @alexey_r - 不过,您无能为力 - 出于乔恩给出的所有原因。
      【解决方案6】:

      没有。使用 yield 会产生一个单向的 IEnumerable

      【讨论】:

      • 为了完整性:或一个 IEnumerator[]
      【解决方案7】:

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

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

      (以某种有损方式生成数据的任何事物都一样。想象一个迭代器,它在每次迭代中为 Conway's Life 返回一个新板 - 有多个板可能都是 以前的一个,所以要再次返回,你必须记住你已经返回的内容。)

      【讨论】:

      • 显然在很多情况下它没有意义,是的。可惜我们没有得到任何帮助。
      • 我们也没有任何语言支持向后迭代。我不得不说,我不记得我上一次需要这样做是什么时候了。您是否经常遇到这种情况?
      • 嗯,C++ 不仅有双向迭代器,还有随机访问迭代器。大量的算法可以从这些额外的访问模式中受益匪浅。我知道语言机器只需要前向迭代器(IEnumerator),但如果我们让 I BidirectionalEnumerator 使用 MovePrevious() 从 IEnumerator 继承,并且 IRandomAccessEnumerator 使用 MoveTo(index) 方法从它继承,那就太好了。然后我们可以将这些传递给需要集合 + 位置的集合不可知算法。
      • @ZarShardan:我怀疑这种情况下的“迭代器”在 C# 和 C++ 中具有不同的含义。在 C# 中,迭代器是用迭代器块实现的方法或属性,即带有 yield return 语句等的东西。你会如何建议向后运行?
      • @JonSkeet 是的,这正是我的观点。据我了解,IEnumerator 的主要目的是支持 foreach/yield return 机制。而 C++ 迭代器主要用作与容器无关的集合访问模式。 C# 似乎没有这样的功能,但它非常有用,因此来自 C++/C# 开发人员的所有这些问题不断涌现。但这应该不难由开发者自己作为一个小型实用程序库来实现。
      猜你喜欢
      • 2021-04-18
      • 2016-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-08
      • 2011-04-18
      • 2015-05-16
      • 2015-08-22
      相关资源
      最近更新 更多