Fall In Love with Visual Basic All Over Again in Visual Studio 2008

Fall In Love with Visual Basic All Over Again in Visual Studio 2008

ou undoubtedly know by now that Visual Studio 2008 was released to manufacturing in November, just in time for Christmas. It comes wrapped up with a new version of Visual Basic, which includes (among others) these great new features:

  • Enhanced IntelliSense
  • Snippets that Work
  • Friend Assemblies
  • Updated Ternary Operator
  • Better Nullable Types
  • Partial Methods
  • Object Initializers
  • Implicit Types / (Local) Type Inference
  • Anonymous Types
  • Relaxed Delegates
  • Extension Methods
Author’s Note: The list is color coded: the green items are features I love, the red items are features you should avoid, and the blue item is a feature I think you should be wary of. The section heading colors for the listed items match the list colors, so you can immediately tell how I feel about each feature.

While many of the great enhancements in the 2008 version of Visual Studio involve Language-Integrated Query (LINQ), this article focuses instead on enhancements made directly to Visual Basic 9. As you probably know, LINQ is the star of Visual Studio 2008, and Microsoft included many of the features in the list expressly to support LINQ.

To get the greatest benefit from this article, I recommend that you download the sample code and work through that as you read. Many of the examples refer directly to the sample code project. When you open the sample solution, you’ll notice that the main form window contains buttons whose names match the feature list above. As you read, double-click the appropriate form button and Visual Studio will open the Click event handler for the button. These event handlers serve as the sample code for each feature. In some cases, other classes may be part of the sample code as well, but those cases will be clearly stated in the article.

Multi-Targeting Support in Visual Studio 2008
Before reviewing the features individually, unlike previous versions of Visual Studio, where the version of the IDE was tied directly to a version of the .NET Framework, Visual Studio 2008 supports multi-targeting. This means you can select from among versions 2.0, 3.0, and 3.5 of the .NET Framework to determine the target version to use when building your solution. This is a wonderful feature because it means you don’t have to wait for your development group to adopt the 3.5 framework to start using Visual Studio 2008. I’ve mentioned it here because some of the new features new to VB9 are available to you even if you continue to target the 2.0 framework. As you read through the new features, I’ll point out the features you are free to use immediately.

For new projects, you can choose the framework version from the upper right corner of the New Project dialog (see Figure 1).

To change the framework version for an existing project, open the Project Properties, select the Compile tab, and then click the “Advanced Compile Options?” button. That opens another dialog. You can then change the framework target by selecting it from the “Target framework” dropdown shown in Figure 2.

Figure 1. Targeting a Framework in New Projects: Visual Studio 2008’s “New Project” dialog lets you select the framework your new project will target.
Figure 2. Targeting a Framework in Existing Projects: Drill down through the Project Properties dialog to change the framework target by selecting the “Compile” tab, and then clicking the “Advanced Compile Options…” button.

For the curious, notice that the project’s list of referenced assemblies changes depending on which framework target you choose.

Enhanced IntelliSense
When you use Visual Studio 2008 for the first time, you will notice some big changes in IntelliSense. Although IntelliSense is largely a function of the Visual Studio IDE, I have included it in this article because I suspect that VB developers depend more heavily on IntelliSense than others, and because the changes are so useful. As you probably suspected, this feature is available even if you don’t upgrade to the .NET Framework 3.5.

Figure 3. Visual Studio 2005 Intellisense: Visual Studio 2005 provided a smaller set of ‘sensible’ items than Visual Studio 2008, but you had to type “Me.” Before you could see a button named “btnIntelliSense” in the list.

To illustrate the changes, suppose you use the common convention of assigning the prefix btn to your button names, for example btnClose. Previous versions of Visual Studio required you to type “Me.btn” to retrieve an IntelliSense list of the buttons on a form whose names begin with “btn” (see Figure 3).

In contrast, VS2008 attempts to provide IntelliSense after nearly every keystroke, so by simply typing “btn” (not “Me.btn”), you get an IntelliSense list of in-scope members and language keywords starting with “btn” (see Figure 4).

