E is for… Extension Method

e

Extension methods were added to C# 3.0 and Visual Basic 9.0, coinciding with the release of Visual Studio 2008 and the .NET Framework 3.5.  An extension method allows you to add to the definition of a compiled type, including classes, structures, and interfaces, without explicitly modifying the extended type.  In fact, the extended type could even be sealed!

Let’s take a look at a simple extension method to get a better understanding of how they tick.

    1:  public static class CExtensions
    2:  {
    3:      public static String InitCap(this String sIn)
    4:      {
    5:          Boolean whiteSpace = true;
    6:          StringBuilder sOut = new StringBuilder(sIn.Length);
    7:          
    8:          foreach (char c in sIn)
    9:          {
   10:              if (whiteSpace && Char.IsLetter(c))
   11:                  sOut.Append(Char.ToUpper(c));
   12:              else
   13:                  sOut.Append(c);
   14:              whiteSpace = Char.IsWhiteSpace(c);
   15:          }
   16:          return sOut.ToString();
   17:      }
   18:  }

Here’s a few things you’ll notice right off:

  • The method must be defined within a static class (and so the method itself must be marked static)
  • The modifier this is prepended to the first, and only the first, argument of the method, and the type of that argument is the type being extended.

In Visual Basic, it’s a bit different, as you can see in the code snippet below.  Note especially the use of the Extension attribute in line 4 (it’s located in the System.Runtime.CompilerServices namespace imported in line 1).  For Visual Basic, the extension method must reside in a module, which is then imported into the source file that will make the reference to the extension method.

    1:  Imports System.Runtime.CompilerServices
    2:  Public Module VBExtensions
    3:   
    4:      <Extension()> _
    5:      Public Function InitCap(ByVal sIn As String) As String
    6:   
    7:          Dim whiteSpace As Boolean = True
    8:          Dim sOut As StringBuilder =  New StringBuilder(sIn.Length)
    9:   
   10:          For Each c As Char In sIn
   11:              If whiteSpace And Char.IsLetter(c) Then
   12:                  sOut.Append(Char.ToUpper(c))
   13:              Else
   14:                  sOut.Append(c)
   15:              End If
   16:              whiteSpace = Char.IsWhiteSpace(c)
   17:          Next
   18:   
   19:          InitCap = sOut.ToString()
   20:      End Function
   21:  End Module

Using a small C# console application (below) as an example, we can call our VB extension method in two ways:

  • on the object instance itself (line 11)
  • by invoking the static method on the defining class or module (line 12)
    1:  using VB;
    2:   
    3:  namespace CConsoleApp
    4:  {
    5:      class Program
    6:      {
    7:          static void Main(string[] args)
    8:          {
    9:              String s = "each of  these words will be capitalized";
   10:   
   11:              Console.WriteLine(s.InitCap());
   12:              Console.WriteLine(VBExtensions.InitCap(s));
   13:   
   14:              Console.ReadLine();
   15:          }
   16:      }
   17:  }

If you use the first option, which is a bit more natural, the invocation looks no different from that of a standard object method.  In Intellisense though, you’ll get a subtle cue that this is an extension method, namely a blue, downward pointing arrow icon next to the method name:

image

Keep in mind, for extension methods to be ‘visible’ they must be in scope, so if you’re not seeing the extension methods you expect, make sure you’ve included the appropriate using or imports directive in your current code file.

Now, given that the syntax for invoking an extension method is no different from an instance method, what happens if there is a name collision?  For instance, in the following code, I’ve tried to get cute and redefine the String.ToUpper method to do exactly the opposite (line 18):

    1:  namespace CConsoleApp
    2:  {
    3:      class Program
    4:      {
    5:          static void Main(string[] args)
    6:          {
    7:              String s = "AbCdEfGh";
    8:              Console.WriteLine(s.ToUpper());
    9:   
   10:              Console.Read();
   11:          }
   12:      }
   13:   
   14:      public static class CExtensions
   15:      {
   16:          public static String ToUpper(this String sIn)
   17:          {
   18:              return sIn.ToLower();
   19:          }
   20:      }
   21:  }

The program output though is what you’d want (I hope!), and you get the string “ABCDEFGH”, because an instance method will have precedence over an extension method.  This is true even if the extension method would be considered a ‘better match.’  Consider, for instance, an instance method with a long argument and an extension method of the same name with an int argument.  If the method is invoked with an int argument, the instance method will still be called, because the int can be widened into a long with no loss of precision.

But now you’re thinking, anyone can write these extension methods, and they could be sprinkled all over my code, what happens if there are two extension methods with the same signature, and they are both in scope?  Well, there’s a set of precedence rules that determines the outcome.  The list below is ordered from highest precedence to lowest, and if there is still ambiguity, you’ll get an error indicating that when building your project or solution.

1.

Extension methods defined inside the current module.

  
  1. Extension methods defined inside data types in the current namespace or any one of its parents, with child namespaces having higher precedence than parent namespaces.

  2. Extension methods defined inside any type imports in the current file.

  3. Extension methods defined inside any namespace imports in the current file.

  4. Extension methods defined inside any project-level type imports.

  5. Extension methods defined inside any project-level namespace imports.

Be aware of how these precedence rules can change your application behavior.  For instance, let’s say the author of a class on which you’ve defined an extension method adds a new instance method of the same name and signature, but different behavior.  The next time you build and run your application, your method will no longer be invoked!  Unfortunately, there’s no compiler warning alerting you to the fact that there exists an instance method with the same name as an extension method you’ve defined.

A similar outcome can occur if someone else, perhaps even on your team, writes his or her own extension method that happens to override yours because of the precedence rules above.

To help mitigate these situations, there’s a few best practices to keep in mind:

Define your extension methods

  • as far down the hierarchy chain as possible, so you’re less likely to collide with other uses of a more general type.
  • on interfaces versus classes, since it’s less likely the original author will add new method on that interface; however, if he does, and there’s a conflict, neither one is available!
  • in a common namespace expressly for extension methods thus making the use of them more explicit to the consumer.

By far, the most obvious uses of extension methods in the .NET Framework are those added to support LINQ (Language Integrated Query) functionality introduced in the .NET Framework 3.5.  Much of the SQL-like query syntax you’ve seen in various LINQ contexts is actually backed by extension methods in the System.Linq.Enumerable class.