When the compiler sees the yield keyword it will implement a state machine in a nested private class inside the Program class. This nested class will implement IEnumerator. (Before C# had the yield keyword, we needed to do this ourselves)
This is a slightly simplified and more readable version:
private sealed class EnumeratorWithSomeWeirdName : IEnumerator<string>, IEnumerable<string>
{
private string _current;
private int _state = 0;
private List<string> list_;
private List<string>.Enumerator _wrap;
public string Current
{
get { return _current; }
}
object IEnumerator.Current
{
get { return _current; }
}
public bool MoveNext()
{
switch (_state) {
case 0:
_state = -1;
list_ = new List<string>();
list_.Add("1");
list_.Add("2");
list_.Add("3");
list_.Add("4");
list_.Add("5");
_wrap = list_.GetEnumerator();
_state = 1;
break;
case 1:
return false;
case 2:
_state = 1;
break;
default:
return false;
}
if (_wrap.MoveNext()) {
_current = _wrap.Current;
_state = 2;
return true;
}
_state = -1;
return false;
}
IEnumerator<string> GetEnumerator()
{
return new EnumeratorWithSomeWeirdName();
}
IEnumerator IEnumerator.GetEnumerator()
{
return new EnumeratorWithSomeWeirdName();
}
void IDisposable.Dispose()
{
_wrap.Dispose();
}
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
The Y() method will change too. It will simply return an instance of this nested class:
public static IEnumerable<string> Y()
{
return new EnumeratorWithSomeWeirdName();
}
Notice that nothing happens at this point. You are only getting an instance of this class.
Only when you start enumerating (with the foreach loop) the MoveNext() method on the instance will be called. This will yield the items one at a time. (This is important to realize)
The foreach loop is also syntactic sugar; it actually calls GetEnumerator():
using(IEnumerator<string> enumerator = list.GetEnumerator()) {
while (enumerator.MoveNext()) yield return enumerator.Current;
}
If you call ys.GetEnumerator() you can even see that it has a method MoveNext() and a property Current, just like an IEnumerator should.
If your Main method had a line like:
foreach (string s in ys) Console.WriteLine(s);
and you would step through it with the debugger, you would see the debugger jumping back and forth between the Main and Y methods.
It is normally impossible to go in and out of a method like this, but because in reality it is actually a class, this works. (Because string.Join simply enumerates the whole thing, your example would not show this.)
Now, every time you call
Console.WriteLine(string.Join(",", ys));
another foreach loop is called, so another Enumerator is created. This is possible because the inner class also implements IEnumerable (They just thought of everything when they implemented the yield keyword) So there's a lot of compiler magic going on. One line with a yield return turns into an entire class.