Extension methods
were introduced with the .NET 3.5 framework as a mechanism to add methods to extend
existing types without modifying the original assembly. This is how the Linq methods
were implemented to enable some very powerfull predicate function based operations
to be performed over all existing collection types.
Searching for web controls on a page is one of those tasks that seems to come up
for all kinds of reason while programming using web forms. I was reminded of this
problem recently:
I'm personally favoring the MVC framework
now, however, while at work the other day one of my collegues was working through
an old web forms project of mine where a variable number of checkbox controls were
being rendered in two separate lists on the page. Them, on post-back he needed to
get a list of the checkboxes that were now checked.
Lambda expressions combined with recursive calls are a very powerful way of seaching
through a pages controls. Originally I just used a simple funciton defined in the
code-behind, however a much cleaner and reusable method would be to define the functions
on the control class its self.
That's where extension methods come in. The code below shows a nice simple example
of a couple of useful control search functions which then appear inside any object
inheriting from System.Web.UI.Control.
To define extension methods on an exising class in C# you would do the following:
Firstly create a public static class with whatever name you like in whatever namespace
you like (I've defined mine in DanielBradley.WebControlExtensions.ControlExtensions).
Secondly, define a public static (shared) function where you specify the first parameter
with the keyword "this" and it's type as the type of the class you
want to extend.
Finally, define everything else exactly how you would normally, with the correct
return type, your other parameters and you simply look at your first parameter as
the current object the functions is running in.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
namespace DanielBradley.WebControlExtensions
{
public static
class ControlExtensions
{
public static
Control FirstOrDefault<TSource>(this
Control ctrl, Func<TSource,
bool> predicate) where
TSource : Control
{
Type targetType =
typeof(TSource);
foreach (Control
c in ctrl.Controls)
{
if (c.GetType() == targetType && predicate((TSource)c))
{
return c;
}
Control recMatch = c.FirstOrDefault<TSource>(predicate);
if (recMatch != null)
{
return recMatch;
}
}
return null;
}
public static
IEnumerable<TSource> FindRecursive<TSource>(this Control
ctrl, Func<TSource,
bool> predicate) where TSource :
Control
{
if (ctrl == null
|| ctrl.Controls.Count == 0)
return new
List<TSource>();
return ctrl.Controls.OfType<TSource>().Where(predicate).Union(
ctrl.Controls.Cast<Control>().SelectMany(c
=> c.FindRecursive<TSource>(predicate)));
}
public static
IEnumerable<TSource> FindRecursive<TSource>(this Control
ctrl, Func<TSource,
bool> predicate, int depthLimit)
where TSource : Control
{
if (ctrl == null
|| ctrl.Controls.Count == 0)
return new
List<TSource>();
if (depthLimit == 0)
{
return ctrl.Controls.OfType<TSource>().Where(predicate);
}
else
{
return ctrl.Controls.OfType<TSource>().Where(predicate).Union(
ctrl.Controls.Cast<Control>().SelectMany(c
=> c.FindRecursive<TSource>(predicate, depthLimit - 1)));
}
}
}
}
Extension methods in C# are implemented as a specific language feature, however,
you can also implement
extension methods in VB.NET through use of attributes:
Firstly import System.Runtime.CompilerServices.
Secondly define your (public) function, add the Extension attribute and define a
first parameter as the type you want to add the function to (into which the object
your function is running on is passed in to).
Here's the equivilant VB code (actually re-written not auto-converted :)
Imports System.Runtime.CompilerServices
Imports System.Web.UI
Public Module
ControlExtensions
<Extension()> _
Public Function
FirstOrDefault(Of TSource
As Control)(ByVal ctrl
As Control, ByVal predicate
As Func(Of TSource,
Boolean))
Dim targetType = GetType(TSource)
For Each c
As Control In
ctrl.Controls
If c.GetType.Equals(targetType)
AndAlso predicate(c) Then
Return c
End If
Dim recMatch = c.FirstOrDefault(predicate)
If
recMatch IsNot Nothing
Then
Return recMatch
End If
Next
Return Nothing
End Function
<Extension()> _
Public Function
FindRecursive(Of TSource
As Control)(ByVal ctrl
As Control, ByVal predicate
As Func(Of TSource,
Boolean))
If ctrl Is
Nothing OrElse
ctrl.Controls.Count = 0 Then
Return New List(Of
TSource)
Return ctrl.Controls.OfType(Of
TSource).Where(predicate).Union( _
ctrl.Controls.Cast(Of Control).SelectMany(Of TSource)(Function(c)
c.FindRecursive(predicate)))
End Function
<Extension()> _
Public Function
FindRecursive(Of TSource
As Control)(ByVal ctrl
As Control, ByVal predicate
As Func(Of TSource,
Boolean), ByVal depthLimit
As Integer)
If ctrl Is
Nothing OrElse
ctrl.Controls.Count = 0 Then
Return New List(Of
TSource)
If depthLimit = 0
Then
Return ctrl.Controls.OfType(Of
TSource).Where(predicate)
Else
Return ctrl.Controls.OfType(Of
TSource).Where(predicate).Union( _
ctrl.Controls.Cast(Of Control).SelectMany(Of TSource)(Function(c)
c.FindRecursive(predicate, depthLimit - 1)))
End If
End Function
End Module
Having written these examples there's loads of ideas of useful stuff that's
springing to mind that you could extend from here:
- Make the predicate optional.
- Write some generic tree search implementations on the IEnumerable interface.
- Any other recursive based algorithms that could be useful on sets?
Anyway, there's the post, hope this is useful - might perhaps pad this out a
little then move it into a project on github or something if it's potentially
useful to people.
Daniel