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。即使执行被推迟,它不应该仍然有值吗?
我不知道你的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);
}
}
}
}