Extension Methods Are Helper Methods That Taste Like Candy
Several people have asked me to write about C# extension methods, or to give my general guidance on them. I have to admit I have been waffling on the subject. It is a very hard subject to address. It almost feels like the goto statement.
But, I haven't really addressed extension methods even though I alluded to them in my post about static methods and they obviously can qualify as helper methods. Most of you who read my blog would probably assume that I am against extension methods since they are static methods and also helper methods.
I'm gonna start with a question / example for this one
I'm not going to say what I think just yet. Instead, I want to present you with a situation, and I want you to think about it from an object oriented and system design perspective.
Let us say that we have an application that frequently has the need to use a string as a URI. Perhaps in that application you will generally have the need to have a browser goto that URI.
You might have some code that would look like:
Not hugely complex code, but let's say that it is sprinkled throughout your application in different forms because it is a cross cutting concern. How does this code make you feel?
I want you to think about the differences between these two examples just in terms of readability. Nothing else. Put away your other concerns for now, and just answer the question. “Which is easier to read?”
Ultimately the art of software development is about making trade-offs
The true art part of software development is involved in making trade-offs between different values. We have perhaps an ordering in our mind of what practices are of what precedence or importance. Sometimes to get flexibility to you have to sacrifice simplicity. Sometimes to get clarity, it comes at the cost of length. Sometimes to get simplicity, it comes at the cost of efficiency. It is somewhat situational as to which of these values trump the other, although I think the software developers you most respect are going to have different basic weights to these values.
My main point here is that there is one value or principle in software development (of code that must be maintained), that trumps almost every other value. That value is readability. Perhaps the most important thing we can do is make our code readable. Derick Bailey has an excellent post on Expressive Code that really illustrates this point by showing how he is willing to double the size of his code to make it more expressive.
In the example I have shown you we are actually reducing the lines of code, even down to 1, and making it more expressive or at least just as expressive. We are also making it easier to read (because it is plainer English), and easier to write. But, what's the price we are paying for this? The price we are paying is that we are adding static helper methods. Yes, this is generally a bad thing. Yes, if you point-blank ask me if static methods are bad, I will not even say “it depends”, I will just tell you “yes.” Yes, if you ask me if helper methods are bad, I will give you the same answer. No, I'm not contradicting myself. What I am doing is making an understood trade-off in value.
I'm not going to suggest putting lots of code into extension methods just because it is cool or it is the easy thing to do, but when you see a situation where you can get a large improvement in readability and reduce lines of code, you might want to consider that an extension method might be worth the trade-off.
Taking one more look at the example
One thing to consider about this example is that if we apply the normal logic for getting rid of helper methods, how will that work? Normally we would ask where a helper method should really live. Who does it actually belong to? In this case, we could say that the ToUri is appearing on string so obviously it belongs on string. The problem with that is, do you really want to couple your string implementation to Uri? Probably not.
It also works fine to have string as a constructor to Uri, because string happens to be treated as a base type in C#, but if string were some other class, you would have to strongly consider whether you wanted to couple two classes together at all. Yes, yes, you can create an interface and use dependency injection to decouple the classes, but that has a cost. That cost can be high.
Extension methods can be a great way to conditionally connect two concrete classes in a system together in certain paradigms. If you want to play in the playground of Uri's and webpages, import the extension methods that lets you treat strings as Uris and lets you automatically goto Uris. If you don't, just don't import that extension method. If you want your IEnumerable to be able to do select and where clauses, import the LINQ extension methods.
Speaking of LINQ
It is pretty hard for us to say extension methods are bad, then praise LINQ. If you like LINQ, and you like the syntax, then you have to in some way accept that extension methods can conditionally be useful and good. Microsoft has basically built into the language a guidance by adding LINQ. Since so much of the code you will be writing will be written against their API, it is not advisable to try and fight against extension methods. The battle has already been won.
I am just going to briefly touch on this subject, since I want to cover it in much more detail in a separate post. One thing that is cool about extension methods, and is one of the huge problems about regular old static helper methods, is discoverability. The problem with regular static helper methods is they are hard to find, so you may not even know they exist. With extension methods, they show up and are discoverable from the object they operate on. This may not seem like a big deal, but when working with large or complex systems, disocoverability of APIs is very, very important.
Wrapping it up
In short, I am not advocating the overuse of extension methods, but I am giving them my official seal of approval. I believe overall they present a positive value to C# as a language because of the uses where they greatly simplify readability and reduce code. It is still very important to remember to carefully think about when and how to construct extension methods. Another thing to think about as far as extension methods are concerned is that they are not polymorphic since they are static.