2006.09.22 12:53 PM

ASP.NET Control Enumeration

The System.Web.UI.HtmlControls and WebControls on an ASP.NET page marked runat="server" are represented server-side as a hierarchy. Everything else on the page (non-server-side controls, free text, white-space, etc.) appears within the hierarchy as text within LiteralControl objects. The page itself, which derives from System.Web.UI.Control, represents the top of the hierarchy.

Each Control-derived object exposes its child controls via the Controls property. The Controls property returns a ControlsCollection object, which implements ICollection and, therefore, IEnumerable, so its contents can be enumerated easily using foreach. However, in order to enumerate all of the controls on a page or beneath some arbitrary control on a page, some extra work is required.

One common way to do this is with a recursive tree traversal routine like this:

private void ProcessControls(Control control) {
  foreach (Control c in control.Controls) {
    // using c, check something and/or do something...
    // using c, check for something else and/or do something else...
    if (c.Controls.Count > 0) {
      ProcessControls(c);
    }
  }    
}

That's preorder traversal, and here's postorder:

private void ProcessControls(Control control) {
  foreach (Control c in control.Controls) {
    if (c.Controls.Count > 0) {
      ProcessControls(c);
    }
    // using c, check something and/or do something...
    // using c, check for something else and/or do something else...
  }    
}

Note that the recursive call in each routine is conditioned on whether the control has child controls. This isn't strictly necessary, but there's really no point in pushing stuff on the stack, calling a method, and instantiating an enumerator unless there's a good reason for doing it. It's a lot cheaper to just check the collection's count before making the call.

Anyhow, these approaches work, but there are some potential issues to consider.

The first thing is that in the code above the control passed to the routine isn't processed - only its child controls are. In order to process the passed control, the processing logic, which may involve some in-line code, or a call to a routine, or some conditional logic, or whatever, would have to be repeated at the top or bottom of the routine (depending on whether using pre or postorder traversal). Processing of the passed control may not always be a requirement, but when it is, it's kind of drag to deal with the repetition.

Secondly, it's difficult to generalize this type of recursive traversal logic for use with multiple separate operations, perhaps to occur at different times, on different pages, for different post-back events, or whatever. You might add some additional parameters with which to condition and/or vary the processing logic, but you would still wind up burying it (or calls to it) in the traversal logic. You might also pass a delegate (or array of delegates) or raise a custom event during the traversal, but those approaches are a little obscure. More likely what will happen is the traversal logic will be repeated multiple times, once for each distinct operation, resulting in unnecessary code repetition and control enumeration.

One way around this is to use the traversal logic to flatten the control hierarchy into an array which can then be used to subsequently process the controls independent of the traversal. The following code does this using recursive preorder traversal and a generic List (.NET 2.0 only; for earlier versions use an ArrayList or some other dynamically expanding collection):

List<Control> controls = new List<Control>();

FillControlList(this.Form);

private void FillControlList(Control control) {
  // if desired: controls.Add(control)
  foreach (Control c in control.Controls) {
    controls.Add(c);
    if (c.Controls.Count > 0) {
      FillControlList(c);
    }
  }
}

...

foreach (Control c in controls) {
  // using c, check something and/or do something...
  // using c, check for something else and/or do something else...
}

The result is a general purpose routine that fills a strongly typed list with all of the controls. The resulting list can then be enumerated and processed as many times and in as many different ways as necessary (whenever, wherever), independent of the recursive traversal logic. Also, as per the comment in GetControls, it's very easy to include the passed control in the list, if needed.

The downside to this approach, for folks counting bytes and cycles, is that it repeats storage of the control references and requires multiple iterations over the controls (once for initialization and at least once for processing). In addition, the resulting static list of controls is immune to post-initialization property changes that may render some controls irrelevant to the subsequent processing.

