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
3: public static String InitCap(this String sIn)
5: Boolean whiteSpace = true;
6: StringBuilder sOut = new StringBuilder(sIn.Length);
8: foreach (char c in sIn)
10: if (whiteSpace && Char.IsLetter(c))
14: whiteSpace = Char.IsWhiteSpace(c);
16: return sOut.ToString();
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
thisis 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
4: <Extension()> _
5: Public Function InitCap(ByVal sIn As String) As String
7: Dim whiteSpace As Boolean = True
8: Dim sOut As StringBuilder = New StringBuilder(sIn.Length)
10: For Each c As Char In sIn
11: If whiteSpace And Char.IsLetter(c) Then
15: End If
16: whiteSpace = Char.IsWhiteSpace(c)
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;
3: namespace CConsoleApp
5: class Program
7: static void Main(string args)
9: String s = "each of these words will be capitalized";
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:
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
3: class Program
5: static void Main(string args)
7: String s = "AbCdEfGh";
14: public static class CExtensions
16: public static String ToUpper(this String sIn)
18: return sIn.ToLower();
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.
Extension methods defined inside the current module.
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.
Extension methods defined inside any type imports in the current file.
Extension methods defined inside any namespace imports in the current file.
Extension methods defined inside any project-level type imports.
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