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