2008-10-31 10 views
33

Wenn ich überprüfte Kontrollkästchen auf einer ASP.NET-Seite finden wollte, konnte ich die folgende LINQ-Abfrage verwenden.Rekursive Steuerelementsuche mit LINQ

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

Das funktioniert gut, wenn die Kontrollkästchen in der aktuellen Steuer Sammlung verschachtelt sind, aber ich würde gerne wissen, wie die Suche zu erweitern, indem ich in die Steuer Kollektionen der Top-Level-Kontrollen zu bohren.

Die Frage ist hier gefragt wurde:

Finding controls that use a certain interface in ASP.NET

und erhielt nicht-LINQ Antworten, ich habe bereits meine eigene Version einer rekursiven Steuer Suche nach Typ und ID als Erweiterungsmethoden, aber ich fragte mich, wie einfach das ist in LINQ zu tun?

Antwort

43

Nehmen Sie die Typ/ID-Überprüfung der Rekursion, also haben Sie einfach eine "geben Sie mir alle Kontrollen, rekursiv" -Methode, z.

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

, die etwas ineffizient ist (in Bezug auf viele Iteratoren zu schaffen), aber ich bezweifle, dass Sie einen sehr tiefen Baum haben werden.

Sie können dann Ihre ursprüngliche Abfrage schreiben wie:

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

(EDIT:. Changed AllControls zu GetAllControls und es richtig als Methode verwenden)

+0

das funktioniert, aber wenn Sie Dritten Kontrollen und einem eigenen Server Kontrollen verwendet wurden, wie wir in meinem aktuellen Arbeitsplatz tun , können Sie ein paar Ebenen der Kontrolle nesting bekommen. Ich nehme an, eine ASP.NET-Seite ist nur ein Beispiel für ein allgemeineres Problem, nämlich das Aufspüren verschachtelter Sammlungen in Linq. – PhilGriffin

+0

Ich habe gerade diese Technik schon einmal benutzt. (Wenn Sie wirklich süß werden wollen, können Sie dies als einen Ausdruck mit dem Y-Combinator tun!) BTW sollte nicht AllControls als Methode verwendet werden? – yfeldblum

+1

@Phil: Ja, es kann ein paar Stufen runter gehen und ein wenig ineffizient sein - aber haben Sie einen Grund zu glauben, dass es ein * bedeutender * Flaschenhals sein wird? Tun Sie die einfachste Sache, die zuerst funktioniert, und optimieren Sie dann, wenn es wirklich ein Problem ist. (z. B. wird ein einzelner Datenbankanruf diese Kosten wahrscheinlich überfordern.) –

0

Mein Vorschlag der AllControls rekursiv zu machen ist:

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

die zweite foreach sieht seltsam, aber das ist der einzige Weg, ich weiß, zu den rekursiven Aufruf „abzuflachen“.

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

nun auf die obige Funktion basiert, können wir so etwas tun:

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