Language
Integer data types
A VB6 Integer variable is a 16-bit signed integer, therefore it should be translated to
Short under VB.NET; likewise, a VB6 Long variable is a 32-bit signed integer and should be translated to
Integer under VB.NET. (A VB.NET Long variable is a 64-bit signed integer.)
Currency data type
The Currency data type isn’t supported by VB.NET; variables of this type should be converted to
Decimal. However, the Decimal data type has greater precision and range than Currency, therefore you have no guarantee that math expressions deliver the same result they do in VB6. For example, in VB6 a Currency operation might raise an overflow, but it would be evaluated correctly under VB.NET.
Variant data type
VB.NET doesn’t support the Variant data type; all Variant members are translated to
Object members. In many cases, however, the two types aren’t equivalent. For example, an Object .NET variable can’t hold the special Empty, Null, and Missing values.
VB Migration Partner offers the option to render a Variant variable with the special
VB6Variant type, if a
SetType or
ChangeType pragma is specified.
NOTE: starting with version 1.52, the VB6Variant class is not officially supported.
Type declaration suffixes
A consequence of the fact that VB.NET redefines the meaning of Integer and Long keywords is that the meaning of type declaration suffixes has changed too. Now a variable whose name ends with “%” is a 32-bit integer; a variable whose name ends with “&” is a 64-bit integer; a variable whose name ends with “@” is a Decimal variable. However, it is recommended that you get rid of type declaration suffixes and convert them in standard and more readable As clauses.
Fixed-length strings
VB6 fields, local variables, and members of Type structures can be defined as fixed-length strings, as in:
Dim buffer As String * 256
An uninitialized fixed-length string initially contains only ASCII 0 characters; when you assign any value to it, the value is truncated to the maximum length, or padded with spaces to the right if shorted than the maximum length. VB.NET doesn’t support fixed-length strings. The Microsoft.VisualBasic.Compatibility.dll assembly defines a
FixedLengthString type which behaves like fixed-length strings, but there are significant differences with the original VB6 type.
VB Migration Partner maps fixed-length strings to the
VB6FixedString type, which mimics VB6 behavior more closely:
Dim buffer As VB6FixedString(256)
Conversions between Date and Double types
VB6 allows you to use a Double variable whenever a Date value is expected, and vice versa. This is possible because a Date value is nothing but a Double value whose integer portion represents the number of days elapsed since December 30, 1899 and whose fractional part represents the time portion of the date. When converting a piece of VB6 to VB.NET such implicit conversions must become explicit by calls to the
ToOADate and
FromOADate methods of the Date type:
Dim dat As Date, dbl As Double
…
dbl = dat.ToOADate()
dat = Date.FromOADate(dbl)
For uniformity and readability’s sake, VB Migration Partner generates calls to
DateToDouble6 and
DoubleToDate6 methods when the original VB6 code implicitly converts a Date value to Double, or vice versa.
Conversions between String and Byte arrays
VB6 supports implicit conversions from String to Byte arrays, and vice versa, as in this code snippet:
Dim s1 As String, s2 As String, b() As Byte
s1 = "abcde"
b = s1
s2 = b
VB.NET doesn’t support such implicit conversions and requires explicit calls to methods of the
System.Text.Encoding class:
Dim s1 As String, s2 As String, b() As Byte
s1 = "abcde"
b = Encoding.Unicode.GetBytes(s1)
s2 = Enconding.Unicode.GetString(b)
For uniformity and readability’s sake, VB Migration Partner generates calls to
ByteArrayToString6 and
StringToByteArray6 methods when the original code implicitly converts a Byte array to a String, or vice versa.
Conversions from Boolean values
VB6 supports implicit conversions from Boolan values to other numeric data types. VB.NET requires that you use the appropriate conversion operator.
VB Migration Partner uses the
CByte operator when converting to a Byte variable and
CShort when converting to any other numeric data type.
VB.NET keywords
Several VB.NET keywords aren’t reserved words under VB6 and can be used as member names. Examples are AddHandler, Handles, Shadows, and TimeSpan. When the name of a VB6 member matches a VB.NET keyword it must be enclosed between square brackets, as in
Dim [handles] As Integer
Block variables
If Dim keyword appears inside an If, For, For Each, Do, While, or Select block, then VB2005 limits the scope of the variable to the block itself whereas VB6 makes the variable visible to the entire method:
Sub Test(ByVal n As Integer)
If n > 0 Then
Dim x As Integer
…
Else
…
End If
…
End Sub
VB Migration Partner automatically moves the variable declaration outside the code block:
Sub Test(ByVal n As Short)
Dim x As Short
If n > 0 Then
…
Else
…
End If
…
End Sub
Auto-instancing variables
VB6 variables declared with the “As New” clause are known as
auto-instancing variables. The key property of these variables is lazy instantiation: the object referenced by the variable is created as soon as one of its members is referenced; if the variable is set to Nothing, the object is re-instantiated the next time the variable is referenced. (A side-effect of this behavior is that testing such a variable against Nothing always returns False.) The .NET Framework has no notion of auto-instancing variables; in fact the following VB.NET statement
Dim w As New Widget
is just a shorthand for the following, more verbose, declaration:
Dim w As Widget = New Widget
where it is clear that the object is instantiated when it is declared. If you later set the variable to Nothing, no object is re-created when you reference the variable again.
By default, VB Migration Partner translates auto-instancing variables verbatim, therefore the original VB6 semantics is lost and runtime errors might occur in the converted program. However, it offers the ability to generate code that preserves the VB6 behavior and avoids subtle bugs or unexpected exceptions. You can enable this feature by means of the
AutoNew pragma.
Auto-instancing arrays
In addition to individual auto-instancing variables, VB6 also supports auto-instancing arrays, as in the following statement:
Dim arr(10) As New Widget
Each element of such an array behaves like an auto-instancing variable. However, the “As New” clause is invalid for VB.NET arrays, therefore VB Migration Partner drops the “New” keyword and generate a regular array:
Dim arr(10) As Widget
It is up to the developer to correctly initialize all the elements in the array to avoid NullReference exceptions. However, if the application relies on the auto-instancing semantics, such a fix causes the VB.NET application to behave differently from the original VB6 code. VB Migration Partner offers the ability to create a “true” auto-instancing array, by means of the
AutoNew pragma.
Parameter default passing mechanism
Under VB6, method parameters are passed by-reference if the method parameter isn’t explicitly declared with the ByVal keyword. Under VB.NET, method parameters are passed by-value if the method parameter isn’t explicitly declared with the ByRef keyword. Both VB Upgrade Wizard and VB Migration Partner correctly add an explicit ByRef for parameters that don’t specify a ByVal keyword.
Additionally, VB Migration Partner detects parameters that unnecessarily use ByRef and can optional convert them to ByVal parameters.
Optional parameters
In VB6 you can include or omit the default value of an optional parameter; if you omit it, the default value for its type is assumed (0 for numeric types, “” for strings, Nothing for objects):
Sub Test(ByVal Optional x As Short, ByVal Optional y As String)
End Sub
VB.NET requires that the default value for a parameter be specified:
Sub Test(ByVal Optional x As Integer = 0, ByVal Optional y As String = "")
End Sub
ParamArray parameters
VB6 requires that ParamArray parameters be specified with an implicit ByRef keyword:
Sub Test(ParamArray arr() As Variant)
End Sub
By contrast, VB.NET requires that an explicit ByVal keyword be specified:
Sub Test(ByVal ParamArray arr() As Object)
End Sub
This detail makes the difference if the method modifies one of the elements of the parameter and some code relies on the fact that the argument is modified, as in this code:
Sub Increment(ParamArray arr() As Variant)
Dim i As Integer
For i = 0 To UBound(arr)
arr(i) = arr(i) + 1
Next
End Sub
Sub Main()
Dim n As Variant
n = 10
Increment n
Debug.Print n
End Sub
After the migration to VB.NET, individual values passed to a ParamArray parameter are passed by value, therefore any change inside the method isn’t propagated back to the caller.
Sub Increment(ByVal ParamArray arr() As Object)
Dim i As Short
For i = 0 To UBound(arr)
arr(i) = arr(i) + 1
Next
End Sub
Sub Main()
Dim n As Object
n = 10
Increment(n)
Debug.WriteLine(n)
End Sub
VB Migration Partner copes with this issue in two ways. First, it makes you aware of the potential problem by emitting a warning if any element of a ParamArray vector is modified inside the method; second, it provides a pragma that allows you to generate an overload of the method that doesn’t suffer from the issue.
Assignments between arrays
When you assign an array to another array variable under VB6, a
copy of the source array is assigned to the target variable. If you later modify either the source or the target array, the other array isn’t affected. VB.NET arrays are reference types, therefore assignment between arrays are resolved internally by just assigning the target variable a
pointer to the source array. If you later modify either array, the other array is modified.
Dim a(10) As Integer
Dim b() As Integer
b = a
a(0) = 999
MsgBox b(0)
VB Migration Partner solves this problem by invoking the destination array’s
Clone method:
targetArray = sourceArray.Clone()
Assignments between Structures
Both VB6’s Type blocks and VB.NET Structure blocks are value types; this implies that when you assign a Structure to a variable of same type, then a
copy of the entire structure is assigned. If you later modify either the destination or the target Structure, then the other Structure isn’t affected in any way. However, in VB.NET there’s a caveat: if the Structure contains one or more arrays or fixed-length strings, then the target Structure shares a reference to these arrays or fixed-length strings. For example, consider the following code:
Structure TestUDT
Public Names() As String
Public Location As VB6FixedString
End Structure
Dim source As TestUDT
ReDim source.Names(10)
source.Location = "Italy"
Dim dest As TestUDT = source
source.Names(1) = "Code Architects"
Debug.WriteLine(dest.Names(1))
To have Structure assignments behave exactly as in VB6, if a Structure includes arrays or fixed-length strings then VB Migration Partner expands the Structure definition with a Clone method that returns a distinct copy of the Structure. All assignments between Structures of such types are then modified to call the Clone method:
Dim dest As TestUDT = source.Clone()
Structure initialization
If you declare a VB6 Type variable, all the elements in the Type are correctly initialized; a VB.NET Structure can include neither a default constructor nor field initializers, therefore a Structure variable that has been just declared can have one or more uninitialized fields. For example, consider the following VB6 Type block:
Type TestUDT
n As Integer
s As String * 10
a(10) As String
w As New Widget
End Type
and now consider the corresponding VB.NET Structure:
Structure TestUDT
Public n As Integer
Public s As VBFixedString
Public a() As String
Public w As Widget
Public Sub InitializeUDT()
s = New VBFixedString(10)
ReDim a(10)
w = New Widget
End Sub
End Structure
The InitializeUDT method is necessary because .NET Structures can’t have constructors with zero arguments or field initializers. The Upgrade Wizard requires that you manually invoke the InitializeUDT method to ensure that a structure variable be correctly initialized before being used:
Dim udt As TestUDT
udt.InitializeUDT
VB Migration Partner frees you from the need to manually initialize the structure, because it generates a constructor with one (dummy) parameter and automatically invokes this constructor whenever the application defines a structure variable that requires this treatment:
Structure TestUDT
Public n As Integer
Public s As VBFixedString
Public a() As String
Public w As Widget
Public Sub InitializeUDT()
s = New VBFixedString(10)
ReDim a(10)
w = New Widget
End Sub
Public Sub New(ByVal dummy As Boolean)
InitializeUDT()
End Sub
End Structure
Dim udt As New TestUDT(True)
Method calls
VB.NET requires that the list of arguments passed to a Sub method be always enclosed in parenthesis; in VB6 only calls that return a value require that argument list be enclosed in parenthesis:
TestSub(12, "abc")
Late-bound method calls
VB.NET supports late-bound calls, but requires that the
Option Strict Off directive be declared at the project-level or at the top of current file.
VB Migration Partner declares Option Strict Off at the top of each file. After the migration process you should attempt to drop these statements where possible, adjusting the code in the file as necessary.
Fields passed by reference to a method
Consider the following VB6 code, inside the Widget class:
Public ID As String
Public Function GetString() As String
Dim widget As New Widget
widget.ID = "abcde"
TestMethod widget.ID
GetString = widget.ID
End Function
Sub Test(ByRef text As String)
text = UCase(text)
…
End Sub
Invoking the
GetString method under VB6 delivers the result “abcde”, which demonstrates that the ID field has been passed to Test method using by-value semantics even if the receiving
text parameter is declared with the ByRef keyword. This behavior can be explained by knowing that a VB6 field is actually compiled as a Property Get/Let pair and therefore the
Test method is actually receiving the result of the call to the “setter” block, not the actual field.
When this code is converted “as-is” to VB.NET, the ID field is uppercased on return from the Test method, which proves that VB.NET differs from VB6 in how fields are handled.
VB Migration Partner detects the potential bug and emits code that enforces the by-value semantics. The Upgrade Wizard converts the code as-is and doesn’t emit a warning in this case.
Uninitialized local variables
If a local variable of reference type – that is, a String or Object variable – is declared and not immediately initialized, the VB.NET compiler emits the following warning:
Variable 'varname' is used before it has been assigned a value.
A null reference exception could result at runtime.
To avoid this warning you should initialize the variable within the Dim statement:
Dim text As String = ""
Dim obj As Object = Nothing
References to methods defined in modules
If code inside a VB6 form invokes a method defined in a BAS module and the base System.Windows.Forms.Form class exposes a public or protected method with same name, then a compilation error occurs (if the two methods have different syntax) or, worse, the program might not work as intended and possibly throw unexpected exceptions at runtime. For example, suppose that the following statements are located inside a VB6 form:
PerformLayout(False)
ProcessKeyDialog("x"c)
The
PerformLayout method is exposed by the .NET System.Windows.Forms.Form class, but it has a different syntax and therefore it is marked under VB.NET as a compilation error. The
ProcessKeyDialog method is also exposed by the .NET Form class and it takes a character as an argument. Consequently, the form’s
ProcessKeyDialog method is invoked instead of the method defined in the Helpers.bas module, which surely causes a malfunctioning. In both cases, you can resolve the ambiguity by prefixing the method with the module’s name (VB Migration Partner applies this fix):
Helpers.PerformLayout(False)
Helpers.ProcessKeyDialog("x")
VB Migration Partner detects method calls that are ambiguous and generates code that behaves as intended.
Event handlers
In VB6 a method that handles an event must follow the
object_eventname naming convention. In VB.NET event handlers can have any name, provided that they are marked with an opportune
Handles clause:
Private Sub NameClickHandler(ByVal sender As Object, ByVal e As EventArgs) _
Handles txtName.Click
End Sub
Notice that the Handles clause for events raised by the form itself must reference the MyBase object:
Private Sub FormClickHandler(ByVal sender As Object, ByVal e As EventArgs) _
Handles MyBase.Click
End Sub
Name collisions for Type…End Type blocks
In VB6 it is legal to have a private Type…End Type block with same name as a public class or as a Declare method defined elsewhere in the project. When the code is converted to VB.NET, the Structure that corresponds to the original Type must be renamed to avoid these name collisions.
Sub Main
When a Sub Main method is converted to VB.NET it should be decorated with an
STAThread attribute:
<STAThread()> _
Public Sub Main()
End Sub
Member shadowing
A method, property, or event defined in a VB6 form might coincidentally have same name as a member exposed by the System.Windows.Forms.Form class, for example:
Sub PerformLayout(ByVal refresh As Boolean)
End Sub
When this code is converted and compiled under VB.NET a warning occurs, because the .NET Form class exposes a method named
PerformLayout. To make the compiler happy you should add a Shadows keyword:
Shadows Sub PerformLayout(ByVal refresh As Boolean)
End Sub
VB Migration Partner automatically applies this fix when necessary.
Null propagation
VB6 Variant variables can hold the special Null value, but no corresponding value exists in VB.NET. What makes matters worse is that several VB6 functions – namely Str, Hex, Oct, CurDir, Environ, Chr, LCase, UCase, Left, Mid, Right, Trim, RTrim, LTrim, and Space – support null propagation, as the following VB6 code demonstrates:
Dim var As Variant
var = Null
var = var + 10
var = Left(var, 1)
A Null value is neither True nor False, therefore when the test condition of an If block is evaluated as Null, then the Else blocks is always executed; prefixing the expression with the Not operator doesn’t transform the expression into a non-Null value. You often need to manually fixing converted VB6 code that relies on the peculiar way in which VB6 deals with Null values.
A Null value is often the result of a read operation from a database field that contains the NULL value. When converted to VB.NET, the actual value stored in the variable is
DBNull.Value, but the two values aren’t equivalent. For example, an exception is thrown at runtime if the test condition in an If statement evaluates to DBNull.Value, whereas no runtime error occurs in VB6 if the test condition of an If statement evaluates to Null.
VB Migration Partner offers a partial solution to the null propagation problem thanks to the special
VB6Variant type and the
NullSupport pragma.
Enum member names
VB6 allows you to use virtually any character inside the name of an Enum member. If the name isn’t a valid Visual Basic identifier you just need to enclose the name inside square brackets:
Public Enum Test
[two words]
[dollar$ symbol]
[3D]
[New]
End Enum
VB.NET forbids Enum member’s names that start with a digit or contain spaces or other symbols. VB.NET does support square brackets in Enum names, but they only allow to define names that match a language’s keyword.
VB Migration Partner handles this situation by replacing invalid characters with underscores and using a leading underscore if the first character is a digit:
Public Enum Test
two_words
dollar__symbol]
_3D
[New]
End Enum
References to enum members
In VB6 the name of an enum member is considered as a global name. For example, you can reference members of the ColorConstants enum type with or without including the enum name:
txtName.BackColor = ColorContants.vbYellow
txtName.BackColor = vbYellow
In VB.NET the name of the enum type can’t be omitted, therefore only the first syntax form is valid.
Date variables in For…Next loops
VB6 supports Date variables as controlling variables in For…Next loops:
Dim d As Date
For d = Date To Date + 10
Next
Date variables can’t be used as controlling variables in VB.NET For…Next blocks, therefore VB Migration Partner converts the above code by using an “alias” variable of type Double:
Dim d As Date
For d_Alias As Double = DateToDouble6(Date) To DateToDouble6(Date + 10)
d = DoubleToDate6(d_Alias)
Next
Multi-dimensional arrays in For Each…Next loops
There is a minor difference in how elements of a multi-dimensional array are accessed when the array appears in a For Each…Next loop:
Dim arr(10, 20) As Double
Dim v As Variant
For Each v In arr
…
Next
Under VB6, elements are accessed in column-wise order – that is, first all the elements of first column, then all elements in second column, and so forth. Conversely, under VB.NET the elements are accessed in row-wise fashion – that is, first all the elements of the first row, then all the elements of second row, and so forth. If visiting order is significant, the same loop delivers different results after the migration to VB.NET.
VB Migration Partner emits a warning when a multi-dimensional array appears in a For Each…Next block. If you believe that preserving the original visiting order is important, you can insert a call to the
TransposeArray6 method (defined in VBMigrationPartner_Support module), which transposes array elements so that the migrated code works as the original one:
For Each v In TransposeArray6(arr)
…
Next
File operations with UDTs
VB6 and VB.NET greatly differ in how UDTs - Structures in VB.NET parlance – are read from or written to files. Not only are structure elements stored to file in a different format, but the two languages also manage the End-of-File condition in a different way. In VB6 you can read a UDT even if the operation would move the file pointer beyond the current file’s length; in VB.NET such an operation would cause an exception.
VB Migration Partner correctly handles both these problems, by generating code that invoke alternate file-handling methods, such as
FileGet6 and
FilePut6.
Collections can’t be modified inside For Each…Next loops
VB6 allows you to modify a collection inside a For Each…Next loop that iterates on the collection itself. For example, the following code works well and is indeed quite common in VB6:
Dim frm As Form
For Each frm In Forms
Unload frm
Next
This code throws an exception under VB.NET, because unloading a form causes the Forms collection to change inside the loop. The simplest way to work around this problem is having the loop iterate on a copy of the collection, as in this example:
Dim values As New List(Of String)
…
For Each item As String In New List(Of String)(values)
…
Next
VB Migration Partner doesn’t generate this fix, because it is impossible to automatically detect whether the collection is indirectly modified by any method call inside the loop.
DAO.DBEngine object
The DAO.DBEngine object is a global object, which means that VB6 application can reference its members without having to instantiate it first and that it isn’t necessary to include the class name in the method call. In practice, this means that the following VB6 method calls to the OpenDatabase method are both valid and have the same effects:
Dim db1 As DAO.Database, db2 As DAO.Database
Set db1 = DBEngine.OpenDatabase("biblio.mdb")
Set db2 = OpenDatabase("northwind.mdb")
VB.NET doesn’t support global objects, therefore the above statements must be converted as follows:
Dim dbeng As New DAO.DBEngine
Dim db1 As DAO.Database, db2 As DAO.Database
Set db1 = dbeng.OpenDatabase("biblio.mdb")
Set db2 = dbeng.OpenDatabase("northwind.mdb")
ByVal keyword in method calls
VB6 allows you to pass an argument to a by-reference parameter using by-value semantics, by prefixing the argument with the ByVal keyword:
CopyMethod ByVal address, arr(0), 1024
This calling syntax can be used only with Declare methods, and is especially useful with methods whose arguments are declared As Any, because you can’t use the ByVal keyword in the declaration of “As Any” parameters. VB.NET supports neither the ByVal keyword in method call nor As Any parameters in Declare statements.
VB Migration Partner accounts for such ByVal keywords and generates the corresponding overload for the Declare method.
Declare statements pointing to Visual Basic runtime
Expert VB6 developers can invoke methods defined in VB6 runtime, for example to retrieve information about variables and arrays. Code that uses methods defined in the VB6 runtime can’t be migrated to VB.NET, both because the MSVBVM60.DLL library isn’t available and because.NET variables and arrays are stored differently from VB6 and therefore the methods wouldn’t work anyway because.
VB Migration Partner flags Declare statements that point to VB6 runtime with an appropriate warning.
Resource files
VB6 resource files can’t be used under VB.NET and should be converted separately. In addition to convert files to the .NET Framework format, VB.NET applications must be able to reference resources as
My.Resources.Xxxx elements.
Image format tests
VB6 developers can test the type of an image by testing the picture’s Type property against the values of the PictureTypeConstants enumerated type, as in:
If Picture1.Picture.Type = PictureTypeConstants.vbPicTypeEMetafile Then …
The .NET Image type doesn’t expose the Type property, but has an equivalent property named
RawFormat.
Here’s how VB Migration Partner translates previous code:
If Picture1.Picture.RawFormat is System.Drawing.Imaging.ImageFormat.Emf Then …
Remarks starting with three apostrophes
Many developers like to emphasize comments by creating lines of asterisks, dashes, or apostrophes:
The problem in converting this code is that any remark that starts with three apostrophes is considered as an XML comment under VB.NET, therefore this code causes a compilation warning under VB.NET.
VB Migration Partner recognizes the problem and replaces the third apostrophe with a space: