It seems that more programmers are switching to C# from Visual Basic (VB) than ever before. As we all see when searching the Web, there are more samples in C# than there are in VB. Visual Basic is a perfectly good language for creating .NET applications, but it’s just not the cool kid down the block anymore. If you currently have VB applications, and your current programmer(s) quit, you’re going to be hard-pressed to replace them. Programmers don’t want to keep working in what are viewed as legacy technologies. Right or wrong as that might be, you can't fight the trend.

How do you prepare for converting your VB applications to C#? I’m currently helping one company do that. They have a very large Windows Forms application that they’ve committed to converting to C#. In this article, I’ll take you through the process my team and I have come up with to make the conversion much easier.

Our First Steps

I started programming Visual Basic right when VB 3.0 came out. I kept programming in VB until .NET was released. I then programmed in both for a couple of years, but then switched completely to C#. Making the switch was a little painful at first because I realized how much sloppy coding Visual Basic lets you get away with. Yes, I was guilty of it back then, too. C# doesn’t allow you get away with as many bad programming practices. Remembering my transition, I’ve come up with a list of things you can do in your VB programming to ease your transition to C# today.

Working with my current client, I took their complete VB Windows Forms application and ran it through a tool that purports to convert VB to C#. The tool, Instant C# (http://bit.ly/2rCwDL5), did a decent job on most of the VB code, but it can't convert everything if you don't write VB the correct (.NET) way. After the first run of the conversion tool, I encountered about 15,000 errors upon the first compile of the translated C# code.

As I worked my way through the errors, I compared the C# code to what was in the original VB code. From this list, I came up with the list of best practices you should employ in your VB programming to make the transition simpler. The rest of this article describes the things you should start doing immediately in your VB programs.

The .NET CLR and Framework Class Libraries

Microsoft designed .NET on a Common Language Runtime (CLR) library. This CLR provides a set of services for all languages that wish to use .NET, such as Visual Basic, C#, F#, COBOL, etc. The services of the CLR give each language a common data-type system, automatic memory management, garbage collection, and many other features. Each language compiler takes the syntax of the language and converts the statements into the appropriate intermediate language (MSIL) that’s eventually compiled into executable code.

There’s a set of Framework classes that all languages have access to. For example, the Convert class has methods such as ToInt32(), ToBoolean(), etc. All languages can use these methods. VB has its own versions of some of these methods to be consistent with older versions of VB. For example, in Visual Basic you might write CInt(stringValue) to convert a string to an integer instead of using the Convert.ToInt32() method. Although these methods ultimately do the same thing, the CInt() method doesn’t translate to the same MSIL because it does a lot of type checking at runtime to handle some of the quirks that VB lets you get away with.

Visual Basic Differences

The BASIC language was developed in the 1960s, about 40 years before .NET. It has different ways of doing things compared to what is considered the "correct .NET way of doing things." It’s not that these things are necessarily bad, it’s just different from the way .NET, and modern compile languages, do things. Although many statements, such as CInt(), can be converted into MSIL, some statements in VB might take many more MSIL statements compared to a pure .NET method. This can cause performance issues, and introduce runtime errors, instead of catching errors during compile-time. As we all know, compile-time errors are preferable to runtime errors.

Turn on Option Strict

One of the best things you can do right now is to turn Option Strict on in all your VB projects. Go to your project properties, click on the Compile tab, and you can turn this option on there. When you create a new VB project in Visual Studio, the default value for Option Strict is off. This is done for backward-compatibility reasons. It’s recommended by Microsoft, and widely considered to be a best practice, to turn Option Strict on to increase performance and reduce the possibility of naming and runtime errors. For more information on this option in Visual Studio, read the Microsoft docs at http://bit.ly/2DwWOV8. Also, http://bit.ly/2GcT3Gd has a good reference regarding why turning on Option Strict is a good idea.

The primary reason for the large number of errors I got on the first conversion was because of Option Strict being turned off in their projects. The reason for this is that Visual Basic performs a lot of type conversions at runtime and is very forgiving when converting from one type to another. This isn’t a good practice as it leads to slow performance and runtime errors. You need to eliminate all code that does automatic type conversion.

Turn on Option Explicit

Set the Option Explicit to on in all your VB projects. The default value for this is on, but it might be good to check it. Go to your project’s properties, click on the Compile tab, and you can check that this option is turned on. This option forces you to declare all variables prior to using them. In the old days of BASIC programming, you didn’t need to declare variables to use them. As we all know, compilers do much better when you declare variables with a specific data type.

Implicit Data Type Conversions

