Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is yield an enumerable?

Tags:

I was toying around with yield and IEnumerable and I'm now curious why or how the following snippet works:

public class FakeList : IEnumerable<int> {     private int one;     private int two;      public IEnumerator<int> GetEnumerator()     {         yield return one;         yield return two;     }      IEnumerator IEnumerable.GetEnumerator()     {         return GetEnumerator();     } } 

Now how does the compiler turn this:

public IEnumerator<int> GetEnumerator() {     yield return one;     yield return two; } 

into an IEnumerator<int>?

like image 450
Mafii Avatar asked May 31 '16 08:05

Mafii


People also ask

How does yield return work?

When a yield return statement is reached in the iterator method, expression is returned, and the current location in code is retained. Execution is restarted from that location the next time that the iterator function is called.

What is yield break?

It specifies that an iterator has come to an end. You can think of yield break as a return statement which does not return a value. For example, if you define a function as an iterator, the body of the function may look like this: for (int i = 0; i < 5; i++) { yield return i; } Console.

What is the return type of IEnumerable?

IEnumerable has just one method called GetEnumerator. This method returns another type which is an interface that interface is IEnumerator. If we want to implement enumerator logic in any collection class, it needs to implement IEnumerable interface (either generic or non-generic).

Does yield break return null?

"yield break" breaks the Coroutine (it's similar as "return"). "yield return null" means that Unity will wait the next frame to finish the current scope. "yield return new" is similar to "yield return null" but this is used to call another coroutine.


2 Answers

When using yield return, the compiler generates an enumerator class for you. So the actual code which is used is much more complex than just two return statements. The compiler adds all necessary code to return an enumerator for you, which iterates over the results from the yield return.


This is the generated code from your FakeList.GetEnumerator():

using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices;  public class FakeList : IEnumerable<int>, IEnumerable {     private int one;     private int two;      [IteratorStateMachine(typeof(<GetEnumerator>d__2))]     public IEnumerator<int> GetEnumerator()     {         yield return this.one;         yield return this.two;     }      IEnumerator IEnumerable.GetEnumerator() =>          this.GetEnumerator();      [CompilerGenerated]     private sealed class <GetEnumerator>d__2 : IEnumerator<int>, IDisposable, IEnumerator     {         private int <>1__state;         private int <>2__current;         public FakeList <>4__this;          [DebuggerHidden]         public <GetEnumerator>d__2(int <>1__state)         {             this.<>1__state = <>1__state;         }          private bool MoveNext()         {             switch (this.<>1__state)             {                 case 0:                     this.<>1__state = -1;                     this.<>2__current = this.<>4__this.one;                     this.<>1__state = 1;                     return true;                  case 1:                     this.<>1__state = -1;                     this.<>2__current = this.<>4__this.two;                     this.<>1__state = 2;                     return true;                  case 2:                     this.<>1__state = -1;                     return false;             }             return false;         }          [DebuggerHidden]         void IEnumerator.Reset()         {             throw new NotSupportedException();         }          [DebuggerHidden]         void IDisposable.Dispose()         {         }          int IEnumerator<int>.Current =>             this.<>2__current;          object IEnumerator.Current =>             this.<>2__current;     } } 

Do you see the <GetEnumerator>d__2 class? That is generated based on your two yield returns.

like image 84
Patrick Hofman Avatar answered Sep 27 '22 20:09

Patrick Hofman


When the compiler sees yield return or yield break it takes the function and coverts the logic it into a class that implements a state machine. An instance of this class is then returned when the method is called.

C# In Depth has a section on what the code looks like.

like image 34
Sean Avatar answered Sep 27 '22 20:09

Sean