【问题标题】:How does IEnumerable<T>.Reverse work?IEnumerable<T>.Reverse 如何工作?
【发布时间】:2010-11-06 07:13:49
【问题描述】:

我正在检查反射器中的代码,但我还没有发现它如何反向枚举集合?

由于没有计数信息,枚举总是从集合的“开始”开始,对吧?

这是 .NET 框架中的一个缺点吗?成本比常规枚举高吗?

【问题讨论】:

标签: c# .net linq collections


【解决方案1】:

简而言之,它缓冲所有内容,然后向后遍历。效率不高,但从这个角度来看,OrderBy 也不是。

在 LINQ-to-Objects 中,有缓冲操作(Reverse、OrderBy、GroupBy 等)和非缓冲操作(Where、Take、Skip 等)。


作为使用IList&lt;T&gt; 的非缓冲Reverse 实现的示例,请考虑:

public static IEnumerable<T> Reverse<T>(this IList<T> list) {
    for (int i = list.Count - 1; i >= 0; i--) {
        yield return list[i];
    }
}

请注意,如果您在迭代列表时对其进行变异,这仍然有点容易受到错误的影响......所以不要这样做;-p

【讨论】:

  • 谢谢马克。通过缓冲,你的意思是它复制整个集合,就像 Levi 说的那样?
  • 谢谢马克。出于好奇,您是否还知道是否可以使用新界面进行更好的枚举?我一直认为 IEnumerable 很好(确实如此),但是是否能够设计出在这些情况下也能表现更好的呢?
  • @Joan - IList 是一个允许高效反向迭代的接口示例,因为它允许 any 访问模式高效。
  • @Earwicker - 好吧,它只是提供对索引器和计数的访问 - 它是否有效取决于具体的实现。但常见的假设是 IList[] 通过索引器提供相当有效的访问。
  • @JoanVenge 替代实现(在原始 IEnumerable 上)Count()、Reset() 和 MoveNext() 直到索引。
【解决方案2】:

它的工作原理是将底层 IEnumerable 复制到一个数组,然后向后枚举该数组。 如果底层 IEnumerable 实现了 ICollection(如 T[]、List 等),则跳过复制步骤,枚举器直接遍历底层集合。

有关更多信息,请查看 Reflector 中的 System.Linq.Buffer

编辑:始终复制基础集合,即使它是 ICollection。这可以防止底层集合中的更改被 Buffer 传播。

【讨论】:

  • 我正在查看 Buffer ctor,但我看不到它跳过复制步骤的任何时间 - 需要详细说明吗?
  • @Marc, @Levi:它仍然制作副本,但使用 ICollection.CopyTo 方法而不是枚举序列。
【解决方案3】:

它将所有项目加载到内存中,然后逐步遍历它们(向后)。这效率要低得多。

【讨论】:

    【解决方案4】:

    编辑:Opps,为反向编写了错误的测试,我为错误的答案道歉。它在更正测试后进行缓冲(使用 Reverse() 返回的枚举)

    看起来反向扩展方法仅在填充集合时才有效。在使用 yield return 时,它什么也不做。

    使用反向认为它必须缓冲才能工作时遇到问题,发现它不适用于产量。它只是通过它而不做任何事情。下面是我的测试代码。

            [TestMethod]
        public void loopTest()
        {
            var series = this.GetSeries();
    
            series.Reverse();
    
            foreach (var l in series)
            {
                Debug.WriteLine(l);
            }
        }
    
        private IEnumerable<long> GetSeries()
        {
            var series = new List<long>() { 1, 2, 3, 4 };
    
            foreach (var entry in series)
            {
                Debug.WriteLine(entry);
    
                yield return entry;
            }
        }
    

    reverse 根本不调用 GetSeries 函数,本论坛所有缓冲区讨论都是凭空而来的。

    【讨论】:

    • Reverse 扩展方法实际上并不反转底层集合。相反,它会生成一个新的可枚举对象,它将以相反的顺序枚举集合。您看起来像这样series.Reverse(); 的行无效。如果你把这行改成var reversed = series.Reverse();,然后遍历reversed,你会得到正确的答案。
    猜你喜欢
    • 1970-01-01
    • 2011-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    相关资源
    最近更新 更多