The largest number of errors you might get from turning on Option Strict is implicit data type conversions. These implicit conversions allow the VB runtime engine to handle coercing one data type to another, instead of enforcing type-correctness at compile time. Consider the following valid code in VB.

Dim anyValue As Object
Dim anyString As String
anyValue = "Some value"
anyString = anyValue

The above code compiles just fine with Option Strict Off, but once you turn Option Strict On, the last line gives an error: "Option Strict On disallows implicit conversions from Object to String". This is because you’re assigning an object to a string. If the value within the variable anyValue can’t be translated into a string, you receive a runtime error. Therefore, Option Strict On is the best option. You don’t want runtime errors, you want the compiler to warn you when you have one type being coerced into another. Consider the following code snippet.

Dim anyValue As Object
Dim anyString As String
anyValue = "Some value"
anyString = anyValue
  
anyValue = Convert.ToInt32(anyString)

The last line of this code causes a runtime error because the value in the variable anyString doesn’t contain a valid integer value. Consider this next line as a replacement for the line that assigns the variable anyValue to "Some Value" in the last snippet. :

anyValue = "15"

If you assign a string with a numeric value like that, the code illustrated previously works perfectly. Now you understand why using implicit conversions is such a bad idea. Sometimes your code will work and sometimes it won’t. These types of errors can be very difficult to debug, because the code may succeed when you input a correct value, but if your user enters some bad data, you get a runtime error.

Methods that Return an Object

Many third-party Grid controls have methods that return an object data type because they don't know what you put into each cell of a grid. For example, the GetRowCellValue() method in the DevExpress Grid returns an object data type. Many VB developers just take that value and assign it directly to another variable. This means that the VB runtime has to perform the type conversion. If the value in the cell can't be converted into the target variable's type, you get a runtime error.

txtNotes.Text = grid1.GetRowCellValue(
     grid1.FocusedRowHandle,
     grid1.Columns("Code"))

Instead of the above, create a generic method that attempts to convert to the specified data type and returns a default value if the data in the grid cell can't be converted.

Test for Equality

When you’re testing for equality, both operands on each side of the equal sign must be the same data type. In the code below, the equal sign is testing an object against a string, which isn’t valid.

Dim dt As DataTalbe
' Load Data Table here
For Each dr In dt.Rows
  If dr("Code") = "PP" Then ' BAD CODING
    dr.Delete()
  End If
Next

An easy way to fix the code above is to add the ToString() method after the dr("Code") object. This converts the object to a string, so the two string data types may be compared correctly. Like this:

If dr("Code").ToString() = "PP" Then

Assign SelectedValue or SelectedItem

The SelectedValue and SelectedItem properties of a combo or list box in Windows Forms returns an object data type. Common code that I see in VB applications is where the SelectedValue from a combo or list box is assigned to a string or integer data type. The code below is what you should not do.

Dim strVal As String
Dim intVal As Integer
stringVal = cboState.SelectedValue
intVal = cboState.SelectedItem

Instead of the above code, you should use code like the following.

Dim strVal As String
Dim intVal As Integer
stringVal =
   cboState.SelectedValue.ToString()
intVal =
   Convert.ToInt32(cboState.SelectedItem)

Ideally, when converting to an integer from an object, you should ensure that the data within the SelectedItem object is indeed an integer. Otherwise you receive a runtime error. Become familiar with the TryParse() methods on each data type to help you determine if the data can be converted.

Ensure that the data within the SelectedItem object is indeed an integer or risk a runtime error.

Test SelectedValue and SelectedItem

Another common practice in many VB programs is comparing SelectedValue or SelectedItem properties to some other data type. In the code below, the SelectedValue is compared directly to a string data type.

If combo1.SelectedValue = "GOOD" Then
  ...
End If

Instead of writing the above code, write the code like this:

If combo1.SelectedValue.ToString() = "GOOD" Then
  ...
End If

Remember, this doesn't necessarily protect you from a runtime error, but this code will convert to C# just fine. You should always do type-checking whenever you compare an object value to any other value.

Assign a Form to Specific Form Type

It’s easy to create a multiple document interface (MDI) in Windows Forms applications. When you have many child forms contained within the main form, you sometimes write code to get the currently active child form and cast it to a real form type.

frmCust = Me.ActiveMdiChild

Always perform type checking before doing this conversion, and use the CType() or DirectCast() functions to perform the conversion.

If Me.ActiveMdiChild Is frmCust Then
  frmCust = CType(Me.ActiveMdiChild, frmCust)
End If

Use of Split() Method

