Jim O'Neil
Technology Evangelist
Join App Builder
Keep The Cash! Earn $100 for every app you publish! Let me know how I can help!
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:
this
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.
Extension
System.Runtime.CompilerServices
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:
1: using VB;
2:
3: namespace CConsoleApp
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();
16: }
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):
String.ToUpper
1: namespace CConsoleApp
3: class Program
5: static void Main(string[] args)
7: String s = "AbCdEfGh";
8: Console.WriteLine(s.ToUpper());
10: Console.Read();
11: }
12: }
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.
long
int
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
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.
System.Linq.Enumerable