Figure 4. Visual Studio 2008 Intellisense: In Visual Studio 2008 you can get to the button name in the IntelliSense list without typing the “Me.” qualifier.
Figure 5. Enhanced IntelliSense: Visual Studio 2008 makes IntelliSense a more comprehensive programming aid by including more language terms, such as keywords.

Even better, the improved IntelliSense list includes even language keywords such as Dim (see Figure 5). Best of all, the increased IntelliSense involvement causes no noticeable delay.

Snippets That Work
In case you haven’t used this feature before, here’s a simple demo. Open a class or module and type the word “property,” then press the Tab key. Visual Studio replaces the word “property” with a property snippet?a private local variable declaration followed by a public property, called NewProperty by default.

      Private newPropertyValue As String   Public Property NewProperty() As String       Get           Return newPropertyValue       End Get       Set(ByVal value As String)           newPropertyValue = value       End Set   End Property
Figure 6. Unwanted Highlighting: In Visual Studio 2005 it was difficult to get rid of the green snippet highlighting.

To customize the snippet, you can simply type over the snippet’s highlighted areas to provide your own names or types, and press Tab to move to the next highlighted area. Figure 6 shows a new property snippet modified to support an age variable.

The improvement lies in the highlighting. In previous versions the highlighted areas wouldn’t go away even after you replaced the default values until you had saved, closed, and then reopened the file. Now, though, if you simply move your cursor outside the property text area and press Enter; the highlighting disappears. Very nice indeed! True, the new snippet functionality is an IDE enhancement rather than a part of VB9, but I liked it so much I thought it would be good to include in this article.

Friend Assemblies
Another feature I really like is “friend assemblies.” The Visual Basic Friend keyword extends the scope of a class, module, or method to the entire assembly where the object is defined. The friend assemblies feature lets you scope an entire assembly to the same level. In other words, by using the Friend keyword, a class may now expose all its declarations to another assembly in the same way that it could formerly expose its declarations to other code files in the same project.

On the surface, this feature doesn’t seem very useful. However, consider a tool that includes a heavily used run-time component and several debugging and management tools. In such a situation, one might be compelled to keep the size of the run-time assembly as small as possible, pushing functionality not required by the run-time component into another assembly. By doing that, debugging or management components would not be privy to the Friend status they would enjoy if they were included in the run-time assembly. The now-external components would have to access the run-time components via public interfaces on the run-time assembly. That means you’d have to expose at least some portions of your application’s internal state using a public interface?and that could be a security problem.

Using VB9’s friend assemblies, you could declare inside the run-time component that the Friend scope should include the debugging assembly. That would give the debugging assembly code access to methods and values inside the run-time assembly without unnecessarily exposing otherwise private methods and properties through public interfaces.

Another possible use is to give Friend scope to unit test assemblies. Writing unit tests can sometimes be difficult if the functionality you’re trying to test isn’t visible outside the assembly. In this case, you would extend Friend scope to the unit test assembly.

To demonstrate friend assemblies, you’ll find two projects in the sample code named VB9_Demo and VB9_Tools. Inside VB9_Demo’s frmDemo class, the btnFriendAssemblies_Click event calls a method declared in the MyTools class in the VB9_Tools project. It can do this because the MyTools class is declared as Friend. In previous versions, that would have restricted its visibility to only other classes and modules inside the VB9_Tools project. To extend the “friend” scope of the MyTools class to include the VB9_Demo assembly, you must remember to do two things:

  1. Import the System.Runtime.CompilerServices namespace. That’s required because that’s where the InternalsVisibleTo attribute is declared.
  2. Add the InternalsVisibleTo attribute to the MyTools class declaration as shown below:
   Imports System.Runtime.CompilerServices          Friend Class MyTools       Friend Shared Function GetTime() As String           Return "The current time is " & Now.ToLongTimeString()       End Function   End Class

