Click here to Skip to main content
15,881,204 members
Articles / Mobile Apps
Article

A Very Efficient Generic Recursive Iterator using Yield Return

Rate me:
Please Sign up or sign in to vote.
4.00/5 (20 votes)
1 Jun 2007CPOL3 min read 52.5K   39   9
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...

C#
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:

C#
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!!

C#
public delegate bool delTypeCheck(Type type);

/// <summary>
/// Examines the full content of this control's Controls 
/// collection and returns the enumerator instead of a generic list.
/// </summary>
/// <typeparam name="T">The Type we are looking for - 
/// we will also treat any derived types as a match!</typeparam>
/// <param name="ctrMain">The control to examine.</param>
/// <param name="IsExemptCheck">A delegate allowing us to specify 
/// which controls to exempt from examining.</param>
/// <returns>The actual enumerator allowing us to NOT have 
/// to create a helper intermediate list.</returns>
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:

C#
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.

C#
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! :)

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)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
Ever since my dad bought me a Commodore 64 (some years back) I have been hooked to programming. For most of my working career I have worked intensely with C# (WinForms and WPF) but a few years back I started to investigate browser technologies and frameworks which are much more powerful than I thought at first!

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@gmail.com

Comments and Discussions

 
SuggestionBetter use of delegate and update for 3.5+ Pin
KlayVessel12-Jan-12 1:17
KlayVessel12-Jan-12 1:17 
GeneralBetter and more efficient approach Pin
Member 224161228-Aug-10 10:18
Member 224161228-Aug-10 10:18 
QuestionDoesn't this create a new iterator object for each GetControlsOfType call? Pin
adante4-May-10 4:35
adante4-May-10 4:35 
GeneralExcellent Pin
Luc Pattyn10-Feb-10 3:22
sitebuilderLuc Pattyn10-Feb-10 3:22 
GeneralKudos. Elegant Solution Pin
mdunn65-Sep-07 10:54
mdunn65-Sep-07 10:54 
QuestionWhy not use is? Pin
PIEBALDconsult5-Jun-07 10:23
mvePIEBALDconsult5-Jun-07 10:23 
AnswerRe: Why not use is? Pin
marcel heeremans5-Jun-07 23:20
marcel heeremans5-Jun-07 23:20 
You are right - in this case the statement could be replaced by c is T.
However, I left the full line in there since you may just want to search for a specific type in which case the 'is' statement would leave you no choice and would return the type you search for as well as all derived types!

Valid point though - I will make a note of this in the article! Thanks
Marcel
GeneralLooks Good, Slight Error Pin
IgDev5-Jun-07 9:52
IgDev5-Jun-07 9:52 
GeneralRe: Looks Good, Slight Error Pin
IgDev5-Jun-07 9:53
IgDev5-Jun-07 9:53 

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

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