Major VB.NET Changes
- May 18, 2001
Major VB.NET Changes
VB.NET has introduced major changes to the VB language. Some are modifications to existing ways of working, whereas others are brand new. This chapter will cover some of those changes, but this is by no means an exhaustive list of all changes from VB to VB.NET. First, you'll see some of the features that have changed. Then you will see some of the new features.
There are a number of general changes to be aware of when moving from VB to VB.NET. Among them are such topics as the removal of default properties, subs, and functions requiring parentheses (ByVal being the default), and changes to the logical operators. These changes and others are detailed in this section.
In VB6, objects could have default properties. For example, the following code is perfectly valid in VB6:
This code takes the string "Hello, World" and sets the default property of the textbox, the Text property, to the string. The major drawback to default properties is that they require you to have a Set command in VB. For example, take a look at the following block of VB6 code:
Dim txtBillTo as TextBox Dim txtShipTo as TextBox txtShipTo = txtBillTo
The line of code txtShipTo = txtBillTo sets the Text property of txtShipTo to the value in the Text property of txtBillTo. But what if this isn't what you wanted? What if, instead, you wanted to create an object reference in txtShipTo that referred to txtBillTo? You'd have to use this code:
Set txtShipTo = txtBillTo
As you can see, default properties require you to use the Set keyword to set references from one object variable to another.
VB.NET gets around this problem by getting rid of default properties. Therefore, to copy the Text property from txtBillTo into the Text property of txtShipTo, you'd have to use this code:
txtShipTo.Text = txtBillTo.Text
Setting the two variables equal to each other sets a reference from one to the other. In other words, you can set an object reference without the Set keyword:
txtShipTo = txtBillTo ' Object reference in VB.NET
To be more precise, default properties without parameters are no longer supported. Default properties that require parameters are still valid. Default properties with parameters are most common with collection classes, such as in ADO. In an ADO example, if you assume that rs is an ADO Recordset, check out the following code:
Rs.Fields.Item(x).Value ' OK, fully qualified Rs.Fields(x).Value ' OK, because Item is parameterized Rs.Fields(x) ' Error, because value is not parameterized
The easy solution is to fully qualify everything. This avoids any confusion about which properties are parameterized and which are not.
Subs and Functions Require Parentheses
When you used the msgbox function, you must now always use parentheses with functions, even if you are ignoring the return value. In addition, you must use parentheses when calling subs, which you did not do in VB6. For example, assume that you have this sub in both VB6 and VB.NET:
Sub foo(ByVal sGreeting As String) ' implementation code here End Sub
In VB6, you could call this sub one of two ways:
foo "Hello" Call foo("Hello")
In VB.NET, you also could call this sub one of two ways:
Foo("Hello") Call foo("Hello")
The difference, of course, is that the parentheses are always required in the VB.NET calls, even though you aren't returning anything. The Call statement is still supported, but it is not really necessary.
Changes to Boolean Operators
The And, Not, and Or operators have undergone some changes. One of those changes is that they now "short-circuit." For example, in VB6, if you had two parts of an And statement and the first failed, VB6 still examined the second part. Now the second part would not be examined; instead, the statement would fail immediately. Examine the following code:
Dim x As Integer Dim y As Integer x = 1 y = 0 If x = 2 And y = 5/y Then ...
You might look at this code and say that because x=1, when you look at the first part of the If statement, you know that x is not equal to 2, so you can quit. However, VB6 examines the second part of the expression, so this code would cause a divide-by-zero error. VB.NET has short-circuiting, which says that as soon as part of the condition fails, you quit looking. Therefore, this statement does not cause an error in VB.NET.
You can now initialize your variables when you declare them. You could not do this in VB6. In VB6, the only way to initialize a new variable was to do so on a separate line, like this:
Dim x As Integer x = 5
In VB.NET, you can rewrite this into one line of code:
Dim x As Integer = 5
Another significant and much-requested change is that of declaring multiple variables, and what data type they assume, on one line. For example, you might have the following line:
Dim x, y As Integer
As you're probably aware, in VB6, y would be an Integer data type, but x would be a Variant. In VB.NET, this has changed, so both x and y are Integers. If you think, "It's about time," there are many who would agree.
Support for New Assignment Operators
VB.NET now supports shortcuts for performing certain assignment operations. In VB6, you incremented x by 1 with the following line of code:
x = x + 1
In VB.NET, you can type an equivalent statement like this:
x += 1
Not only can you use the plus sign, but VB.NET now supports -=, *=, /=, \=, and ^= from a mathematical standpoint, and &= for string concatenation. If all this looks like C/C++, that's where it came from. Unfortunately, the ++ operator is not supported.
ByVal Is Now the Default
In what many consider a strange decision, the default way to pass parameters in VB has always been by reference. The decision was actually made because passing by reference is faster within the same application, but it can be costly if you are calling components across process boundaries. If you're a little rusty, by reference means that you are passing only the address of a variable into the called routine. If the called routine modifies the variable, it's actually just updating the value in that memory location, and, therefore, the variable in the calling routine is also changed. Take this VB6 example:
Private Sub Command1_Click() Dim x As Integer x = 3 foo x MsgBox x End Sub Sub foo(y As Integer) y = 5 End Sub
If you run this example, the message box will show the value 5. That happens because in VB6, when you pass x to foo, you are just sending the address, so when foo modifies y to 5, it is changing the value in the same memory location to which x points, and this causes the value of x to change.
If you tried to type this example into VB.NET, you'd see something happen. First, of course, you'd have to add parentheses around x in your call to foo. However, when you tried to type the definition of foo, VB.NET would automatically add the word ByVal into the definition, so it would end up looking like this:
Sub foo(ByVal y As Integer)
If you wanted to pass by reference, you would have to add the ByRef keyword yourself, instead of VB.NET using the new default of ByVal.
While...Wend Becomes While...End While
The While loop is still supported, but the closing of the loop is now End While instead of Wend. If you type Wend, the editor automatically changes it to End While.
VB.NET adds the ability to create variables that are visible only within a block. A block is any section of code that ends with one of the words End, Loop, or Next. This means that For...Next and If...End If blocks can have their own variables. Take a look at the following code:
While y < 5 Dim z As Integer ... End While
The variable z is now visible only within the While loop. It is important to realize that while z is visible only inside the While loop, its lifetime is that of the procedure. That means that if you re-enter the While statement, z will have the same value that it did when you left. Therefore, realize that the scope is block level, but the lifetime is procedure level.
VB.NET has changes that affect how you define and work with procedures. Some of those changes are mentioned in this section.
Optional Arguments Require a Default Value
In VB6, you could create an optional argument (or several optional arguments) in a procedure. You could, optionally, give them a default value. That way, if someone chose not to pass in a value for an argument, you had a value in it. If you did not set a default value and the caller did not pass in a value, the only way you had to check was to call the IsMissing statement.
IsMissing is no longer supported because VB.NET will not let you create an optional argument that does not have a default value. IsMissing is not needed because your optional parameters are guaranteed to have a value. For example, your declaration might look like this:
Sub foo(Optional ByVal y As Integer = 1)
Static Not Supported on Subs or Functions
In VB6, you could put Static in the declaration of a sub or function. Doing so made every variable in that sub or function static, which meant that they retained their values between calls to the procedure. For example, this was legal in VB6:
Static Sub foo() Dim x As Integer x = x + 1 End Sub
In this example, x will retain its value between calls. So, the second time this procedure is called, x already has a value of 1, and the value of x will be incremented to 2.
VB.NET does not support Static on the sub or function declaration. In fact, the Visual Studio.NET Beta 1 documentation states that you need to place Static in front of each variable for which you want to preserve the value. However, the IDE won't support Static declarations of variables, telling you to use module-level or class-level variables instead. It is unclear what Microsoft's final position will be with regard to support for the Static keyword.
The Return Statement
The Return statement can be used to produce an immediate return from a called procedure, and it can optionally return a value. For example, take a look at this block of code:
Function foo() As Integer While True Dim x As Integer x += 1 If x >= 5 Then Return x End If End While End Function
In this code, you enter what normally would be an infinite loop by saying While True. Inside the loop, you declare an integer called x and begin incrementing it. You check the value of x and, when it becomes greater than or equal to 5, you call the Return statement and return the value of x. This causes an immediate return and does not wait to hit the End Function statement.
The old way of doing it still works, however. For example, the following code will return a value just as it did in VB6:
Function foo() As Integer Dim x As Integer x += 1 foo = x End Function
ParamArrays Are Now Passed ByVal
A parameter array, or ParamArray, is used when you do not know how many values you will pass into a procedure. ParamArrays allow for an unlimited number of arguments. A parameter array automatically sizes to hold the number of elements you are passing in.
A procedure can have only one ParamArray, and it must be the last argument in the definition. The array must be one-dimensional, and each element must be the same data type. However, the default data type is Object, which is what replaces the Variant data type in VB.NET.
In VB6, all the elements of a ParamArray are always passed ByRef. This cannot be changed. In VB.NET, all the elements are passed ByVal. This cannot be changed.
Properties Can Be Modified ByRef
In VB6, if a class had a property, you could pass that property to a procedure as an argument. If you passed the property ByVal, of course, it just copied the value. If you passed it ByRef, the called procedure could modify the value, but this new value was not reflected back in the object's property.
In VB.NET, however, you can pass a property to a procedure ByRef, and any changes to the property in the called procedure will be reflected in the value of the property for the object.
Take the following VB.NET example. Don't worry if the Class syntax looks strange; just realize that you are creating a class called Test with one property: Name. You instantiate the class in the Button1_Click event procedure and pass the Name property, by reference, to foo. foo modifies the variable and ends. You then use a message box to print out the Name property.
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Dim x As New Test() foo(x.Name) msgbox(x.Name) End Sub Sub foo(ByRef psName As String) psName = "Torrey" End Sub Public Class Test Dim msName As String Property Name() As String Get Name = msName End Get Set msName = value End Set End Property End Class
When you run this example, the message box will display "Torrey." This shows that the property of the object is being passed by reference and that changes to the variable are reflected back in the object's property. This is new to VB.NET.
Arrays have undergone some changes as well. Arrays were somewhat confusing in previous versions of VB. VB.NET seeks to address any confusion by simplifying the rules and removing the ability to have nonzero lower boundaries.
In VB6, if you left the default for arrays to start at 0, declaring an array actually gave you the upper boundary of the array, not the number of elements. For example, examine the following code:
Dim y(2) As Integer y(0) = 1 y(1) = 2 y(2) = 3
In this VB6 code, you declare that y is an array of type Integer, and the upper boundary is 2. That means that you actually have three elements: 02. You can verify this by setting those three elements in code.
In VB.NET, array declaration works differently. If you tried the same code shown earlier, you would receive an error. That's because, in VB.NET, the declaration shows the array size, not the upper boundary. Look at this statement:
Dim y(2) As Integer
This line of code in VB.NET says to create an array with two elements, and the indexes will be 01. Therefore, array definitions will now be one less than you had in VB6.
Lower Boundary Is Always Zero
VB6 allowed you to have a nonzero lower boundary in your arrays in a couple of ways. First, you could declare an array to have a certain range. If you wanted an array to start with 1, you declared it like this:
Dim y(1 To 3) As Integer
This would create an array with three elements, indexed 13. If you didn't like this method, you could use Option Base, which allowed you to set the default lower boundary to either 0 (the default) or 1.
VB.NET removes those two options from you. You cannot use the "1 to x" syntax, and Option Base is no longer supported. In fact, because the lower boundary of the array is always 0, the Lbound function is no longer supported.
Array Assignment Is ByRef Instead of ByVal
In VB6, if you had two array variables and set one equal to the other, you actually were creating a copy of the array in a ByVal copy. Now, in VB.NET, setting one array variable to another is actually just setting a reference to the array. To copy an array, you can use the Clone method.
Data Type Changes
There are several changes to data types that are important to point out. These changes can have an impact on the performance and resource utilization of your code. The data types in VB.NET correspond to the data types in the System namespace, which is important for cross-language interoperability.
Short, Integer, and Long
In VB.NET, the Short data type is a 16-bit integer, Integer is a 32-bit integer, and Long is a 64-bit integer. In VB6 code, you might have used Long in many places because it was 32-bit and therefore performed better on 32-bit operating systems than Integer, which was just 16-bit. On 32-bit operating systems, 32-bit integers perform better than 64-bit integers, so in VB.NET, you will most likely want to return to using the Integer data type instead of the Long data type.
Automatic String/Numeric Conversion Not Supported
In VB6, it was easy to convert from numbers to strings and vice versa. For example, examine this block of code:
Dim x As Integer Dim y As String x = 5 y = x
In VB6, there is nothing wrong with this code. VB will take the value 5 and automatically convert it into the string "5". VB.NET, however, disallows this type of conversion by default. Instead, you would have to use the CStr function to convert a number to a string, or the Val function to convert a string to a number. You could rewrite the preceding code for VB.NET in this manner:
Dim x As Integer Dim y As String x = 5 y = CStr(x)
Fixed-Length Strings Not Supported
In VB6, you could declare a fixed-length string by using a declaration like the one shown here:
Dim y As String * 30
This declared y to be a fixed-length string that could hold 30 characters. If you try this same code in VB.NET, you will get an error. All strings in VB.NET are of variable length.
All Strings Are Unicode
If you got tired of worrying about passing strings from VB to certain API calls that accepted either ANSI or Unicode strings, you will be happy to hear that all strings in VB.NET are Unicode.
The Value of True Has Changed
Long ago, someone at Microsoft decided that True in VB was equal to 1. This has finally changed, and True is now equal to 1. False is equal to 0.
In reality, 0 is False and any nonzero is True. However, if you ask for the value of True, you will get 1 instead of 1. You can verify that any nonzero is True by using this code:
Dim x As Integer x = 2 If CBool(x) = True Then msgbox("Value is True") End If
If you make x any positive or negative integer, the CBool function will make it True. If you set x equal to 0, CBool will return False.
The Currency Data Type Has Been Replaced
The Currency data type has been replaced by the Decimal data type. The Decimal data type is a 12-byte signed integer that can have up to 28 digits to the right of the decimal place. It supports a much higher precision than the Currency data type and has been designed for applications that cannot tolerate rounding errors. The Decimal data type has a direct equivalent in the .NET Framework, which is important for cross-language interoperability.
The Variant Data Type Has Been Replaced
The Variant data type has been replaced. Before you start thinking that there is no longer a catch-all variable type, understand that the Variant has been replaced by the Object data type. The Object data type takes up only 4 bytes because all it holds is a memory address. Therefore, even if you set the variable to an integer, the Object variable holds 4 bytes that point to the memory used to store the integer. The integer is stored on the heap. It will take up 8 bytes plus 4 bytes for the integer, so it consumes a total of 12 bytes. Examine the following code:
Dim x x = 5
In this case, x is a variable of type Object. When you set x equal to 5, VB.NET stores the value 5 in another memory location and x stores that memory address. When you attempt to access x, a lookup is required to go find that memory address and retrieve the value. Therefore, just like the Variant, the Object data type is slower than using explicit data types.
According to the VB.NET documentation, the Object data type allows you to play fast and loose with data type conversion. For example, look at the following code:
Dim x x = "5" ' x is a string x = x - 2
According to the documentation, x will now hold the numeric value 3. However, in Visual Studio.NET Beta 1, you receive an Invalid Cast Exception error, even if you turn off Option Strict, which will be discussed in a moment. But if you change the code to use string concatenation, the conversion seems to work fine. For example, the following code will run without an error, and x will end up holding the string "52":
Dim x x = "5" ' x is a string x = x & 2
An Object data type can be Nothing. In other words, it can hold nothing, which means that it doesn't point to any memory address. You can check for nothing by setting the Object variable equal to Nothing or by using the IsNothing function. Both checks are shown in the following code:
Dim x If x = Nothing Then msgbox("Nothing") If IsNothing(x) Then msgbox("Still nothing")
Structured Error Handling
The error handling in VB.NET has changed. Actually, the old syntax still works, but there is a new error-handling structure called Try...Catch...Finally that removes the need to use the old On Error Goto structure.
The overall structure of the Try...Catch...Finally syntax is to put the code that might cause an error in the Try portion and then catch the error. Inside the Catch portion, you handle the error. The Finally portion runs code that happens after the Catch statements are done, regardless of whether there was an error. Here is a simple example:
Dim x, y As Integer ' Both will be integers Try x \= y ' cause division by zero Catch ex As Exception msgbox(ex.Message) End Try
Here, you have two variables that are both integers. You attempted to divide x by y, but because y has not been initialized, it defaults to 0. That division by zero raises an error, and you catch it in the next line. The variable ex is of type Exception, which holds the error that just occurred, so you simply print the Message property, much like you printed the Err.Description in VB6.
In fact, you can still use Err.Description and the Err object in general. The Err object will pick up any exceptions that are thrown. For example, assume that your logic dictates that an error must be raised if someone's account balance falls too low, and another if it drops into the negative category. Examine the following code:
Try If bal < 0 Then Throw New Exception("Balance is negative!") ElseIf bal > 0 And bal <= 10000 Then Throw New Exception("Balance is low; charge interest") End If Catch MessageBox.Show("Error: " & Err.Description) Finally MessageBox.Show("Executing finally block.") End Try
In this case, your business logic says that if the balance drops below zero, you raise an error informing the user that the balance is below zero. If the balance drops below 10,000 but remains above zero, you notify the user to start charging interest. In this case, Err.Description picks up the description you threw in your Exception.
You can also have multiple Catch statements to catch various errors. To have one Catch statement catch a particular error, you add a When clause to that Catch. Examine this code:
Try x \= y ' cause division by zero Catch ex As Exception When Err.Number = 11 msgbox("You tried to divide by zero") Catch ex As Exception msgbox("Acts as catch-all") End Try
In this example, you are looking for an Err.Number of 11, which is the error for division by zero. Therefore, if you get a division-by-zero error, you will display a message box that says, "You tried to divide by zero." However, if a different error were to occur, you would drop into the Catch without a When clause. In effect, the Catch without a When clause acts as an "else" or "otherwise" section to handle anything that was not handled before.
Notice that the code does not fall through all the exceptions; it stops on the first one it finds. In the preceding example, you will raise an Err.Number of 11. You will see the message "You tried to divide by zero," but you will skip over the catch-all Catch. If you want to run some code at the end, regardless of which error occurred (or, indeed, if no error occurred), you could add a Finally statement. The code to do so follows:
Try x \= y ' cause division by zero Catch ex As Exception When Err.Number = 11 msgbox("You tried to divide by zero") Catch ex As Exception msgbox("Acts as catch-all") Finally msgbox("Running finally code") End Try
In this code, whether or not you hit an error, the code in the Finally section will execute.
There is also a documented way to exit from a Try statement early. The Exit Try keyword is supposed to break you out of the Try early, ignoring any Finally code. This does not work in Beta 1, so it is either subject to change or simply not enabled yet. The syntax would look like this:
Try x \= y ' cause division by zero Catch ex As Exception When Err.Number = 11 msgbox("You tried to divide by zero") Exit Try Catch ex As Exception msgbox("Acts as catch-all") Finally msgbox("Running finally code") End Try
Option Strict is a new statement that specifically disallows any type conversions that would result in data loss. You can use only widening conversions; this means that you could convert from an Integer to a Long, but you could not convert from a Long to an Integer. Option Strict also prevents conversions between numbers and strings.
Option Strict is on, by default, in Visual Studio.NET Beta 1. To turn it off, go to the top of the code window and type Option Strict Off.
Structures Replace UDTs
User-defined types, or UDTs, were a way to create a custom data type in VB6. You could create a new data type that contained other elements within it. For example, you could create a Customer data type using this syntax:
Private Type Customer Name As String Income As Currency End Type
You could then use that UDT in your application, with code like this:
Dim buyer As Customer buyer.Name = "Craig" buyer.Income = 20000 MsgBox buyer.Name & " " & buyer.Income
The Type statement is no longer supported in VB.NET. It has been replaced by the Structure statement. The Structure statement has some major changes, but to re-create the UDT shown earlier, the syntax is this:
Structure Customer Dim Name As String Dim Income As Decimal End Structure
Notice that the only real difference so far is that you have to Dim each variable inside the structure, which is something you did not have to do in the Type statement, even with Option Explicit turned on. Notice also that Dim is the same as Public here, meaning that the variables are visible to any instance of the structure.
Structures have many other features, however. One of the biggest differences is that structures can support methods. For example, you could add a Shipping method to the Customer structure to calculate shipping costs based on delivery zone. This code shows adding a DeliveryZone property and a DeliveryCost function:
Structure Customer Dim Name As String Dim Income As Decimal Dim DeliveryZone As Integer Function DeliveryCost() As Decimal If DeliveryZone > 3 Then Return 25 Else Return CDec(12.5) End If End Function End Structure
Here, you have a built-in function called DeliveryCost. To use this structure, your client code would look something like this:
Dim buyer As Customer buyer.Name = "Craig" buyer.Income = 20000 buyer.DeliveryZone = 4 msgbox(buyer.DeliveryCost)
In this case, the message box would report back a value of 25.
If you think that this looks like a class, you are correct. In fact, structures can have properties, methods, and events. They also support implementing interfaces, and they can handle events. There are some caveats, however. Some of those stipulations include:
You cannot inherit from a structure.
You cannot initialize the properties inside a structure. For example, this code is illegal:
Structure Customer Dim Name As String = "Craig" End Structure
Properties are public by default instead of private, as they are in classes.
Structures are value types rather than reference types. That means that if you assign a structure variable to another structure variable, you get a copy of the structure and not a reference to the original structure. The following code shows that a copy is occurring because an update to seller does not update buyer:
Dim buyer As Customer Dim seller ' Object data type seller = buyer seller.Name = "Linda" msgbox(buyer.Name) msgbox(seller.Name)
Note that the preceding code will not work if you have Option Strict turned on. If you want to test this, you'll have to enter Option Strict Off.
There are numerous IDE changes. Several of those have been addressed already: the lack of the Line and Shape controls, the new menu builder, the new way of setting the tab order, and the Dynamic Help window. There are many other changes, such as the debugging windows, but they will not be discussed in this small book.