The Split() method needs to you pass a character, or a character array, with the characters to use to split a string into substrings. The following is valid VB syntax, but some C# converters have problems converting the syntax New Char() {"*"}.

Dim files() As String = _
 fileNames.Substring(0, fileNames.Length - 1) _
   .Split(New Char() {","})

To help with C# conversion, modify the code above to create a single character variable. You can then use the following syntax instead of the above.

Dim delimiter As Char = ","c
Dim files() As String = fileNames.Substring(0,
   fileNames.Length - 1).Split(delimiter)

Avoid Using the My Namespace

The My namespace exposes a set of objects to the VB programmer. Each of these objects is available through the normal .NET CLR objects. To use the My namespace in C#, you must include a VB DLL in your C# project. Avoid the usage of the My namespace so you can eliminate references to VB after you’ve converted to C#.

All objects within the My namespace have equivalents in the .NET Framework classes. It might take you a few more lines of code to get the same functionality, but you end up learning .NET better, and your conversion will be much easier.

It might take you a few more lines of code to get the same functionality, but you end up learning .NET better, and your conversion will be much easier.

No Modules

A Module statement in VB defines a reference type in which you declare events, variables, properties, and procedures to be called. There’s no equivalent of this in C#. The closest you could come to in C# is a class with all shared properties and methods. A module can’t be instantiated as an object and can't use inheritance. This makes modules basically worthless and should be avoided at all costs. To summarize; just use classes.

Case Sensitivity

When writing VB code, be consistent on the case of your method and variable names. Most of the time, the Visual Studio editor fixes these up, but not always. Remember, C# is a case-sensitive language, so if you don't have the case correct, a C# program won’t compile. A call to a Copy() method is different than a call to a copy() method in C#, but works fine in VB. Notice the upper-case C, versus the lower-case c. These rules apply to method, property, variable, and enumeration names.

Visual Studio fixes most of the errors for you for all the .NET classes, but it won't necessarily do the same for any custom class, property, method, or enumeration names. This is especially true if you have a VB application that’s very old and you’ve been using it for a long time. All that old VB code was probably written at a time when Visual Studio didn’t automatically fix all casing of names.

As an example, the following names are invalid when translated to C#, but work perfectly fine in VB.

OracleDBType
Oracle.VarChar2

The above variable names should have been written as shown in the following bullets.

OracleDbType
Oracle.Varchar2

Notice the upper versus the lower-case b in the first name. Notice the use of the upper-case C instead of the lower-case c used in the second name. Because these variable names came from a third-party library and were written a long time ago—or at least not fixed—and Visual Studio doesn’t automatically fix them.

Calling Methods

When calling methods such as Trim(), Copy(), ToUpper(), etc., always put the open and closing parentheses after the name of the method. Properties don’t use parentheses but methods always do. No code translator out there can distinguish between what should be a method or a property if you don't follow this basic convention. For example, the following code is valid in VB, but not in C#.

Dim str As String = " a lower case string "
' Valid in VB, not C#
str = str.ToUpper.Trim ' Missing parantheses

Properties don’t use parentheses but methods always do.

Calling Shared Methods

Don’t declare an instance of a class that contains Shared methods and invoke a method through that instance. Consider the following class definition and shared method.

Public Class CommonFunctions
  Public Shared Sub SayHello()
    MessageBox.Show("Hello")
  End Sub
End Class

Visual Basic allows you to create an instance of the CommonFunctions class and call the SayHello() method.

Dim cf As New CommonFunctions()
cf.SayHello() ' THIS IS BAD!

The newer versions of Visual Studio should warn you about these types of calls, but they’re just warnings. The code still compiles and works at runtime. Just call the method by referencing the name of the class CommonFunctions.SayHello().

Reserved Word Usage

Be careful of using reserved words as variable names. This includes reserved words in both VB and in C#. In the conversion I performed for my client, there were many instances where I found declarations such as Dim int as Integer. In C#, "int" is a keyword and this causes issues when converting the code. The following links are a list of reserved words in both C# and VB that you should avoid.

Eliminate Array Usage

It’s highly recommended to stop using arrays and switch to generic collections instead. Use List<string>, List<integer>, and any of the other generic collection classes. The List<T> collection can be used with any data type, even your own objects.

Don’t use the Redim statement; this command doesn’t exist in C#. If you switch to using collections, you won’t need to use this command.

Avoid Exit Statements

