如何使用LINQ选择复合对象的所有后代

如何使用LINQ更好地使ComponentTraversal.GetDescendants()

 public static class ComponentTraversal { public static IEnumerable GetDescendants(this Composite composite) { //How can I do this better using LINQ? IList descendants = new Component[]{}; foreach(var child in composite.Children) { descendants.Add(child); if(child is Composite) { descendants.AddRange((child as Composite).GetDescendants()); } } return descendants; } } public class Component { public string Name { get; set; } } public class Composite: Component { public IEnumerable Children { get; set; } } public class Leaf: Component { public object Value { get; set; } } 

回答

我编辑了Chris的答案,提供了我添加到Common库中的通用扩展方法。 我可以看到这对其他人也有帮助,所以这里是:

  public static IEnumerable GetDescendants(this T component, Func isComposite, Func<T,IEnumerable> getCompositeChildren) { var children = getCompositeChildren(component); return children .Where(isComposite) .SelectMany(x => x.GetDescendants(isComposite, getCompositeChildren)) .Concat(children); } 

谢谢克里斯!

也,

请访问http://blogs.msdn.com/b/wesdyer/archive/2007/03/23/all-about-iterators.aspx查看LukeH的答案。 他的回答提供了更好的方法来解决这个问题,但我没有选择它,因为它不是我问题的直接答案。

 var result = composite.Children.OfType().SelectMany(child => child.GetDescendants()).Concat(composite.Children); return result.ToList(); 

在从不完美的语法转换到LINQ时,通常很容易一次一步地进行转换。 这是如何工作的:

  1. 这是循环复合.Children,所以这将是我们应用LINQ的集合。
  2. 循环中有两个常规操作,所以让我们一次做一个
  3. “if”语句正在执行filter。 通常,我们会使用“Where”来执行filter,但在这种情况下,filter基于类型。 LINQ为此内置了“OfType”。
  4. 对于每个子复合,我们希望递归调用GetDescendants并将结果添加到单个列表中。 每当我们想要将元素转换为其他元素时,我们都使用Select或SelectMany。 由于我们希望将每个元素转换为列表并将它们合并在一起,因此我们使用SelectMany。
  5. 最后,要在composite.Children本身中添加,我们将这些结果连接到最后。

通常有很好的理由避免(1)递归方法调用, (2)嵌套迭代器,以及(3)大量一次性分配。 这种方法避免了所有这些潜在的陷阱:

 public static IEnumerable GetDescendants(this Composite composite) { var stack = new Stack(); do { if (composite != null) { // this will currently yield the children in reverse order // use "composite.Children.Reverse()" to maintain original order foreach (var child in composite.Children) { stack.Push(child); } } if (stack.Count == 0) break; Component component = stack.Pop(); yield return component; composite = component as Composite; } while (true); } 

这是通用的等价物:

 public static IEnumerable GetDescendants(this T component, Func hasChildren, Func> getChildren) { var stack = new Stack(); do { if (hasChildren(component)) { // this will currently yield the children in reverse order // use "composite.Children.Reverse()" to maintain original order // or let the "getChildren" delegate handle the ordering foreach (var child in getChildren(component)) { stack.Push(child); } } if (stack.Count == 0) break; component = stack.Pop(); yield return component; } while (true); } 

我不知道更好,但我认为这执行相同的逻辑:

 public static IEnumerable GetDescendants(this Composite composite) { return composite.Children .Concat(composite.Children .Where(x => x is Composite) .SelectMany(x => x.GetDescendants()) ); } 

它可能更短,但你拥有的东西没有任何问题。 正如我上面所说,这应该执行相同的事情,我怀疑function的性能是否得到改善。

当您可能想要实现迭代器时,这是一个很好的示例。 这具有以稍微更易读的语法进行延迟评估的优点。 此外,如果您需要添加其他自定义逻辑,则此表单更具可扩展性

  public static IEnumerable GetDescendants(this Composite composite) { foreach(var child in composite.Children) { yield return child; if(!(child is Composite)) continue; foreach (var subChild in ((Composite)child).GetDescendants()) yield return subChild; } }