Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSharpNotesForProfessionals.pdf
Скачиваний:
57
Добавлен:
20.05.2023
Размер:
6.12 Mб
Скачать

"Starting iteration" is printed first even though the iterator method was called before the line printing it because the line Integers().Take(3); does not actually starts iteration (no call to IEnumerator.MoveNext() was made)

The lines printing to console alternate between the one inside the iterator method and the one inside the foreach, rather than all the ones inside the iterator method evaluating first

This program terminates due to the .Take() method, even though the iterator method has a while true which it never breaks out of.

Section 120.6: Try...finally

If an iterator method has a yield inside a try...finally, then the returned IEnumerator will execute the finally statement when Dispose is called on it, as long as the current point of evaluation is inside the try block.

Given the function:

private IEnumerable<int> Numbers()

{

yield return 1; try

{

yield return 2; yield return 3;

}

finally

{

Console.WriteLine("Finally executed");

}

}

When calling:

private void DisposeOutsideTry()

{

var enumerator = Numbers().GetEnumerator();

enumerator.MoveNext(); Console.WriteLine(enumerator.Current); enumerator.Dispose();

}

Then it prints:

1

View Demo

When calling:

private void DisposeInsideTry()

{

var enumerator = Numbers().GetEnumerator();

enumerator.MoveNext(); Console.WriteLine(enumerator.Current); enumerator.MoveNext(); Console.WriteLine(enumerator.Current);

GoalKicker.com – C# Notes for Professionals

609

enumerator.Dispose();

}

Then it prints:

1

2

Finally executed

View Demo

Section 120.7: Eager evaluation

The yield keyword allows lazy-evaluation of the collection. Forcibly loading the whole collection into memory is called eager evaluation.

The following code shows this:

IEnumerable<int> myMethod()

{

for(int i=0; i <= 8675309; i++)

{

yield return i;

}

}

...

// define the iterator

var it = myMethod.Take(3);

//force its immediate evaluation

//list will contain 0, 1, 2

var list = it.ToList();

Calling ToList, ToDictionary or ToArray will force the immediate evaluation of the enumeration, retrieving all the

elements into a collection.

Section 120.8: Using yield to create an IEnumerator<T> when implementing IEnumerable<T>

The IEnumerable<T> interface has a single method, GetEnumerator(), which returns an IEnumerator<T>.

While the yield keyword can be used to directly create an IEnumerable<T>, it can also be used in exactly the same way to create an IEnumerator<T>. The only thing that changes is the return type of the method.

This can be useful if we want to create our own class which implements IEnumerable<T>:

public class PrintingEnumerable<T> : IEnumerable<T>

{

private IEnumerable<T> _wrapped;

public PrintingEnumerable(IEnumerable<T> wrapped)

{

_wrapped = wrapped;

}

// This method returns an IEnumerator<T>, rather than an IEnumerable<T>

GoalKicker.com – C# Notes for Professionals

610

// But the yield syntax and usage is identical. public IEnumerator<T> GetEnumerator()

{

foreach(var item in _wrapped)

{

Console.WriteLine("Yielding: " + item); yield return item;

}

}

IEnumerator IEnumerable.GetEnumerator()

{

return GetEnumerator();

}

}

(Note that this particular example is just illustrative, and could be more cleanly implemented with a single iterator method returning an IEnumerable<T>.)

Section 120.9: Lazy Evaluation Example: Fibonacci Numbers

using System;

using System.Collections.Generic; using System.Linq;

using System.Numerics; // also add reference to System.Numberics

namespace ConsoleApplication33

{

class Program

{

private static IEnumerable<BigInteger> Fibonacci()

{

BigInteger prev = 0; BigInteger current = 1; while (true)

{

yield return current;

var next = prev + current; prev = current;

current = next;

}

}

static void Main()

{

// print Fibonacci numbers from 10001 to 10010

var numbers = Fibonacci().Skip(10000).Take(10).ToArray(); Console.WriteLine(string.Join(Environment.NewLine, numbers));

}

}

}

How it works under the hood (I recommend to decompile resulting .exe file in IL Disaambler tool):

1.C# compiler generates a class implementing IEnumerable<BigInteger> and IEnumerator<BigInteger>

(<Fibonacci>d__0 in ildasm).

2.This class implements a state machine. State consists of current position in method and values of local variables.

3.The most interesting code are in bool IEnumerator.MoveNext() method. Basically, what MoveNext() do:

GoalKicker.com – C# Notes for Professionals

611

Restores current state. Variables like prev and current become fields in our class (<current>5__2 and <prev>5__1 in ildasm). In our method we have two positions (<>1__state): first at the opening curly brace, second at yield return.

Executes code until next yield return or yield break/}.

For yield return resulting value is saved, so Current property can return it. true is returned. At this point current state is saved again for the next MoveNext invocation.

For yield break/} method just returns false meaning iteration is done.

Also note, that 10001th number is 468 bytes long. State machine only saves current and prev variables as fields. While if we would like to save all numbers in the sequence from the first to the 10000th, the consumed memory size will be over 4 megabytes. So lazy evaluation, if properly used, can reduce memory footprint in some cases.

Section 120.10: The di erence between break and yield break

Using yield break as opposed to break might not be as obvious as one may think. There are lot of bad examples on the Internet where the usage of the two is interchangeable and doesn't really demonstrate the di erence.

The confusing part is that both of the keywords (or key phrases) make sense only within loops (foreach, while...) So when to choose one over the other?

It's important to realize that once you use the yield keyword in a method you e ectively turn the method into an iterator. The only purpose of the such method is then to iterate over a finite or infinite collection and yield (output) its elements. Once the purpose is fulfilled, there's no reason to continue method's execution. Sometimes, it happens naturally with the last closing bracket of the method }. But sometimes, you want to end the method prematurely. In a normal (non-iterating) method you would use the return keyword. But you can't use return in an iterator, you have to use yield break. In other words, yield break for an iterator is the same as return for a standard method. Whereas, the break statement just terminates the closest loop.

Let's see some examples:

///<summary>

///Yields numbers from 0 to 9

///</summary>

///<returns>{0,1,2,3,4,5,6,7,8,9}</returns> public static IEnumerable<int> YieldBreak()

{

for (int i = 0; ; i++)

{

if (i < 10)

{

// Yields a number yield return i;

}

else

{

//Indicates that the iteration has ended, everything

//from this line on will be ignored

yield break;

}

}

yield return 10; // This will never get executed

}

///<summary>

///Yields numbers from 0 to 10

///</summary>

///<returns>{0,1,2,3,4,5,6,7,8,9,10}</returns>

GoalKicker.com – C# Notes for Professionals

612