In late 2007, C# 3.0 was released with some of the best features in the language. It was in this version that C# took a huge leap forward, opening a gap between it and its foremost competitor Java.
Those features included:
- LINQ
- Lambda Expressions
- Expression trees
- Anonymous types
- Extension methods
- And some others
In a way, the C# 3.0 feature set was created around the LINQ feature. Every feature is used in LINQ, and in fact necessary to create the excellent method syntax (aka fluent-syntax) we mostly use today.
Let’s talk about extension methods. They are probably most widely used in the LINQ feature. Any operation like Select
, Where
, OrderBy
, etc. is an extension method. These methods can be used with collections like array, List<T>
, and Dictionary<T>
, even though the methods aren’t actually included in those classes. A beautiful concept really.
There’s no argument that extension methods can make our code much nicer. Consider the following comparison:
public IEnumerable<Customer> WithExtensionMethods(List<Customer> customers)
{
return customers
.Where(c => c.Age > 30 && c.Age < 40)
.Where(c => c.Gender == "Male")
.OrderBy(c => c.Transactions);
}
public IEnumerable<Customer> WithoutExtensionMethods(List<Customer> customers)
{
return Enumerable.OrderBy(Enumerable.Where(
Enumerable.Where(customers, c => c.Gender == "Male"),
c=>c.Age > 30 && c.Age < 40), //where
c=>c.Transactions);//orderBy
}
Don’t know about you, but I wouldn’t want to live in a world where I have to write that 2nd type of code.
So extension methods are great, but when should we use them? And when shouldn’t we? Let’s talk guidelines.
Extension Methods Guidelines
Like most coding style guidelines, the following is a bit opinionated. Feel free to add some of your own opinions in the comments below.
- Use an extension method when the functionality is most relevant to the extended type. For example, it’s reasonable to have an extension method
date.AddDays(numDays)
that extendsDateTime
. But it’s not reasonable the other way around withdays.AddToDate(date)
that extendsint
. That’s because this method is more relevant toDateTime
than toint
. - Use extension methods on interfaces to add common functionality to classes that don’t have a common base class. That’s the case with LINQ methods extending the
IEnumerable
interface. - You should have a good reason to use an extension method instead of an instance method. One such reason is when dealing with classes that aren’t your own, like a class from a 3rd party library. For example, you can extend
FileInfo
from the .NET framework:
public static int CountLines(this FileInfo fileInfo)
{
return File.ReadAllLines(fileInfo.FullName).Length;
}
- You can use extension methods to achieve separation of concerns when you don’t want to mix some dependency with the extended type. For example, you might want to extend
Customer
with a method like this:
public static AdjustLastSeen(this Customer customer, TimeZoneManager timeZoneManager)
{
// ...
}
In the above case, if you don’t want Customer
to have a dependency on TimeZoneManager
, you can achieve this with an extension method. Note that in similar cases, extension methods might not be the best choice.
- By using extension methods with a return type, you can achieve a functional programming syntax. For example:
public static IEnumerable<Customer> AboveAge(this IEnumerable<Customer> customers, int age)
{
return customers.Where(c => c.Age > age);
}
public static IEnumerable<Customer> OnlyMale(this IEnumerable<Customer> customers)
{
return customers.Where(c => c.Gender == "Male");
}
public static IEnumerable<Customer> OnlyFemale(this IEnumerable<Customer> customers)
{
return customers.Where(c => c.Gender == "Female");
}
// usage:
var potentialBuyers = customers.AboveAge(42).OnlyFemale();
That’s not to say you should never use extension methods without a return type.
- When you’re not sure which Type is the one to extend, don’t use extension methods. For example, to build a house from brick and mortar I can extend brick with
brick.BuildHouse(mortar)
, or I can extend mortar withmortar.BuildHouse(brick)
. Since neither is really more suitable than the other, this probably shouldn’t be an extension method. - Avoid having a state in your extension methods. It’s best practice to avoid having a state in static classes entirely because it makes them much harder to mock for tests.
One possible exception is if you want to use memoization in your extension methods. That’s the caching of inputs and output to reuse results. Use it with care, since you have to consider eviction policies and object lifetime. Well, that’s a subject for another blog post.
- Avoid extending primitives. There are a couple of reasons for that. For one thing, it will be very hard to find a method that’s most relevant to the primitive (see item #1). Besides, there’s the technical issue of your IDE (Visual Studio) showing this method in auto-complete intellisense whenever you’re using that primitive.
- Group extension method for the same type in the same class. It’s easier to find those methods in the code. Besides, they all should have a lot in common since they are all relevant to the same type.
Summary
Extension methods are an excellent addition to the C# language. They enable us to write nicer, more readable code. They allow for more functionally styled programming, which is very much needed in an object-oriented language.
They also should be used with care. Inappropriate use of extension methods can create less readable code, make it harder to test and even be prone to errors. For example, when you extend the int
type with the method AddBalanceToBankAccount
, that extension method will appear in Visual Studio’s intellisense for any integer, forever tempting you to add that balance.