C# Extension Methods

.NET has supported extension methods since version 2.0, though in 2.0 they were pretty clunky. This post explains .NET extension methods starting with framework version 3.5.

There are several benefits to extension methods, including:

  • Extension methods can be added to any class without having to inherit the class. You can even add methods to classes marked as final (meaning they can’t be inherited).
  • An extension method is defined in a separate namespace, and if that namespace isn’t included in your code then the extension method isn’t available. Existing programs that use the class are unaffected because the class itself hasn’t been altered.
  • An extension method can be added to any class, including .NET Framework classes, classes written by others, and .NET primitive types such as int and long. You don’t even need the class’s source code.

It’s also worth pointing out that an extension method can return any type, including void. An int extension doesn’t have to return an int, and a String extension doesn’t have to return a String.

Basic No-Parameter Extension Method

This example adds a Square method to the int type. The method returns the square of the instance that calls it. The functionality is very basic by design, allowing us to focus on the extension syntax and not on the method’s implementation logic.

namespace MyIntExtensions {
  public static class Whatever {
    public static long Square(this int theValue) {
      return (long)theValue * theValue; // Cast one of these to force a long result
    }
  }
}

 
Here’s a breakdown of the extension method:

  • Line 1: The namespace is important. To access the extension method, you must include the namespace with the using directive.
  • Line 2: The class name is not important, which is why I’ve named it Whatever. It never comes into play.
  • Line 3: Extension methods must be public and static.
  • Line 3: The this int that starts the parameter signature is what really defines this as an extension method. It means that the method is extending an int type. To extend the String class you’d use this String; to extend a class named MyOwnClass you’d use this MyOwnClass.
  • Line 3: .NET will pass the instance that’s calling the extension method to the theValue parameter; study the method above and the call below and this will make sense.
  • Line 3: The name of the theValue parameter was made up by me. It’s not a keyword. You can use any name you want.
  • Line 3: Note that the return value for the method will be a long type (public static long). That’s because the square of an int can be larger than the valid int range.

This simple console program calls the Square extension method:

using MyIntExtensions;
namespace Test {
  class TestExt {
    static void Main(string[] args) {
      int myInt = 5;
      long squaredVal = myInt.Square();
      Console.WriteLine(squaredVal); // output is "25"
    }
  }
}

 
Here’s a breakdown of the calling class:

  • Line 1: The using MyExtensions declaration makes the extension method available to this program.
  • Line 6: Remember that the signature for the Square extension method is public static long Square(this int theValue). When you call the Square extension method, .NET passes myInt to the myValue parameter.

Basic One-Parameter Extension Method

The first parameter of an extension method is a placeholder for an instance of the class being extended. Any parameters that follow it are the actual parameters to pass to the method. This example adds another extension method named Log, which returns the integer value’s logarithm. It takes one parameter: the logarithmic base. Again, this isn’t a complicated method; all I’m trying to do here is illustrate how extensions work.

namespace MyIntExtensions {
public static class Whatever {
    public static long Square(this int theValue) {
      return (long)theValue * (long)theValue;
    }
    public static double Log(this int theValue, double theBase) {
      return Math.Log((double)theValue, theBase);
    }
  }
}

 
This console program calls the Square and Log methods:

using MyIntExtensions;
namespace Test {
  class TestExt {
    static void Main(string[] args) {
      int myInt = 16;
      Console.WriteLine(myInt.Square()); // output is "256"
      Console.WriteLine(myInt.Log(4)); // output is "2"
    }
  }
}

 
When the program calls myInt.Log(4), .NET passes myInt as the first parameter (theValue) and 4 as the second parameter (theBase).

Extension Method Optional Parameters

Optional parameters for extension methods are no different than optional parameters for "regular" methods. In this example, the Log extension method’s logarithmic base is made optional; if not specified it defaults to the "natural log" of base e.

namespace MyIntExtensions {
public static class Whatever {
    public static int Square(this int theValue) {
      return theValue * theValue;
    }
    public static double Log(this int theValue, double theBase = Double.NaN) {
      return Math.Log((double)theValue, Double.IsNaN(theBase) ? Math.E : theBase);
    }
  }
}

 
This console program calls the Log extension method, with the first call specifying the logarithmic base and the second call relying on the default:

using MyIntExtensions;
namespace Test {
  class TestExt {
    static void Main(string[] args) {
      int a = 16;
      Console.WriteLine(a.Log(4)); // output is "2"
      Console.WriteLine(a.Log());  // output is 2.77258872223978
    }
  }
}

 

Conclusion

Extension methods are a powerful tool for adding functionality to classes you otherwise couldn’t modify.

The .NET Framework itself uses extension methods liberally. A notable example is Linq methods such as OrderBy, which are extension methods in the System.Linq namespace – in other words they’re only available when you include the System.Linq namespace in your program.

The syntax for extension methods isn’t immediately intuitive, or at least it wasn’t to me. If you find it confusing, try writing a couple simple extension methods of your own. You’ll get comfortable with the syntax pretty quickly.

Leave a Reply

Your email address will not be published. Required fields are marked *