The wonders of the yield keyword

Well, since I was thinking about creating a custom enumerator, I noticed this yield behavior

Say you have something like this:

public class EnumeratorExample { public static IEnumerable<int> GetSource(int startPoint) { int[] values = new int[]{1,2,3,4,5,6,7}; Contract.Invariant(startPoint < values.Length); bool keepSearching = true; int index = startPoint; while(keepSearching) { yield return values[index]; //The mind reels here index ++ keepSearching = index < values.Length; } } } 

What makes it possible under the compiler's hood to execute index ++ and the rest of the code in a while loop after you technically return from the function?

+6
yield c # enumeration yield-return
source share
5 answers

The compiler rewrites the code in the state machine. The only method you wrote is divided into different parts. Each time you call MoveNext (either implicitly or explicitly), the state advances and the correct code block is executed.

Recommended reading if you want to know more details:

+9
source share

The compiler generates the final machine on your behalf.

From the language specification:

10.14 Iterators

10.14.4 Enumerator Objects

When a member of a function returns an enumerator interface type implemented using an iterator block, calling the function element does not immediately execute code in the iterator block. Instead, an enumerator object is created and returned. This object encapsulates the specified code in the iterator block and code execution in the iterator block occurs when enumerator objects MoveNext method is called. The enumerator object has the following characteristics:

β€’ It implements IEnumerator and IEnumerator, where T is the output type of the iterator.

β€’ It implements System.IDisposable.

β€’ It is initialized with a copy of the argument value (if any) and an instance of the value passed to the function member.

β€’ It has four potential states, before, running, paused and after, and is initially in the before state.

An enumerator object is usually an instance of an compiler-generated enumeration class that encapsulates code in an iterator block and implements enumerator interfaces, but other implementation methods are possible. If the class of the enumerator is generated by the compiler, that the class will be nested, directly or indirectly, in the class containing the member of the function, it will have private access, and this will have a name reserved for use by the compiler (Β§2.4.2).

To understand this, here is how Reflector decompiles your class:

 public class EnumeratorExample { // Methods public static IEnumerable<int> GetSource(int startPoint) { return new <GetSource>d__0(-2) { <>3__startPoint = startPoint }; } // Nested Types [CompilerGenerated] private sealed class <GetSource>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable { // Fields private int <>1__state; private int <>2__current; public int <>3__startPoint; private int <>l__initialThreadId; public int <index>5__3; public bool <keepSearching>5__2; public int[] <values>5__1; public int startPoint; // Methods [DebuggerHidden] public <GetSource>d__0(int <>1__state) { this.<>1__state = <>1__state; this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId; } private bool MoveNext() { switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<values>5__1 = new int[] { 1, 2, 3, 4, 5, 6, 7 }; this.<keepSearching>5__2 = true; this.<index>5__3 = this.startPoint; while (this.<keepSearching>5__2) { this.<>2__current = this.<values>5__1[this.<index>5__3]; this.<>1__state = 1; return true; Label_0073: this.<>1__state = -1; this.<index>5__3++; this.<keepSearching>5__2 = this.<index>5__3 < this.<values>5__1.Length; } break; case 1: goto Label_0073; } return false; } [DebuggerHidden] IEnumerator<int> IEnumerable<int>.GetEnumerator() { EnumeratorExample.<GetSource>d__0 d__; if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2)) { this.<>1__state = 0; d__ = this; } else { d__ = new EnumeratorExample.<GetSource>d__0(0); } d__.startPoint = this.<>3__startPoint; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } void IDisposable.Dispose() { } // Properties int IEnumerator<int>.Current { [DebuggerHidden] get { return this.<>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return this.<>2__current; } } } } 
+4
source share

Profitability is magic.

Well, not quite. The compiler generates a full class to generate the enumeration that you do. It is mainly sugar to make your life easier.

Read this for an introduction.

EDIT: Wrong. The link has changed, check again if you have one.

+2
source share

This is one of the most difficult parts of the C # compiler. It’s best to read the chapter on the free Jon Skeet C # sample in depth (or better, get a book and read it :-)

Implementing iterators is an easy way

For further explanation see Marc Gravell here:

Can someone demystify the yield keyword?

+2
source share

All Articles