VBUC FEATURES

C# Generation

The Visual Basic Upgrade Companion has the ability to generate either VB.NET or C#.NET source code. C# generation includes an important set of transformations related to:

C# Syntax

A target syntax writer covering all the C# language features was created to print .cs files with their appropriate syntax. This section contains source code examples to demonstrate how the VBUC handles the common annoyances while upgrading to C#.

General Language Constructions

Several language constructions have a different structure in C#.  These differences require particular transformation rules to achieve the appropriate distribution of the target elements.  In other cases some constructions are completely missing in C# or have similar ones with a different behavior.

Some examples of these differences are:

  • Declarations of classes, modules, user controls, forms, events, native methods, arrays
  • Statements like “Select Case” to “switch” or “With” removal
  • Expressions like “IIF” to the ternary operator “?”
  • Other cases explained with more detail below

Strict Typing

While VB6 had a very permissive type system which resolved types in runtime, C# works with a much more efficient, strict typing system that require all the variables to be properly declared and most type coercions to be explicitly expressed in the source code.

Deducing all the typing information from the VB6 source code is a difficult task since it includes very complex relationships, and in several cases variables are used in an inconsistent way.

The VBUC’s typing mechanism infers a substantial amount of the implicit typing information, allowing a better conversion process by generating most of the required type castings and coercions.

Additionally a coercion engine was added to the VBUC to allow the generation of type corrections, which were not necessary for converting to VB.NET but are required for C#.

For a source code example visit section Resolve Late Bound Variable Types

Event Declaration and Invocation

VB6 control events are defined by name, and its usage is very simple. In C# there is some additional work needed to implement the equivalent structure. In this platform the method that handles the event actions needs to be attached to System.EventHandler to open a listener for that event.

This source sample demonstrates the transformations needed to upgrade a VB6 click event into a fully equivalent .NET event handler.

Note: the VB6 event has a specific naming pattern
<control>_<event name>

Original VB6 Code

Private Sub Command1_Click()
Dim base As Integer
Dim index As Integer
Dim Result As Long
With Text1
If (IsNumeric(.Text) And IsNumeric(Text2.Text)) Then
base = CInt(.Text)
With Text2
index = CInt(.Text)
End With
Result = Module1.Power(base, index)
MyClass.storePower Result
MsgBox Result
Else
MsgBox "Incorrect values found", vbCritical, "Error!"
End If
End With

End Sub

Resulting C# code

The VBUC will generate the necessary code to handle the event contained into the form source code and will add into the designer code the attachment to a system.EventHandler:

Note: the gray highlight for the with structure removal
in the resulting source code.

Designer Code

this.Command1.Click += new System.EventHandler(this.Command1_Click);

Handling Code

