Tuesday, May 11, 2010

05/11: Yield Return (break) in C#

 

Give way to the yield keyword!

I ran into this keyword by accident (yield, accident... very funny), and I must say - this accident was a good one!
yield is a small keyword who has gotten used to live by the shadows of the celebrity keywords (static, const, Paris Hilton, Madonna, etc.), and I say – let's change it! Let's give yield what it deserves!

Why am I so thrilled about it?
OK, so let's introduce yield first – yield comes to help us to create an enumerator out of a single method.
For example, this code:

public static IEnumerable YieldTest(int num)

{

for (int i = 0; i < num; i++)

  {

yield return i;

  }

yield break;

}     

static void Main(string[] args)

{

foreach (int num in YieldTest(8))

    {

Console.WriteLine(num);

    }

}

Will result in:

0
1
2
3
4
5
6
7
Press any key to continue . . .

As you could see, yield has 2 possible continuations – return and break:
yield return <something> means that <something> is going to be the next enumerator value.
yield break means that the party is over and so is our enumeration…
This means that yield return does not act as a regular return keyword - it doesn't really end the method's execution. yield return pauses the method execution and the next time you call it (for the next enumeration value), the method will continue to execute from the last yield return call. It sounds a bit confusing I think... Think of it that way - IEnumerator.MoveNext() really moves forward in the method till the next yield return\break statement.

Still asking yourself why am I so thrilled about my new precious keyword?
Well, this is a code saver! and I like code savers!

I can see it saves me code lines on 2 occasions:

1. When developing my very own enumerable object. See the difference in the next 2 samples:
A regular implementation (2 classes, 44 lines):

public class Users :IEnumerable

{

// --- IEnumerable Members ---

public IEnumerator GetEnumerator()

    {

return new UserEnumerator(10);

    }

}

public class UserEnumerator : IEnumerator

{       

private int m_index;

private int m_maxIndex;

private string GetUserName(int index)

    {

return "User #" + index.ToString();

    }       

public UserEnumerator(int maxIndex)

    {

        m_maxIndex = maxIndex;

        Reset();

    }       

// --- IEnumerator Members ---

public object Current

    {

get { return GetUserName(m_index); }

    }

public bool MoveNext()

    {

        m_index++;

return (m_index < m_maxIndex);

    }

public void Reset()

    {

        m_index = -1;

    }       

}

A yield implementation (1 class, 19 lines):

public class UsersYield :IEnumerable

{

private string GetUserName(int index)

    {

return "User #" + index.ToString();

    }   

// --- IEnumerable Members ---

public IEnumerator GetEnumerator()

    {

for (int i = 0; i < 10; i++)

        {

yield return GetUserName(i);

        }

yield break;

    }

}

2. When developing a method that returns a list and I know that all I'm gonna do is loop over it. Here, the amount of code it saves is not as big as the in the previous bullet, but the code makes more sense, which is even better! For example:

private IEnumerable GetDaysOfWeek()

{

yield return "Sunday";

yield return "Monday";

yield return "Tuesday";

yield return "Wednesday";

yield return "Thursday";

yield return "Friday";

yield return "Saturday";

yield break;

}

public void LoopOverDaysOfWeek()

{

foreach (string day in GetDaysOfWeek())

    {

// do something               

    }

}

Did you notice in the last 2 sample codes that yield can return IEnumerator and IEnumerable? The fun just never stops!

So the yield keyword is indeed very helpful BUT, and it's a big but as you can see, be careful with it!
For instance, one can go and write the following code:

public IEnumerable BadYieldUsage()

{

int i = 100;

while (i > 0)

    {

if (i % 2 == 0)

        {           

if (i > 12)

            {

yield return "Even";

            }

else

            {

yield break;

            }

        }

else

        {

yield return "Odd";

        }

        i--;

    }

}

I want to promise you – if you write a similar code block and actually use it, the end of the world, without any doubt, will come.
Keep the enumeration code as simple as you can because debugging it afterwards will turn you into a suicidal human being.

In conclusion, the yield keyword on the one hand, will save you lines of code, make it more readable in some cases and eventually make you happier. On the other hand, overusing it might kill you.

0 Comments:

Post a Comment

<< Home