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.
  1. 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 extends DateTime. But it’s not reasonable the other way around with days.AddToDate(date) that extends int. That’s because this method is more relevant to DateTime than to int.
  2. 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.
  3. 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;
}
  1. 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.

  1. 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.

  1. 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 with mortar.BuildHouse(brick). Since neither is really more suitable than the other, this probably shouldn’t be an extension method.
  2. 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.

  1. 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.
  2. 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.

M ↓   Markdown
?
Anonymous
0 points
6 years ago

While I generally agree with #1, I have occasional seen some really nice examples that break this rule. It may be one where you should probably abide by the rule, but you can break it from time-to-time to improve readability.

For example, Humanizer allows you to write things like 30.Seconds() to get a TimeSpan object representing, well, 30 seconds. This is much easier to read than TimeSpan.FromSeconds(30)

?
Anonymous
0 points
6 years ago

That's a good example, I guess there's always an exception to the rule:)

?
Anonymous
0 points
6 years ago

Out of all these guidelines, #2 is the one I use the most. It has yet another benefit in my opinion; it allows to greatly reduce interfaces size. The less surface an interface exposes, the most desirable; and in a practical way, this also reduces the implementation effort. My extensions on interfaces usually consist in "overloads" with defaults or more complex logic built out of the building blocks the interface provides.

?
Anonymous
0 points
6 years ago

Extending interfaces can be very powerful. Especially if you have a big code base with a ton of interfaces

?
Anonymous
0 points
6 years ago

I think that when discussing extension methods in this day and age, it would be appropriate to mention the new C# 8.0 default implementation in interfaces which is somewhat related IMO.
https://devblogs.microsoft....

?
Anonymous
0 points
6 years ago

Absolutely. Can be a good alternative when you own the interface.

?
Anonymous
0 points
6 years ago

Hi Michael, you always have valuable insights and I enjoy reading your blogs. However, it would be wonderful if you could have a print version link so that I can print out your articles. Could please you make one?

?
Anonymous
0 points
6 years ago

Thanks, Jeff. Sure, I added to my TODO list. Now it's just a matter of when I'll get to it.

?
Anonymous
0 points
6 years ago

Do you advocate keeping the extension code close to the code that it extends (PersonExtensions.cs next to the Person.cs file), or would you put all extension method classes in a folder called, maybe, ExstensionMethods.

?
Anonymous
0 points
6 years ago

Alex, I think the "ExtensionMethods" folder is better, but I don't feel that strongly about it (it's not a must). My reasoning is that if you have an extension method instead of an instance method, then you wanted some kind of separation of concerns. That's why a different location is probably better.

?
Anonymous
0 points
6 years ago

Good tips Michael.
I found #2 helpful lot of times. Adding an example for #2 would clear things for those who are not sure how to extend an interface

?
Anonymous
0 points
6 years ago

Thanks, Karthik. Yeah, it's a powerful feature.
A simple example can be

[code lang=text]
public void AddToLog(this IError)
{
// ...
}
[/code]