产量返回延迟迭代问题

时间:2013-07-23 15:07:36

标签: c# recursion iterator yield-return

我知道yield return会利用延迟加载但是我想知道我是否可能滥用迭代器或者很可能需要重构器。

我的递归迭代器方法返回给定PageNode的所有祖先,包括pageNode本身。

public class PageNodeIterator {
    //properties and constructor left out for brevity

    public IEnumerable<IPageNode> ancestorsOf(IPageNode pageNode) {
        if(pageNode == null) throw new ArgumentNullException(("pageNode"));

        if (pageNode.url != pageNodeService.rootUrl) {
            yield return pageNode;
            if (pageNode.parent != null)
                foreach (var node in ancestorsOf(pageNode.parent))
                    yield return node;
        }
    }
}

在我对ancestorsOf的调用中,我正在调用方法,然后反转返回的IEnumerable的顺序,但由于加载是延迟的,因此调用实际上不会发生,直到我调用{{ 1}}在下一行,并且在我的迭代器方法中ToArray()为空,并抛出空引用异常。

pageNodeService

所以,我想知道我哪里出错了。在这种情况下,如果有的话,使用迭代器的正确方法是什么?

我也想知道为什么ancestors = pageNodeIterator.ancestorsOf(currentNode).Reverse(); return ancestors.ToArray()[1].parent.children; 在执行时为空。即使执行被延期,它还不应该保持价值吗?

4 个答案:

答案 0 :(得分:9)

我不知道你的bug在哪里,而StackOverflow不是用于调试代码的服务;我会通过在调试器中运行它并查找错误来解决您的问题。

但是,我将借此机会指出:

public IEnumerable<IPageNode> AncestorsOf(IPageNode pageNode) {
    if(pageNode == null) throw new ArgumentNullException(("pageNode"));
    // Do stuff that yields 

稍有问题,因为在第一次调用MoveNext之前,块中的所有代码都不会运行。换句话说,如果你这样做:

var seq = AncestorsOf(null); // Not thrown here!
using (var enumtor = seq.GetEnumerator())
{
    bool more = enumtor.MoveNext(); // Exception is thrown here!

这对人们来说非常令人惊讶。而是像这样编写代码:

public IEnumerable<IPageNode> AncestorsOf(IPageNode pageNode) {
    if(pageNode == null) throw new ArgumentNullException(("pageNode"));
    return AncestorsOfIterator(pageNode);
}
private IEnumerable<IPageNode> AncestorsOfIterator(IPageNode pageNode)
{
    Debug.Assert(pageNode != null);
    // Do stuff that yields 
}

答案 1 :(得分:3)

不是真正的答案...更多关于消除递归的替代实现的建议。发表评论的时间太长了。

    public IEnumerable<IPageNode> ancestorsOf(IPageNode pageNode) {
        if(pageNode == null) throw new ArgumentNullException(("pageNode"));
        Stack<IPageNode> stack = new Stack<IPageNode>();
        stack.Push(pageNode);
        while(stack.Any())
        {
            IPageNode n=stack.Pop();
            if (n.url != pageNodeService.rootUrl) {
                yield return n;
                if(n.parent != null)
                {
                    stack.Push(n.parent);
                }
            }
        }
    }

考虑一下,你可以完全删除Stack:

public IEnumerable<IPageNode> ancestorsOf(IPageNode pageNode) {
    if(pageNode == null) throw new ArgumentNullException(("pageNode"));
    IPageNode n = pageNode;
    while(n != null && n.url != pageNodeService.rootUrl)
    {
        yield return n;
        n = n.parent;
    }
}

答案 2 :(得分:2)

在这个地方使用yield是否有意义 - 因为通过调用Reverse,all the stuff must be buffered anyway所以你可以只返回完整的祖先列表。

答案 3 :(得分:0)

如果需要,在此迭代器之外添加起始节点。

public class PageNodeIterator {
    //properties and constructor left out for brevity

    public IEnumerable<IPageNode> ancestorsOf(IPageNode pageNode) {
        if(pageNode == null) throw new ArgumentNullException(("pageNode"));

        if (pageNode.url != pageNodeService.rootUrl)
        {
            if (pageNode.parent != null ) 
            {
                yield return pageNode.parent;
                yield return ancestorsOf(pageNode.parent);
            }
        }
    }
}