Don't use Exit Try, Exit Sub, and Exit Function. These don’t exist in C# and the converters will create goto statements for these. Rewrite the logic in your VB method to just fall out the end of the sub or function when some condition is met.

  • Instead of Exit Sub, use the Return statement with nothing after it.
  • Instead of Exit Function, use the Return statement followed by some value.

The Exit Do and Exit For statements are ok to use as these statements both translate to a break statement in C#.

Hungarian Notation

Hungarian notation has not been used in over 20 years. This naming standard was used before tools like Visual Studio allowed us to hover over a variable and see its data type. Today, just use descriptive variable names and you’ll be fine.

Don’t Use ByRef

Ever since object-oriented programming was invented, it’s rarely a good idea to pass in a parameter to a method by reference. This allows the method to modify that parameter to a different value, and the calling method now has a different value in it. These types of bugs are very hard to track down. Avoid the use of the ByRef statement. There are many ways to eliminate this statement’s usage.

  • Set properties in a class prior to calling the method and that method sets other properties.
  • If you have a single ByRef and a Sub, make that Sub a Function and Return a value from the function.
  • If you have multiple ByRef arguments to a method, return an object from the function with the appropriate properties set.

Eliminate Late Binding

Late binding refers to having an object's type determined at runtime. There are many cases you might not think of as doing late binding but that can easily be seen once you turn on Option Strict. For example, if you bind a ComboBox control to a DataTable, each row in the combo box is now a DataRow object. If you iterate over the Items collection in the combo box to find where a specific column equals a certain code, you might write code like the following:

For i = 0 To .Items.Count - 1
  If CType(cbo.Items(i)("Code"), String) =
         CodeToFind Then
    .SelectedIndex = aIndex
    Exit For
  End If
Next

The Items() indexer is of the type object and thus using an index to reference a DataRow is considered late binding. You must cast that expression to a DataRow before you can access the field name Code. Replace this code with code like the following to eliminate late binding.

Dim dr As DataRow
For i = 0 To .Items.Count - 1
  dr = CType(.Items(i), DataRow)
  If CType(dr("Code"), String) = CodeToFind Then
    .SelectedIndex = aIndex
    Exit For
  End If
Next

String Handling

Use a plus (+) sign for arithmetic and an ampersand (&) for string concatenation. Don't mix these two, as this can cause some unintended side effects if there are numbers contained in the string. Consider using String.Format() instead of string concatenation. This method is also more efficient than normal string concatenation.

If you are concatenating many strings together, use the StringBuilder class. The StringBuilder class is efficient because it allocates a large block of memory at one time. Strings in .NET are immutable, meaning that once you place a value in them, they are stuck at that memory location. If you add something to that string, a new area of memory must be allocated to store the old value, plus the extra characters you just added. This is an expensive operation. The StringBuilder pre-allocates a set of memory so there will be fewer of these operations.

Null Testing

I’ve done many code reviews in my more than 32-year career, and I frequently see programmers checking values from a DataRow like the following:

Dim dr As DataRow
dr = dt.Rows(0)
If Not dr("Code") Is System.DBNull.Value
   AndAlso Not dr("Code")) Is Nothing
   AndAlso Not dr("Code") = "" Then
  TheCode = dr("Code")
Else
  TheCode = String.Empty
End If

There are so many problems with the above code, it’s hard to know where to start. The dr("Code") object is queried four times! Accessing a value through an indexer like this is not very efficient. The last test compares an object against an empty string, so this is an implicit type conversion. The repeated use of the Not operator makes the logic very difficult to follow.

Instead of writing this type of code, create a new method in one of your classes that contains shared methods to check values against DbNull, Nothing, and empty strings.

Public Shared Function
  IsStringNullOrEmpty(value As Object)
      As Boolean
  Dim ret As Boolean = False
  If DBNull.Value.Equals(value) Then
    ret = True
  ElseIf String.IsNullOrEmpty(value) Then
    ret = True
  End If
  Return ret
End Function

You can now simplify the code you wrote earlier to compare the "Code" field value to read as follows.

Dim dr As DataRow
' Assign dr to a row in a data table
If Common.IsStringNullOrEmpty(dr("Code")) Then
TheCode = String.Empty
Else
  TheCode = dr("Code")
End If

This is much more efficient than the previous code because the DataRow is only accessed once, it eliminates the Not operators, and the code is easier to understand.

Eliminate Old Visual Basic Functions

VB has a Microsoft.VisualBasic namespace that’s imported automatically in your projects. This namespace contains global functions you can use such as CLng(), CInt(), Asc(), etc. If you wish to move to C#, you must start eliminating usage of these built-in functions. Most VB to C# conversion tools translate these automatically for you, but you should get used to using the equivalent CLR objects and methods.

