【问题标题】:methods using yield not allowed to call themselves使用 yield 的方法不允许调用自己
【发布时间】:2011-11-25 08:47:15
【问题描述】:

这很可能是用户错误(我有点希望如此)。我在 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

    }
}

【问题讨论】:

    标签: c# iterator yield


    【解决方案1】:

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

    你需要yield return调用返回的每一项:

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

    【讨论】:

      【解决方案2】:

      您正在拨打电话,但什么也没做。这里需要实际使用结果

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

      【讨论】:

        【解决方案3】:

        这是一个非常有趣的问题,它可能导致任意深度结构的大量资源利用。我认为斯基特先生提出了一种“扁平化”技术,但我不记得链接了。这是我们根据他的想法使用的代码(它是 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); 
        

        现在这是一个“预购”,节点名称在前,但如果您愿意,您可以轻松编写后购。

        【讨论】:

          猜你喜欢
          • 2012-03-29
          • 1970-01-01
          • 2017-05-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-21
          • 1970-01-01
          • 2011-04-12
          • 1970-01-01
          相关资源
          最近更新 更多