Basic Instincts

Extension Methods

Adrian Spotty Bowles

Contents

Creating and Using Extension Methods
The Advantage of Extension Methods
Security Concerns
Overloading with Extension Methods
Which Types
Extending Object Type
Extension Methods in .NET Framework 2.0 Apps

With traditional object-oriented development, extending classes is typically achieved by deriving from a base class and enhancing the functionality in the derived class. Visual Basic® still supports these object-oriented concepts, but classes are sometimes marked NotInheritable to prevent modification of their behavior through inheritance. As a result, there is no way to customize these classes. An example of this is the System.String class.

A new feature made available in Visual Basic 2008, however, lets you extend any existing type's functionality, even when a type is not inheritable. And these extension methods play a crucial role in the implementation of LINQ.

Many types that already exist can't be easily updated without breaking existing code. An example of this is the interface IEnumerable(Of T). In order to support LINQ, new methods had to be added to this interface, but changing the interface by adding new methods would break compatibility with existing consumers. Adding a new interface was a possibility, but creating a new interface to supplement the existing IEnumerable(Of T) interface would have appeared to be an odd design.

What was needed was a way to extend existing types with new functionality without changing the existing contract.

Extension methods provide a simple mechanism to extend types in the system (value, reference, and interface types) with new methods. These methods extend the original type and can be called like regular, defined instance methods, but they leave the original type and its methods untouched. Extension methods create the illusion that they are defined on a real type, but, in reality, no changes are made to the original types.

This is not a standard object-oriented concept; it is a specific Microsoft® .NET Framework implementation feature. While this feature opens up a new set of possibilities, it's worth noting that the underlying intermediate language (IL) code generated by the compiler is really doing nothing new or specific to the .NET Framework 3.5. In actuality, it is simply making a shared method call.

This means you have the capability to use this feature in Visual Basic 2008 to target earlier versions of the .NET Framework. It shouldn't introduce any additional security issues since this feature doesn't change the type being extended and doesn't actually do anything that you couldn't already do with earlier versions of the Framework.

Creating and Using Extension Methods

The code in Figure 1 shows an AlternateCase extension method, which returns an alternately cased string. This will extend the System.String class, which, as I noted, is marked NotInheritable.

Figure 1 AlternateCase Extension Method

Imports System.Runtime.CompilerServices Module Module1 Sub Main() Dim s As String = "Hello World" 'Call AlternateCase method as an extension member on 'variable s Console.WriteLine(s.AlternateCase) 'Invoke if it was not an extension method or 'resolve conflicts Console.WriteLine(Extensions.AlternateCase(s)) End Sub End Module Module Extensions <Extension()> _ Function AlternateCase(ByVal x As String) As String Dim bCase = False Dim SB as new System.Text.StringBuilder For Each c In x If bCase Then SB.Append(c.ToUpper) Else SB.Append(c.ToLower) End If bCase = Not bCase Next Return SB.ToString End Function End Module

I'll step you through this example. The method declaration contains the attribute that identifies this method as an extension method. Extension methods are defined by the application of the extension attribute found in the System.Runtime.CompilerServices namespace contained with System.Core.Dll. For those of you not familiar with attributes, they can be used to provide metadata that is used by the compiler to trigger additional functionality. In this case, the extension attribute provides information that tells the compiler that method can be called on the type identified as the first argument to the extension method—String, in my example.

The Visual Basic language insists that all extension method declarations be placed within a Module. And by definition, extension methods can be created as either subroutines or functions, but trying to declare them as anything else, such as properties or events, is prohibited. There are no restrictions on the types that can be extended—you can extend any class, structure, or interface.

Extension methods are restricted to modules in order to ensure that they are easy to consume. This design is intended to build upon the concept of bringing new APIs into scope within a Visual Basic program using the Imports statement to bring all methods in a namespace into scope.

Every method in Visual Basic has to be declared inside a type, whether it is a class, structure, module, or anything else. Extension methods are no exception, though they differ slightly in that they are defined in a type outside of the type they are extending. This opens the potential for naming conflicts when using extension methods from multiple modules or namespaces.

Restricting the declaration of extension methods to modules dramatically simplifies the design since modules can't participate in inheritance chains and there is no concept of partial modules. Using an Imports statement with a Namespace/Module brings those shared methods into scope and allows you to call the methods without having to fully qualify them.

As an example, say I place the Module Extensions within a different namespace, such as this:

Namespace NewFeatures Module Extensions <Extension()> Function AlternateCase(ByVal x As String) As String Dim bCase = False Dim SB as new System.Text.StringBuilder For Each c In x If bCase Then SB.Append(c.ToUpper) Else SB.Append(c.ToLower) End If bCase = Not bCase Next Return SB.ToString End Module End Namespace

