Can someone explain this lazy evaluation code?

So this question just asked SO:

How to handle endless "IEnumerable?"

My sample code is:

public static void Main(string[] args) { foreach (var item in Numbers().Take(10)) Console.WriteLine(item); Console.ReadKey(); } public static IEnumerable<int> Numbers() { int x = 0; while (true) yield return x++; } 

Can someone explain why this is priced lazily? I searched for this code in Reflector and I'm more confused than when I started.

Reflex outputs:

 public static IEnumerable<int> Numbers() { return new <Numbers>d__0(-2); } 

For the number method and, it seems, generated a new type for this expression:

 [DebuggerHidden] public <Numbers>d__0(int <>1__state) { this.<>1__state = <>1__state; this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId; } 

It makes no sense to me. I would suggest that this is an endless loop until I have connected this code and executed it myself.

EDIT . So now I understand that .Take () can tell foreach that the listing ended when it really is not, but should Numbers () not be called, is it whole before it clings to Take ()? The result of Take is what is actually listed, right? But how to execute when Numbers is not fully evaluated?

EDIT2 : So this is just a specific compiler trick performed by the yield keyword?

+6
c # linq lazy-evaluation ienumerable reflector
source share
3 answers

The reason this is not an infinite loop is that you list only 10 times according to using the Linq Take (10) call. Now, if you wrote the code something like this:

 foreach (var item in Numbers()) { } 

Now this is an infinite loop, because your enumerator will always return a new value. The C # compiler takes this code and converts it into a state machine. If your enumerator does not have a security offer to break execution, then the caller must indicate in his example.

The reason code is lazy is also the reason why code works. Essentially, Take returns the first element, then your application consumes, then it takes another, until it accepts 10 elements.

Edit

This actually has nothing to do with adding Take. They are called iterators. The C # compiler performs complex conversion of your code, creating an enumerator from your method. I recommend reading it, but basically (and it may not be 100% accurate), your code will go into the Numbers method, which you could imagine as initializing a state machine.

As soon as your code falls into the return profitability, you basically say that Numbers () stop execution, return this result, and then when they request the next element to be executed on the next line after the return returns.

Eric Lippert has an excellent series on different aspects of iterators

+1
source share

It's connected with:

  • What iEnumerable does when certain methods are called
  • Enumeration Character and Exit Operator

When you list any IEnumerable, the class gives you the next element that it will give you. He does not do anything for all his subjects, he simply gives you the next point. He decides what this subject will be. (For example, some collections are ordered, some not. Some do not guarantee a specific order, but it seems to always return them in the same order you put them in.).

The IEnumerable Take() extension method will be listed 10 times, getting the first 10 elements. You can do Take(100000000) , and that will give you a lot of numbers. But you just do Take(10) . It just queries Numbers() for the next item., 10 times.

Each of these 10 Numbers elements gives the following element. To understand how to do this, you will need to read the Yield instruction. syntactic sugar for something more complex. Profitability is very powerful. (I am a VB developer and very annoyed that I still do not). This is not a function; This is a keyword with certain restrictions. And this makes the definition of an enumerator much easier than otherwise.

Other IEnumerable extension methods always go through each element. Call. AsList will explode. Using it, most LINQ queries will explode it.

+2
source share

Basically, your Numbers () function creates an Enumerator.
If you have reached the end of the enumerator, foreach will check at each iteration, and if not, it will continue. Your primary enumerator will never end, but it does not matter. This is priced lazily. The enumerator will generate live results.
This means that if you write .Take (3), the loop will execute three times. The enumerator will still have some elements "left" in it, but they will not be generated, since at present no method needs them.
If you try to generate all numbers from 0 to infinity, as the function implies, and return them all at once, this program, which uses only 10 of them, will be much slower. That the benefit of lazy appreciation is that which has never been used, is never calculated.

0
source share

All Articles