15

I recently saw a code base which had a data class Address defined somewhere and then in a different place:

fun Address.toAnschrift() = let { address ->
    Anschrift().apply {
        // mapping code here...
    }
}

I found it confusing to not have this method on address directly. Are there any established patterns or best practices when to use extension methods? Or does the book on that still have to be written?

Note that despite the Kotlin syntax in my example, I am interested in general best practice as it would also apply to C# or other languages with those capabilities.

reevesy
  • 107

4 Answers4

21

Extension methods do not provide something that can't be done in other ways. They are syntactical sugar to make development nicer, without providing an actual technical benefit.


Extension methods are relatively new. Standards and conventions are usually decided by popular opinion (plus some long term experience on what ends up being a bad idea), and I don't think we're at the point where we can draw a definitive line that everyone agrees with.

However, I can see several arguments, based on things I've heard from coworkers.

1. They are usually replacing static methods.

Extension methods are most commonly based on things that would otherwise have been static methods, not class methods. They look like class methods, but they aren't really.

Extension methods simply shouldn't be considered an alternative for class methods; but rather as a way to give otherwise syntactically ugle static methods a "class methody" look and feel.

2. When your intended method is specific to a consuming project but not the source library.

Just because you develop both the library and the consumer doesn't mean that you're willing to put the logic wherever it fits.

For example:

  • You have a PersonDto class coming from your DataLayer project.
  • Your WebUI project wants to be able to convert a PersonDto to a PersonViewModel.

You can't (and wouldn't want to) add this conversion method to your DataLayer project. Since this method is scoped to the WebUI project, it should live inside that project.

An extension method allows you to have this method globally accessible within your project (and possible consumers of your project), without requiring the DataLayer library to implement it.

3. If you think data classes should only contain data.

For example, your Person class contains the properties for a person. But you want to be able to have a a few methods that format some of the data:

public class Person
{
    //The properties...

    public SecurityQuestionAnswers GetSecurityAnswers()
    {
        return new SecurityQuestionAnswers()
        {
            MothersMaidenName = this.Mother.MaidenName,
            FirstPetsName = this.Pets.OrderbyDescending(x => x.Date).FirstOrDefault()?.Name
        };
    }
}

I've seen plenty of coworkers who hate the idea of mixing data and logic in a class. I don't quite agree with simple things like formatting data, but I do concede that in the above example, it feels dirty for Person to have to depend on SecurityQuestionAnswers.

Putting this method in an extension method prevents sullying the otherwise pure data class.

