.net how does yield work
Not to mention that the code looks a little strange. Well, let's take a crack at it. This code is the same, but the names are easier on the eyes, and excessive code structures are eliminated.
Also, the C compiler has no problem understanding this code, in comparison to the code listed earlier. This is the code format I use from now on in the article. If you want to see what this code looks like as-is, grab dotPeek or even better - ildasm and go ahead :. This code creates a special object. The object stores a link to the current item and the maxValue parameter value.
The compiler created the generator class automatically, and all the logic we put into the function is implemented there. Now we can take a look at what this class contains. Nothing unexpected, really Except for IDisposable that came out of nowhere!
Let's figure out what happened. The state field stores the '-2' startState value passed to the generator at the initialization. The initialThreadId field stores the ID of the thread where the object was created.
I'll explain the purpose of these fields later. Now let's take a look at the GetEnumerator implementation:. See how when certain conditions are met, the method returns the same object instead of a new one? This peculiarity might seem quite unexpected. The following code fragment confirms it:. At the GetEnumerator method call, the returned object's state field is assigned to '0'. This is an important step. Take another look at the GetFibonacci method or, to be exact, at what the compiler transformed it into.
I'll clarify the mechanics of this further on in this article. Right now, let's take a look at the MoveNext method:. This method implements all logic we programmed into the GetFibonacci method. This is the value we get when we access the sequence generator's Current property.
When the generator's state field value is '-1', the generator exits - MoveNext does not do anything and returns false. Previously we discussed that when you create a new generator, the '-2' value is recorded to the state field.
But take a look at the code. Essentially, the generator does not work. Luckily, the GetEnumerator method call replaces the -2 state with 0. What about calling MoveNext without calling GetEnumerator? Is this possible? Nevertheless, the returned object implements both IEnumerable and IEnumerator - so you can use type casting. In this case the developer does not need GetEnumerator and can call the generator's MoveNext. However, all calls will return false. Thus, though you may be able to 'cheat' the system, this hardly benefits you in any way.
Casting this object to IEnumerator produces a generator that is useless until the GetEnumerator method is called. At the same time, if a generator seems 'dead', it may suddenly start working after the GetEnumerator method call. The code below demonstrates this behavior:.
What do you think the console will display after the code above is executed? Hint: The code produces the Fibonacci sequence's first five elements - 1, 1, 2, 3, 5. We have just reviewed a case of casting to IEnumerator. Is it possible to play around with casting to IEnumerable? Obviously, an object returned by GetEnumerator 's first call can be cast to IEnumerable and will work as expected.
Take a look at this example:. This code above prints three 'True' entries in the console window, because all three references point to the same object. Here, casting does not bring any surprises, and will produce a link to an existing and, therefore, correctly working object.
What about a different scenario? For example, GetEnumerator is called for the second time or in a different thread - and the value it returns is cast to IEnumerable.
Take a look at this sample yield method:. At a first glance the RepeatLowerString method receives a string as a parameter, converts it to lowercase and returns it indefinitely.
Have you noticed something odd in the code above? The RepeatLowerString method, opposite to what you may expect, generates a sequence of references to the unchanged someString string.
This happens because the ToLower method creates a new string and does not modify the original string. It is not too important in our case, but in real software such mistakes lead to sad consequences and they are worth fighting against.
An incorrect ToLower method call may not seem significant. However, sometimes a function is called incorrectly somewhere in a large pile of code - and that error is almost impossible to track down. If the project is large, its developers often use a static code analyzer. A static code analyzer is an application that can quickly detect many code bugs. For example, a static code analyzer could scan the RepeatLowerString method and find that error I described earlier.
However, the analyzer is definitely not limited to detecting "meaningless calls" - it covers an extensive list of problems. I recommend that you use a static analyzer on your projects. The PVS-Studio tool is a good choice. You can read more about PVS-Studio on its official website and get the analyzer's free trial version. All this masterful formation will crash with NullReferenceException.
Didn't expect this? Maybe not. Buy now we already have enough information to explain this behavior. Let's walk through the example step-by-step. The exception was thrown when magicEnumerator. MoveNext called the ToLower method. ToLower is called for the someString parameter. But where did this value come from? Is that where null came from? Yes it is. But how did null end up in this field? Let's take one more look at the code snippet:. Sadly, no.
So this field gets the default value - that is, that very null. If GetEnumerator returns something other than this , the resulting object cannot fulfill the role of IEnumerable.
This peculiarity does not affect yield methods that do not require any parameters. The GetPositive method returns an ascending sequence of positive numbers, starting with 1. Now take a look at the GetPositive method use example:.
This code works correctly and displays numbers 1 through 5 on the screen. But don't do this. No, really :. When reviewing the generated class, you may have an inevitable question: why this class has two fields to store the parameter value - instead of one. By this time, you may have guessed what is happening here, but just in case, let's take a closer look. This is a simple method that produces an ascending sequence of integers, starting with i that is passed as a parameter.
The created generator's MoveNext method looks something like this:. Look closely. This field's initial value was set at the GetEnumerator method's call. The GetInts yield method we listed earlier returns IEnumerable type objects.
For this type of objects you can call GetEnumerator several times. As we know, at the first call the generator returns itself. Keeping this thought in mind, let's take a look at the following code:. In the first line, GetInts is called, and it returns the enumerable generator. Then we get firstEnumerator. This will be practically the same object as enumerable.
At the GetEnumerator method's call, an IEnumerator type object is returned. Then the MoveNext method is called a couple of times. At the end of the code snippet, the second IEnumerator is acquired. Obviously, the value is the same as the one passed to the GetInts yield method initially. Objects the GetEnumerator method returns, are to an extent independent of each other. To start generating sequences, they use parameters passed at the yield method's call.
This is possible thanks to storing the original parameter in an additional field. Info We reference the System. Collections namespace for the first version, and the System.
Generic namespace for the second. Part A In the part of the foreach-loop following the "in" keyword, there is a method call to ComputePower.
Part B ComputePower receives 2 parameters. It returns an IEnumerable int type, which we can use in a foreach-loop. C program that uses foreach, yield return. The C code you have that uses yield is not directly executed by the runtime. Instead, the C compiler transforms that code before the runtime ever occurs. Tip The compiler-generated class, marked with CompilerGenerated, instead uses several integral types.
Result We see an entire class that is similar to how your code would look if you manually implemented the interfaces. Info The punctuation characters allow the compiler to ensure no naming conflicts will occur with your code. Benchmark, yield. Is yield return fast? By using deferred execution we can make some methods simpler, some faster and some even possible where they were impossible before remember the infinite number generator. Suppose we have products. If the above method did not have deferred execution, it would mean we would:.
While maybe a contrived example, it shows clearly how deferred execution can greatly increase efficiency. Side note: I want to make clear that deferred execution in itself does not make your code faster. Inherently, it has no effect on the speed or efficiency of your code. The value of deferred execution is that it allows you to optimize your code in a clean, readable and maintainable way.
This is an important distinction. The yield-keyword is often misunderstood. Its behavior can seem a bit strange at first sight. Its main use cases are custom and stateful iteration which allow you to create simple yet powerful code.
I hope this article helped explaining the semantics of the yield-keyword and the effects and implications it has on calling code. Feel free to ask any questions in the comments! WriteLine number ; When you debug this using F11, Step Into , you will see how the current line of execution jumps between the foreach-loop and the yield return statements. The entire method gets executed and the list is constructed. The foreach-construct loops over all the values in the list.
The net result is that we get numbers 1 to 5 printed in the console. WriteLine number ; At first sight, we might think that this is a function which returns a list of 5 numbers.
This is what happens when we execute this code: GenerateWithYield is called. This returns an IEnumerable. Each iteration of the foreach loop calls the iterator method. When the yield return statement is reached the value is returned, and the current location in code is retained. The end result is that you get the numbers 1 to 5 printed in the console. Take 5 Console.
WriteLine number ; foreach var number in GenerateWithYield.
0コメント