【问题标题】:Linq recursive search nodes [duplicate]Linq递归搜索节点[重复]
【发布时间】:2026-01-09 08:25:04
【问题描述】:

试图获得所有不为空的List<Node>() 的最终列表。如何处理子节点?

public class Node
{
    public string Name { get; set; }

    public List<Node> Nodes { get; set; }
}

public class Program
{
    static void Main(string[] args)
    {
        List<Node> Items = new List<Node>();
        Items.Add(new Node { Name = "Test0" });
        Items.Add(new Node { Name = "Test1" });
        Items.Add(new Node { Name = "Test2" });
        Items.Add(new Node { Name = "Test3" });
        Items.Add(new Node { Name = "Test4" });
        Items.Add(new Node { Name = "Test5" });
        Items.Add(new Node
        {
            Name = "Test6",
            Nodes = new List<Node>
            {
                new Node
                {
                    Name = "Test6.1",
                    Nodes = new List<Node>
                    {
                        new Node
                        {
                            Name = "Test6.1.1", Nodes = new List<Node>()
                        }
                    }
                },

            }
        });
        Items.Add(new Node { Name = "Test7", Nodes = new List<Node> { } });
        Items.Add(new Node { Name = "Test8", Nodes = new List<Node> { } });

        var NotNullNodes = Items.SelectMany(m => m.Nodes);
    }
}

【问题讨论】:

  • 您需要一种方法来执行此递归。类似this post 的东西,或者你只需​​使用类似possible duplicate 的堆栈
  • 从理论角度来看,有很多方法可以找到您的节点。它被称为树结构的遍历。值得一看this article
  • 它必须是递归的吗?你想学习递归吗? Linq 本身不会遍历你的树。你需要这样做

标签: c# linq


【解决方案1】:

另一种 linq 递归解决方案:

public static IEnumerable<Node> GetAllNodes( Node root )
{
    if( root == null )
    {
        yield break;
    }

    yield return root;

    if ( root.Nodes == null )
    {
        yield break;
    }

    foreach ( Node descendant in root.Nodes.SelectMany( GetAllNodes ) )
    {
        yield return descendant;
    }
}

这样使用:

Items.SelectMany( GetAllNodes )

【讨论】:

    【解决方案2】:

    好吧,SelectMany 仅将一层变平;在您的情况下,您需要对图表进行某种搜索,例如BFS - 广度优先搜索

      public static partial class EnumerableExtensions {
        public static IEnumerable<T> BreadthFirstSearch<T>(
          this IEnumerable<T> source, 
          Func<T, IEnumerable<T>> children) {
    
          if (Object.ReferenceEquals(null, source))
            throw new ArgumentNullException(nameof(source));
          else if (Object.ReferenceEquals(null, children))
            throw new ArgumentNullException(nameof(children));
    
          HashSet<T> proceeded = new HashSet<T>();
    
          Queue<IEnumerable<T>> queue = new Queue<IEnumerable<T>>();
    
          queue.Enqueue(source);
    
          while (queue.Count > 0) {
            IEnumerable<T> src = queue.Dequeue();
    
            if (Object.ReferenceEquals(null, src))
              continue;
    
            foreach (var item in src) 
              if (proceeded.Add(item)) {
                yield return item;
    
                queue.Enqueue(children(item));
              }
          }
        }
      }
    

    那么你就可以放

    var NotNullNodes = Items.BreadthFirstSearch(item => item.Items ?? new List<Node>()); 
    

    【讨论】:

      【解决方案3】:

      这是一个递归获取所有节点的函数:

          public static List<Node> GetAllNodes(List<Node> items)
          {
              List<Node> allNodes = new List<Node>();
      
              foreach(Node n in items)
                  if (n.Nodes != null && n.Nodes.Count > 0)
                      allNodes.AddRange(GetAllNodes(n.Nodes));
      
              allNodes.AddRange(items);
      
              return allNodes;
          }
      

      【讨论】:

        【解决方案4】:

        如果您为 IEnumerable 创建扩展方法,则可以像使用现有的 LINQ 方法一样使用它。见extension methods demystified

        static IEnumerable<Node> Flatten(IEnumerable<node> nodesWithSubNodes)
        {
            // Todo: check nodesWithSubNodes not null
            foreach (var node in nodesWithSubNodes)
            {
                yield return node;
                if (node.SubNodes != null)  // not needed if you are certain that not null
                {
                    foreach (var subNode in nodes.SubNodes.Flatten())
                        yield return subNode;
                }
        }
        

        用法:

        var result = myNodes.Flatten()
                     .Take(3)
                     .ToList();
        

        这样做的好处是它看起来像一个现有的 LINQ 函数。它也非常有效,因为它不会枚举比您实际查询更多的元素,如示例中所示。

        【讨论】: