25

I am checking out the code in the reflector, but I haven't yet found out how it can enumerate through a collection backwards?

Since there is no count information, and enumeration always starts from the "start" of the collection, right?

Is it a drawback in the .NET framework? Is the cost higher than regular enumeration?

GEOCHET
  • 21,119
  • 15
  • 74
  • 98
Joan Venge
  • 315,713
  • 212
  • 479
  • 689
  • There's no IEnumerable.Reverse method that I've ever seen (and MSDN seems to support this)! – Noldorin Jun 26 '09 at 22:50
  • 8
    That's because it's an extension method: http://msdn.microsoft.com/en-us/library/bb358497.aspx – Joel Mueller Jun 26 '09 at 22:52
  • 2
    @Noldorin: Enumerable.Reverse is an extension method on IEunumerable – Marc Gravell Jun 26 '09 at 22:52
  • Also see http://stackoverflow.com/questions/12390971/why-there-is-two-completely-different-version-of-reverse-for-list-and-ienumerabl. And http://stackoverflow.com/questions/9337284/does-system-linq-enumerable-reverse-copy-all-elements-internally-to-an-array for a question on recent development on this. – nawfal May 30 '13 at 14:04

4 Answers4

50

In short, it buffers everything and then walks through it backwards. Not efficient, but then, neither is OrderBy from that perspective.

In LINQ-to-Objects, there are buffering operations (Reverse, OrderBy, GroupBy, etc) and non-buffering operations (Where, Take, Skip, etc).


As an example of a non-buffering Reverse implementation using IList<T>, consider:

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

Note that this is still a little susceptible to bugs if you mutate the list while iterating it... so don't do that ;-p

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thanks Marc. By buffering, you mean it copies the entire collection, like Levi says? – Joan Venge Jun 26 '09 at 22:54
  • Thanks Marc. Out of curiosity do you also know if a better enumeration could be done, with a new interface? I always thought IEnumerable was good (and it is), but would one be able to design one that would perform better in these cases too? – Joan Venge Jun 26 '09 at 23:10
  • 1
    @Joan - IList is an example of an interface that allows efficient reverse iteration, because it allows _any_ access pattern to be efficient. – Daniel Earwicker Jun 27 '09 at 15:02
  • 1
    @Earwicker - well, it simply provides access to an indexer and a count - whether it is efficient or not depends on the specific implementation. But the common *assumption* is that an IList[] provides fairly efficient access via the indexer. – Marc Gravell Jun 27 '09 at 16:38
  • @JoanVenge Alternative implementation would do(on original IEnumerable) Count(), Reset() and MoveNext() till index. – Arek Bal Dec 27 '14 at 01:52
  • Can I make this work with a TreeNodeCollection ? `IEnumerable Reverse(this TreeNodeCollection list)` doesn't works for me... – Jack Mar 09 '15 at 19:35
  • go it, should be `IEnumerable Reverse(this TreeNodeCollection list)` – Jack Mar 09 '15 at 19:36
  • I found that this `Reverse()` extension has a different return type then the return type of `List`'s `Reverse()` so it didn't compile. – Gerard Sep 19 '17 at 14:40
6

It works by copying the underlying IEnumerable<T> to an array, then enumerating over that array backward. If the underlying IEnumerable<T> implements ICollection<T> (like T[], List<T>, etc.), then the copy step is skipped and the enumerator just iterates over the underlying collection directly.

For more information, check out System.Linq.Buffer<TElement> in Reflector.

Edit: The underlying collection is always copied, even if it's an ICollection<TElement>. This prevents changes in the underlying collection from being propagated by the Buffer<TElement>.

Levi
  • 32,628
  • 3
  • 87
  • 88
  • I'm looking at the Buffer ctor, and I can't see any time when it skips the copy step - care to elaborate? – Marc Gravell Jun 26 '09 at 22:56
  • @Marc, @Levi: It still makes a copy, but does it using the ICollection.CopyTo method rather than enumerating the sequence. – LukeH Jun 26 '09 at 23:02
3

it loads all items to memory and then steps through them (backwards). this is far less efficient.

Matt Lacey
  • 65,560
  • 11
  • 91
  • 143
-3

Edit: Opps, wrote wrong test for reverse, my apology for wrong answer. It does buffer after correcting test(using enumerable returned by Reverse())

Looks like Reverse extension method only works when collection is populated. While using yield return it does not do anything.

Ran into problem using reverse thought it must buffer for it to work, found it does not work with yield. It just go pass it and don't do anything. below is my test code.

        [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 do not call GetSeries function at all, all buffer talks in this forum looks from thin air.

mamu
  • 12,184
  • 19
  • 69
  • 92
  • 1
    The Reverse extension method does not actually reverse the underlying collection. Rather, it produces a new enumerable that will enumerate the collection in reverse order. Your line that looks like this `series.Reverse();` has not effect. If you change the line to look like this `var reversed = series.Reverse();` and then iterate over `reversed`, tnen you will get the correct answer. – wageoghe Oct 31 '11 at 13:41