¿Enumeración de colecciones que no son inherentemente IEnumerable?

Cuando desee enumerar recursivamente un objeto jerárquico, seleccionando algunos elementos según algunos criterios, hay numerosos ejemplos de técnicas como “aplanamiento” y luego filtrado utilizando Linq: como los que se encuentran aquí:

Texto del enlace

Pero cuando enumera algo como la colección Controls de un Formulario, o la colección Nodos de un TreeView, no he podido utilizar este tipo de técnicas porque parecen requerir un argumento (al método de extensión) que es un IEnumerable colección: pasar en SomeForm.Controls no se comstack.

Lo más útil que encontré fue esto:

Texto del enlace

Lo que sí le da un método de extensión para Control.ControlCollection con un resultado IEnumerable que luego puede usar con Linq.

Modifiqué el ejemplo anterior para analizar los Nodos de un TreeView sin problemas.

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; } } } } 

Este es el tipo de código que estoy escribiendo ahora usando el método de extensión:

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

Y creo que puede haber una manera más elegante de hacer esto donde se pasan las restricciones.

Lo que quiero saber si es posible definir dichos procedimientos de forma genérica, de modo que: en tiempo de ejecución pueda pasar el tipo de colección, así como la colección real, a un parámetro genérico, por lo que el código es independiente de si es un TreeNodeCollection o Controls.Collection.

También me interesaría saber si hay alguna otra forma (¿más barata? Fastser?) Que la que se muestra en el segundo enlace (arriba) para obtener una TreeNodeCollection o Control.ControlCollection en un formato que pueda utilizar Linq.

Un comentario de Leppie sobre ‘SelectMany en la publicación SO vinculada al primero (arriba) parece una pista.

Mis experimentos con SelectMany han sido: bueno, llámalos “desastres”. 🙂

Aprecie cualquier puntero. He pasado varias horas leyendo cada publicación SO que pude encontrar que tocaba en estas áreas, y adentrándome en un exotismo como el “y-combinator”. Una experiencia “humillante”, debo agregar 🙂

Este código debería hacer el truco

 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; } } } } 

Aquí hay un ejemplo de cómo usarlo

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

Actualización: En respuesta a la publicación de Eric Lippert.

Aquí hay una versión muy mejorada que utiliza la técnica que se analiza en 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); } } } } 

Hice una prueba de rendimiento simple usando la siguiente técnica de evaluación comparativa . los resultados hablan por si mismos. La profundidad del árbol solo tiene un impacto marginal en el rendimiento de la segunda solución; mientras que el rendimiento disminuye rápidamente para la primera solución, conduciendo finalmente a una StackOverflowException cuando la profundidad del árbol es demasiado grande.

benchmarking

Pareces estar en el camino correcto y las respuestas anteriores tienen algunas buenas ideas. Pero observo que todas estas soluciones recursivas tienen algunos defectos profundos.

Supongamos que el árbol en cuestión tiene un total de n nodos con una profundidad de árbol máxima de d <= n.

En primer lugar, consumen el espacio de la stack del sistema en la profundidad del árbol. Si la estructura del árbol es muy profunda, esto puede hacer explotar la stack y bloquear el progtwig. La profundidad del árbol d es O (lg n), dependiendo del factor de ramificación del árbol. Peor aún es el caso de una bifurcación, solo una lista vinculada, en cuyo caso un árbol con solo unos pocos cientos de nodos volará la stack.

Segundo, lo que estás haciendo aquí es construir un iterador que llame a un iterador que llame a un iterador … para que cada MoveNext () en el iterador superior realmente haga una cadena de llamadas que sea O (d) en costo. Si hace esto en cada nodo, entonces el costo total en llamadas es O (nd), que es el caso más desfavorable O (n ^ 2) y el mejor caso O (n lg n). Puedes hacer mejor que ambos; no hay razón por la cual esto no pueda ser lineal en el tiempo.

El truco consiste en dejar de utilizar la stack pequeña y frágil del sistema para realizar un seguimiento de qué hacer a continuación y comenzar a utilizar una stack asignada en el montón para realizar un seguimiento explícito.

Debe agregar a su lista de lectura el artículo de Wes Dyer sobre esto:

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

Él da algunas buenas técnicas al final para escribir iteradores recursivos.

No estoy seguro acerca de TreeNodes, pero puede hacer la colección Controls de un formulario IEnumerable usando System.Linq y, por ejemplo

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

Perdón por decir que no sé cómo hacer que esto sea recursivo, fuera de mi cabeza.

Basado en la solución de 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(); 

Editar: para BillW

Supongo que estás pidiendo algo como esto.

 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();