Estoy disfrutando de lo que será mi último proyecto en C# 2.0 y .NET 2.0 antes de pasar a WPF, y la verdad es que se hace amargo algunas veces cuando ya conoces C# 3.0, por ejemplo se hecha de menos LINQ :D
En este artículo quiero demostrar como hacer queries a una colección de elementos en forma de arbol desde C# 2.0, de forma que se pueda definir el tipo de colección, criterio de búsqueda que se quiere usar y obtener los resultado conforme se vayan obteniendo.
Una función recursiva, es una función que se llama a si misma y controla cuando parar de hacerlo mediante una condición. Si esa condición no esta bien definida... a parte del cuelgue del hilo en cuestión lo más posible será terminar recibiendo una sonora StackOverflowException.
Para recorrer una estructura en arbol nada mejor que una función recursiva, un ejemplo muy simple es esta función que recorre todos los controles hijos de un control dado recursivamente:
/// <summary>
/// Recorre recursivamente todos los
/// controles hijos de un control dado.
/// </summary>
void lookRecursive(Control Parent)
{
// Recorro los subnodos
foreach (Control child in Parent.Controls)
{
Debug.Print(child.Name);
// Si el subnodo tiene más subnodos
// ejecuto recursivamente.
if (child.Controls.Count > 0)
lookRecursive(child);
}
}
Podemos hacer que la función nos devuelva un elemento introduciendo una variable para el almacenamiento, si por ejemplo lo que queremos es que encuentre un determinado control por su nombre y lo devuelva (ya hay un método por ahí por la BCL que lo hace):
/// <summary>
/// Recorre recursivamente todos los
/// controles y encuentra el de un nombre
/// determinado.
/// </summary>
Control findControl(Control Parent, String Name)
{
Control temp=null;
// Recorro los subnodos
foreach (Control child in Parent.Controls)
{
// Criterio de búsqueda
if (child.Name == Name)
temp = child;
else if((temp==null) && (child.Controls.Count > 0))
temp = findControl(child,Name);
}
return temp;
}
Ahora vamos a complicarlo un poco más añadiendo las siguientes características:
- Adaptable a cualquier tipo de objetos: Usando genéricos.
- Definir nuestro propio criterio de búsqueda: Pasando un Predicate como argumento que contenga el criterio.
- Definir como se obtienen los sub-nodos: Pasando un delegado que lo especifique.
- La posibilidad de que el resultado no sea único: Devolviendo una colección ó array.
- Poder parar de recorrer cuando encontremos lo que queremos: Que la colección devuelta sea IEnumerable ó IEnumerable<T>y devolver con yield.
Asi queda:
delegate IEnumerable getChilds<T>(T Element);
IEnumerable<T> findRecursive<T>(T Parent, getChilds<T> Elements, Predicate<T> Criteria)
{
foreach (T element in Elements.Invoke(Parent))
{
if (Criteria.Invoke(element))
yield return element;
foreach (T element2 in findRecursive<T>(element,Elements, Criteria))
yield return element2;
}
}
El delegado getChilds define como obtener subnodos de un nodo dado.
Nota: En este ejemplo, en el delegado devuelvo IEnumerable y no IEnumberable<T> (que seria lo lógico) porque Control.Controls es un ControlCollection que solo implementa dicha interfaz, no la genérica :P
Para usarlo... por ejemplo, queremos que todos los paneles pasen a tener borde FixedSingle:
// Defino el criterio de búsqueda,
// en este caso, quiero los controles
// que sean paneles.
Predicate<Control> lookForPanels = delegate(Control c)
{
return c is Panel;
};
// Defino como obtengo los hijos de
// este tipo de objeto.
getChilds<Control> getChildControls = delegate(Control C)
{
return C.Controls;
};
// Por último efectuo la búsqueda
// y obtengo los resultados.
foreach (Panel c in findRecursive<Control>(this, getChildControls, lookForPanels))
{
c.BorderStyle = BorderStyle.FixedSingle;
}
Y si se quiere más complejo también se puede recorrer una estructura en arbol con múltiples hilos:P