23

IEnumerator contains MoveNext(), Reset() and Current as its members. Now assume that I have moved these methods and property to IEnumerable interface and removed GetEnumerator() method and IEnumerator interface.

Now, the object of the class which implements IEnumerable will be able to access the methods and the property and hence can be iterated upon.

  • Why was the above approach not followed and the problems I will face if I follow it?
  • How does presence of IEnumerator interface solve those problems?
zizouraj
  • 473
  • 2
  • 11
  • quick thought; it is mainly used for foreach loop - compiler stuff!! – codebased Sep 09 '14 at 11:53
  • 6
    GetEnumerator returns a new enumerator so that multiple enumerators can navigate independent through a collection. With your solution is this not possible. – Jehof Sep 09 '14 at 11:54
  • 15
    @flindeberg There is no homework tag. He isn't doing anything improperly. – Servy Sep 09 '14 at 14:49
  • 2
    @flindeberg If someone wants to explain that a problem is a homework problem they're certainly welcome to. It's not required, and a tag is strictly an incorrect way to indicate that a question is a homework problem. – Servy Sep 09 '14 at 14:57
  • @flindeberg I thought we got over this a long time ago... homework questions shouldn't be treated any differently, especially not with a meta tag that was nuked from orbit years ago. – tckmn Sep 09 '14 at 17:52

5 Answers5

50

An iterator contains separate state to the collection: it contains a cursor for where you are within the collection. As such, there has to be a separate object to represent that extra state, a way to get that object, and operations on that object - hence IEnumerator (and IEnumerator<T>), GetEnumerator(), and the iterator members.

Imagine if we didn't have the separate state, and then we wrote:

var list = new List<int> { 1, 2, 3 };

foreach (var x in list)
{
    foreach (var y in list)
    {
         Console.WriteLine("{0} {1}", x, y);
    }
}

That should print "1 1", "1 2", "1 3", "2 1" etc... but without any extra state, how could it "know" the two different positions of the two loops?

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
25

Now assume that I have moved these methods and property to IEnumerable interface and removed GetEnumerator() method and IEnumerator interface.

Such a design would prevent concurrent enumeration at the collection. If it was the collection itself that tracked the current position, you couldn't have several threads enumerating the same collection, or even nested enumerations such as this:

foreach (var x in collection)
{
    foreach (var y in collection)
    {
        Console.WriteLine("{0} {1}", x, y);
    }
}

By delegating the responsibility of tracking the current position to a different object (the enumerator), it makes each enumeration of the collection independent from the others

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
8

A bit of a long answer, the two previous answers cover most of it, but I found some aspects I found interesting when looking up foreach in the C# language specification. Unless you are interested in that, stop reading.

Now over to the intering part, according to C# spec expansion of the following statements:

foreach (V v in x) embedded-statement

Gves you:

{`
 E e = ((C)(x)).GetEnumerator();
 try {
    while (e.MoveNext()) {
        V v = (V)(T)e.Current;
        embedded-statement
    }
}
 finally {
    … // Dispose e
 }
}

Having some kind of identity function which follows x == ((C)(x)).GetEnumerator() (it is it's own enumerator) and using @JonSkeet's loops produces something like this (removed try/catch for brevity):

var list = new List<int> { 1, 2, 3 };

while (list.MoveNext()) {
 int x = list.Current; // x is always 1
 while (list.MoveNext()) {
   int y = list.Current; // y becomes 2, then 3
   Console.WriteLine("{0} {1}", x, y);
 }
}

Will print something along the lines of:

1 2
1 3

And then list.MoveNext() will return false forever. Which makes a very important point, if you look at this:

var list = new List<int> { 1, 2, 3 };

// loop
foreach (var x in list) Console.WriteLine(x); // Will print 1,2,3
// loop again
// Will never enter loop, since reset wasn't called and MoveNext() returns false
foreach (var y in list) Console.WriteLine(x); // Print nothing

So with the above in mind, and note that it is totally doable since the foreach statement looks for a GetEnumerator()-method before it checks whether or not the type implements IEnumerable<T>:

Why was the above approach not followed and the problems I will face if I follow it?

You cannot nest loops, nor can you use the foreach statement to access the same collection more than once without calling Reset() manually between them. Also what happens when we dispose of the enumerator after each foreach?

How does presence of IEnumerator interface solves those problems?

All iterations are independent of each other, whether we are talking nesting, multiple threads etc, the enumeration is separate from the collection itself. You can think of it as a bit like separations of concerns, or SoC, since the idea is to separate the traversal from the actual list itself and that the traversal under no circumstances should alter the state of the collection. IE a call to MoveNext() with your example would modify the collection.

flindeberg
  • 4,887
  • 1
  • 24
  • 37
3

Others have already answered your question, but I want to add another little detail.

Let's decompile System.Collection.Generics.List(Of T). Its definition looks like this:

public class List<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable

and its enumerator definition looks like this:

public struct Enumerator : IEnumerator<T>, IDisposable, IEnumerator

As you can see, List itself is a class, but its Enumerator is a struct, and this design helps boost performance. Lets assume that you do not have a separation between IEnumerable and IEnumerator. In this situation you are forced to make List a struct, but this is not a very good idea, so you cannot do that. Therefore, you are loosing a good opportunity to gain some performance boost.

With the separation between IEnumerable and IEnumerator you can implement each interface as you like and use struct for enumerators.

Community
  • 1
  • 1
RX_DID_RX
  • 4,113
  • 4
  • 17
  • 27
  • How does Enumerator being a struct helps boost performance and what happens if it were a class? – zizouraj Sep 10 '14 at 06:10
  • @zizouraj: structs are passed by value, allocated on the stack and don't have to be collected by the GC (although that's actually an implementation detail, but an important one performance wise). Note that in 99% cases you **shouldn't use structs** (especially not mutable ones), and that BCL team allowed themselves this microoptimization just because this is a core collection intended to be used a lot. – vgru Sep 10 '14 at 09:03
  • @Groo: Using a struct to implement `IEnumerator` won't improve performance in cases where code uses `IEnumerable.GetEnumerator()`. Both C# and VB.NET look to see if a collection has a method called `GetEnumerator` which returns something that has a `MoveNext` method and `Current` property; `List` has such a method, separate from its implementation of `IEnumerable.GetEnumerator`. It's too bad VB.NET and C# require the method to be named `GetEnumerator`, since otherwise the ideal behavior would have been to have `GetEnumerator` return a class, but have the method used by... – supercat Sep 10 '14 at 13:34
  • `foreach` return a struct. That ship has long since sailed, however. – supercat Sep 10 '14 at 13:34
2

The iteration logic (foreach) is not bound to IEnumerables or IEnumerator. What you need foreach to work is a method called GetEnumerator in the class that returns an object of class that has MoveNext(), Reset() methods and the Current property. For example the following code works and it will create an endless loop.

In a design perspective the seperation is to ensure that the container(IEnumerable) does not keep any state during and after the completion of the iteration(foreach) operations.

    public class Iterator 
    {
        public bool MoveNext()
        {
            return true;
        }

        public void Reset()
        {

        }

        public object Current { get; private set; }
    }

    public class Tester
    {
        public Iterator GetEnumerator()
        {
            return new Iterator();
        }

        public static void Loop() 
        {
           Tester tester = new Tester();
           foreach (var v in tester)
           {
              Console.WriteLine(v);
           }

        }

    }