If a function you’re calling isn’t prefixed by an object, then it’s a Visual Basic function and not a .NET method. In this next section, I’ll take you through many of the common VB functions, and point out the equivalent .NET library methods to use instead.

Asc

If you want to find out the ASCII number for a letter like A, the Asc() function allows you to do that. If you use this function, I suggest creating your own method to perform this conversion as there’s no equivalent in the .NET Framework. Here’s a method, named SafeAsc, that replaces the Asc() function.

Dim value As Char = "A"c
Debug.WriteLine(Asc(value)) ' Old Method
Debug.WriteLine(SafeAsc(value)) ' New Method
Private Shared Function
     SafeAsc(ByVal str As String) As Short
  Return Convert.ToInt16(
       Encoding.[Default].GetBytes(str)(0))
End Function

CBool

The CBool() function converts a string or integer value into a Boolean true or false value. If the string contains True or False, that string is converted into the appropriate Boolean value. If it contains any other values, an exception is thrown.

If you convert an integer value into a Boolean value using CBool(), zero is converted to false, and any non-zero number is converted to true. The equivalent function in .NET is Convert.ToBoolean().

Dim str As String = "True"
Dim value As Integer = 33
Debug.WriteLine(CBool(str))
' The following throws an exception if 'str'
' contains a numeric value such as 0 or 1
Debug.WriteLine(Convert.ToBoolean(str))
' When using a numeric, any
' non-zero value is considered true
Debug.WriteLine(Convert.ToBoolean(value))

CByte

The CByte() function takes a numeric value and converts it into a byte. The equivalent in .NET is the Convert.ToByte() method.

Dim value As Double = 41.8
Debug.WriteLine(CByte(value))
Debug.WriteLine(Convert.ToByte(value))

CChar

The CChar() function takes a string value and converts it to a single character. The closest equivalent in .NET is the Convert.ToChar() method. However, be aware that if the string value contains more than one character, Convert.ToChar() throws an error; the CChar() does not.

Dim value As String = "BCD"
' The following line of code returns "B".
Debug.WriteLine(CChar(value))
' The following line will bomb
' if the string is > 1 character
' Debug.WriteLine(Convert.ToChar(aString))
value = "B"
Debug.WriteLine(Convert.ToChar(value))

CDate

The CDate() function takes a string value and converts it to a Date data type. The equivalent in .NET is the Convert.ToDate() method.

Dim value As String = "1/1/2018"
Debug.WriteLine(CDate(value))
Debug.WriteLine(Convert.ToDateTime(value))

CDbl

The CDbl() function takes a numeric value and converts it to a Double data type. The equivalent in .NET is the Convert.ToDouble() method.

Dim value As Integer = 42
Debug.WriteLine(CDbl(value))
Debug.WriteLine(Convert.ToDouble(value))

CDec

The CDec() function takes a numeric value and converts it to a Decimal data type. The equivalent in .NET is the Convert.ToDecimal() method.

Dim value As Integer = 42
Debug.WriteLine(CDec(value))
Debug.WriteLine(Convert.ToDecimal(value))

Chr

This function is the opposite of the Asc() function. It returns a letter for an integer value. There’s no equivalent to this method in .NET, so you need to create your own function. Here’s a method named SafeChr() that you can use instead of the Chr() function.

Private Shared Function SafeChr
  (ByVal CharCode As Integer) As String
  If CharCode > 255 Then
    Throw New ArgumentOutOfRangeException
       ("CharCode", CharCode,
        "CharCode must be between 0 and 255.")
  End If
  Return System.Text.Encoding.[Default]
            .GetString({CByte(CharCode)})
End Function

You can use this method as shown in the following code:

Dim value As Integer = 65
Debug.WriteLine(Chr(value)) ' Old Method
Debug.WriteLine(SafeChr(value)) ' New Method

CInt

The CInt() function takes a numeric value and converts it to a Integer data type. The equivalent in .NET is the Convert.ToInt32() method.

Dim value As Double = 41.8
Debug.WriteLine(CInt(value))
Debug.WriteLine(Convert.ToInt32(value))

CLng

The CLng() function takes a numeric value and converts it to a Long data type. The equivalent in .NET is the Convert.ToInt64() method.

Dim value As Double = 41.8
Debug.WriteLine(CLng(value))
Debug.WriteLine(Convert.ToInt64(value))

CObj

The CObj() function takes a value and converts it to an Object data type. There’s no equivalent in .NET except to use a cast operator. I recommend that you use the DirectCast() function in VB because the conversion tools generally convert this to a cast in C#.

