10

It's been asked a couple of times on SO how you can implement a bidirectional enumerator (here, here). My question is not how (which is trivial for most cases), but why no such type exists in the .NET platform.

public interface IBidirectionalEnumerator<T> : IEnumerator<T>
{
    bool MovePrev();
}

Obviously, there are many collection types which can't implement this, as MoveNext() is destructive or changes the state of the underlying collection. But conversely, many types can implement this trivially (List, IList, LinkedList, array).

Why does no such type exist?

Community
  • 1
  • 1
JSBձոգչ
  • 40,684
  • 18
  • 101
  • 169
  • 4
    There is something very wrong with your collection design if enumerating over it changes its state. – Joren Oct 20 '09 at 18:12
  • Agreed. But there are indeed a few collections that shoul change while enumerating, like a stack or a queue. – Maximilian Mayerl Oct 20 '09 at 18:15
  • 2
    Unless you use IEnumerable wrapped over a streaming type or a system resouce such as a StreamReader or list of files. – Matthew Whited Oct 20 '09 at 18:18
  • 1
    Also, while all collections are enumerations, not all enumerations are collections. – Erik Forbes Oct 20 '09 at 20:42
  • Agreed Matthew, my comment was too short-sighted. Side effects are indeed okay (streams are indeed a good example, and queries are another), but they have to be localized. I think enumerating over something multiple times, even when switching back and forth between enumerators, should be consistent. Otherwise you are doing very unintuitive things. – Joren Oct 20 '09 at 20:45

6 Answers6

4

When you are designing a framework, you have to make decisions about doing stuff at different abstraction levels. The trade-off is that if you choose to expose things at a high abstraction level, you'll achieve generalization at the cost of losing fine grained control over things. If you choose to expose stuff at lower abstraction levels, your concept will cannot be generalized as well but you can control details at a lower level.

It's a design decision. Implementing both will be costly and make framework more bloated and you'll need to support both when adding features. You'll need to preserve backward compatibility in the future. It's not a wise thing to add everything you can think of to the BCL without making sure it has a significant benefit.

Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
3
  • IEnumerator supports the c# foreach statement as well as looping constructs of other languages.
  • There is no statement or common programming idiom that IBidirectionalEnumerator enables.
Amy B
  • 108,202
  • 21
  • 135
  • 185
  • 2
    This is not completely true. C# only requires the `MoveNext` method and the `Current` property to work. It doesn't rely on the interface at all. – Mehrdad Afshari Oct 20 '09 at 18:19
  • 3
    Actually `IEnumerator` isn't needed for `foreach`. As long as your class has a method `GetEnumerator()` that takes no parameters and returns an object which has an `object Current { get; }` property and a `void MoveNext()` method, then `foreach` will work quite happily with it. – Greg Beech Oct 20 '09 at 18:21
  • Greg: Really?! So the IEnumerator is effectively duck-typed? That's a revelation. – JSBձոգչ Oct 20 '09 at 18:23
  • It's not duck-typed. C# simply check if the type implements the interface OR has the needed methods/properties. The IL that is produced for a foreach loop doesn't use IEnumerable anywhere, so no need to duck-type. – Maximilian Mayerl Oct 20 '09 at 18:27
  • 3
    That's what I meant by duck typing. It's compile-time duck typing, true, but it still quacks like a duck. – JSBձոգչ Oct 20 '09 at 18:42
  • I think C++ iterators are a pretty common programming idiom: https://www.cprogramming.com/tutorial/stl/iterators.html – Zar Shardan Jun 23 '18 at 16:15
2

Because either nobody thought about it, or nobody thought that it would be particularly useful or because there was not enough budget or...

It's not really necessary, is it? You can easily implement it yourself. Maybe the BCL team thought it was not worth the pain of implementing, testing, documenting etc. Never underestimate the cost of a feature, it may sound "easy", but it does have costs.

Particulary because a single interface that nobody implements would seem strange, wouldn't it? You would expect List, Array etc. to implement the interface, which is quite a lot of work in the end.

Maximilian Mayerl
  • 11,253
  • 2
  • 33
  • 40
2

Obviously, there are many collection types which can't implement this, as MoveNext() is destructive or changes the state of the underlying collection.

MoveNext() is non-destructive. In fact, if the state of the underlying collection is changed between the time that the IEnumerator was created and the time MoveNext() is called, the call to MoveNext() will fail.

The purpose of IEnumerator is to iterate over all of the items in a collection once, in the collection's native order if the collection has a native order. IEnumerator is not intended as a collection-navigation device, such as one might find in C++.

yfeldblum
  • 65,165
  • 12
  • 129
  • 169
1

Also, what would be the motivation for this? Language support for "backward" iteration?

The iterator pattern doesn't give much weight to the concept of "directionality" of a set of elements. It's a simple pattern for providing a simple interface for iterating over a set.

gn22
  • 2,076
  • 12
  • 15
1

A bigger question is why .Net doesn't implement IReadableByIndex, which would in turn be inherited by IList. Such a type would have added nothing to the work required to produce read-write IList implementations, and would have reduced the work required to produce read-only implementations (which would implement IReadableByIndex, rather than IList).

It's not too useful to ponder such "why"s, however. .Net is what it is. The only way to remedy the situation for .Net 5.0 would be to allow a means of declaring that an interface which implements a read-write property can be deemed to implicitly implement a read-only version (so as to allow IList to inherit a covariant IReadableByIndex without having to add an explicit Get method).

supercat
  • 77,689
  • 9
  • 166
  • 211