views:

578

answers:

3

I have a situation in a WebForm where I need to recurse throguh the control tree to find all controls that implement a given interface.

How would I do this?

I have tried writing an extension method like this

public static class ControlExtensions
{
    public static List<T> FindControlsByInterface<T>(this Control control)
    {
        List<T> retval = new List<T>();
        if (control.GetType() == typeof(T))                
            retval.Add((T)control);


        foreach (Control c in control.Controls)
        {
            retval.AddRange(c.FindControlsByInterface<T>());
        }

        return retval;
    }
}

But it does not like the cast to T on line 7. I also thought about trying the as operator but that doesn't work with interfaces.

I saw Scott Hanselmans disucssion but could not glean anything useful from it.

Can anyone give me any pointers. Thanks.

Greg

+9  A: 

I think you need to split this method into 2 parts

  1. Find Controls recursively
  2. Find Controls implementing the interface based off of #1

Here is #1

public static IEnumerable<Control> FindAllControls(this Control control) {
    yield return control;
    foreach ( var child in control.Controls ) {
        foreach ( var all in child.FindAllControls() ) {
          yield return all;
        }
    }
}

Now to get all controls of a type, use the OfType extension method

var all = someControl.FindAllControls().OfType<ISomeInterface>();
JaredPar
Nice! you missed static off you method declaration
Greg B
Also, line 3 needs to be "yield return control;" otherwise you get "Keyword 'this' is not valid in a static property, static method ..."
Greg B
Other than that. Perfect!
Greg B
Nice solution JaredPar. I haven't tried it, but it looks like you could replace the inner foreach loop with 'yield return child.FindAllControls();' ?
dewald
@dewald, unfortunately you can't because yield return won't expand an enumerable. You have to manually expand it with a for loop :(
JaredPar
+2  A: 

I would use the as keyword.

public static class ControlExtensions {
    public static List<T> FindControlsByInterface<T>(this Control control) where T : class
    {
        List<T> retval = new List<T>();
        T item = control as T;
        if (T != null)
            retval.Add(item);

        foreach (Control c in control.Controls)
            retval.AddRange(c.FindControlsByInterface<T>());

        return retval;
    }
}
Zyphrax
That would require a class constraint on T.
Tormod Fjeldskår
Hi Zyphrax, as I said above "I also thought about trying the as operator but that doesn't work with interfaces.". Otherwise this would have worked
Greg B
ok, I kinda assumed that would work. You can also use typeof(IInterface).isAssignableFrom(typeof(AClass)), I'd have to open Reflector to see the inner workings of that method. But JaredPar's solution is very nice.
Zyphrax
+1  A: 

Is the cast really necessary? If you have a control implementing T, it should not be. Also, take a look at the is keyword:

if (control is T)   
        retval.Add(control);
DanDan
Yes it is, because the compiler wont allow you to add the control of type Control to the List<T> because it doesn't know what T is and therefore you could be trying to do List<string>.Add(new BananaRama()); and It isn't going to like that
Greg B