Updated Ternary Operator defines ternary as “consisting or involving three; threefold; triple.” For VB developers, the ternary IIf operator has provided a concise method for making conditional assignments. The IIf operator accepts three parameters. The first parameter is a Boolean expression. When the Boolean expression evaluates to True, the operator returns the value of the second parameter, and when it evaluates to False the operator returns the value of the third parameter.

For example, the following IIf operator code alternates the color string assigned to a variable. If the initial value of strColor is “White,” the operator returns the second parameter value, “Gray,” Conversely, an initial value of “Gray” results in the return of the third parameter, “White.”

   strColor = IIf(strColor.Equals("White"), "Gray", "White").ToString()

Watch out, though; there is a potential side effect to using the IIf operator. The standard IIf operator always evaluates both the possible return values. Therefore, if either the second or third parameters are function calls, they will be called and evaluated.

Take a moment to review the ComputeOvertimePay and ComputeRegularPay functions shown below. Both work similarly, computing regular or overtime pay based on a parameter containing the number of hours worked. If you call ComputeOvertimePay with intHours <= 40, it throws an exception. The ComputeRegularPay function works similarly.

   Private Function ComputeOvertimePay(ByVal intHours As Integer) As Decimal       If intHours > 40 Then           Return CDec(intHours * 1.5 * c_PayPerHour)       Else           Throw New ApplicationException("Hours too low.")       End If   End Function      Private Function ComputeRegularPay(ByVal intHours As Integer) As Decimal       If intHours <= 40 Then           Return intHours * c_PayPerHour       Else           Throw New ApplicationException("Hours too high.")       End If   End Function

Now consider the code below that uses the ternary operator to compute an employee's wages. Passing a value of 32 to both functions causes the ComputeOvertimePay function to throw an exception.

   pay = CDec(IIf( intHours > 40, _      ComputeOvertimePay(32), ComputeRegularPay(32)))

In short, the IIf ternary operator functions similar to the more verbose code shown below. In other words, the single line of code above is the same as writing:

   intHours1 = ComputeOverTimePay(32)   intHours2 = ComputeRegularPay(32)    If intHours > 40 Then       Return intHours1   Else       Return intHours2   End If

VB9 introduces a new and syntactically simplified form of the ternary operator called If (note the loss of the extra "I") that prevents the unwanted behavior by evaluating only the appropriate expression?the other expression is never evaluated. In the code sample below, because the first expression (intHours > 40) evaluates to False, the new If operator calls only the ComputeRegularPay function.

   inHours = 32   pay = CDec(If( intHours > 40, _      ComputeOvertimePay(32),       ComputeRegularPay(32)))

In other words, it functions identically to:

   If intHours <= 40 Then       Return ComputeOverTimePay(32)   Else       Return ComputeRegularPay(32)   End If

Another use of the ternary If is to replace NULL values. In the code sample below, if the initial value of str is Nothing, then the If operator returns an empty string.

   str = If(str, String.Empty)

The new ternary operator (If version) is available even if you target the 2.0 framework.

Better Nullable Types
Prior to VS2005, there was no elegant way to make a value type nullable?you needed to maintain additional state variables to keep track of whether a value type was set. The 2.0 version of the framework added the generic Nullable type, which wasn't elegant but provided a workable solution. The Nullable generic type functions as demonstrated in the code sample below.

   Dim int As Integer   Dim intNullable As Nullable(Of Integer)   intNullable = Nothing   intNullable = 65   int = intNullable.Value

VB9 adds Nullable types. In VB9, to mark a type as Nullable, add a question mark after the type name in the variable declaration. As shown in the code sample below, it is used more similarly to a standard type. Nullable types are different from normal value types because they can be set to Nothing and they expose an additional function named HasValue that you can use to query for the Nothing-ness of the type. Note that you must perform a typecast to assign the value to a standard (non-nullable) variable.

   ' intNullable is a NULLable type   Dim intNullable As Integer?   Dim int As Integer   intNullable = 65   ' a type cast is necessary because the types are different   int = CInt(intNullable)   intNullable = Nothing

