IEnumerable<T>.Reverse是如何工作的?

26

我正在检查反射器中的代码,但还没有找到如何使它能够倒序枚举集合的方法?

因为没有计数信息,并且枚举始终从集合的“开始”处开始,对吗?

这是.NET框架的缺点吗?和常规枚举相比成本更高吗?


我从未见过任何IEnumerable<T>.Reverse方法(MSDN似乎也支持这一点)! - Noldorin
8
这是因为它是一个扩展方法: http://msdn.microsoft.com/zh-cn/library/bb358497.aspx - Joel Mueller
2
@Noldorin:Enumerable.Reverse是IEunumerable<T>的扩展方法。 - Marc Gravell
还可以参考以下链接:https://dev59.com/62ct5IYBdhLWcg3wCZMT 和 https://dev59.com/Xmox5IYBdhLWcg3wOx_9,了解此问题的最新发展。 - nawfal
4个回答

51
简而言之,它会缓存所有内容,然后反向遍历。效率不高,但从这个角度来看,OrderBy也不高效。
在LINQ-to-Objects中,有缓存操作(Reverse、OrderBy、GroupBy等)和非缓存操作(Where、Take、Skip等)。
作为使用IList<T>的非缓存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

1
@Joan - IList是一个接口的例子,它允许有效的反向迭代,因为它允许任何访问模式都是高效的。 - Daniel Earwicker
1
@Earwicker - 嗯,它只是提供了对索引器和计数器的访问 - 它是否高效取决于具体实现。但是普遍的假设是,IList[<T>]通过索引器提供相当高效的访问。 - Marc Gravell
@JoanVenge 另一种实现方式是在原始 IEnumerable 上使用 Count()、Reset() 和 MoveNext() 直到索引位置。 - Arek Bal
去吧,应该是 IEnumerable<TreeNode> Reverse(this TreeNodeCollection list) - Jack
我发现这个 Reverse() 扩展方法的返回类型与 ListReverse() 返回类型不同,因此它无法编译。 - Gerard
显示剩余3条评论

6
它的工作原理是将基础的IEnumerable<T>复制到一个数组中,然后向后枚举该数组。如果基础的IEnumerable<T>实现了ICollection<T>(如T[],List<T>等),则跳过复制步骤,枚举器直接迭代基础集合。要了解更多信息,请查看Reflector中的System.Linq.Buffer<TElement>。编辑:即使它是ICollection<TElement>,基础集合也总是被复制。这可以防止Buffer<TElement>传播基础集合中的更改。

我正在查看Buffer<T>构造函数,但我没有看到任何跳过复制步骤的情况 - 能否详细说明一下? - Marc Gravell
@Marc,@Levi:它仍然会复制,但是使用ICollection<T>.CopyTo方法而不是枚举序列来完成。 - LukeH

3
它会将所有项目加载到内存中,然后反向逐个步进。这样效率要低得多。

-3

编辑:糟糕,我写错了反转测试,对于错误答案我深表歉意。经过更正测试(使用由 Reverse() 返回的可枚举对象),它确实会缓冲。

看起来 Reverse 扩展方法仅在集合已填充时才起作用。在使用 yield return 时,它不做任何事情。

在使用反转时遇到问题,认为它必须缓冲才能工作,发现它不能与 yield 一起使用。它只是跳过它而不执行任何操作。下面是我的测试代码。

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

不要反向调用GetSeries函数,所有在此论坛中的缓冲区似乎都是从空气中出现的。


2
Reverse扩展方法实际上并不会翻转基础集合。相反,它会生成一个新的可枚举对象,以相反的顺序枚举集合。你的代码行看起来像这样 series.Reverse(); 没有任何效果。如果你将该行更改为 var reversed = series.Reverse(); 然后迭代 reversed,那么你将得到正确的答案。 - wageoghe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接