在这篇文章(http://blogs.msdn.com/oldnewthing/archive/2008/08/13/8854601.aspx)上,有一个关于迭代器的流行问题和一个关于角落案例的问题:
练习:请考虑以下内容 片段:foreach(int in in CountTo100Twice()){...}
解释第150次电话会发生什么 上面的循环中的MoveNext()。 讨论它对递归的影响 枚举器(例如树遍历)。
我没有运行此代码,但我假设这是一个问题,应该是文章的答案(下面提供的所有链接),但我找不到文章所分享知识的答案或在这篇特别文章的评论中。
有谁知道答案是什么?还有哪些角落案例?
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
由于
答案 0 :(得分:2)
一些事情。
(1)Jon当然是正确的;问题是像这样的嵌套迭代器给你一个迭代器逻辑的调用堆栈。如果您正在迭代一个深度递归定义的数据结构,这可能会破坏堆栈,并且有很简单的方法可以将应该是线性算法转换为二次算法。有关详细信息,请参阅Wes的文章。
(2)我们可以在没有性能问题的语言中构建一种新的迭代器逻辑。我很乐意实现这一点,但现在它还不够优先。如果您对如何操作的技术细节感兴趣,请阅读this paper。
(3)有很多角落案件;已经提到的(延迟执行边界检查和finally块的延迟执行)是最常见的两种。不幸的是,在许多版本的C#中,代码生成器中存在一些错误,这些错误会加剧后一个问题。假设您已尝试{try {... yield ...} finally {X()}} finally {Y()} - 您可以进入我们生成的代码在X之前意外调用Y()的奇怪情况()在清理路径上,这显然是错误的。我们已经为服务包修好了,但是如果你找到其他人,请告诉我。
(4)还有一些现存的非常模糊的错误涉及迭代器的确切行为,当做一个疯狂的事情,比如一个终止的收益突破,然后分支到一个外部最终,这会产生第二个多余的收益率突破。再说一次,如果您碰巧找到奇异行为的迭代器,请随时引起我的注意。
答案 1 :(得分:1)
我怀疑他可能指的是每次调用MoveNext()
都调用一个状态机,而状态机又调用另一个状态机上的MoveNext()
,使其全部成为有点低效。
这是关于here by Wes Dyer和here by Eric Lippert的博文。
我用迭代器块说的主要情况是在第一次调用MoveNext()
之前执行 nothing 。所以这个方法:
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;
}
}
}
在开始迭代之前,实际上不会抛出异常。相反,你需要写这样的东西:
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;
}
}
}
再一次,Eric在博客上写道:part 1,part 2。我也写了一篇suggestion to make life easier,虽然我怀疑它会发生什么。
答案 2 :(得分:1)
当您考虑try..finally
以及派生using
和lock
语句时,会发生一个有趣的收益/迭代器案例。考虑上面发布的迭代器块Jon Skeet:
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;
}
}
}
如果您在foreach
的上下文之外使用此迭代器块,只需手动调用MoveNext()
几次,并且从不完成迭代,那会发生什么{ {1}}?答案:using
的{{1}}部分永远不会被调用,因此永远不会在finally
上调用using
而永远不会关闭打开的文件。同样,假设Dispose
已替换为TextReader
。永远不会调用using
的{{1}}部分永远不会释放对象的锁定。
课程:始终避免在迭代器块中使用lock(something)
及其衍生物。