tags:

views:

157

answers:

3

I am watching Jon Skeet's Copenhagen C# talk videos and I ended up with this code.
QUESTION:
What is happening after the code prints Finished. I mean why is iterator.MoveNext() failing?

CODE:

 class IteratorBlocks
    {
        public static IEnumerable<string> GetStringsForever()
        {
            string current = "";
            char nextChar = 'a';
            try
            {
                while (true)
                {
                    current += nextChar;
                    nextChar++;

                    if (nextChar > 'z')
                    {
                        nextChar = 'a';
                    }

                    yield return current;
                }
            }
            finally
            {
                Console.WriteLine("Finished");
            }
        }
    }

    class Program
    {

        static void Main(string[] args)
        {
            IEnumerable<string> strings = IteratorBlocks.GetStringsForever();

            IEnumerator<string> iterator = strings.GetEnumerator();

            for (int i = 0; i < 10; i++)
            {
                iterator.MoveNext();
                Console.WriteLine(iterator.Current);
            }

            /* 
               I am not able to get what the code is doing beyond this line?
             */

            iterator.Dispose();

            for (int i = 0; i < 10; i++)
            {
                iterator.MoveNext();
                Console.WriteLine(iterator.Current);
            }

        }
    }

OUTPUT:

a
ab
abc
abcd
abcde
abcdef
abcdefg
abcdefgh
abcdefghi
abcdefghij
Finished
abcdefghij
abcdefghij
abcdefghij
abcdefghij
abcdefghij
abcdefghij
abcdefghij
abcdefghij
abcdefghij
abcdefghij
+3  A: 

Calling MoveNext() is just going to be returning false without doing anything else, as you've disposed of the iterator. The state machine built by the C# compiler will go into the "after" state, and stay there. See section 10.14.4.2 of the C# 3 spec for details.

The Current property will continue to return the last value it returned - the behaviour in this situation is explicitly undefined in MSDN. (I could have sworn it was meant to throw an exception, but apparently not.)

Does that make sense? Dispose doesn't "reset" the iterator (and the Reset method itself isn't supported by C# iterator blocks). If you want to iterate again, you need to call GetEnumerator again.

Now, I can't remember exactly what I said in the Copenhagen talk, so apologies if any of this appears to go against what the video shows :)

Jon Skeet
@Jon Don't worry Jon you said nothing wrong in the talk :-). Actually I accidently copy-pasted the second for loop and then ran into the trouble. Thanks (+1A).
TheMachineCharmer
@Jon 'Dispose doesn't "reset" the iterator' but what does it do then in this case?
TheMachineCharmer
@david: It just makes it stop - it puts it in the "after" state, at which point further calls to MoveNext() will just return false without doing anything else.
Jon Skeet
A: 

When you use .NET's iterator pattern with yield return you are getting a little state machine built for you. Calling dispose moves that state machine to it's final state. In that final state the iterator will no longer move forward but it will remember it's last state (Current). If you look at the generated IL (or maybe through Reflector, haven't tried) it becomes pretty clear.

Mike Two
A: 

Strange things happen if you keep on working with a disposed object!

Mongus Pong
That depends on the object. Dispose() *can* be used to provide "reset" behaviour. In the case of MemoryStream, you can still get at the data using ToArray after disposal, which can be useful.
Jon Skeet
Is that an accidental language feature? Or is it something that is going to continue working through future versions of .net?
Mongus Pong