Dim value As Double = 41.8
Debug.WriteLine(CObj(value))
Debug.WriteLine(DirectCast(value, Object))

CShort

The CShort() function takes a numeric value and converts it to a Short data type. The equivalent in .NET is the Convert.ToInt16() method.

Dim value As Double = 41.8
Debug.WriteLine(CShort(value))
Debug.WriteLine(Convert.ToInt16(value))

CSng

The CSng() function takes a numeric value and converts it to a Single data type. The equivalent in .NET is the Convert.ToSingle() method.

Dim value As Double = 41.8
Debug.WriteLine(CSng(value))
Debug.WriteLine(Convert.ToSingle(value))

CStr

The CStr() function takes a numeric value and converts it to a String data type. The equivalent in .NET is the Convert.ToString () method. You may also use the ToString() method on the numeric data type.

Dim value As Double = 41.8
Debug.WriteLine(CStr(value))
Debug.WriteLine(Convert.ToString(value))
Debug.WriteLine(value.ToString())

Filter

The Filter() function in VB iterates over an array to locate a value you pass in. It then creates a subset of the original array with the items that match your value. For example, here, you create an array of three words and return a subset of that array that contains the word "is".

Dim values(2) As String
values(0) = "This"
values(1) = "Is"
values(2) = "It"
Dim subset() As String
' Returns ["This", "Is"].
subset = Filter(values, "is", True,
                CompareMethod.Text)

C# doesn’t have a Filter() method, so use a LINQ query instead.

Dim values(2) As String
values(0) = "This"
values(1) = "Is"
values(2) = "It"
Dim subset() As String
' Returns ["This", "Is"].
subset = values.Where(Function(s)
           s.ToLower().Contains("is")).ToArray()

Format

The Format() function helps you format numbers, dates, and times. The ToString() method on the Date and each of the number data types allow you to pass formatting characters to perform any formatting you want. Consider if you have the following date declared in a variable named "dt".

Dim dt As Date = #1/15/2018 2:04:23 PM#

Here, you see the Format() function and the equivalent .NET method on the Date data type to return a long time string.

Debug.WriteLine(Format(dt, "Long Time"))
Debug.WriteLine(dt.ToLongTimeString())

The next piece of code formats a date using a long date format.

Debug.WriteLine(Format(dt, "D"))
Debug.WriteLine(dt.ToLongDateString())

You can use your own formatting using both methods.

Debug.WriteLine(Format(dt, "dddd, MMM d yyyy"))
Debug.WriteLine(dt.ToString("dddd, MMM d yyyy"))

When you want to format numeric values, you have the same options.

Debug.WriteLine(Format(5459.4, "##,##0.00"))
Debug.WriteLine(5459.4.ToString("##,##0.00"))
Debug.WriteLine(Format(5, "0.00%"))
Debug.WriteLine(5.ToString("0.00%"))

FormatCurrency

The FormatCurrency() function is used to format a numeric value as currency. Consider the following double value.

Dim value As Double = -4456.43

You can return a currency value using the FormatCurrency() or the ToString() method on the Double data type.

' Returns "($4,456.43)".
Debug.WriteLine(
   FormatCurrency(value, , , TriState.True,
                             TriState.True))
Debug.WriteLine(value.ToString("C"))

If you set the TriState enumeration to a false value, the negative sign prefixes the number instead of wrapping the number within parentheses.

' Returns "-$4,456.43".
Debug.WriteLine(
   FormatCurrency(value, , , TriState.False,
                             TriState.True))
Debug.WriteLine(value.ToString("$#,##0.00"))

InStr

The InStr() method returns an index number for where a string value exists in another string. The String data type has the IndexOf() method that performs this same operation. Be aware though, the InStr() function numbers its characters in the string starting with one, whereas IndexOf() numbers from zero.

Dim value As String = "Hello from VB"
' Returns 7
Debug.WriteLine(InStr(value, "from"))
' Returns 6
Debug.WriteLine(value.IndexOf("from"))

InStrRev

The InStrRev() method returns an index number of where a string value exists in another string, but the index is from the end of the string. The String data type has the LastIndexOf() method that performs this same operation. Be aware that, the InStrRev() function numbers its characters in the string starting with one, whereas LastIndexOf() numbers from zero.

Dim value As String = "Hello from VB"
' Returns 7
Debug.WriteLine(InStrRev(value, "from"))
' Return 6
Debug.WriteLine(value.LastIndexOf("from"))

Join

