2011-09-26 56 views
1

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

    } 
} 

回答

2

不必返回递归调用的结果。

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

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

您正在进行的呼叫,但什么都不做吧。您需要实际使用的结果,这里

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

这是可能导致资源利用率为任意深的结构有很多非常有趣的问题。我认为斯基特先生提出了一种“扁平化”技术,但我不记得这一联系。下面是我们使用基于他的想法的代码(这是在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); 

现在,这是一个“预购”,其中的节点名称是第一位的,但你可以很容易地写一个后命令,如果这是你喜欢什么。