屈服/迭代器角落情况

时间:2009-05-15 20:26:38

标签: c#

在这篇文章(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

由于

3 个答案:

答案 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 Dyerhere 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 1part 2。我也写了一篇suggestion to make life easier,虽然我怀疑它会发生什么。

答案 2 :(得分:1)

当您考虑try..finally以及派生usinglock语句时,会发生一个有趣的收益/迭代器案例。考虑上面发布的迭代器块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)及其衍生物。