To bring the extension method declarations within this namespace into scope, I would use the following statement:

Imports NewFeatures Imports System.Runtime.CompilerServices Module Module1 Sub Main() Dim s As String = "Hello World" 'Call AlternateCase method as an extension member on 'variable s Console.WriteLine(s.AlternateCase) 'Invoking if it was not an extension method or 'resolve conflicts Console.WriteLine(Extensions.AlternateCase(s)) End Sub End Module

Extension methods are not unique to Visual Basic; C# has a similar implementation with extension methods having to be declared in a static class. This provides functionality similar to that of a module. If you work with both C# and Visual Basic, extension methods allow interoperability—you can create extension methods in one language and consume them in the other.

Note that if you've upgraded a project from an earlier version, you'll need to upgrade your Framework target to version 3.5 and then manually add a reference to System.Core.Dll. If you don't want to upgrade your Framework target, you can still take advantage of extension methods by recreating the ExtensionAttribute—more on this later. You'll also need to Import the System.Runtime.CompilerServices namespace (which is where ExtensionAttribute resides) or fully qualify the attribute when you use it.

The final piece of the example is the call. Improved IntelliSense® allows you to see the extension methods along with all the existing instance methods. You'll see that extension methods have a slightly different icon (a blue down arrow, as shown in Figure 2) to differentiate them from instance methods.

Figure 2 Extension and Instance Method Icons

Figure 2** Extension and Instance Method Icons **(Click the image for a larger view)

The Visual Studio® IDE functionality—such as Goto Definition, Rename, and so on—works for extension methods just as for normal instance methods. You'll notice that tooltips also show the extension attribute when displaying the signature.

You may have noticed other extension methods in IntelliSense, such as First and FirstorDefault. These are some of the extension methods that have already been provided as part of LINQ and are included in the default .NET Framework 3.5 references. They are extending a generic interface System.Collections.Generic.IEnumerable(Of T) from within the System.Linq namespace.

LINQ functionality is enabled by default in version 3.5 projects, which means you get all the extension methods defined in System.Core.Dll. You can confirm this by looking at the default assemblies references and imported namespaces—going to Project Properties | References | Imported Namespaces will show System.Linq already checked. You can also use a file-level Imports statement (Imports System.Linq) to bring these methods back into scope.

The Advantage of Extension Methods

Extension methods allow you to add functionality to a type that you don't want to modify, thus avoiding the risk of breaking code in existing applications. You can extend standard interfaces with additional methods without physically altering the existing class libraries. You can extend .NET types and older COM/ActiveX® control types for new code without risk of breaking old applications that use these types.

Prior to extension methods, in order to add functionality to classes and interfaces, you had a few options:

  • You could add the functionality to the source code, but this requires access to source code that may not be available.
  • You could use inheritance to inherit the functionality contained within one type into a new derived type, but not all types were inheritable.
  • You could re-implement the functionality from scratch.

All three of these approaches made extending third-party types extremely difficult. If you could get the source code and recompile the component, you would also need to ensure that you maintained compatibility with the original component to avoid breaking existing applications that used the component. If you created a new derived class or re-implemented the functionality from scratch, you would need to either fix all existing applications to use the new component or branch between the original functionality and the new updated functionality—and this would require two versions of the component.

None of these solutions were ideal. With extension methods, you can simply add the functionality to existing classes. All existing applications continue to work with the original binaries as nothing has been changed in them, but applications can now extend the functionality going forward.

Performance is relatively unaffected. Even though your Visual Basic source code may look different, it compiles to a simple shared method call and generates the same IL as if you had used the shared method call. So there is no performance hit through using them.

That said, extension methods are not a replacement for object-oriented concepts. If you are familiar with object-oriented concepts and have a well-designed application, you are probably already using inheritance somewhere in your application and should continue doing so. You probably own many of the types you are using, meaning you have access to the source code and can extend the functionality through normal object-oriented concepts. Extension methods really provide a way to enhance classes that you previously could not extend. Extension methods should be used sparingly, when other approaches are not possible or suitable.

Security Concerns

I have often heard the concern that extension methods can be used to hijack or subvert the intended behavior of existing methods. Visual Basic addresses this by ensuring that, wherever possible, an instance method is preferable over an extension method.

The language allows extension methods to be used to create overloads for existing instance methods with different signatures. This allows extension methods to be used to create overloads, while preventing the existing instance method from being overridden. If an extension method exists with the same signature as an instance method, the shadowing rules that are built into the compiler will prefer the instance method, therefore eliminating the possibility of an extension method overriding existing base class instance functionality (see Figure 3).

