Since the birth of the Visual Basic language in 1991, it has been a highly efficient tool for building applications. Nearly 20 years later, it continues to provide easy integration with the Microsoft .NET Framework, enabling developers to write applications that span desktops, phones, browsers and even the cloud.
Microsoft will release Visual Studio 2010 this month, which includes Visual Basic version 10 (sometimes called VB 2010 or VB10). This release is the most powerful yet and includes many time-saving features to help developers get more done with fewer lines of code. Here, you'll be provided with all the necessary content to fully understand and take advantage of Visual Basic in Visual Studio 2010.
co-evolution
In the past, Visual Basic and C# were developed by separate teams, which often resulted in features appearing first in one language and then in the other. For example, C# has autoimplemented properties and collection initializers that are not found in Visual Basic, and Visual Basic has features such as late binding and optional parameters that are not found in C#. But every time a language gets a new feature, many customers ask for that feature to be added to the other language as well.
To address this need, Microsoft merged the Visual Basic and C# teams to implement a co-evolution strategy. The purpose is to promote the common development of these languages. When a major feature is introduced in one language, it appears in the other language as well. That's not to say that every feature will be present in both languages and work exactly the same way; in fact, each language has its own history, soul and feel – and it's important to preserve these characteristics.
In .NET Framework 4, Visual Basic and C# took a big step toward this goal, each absorbing many of the other's existing features. However, co-evolution does not only affect previous functionality; it is also a strategy for the future development of these languages. In this spirit, .NET Framework 4 introduces powerful new features in both languages, such as the dynamic language runtime, embedded interop types, and generic variance, allowing Visual Basic and C# developers to take full advantage of .NET Framework.
What's new in Visual Basic 2010
New features in Visual Basic 2010 are designed to help you do more with fewer lines of code. Our Visual Basic design team took a close look at the places where developers typically have to write a lot of tedious boilerplate code and found ways to let the compiler do the work instead. Of course, this is an overall view, now let’s take a deeper look at each feature.
Implicit line continuation character
Visual Basic is a line-oriented language that uses a clear syntax similar to English to enhance readability. But this often results in code hitting the 80-character-per-line limit, forcing developers to do a lot of scrolling. You can use the underscore character to tell the compiler that processing of the next line should continue as the current line (that is, to treat multiple physical lines as a single logical line). But having to type the underscore character repeatedly has always been annoying, and in fact a top feature request for years was to have the compiler fix this problem.
In Visual Basic 2010, the compiler can solve this problem. The compiler now knows which tokens (such as commas, parentheses, and operators) tend to appear before line continuation characters, and it inserts characters so developers no longer need to insert characters. For example, it is certainly not logical to end a Visual Basic statement with a comma; the compiler knows this, so when the compiler sees a stream of tokens such as {comma, enter}, it infers the presence of a line continuation character, as shown in Figure The example in 1 is shown.
Figure 1 Infer line continuation characters
<Extension()>
Function FilterByCountry(
ByVal customers As IEnumerable(Of Customer),
ByVal country As String) As IEnumerable(Of Customer)
Dim query=
From c In customers
Where c.Country = country
Select <Customer>
<%=
c.Name &
, &
c.Country
%>
</Customer>
Return query
End Function
In Visual Basic 2008, the code in Figure 1 would require 9 underscore characters. However, in each of the following cases, the compiler infers when the underscore character is necessary and allows it to be ignored:
After the <Extension()> attribute
after ((left parenthesis) in method declaration
after the , (comma) of the first parameter
before ) (right parenthesis) in method declaration
after = (equal sign)
after <%= (the opening tag of an embedded expression)
After each ampersand (ampersand) in XML text
before %> (end tag of embedded expression)
This new compiler feature is particularly useful for method signatures, which will also work fine for longer than 80 characters in the example shown (if each part is on the same line). In Figure 2, you will see all combinations of tags and positions for which line continuation characters are implicit.
Figure 2 When the line continuation character is implicit
mark | Before | after |
, (comma), . (period), > (attribute), ( { (left bracket), <%= (embedded expression start tag (XML text)) | X | |
), }, ] (right bracket), %> (embedded expression end tag) | X | |
All LINQ keywords: Aggregate, Distinct, From, Group By, Group Join, Join, Let, Order By, Select, Skip, Skip While, Take, Take While, Where, In, Into, On, Ascending, Descending | X | X |
Operator: +, -,*,/,/,^,>>,<<,Mod,&,+=,-=,*=,/=,/=,^=,>>=,<<=, &=, <, <=, >, >=, <>, Is, IsNot, Like, And, Or, Xor, AndAlso, OrElse | X | |
With (in object initializer) | X |
As you can see, there are over 60 places in the language where the underscore character is not required. (In fact, none of the code examples in this article require line continuation characters.) Of course, you can still use the underscore character, so code from previous versions of Visual Basic will still compile as expected.
StatementLambda
The term lambda may sound scary at first, but a lambda is just a function defined within another function. Visual Basic 2008 introduced lambda expressions with the Function keyword:
Dim customers As Customer() = ...
Array.FindAll(customers, Function(c) c.Country = Canada)
Lambda expressions enable you to express logic locally in a granular and compact manner without having to split it across multiple methods. For example, here is the representation of previous code in Visual Basic 2005 (which does not support lambda expressions):
Dim query = Array.FindAll(customers, AddressOf Filter)
...
Function Filter(ByVal c As customer) As Boolean
Return c.Country = Canada
End Function
Unfortunately, Visual Basic 2008's lambda expressions require the expression to return a value, so the following code:
Array.ForEach(customers, Function(c) Console.WriteLine(c.Country))
Will lead to the following situation:
'Compile error: Expression does not produce a value.
Console.WriteLine is a Sub procedure (void in C#), so it does not return a value, which is why the compiler generates an error. To handle this situation, Visual Basic 2010 introduced support for statement lambdas, which are lambdas that contain one or more statements:
Array.ForEach(customers, Sub(c) Console.WriteLine(c.Country))
Since Console.WriteLine does not return a value, we can just create a Sub lambda instead of a Function lambda. Here's another example using multiple statements:
Array.ForEach(customers, Sub(c)
Console.WriteLine(Country Name:)
Console.WriteLine(c.Country)
End Sub)
When this code runs, it prints two lines for each customer. Also note that if you hover over c while coding, you will see that the compiler will infer the type as Customer (it is also legal to type c As Customer to explicitly declare the type). Writing event handlers dynamically is another great use of statement lambdas:
AddHandler b.Click, Sub(sender As Object, e As EventArgs)
MsgBox(Button Clicked)
'insert more complex logic here
End Sub
And, in fact, you can use statement lambdas with a feature introduced in Visual Basic 2008: loose delegation. (You can use delegates – type-safe function pointers – to execute multiple functions at once.) This combination results in a much simpler signature:
AddHandler b.Click, Sub()
MsgBox(Button Clicked)
'insert more complex logic here
End Sub
Delegate looseness allows you to completely ignore parameters in event handlers – this is a great advantage, as long as they are not used at all, so they are only visually intrusive.
In addition to the single-line Sub lambdas and multi-line Sub lambdas we have seen so far, Visual Basic 2010 also supports multi-line Function lambdas:
Dim query = customers.Where(Function(c)
'Return only customers that have not been saved
'insert more complex logic here
Return c.ID = -1
End Function)
Another interesting aspect of statement lambdas is how they intersect with the anonymous delegates introduced in Visual Basic 2008. People often confuse these delegates with C#'s anonymous methods, although they are not strictly the same. Anonymous delegation occurs when the Visual Basic compiler infers the delegate type based on the lambda's method signature:
Dim method = Function(product As String)
If product = Paper Then
Return 4.5 'units in stock
Else
Return 10 '10 of everything else
End If
End Function
MsgBox(method(Paper))
If you run this code, you will see the value 4.5 displayed in the message box. Additionally, if you hover over method, you will see the text Dim method As <Function(String) As Double>. Since we did not provide an actual delegate type, the compiler will automatically generate one as follows:
Delegate Function $compilerGeneratedName$(product As String) As Double
This is called an anonymous delegate because it will only appear in code generated by the compiler, not in code as written. Note that when in fact no As clause is provided to specify the lambda's return type, the compiler infers the return type to Double. The compiler will look at all return statements within the lambda and will determine the types Double (4.5) and Integer (10):
'Notice the As Single
Dim method = Function(product As String) As Single
If product = Paper Then
Return 4.5 'units in stock
Else
Return 10 '10 of everything else
End If
End Function
It then runs its baseline type algorithm and determines that it can safely convert 10 to a Double, but cannot safely convert 4.5 to an Integer; therefore Double is the better choice.
You can also control the return type explicitly, in which case the compiler will not try to infer the type. It is very common to assign the lambda to a variable with an explicit delegate type, rather than relying on the compiler to infer the delegate type:
Dim method As Func(Of String, Single) =
Function(product)
If product = Paper Then
Return 4.5 'units in stock
Else
Return 10 '10 of everything else
End If
End Function
Because an explicit target type is provided, there is no need to declare As String or As Single; the compiler can infer its existence based on the delegate type on the left side of the statement. So if you hover over product, you'll see that the inferred type is String. You no longer have to specify As Single because the delegate type already provides that information. In the previous example, the signature of the Func delegate (which is included with the .NET Framework) looks like this:
Delegate Function Func(Of T, R)(ByVal param As T) As R
There is one small exception, as we will see later in the section on Generic Variance.
Auto-implemented properties
In Visual Basic, properties are class members that expose the state of an object to the outside world. A typical property declaration looks like the following:
Private _Country As String
Property Country As String
Get
Return_Country
End Get
Set(ByVal value As String)
_Country = value
End Set
End Property
An actually very simple concept with 10 lines of code. Since a typical object often has dozens of properties, you end up including a lot of boilerplate code in your class definition. To simplify such tasks, Visual Basic 2010 introduces autoimplemented properties, which allow you to define simple properties with just one line of code:
Property Country As String
In this case, the compiler will continue to run and automatically generate getters, setters, and supporting fields. The name of a supported field is always the name of an attribute preceded by an underscore character: _Country in this example. This naming convention ensures binary serialization compatibility when changing auto-implemented properties to regular properties. Binary serialization will continue to work as long as the names of the backing fields are the same.
One of the nice things you can do with automatically implemented properties is specifying an initializer that sets the property's default value when the constructor runs. For example, a common scenario with entity classes sets the primary key to a value like -1 to indicate that it is in an unsaved state. The code will look like this:
Property ID As Integer = -1
When the constructor runs, the backing field (_ID) is automatically set to the value -1. Initializer syntax also works for reference types:
Property OrderList As List(Of Order) = New List(Of Order)
Because you don't have to type the name of the type twice, the previous line of code may not have very obvious Visual Basic characteristics. The good news is that there is a shorter syntax for regular variable declarations that is consistent with the syntax allowed by Visual Basic:
Property OrderList As New List(Of Order)
You can even use this syntax with an object initializer to allow other properties to be set:
Property OrderList As New List(Of Order) With {.Capacity = 100}
Obviously, for more complex properties, an extended syntax is still necessary. You can still type Property{Tab} to activate the old property fragment. Alternatively, after typing the first line of the property, you can just enter Get{Enter} and the IDE will generate the old-style property:
Property Name As String
Get
End Get
Set(ByVal value As String)
End Set
End Property
One usually finds: the new property syntax is almost identical to that of public fields, so why not use public fields instead? There are several reasons:
Most of the .NET data binding infrastructure works in terms of properties rather than fields.
An interface cannot enforce the presence of a field; it can enforce the presence of an attribute.
Properties provide longer-term flexibility for changing business rules. For example, suppose someone introduces a rule that phone numbers must be 10 digits long. This validation cannot be performed if assigned to a public field. Changing public fields to properties is a breaking change for scenarios such as binary serialization and reflection.
Collection initializer
A common .NET practice is to instantiate a collection and then populate the collection by calling the Add method once for each element:
Dim digits As New List(Of Integer)
digits.Add(0)
digits.Add(1)
digits.Add(2)
digits.Add(3)
digits.Add(4)
digits.Add(5)
digits.Add(6)
digits.Add(7)
digits.Add(8)
digits.Add(9)
But for a fundamentally simple concept, there's a lot of syntactic overhead. Visual Basic 2010 introduced collection initializers to make it easier for you to instantiate collections. For this code:
Dim digits = New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
The compiler will automatically generate all calls to the Add method. You can also use Visual Basic's As New syntax functionality:
Dim digits As New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
Note that on the Visual Basic Team we always recommend using the second syntax (As New) over the former because it makes the code more resilient to changes in the Option Infer setting.
You can use a collection initializer against any type that meets the following requirements:
You can iterate over the type using a For Each statement – that is, the type implements IEnumerable. (For a more precise/detailed definition of collection types, see Section 10.9.3 of the Visual Basic Language Specification at msdn.microsoft.com/library/aa711986(VS.71).aspx).
The type has an accessible (but not necessarily public) parameterless constructor.
The type has an accessible (but not necessarily public) instance or extension method named Add.
This means that you can also use collection initializers for more complex types, such as dictionaries:
Dim lookupTable As New Dictionary(Of Integer, String) From
{{1, One},
{2, Two},
{3, Three},
{4, Four}}
(Note that even though this statement spans five lines, there are no underscore characters.) In this case, the compiler will generate code equivalent to the old way of initializing a dictionary:
Dim lookupTable As New Dictionary(Of Integer, String)
lookupTable.Add(1, One)
lookupTable.Add(2, Two)
lookupTable.Add(3, Three)
lookupTable.Add(4, Four)
The compiler is calling the Add method with two parameters instead of one. It knows to do this because the value passed into the collection initializer is inside nested braces, like this: {{1, One}, {2, Two}, …}. For each set of nested braces, the compiler attempts to pass these parameters to a compatible Add method.
It is also possible to provide your own custom Add implementation by using extension methods:
<Extension()>
Sub Add(ByVal source As IList(Of Customer),
ByVal id As Integer,
ByVal name As String,
ByVal city As String)
source.Add(New Customer With
{
.ID = id,
.Name = name,
.City = city
})
End Sub
(Look at all those missing underscore characters!) This method extends any type that implements IList(Of Customer), which then allows you to use the new collection initializer syntax, like this:
Dim list = New List(Of Customer) From
{
{1, Jon, Redmond},
{2, Bob, Seattle},
{3, Sally, Toronto}
}
(Add three customers to the list). You can also use collection initializers with automatically implemented properties:
Property States As New List(Of String) From {AL, AK, AR, AZ, ...}
array literal
In addition to more powerful ways to work with collection types, Visual Basic 2010 also provides some powerful enhancements for working with arrays. Assume the following code (works fine in older versions):
Dim numbers As Integer() = New Integer() {1, 2, 3, 4, 5}
By looking at the elements in this array, it's obvious that each element is an integer, so having to actually print out the Integer twice in this line doesn't really add any value. Array literals allow you to create an array by placing all the elements of an array within curly braces and letting the compiler automatically infer the types:
Dim numbers = {1, 2, 3, 4, 5}
The type of numbers is not Object but Integer() (as long as Option Infer is enabled), the reason being that the array literal now represents itself and has its own type. Let's assume a more complex example:
Dim numbers = {1, 2, 3, 4, 5.555}
In this case, the type of numbers will be inferred as Double(). The compiler determines the type by examining each element in the array and calculating the base type, using the same algorithm discussed earlier for inferring the return type of a statement lambda. What happens if there is no base type? For example, as shown in the following code:
Dim numbers = {1, 2, 3, 4, 5}
In this case, converting an Integer to a String will reduce the conversion range (that is, data loss may occur at run time), and similarly, converting a String to an Integer will also reduce the conversion range. The only safe type to choose from is Object() (the compiler will generate an error if Option Strict is enabled).
Array literals can be nested to form multidimensional arrays or jagged arrays:
'2-dimensional array
Dim matrix = {{1, 0}, {0, 1}}
'jagged array - the parentheses force evaluation of the inner array first
Dim jagged = { ({1, 0}), ({0, 1}) }
dynamic language runtime
Although Visual Basic is technically a static language in nature, it has always had very powerful dynamic features, such as late binding. Visual Studio 2010 comes with a new platform called the Dynamic Language Runtime (DLR) that makes it easier to build dynamic languages – and communicate between them. Visual Basic 2010 has been updated to fully support the DLR in its late binders, allowing developers to use libraries and frameworks developed in other languages (such as IronPython/IronRuby).
An outstanding advantage of this feature is that syntactically nothing has changed (in fact, not a single line of code has been modified in the compiler to support this feature). Developers can still perform late binding operations as they did in previous versions of Visual Basic. What has changed is the code in the Visual Basic runtime (Microsoft.VisualBasic.dll), which now recognizes the IDynamicMetaObjectProvider interface provided by the DLR. If an object implements this interface, the Visual Basic runtime builds a DLR CallSite and allows the object and the language that provides it to inject their own semantics into the operation.
For example, the Python standard library contains a file called random.py, which has a method called shuffle that can be used to randomly rearrange the elements in an array. Calling this method is simple:
Dim python As ScriptRuntime = Python.CreateRuntime()
Dim random As Object = python.UseFile(random.py)
Dim items = {1, 2, 3, 4, 5, 6, 7}
random.shuffle(items)
At runtime, Visual Basic sees that the object implements IDynamicMetaObjectProvider and therefore passes control to the DLR, which then communicates with Python and executes the method (passing the array defined in Visual Basic as a parameter to the method).
This is an example of calling a DLR-enabled API, but developers can also create their own APIs that use this functionality. The key is to implement the IDynamicMetaObjectProvider interface, in which case the Visual Basic and C# compilers will recognize objects with special dynamic semantics. Please do not implement this interface manually, it is easier to inherit from the System.Dynamic.DynamicObject class (which already implements this interface) and override only a few methods. Figure 3 shows a complete example of creating a custom dynamic object (a property bag that appears to create properties on the fly) and using normal Visual Basic late binding to call the object. (For more information on using DynamicObject, read Doug Rothaus's excellent article at blogs.msdn.com/vbteam/archive/2010/01/20/fun-with-dynamic-objects-doug-rothaus.aspx .)
Figure 3 Create a custom dynamic object and call the object using Visual Basic late binding
Imports System.Dynamic
Module Module1
Sub Main()
Dim p As Object = New PropertyBag
p.One = 1
p.Two = 2
p.Three = 3
Console.WriteLine(p.One)
Console.WriteLine(p.Two)
Console.WriteLine(p.Three)
End Sub
Class PropertyBag : Inherits DynamicObject
Private values As New Dictionary(Of String, Integer)
Public Overrides Function TrySetMember(
ByVal binder As SetMemberBinder,
ByVal value As Object) As Boolean
values(binder.Name) = value
Return True
End Function
Public Overrides Function TryGetMember(
ByVal binder As GetMemberBinder,
ByRef result As Object) As Boolean
Return values.TryGetValue(binder.Name, result)
End Function
End Class
End Module
generic variance
This is a function that may indeed sound complex at first (with terms like covariance and contravariance), but it's actually quite simple. If you have an object of type IEnumerable(Of Apple) and want to assign it to IEnumerable(Of Fruit), this should be legal because every Apple is a Fruit (enforced by inheritance). Unfortunately, prior to Visual Basic 2010, generic variance was not supported in the compiler, even though it was actually supported in the common language runtime (CLR).
Let's take a look at the example in Figure 4. In Visual Basic 2008, the code in Figure 4 will produce a compile error on the Dim enabledOnly line (or, if Option Strict is disabled, a runtime exception). The workaround is to call the .Cast extension method as follows:
'Old way, the call to Cast(Of Control) is no longer necessary in VB 2010
Dim enabledOnly = FilterEnabledOnly(buttons.Cast(Of Control))
This is no longer necessary because in Visual Basic 2010, the IEnumerable interface has been marked as covariant by using the Out modifier:
Interface IEnumerable(Of Out T)
...
End Interface
Figure 4 Example of generic variance
Option Strict On
Public Class Form1
Sub Form1_Load() Handles MyBase.Load
Dim buttons As New List(Of Button) From
{
New Button With
{
.Name = btnOk,
.Enabled = True
},
New Button With
{
.Name = btnCancel,
.Enabled = False
}
}
Dim enabledOnly = FilterEnabledOnly(buttons)
End Sub
Function FilterEnabledOnly(
ByVal controls As IEnumerable(Of Control)
) As IEnumerable(Of Control)
Return From c In controls
Where c.Enabled = True
End Function
End Class
This means that the generic parameter T is now a variable (that is, it is suitable for inheritance), and the compiler will ensure that the parameter is only used where the type comes from an interface. Generic parameters can also be inverse variables, which means they are only used where they are entered. A type can actually have both. For example, the Func delegate discussed earlier has both contravariant parameters (for what is passed in) and covariant parameters (for the return type):
Delegate Function Func(Of In T, Out R)(ByVal param As T) As R
You can use In and Out modifiers on custom interfaces and delegates. Many common interfaces and delegates in the .NET Framework 4 are marked as variables; common examples include all Action/Func delegates, IEnumerable(Of T), IComparer(Of T), IQueryable(Of T), etc.
The great thing about generic variance is that it's a feature you don't have to worry about at all - if it's doing its job, you'll never notice it. Situations that once resulted in compiler errors or required calling .Cast(Of T) work fine in Visual Basic 2010.
Improved optional parameters
Optional parameters provide a useful efficiency feature that allows developers to build more flexible methods and avoid cluttering a class with many method overloads. In the past there was a slight restriction that optional parameters could not be null (or even of any non-internal structure type). Visual Basic 2010 now allows you to define optional parameters of any value type:
Sub DisplayOrder(ByVal customer As Customer,
ByVal orderID As Integer,
Optional ByVal units As Integer? = 0,
Optional ByVal backgroundColor As Color = Nothing)
End Sub
In this example, units are of type Nullable(Of Integer) and backgroundColor is a non-content structure type, but they are still used as optional parameters. Visual Basic 2010 also provides better support for generic optional parameters.
Embedded interop types
A common weakness for applications that perform COM interop is the need to use a Primary Interop Assembly (PIA). A PIA is a .NET assembly that acts as a runtime callable wrapper (RCW) on a COM component and has a unique GUID that identifies it. The .NET assembly communicates with the PIA, which then performs any necessary marshaling to move data between COM and .NET.
Unfortunately, PIAs can complicate deployment because they are additional DLLs that need to be deployed to end-user computers. They can also cause versioning issues – for example, if you want your application to work against both Excel 2003 and Excel 2007, you will need to deploy both PIAs with your application.
The embedded interop type feature embeds directly into the application, but only the types and members from the PIA that are absolutely necessary, so there is no need to deploy the PIA to the end user's computer.
To enable this feature for an existing object (it is enabled by default for new references), select the reference in Solution Explorer and change the Embed Interop Types option in the Properties window (see Figure 5). Alternatively, if compiling with the ming command line compiler, use the /l (or /link) switch instead of /r and /reference.
Figure 5 Enabling embedded interop types in Solution Explorer
After enabling this feature, the application will no longer rely on PIA. In fact, if you open the assembly in Reflector or ildasm, you will notice that there is actually no reference to the PIA at all.
Multiple goals
The best part about all the features in Visual Basic 2010 is that you can use them even in projects targeting .NET Framework 2.0 through .NET Framework 3.5. This means that features such as implicit line continuation characters, array literals, collection initializers, statement lambdas, automatically implemented properties, etc. will all be available in existing projects without having to retarget the .NET Framework 4 .
The exception is embedded interop types, which rely on types that are only available in .NET Framework 4; therefore, if you target .NET Framework versions 2.0 through 3.5, you cannot use this feature. Additionally, types marked as variables are only marked as they were in the .NET Framework 4, so in the previous example, if you target versions 2.0 to 3.5, you still have to call .Cast(Of T). However, if you target these earlier versions, you can create your own variable types (using the In/Out modifiers).
To change the current target framework of your application, double-click My Projects, click the Compile tab, click Advanced Compilation Options, and then select from the combo box at the bottom.
There is actually no ming command line switch to enable this feature when compiling from the ming command line. In effect, the compiler looks at which assembly provides the definition of System.Object (usually mscorlib) and which framework the assembly is targeted to, and then marks that value in the output assembly. (The compiler uses this same mechanism when generating Silverlight assemblies.) When using an IDE, all of this happens transparently, so you usually don't have anything to worry about.
Welcome to try
As you can see, Visual Basic 2010 has many powerful features that allow you to be more productive while writing fewer lines of code and letting the compiler do more of the work. In this article, I've only discussed language features, but there are countless great enhancements to the Visual Basic 2010 IDE. Some enhancements are listed below:
Navigate to
Highlight quotes
generated from use
Better IntelliSense (substring matching, camelcase lookup, suggestion patterns – useful for testing your development style first)
Multiple monitor support
Zoom
The Visual Basic team is eager to hear your feedback on our efforts to improve Visual Basic, so please send us your comments and questions on Microsoft Connect. To learn more about the language and IDE features, check out the content at msdn.com/vbasic, which includes articles, examples, and how-to videos. Of course, the best way to learn is to delve into and use the product, so it's time to install and try it out.