Yield返回延迟迭代问题

本文关键字:问题 迭代 延迟 返回 Yield | 更新日期: 2023-09-27 18:24:21

我知道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的顺序,但由于加载被推迟,所以直到我在下一行调用ToArray()时,调用才真正发生,此时迭代器方法中的pageNodeService为null,并引发null引用异常。

ancestors = pageNodeIterator.ancestorsOf(currentNode).Reverse();
return ancestors.ToArray()[1].parent.children;

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

我还想知道为什么pageNodeService在执行时为null。即使执行被推迟,它不应该仍然有值吗?

Yield返回延迟迭代问题

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

然而,我将借此机会指出:

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

因为在第一次调用CCD_ 8之前块中没有任何代码运行。换句话说,如果你这样做:

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 
}

不是一个真正的答案。。。更多的是建议使用一种替代实现来消除递归。太长,无法作为评论发布。

    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);
                }
            }
        }
    }

仔细想想,你可以完全删除堆栈:

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;
    }
}

在这个地方使用yield是否有意义?因为通过调用Reverse,所有的东西都必须缓冲,所以您可以只返回祖先的完整列表。

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

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);
            }
        }
    }
}