This is a great enhancement for anyone who uses types that can be set to NULL. It prevents the overhead and error-prone nature of using state variables to keep track of whether a value has been set, and it supplants the use of the Nullable generic type, which is not as elegant a solution as the new Nullable types.

Partial Methods
Imagine you have just created a code generation template to standardize the way you access table data. Your tool produces a set of class files that can be compiled into a data access assembly. You obviously don't want developers to recode their extensions to the generated classes each time the code is generated, so the class files produced by the tool are declared as partial classes?the class's functionality is extended by modifying another file containing the other portion of the partial class.

A further requirement of the generated classes is that an event will be fired when a field value changes. Typically, you'd handle this in older versions of VB by defining delegates to be invoked when field values change. This approach requires two things. First, the calling code, at runtime, must register methods to be called when the field value is changed. Second, the generated code needs to ensure that the calling code has in fact registered a valid method before invoking the delegate. This proves somewhat cumbersome for both the author of the generated code and the author of the calling code.

Partial methods solve these problems. Using partial methods, you would declare a stub (no code) for the partial method in the generated class as part of the code generation process. This partial method can be called from code in the generated class. Then, in the non-generated portion of the partial class, you implement only those partial methods you are interested in handling. At compile time, only calls to partial methods with an implementation get compiled into the final assembly. The compiler removes method calls made to unimplemented partial methods, streamlining the resulting code. This is similar to the delegate solution, except that the cost of detecting an available method to call is paid at compile time instead of at runtime. In addition, the code is more readable because the partial methods solution doesn't require the various constructs to manage the delegates.

The two code samples below demonstrate the use of partial methods in a code generation environment. The first sample (from the DataLayer_auto.vb file in the downloadable sample code) contains a partial class named "DataLayer" created by a code generator. The Set portion of the SSN property declaration calls the SSNChanged partial method.

   ' DataLayer_auto.vb file   Partial Public Class DataLayer       . . .       Private m_strSSN As String          Public Property SSN() As String           Get               Return m_strSSN           End Get           Set(ByVal value As String)               m_strSSN = value               SSNChanged(m_strSSN)           End Set       End Property          . . .          Partial Private Sub SSNChanged(ByVal newValue As String)       End Sub      End Class   

The second sample (from the DataLayer.vb file in the downloadable code) also contains an implementation of the SSNChanged method. It's implemented here so that it will not be overwritten when the code generation tools generates a new version of the DataLayer_auto.vb file.

   ' DataLayer.vb file   Public Class DataLayer          Private Sub SSNChanged( _          ByVal newValue As String)           If newValue = "-1" Then 
Figure 7. Implemented Partial Method: At this breakpoint at runtime, the IDE highlights the implemented SSNChanged method call as an executable line of code.
m_strSSN = "000000000" End If End Sub End Class

To see the SSNChanged partial method in action, set a breakpoint on the m_strSSN = value line in the Set portion of the SSN property in the generated DataLayer_auto.vb file. Run the project and click the "Partial Methods" button. When execution reaches the breakpoint, you can step into the SSNChanged method implementation (see Figure 7).

Figure 8. Unimplemented Partial Method: When the partial method SSNChanged is unimplemented, the IDE simply skips over the SSNChanged method call when stepping through the code.

Now, comment out the SSNChanged method implementation in the DataLayer.vb file and run the project again. This time, when the IDE reaches the breakpoint, it just steps over the line that calls the SSNChanged method, because it doesn't exist in the compiled version. You can see this contrasting behavior in Figures 5 (SSNChanged implemented) and Figure 8 (SSNChanged is not implemented).

This method of extending auto-generated code turns out to be both slightly faster?and more importantly?easier to maintain than the delegate method. However, there are some restrictions to using partial methods that will limit your ability to make heavy use of them. First, they are usable only inside partial classes. Second, partial methods are required to be declared as Private because the external interface of a class would need to change based on whether the partial methods were implemented. Because of these limitations, I cannot think of a good place to use partial methods outside of extending auto-generated code.

