Wednesday, January 2, 2008

ChangeAll extension method alters every item in an iCollection(Of T)

My favorite feature of the new .NET 3.5 framework is extension methods.  Ever since I heard they would be available in this version of the .NET framework I have been designing functions in a way that they could be easily converted to extension methods.

For the unenlightened, extension methods are a new feature of the .NET 3.5 framework that allows you to extend any class — whether it is a built-in class, a third-party class, or your own class.

When you create an extension method, you can call the new method as if it were a native method of the class.

As an easy-to-understand example, I created an extension of the .NET String class called SuperTrim().  The method works like the built-in Trim() method, but instead of just trimming spaces from the front and back of a string, it trims any kind of control characters (any character with an ASCII code of less than 32, plus a few others).

After creating the extension method, I can apply SuperTrim() to an string in exactly the same way I would apply a regular Trim():

newString = oldString.SuperTrim()

Now that's cool! 

Last week I came upon a scenario in a program where I had to manipulate all of the items in a collection, so I tried using the built-in ForEach() method.  The trouble is that ForEach() is designed to do something with all the items in a collection, but not to change all of the items in a collection.

ForEach() passes each item, one by one, to your specified function, but it explicitly only passes the item by value, not by reference.  If your function changes the value passed, it is not changed in the collection — only within the scope of your function.

ForEach() also does not pass an index number or any other value that could help determine which item in the collection to change, so any attempt to use ForEach() to change items in the original collection would be a messy affair.

In response, I created an extension to iCollection called ChangeAll().  You pass a function to ChangeAll() that will receive each item, one by one, and the return value from your function will become the new value of that item in the collection.

Incidentally, ChangeAll() works great with the new lambda expressions — another wonderful .NET 3.5 feature!

Here is a sample usage of ChangeAll(), which trims all items in a collection (using a lambda expression):

myColl.ChangeAll(Function(value As String) Trim(value))

Pretty nice, eh?

If you don't want to use a lambda, you can just as easily use a regular function, like this:

Function TrimIt(ByVal value As String) As String
    Return Trim(value)
End Function

myCollection.ChangeAll(AddressOf TrimIt) 

There are lots of other uses, besides trimming every item in the collection.  The point is that now it is very easy to change each item of a collection, in a way that is simple and easy to read/understand — thanks to the new extension methods.

When creating ChangeAll() I first tried extending iEnumerable(Of T), but found that the base class does not have sufficient functionality to do what I needed.  So I then extended iCollection(Of T) instead, which can do some basic manipulation of the collection's items.

So without further adieu, here is the code, in its entirety.  This is the entire contents of a file I call "ColectionExtensions.vb" (you can give the file any name).  It includes inline documentation (yay!).

Option Explicit On
Option Strict On


Public Module CollectionExtensions

  ''' <summary>
  ''' Executes a transformation function on each item in
  ''' an ICollection(T) generic collection, replacing
  ''' each item with the return value.
  ''' </summary>
  ''' <typeparam name="T">
  ''' The type contained in Collection and that is passed
  ''' to ChangeFunction, as well as the type that must be
  ''' returned from ChangeFunction.
  ''' </typeparam>
  ''' <param name="Collection">
  ''' The collection on which ChangeFunction will be
  ''' applied to each item.
  ''' </param>
  ''' <param name="ChangeFunction">
  ''' Each item in Collection will be passed to this
  ''' function, and the return value will replace the
  ''' original item in the collection. If you wish an
  ''' item to remain unchanged, this function must
  ''' return the item's original value.
  ''' </param>
  ''' <returns>
  ''' Returns the collection, so this method may be
  ''' daisy-chained.
  ''' </returns>
  <Extension()> _
  Public Function ChangeAll(Of T)( _
    ByVal Collection As ICollection(Of T), _
    ByVal ChangeFunction As Func(Of T, T)) _
  As ICollection(Of T)

    If (Not Collection.IsReadOnly) Then
      Dim newCollection(Collection.Count - 1) As T

      Collection.CopyTo(newCollection, 0)

      For Each item As T In newCollection

    End If

    Return Collection
  End Function

End Module

Obviously, all of the above is written in VB, but exactly the same functionality can be created in C# with a few tweaks in syntax.

I hope that this extension can be useful to you, but more importantly, that it can demonstrate the usefulness of the technique, so that you can similarly extend classes in your applications.


At 6:27 PM, LottoMining said...

Yes, that does look like an extremely useful function. I wonder if MSFT added it to go with 3.5 Framework's ability to look at the actual source code of .NET … so if you see something you don't like you can use the Extension method(s) and tweak to your liking.

I'll add it to my list of things to look into when I get my hands on VS/SQL 2008 in February (if they have a huge launch like they did in November of 2005). I was most looking forward to the XML support built into "the new VB", the new BI stuff for the database and Office 2007 integration. So much technology so little time.

At 10:46 PM, Todd said...

Ah yes, that's another great new VB feature: Xml literals. What a tremendous time saver, and what a huge improvement in readability! Sometimes these new features are so radically good that I forget to use them.

The source code thing I'm only mildly excited about. To be honest there aren't all that many times I wished for it, and since it's available I have never had the need.

I think MS is going to do a big launch event, but I can't remember when it will be. I've been using VS2008 for a few months now, so to me it's already launched.

Post a Comment

<< Home