The Join() function gathers each array element and concatenates them into a single string value. By default, Join() places a space between each array element when concatenating into the string. You may specify which character you want to use by passing in a second parameter to the Join() function. The String class has a Join() method that performs the same functionality.

Dim values(2) As String
values(0) = "This"
values(1) = "is"
values(2) = "it."
Debug.WriteLine(Join(values))
Debug.WriteLine(String.Join(" ", values))

LCase

The LCase() function converts all characters in a string to lower-case. The ToLower() method on the String data type performs the same operation.

Dim value As String = "HELLO WORLD"
Debug.WriteLine(LCase(value))
Debug.WriteLine(value.ToLower())

Left

The Left() function extracts the specified amount of characters from the left part of a string value. The Substr() method on the String data type performs the same operation.

Dim value As String = "Hello World"
Debug.WriteLine(Left(value, 5))
Debug.WriteLine(value.Substring(0, 5))

Len

The Len() function returns the number of characters in a string. The Length() method on the String data type performs the same operation.

Dim value As String = "Hello World"
Debug.WriteLine(Len(value))
Debug.WriteLine(value.Length)

LTrim

The LTrim() function removes leading spaces from a string and returns a new string. The TrimStart() function on the String data type performs the same operation.

Dim value As String = " Hello World"
Debug.WriteLine(LTrim(value))
Debug.WriteLine(value.TrimStart())

Mid

The Mid() function extracts characters from a specific starting point in a string, and returns a new string from that point to the end of the string. Again, be aware that the VB functions are all one-based and the .NET methods are all zero-based.

Dim value As String = "Hello World"
Debug.WriteLine(Mid(value, 7))
Debug.WriteLine(value.Substring(6))

MsgBox

The MsgBox() function displays a modal dialog the user must to respond to. The .NET equivalent in Windows Forms is the MessageBox.Show() method. Below are a few examples of MsgBox() and MessageBox.Show().

Dim value As String = "Hello World"
MsgBox(value) ' VB
MessageBox.Show(value) ' .NET
' The next two lines are VB
Dim result As MsgBoxResult
Result = MsgBox(value, MsgBoxStyle.Exclamation)
' The next lines are the .NET equivalent
Dim mresult As MessageBoxResult
mresult = MessageBox.Show(value, "Caption",
               MessageBoxButton.OK,
               MessageBoxImage.Exclamation,
               MessageBoxResult.OK)

Replace

The Replace() function replaces one string value to another within a string. The Replace() method on the String data type performs this same operation.

Dim value As String = "Hello World"
Debug.WriteLine(Replace(value, "World", "VB"))
Debug.WriteLine(value.Replace("World", "VB"))

Right

The Right() function extracts the specified amount of characters from the end of a string value. The Substr() method on the String data type performs the same operation.

Dim value As String = "Hello World"
Debug.WriteLine(Right(value, 5))
Debug.WriteLine(value.Substring(value.Length-5))

RTrim

The RTrim() function removes trailing spaces on a string and returns a new string value. The TrimEnd() function on the String data type performs the same operation.

Dim value As String = "Hello World "
Debug.WriteLine(RTrim(value))
Debug.WriteLine(value.TrimEnd())

Space

The Space() function creates a string variable with a specified amount of spaces in it. The PadLeft() method on the String data type performs the same operation.

Dim value As String
value = Space(50)
value = value.PadLeft(50, " "c)

Split

The Split() function converts a string into an array by looking for spaces within the string. Each word within the string is converted into an element of the resulting array. You may optionally specify the delimiter to use to separate each word in the string. The .NET equivalent to use is the Split() method on the String data type.

Dim value As String = "This is it."
Dim values As String()
values = Split(value)
values = value.Split(" "c)

StrDup

The StrDup() function takes a character value and duplicates that value a specified amount of times. You can also pass in a character and number to the String() constructor to perform this same operation.

' Returns "PPPPP"
Debug.WriteLine(StrDup(5, "P"))
Debug.WriteLine(New String("P"c, 5))

StrReverse

The StrReverse() method reverses all of the characters in a string and returns a new string. There is no .NET equivalent for reversing a string, but you can write a function like the ReverseString() function shown below.

Public Shared Function
     ReverseString(value As String) As String
  Dim chrs() As Char
  chrs = value.ToCharArray()
  Array.Reverse(chrs)
  Return String.Join(" ", New String(chrs))
End Function

You can then use either of these to perform the same operation.

Dim value As String = "Hello World"
Debug.WriteLine(StrReverse(value))
Debug.WriteLine(ReverseString(value))

