Author avatar

Peter O'Hanlon

Writing and Using Extension Methods with C#

Peter O'Hanlon

  • Sep 16, 2019
  • 7 Min read
  • 166 Views
  • Sep 16, 2019
  • 7 Min read
  • 166 Views
Languages Frameworks and Tools
C#

What Are Extension Methods?

If we read the description of extension methods, it tells us that they were created to let us extend the behavior of existing types without having to derive from those types. This feature is particularly useful when we are dealing with types that are sealed so that we can't inherit from them or types where the only thing we have available to us is an interface definition.

In order to get a better understanding of what extension methods are and how to write them, we are going to look at a fictional TemperatureConverter class from a third-party DLL. This class accepts a temperature in Celsius when it's created and then has a method to convert that value to Fahrenheit. It would look like the below code (along with the corresponding interface).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace Pluralsight.Examples
{
  public interface IUnit
  {
    double Value { get; }
  }

  public sealed class TemperatureConverter : IUnit
  {
    public TemperatureConverter(double value)
    {
      Value = value;
    }
    public double Value { get; }

    public double ToFahrenheit() => Value * 9 / 5 +32;
  }
}
csharp

Creating Extension Methods

Two of the big problems with the TemperatureConverter class are that we have limited possible temperature conversions and that we cannot modify the class because it's from a third-party. What happens if we want to use more exotic conversions such as Romer or Reaumur? There's no way for us to modify the class and, as it's sealed, we cannot inherit from it and add our missing methods.

This is exactly the type of problem that extension methods were created to solve. Extension methods are an elegant way of introducing new functionality to classes without having to change the classes themselves. In order to create an extension method, we are required to create a static class with static methods. The first parameter in the static method must have the this keyword before the type. With this information, we are ready to add the ability to convert Celsius to Romer.

1
2
3
4
5
6
7
namespace MyLibrary.Extensions
{
  public static class TemperatureConverterExtensions
  {
    public static double ToRomer(this TemperatureConverter temperature) => temperature.Value * 21 / 40 + 7.5;
  }
}
csharp

In our ToRomer method signature, we see that the only difference between an extension method and an ordinary static method is the this keyword. Using this tells the compiler that the method is available to be used as an extension method. There is nothing stopping us from using this as an ordinary static method but we can now use the method as though it was available as part of the original TemperatureConverter class.

Let's test this out by writing a fictional Celsius class that creates an instance of our TemperatureConverter class and calls the ToFahrenheit method, from the original TemperatureConverter class, along with the ToRomer method, from our extension.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace MyLibrary.Models
{
  public class Celsius
  {
    private TemperatureConverter _converter;
    public Celsius(double celsius)
    {
      _converter = new TemperatureConverter(celsius);
    }
    
    public double Fahrenheit => _converter.ToFahrenheit();
    public double Romer => _converter.ToRomer();
  }
}
csharp

In this code sample, it really does look like both methods are part of the original class. But wait, if we were typing this inside Visual Studio, why don't we see Intellisense pop up? There's a reason that I included the namespace in each of these examples - the namespace is different for extension methods. This is one of the "gotchas" with extension methods. In order to be able to use the extension method, we have to add it using << namespace >>, otherwise we will get a compilation error. So, we need to add using MyLibrary.Extensions; to the file containing the Celsius class.

We aren't just limited to sealed classes with extension methods. We can add an extension method for a non-sealed class as well. Let's create a Weight class that simply exposes the Value.

1
2
3
4
5
6
7
8
public class Weight : IUnit
{
  public Weight(double value)
  {
    Value = value;
  }
  public double Value { get; }
}
csharp

Obviously, this class provides us with limited value because it's not actually doing anything weight related. We can solve this deficiency by creating an extension method that assumes the input value is in pounds and converts the weight into stones.

1
2
3
4
5
6
7
public static class WeightExtensions
{
  public static double ToStone(this Weight weight)
  {
    return weight.Value / 14;
  }
}
chsarp

What Else Can Extension Methods Do?

You might be wondering if extension methods limit us to using classes. With extension methods, we can extend structs, interfaces, inherited classes, and base classes. The ability to extend interfaces is particularly useful when using Dependency Injection, where we inject classes into code using the registered interface. Let's go back to our temperature example and extend our extensions class to include conversion to the Reaumur temperature using the interface instead.

1
2
3
4
5
public static class TemperatureConverterExtensions
{
  public static double ToRomer(this TemperatureConverter temperature) => temperature.Value * 21 / 40 + 7.5;
  public static double ToReaumur(this IUnit temperature) => temperature.Value * 4 / 5;
}
csharp

We can update our Celsius model class to return the Reaumur like this.

1
public double Reaumur => _converter.ToReaumur();
csharp

Something to be aware of when using the interface approach is that the implementation is available to ANY class that implements that interface. This means that there is nothing preventing us from using the ToReaumur method on the Weight class because Weight implements the IUnit interface.

A limitation of the extension method approach is that extension methods cannot access the internal features of the class that they are extending without using reflection.

Conclusion

In this guide, we have looked at how we can extend the functionality of existing classes without having to modify the source code (something that is particularly useful when working with third-party code) by using extension methods. We explored how we can extend, first a sealed class and then a non-sealed class, before discovering that we can also extend interfaces and structures. We discovered that referencing extension methods means that our code may have to reference the namespace the extension method is in.

0