Figure 3 Extension Method Shadowing

Imports System.Runtime.CompilerServices Module Module1 Sub Main() Dim x As New ClsFoo Dim s As String = x.Method01 End Sub End Module Class ClsFoo Function Method01() As String Return "Instance" End Function End Class Module ExtMethods <Extension()> _ Function Method01(ByVal a As clsFoo) As String Return "Extension" End Function End Module

As the implementation of extension methods does not change the original type, this additional functionality is known only to those applications that are written to use the extension method. Existing applications that use the original type will see no difference.

If you take a look at the underlying IL that is generated by the compiler, you'll see that the extension method call is transposed into a simple shared method call. Thus, there are no increased security risks from using extension methods since you are doing nothing that you couldn't already do through a normal shared method call.

Overloading with Extension Methods

Extension methods let you overload instance methods. To help ensure that applications work correctly, there are a lot of rules built into the compiler to provide consistent binding behavior. The goal was to make extension methods appear to be instance methods, but also provide a consistent experience—when you enter legacy code into a project using extension methods, the code should continue to work in the same way.

An instance method with an acceptable signature using widening conversion will almost always be preferred over an extension method with an exact signature match. If this results in binding to the instance method when you really want to use the extension method, you can explicitly call the extension method using the shared method calling convention. This is also the way to disambiguate two methods when neither is more specific.

Fields and properties always shadow extension methods by name. Figure 4 shows an extension method and public field with the same name and various calls. Though the extension method contains a second argument, the field shadows the extension method by name and all calls using this name result in accessing the field. The various overloaded calls will all compile, but their results at run time may be unexpected since they will bind to the property and use the default property behavior to return a single character or result in a runtime exception. It is important to choose the names of your extension methods so you avoid clashes with properties, fields, and existing instance methods.

Figure 4 Extension Method and Public Field with Same Name

Imports System.Runtime.CompilerServices Module Module1 Sub Main() Dim x As New Foo 'Field - (Test) Console.WriteLine(x.TypeField) 'Default Property Transformation on field resulting in T Console.WriteLine(x.TypeField(0)) 'Invalid Cast Exception Converting "fred" to integer 'to use in default property transformation Console.WriteLine(x.TypeField("fred")) End Sub End Module Class Foo Public TypeField As String = "Test" End Class Module Extension <Extension()> _ Function TypeField(ByVal a As Foo, ByVal x As String) As String Return "Extension" End Function End Module

The rules in the compiler regarding which method the calling code will be bound to are complex. These rules are weighted towards selecting an existing instance method wherever possible. However, there are a few exceptions. The general points to consider are as follows:

  1. Instance methods will almost always be considered before extension methods. Widening instance methods are considered before extension methods. Non-applicable instance method candidates, such as those that are out of scope, are not considered in the overload resolution.
  2. Instance methods with the correct type-matching signature will be considered before methods with widening conversions.
  3. Class Properties/Fields will shadow extension methods by Name.

It should be clear that the name and signature of the extension method are critical in determining the method to be chosen by the compiler, whether it is an extension or instance method.

Which Types

Another consideration relates to which types you are extending. Although you can extend virtually any type, this doesn't mean that you should. You should be aware of "silent rebinding," which can be described as when a method that is being bound to changes, but this change is not immediately obvious to the developer. No warning or error is generated and the only indication that this has occurred is the resulting change in behavior caused by using a different method.

Imagine that you create an extension method that overloads a method in a third-party class library. Your application works as expected. The third-party tool provides an update, which you install on your machine. The application compiles just fine with no errors. But when you run the application, the behavior is incorrect. This may occur if the updated component included additional overloads to the method that you had extended, resulting in the additional instance method overload being called and not your extension method.

Extension methods are fragile in versioning scenarios. This is one reason why you should use extension method names very carefully—especially when used with types that are frequently updated with new overloads. Using Option Strict On can help prevent silent rebinding, as it will alert you to certain conditions where implied conversions can take place.

Extending Object Type

You are able to extend type Object, which allows the method to be seen on any type that derives from type Object. But if you create a type as object and try to consume the extension method on it, you won't see the extension method. This is because extension methods do not support late binding.

Note that in Figure 5, the first call will work because variable s is of type Integer and this derives from Object. As a result, you will see the extension method. Variable T, however, is of type object and is late-bound, and therefore will result in a compile error.

Figure 5 Support for Inheritance