Object Initializers
VB9 provides a new syntax for initializing objects. In past versions of VB, if a constructor was unavailable or couldn't initialize an object completely, you had to finish initialization after creating the object. I have run into more than one instance where code was later inserted between when the object was created and when its initialization was completed, resulting in unanticipated object behavior. The code sample below demonstrates two common ways of initializing an object in older VB versions.

   Dim obj1 As New DataLayer   obj1.FirstName = "Michael"   obj1.LastName = "Jones"      ' OR       Dim obj2 As New DataLayer   With obj2       .FirstName = "Michael"       .LastName = "Jones"   End With

VB9's new method is a bit ungainly, but ensures that the new object gets initialized in a way that is more difficult to interrupt. Interrupting the object's initialization would require a developer to make a conscious decision to modify the initialization code because the new initialization syntax makes it clear that object creation and object initialization should happen together. Here's an identical initialization using the new syntax:

   Dim obj3 As New DataLayer With _       {.FirstName = "Michael", .LastName = "Jones"}

Both the old and new methods result in identical compiled code; the new syntax is merely more concise?and perhaps more self-documenting in some cases.

This feature is available regardless of the framework version you target. I recommend using this method of initializing objects unless the number of fields to be initialized makes the code more difficult to read.

Implicit Types
Also known as Local Type Inference, implicit types are among my least favorite features of VB9. This feature allows VB9 to determine the type you are declaring without you having to explicitly declare the type. My main fear with widespread use of this feature is a loss of readability.

Microsoft clearly added implicit types to make LINQ more powerful, but I think this feature is best left alone outside of LINQ. It uses the expression's type to correctly determine the type of the new variable. If the variable is not initialized when it is created, the compiler will dimension it as an Object. In the code fragments below, contrast the VB8 style of explicitly declaring types with the less rigid declaration style supported by VB9, where no types are specified.

   'VB8   Dim age As Integer = 65   Dim name As String = "Michael Jones"   Dim things As New List(Of String)      'VB9   Dim vb9_age = 65   Dim vb9_name = "Michael Jones"   Dim vb9_things = New List(Of String)

However, fear not, VB9 gets the type right?almost all the time. In the sample project, hover over the VB9 variable declarations in the btnImplicitTypes_Click method call. The tooltip shows that VB9 knows that vb9_age is an Integer and that vb9_name is a String.

But you can run into trouble in two common cases if you allow VB9 to infer the type. These are:

  1. When there are multiple types that are suitable, such as Integer and Long (the default in this case is Integer)
  2. When you want to create a derived type and declare it as a base type. In such cases, you must provide an explicit variable type in the declaration.

Another instance where you might use implicit types is in a For Each loop. As shown in the code sample below, VB9 correctly determined that the variable thing is a String because it's used in a For Each construct where the target is a List(Of String).

   For Each thing In vb9_things       Debug.WriteLine(thing)   Next

Here again, I think it is worth providing the index variable's type to make the code more readable. I suggest you shy away from this feature. It doesn't add any compelling productivity features and can only serve to make your code less readable.

Anonymous Types
My least favorite of all the new features in VB9 is anonymous types. Like implicit types, this feature was added expressly to support LINQ. Used without LINQ, this feature is nothing but trouble. I do not recommend it because it decreases code readability.

Anonymous types allow developers to create structure-like objects without giving them an explicit name or definition. This feature is used in combination with implicit types to declare variables where the type name is not specified. LINQ uses anonymous types to create types for LINQ query results. Since the type structure is derived from the LINQ query, it changes based on the content of query. Since there isn't an intuitive way to define the structure a priori, anonymous types were introduced to solve the problem.

I have provided some sample code below to demonstrate the use of anonymous types. Notice: In addition to the lack of a type name there is also no explicit reference to a pre-defined type. However, as you will see, if you step through the code in the sample project, VB treats the types and variables just as you would expect.

   'Notice, no declaration type name   Dim theArticle = New With _       {.Name = "Michael Jones", .ArticleTopic = "Visual Studio 9"}      ' NEVER NEVER NEVER NEVER NEVER NEVER DO THIS      'VB created a type and    txtResults.Text = theArticle.Name & vbCrLf   txtResults.Text += theArticle.ArticleTopic & vbCrLf   txtResults.Text += "Type name: " & theArticle.GetType.ToString()   txtResults.Text += vbCrLf

