Corner exit / iterator cases
This article ( http://blogs.msdn.com/oldnewthing/archive/2008/08/13/8854601.aspx ) has a pop question about iterators and one regarding the corner case:
Exercise: Consider the following snippet: foreach (int i in CountTo100Twice ()) {...}
Explain what happens on the 150th call to MoveNext () in the above loop. Discuss its implications for recursive enumeration (eg tree traversal).
I am not running this code, but I am assuming this is a question, presumably answered from the articles (all links given below), but I cannot find the answer in the knowledge shared by the article or in the comments on this particular article.
Does anyone know what the answer is? What other corner cases are there?
1) http://blogs.msdn.com/oldnewthing/archive/2008/08/12/8849519.aspx
2) http://blogs.msdn.com/oldnewthing/archive/2008/08/13/8854601.aspx
3) http://blogs.msdn.com/oldnewthing/archive/2008/08/14/8862242.aspx
4) http://blogs.msdn.com/oldnewthing/archive/2008/08/15/8868267.aspx
thanks
a source to share
I suspect it may be referring to the fact that each call MoveNext()
invokes a state machine, which in turn invokes MoveNext()
on another destination computer, making the whole thing a little inefficient.
Here it is reported here Wes Dyer and here Eric Lippert .
The main corner case I would say with iterator blocks is that nothing gets executed until the first call MoveNext()
. So this method:
public IEnumerable<string> ReadLines(string file)
{
if (file == null)
{
throw new ArgumentNullException("file");
}
using (TextReader reader = File.OpenText(file))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
doesn't actually throw an exception until you start iterating. Instead, you need to write something like this:
public static IEnumerable<string> ReadLines(string file)
{
if (file == null)
{
throw new ArgumentNullException("file");
}
return ReadLinesImpl(file);
}
public static IEnumerable<string> ReadLinesImpl(string file)
{
using (TextReader reader = File.OpenText(file))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
Again, Eric has blogged: part 1 , part 2 . I added a suggestion to the blog to make life easier , although I doubt it will ever come.
a source to share
A few things.
(1) John is certainly right; the problem is that nested iterators like this give you the call stack of the iterator logic. If you iterate over a deep, recursively defined data structure, it can lead to stack deletion, and there are easy ways to turn what should be a linear algorithm into a quadratic algorithm. See Wes article for details.
(2) We could build a new type of iterative logic in a language that had no performance issue. I would like to implement this, but it is not a high enough priority right now. If you are interested in technical details on how to do this, read this article .
(3) There are many corner cases; the ones already mentioned (deferred execution of bounds estimates and deferred execution of finally blocks) are the two most common. Unfortunately, many versions of C # have bugs in the code generator that make the latter problem worse. Suppose you tried {try {... yield ...} finally {X ()}} finally {Y ()} - there are strange situations you can get into where the code we generate accidentally calls Y ( ) to X () in the cleanup path, which is clearly wrong. We fixed them for the service pack, but if you find others, please let me know.
(4) There are also some still extremely obscure bugs related to the exact behavior of the iterator when doing crazy things like exiting from the final result, which then goes to the outer final, which makes a second redundant exit. Again, if you do manage to find fancy iterators, feel free to draw them in.
a source to share
One interesting corner case with yield / iterator happens when you consider try..finally
both derived expressions using
and lock
. Consider Jon Skeet's iterator block above:
public IEnumerable<string> ReadLines(string file)
{
if (file == null)
{
throw new ArgumentNullException("file");
}
using (TextReader reader = File.OpenText(file))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
If you use this iterator block out of context foreach
, MoveNext()
manually calling twice , and you never end up iterating, what happens to that using
? Answer. finally
the part is using
never called, so it never calls Dispose
in TextReader
and never closes an open file. Likewise, imagine what using
have been replaced by lock(something)
. The part finally
lock
will never be called, never releasing the object's lock.
Lesson . Always avoid using try..finally
and its derivatives in an iterator block.
a source to share