views:

615

answers:

3

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?

+12  A: 

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
Thanks Marc. By buffering, you mean it copies the entire collection, like Levi says?
Joan Venge
Exactly that, yes.
Marc Gravell
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
@Joan - IList is an example of an interface that allows efficient reverse iteration, because it allows _any_ access pattern to be efficient.
Daniel Earwicker
@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[<T>] provides fairly efficient access via the indexer.
Marc Gravell
Thanks guys. .
Joan Venge
+3  A: 

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
I'm looking at the Buffer<T> ctor, and I can't see any time when it skips the copy step - care to elaborate?
Marc Gravell
@Marc, @Levi: It still makes a copy, but does it using the ICollection<T>.CopyTo method rather than enumerating the sequence.
LukeH
+2  A: 

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

Matt Lacey