If you are still a little curious about this, notice the tooltip Visual Studio provides when you hover over the declaration of theArticle in the code above (see Figure 9).

Figure 9. Anonymous Type Tooltip: Visual Studio displays a tooltip that lets you know when you're inspecting an anonymous type.

When you run the code, Visual Studio displays the run-time name of the type VB assigned to the anonymous type as: VB$AnonymousType_0`2[System.String, System.String].

Anonymous types might be a good way to store temporary data. It is an easy way to build a data structure to store ad hoc data in your program. However, it seems only to save you the time of declaring the type (class or structure). I advise against using this feature in your projects. Take the time to create the structure you need and use the fully declared type instead.

Relaxed Delegates
Delegates are a mainstay of the .NET Framework; they're used to manage events and to provide an efficient and type-safe callback mechanism. In previous versions of VB, the compiler has strictly ensured that a delegate's method signature matches the expected signature?so you haven't been able to (for example) assign a method with a KeyPress event signature to a button's Click event; the method signatures of the two delegates don't match.

VB9 relaxes the enforcement of delegate signatures, so you can now use a single method to handle both Click and KeyPress events from a button. Further, if you don't need the arguments, you can eliminate them from the method signature. The code fragment below shows a single method that handles Click and KeyPress events for the button named btnRelaxedDelegates. Notice that the parameters have also been removed to add a bit of implicit documentation, showing that the method doesn't need the standard parameters:

   Private Sub btnRelaxedDelegates_EventHandler() _       Handles btnRelaxedDelegates.Click, btnRelaxedDelegates.KeyPress          txtResults.Text += Now.ToLongTimeString & vbCrLf      End Sub

As with some of the others, I am a bit torn about whether I like this feature. I do like the idea of improving code sharing; bit it only saves you from having to call a common method from both event handler methods. It feels somewhat inappropriate because of the potential to cause confusion make the code base less maintainable. I will choose to shy away from this feature.

Extension Methods
How many times have you wanted to add functionality to a .NET Framework class to provide helper functions for your project? For example, suppose you wanted to create a derived DateTime method class that would return a date formatted in a specific way. If you've ever tried this, you've probably found that, in many cases, the class you want to extend is marked as Sealed, meaning you cannot derive a new MyDateTime class to add your methods to.

VB9 solves this problem with my favorite new feature, Extension Methods. Using extension methods, you can extend a type by adding methods that appear to be instance methods.

Extension methods are just like normal methods but have two important differences. First, you apply the System.Runtime.CompilerServices.Extension attribute to the method to instruct the IDE to treat the method as an extension method. Second, the first parameter of the method is always the type you want to extend. Here's an example. Suppose you want to code an extension to the DateTime class to add a method named ToLogFileString, which returns a string formatted in a specific way for your log file:

    _   Function ToLogFileString(ByVal dte As DateTime) As String       Return dte.ToShortDateString & " " & dte.ToShortTimeString   End Function

Note the attribute on the method. Of course, you must ensure that namespace where the extension method is defined is visible to the code where you call the extension method; in some cases you will need to import the appropriate namespace. Having done that, you can then call the extension method just as though it were an instance method:

   strLogFile += Now.ToLogFileString()

The trick behind extension methods is that VB9 helps you out a bit with the syntax of the call. As Figure 10) shows, it even includes the extension method in IntelliSense for the DateTime type!

Figure 10. Intellisense Extension Methods: Extension methods even appear in IntelliSense lists, making them even easier to use.

Behind the scenes, VB9 translates your call (which looks like a call to an instance method) to a shared method call, passing the base object as the first parameter of the call to the extension method. In the example above, the DateTime object returned from the call to Now is provided as the first parameter to the ToLogFileString method.

Because the object gets passed as the first parameter, extension methods can do things that normal instance methods (methods defined in the object's class) can't do. For example, I have always disliked the way the String class's IsNullOrEmpty method is called. Since you can't call methods on an object that is NULL, you must call IsNullOrEmpty as a shared method:


However, if you define an extension method to handle the same function (renamed to IsEmptyOrNull?you can't shadow an existing method on the extended class), the extension method can detect NULL values passed in as the parameter. You can use this to detect NULL strings:

    _   Function IsEmptyOrNull(ByVal str As String) As Boolean       Return String.IsNullOrEmpty(str)   End Function

Now you can call the IsEmptyOrNull extension method even on String variables that are NULL:

blnValidString = str.IsEmptyOrNull()

Be careful, though, this capability has a side effect; it requires you to verify that the first parameter is not NULL before using it. Be sure your extension methods verify that the first parameter is valid before attempting to use it.

Extension methods even work on derived types. You can define an extension method named IsSomething that returns a Boolean value indicating whether the variable is set to an instance of an object:

    _   Function IsSomething(ByVal obj As Object) As Boolean       Return obj IsNot Nothing   End Function

By defining the first parameter as Object, you can now use the IsSomething extension method against any class. Here are several examples:

   Dim bln as Boolean      Dim strName As String   bln = strName.IsSomething()      Dim frmMain As System.Windows.Forms.Form   bln = frmMain.IsSomething()      Dim objThread As System.Threading.Thread   bln = objThread.IsSomething()

There are several more important points to make about extension methods:

  • Add any additional parameters you need on the extension method after the first parameter, which must be the object itself.
  • You can overload extension methods just like conventional methods, using the same rules governing method overloading.
  • You may define extension methods in any assembly. They can be defined in the same project or in a separately compiled assembly?you just have to ensure that the method's namespace is visible by the code that will use the extension method.

These three points are demonstrated in the VB9Extensions.vb file in the sample VB9_Tools project. You'll find two different versions of the ToLogFileString method implemented; one has only the special "type" parameter and the other includes a second parameter that the caller must provide. The code below shows calls to both methods:

   str = Now.ToLogFileString()   str = Now.ToLogFileString(strModuleName)

Using Extension Methods in .NET 2.0
By default, you cannot create extension methods when you target the 2.0 framework. Recall that you use the Extension attribute to declare extension methods. This attribute is new to the .NET Framework version 3.5, and doesn't exist in the 2.0 framework. However, it is possible to "hack" your .NET 2.0 projects slightly to support extension methods.

To support extension methods in .NET 2.0 projects, you must define your own Extension attribute. I recommend creating a separate assembly to define the extension attribute. The sample code contains a project named ExtensionsIn20 containing a code file that defines an ExtensionAttribute class. The class file is very small; here's the complete code.

   Namespace System.Runtime.CompilerServices       Public Class ExtensionAttribute           Inherits Attribute       End Class   End Namespace
Author's Note: It is important to make sure the Root Namespace property in the project properties is empty. Otherwise, the namespace declaration shown above will be appended to the project's root namespace value.

Compile the ExtensionsIn20 project, and add a reference to it from a Visual Basic project targeting the 2.0 framework where you want to define extension methods. In the module where you define your extension methods, import the System.Runtime.CompilerServices namespace, and decorate your extension methods exactly as you would when using the .NET 3.5 version of the attribute. When it comes time to upgrade your project to .NET 3.5, simply remove the reference to the ExtensionsIn20 assembly, and your code will instead reference the Extension attribute defined in .NET 3.5's System.Runtime.CompilerServices namespace.

Overall, Visual Basic 9 is a compelling upgrade, especially because it's combined with some of the fantastic features in Visual Studio 2008. As discussed, there are some features you should be careful about using, and some that you should avoid completely. The advantages aren't limited to the newest framework version either. Even if your development group has not yet decided to upgrade to the .NET Framework 3.5, the new multi-targeting support means you can easily build .NET 2.0 projects from Visual Studio 2008.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist