would probably be too strong of a title for this post. Something more appropriate would be "Extension Methods Should Be Used Carefully." But that just doesn't excite the masses as much, does it? I bet your pulse is already spiked. I wonder if I'll get any "No they don't!" comments. Anyways, let us continue on. In one of my recent posts I wrote a Map method that looked like this:
public static IEnumerable<TResult> Map<TArg, TResult>(
this IEnumerable<TArg> list,
Func<TArg, TResult> func)
{
if (list == null || list.Count() == 0)
{
return new List<TResult>();
}
else
{
List<TResult> result = new List<TResult>();
result.Add(func(list.First()));
result.AddRange(Map(list.Skip(1)
.Take(list.Count() - 1).ToList(), func));
return result;
}
}
As one of my readers, James Curran, pointed out the implementation of the method was horribly inefficient. And yes, I was aware of that. I was trying to write it in a truly functional manner, but I agreed and updated the method with a more efficient one. Case closed...but not really. James' comments said that Count() was linear and so using it twice was really making an already inefficient algorithm the code equivalent of a beached blue whale (this is not a direct quote, he said it much more eloquently).
My first reaction was "Psssssha, whatever. Count() isn't linear, everyone knows that most every collection tracks the count as items are put in and taken out, and so when I call Count() I'll just get that integer returned to me. It doesn't count the total every time! Duuuuh. That would be a fairly naive implementation of Count! If someone implemented a collection with a Count() method that didn't operate in constant time I would slap them in their stupid face!"
But then I stopped for a second. And I realized that this extension method was being made for IEnumerable and not ICollection. So, do you see the problem here? IEnumerable doesn't have a Count() method, so how was I calling it on the variable then? I swear I compiled it!
Well, the short answer is that Count() is an extension method in the System.Linq namespace and it in fact can be linear time. It does try to cast to ICollection first, just in case you are using a collection it doesn't want to actually loop through the whole thing. But the point is that this method could have been linear time.
Check this bad boy out (this is the source from Reflector, come on Microsoft, get that .net framework sourcecode out!):
public static int Count<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
ICollection<TSource> is2 = source as ICollection<TSource>;
if (is2 != null)
{
return is2.Count;
}
int num = 0;
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
{
num++;
}
}
return num;
}
Doh!
So, now we are back to the title of this article. Be careful when using and implementing extension methods. Especially when you are implementing extension methods that are named the same as other commonly used methods. In this instance I just used Count() because I am so used to using it on collections and when the code compiled and the tests succeeded, I went with it (my bad). Little did I know that I was turning my already slow algorithm into a lumbering aquatic mammal. So in conclusion, please oh please use them carefully, extension methods are awesome and powerful but they really do reduce discoverability of code. So, next time you want to implement an extension method with a name like Count(), Length(), Remove(), etc... just think WWJD? (What would Justin Do?)