Tengo un IEnumerable<T>
método que estoy usando para buscar controles en una página de WebForms.
El método es recursivo y tengo algunos problemas para devolver el tipo que quiero cuando yield return
devuelve el valor de la llamada recursiva.
Mi código se ve de la siguiente manera:
public static IEnumerable<Control>
GetDeepControlsByType<T>(this Control control)
{
foreach(Control c in control.Controls)
{
if (c is T)
{
yield return c;
}
if(c.Controls.Count > 0)
{
yield return c.GetDeepControlsByType<T>();
}
}
}
Esto actualmente arroja un error "No se puede convertir el tipo de expresión". Sin embargo, si este método devuelve el tipo IEnumerable<Object>
, el código se construye, pero el tipo incorrecto se devuelve en la salida.
¿Hay alguna forma de usar yield return
mientras se usa también la recursividad?
c#
generics
ienumerable
yield
Jamie Dixon
fuente
fuente
if(c.Controls.Count > 0)
->if(c.Controls.Any())
, especialmente si también estás cediendo :)yield
. Por favor, vea a continuación :) Y también es unayield return
en las funciones recursivas, el uso de la memoria aumenta de forma explosiva. Ver stackoverflow.com/a/30300257/284795Respuestas:
Dentro de un método que regresa
IEnumerable<T>
,yield return
tiene que regresarT
, no unIEnumerable<T>
.Reemplazar
con:
fuente
Debe entregar cada uno de los elementos producidos por la llamada recursiva:
Tenga en cuenta que hay un costo para recurrir de esta manera: terminará creando muchos iteradores, lo que puede crear un problema de rendimiento si tiene un árbol de control realmente profundo. Si desea evitar eso, básicamente necesita hacer la recursión usted mismo dentro del método, para asegurarse de que solo se haya creado un iterador (máquina de estado). Consulte esta pregunta para obtener más detalles y una implementación de muestra, pero esto obviamente también agrega una cierta cantidad de complejidad.
fuente
c.Controls.Count > 0
vs..Any()
:)Como Jon Skeet y el coronel Panic señalan en sus respuestas, el uso
yield return
de métodos recursivos puede causar problemas de rendimiento si el árbol es muy profundo.Aquí hay un método genérico de extensión no recursiva que realiza un recorrido en profundidad de una secuencia de árboles:
A diferencia de la solución de Eric Lippert , RecursiveSelect trabaja directamente con los enumeradores, por lo que no necesita llamar a Reverse (que almacena la secuencia completa en la memoria).
Usando RecursiveSelect, el método original del OP se puede reescribir simplemente así:
fuente
Otros le proporcionaron la respuesta correcta, pero no creo que su caso se beneficie con el rendimiento.
Aquí hay un fragmento que logra lo mismo sin ceder.
fuente
yield
también? ;)foreach
bucle adicional . ¡Ahora puedo hacer esto con programación funcional pura!Debe devolver los elementos del enumerador, no del enumerador en sí, en su segundo
yield return
fuente
Creo que debe devolver cada uno de los controles en los enumerables.
fuente
La sintaxis de Seredynski es correcta, pero debe tener cuidado de evitarla
yield return
en las funciones recursivas porque es un desastre para el uso de la memoria. Consulte https://stackoverflow.com/a/3970171/284795 que escala explosivamente con la profundidad (una función similar estaba usando el 10% de la memoria en mi aplicación).Una solución simple es usar una lista y pasarla con la recursión https://codereview.stackexchange.com/a/5651/754
Alternativamente, puede usar una pila y un ciclo while para eliminar las llamadas recursivas https://codereview.stackexchange.com/a/5661/754
fuente
Si bien hay muchas buenas respuestas, todavía agregaría que es posible usar métodos LINQ para lograr lo mismo,.
Por ejemplo, el código original del OP podría reescribirse como:
fuente
OfType
no es realmente un significado diferente. A lo sumo un pequeño cambio realista. Un control no puede ser hijo de controles múltiples, por lo que el árbol atravesado ya no es obligatorio. Usar enUnion
lugar deConcat
es verificar innecesariamente la unicidad de una secuencia que ya se garantiza que es única y, por lo tanto, es una degradación objetiva.