C# 3.0 New Feature for the Week #4: Extension Methods (and a PowerCollections Bonus!)
Consider the following. You need to sort an IEnumerable<T>. By default, Sort is only available for T[] (arrays) and List<T> (generic list). So you write something like this (probably not the most efficient implementation, this is just an example):
namespace Utils
{
public static class CollectionUtils
{
public static IEnumerable<T> Sort<T>(IEnumerable<T> collection)
{
List<T> toSort = new List<T>(collection);
toSort.Sort();
return toSort;
}
}
}
And you'll use it like this:
IEnumerable<string> sorted = CollectionUtils.Sort(myCollection);
Which is OK, but not as pretty as myCollection.Sort(), a much cleaner syntax. With C# 3.0 new feature of Extension methods, it is now possible to achieve just that, with only one little change to our method signature:
public static IEnumerable<T> Sort<T>(this IEnumerable<T> collection)
Note the "this" key word before the parameter. Now in every code that references the Utils namespace, we can have pretty syntax:
using Utils;
...
IEnumerable<string> sorted = (new string[]{"hello", "friend"}).Sort();
Actually, Microsoft created this syntax for the sake of Linq, which requires a lot of operations on Enumerables. You will note now that in every time you use an IEnumerable<T> you get a whole bunch of extra methods:
This is thanks to the System.Linq.Enumerable static class which provides all these static methods on IEnumerable<T>. Still, it doesn't have a definition for Sort, and maybe some other methods you have in your own CollectionUtils class (I'm sure we all got one). Therefore, nothing stops you from going right now and changing all of the methods in your CollectionUtils/StringUtils/WhateverUtils to use the new syntax. Older code will compile just fine, since you can still call the methods with the direct syntax.
PowerCollections and Extension Methods
In fact, I liked the extension methods syntax so much, that I had to go and change the PowerCollections library that I use extensively. This is an open source library, which contains a lot of collections and data structures that do not come by default with the framework. It also has an Algorithms static class which falls beautifully to the "must be converted to use extension methods category". And so, I did. Now I have this:
using Wintellect.PowerCollections;
...
public void PowerCollectionsDemo()
{
string[] array = new string[] { "G", "H", "A", "B" };
array.Sort(); //Yey, can delete that from my own CollectionUtils
array.StableSortInPlace(); //A bit more clever
int result = array.LexicographicalCompare(new string[] {"B", "C", "D" });
Console.WriteLine(array.Reverse().AsString());
//... And a lot more
}
This is just an example of some of the methods you get with PowerCollections, and it all becomes even nicer with the new syntax. Sort is there, as you can see (and it works for any collection), but also dozens of other methods. Note the usage of the "AsString" method. This method recursively converts a collection to a string representation, but it was used to be called ToString. I had to go and change that to AsString since calling array.ToString() would have triggered the Array type member ToString, which would have printed something like System.String[]. Whenever the compiler has a conflict between a class method and an extension method, it will choose the class method.
Anyway, you can download the Binaries and Source for the changed PowerCollections right here.
How is it implemented?
Let's reflect on our last method.
public void PowerCollectionsDemo()
{
string[] array = new string[] { "G", "H", "A", "B" };
Algorithms.Sort<string>(array);
Algorithms.StableSortInPlace<string>(array);
int result = Algorithms.LexicographicalCompare<string>(array, new string[] { "B", "C", "D" });
Console.WriteLine(Algorithms.AsString<string>(Algorithms.Reverse<string>(array)));
}
Well, not much to it. All our extension method calls were converted by the compiler to call the static Algorithms class directly. One thing that should be mentioned is that you should always remember that this is no more than syntactic sugar for static methods. Your extension methods should always behave as such - that is, do not maintain state.
Until the next week, happy coding.