Trim

The Trim() function removes both leading and trailing spaces from a string. The Trim() function on the String data type performs the same operation.

Dim value As String = " Hello World "
Debug.WriteLine(Trim(value))
Debug.WriteLine(value.Trim())

UCase

The UCase() function converts all characters in a string to upper-case. The ToUpper () method on the String data type performs the same operation.

Dim value As String = "hello world"
Debug.WriteLine(UCase(value))
Debug.WriteLine(value.ToUpper())

vbCrLf and vbNewLine

Avoid using the vbCrLf and vbNewLine and use Environment.NewLine instead. The vbCrLf and vbNewLine are not part of .NET, but, are VB-specific. If you perform a conversion to C#, these will need to be rewritten.

Dim value As String =
     "Hello World" & vbNewLine
     & "Hello VB" & vbCrLf
     & "Hello To You"
Dim newValue As String =
   "Hello World" & Environment.NewLine
   & "Hello VB" & Environment.NewLine
   & "Hello To You"
Debug.WriteLine(value)
Debug.WriteLine(" ")
Debug.WriteLine(newValue)

Approaching Your Conversion Process

Going through your whole application and finding all instances of the items pointed out in this article could take a lot of time. Time that you probably don’t have. There are some tools that may be able to help you.

Turn Option Strict On

The first thing you can do is simply turn on Option Strict in each of your projects. You should get a series of error and warning messages in the Error List window. Work your way through these errors one by one. If you only have one hour a day to devote to this endeavor, you can always turn the Option Strict option on, work on some errors, then turn it back off so you can check everything back into your source control. The project will still compile. This also avoids errors happening for the other developers working on this project.

Even if you only commit an hour a day to working through errors, it’s worth the trouble.

Upgrade to Visual Studio 2017

Each version of Visual Studio gets better at helping detect errors and providing hints on how to refactor your code. Visual Studio 2017 is the latest version at the time of the writing of this article, and provides tools built right into the editor. If you bring up your old VB code in the VS 2017 editor, you might see some squiggles under some code that needs to be fixed. The compiler has improved dramatically over the previous versions so more errors and warnings are reported.

Code Analysis

Select Build > Run Code Analysis on Solution to see any warnings or messages that appear in the Error List window in VS 2015 and later. This tool, like the compiler, helps you identify areas to fix that to make your conversion to C# an easier process.

FxCop

This tool helps you maintain consistency in your code. The default rules in FxCop check for potential design flaws, incorrect casing, cross-language keyword collisions, and many other problems related to naming. In addition, FxCop tells you about performance, usage, and security problems. This tool is available free from Microsoft and I highly recommend that you integrate it into your build process.

VB to C# Conversion

After you have made all the changes you can to your VB application, start trying to convert to C#. If you have a solution comprised of many different projects, start with a single project at a time. You can perform the conversion, replace the old VB project with the new C# project, and ensure that it works as expected. You can then continue this process until all your projects are converted. If there’s no hurry to do this conversion process, you might tackle one project, test it out over a week, a month, or two months before you move on to the next project. If time is of the essence, by following the guidance I’ve provided in this article, the conversion should go quickly.

There are a few online code converters that are free to use, such as the Telerik Code Converter located at http://converter.telerik.com. This tool converts a single class or method at a time. The code converter at Developer Fusion, http://www.developerfusion.com/tools/convert/vb-to-csharp also converts a single class or method at a time.

If you’re looking for a converter that tackles a complete project and/or solution at one time, check out the Instant C# tool by Tangible Software Solutions. You can see this tool at http://bit.ly/2rCwDL5. For a very reasonable price, it converts your complete Windows Forms or Web Forms application to C#. It was by running this code conversion tool that I was able to identify the problem areas I pointed out in this article. To be fair, the other converters I mentioned found the same problems.

The solution I was converting had about one million lines of code spread out over 23 projects. Instant C# took approximately 35 minutes to perform the conversion of these projects. After the first conversion and receiving the 15,000 errors, I decided it would be easier for my client to fix their VB code first. They are more familiar with VB and can make the changes following this article's guidelines easier in VB than in C#. In addition, they get more familiar with the .NET methods they’re going to be using in C#.

Summary

Converting from one language to another is not an easy process. Realize that it is a long journey, and approach it as a long-term strategy rather than a short-term process. Even with good conversion tools, like the ones I pointed out in this article, there’s no perfect conversion. You’re going to have lines of code that won’t convert correctly. However, that number can be greatly reduced by following the techniques outlined in this article. Good luck on your conversion!