Note that this argument is one of style. This is similar to partial classes. There is little to no technical benefit to doing so, but it allows you to separate code into multiple files if you deem it cleaner (e.g. if many people use the class but don't care about your additional custom methods).

4. Helper methods

In most projects, I tend to end up with helper classes. These are static classes that usually provide some collection of methods for easy formatting.

E.g. one company I worked at had a particular datetime format they wanted to use. Rather than having to paste the format string all over the place, or make the format string a global variable, I opted for a DateTime extension method:

public static string ToCompanyFormat(this DateTime dt)
{
    return dt.ToString("...");
} 

Is that better than a normal static helper method? I think so. It cleans up the syntax. Instead of

DateTimeHelper.ToCompanyFormat(myObj.CreatedOn);

I could do:

myObj.CreatedOn.ToCompanyFormat();

This is similar to a fluent version of the same syntax. I like it more, even though there is no technical benefit from having one over the other.


All of these arguments are subjective. None of them cover a case that could otherwise never be covered.

  • Every extension method can easily be rewritten to a normal static method.
  • Code can be separated in files using partial classes, even if extension methods didn't exist.

But then again, we can take your assertion that they are never necessary to the extreme:

  • Why do we need class methods, if we can always make static methods with a name that clearly indicates that it's for a particular class?

The answer to this question, and yours, remains the same:

  • Nicer syntax
  • Increased readability and less code pedantry (code will get to the point in less characters)
  • Intellisense provides contextually meaningful results (extension methods are only shown on the correct type. Static classes are usable everywhere).

One particular extra mention though:

  • Extension methods allow for a method chaining coding style; which has become more popular lately, as opposed to the more vanilla method wrapping syntax. Similar syntactical preferences can be seen in LINQ's method syntax.
  • While method chaining can be done by using class methods as well; keep in mind that this doesn't quite apply to static methods. Extension methods are most commonly based on things that would otherwise have been static methods, not class methods.
Robert Harvey
  • 200,592
Flater
  • 58,824
7

I agree with Flater that there hasn't been enough time for convention to crystallize, but we do have the example of the .NET Framework Class Libraries themselves. Consider that when LINQ methods were added to .NET, they weren't added directly onto IEnumerable, but as extension methods.

What can we take from this? LINQ is

  1. a cohesive set of functionality that isn't always needed,
  2. is intended to be used in method-chaining style (e.g. A.B().C() instead of C(B(A))), and
  3. doesn't require access to private state of the object

If you have all three of those features, extension methods make a lot of sense. In fact, I would argue that if a set of methods has feature #3 and either one of the first two features, you should consider making them extension methods.

Extension methods:

  1. Allow you to avoid polluting the core API of a class,
  2. Not only allow method-chaining style, but allow it to more flexibly handle null values since an extension method called from a null value simply receives the null as its first argument,
  3. Prevent you from accidentally accessing/modifying private/protected state in a method that shouldn't have access to it

Extension methods have one more benefit: they can still be used as a static method, and can be passed directly as a value to a higher order function. So if MyMethod is an extension method, instead of say, myList.Select(x => x.MyMethod()), you could simply use myList.Select(MyMethod). I find that nicer.

4

The driving force behind extension methods is the ability to add functionality you need to all classes or interfaces of a certain type regardless of whether they are sealed or in another assembly. You can see evidence of this with LINQ code, adding functions to all IEnumerable<T> objects, whether they were lists or stacks, etc. The concept was to future proof the functions so that if you came up with your own IEnumerable<T> you could automatically use LINQ with it.

That said, there language feature is new enough that you don't have any guidelines on when to use or not use them. However, they do enable a number of things previously not possible:

  • Fluent APIs (see Fluent Assertions for an example of how to add fluent APIs to just about any object)
  • Integrating features (NetCore Asp.NET MVC uses extension methods to wire in dependencies and features in the startup code)
  • Localizing the impact of a feature (your example in the opening statement)

To be fair, only time will tell how far is too far, or at what point extension methods become a hindrance rather than a boon. As mentioned in another answer, there is nothing in an extension method that can't be done with a static method. However, when used judiciously they can make the code easier to read.

What about intentionally using extensions methods in the same assembly?

I think there is value in having core functionality well tested and trusted, and then not messing with it until you absolutely have to. New code that depends on it, but needs it to provide functionality solely for the benefit of the new code can use Extension Methods to add any features to support the new code. Those functions are only of interest for the new code, and the scope of the extension methods are limited to the namespace they are declared within.

I wouldn't blindly rule out the use of extension methods, but I would consider whether the new functionality should really be added to the existing core code that is well tested.

1

Are there any established patterns or best practices when to use extension methods?

One of the most common reasons for using extension methods is as a means of "extending, not modifying" interfaces. Rather than have IFoo and IFoo2, where the latter introduces a new method, Bar, Bar is added to IFoo via an extension method.

One of the reasons Linq uses extension methods for Where, Select etc is in order to allow users to define their own versions of these methods, which are then used by the Linq query syntax too.

Beyond that, The approach that I use, which seems to fit what I commonly see in the .NET framework at least is:

  1. Put methods directly related to the class within the class itself,
  2. Put all methods that aren't related to the core functionality of the class in extension methods.

So if I have an Option<T>, I'd put things like HasValue, Value, ==, etc within the type itself. But something like a Map function, which converts the monad from T1 to T2, I'd put in an extension method:

public static Option<TOutput> Map<TInput, TOutput>(this Option<TInput> input, 
                                                   Func<TInput, TOutput> f) => ...
Robert Harvey
  • 200,592
David Arno
  • 39,599
  • 9
  • 94
  • 129