private void  Command1_Click( Object eventSender,  EventArgs eventArgs)
{
int base_Renamed = 0;
int index = 0;
int Result = 0;
double dbNumericTemp2 = 0;
double dbNumericTemp = 0;
if (Double.TryParse(Text1.Text, NumberStyles.Number, CultureInfo.CurrentCulture.NumberFormat, out dbNumericTemp) && Double.TryParse(Text2.Text, NumberStyles.Number, CultureInfo.CurrentCulture.NumberFormat, out dbNumericTemp2)){
base_Renamed = Convert.ToInt32(Double.Parse(Text1.Text));
index = Convert.ToInt32(Double.Parse(Text2.Text));

Result = Module1.Power(base_Renamed, ref index);
MyClass.storePower(Result);
MessageBox.Show(Result.ToString(), Application.ProductName);
} else{
MessageBox.Show("Incorrect values found", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

Error Handling

The VB6 error handling model, including statements like “On Error Goto”, “On Error Resume Next” and “Resume”, are not supported in C#. Instead, the “Try ... Catch” structured error handling model should be used.

While not every single “On Error” pattern has an equivalent “Try ... Catch” structure, the VBUC automatically converts the most common used scenarios. The ability to write custom patterns and add them to the VBUC is also available.

For more information see the “Error Handling Transformation” section below.

Module to Classes

C# does not support VB modules.  VB6 modules are converted to classes with static members and the references to these members are qualified with the name of the converted class.

Original VB6 project Structure:

module-to-classes-vb6.jpg

Resulting C#.NET solution:

module-to-classes-csharp-solution.jpg

Parameters

The parameter declaration and passing mechanisms in C# requires several transformations to work with an equivalent behavior as in VB6. 

The following transformations were implemented to support the C# parameters model:

  • Optional parameters declarations:  methods with optional parameters get converted by creating several overloads of the original method receiving different amount of parameters.  The overload with the full set of parameters is the one implementing the original functionality, while all other overloads invoke this one.
  • Optional parameter passing: it remains the same for the converted declaration, and for external functions it generates the corresponding default values.  A default-value-reading module was implemented to feed this process.
  • By-ref parameters: passing expressions as parameters declared as by-ref requires the “ref” keyword to be written before the expression. If the expression is not a proper reference, a temporal variable is generated. Since parameters were “by-ref” by default in VB6, there are many cases where a parameter should have been “by-val”, but this was not explicitly specified by the programmer. This situation causes an important amount of unnecessary temporal variables generated in the C# code to enable the usage of the ref keyword.  This problem is substantially reduced by adding an analysis process which adds the “by-val” modifier to parameters that were declared as by-ref and were not modified in the function body.
  • Out parameters: The VBUC recognizes “out” parameters and generates the necessary “out” keyword when invoking the corresponding external methods.
  • Paramarrays: paramarrays are automatically converted by the VBUC to parameters holding an array of the corresponding type. References to the paramarray are converted accordingly.

Lower Bounds to Zero

Lower bounds in VB6 are 1 by default.  In C# they are always 0.  Several transformations are applied to correct the arrays’ declarations and the indexes used while accessing either arrays or collections.

The VBUC considers the “Option Base” keyword to generate functionally equivalent .NET code that matches the behavior of the VB6 code.

Read the next section for a detailed code example.

Array Dims and Redims

C# arrays are quite inflexible.  They only support lower bound equal to zero and arrays with a constant amount of dimensions (although they can change their magnitudes). However the .NET class Array allows representing flexible arrays that can be re-dimensioned, and their lower bounds changed.

The following modifications are supported by the VBUC:

  • Declaration and access corrections:  every array is inspected during the typing phase to determine what kind of dimensions it is being used with.  Based on this information both the declaration and uses of each array are corrected.
  • Use of Array class for complex cases:  when an array has an inconsistent use of dimensions or lower bounds different to zero or one, they get converted to the class “Array”, which allows re-dimensioning and changing the lower bounds.  All its uses are also modified.
  • Redim statements:  redim statements get converted to an assignment of the array to a construction of the new array:
<arrayref> = new <ArrayType>(<NewDimensions>)

If the array was declared by using the class “Array” an invocation to Array.CreateInstance is used to create the new array.

When the “preserve” keyword was used, a statement is generated after the assignment to copy the original information into the new array instance.

Original VB6 Code (Option Base set to 1):

Public Sub ArrayDimReDim()

'declare the array and the result variable
Dim myTestArray() As Integer
Dim maxInt As Integer

'set the array dimensions
ReDim myTestArray(10)

'populate the array
myTestArray(returnIndex(1)) = 15
myTestArray(returnIndex(2)) = 78
myTestArray(returnIndex(3)) = 23
myTestArray(returnIndex(4)) = 9
myTestArray(returnIndex(5)) = 1
myTestArray(returnIndex(6)) = 5
myTestArray(returnIndex(7)) = 9
myTestArray(returnIndex(8)) = 95
myTestArray(returnIndex(9)) = 2
myTestArray(returnIndex(10)) = 5

'search and display the largest number in the array
maxInt = Module1.max(myTestArray)
MsgBox maxInt, , "MAX INT"

'New array dimansions
ReDim Preserve myTestArray(12)

'populate the newly created fields
myTestArray(11) = 0
myTestArray(12) = 200

'search again for the largest number on the array
maxInt = Module1.max(myTestArray)
MsgBox maxInt, , "MAX INT"

End Sub

The resulting source code contains corrected access indexes for the resulting .NET array and a particular solution to the “ReDim Preserve” keyword using .NET 2.0 generics:

Resulting C#.NET code

static public void  ArrayDimReDim()
{

//declare the array and the result variable
int[] myTestArray = null;

//set the array dimensions
myTestArray = new int[10];

//populate the array
myTestArray[returnIndex(1) - 1] = 15;
myTestArray[returnIndex(2) - 1] = 78;
myTestArray[returnIndex(3) - 1] = 23;
myTestArray[returnIndex(4) - 1] = 9;
myTestArray[returnIndex(5) - 1] = 1;
myTestArray[returnIndex(6) - 1] = 5;
myTestArray[returnIndex(7) - 1] = 9;
myTestArray[returnIndex(8) - 1] = 95;
myTestArray[returnIndex(9) - 1] = 2;
myTestArray[returnIndex(10) - 1] = 5;

//search and display the largest number in the array
int maxInt = Module1.max(myTestArray);
MessageBox.Show(maxInt.ToString(), "MAX INT");

//New array dimansions
myTestArray = ArraysHelper.RedimPreserve<int[]>(myTestArray, new int[]{12});

//populate the newly created fields
myTestArray[10] = 0;
myTestArray[11] = 200;

//search again for the largest number on the array
maxInt = Module1.max(myTestArray);
MessageBox.Show(maxInt.ToString(), "MAX INT");

}

Default Instances for Forms, Classes and User Controls

In Visual Basic every form, class and user control has a default instance with the same name as the class.  For example Form1 can reference the type or the default Form1 instance with the same name. The VBUC creates variables called <TypeName>_definstance, and changes all the corresponding references to this variable to reproduce the same behavior as in Visual Basic.

Original VB6 Code:

Private Sub Form_Load()
Form1.Caption = "This is the main Window"
End Sub

Resulting C#.NET code

private void  Form1_Load( Object eventSender,  EventArgs eventArgs)
{
Form1.DefInstance.Text = "This is the main Window";
}

Indexer Properties

Indexer properties in Visual Basic are done through default properties called “Item”. In C#, the indexer properties are accessed by accessing the instance as an array. The index passed to this “array reference” is sent as a parameter to the “Item” method. The VBUC recognizes and modifies most of the indexer property cases.

Original VB6 Code (code contained into the Class1.cls file):

Dim arr(100) As String

Public Property Get Item(index As Integer) As Variant
Item = arr(index)
End Property

Public Property Let Item(index As Integer, val As Variant)
arr(index) = val
End Property

Resulting C#.NET code

public string this[int index]
{
get
{
return arr[index];
}
set
{
arr[index] = value;
}
}

With Structures

Since C# does not have an equivalent for the With statement, the VBUC expands the missing references and removes the With statement.

Read section Event Declaration and Invocation for a code sample featuring the with structure removal

Return Statements

In VB6, to specify the return value for a function an assignment is performed to its name.  A set of transformations patterns have been implemented into the VBUC to convert this assignments into return patterns.

In some cases the use of temporal variables is required, since the original VB6 assignment do not break the function body execution as return statements do.

The following example shows a very simple function which has different return values depending on the input. In the second condition the function name is assigned but it is not the last statement in the block of code

Original VB6 Code:

Public Function Power(x As Integer, n As Integer) As Integer
If (n = 0) Then
Power = 1
ElseIf (n < 1) Then
Power = 0
MsgBox "Negative power is not supported"
Else
Dim aux As Long
aux = x

While (n > 1)
aux = aux * x
n = n - 1
Wend
Power = aux
End If
End Function

Resulting C# Code:

The code generated by the VBUC features the “return” keyword and a temporary variable used to store the possible return values. Note that the assignments to the iteration variables was upgraded to a more .NET like pattern, see the gray highligth.

static public int Power( int x, ref  int n){
int result = 0;
int aux = 0;
if (n == 0){
result = 1;
}
else if ((n < 1)) {
result = 0;
MessageBox.Show("Negative power is not supported", Application.ProductName);
}
else{
aux = x;

while ((n > 1)){
aux *= x;
n--;
}
result = aux;
}

return result;
}

Interfaces Support for C#

This advanced functionality is similar to the VB.NET Interfaces conversion described in the respective section.  However, it introduces some additional transformations for C#, where the method implementation mechanism is different.

In C#, instead of adding an <Implements Interface.Method> clause in the method declaration, the method implementation model is expressed implicitly through the class method names, which must match the interface method names. The VBUC renames both the method declarations and their references to make them match the interface method names.

For complete code examples please visit section Interfaces support.

Case Sensitive Corrections

C# is a case sensitive language, which means that identifiers must be corrected when converting from VB6 to C# in order to make all the references to the same element show exactly the same case-sensitive name as in its declaration.

Brackets Generation for Array Access

Arrays in VB6 are accessed with round parenthesis, in the same way as subroutines and functions.  For C# this array access expressions must be recognized, differentiating them from function invocations, and generated with the appropriate syntax.

Read section Array dims and redims for a complete code example.

Variable Initialization Generation

Variables in C# must be initialized with default values to avoid recurring compilation errors and warnings about variables being used without having been previously initialized. The VBUC creates the necessary initialization values considering the declared type.  Also the “As New <Type>” clause is converted to a declaration where the variable is initialized with a “new <Type>()” expression.

This code sample demonstrates how the VBUC is able to merge the variable declaration with its initialization value in order to generate pure C#.NET code. This example combines the use of strongly typed variables with late bound variables and type coercions.

Original VB6 Code:

Public Sub VarInit()

Dim integerVar
Dim singleVar As Single
Dim LongVar As Long
Dim floatVar As Double
Dim currencyVar As Currency
Dim dateVar As Date

byteVar = CByte(0)
singleVar = byteVar + 0.5
floatVar = singleVar + 0.31654879
stringVar = "0100"
integerVar = CInt(stringVar)
LongVar = integerVar

booleanVar = (integerVar > byteVar)

currencyVar = "250"
dateVar = Now

End Sub

Resulting C# Code:

static public void  VarInit()
{
int byteVar = 0;
float singleVar = (float) (byteVar + 0.5d);
double floatVar = singleVar + 0.31654879d;
string stringVar = "0100";
int integerVar = Convert.ToInt32(Double.Parse(stringVar));
int LongVar = integerVar;

bool booleanVar = (integerVar > byteVar);

decimal currencyVar = 250;
System.DateTime dateVar = DateTime.Now;
}
Talk To An Engineer