使用多个根节点展平IEnumerable并选择Id属性

我有以下层次结构,我需要展平它并选择所有Id 。 我试过像这样使用SelectMany() .SelectMany(node => node.Children).Select(node => node.Id) 。 这将产生3,5,6的列表。 是否有可能使用Linq获得完整列表1,2,3,4,5,6,7

  • 节点(Id = 1)
  • 节点(Id = 2)
    • 节点(Id = 3)
  • 节点(Id = 4)
    • 节点(Id = 5)
    • 节点(Id = 6)
      • 节点(Id = 7)

您可以使用以下扩展方法来展平层次结构(请参阅下面的答案更新中的替代展平算法):

 public static IEnumerable Flatten( this IEnumerable source, Func> childrenSelector) { foreach (var item in source) { yield return item; var children = childrenSelector(item); if (children == null) continue; foreach (var child in children.Flatten(childrenSelector)) yield return child; } } 

我接受子选择器并递归地生成子项。 然后投影很简单:

 var result = nodes.Flatten(n => n.Children).Select(n => n.Id); 

假设您有以下Node类:

 public class Node { public Node(int id, params Node[] children) { Id = id; if (children.Any()) Children = new List(children); } public int Id { get; set; } public List Children { get; set; } } 

然后使用样本层次结构:

 List nodes = new List { new Node(1), new Node(2, new Node(3)), new Node(4, new Node(5), new Node(6, new Node(7))) }; 

输出将是:

 1, 2, 3, 4, 5, 6, 7 

更新 :您可以在不使用递归的情况下展平层次结构(以获得更好的性能):

 public static IEnumerable Flatten( this IEnumerable source, Func> childrenSelector) { Queue queue = new Queue(); foreach (var item in source) queue.Enqueue(item); while (queue.Any()) { T item = queue.Dequeue(); yield return item; var children = childrenSelector(item); if (children == null) continue; foreach (var child in children) queue.Enqueue(child); } } 

您可以使用扩展方法展平层次结构:

 public static IEnumerable Flatten(this IEnumerable source) { if(source == null) throw new ArgumentNullException("source"); return FlattenIterator(source); } private static IEnumerable FlattenIterator(IEnumerable source) { foreach(var node in source) { yield return node; foreach(var child in node.Children.Flatten()) { yield return child; } } } 

然后你可以选择这样的ID:

 var ids = source.Flatten().Select(n => n.Id); 

简单。

 Func, IEnumerable> flatten = null; flatten = ns => { return ns.Select(n => n.Id) .Concat(ns.SelectMany(n => flatten(n.Children))); };