For instance, if it's only necessary to enumerate visible controls, but after list initialization (or perhaps during list enumeration) some controls are made invisible, then checking for positive control visibility while filling the static list of controls won't suffice. Doing that will likely result in the enumeration of more controls than is necessary (perhaps many more, as the value of the Control type's Visible property is inherited from parent controls when parents are made invisible) and it will require repeated visibility checks before and/or in each piece of processing logic.

One way to overcome these issues is with C# 2.0 iterators and the generic IEnumerable<T> interface. These can be used to traverse and process the controls one at a time, without the overhead and inflexibility of a static list. The following code uses this approach to perform the same preorder traversal as above:

private IEnumerable<Control> GetControls(Control control) {
  // if desired: yield return control;
  foreach (Control c in control.Controls) {
      yield return c;
      if (c.Controls.Count > 0) {
        foreach (Control cc in GetControls(c)) {
          yield return cc;
        }
      }
    }
  }
}

...

foreach (Control c in GetControls(this.Form)) {
  // using c, check something and/or do something...
  // using c, check for something else and/or do something else...
}

The result is a general purpose routine requiring no initialization and only one enumeration of the controls for each operation. And, assuming you stick with preorder traversal, a check for control visibility can easily be added to GetControls, allowing for short-circuiting of child control enumeration by setting a control's Visible property to false in the processing logic.

If you find the inner foreach loop to be kind of smelly, I agree. Unfortunately, C# won’t unwind an IEnumerable<T> object for us on a yield, so we can’t just write:

      ...
      if (c.Controls.Count > 0) {
        yield return GetControls(c);
      }
      ...

Likewise, it won’t chain IEnumerable<T> calls for proper yield handling if we try this:

      ...
      if (c.Controls.Count > 0) {
        GetControls(c);
      }
      ...

So, we’re stuck with the inner loop.

As useful as C# iterators are it’s important to remember that they aren’t magic. There is a cost associated with piling compiler-generated iterator state on the stack with each yield, particularly when done recursively. See Stephen Toub’s posts on “Recursive iterator performance” for more:

While the ability to use iterators recursively is obviously a powerful feature, it should be used with care as there can be serious performance implications. Each call to [the recursive routine] requires an instantiation of the compiler-generated iterator, so recursively iterating over a deep tree could result in a large number of objects being created behind the scenes.

Luckily, as Mr. Toub reminds us in “Recursive iterator performance, part 2”, anything done recursively using an implicit stack can be done iteratively using an explicit stack. So, for grins, here’s the same preorder iterator-based traversal code from above with the recursion factored out:

private IEnumerable<Control> GetControls(Control control) {
  Stack<Control> stack = new Stack<Control>();
  stack.Push(control);
  while (stack.Count > 0) {
    control = stack.Pop();
    yield return control;
    for (int i = control.Controls.Count - 1; i >= 0; i--) { 
      stack.Push(control.Controls[i]);
    }
  }
}

It requires another stinky inner loop to build the stack (the loop is reversed to preserve preorder). I’m never a fan of touching (enumerating, storing) things more than once, but I suppose that if you’re regularly busting the call stack with a huge control tree this is at least a workable alternative.

By the way, contrary to the earlier approaches, that one assumed that the passed in control should be included, not for any reason other than it made initialization of the stack easier. Excluding it is possible, but it makes the code a little less clear:

private IEnumerable<Control> GetControls(Control control) {
  Stack<Control> stack = new Stack<Control>();
  while (control != null) {
    for (int i = control.Controls.Count - 1; i >= 0; i--) { 
      stack.Push(control.Controls[i]);
    }
    control = null;
    if (stack.Count > 0) {
      control = stack.Pop();
      yield return control;
    }
  }
}

There are a couple of other ways to arrange this, but I can't say that any of them are clearer.


Comments

Forgot to link to Mike Stall's take on yielding enumerators:

http://blogs.msdn.com/jmstall/archive/2005/08/12/nesting_yield.aspx

ewbi.develops | 2006.09.25 09:21 PM


TrackBack

TrackBack URL:  http://www.typepad.com/services/trackback/6a00d8341c7bd453ef00d8342b759453ef

Listed below are links to weblogs that reference ASP.NET Control Enumeration:

» Followup on Enums from Patrick Steele's .NET Blog
In a follow-up to my last post, Eric Bachtal pointed out that I could use a class with only public constants [Read More]

Tracked on Sep 23, 2006 9:18:02 AM