Click here to Skip to main content
Click here to Skip to main content

A Very Efficient Generic Recursive Iterator using Yield Return

, 1 Jun 2007
Rate this:
Please Sign up or sign in to vote.
This article demonstrates techniques on how to optimise the common programming task of recursively iterating through a container control using generics and yield return.

Introduction

An often performed exercise by developers is to recursively iterate through all controls within a control container (like a Form or Panel) as well as all their sub-containers.

The code I list here is a simple yet very efficient generic way of doing this. It is by all means no world shocking revolutionary break-through but it is nice in its sheer simplicity!!

The Task

Say an invalid command has taken place and you wish to set all TextBoxes on your form to show "n/a". So you will have to recursively find all TextBoxes in this Form's container and all its other sub-containers that it may contain.

An Inefficient Though Often Used Approach

An often used method would be to create a separate routine that brings back a list of TextBox controls that can then be set to "n/a" within the calling routine. Something like this...

public static List<Control> GetControlsOfType(Control ctrMain)
{
    List<Control> list = new List<Control>();

    foreach (Control c in ctrMain.Controls)
    {
        if (c.GetType().Equals(typeof(TextBox)))
            list.Add(c);

        if (c.Controls.Count > 0)
            list.AddRange(GetControlsOfType(c));
    }

    return list;
}

... used like this:

private void button1_Click(object sender, EventArgs e)
{
    foreach (Control c in Utils.GetControlsOfType(this))
        c.Text = "n/a";
}

This technique works but has some ugly disadvantages:

  • What if the need arises to find all Buttons instead of TextBoxes within your Form? You will have to duplicate your code or put a switch in it to distinguish between control Types.
  • You are creating a new List that is probably used only once after which it is earmarked for Garbage Collection. If you do this often and potentially with many more items, then this could have a real impact on your memory footprint and code efficiency. Remember that you are after all creating and populating a List with ALL the items only to set a Text property after which you no longer need this long list and dispose of it!!

The Generic IEnumerable Approach

Now have a look at the code below. By using Generics, we tackle the first issue of being bound to a specific Type. We are now forced to specify the Type we wish to search for when calling the method by specifying <T>.

Then by using the yield return statement and returning the generic IEnumerable<T> instead of a fully populated temporary List, we are able to dramatically reduce our memory footprint and make the code much more efficient!!

public delegate bool delTypeCheck(Type type);

/// <span class="code-SummaryComment"><summary></span>
/// Examines the full content of this control's Controls 
/// collection and returns the enumerator instead of a generic list.
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><typeparam name="T">The Type we are looking for - </span>
/// we will also treat any derived types as a match!<span class="code-SummaryComment"></typeparam></span>
/// <span class="code-SummaryComment"><param name="ctrMain">The control to examine.</param></span>
/// <span class="code-SummaryComment"><param name="IsExemptCheck">A delegate allowing us to specify </span>
/// which controls to exempt from examining.<span class="code-SummaryComment"></param></span>
/// <span class="code-SummaryComment"><returns>The actual enumerator allowing us to NOT have </span>
/// to create a helper intermediate list.<span class="code-SummaryComment"></returns></span>
public static IEnumerable<T> GetControlsOfType<T>
	(Control ctrMain, delTypeCheck IsExemptCheck) where T : class
{
    // Determine the Type we need to look out for
    Type searchType = typeof(T);

    foreach (Control c in ctrMain.Controls)
    {
        // If user wants to exclude certain types then a call-back has been given
        // So call it with the current control-type and check if it is exempt.
        if (IsExemptCheck != null && IsExemptCheck(c.GetType()))
            continue;   // the type of c is exempt so continue.

        // If a match is found then yield this item back directly    
        if (c is T) yield return (c as T);
            
        // if you want to search for specific Types only (and NOT derived types) then
        // uncomment the following lines (and comment out the above statement).
        //if (c.GetType().Equals(searchType))
        //    yield return (c as T);

        // If the control hosts other controls then recursively call this function again.
        if (c.Controls.Count > 0)
            foreach (T t in GetControlsOfType<T>(c, IsExemptCheck))
                yield return t;
    }
}

This is used like this.

If we want to find all controls that derive from TextBoxBase (which includes TextBoxes as well as RichTextBox) and set the Text property of all these to "n/a", then implement something like the code below:

foreach (Control c in Utils.GetControlsOfType<TextBoxBase>(this, null))
{
     c.Text = "n/a";
}

In addition, exclude certain Types from examining.

Say you want to exclude searching through some Types of containers (in this case Panel), then you can supply an anonymous delegate to exclude these (see code below). The 'IsExemptCheck' delegate hands over the Type that is being examined and asks you to return whether it should be excluded or not. This gives the caller much more scope to also point at a more complex function to determine whether to examine a control. In this case, we simply want to exclude Panel types so a simple anonymous delegate suffices.

foreach (Control c in Utils.GetControlsOfType<TextBoxBase>
	(this, delegate(Type MyType) { return MyType.Equals(typeof(Panel)); }))
{
     c.Text = "n/a";
}

Please note : Don't forget the constraint of 'where T : class'. We put this on to ensure that we will only deal with reference types. Otherwise the statement 'yield return (c as T)' would fail since casting the control c to type T can obviously only occur with Control types (which are reference types!).

Just a Word

With this example, I simply tried to bring across a different way of looking at tackling these kind of tasks.

Using it within a Form to find TextBoxes, you may not directly notice a speed or memory improvement but then again try using it for an iteration of List(s) that contains 1000s of entries and you'll soon spot the difference! Smile | :)

History

  • 1st June, 2007: Article published
  • 6th June, 2007: Article updated - statement change to (c is T) - thanks to PIEBALDconsult!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

marcel heeremans
Software Developer (Senior)
United Kingdom United Kingdom
In the early days I started developing using MS Access version 1 through all versions of VB followed now by .NET. Things are getting better!
 
I studied International Marketing but found the IT sector much more challenging. So straight from uni I took on some IT contract work in London in a buoyant market and never looked back.
 
If you wish to contact me then please do so on heeremans.marcel@googlemail.com

Comments and Discussions

 
SuggestionBetter use of delegate and update for 3.5+ PinmemberKlayVessel12-Jan-12 1:17 
GeneralBetter and more efficient approach PinmemberMember 224161228-Aug-10 10:18 
QuestionDoesn't this create a new iterator object for each GetControlsOfType call? Pinmemberadante4-May-10 4:35 
GeneralExcellent PinmvpLuc Pattyn10-Feb-10 3:22 
GeneralKudos. Elegant Solution Pinmembermdunn65-Sep-07 10:54 
QuestionWhy not use is? PinmemberPIEBALDconsult5-Jun-07 10:23 
AnswerRe: Why not use is? Pinmembermarcel heeremans5-Jun-07 23:20 
GeneralLooks Good, Slight Error PinmemberSteveC-A95-Jun-07 9:52 
GeneralRe: Looks Good, Slight Error PinmemberSteveC-A95-Jun-07 9:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 1 Jun 2007
Article Copyright 2007 by marcel heeremans
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid