The C# yield keyword is an interesting keyword, because when you use it, you can return an IEnumerable<T> without specifying any concrete implementation explicitly.

Here is a code snippet:



static IEnumerable < int > GetNumbers () { yield return 0 ; yield return 1 ; yield return 2 ; } static void Main ( string [] args ) { var numbers = GetNumbers (); foreach ( var n in numbers ) Console . WriteLine ( n ); }

This is the output:



0 1 2

But how does this work? What does GetNumbers return exactly? Lets dig a little bit deeper:



static void Main ( string [] args ) { var numbers = GetNumbers (); Console . WriteLine ( numbers . GetType (). Name ); }

GetType() returns the type of an object and Name gives us the name of the type.

We will see that it outputs <GetNumbers>d__0 . Strange right? Because in C# you can't use < or > in identifiers. That's because <GetNumbers>d__0 is actually a class that's generated by the compiler at compile time. It represents a state machine that implements GetNumbers() .

To understand it better, you have to see the decompiled C#, which is quite easy with the help of SharpLab:



namespace HelloWorld { internal class Program { [ CompilerGenerated ] private sealed class < GetNumbers > d__0 : IEnumerable < int >, IEnumerable , IEnumerator < int >, IDisposable , IEnumerator { private int <> 1 __state ; private int <> 2 __current ; private int <> l__initialThreadId ; int IEnumerator < int >. Current { [ DebuggerHidden ] get { return <> 2 __current ; } } object IEnumerator . Current { [ DebuggerHidden ] get { return <> 2 __current ; } } [ DebuggerHidden ] public < GetNumbers > d__0 ( int <> 1 __state ) { this .<> 1 __state = <> 1 __state ; <> l__initialThreadId = Environment . CurrentManagedThreadId ; } [ DebuggerHidden ] void IDisposable . Dispose () { } private bool MoveNext () { switch (<> 1 __state ) { default : return false ; case 0 : <> 1 __state = - 1 ; <> 2 __current = 0 ; <> 1 __state = 1 ; return true ; case 1 : <> 1 __state = - 1 ; <> 2 __current = 1 ; <> 1 __state = 2 ; return true ; case 2 : <> 1 __state = - 1 ; <> 2 __current = 2 ; <> 1 __state = 3 ; return true ; case 3 : <> 1 __state = - 1 ; return false ; } } bool IEnumerator . MoveNext () { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this . MoveNext (); } [ DebuggerHidden ] void IEnumerator . Reset () { throw new NotSupportedException (); } [ DebuggerHidden ] IEnumerator < int > IEnumerable < int >. GetEnumerator () { if (<> 1 __state == - 2 && <> l__initialThreadId == Environment . CurrentManagedThreadId ) { <> 1 __state = 0 ; return this ; } return new < GetNumbers > d__0 ( 0 ); } [ DebuggerHidden ] IEnumerator IEnumerable . GetEnumerator () { return System . Collections . Generic . IEnumerable < System . Int32 >. GetEnumerator (); } } [ IteratorStateMachine ( typeof (< GetNumbers > d__0 ))] private static IEnumerable < int > GetNumbers () { return new < GetNumbers > d__0 (- 2 ); } private static void Main ( string [] args ) { Console . WriteLine ( GetNumbers (). GetType (). Name ); } } }

As you can see, there is a hidden <GetNumbers>d__0 class generated by the compiler and GetNumbers() is modified to return a new instance of that class. The most interesting part of <GetNumbers>d__0 is the MoveNext() method in which the compiler translates the logic in GetNumbers() into a state machine.

Roslyn (the C# compiler) generates a lot of code on your behalf, this operation is called lowering. Some other examples are when you use a for each or when you use async/await .

Knowing how the compiler translates your code helps you to understand the code better and it also helps you in troubleshooting. For example, if we have a piece of code like this:



static IEnumerable < int > GetNumbers () { yield return 0 ; throw new Exception (); } static void Main ( string [] args ) { try { var numbers = GetNumbers (); foreach ( var n in numbers ) { Console . WriteLine ( n ); } } catch ( Exception ex ) { Console . WriteLine ( ex ); } }

This will be the output:



0 System.Exception: Exception of type 'System.Exception' was thrown. at HelloWorld.Program.<GetNumbers>d__0.MoveNext() at HelloWorld.Program.Main(String[] args)