使用yield的方法不允许调用自己

发布于 2024-12-06 20:23:53 字数 1771 浏览 1 评论 0原文

这很可能是用户错误(我有点希望如此)。我在 C# 中遇到了一个奇怪的情况,如果我尝试在使用 Yield 的方法中进行递归调用,它似乎不会受到尊重(即调用被忽略)。

下面的程序说明了这一点:

// node in an n-ary tree
class Node
{
    public string Name { get; set; }
    public List<Node> ChildNodes { get; set; }
}
class Program
{
    // walk tree returning all names
    static IEnumerable<string> GetAllNames(IEnumerable<Node> nodes)
    {
        foreach (var node in nodes)
        {
            if (node.ChildNodes != null)
            {
                Console.WriteLine("[Debug] entering recursive case");
                // recursive case, yield all child node names
                GetAllNames(node.ChildNodes);
            }
            // yield current name
            yield return node.Name;
        }
    }

    static void Main(string[] args)
    {
        // initalize tree structure
        var tree = new List<Node>
                       {
                           new Node()
                               {
                                   Name = "One",
                                   ChildNodes = new List<Node>()
                                                    {
                                                        new Node() {Name = "Two"},
                                                        new Node() {Name = "Three"},
                                                        new Node() {Name = "Four"},
                                                    }
                               },
                           new Node() {Name = "Five"}
                       };

        // try and get all names
        var names = GetAllNames(tree);

        Console.WriteLine(names.Count());
            // prints 2, I would expect it to print 5

    }
}

This could well be a user error (I'm kinda hoping so). I'm running into a strange case in C# were if I try to make recursive call in a method that uses yield it doesn't seem to be respected (i.e. the call is ignored).

The following program illustrates this:

// node in an n-ary tree
class Node
{
    public string Name { get; set; }
    public List<Node> ChildNodes { get; set; }
}
class Program
{
    // walk tree returning all names
    static IEnumerable<string> GetAllNames(IEnumerable<Node> nodes)
    {
        foreach (var node in nodes)
        {
            if (node.ChildNodes != null)
            {
                Console.WriteLine("[Debug] entering recursive case");
                // recursive case, yield all child node names
                GetAllNames(node.ChildNodes);
            }
            // yield current name
            yield return node.Name;
        }
    }

    static void Main(string[] args)
    {
        // initalize tree structure
        var tree = new List<Node>
                       {
                           new Node()
                               {
                                   Name = "One",
                                   ChildNodes = new List<Node>()
                                                    {
                                                        new Node() {Name = "Two"},
                                                        new Node() {Name = "Three"},
                                                        new Node() {Name = "Four"},
                                                    }
                               },
                           new Node() {Name = "Five"}
                       };

        // try and get all names
        var names = GetAllNames(tree);

        Console.WriteLine(names.Count());
            // prints 2, I would expect it to print 5

    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

云雾 2024-12-13 20:23:53

您正在拨打电话,但没有执行任何操作。您需要在这里实际使用结果

static IEnumerable<string> GetAllNames(IEnumerable<Node> nodes) {
    foreach (var node in nodes) {
        if (node.ChildNodes != null) {
            foreach (var childNode in GetAllNames(node.ChildNodes)) {
                yield return childNode;
            }
        }
        yield return node.Name;
    }
}

You are making the call but doing nothing with it. You need to actually use the result here

static IEnumerable<string> GetAllNames(IEnumerable<Node> nodes) {
    foreach (var node in nodes) {
        if (node.ChildNodes != null) {
            foreach (var childNode in GetAllNames(node.ChildNodes)) {
                yield return childNode;
            }
        }
        yield return node.Name;
    }
}
北城半夏 2024-12-13 20:23:53

您没有返回递归调用的结果。

您需要 yield return 从调用返回的每个项目:

foreach(var x in GetAllNames(node.ChildNodes))
    yield return x;

You aren't returning the results of the recursive call.

You need to yield return each item returned from the call:

foreach(var x in GetAllNames(node.ChildNodes))
    yield return x;
要走就滚别墨迹 2024-12-13 20:23:53

这是一个非常有趣的问题,可能会导致任意深度的结构占用大量资源。我认为斯基特先生提出了一种“扁平化”技术,但我不记得这个链接了。这是我们根据他的想法使用的代码(它是 IEnumerable 的扩展方法):

public static class IEnumerableExtensions
{
    /// <summary>
    /// Visit each node, then visit any child-list(s) the node maintains
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="subjects">IEnumerable to traverse/></param>
    /// <param name="getChildren">Delegate to get T's direct children</param>
    public static IEnumerable<T> PreOrder<T>(this IEnumerable<T> subjects, Func<T,    IEnumerable<T>> getChildren)
    {
        if (subjects == null)
            yield break;

        // Would a DQueue work better here?
        // A stack could work but we'd have to REVERSE the order of the subjects and children
        var stillToProcess = subjects.ToList();

        while (stillToProcess.Any())
        {
            // First, visit the node
            T item = stillToProcess[0];
            stillToProcess.RemoveAt(0);
            yield return item;

            // Queue up any children
            if (null != getChildren)
            {
                var children = getChildren(item);
                if (null != children)
                    stillToProcess.InsertRange(0, children);
            }
        }
    }
}

这避免了递归和大量嵌套迭代器。然后您可以这样称呼它:

// try and get all names 
var names = tree.PreOrder<Node>(n => n.ChildNodes); 

现在这是一个“预购订单”,其中节点名称排在第一位,但如果您愿意,您可以轻松编写后购订单。

This is a very interesting problem that can result in A LOT of resource utilization for arbitrarily-deep structures. I think Mr. Skeet proposed a 'flattening' technique but I don't recall the link. Here's the code we use based on his idea (it's an extension method on IEnumerable):

public static class IEnumerableExtensions
{
    /// <summary>
    /// Visit each node, then visit any child-list(s) the node maintains
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="subjects">IEnumerable to traverse/></param>
    /// <param name="getChildren">Delegate to get T's direct children</param>
    public static IEnumerable<T> PreOrder<T>(this IEnumerable<T> subjects, Func<T,    IEnumerable<T>> getChildren)
    {
        if (subjects == null)
            yield break;

        // Would a DQueue work better here?
        // A stack could work but we'd have to REVERSE the order of the subjects and children
        var stillToProcess = subjects.ToList();

        while (stillToProcess.Any())
        {
            // First, visit the node
            T item = stillToProcess[0];
            stillToProcess.RemoveAt(0);
            yield return item;

            // Queue up any children
            if (null != getChildren)
            {
                var children = getChildren(item);
                if (null != children)
                    stillToProcess.InsertRange(0, children);
            }
        }
    }
}

This avoids recursion and a lot of nested iterators. You would then call it:

// try and get all names 
var names = tree.PreOrder<Node>(n => n.ChildNodes); 

Now this IS a 'pre-order' where the node name comes first, but you could easily write a post-order if that's what you prefer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文