tags:

views:

167

answers:

4

When i have a code block

static void Main()
{

  foreach (int i in YieldDemo.SupplyIntegers())
  {
    Console.WriteLine("{0} is consumed by foreach iteration", i);
  }
}


 class YieldDemo
  {
    public static IEnumerable<int> SupplyIntegers()
     {
         yield return 1;
         yield return 2;
          yield return 3;
       }
   }

can i interpret the principle behind yield return as

  1. Main() calls the SupplyIntegers()
  2. |1| |2| |3| are stored in contiguous memory block.Pointer of "IEnumerator" Moves to |1|
  3. Control returns from SupplyInteger() to Main().
  4. Main() prints the value
  5. Pointer Moves to |2|, and so on.

Clarifications :

(1) Normally we will have one valid return statement is allowed inside a function.How does C# treats when multiple yield return ,yield return,... statements are present?

(2) Once the return is encountered there is no way for control again coming back to SupplyIntegers(), in case it is allowed won't the Yield again starts from 1 ? I mean yield return 1?

+15  A: 

Nope - far from it; I'll write a long-hand version for you... it is too grungy!


Note it also helps if you understand that the foreach is actually:

using(var iterator = YieldDemo.SupplyIntegers().GetEnumerator()) {
    int i;
    while(iterator.MoveNext()) {
        i = iterator.Current;
         Console.WriteLine("{0} is consumed by foreach iteration", i);
    }
}


using System;
using System.Collections;
using System.Collections.Generic;
static class Program
{
    static void Main()
    {

        foreach (int i in YieldDemo.SupplyIntegers())
        {
            Console.WriteLine("{0} is consumed by foreach iteration", i);
        }
    }
}

 class YieldDemo
  {

    public static IEnumerable<int> SupplyIntegers()
     {
         return new YieldEnumerable();
       }
    class YieldEnumerable : IEnumerable<int>
    {
        public IEnumerator<int> GetEnumerator()
        {
            return new YieldIterator();
        }
        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
    }
    class YieldIterator : IEnumerator<int>
    {
        private int state = 0;
        private int value;
        public int Current { get { return value; } }
        object IEnumerator.Current { get { return Current; } }
        void IEnumerator.Reset() { throw new NotSupportedException(); }
        void IDisposable.Dispose() { }
        public bool MoveNext()
        {
            switch (state)
            {
                case 0: value = 1; state = 1;  return true;
                case 1: value = 2; state = 2;  return true;
                case 2: value = 3; state = 3; return true;
                default: return false;
            }
        }
    }
}

As you can see, it builds a state machine in the iterator, with the state machine progressed by MoveNext. I've used the pattern with a state field, as you can see how this would work for more complex iterators.

Importantly:

  • any variables in your iterator block become fields on the state machine
  • if you have a finally block (including using), it goes in the Dispose()
  • portions of code leading to a yield return become a case (roughly)
  • yield break becomes a state = -1; return false; (or similar)

The way the C# compiler does this is very complicated, but it makes writing iterators a breeze.

Marc Gravell
Incredible that people are upvoting this before it actually is a helpful answer.
Joren
@Joren: Thats just how good Marc is. ;) Excellent answer Marc!
jrista
It's really simple, when Marc says he's going to post something, you can pretty much bet that it's going to be right.
Kevin
Irresistible explanation and impeccable notes.As a beginner i imbibed enough things from all of you.Thank you everybody fro the care. :)
@Joren: that's an example of the reputation system working well. People know @Marc Gravell from his previous posts, he says he will write it out longhand, that is the a good way to explain it, and 'cos he's doing it that means we don't have to do it.
Hamish Smith
Hi all,please suggest me some beginners level book to enrich my knowledge in C#.
@Hamish - I have a large amount of confidence in those like Marc who I've seen regularly give very good answers, but still I would never vote on an answer based on something else than the specific merit of that answer. But I suppose I shouldn't be talking too much meta here. :)
Joren
@generix: C# in Depth (Manning, Skeet), chapter 6. It is the free sample chapter, and covers iterator blocks. It isn't exactly a beginner C# book (far from it), but you'll struggle to find a better reference on this topic.
Marc Gravell
@Joren is right. I wouldn't have voted for it either. But if I saw (for example) that Jon had a similar "in progress" marker, I wouldn't have spent a lot of effort duplicating it; **that** was my intention.
Marc Gravell
+2  A: 

It's just a syntax sugar, .net generates IEnumerator class for you and implements MoveNext, Current and Reset methods, than generates IEnumarable class GetEnumerator of which returns that IEnumerator, you can see that magic classes by .net reflector or ildasm. by the way magic classes are not so fast always, so it will be better and safe to implement IEnumerable and IEnumerator manually

Also see here

ArsenMkrt
+1  A: 

in short, (while yr waiting for marc's long-hand version) when the compiler sees yield statements, behind the scenes it builds a new instance of a custom class for you that implements an interface called IEnumerator, which has methods Current(), and MoveNext(), and keeps track of where you are currently in the iteration process... In the case above as your example, it would also keep track of the values in the list to be enumerated through.

Charles Bretana
+1  A: 

Simply put, iterator blocks (or methods with yield statements, if you may) are transformed by the compiler into a compiler-generated class. This class implements IEnumerator and the yield statement is transformed into a 'state' for that class.

For instance, this:

yield return 1;
yield return 2;
yield return 3;

might get transformed into something similar to:

switch (state)
{
    case 0: goto LABEL_A;
    case 1: goto LABEL_B;
    case 2: goto LABEL_C;
}
LABEL_A:
    return 1;
LABEL_B:
    return 2;
LABEL_C:
    return 3;

Iterator blocks are can be seen as abstracted state machines. This code will be invoked by IEnumerator's methods.

Bryan Menard