Imports System.Runtime.CompilerServices Module Module1 Sub Main() Dim s As Integer = 1234 Console.WriteLine(s.Foo) '<- This will work Dim T As Object T = 1234 Console.WriteLine(T.Foo) '<- This will fail End Sub End Module Module Extensions01 <Extension()> _ Function Foo(ByVal b As Object) As String Return "Bar01" End Function End Module

The code could be fixed by changing to the following:

Dim T = 1234 Console.WriteLine(T.Foo)

This will work now because of the type inference feature. Variable T gets inferred as integer based on the value being assigned to it. This differs from Visual Studio 2005, where T would have been type Object containing a boxed integer.

Extension Methods in .NET Framework 2.0 Apps

As I noted earlier, extension methods are not doing anything in the underlying IL code that could not be done using Visual Basic 2005; they are simply making a shared method call. What has changed is the compiler's ability to identify specific methods and allow the change in the calling method syntax, which gives the appearance that the type being used has new functionality and makes the extensions work just like the existing instance methods.

Another new feature in Visual Basic 2008 is multi-targeting, which lets you use Visual Basic 2008 to write code that targets a specific version of the Framework. Since the same compiler is used for multi-targeting, it is possible to use extension methods for applications targeted, for example, to version 2.0 of the Framework. This does take a little workaround, as the IDE prevents adding the required references to a .NET Framework 2.0 targeted application.

If you create a 3.5 application and change the target Framework to version 2.0 (using Project Properties | Compiler | Advanced | Target Framework), you'll see some of the references highlighted as missing, including those containing most of the out-of-the-box LINQ functionality. Trying to add these references to a 2.0 project results in a dialog informing you that they require a different version of the targeted Framework (see Figure 6).

Figure 6 References for Changed Target to the .NET Framework 2.0

Figure 6** References for Changed Target to the .NET Framework 2.0 **(Click the image for a larger
view)

Extension methods can be created and used for 2.0 applications after you have created your own System.Runtime.CompilerServices.Extension attribute. You must first create a new project targeting version 2.0. The default references will not include System.Core, which contains the extension attribute. In the Applications Project Properties, go to the Application Tab, clear the Root Namespace, and then create your own extension attribute in the correct System.Runtime.CompilerServices namespace. The code in Figure 7 shows the creation of the extension attribute and then using this to declare and use extension methods. Once you compile the application, you can call the extension methods.

Figure 7 Creating an ExtensionAttribute Class

Imports System.Runtime.CompilerServices Module Module1 Sub Main() Dim i_x As Integer = 1 Dim i_s As String = "Test" Dim x = i_x.IntegerExtension MsgBox(x) Dim y = i_s.StringExtension MsgBox(y.ToString) End Sub End Module 'CREATE A USER-DEFINED ATTRIBUTE CALLED EXTENSION IN THE CORRECT NS Namespace System.Runtime.CompilerServices <AttributeUsage(AttributeTargets.Assembly Or AttributeTargets.Class Or AttributeTargets.Method, AllowMultiple:=False, Inherited:=False)> Class ExtensionAttribute Inherits Attribute End Class End Namespace 'DEFINED EXTENSION METHODS WITH USER-DEFINED ATTRIBUTE ON THEM Module ExtMethods <Extension()> _ Function StringExtension(ByVal a As String) As String Return "Success" End Function <Extension()> _ Function IntegerExtension(ByVal a As Integer) As Integer Return 100 End Function End Module

Here are some important considerations regarding 2.0-targeted applications. First, type inference is off by default. To turn it back on, use Option Infer On. You'll probably want to do when you migrate .NET Framework 2.0 applications to the version 3.5.

Second, to use extension methods, you need to create your own extension attribute that mimics the one in System.Core. The IDE will prevent you from using the System.Core reference on a 2.0-targeted project.

Finally, this technique will not work for ASP.NET applications targeting the .NET Framework 2.0 because they have a runtime dependency on the 2.0 command-line compiler. When those apps are deployed to Web servers that only have the .NET Framework 2.0 installed, they will not work because the 2.0 VBC.EXE command-line compiler does not understand extension methods.

Even if you don't want to start writing applications for the .NET Framework 3.5 or you have existing applications in version 2.0 that you have to maintain, you can still benefit from upgrading to Visual Basic 2008. The extension method functionality is built into the compiler and can be used for any version of the Framework that the Visual Basic 2008 compiler is able to target.

Send your comments and questions to instinct@microsoft.com.

Adrian Spotty Bowles has developed using every version of Visual Basic and has managed to find his way to Redmond, WA, where he works on the Visual Basic Product team as a Software Design Engineer Tester focused on the Visual Basic compiler. During the Visual Basic 2008 release, he worked on many of the language features, including Extension Methods. You can reach Spotty at Abowles@microsoft.com.