Now that you have the basic idea behind iterators under your belt, you can already answer some questions on iterator usage. Here’s a scenario based on actual events:
I have an iterator that is rather long and complicated, so I’d like to refactor it. For illustrative purposes, let’s say that the enumerator counts from 1 to 100 twice. (In real life, of course, the iterator will not be this simple.)
IEnumerable<int> CountTo100Twice() { int i; for (i = 1; i <= 100; i++) { yield return i; } for (i = 1; i <= 100; i++) { yield return i; } }As we learned in Programming 101, we can pull common code into a subroutine and call the subroutine. But when I do this, I get a compiler error:
IEnumerable<int> CountTo100Twice() { CountTo100(); CountTo100(); }void CountTo100() { int i; for (i = 1; i <= 100; i++) { yield return i; } }
What am I doing wrong? How can I move the “count to 100” into a subroutine and call it twice from the
CountTo100Twice
function?
As we saw last time,
iterators are not coroutines.
The technique above would have worked great
had we
built iterators out of, say, fibers
instead of building them out of state machines.
As state machines, all yield return
statements
must occur at the “top level”.
So how do you iterate with the help of subroutines?
You make the subroutine its own iterator and suck the results out from the main function:
IEnumerable<int> CountTo100Twice() { foreach (int i in CountTo100()) yield return i; foreach (int i in CountTo100()) yield return i; }IEnumerable<int> CountTo100() { for (i = 1; i <= 100; i++) { yield return i; } }
Exercise: Consider the following fragment:
foreach (int i in CountTo100Twice()) { … }
Explain what happens on the 150th call to
MoveNext()
in the above loop.
Discuss its consequences for recursive enumerators
(such as tree traversal).
0 comments