2008-10-31 5 views
33

Si je voulais rechercher les cases à cocher sur une page ASP.NET, je pourrais utiliser la requête LINQ suivante.Recherche de contrôle récursive avec LINQ

var checkBoxes = this.Controls 
        .OfType<CheckBox>() 
        .TakeWhile<CheckBox>(cb => cb.Checked); 

Cela fonctionne très bien si les cases à cocher sont imbriquées dans la collection de contrôle actuel, mais je voudrais savoir comment étendre la recherche en perçant dans les collections de contrôle des commandes de haut niveau.

La question a été posée ici:

Finding controls that use a certain interface in ASP.NET

et a reçu des réponses non-LINQ, je l'ai déjà ma propre version d'une recherche de contrôle récursif sur le type et l'ID en tant que méthodes d'extension, mais je me demandais comment est-ce facile à faire dans LINQ?

Répondre

43

Prenez le contrôle de type/ID hors de la récursivité, alors il suffit d'avoir une méthode "Donnez-moi tous les contrôles, récursivement", par exemple.

public static IEnumerable<Control> GetAllControls(this Control parent) 
{ 
    foreach (Control control in parent.Controls) 
    { 
     yield return control; 
     foreach(Control descendant in control.GetAllControls()) 
     { 
      yield return descendant; 
     } 
    } 
} 

C'est un peu inefficace (en termes de création de beaucoup itérateurs) mais je doute que vous aurez un arbre profond très.

Vous pouvez alors écrire votre requête originale comme:

var checkBoxes = this.GetAllControls() 
        .OfType<CheckBox>() 
        .TakeWhile<CheckBox>(cb => cb.Checked); 

(EDIT:. Changement AllControls à GetAllControls et de l'utiliser correctement comme méthode)

+0

Cela fonctionne, mais si vous utilisez des contrôles tiers et vos propres contrôles serveur comme nous le faisons dans mon lieu de travail actuel , vous pouvez obtenir quelques bons niveaux d'imbrication de contrôle. Je suppose qu'une page ASP.NET est juste un exemple d'un problème plus général, celui de percer des collections imbriquées dans Linq. – PhilGriffin

+0

J'ai déjà utilisé cette technique auparavant. (Si vous voulez être vraiment gentil, vous pourriez le faire comme une expression avec le Y Combinator!) BTW ne devrait-on pas utiliser AllControls comme méthode? – yfeldblum

+1

@Phil: Oui, ça peut descendre à quelques niveaux et être un peu inefficace - mais avez-vous des raisons de croire que ce sera un goulot d'étranglement * significatif *? Faites la chose la plus simple qui fonctionne en premier, puis optimisez si c'est vraiment un problème. (Par exemple, un seul appel à une base de données est susceptible de surcharger ce coût.) –

0

Ma suggestion pour le faire récursivement AllControls est:

public static IEnumerable<Control> AllControls(this Control parent) 
    { 
     foreach (Control control in parent.Controls) 
     { 
      yield return control; 
     } 
     foreach (Control control in parent.Controls) 
     { 
      foreach (Control cc in AllControls(control)) yield return cc; 
     } 
    } 

le deuxième foreach semble bizarre, mais c'est la seule façon que je connaisse à « aplatir » l'appel récursif.

2
public static IEnumerable<Control> AllControls(this Control container) 
{ 
    //Get all controls 
    var controls = container.Controls.Cast<Control>(); 

    //Get all children 
    var children = controls.Select(c => c.AllControls()); 

    //combine controls and children 
    var firstGen = controls.Concat(children.SelectMany(b => b)); 

    return firstGen; 
} 

maintenant basée sur la fonction ci-dessus, nous pouvons faire quelque chose comme ceci:

public static Control FindControl(this Control container, string Id) 
{ 
    var child = container.AllControls().FirstOrDefault(c => c.ID == Id); 
    return child; 
}