In my previous post I gave a high level overview of some of the benefits of using Extension Methods in VB 9.0. Today I'm going to delve into some of the details about how to define extension methods and then use them in your programs.
You can define an extension method by creating a method in a VB module and decorating it with the System.Runtime.CompilerServices.Extension attribute. The type of the methods first parameter specifies the type the method extends, and the remaining arguments indicate the method's signature. In general, we allow an extension method to be defined on any type that can be represented in a VB signature, with the exception of param arrays, optional arguments, and generic type parameters to (because modules may not be generic). Specifically, this enables you to define extension methods on any of the following types:
In order to consume an extension method, you simply need to bring it into scope. The method will then be visible in intellisence and callable as if it was an instance method. This is a rather simple statement, but it has several interesting implications. In particular, this is a distinct departure from the behavior we implemented in the May 2006 Linq CTP, where we required each type that defined extension methods to be individually imported before its extensions could be used. Although this had some advantages, such as providing fine grained control over extension method visibility, it also suffered from numerous drawbacks.
One big problem was the fact that it was different than C#, which makes extension methods visible by importing the namespace that contains the types that define them. To see why this is an issue, consider the following 2 example C# class libraries:
Example 1:
namespace CSharpExtensionMethodLibrary{ static class Extensions1 { public static void ExtensionMethod1(this string x) { } } static class Extensions2 { public static void ExtensionMethod2(this string x) { } } static class Extensions3 { public static void ExtensionMethod3(this string x) { } } static class Extensions4 { public static void ExtensionMethod4(this string x) { } } static class Extensions5 { public static void ExtensionMethod5(this string x) { } }};
Example 2:
namespace CSharpExtensionMethodLibrary{ static class Extensions { public static void ExtensionMethod1(this string x) { } public static void ExtensionMethod2(this string x) { } public static void ExtensionMethod3(this string x) { } public static void ExtensionMethod4(this string x) { } public static void ExtensionMethod5(this string x) { } }};Here the first example defines an extension method library with the extensions defined in several different static classes all in the same namespace. The second example defines the same extension methods concentrated into a single class. From the point of view of a C# consumer, these libraries are equivalent. To consume them, a C# program simply needs to import their containing namespace. For a VB consumer in the May 2006 Linq CTP, however, these libraries are very different. To consume the first one, a VB programmer would need to add 5 imports statements to his code, where as with the second one he would only need to add one.
Using Example 2 from C#:
using CSharpExtensionMethodLibrary; class C1 { void Main() { string x = ""; x.Extension1(); x.Extension2(); x.Extension3(); x.Extension4(); x.Extension5(); } }
Using Example 2 from VB in the May 2006 CTP:
Imports CSharpExtensionMethodLibrary.Extension1 Imports CSharpExtensionMethodLibrary.Extension2 Imports CSharpExtensionMethodLibrary.Extension3 Imports CSharpExtensionMethodLibrary.Extension4 Imports CSharpExtensionMethodLibrary.Extension5
Module M1 Sub Main() Dim x as String = "" x.Extension1() x.Extension2() x.Extension3() x.Extension4() End Sub End Module
Obviously this has the potential of causing API usability problems for VB programmers consuming C# extension methods.
However, by restricting our rules to only allow extension methods to be defined in modules and changing the rules for extension method visibility to include "any extension method in scope", we were able to solve this problem and achieve parity with C# in a way that meshed well with existing VB language concepts. This works because VB modules have the property of "hoisting" there members into their containing namespace, so importing a namespace will also bring the extension methods defined in its types into scope. Therefore, by treating C# static classes as VB modules (for the purposes of extension methods), we can ensure that VB customers have an API usability experience that is comparable to C#.Using Example2.dll from VB in Orcas:
Imports CSharpExtensionMethodLibrary Module M1 Sub Main() Dim x = "" x.Extension1() x.Extension2() x.Extension3() x.Extension4() End Sub End Module
However, we still retain the ability to import a module directly, so the fine grained control offered by the May 2006 CTP is available for anyone who needs it. We've also introduced shadowing rules that give you some degree of control in resolving conflicts between extension methods. More explicitly, we break extension methods into a series of precedence levels based on the mechanism used to bring them into scope. In the event of a conflict between two methods with identical signatures and different precedence levels, we will pick the one with the higher precedence level. The complete list of these levels is shown below (items with lower numbers have higher precedence).
In the event that these shadowing rules are not enough to resolve ambiguities you may always bind to a particular method by using the old Whidbey-Style shared method syntax (i.e. StringExtensions.Speak(x) as opposed to x.Speak()).
I think that is enough for today. Fortunately, I've only scratched the surface. In my next several posts I will dig into some of the issues you need to be aware of when writing extension method libraries, including: