J'ai une IEnumerable<T>
méthode que j'utilise pour trouver des contrôles dans une page WebForms.
La méthode est récursive et j'ai des problèmes pour retourner le type que je veux quand le yield return
retourne la valeur de l'appel récursif.
Mon code ressemble à ceci:
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>();
}
}
}
Cela génère actuellement une erreur «Impossible de convertir le type d'expression». Si toutefois cette méthode retourne le type IEnumerable<Object>
, le code se construit, mais le mauvais type est renvoyé dans la sortie.
Existe-t-il un moyen d'utiliser yield return
tout en utilisant la récursivité?
c#
generics
ienumerable
yield
Jamie Dixon
la source
la source
if(c.Controls.Count > 0)
->if(c.Controls.Any())
, surtout si vousyield
. Veuillez voir ci-dessous :) Et c'est aussi unyield return
dans les fonctions récursives, l'utilisation de la mémoire évolue de manière explosive. Voir stackoverflow.com/a/30300257/284795Réponses:
À l'intérieur d'une méthode qui retourne
IEnumerable<T>
,yield return
doit retournerT
, pas unIEnumerable<T>
.Remplacer
avec:
la source
Vous devez fournir chacun des éléments générés par l'appel récursif:
Notez qu'il y a un coût à récurser de cette façon - vous finirez par créer beaucoup d'itérateurs, ce qui peut créer un problème de performances si vous avez une arborescence de contrôle très approfondie. Si vous voulez éviter cela, vous devez essentiellement effectuer la récursivité vous-même dans la méthode, pour vous assurer qu'il n'y a qu'un seul itérateur (machine d'état) créé. Voir cette question pour plus de détails et un exemple d'implémentation - mais cela ajoute évidemment une certaine complexité également.
la source
c.Controls.Count > 0
vs.Any()
:)Comme Jon Skeet et le colonel Panic le notent dans leurs réponses, l'utilisation
yield return
de méthodes récursives peut entraîner des problèmes de performances si l'arbre est très profond.Voici une méthode d'extension non récursive générique qui effectue une traversée en profondeur d'abord d'une séquence d'arbres:
Contrairement à la solution d'Eric Lippert , RecursiveSelect fonctionne directement avec les énumérateurs de sorte qu'il n'a pas besoin d'appeler Reverse (qui met en mémoire tampon la séquence entière en mémoire).
En utilisant RecursiveSelect, la méthode originale de l'OP peut être réécrite simplement comme ceci:
la source
D'autres vous ont fourni la bonne réponse, mais je ne pense pas que votre cas profite du rendement.
Voici un extrait qui réalise la même chose sans céder.
la source
yield
? ;)foreach
boucle supplémentaire . Maintenant, je peux le faire avec une programmation fonctionnelle pure!Vous devez renvoyer les éléments de l'énumérateur, pas l'énumérateur lui-même, dans votre deuxième
yield return
la source
Je pense que vous devez renvoyer chacun des contrôles dans les énumérables.
la source
La syntaxe de Seredynski est correcte, mais vous devez faire attention à éviter
yield return
les fonctions récursives car c'est un désastre pour l'utilisation de la mémoire. Voir https://stackoverflow.com/a/3970171/284795 il évolue de manière explosive avec la profondeur (une fonction similaire utilisait 10% de mémoire dans mon application).Une solution simple consiste à utiliser une liste et à la transmettre avec la récursivité https://codereview.stackexchange.com/a/5651/754
Vous pouvez également utiliser une pile et une boucle while pour éliminer les appels récursifs https://codereview.stackexchange.com/a/5661/754
la source
Bien qu'il existe de nombreuses bonnes réponses, j'ajouterais tout de même qu'il est possible d'utiliser les méthodes LINQ pour accomplir la même chose,.
Par exemple, le code original de l'OP pourrait être réécrit comme:
la source
OfType
n'est pas vraiment différent. Au plus, un changement styalistique mineur. Un contrôle ne peut pas être un enfant de plusieurs contrôles, donc l'arborescence parcourue est déjà unqiue. Utiliser à laUnion
place deConcat
vérifie inutilement l'unicité d'une séquence déjà garantie d'être unique, et constitue donc un déclassement objectif.