Yield return deferred iteration problems

不想你离开。 提交于 2019-12-24 15:19:06

问题


I know that yield return takes advantage of lazy loading but I'm wondering if I might be misusing the iterator or quite possibly need a refactor.

My recursive iterator method returns all the ancestors of a given PageNode including the pageNode itself.

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

In my call to ancestorsOf, I'm calling the method and then reversing the order of the returned IEnumerable, but since loading is deferred the call doesn't actually happen until I call ToArray() on the next line and at that point pageNodeService in my iterator method is null and a null reference exception is thrown.

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

So, I'm wondering where I've gone wrong. What would be the proper way to use an iterator in this case, if at all?

I'm also wondering why pageNodeService is null at the time of execution. Even the execution is deferred shouldn't it still hold a value?


回答1:


Not really an answer... more of a suggestion for an alternative implementation that eliminates recursion. Too long to post as a comment.

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

Thinking about it, you could remove the Stack altogether:

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:


I don't know where your bug is, and StackOverflow is not a service for debugging your code; I would solve your problem by running it in the debugger and looking for the bug.

However I will take this opportunity to point out that this:

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

is slightly problematic because none of the code in the block runs until MoveNext is called for the first time. In other words, if you do:

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

which is very surprising to people. Instead write your code like this:

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 
}



回答3:


Does it even make sense to use yield in this place - since by calling Reverse, all the stuff must be buffered anyway so you could instead just return the complete list of ancestors.




回答4:


Add starting node outside this iterator if you need it.

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


来源:https://stackoverflow.com/questions/17814039/yield-return-deferred-iteration-problems

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!