枚举本质上不是IEnumerable的集合?

当你想递归枚举一个分层对象,根据一些标准选择一些元素时,有许多技术的例子,比如“展平”,然后使用Linq进行过滤:如下所示:

链接文字

但是,当你枚举类似Form的Controls集合或TreeView的Nodes集合时,我一直无法使用这些类型的技术,因为它们似乎需要一个参数(对于扩展方法),这是一个IEnumerable集合:传入SomeForm.Controls不编译。

我发现最有用的是:

链接文字

这为您提供了Control.ControlCollection的扩展方法,其中包含IEnumerable结果,然后您可以使用Linq。

我修改了上面的例子来解析TreeView的节点没有问题。

public static IEnumerable GetNodesRecursively(this TreeNodeCollection nodeCollection) { foreach (TreeNode theNode in nodeCollection) { yield return theNode; if (theNode.Nodes.Count > 0) { foreach (TreeNode subNode in theNode.Nodes.GetNodesRecursively()) { yield return subNode; } } } } 

这是我现在使用扩展方法编写的代码:

  var theNodes = treeView1.Nodes.GetNodesRecursively(); var filteredNodes = ( from n in theNodes where n.Text.Contains("1") select n ).ToList(); 

而且我认为在传递约束的情况下可能会有一种更优雅的方法。

我想知道是否可以一般性地定义这样的过程,以便:在运行时我可以将集合类型以及实际集合传递给generics参数,因此代码与是否无关它是TreeNodeCollection或Controls.Collection。

我还有兴趣知道是否有任何其他方式(更便宜?fastser?),而不是第二个链接(上面)中显示的方式,以Linq可用的forms获取TreeNodeCollection或Control.ControlCollection。

Leppie关于’在第一个(上图)链接的SOpost中的SelectMany的评论似乎是一个线索。

我对SelectMany的实验是:好吧,称之为“灾难”。 🙂

感谢任何指针。 我已经花了几个小时阅读我发现的那些触及这些区域的SOpost,并漫无目的地进入像“y-combinator”这样的exotica。 一个“谦卑”的经历,我可能会补充:)

这段代码应该可以解决问题

 public static class Extensions { public static IEnumerable GetRecursively(this IEnumerable collection, Func selector) { foreach (var item in collection.OfType()) { yield return item; IEnumerable children = selector(item).GetRecursively(selector); foreach (var child in children) { yield return child; } } } } 

这是一个如何使用它的例子

 TreeView view = new TreeView(); // ... IEnumerable nodes = view.Nodes. .GetRecursively(item => item.Nodes); 

更新:回应Eric Lippert的post。

这是使用All About Iterators中讨论的技术的一个大大改进的版本。

 public static class Extensions { public static IEnumerable GetItems(this IEnumerable collection, Func selector) { Stack> stack = new Stack>(); stack.Push(collection.OfType()); while (stack.Count > 0) { IEnumerable items = stack.Pop(); foreach (var item in items) { yield return item; IEnumerable children = selector(item).OfType(); stack.Push(children); } } } } 

我使用以下基准测试技术进行了简单的性能测试。 结果不言自明。 树的深度对第二种解决方案的性能只有微不足道的影响; 而第一个解决方案的性能迅速下降,当树的深度变得太大时,最终会导致StackOverflowException

标杆

你似乎走在正确的轨道上,上面的答案有一些好主意。 但我注意到所有这些递归解决方案都有一些深层次的缺陷。

假设所讨论的树总共有n个节点,其最大树深度为d <= n。

首先,它们消耗树深处的系统堆栈空间。 如果树结构非常深,那么这可能会导致堆栈崩溃并导致程序崩溃。 树深d是O(lg n),取决于树的分支因子。 更糟糕的情况是根本没有分支 – 只是一个链表 – 在这种情况下,只有几百个节点的树会炸掉堆栈。

其次,你在这里做的是构建一个迭代器,它调用一个调用迭代器的迭代器……这样顶部迭代器上的每个MoveNext()实际上都会在成本中再次调用O(d)。 如果在每个节点上执行此操作,则调用的总成本为O(nd),最差情况为O(n ^ 2),最佳情况为O(n lg n)。 你可以做得比两者都好; 没有理由说这不能及时成为线性的。

诀窍是停止使用小而脆弱的系统堆栈来跟踪下一步该做什么,并开始使用堆分配的堆栈来明确跟踪。

您应该在阅读列表中添加Wes Dyer关于此的文章:

https://blogs.msdn.microsoft.com/wesdyer/2007/03/23/all-about-iterators/

他最后给出了一些编写递归迭代器的好技巧。

我不确定TreeNodes,但你可以使用System.Linq IEnumerableforms的Controls集合,例如

 var ts = (from t in this.Controls.OfType where t.Name.Contains("fish") select t); //Will get all the textboxes whose Names contain "fish" 

很抱歉,我不知道如何使这个递归,从我的头顶。

基于mrydengren的解决方案:

 public static IEnumerable GetRecursively(this IEnumerable collection, Func selector, Func predicate) { foreach (var item in collection.OfType()) { if(!predicate(item)) continue; yield return item; IEnumerable children = selector(item).GetRecursively(selector, predicate); foreach (var child in children) { yield return child; } } } var theNodes = treeView1.Nodes.GetRecursively( x => x.Nodes, n => n.Text.Contains("1")).ToList(); 

编辑:对于BillW

我想你要的是这样的东西。

 public static IEnumerable  GetNodesRecursively(this TCollection nodeCollection, Func getSub) where T, TCollection: IEnumerable { foreach (var theNode in ) { yield return theNode; foreach (var subNode in GetNodesRecursively(theNode, getSub)) { yield return subNode; } } } var all_control = GetNodesRecursively(control, c=>c.Controls).ToList();