2011-09-26 57 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); 

現在,這是一個「預購」,其中的節點名稱是第一位的,但你可以很容易地寫一個後命令,如果這是你喜歡什麼。