Reputation: 7455
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>
?
Upvotes: 27
Views: 3512
Reputation: 157116
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 return
s.
Upvotes: 33
Reputation: 1561
It's a generator. I can't say how the compilator works behind, but when you do:
yield return x;
You will not leave the function like the classic return, instead you will return a value and then continue the execution of the function.
If I remember well, there is a little difference between a real enumerable and this one, if you don't use a method to transform your generator into a true enumerable (depending on what you are trying to accomplish) you can have some troubles.
Upvotes: 6
Reputation: 62532
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.
Upvotes: 13