Every now and then I visit the competition's web site (as much they visit ours) and recently stumbled into this post, which discusses whether VB6ers should migrate to C# rather than VB.NET. The sentence that hit my attention was

By the way, we’ve seen a few people suggesting a double path approach for those who chose to migrate their VB6 applications to C#. This idea mostly comes from those who offer a solution that only converts to VB.NET, and they even say it’s irrational to think about jumping from VB6 to any .NET language other than VB.NET. Well, the immense set of differences between VB6 and C# or VB.NET are accurately resolved by <name of their conversion tool>.

I had a friendly discussion with a couple of the author’s colleagues some time ago on this topic, therefore I assume that the “unnamed competitor” is Code Architects and our VB Migration Partner tool.

The first part of the sentence is absolutely correct: VB Migration Partner tool doesn’t generate C# code and we don’t plan to add this feature in the foreseeable future.

The reason is simple: our tool can generate VB.NET programs that are 100% equivalent to the original VB6 code, either at the first attempt or after adding a few migration pragmas. It converts Gosubs, On Goto/Gosubs, As Any parameters, As New variables and arrays with true auto-instancing semantics, default member references even in late-bound mode, deterministic finalization for IDisposable objects, arrays with any LBound, all 60+ controls in VB6 toolbox, ADO/RDO/ADO databinding, graphic methods, user-defined system coordinates, OLE drag-and-drop events, and many other features that the Upgrade Wizard doesn’t support.

Generating C# code would be a step in the wrong direction for us, because we couldn’t support several of the above features and we would therefore deliver lower quality code. We aren’t interested in that.

UPDATED (June 3, 2011):  never say never!... two years after this post was written, Visual C# 2010 introduced many interesting new features that make the migration from VB6 less error-prone. The first one is a (limited) form of late-binding (sort of), based on dynamic variables; the second one is the support for optional parameters. As a matter of fact, we have changed our mind and are working at a very innovative VB6-to-C# converter that works around the residual C#-related migration issues and still generate maintainable and efficient code. Stay tuned!

The second part of the sentence isn't correct, though. We don't say that it is irrational to convert a VB6 application to C# rather than VB.NET. We only claim that doing it in one single step using an automated conversion tool isn't a great idea for many reasons. Explaining these reasons requires a lengthy and detailed article, thus I thought I’d better post it here than just replying to the original post.

Comparing languages is a fascinating topic and I'd like to thank the author of the original post for giving me the excuse to explore it in depth. Please notice that I am not making any assumption on our competitor's VB6-to-C# converter: I never used it and they don't publish any converted VB.NET or C# application, as we do in our Code Samples section. Therefore this article is just an overview of some obvious issues that you have when converting VB6 code directly to C#.

An important disclaimer:  Let me emphasize that I don’t mean to compare the power of VB6/VB.NET vs. C#. The only purpose of this article is to demonstrate that there are VB6 peculiarities that can’t be easily migrated to C# code without loosing either readability or functional equivalence, especially if you use an automated code converter. In the best case you end up bloated and unreadable C# code, in the worst case you get C# code that doesn’t behave exactly like the VB6/VB.NET code you start with.


COMPARING VB6, VB.NET AND C#

VB.NET and C# languages aren't perfectly equivalent, therefore if you convert directly from VB6 to C# you nearly always lose something. (I wrote seven books on VB6, VB.NET and C# for Microsoft Press, thus I know what I am saying...) The best examples of this non-equivalence are the On Error statements and late binding. For example, consider this simple VB6 method that copies 10 files using the FileSystemObject type library:

Sub CopyFiles(ByVal throwIfError As Boolean)
    If Not throwIfError Then On Error Resume Next
    Dim fso As New FileSystemObject
    fso.CopyFile "sourcefile1", "destfile1"
    fso.CopyFile "sourcefile2", "destfile2"
    fso.CopyFile "sourcefile3", "destfile3"
    ' seven more CopyFile method calls …
End Sub

VB.NET fully supports the On Error Resume Next statement, therefore this code translates to VB.NET as-is, except for the parenthesis around method arguments. Let’s see now the “equivalent” C# implementation:

void CopyFiles(bool throwIfError)
{
    Scripting.FileSystemObject fso = new Scripting.FileSystemObjectClass();
    try
    {
        fso.CopyFile("sourcefile1", "destfile1", true);
    }
    catch
    {
        if (throwIfError)
        {
            throw;
        }
    }
    try
    {
        fso.CopyFile("sourcefile1", "destfile1", true);
    }
    catch
    {
        if (throwIfError)
        {
            throw;
        }
    }
    try
    {
        fso.CopyFile("sourcefile1", "destfile1", true);
    }
    catch
    {
        if (throwIfError)
        {
            throw;
        }
    }
    // seven more try-catch blocks
}

We started with a VB6 method containing 12 executable statements and ended up with a C# method of 111 statements, nearly 10x bigger! You can’t always have concise C# code if your main goal is functional equivalence with the original VB6 code, sorry!

Also, notice that the C# code can’t omit the third (optional) argument in the FileSystemObject.CopyFile method, which makes the code even more verbose. Just imagine what happens when working with methods that have many optional parameters, such as those exposed by Microsoft Office type libraries! (Note that I purposely avoided to convert the FileCopy method into System.IO.File.Copy, because the focus here is on error handling, not file operations.) 

Don’t miss another important point: I translated the VB6 code to C# manually. An automatic translator tool might fail to deliver code that is truly equivalent and might, for example, bracket all the statements in a single try-catch block and add a warning about the different semantics and behavior. The text of such a warning may vary, but it would be a variation of the following sentence:  “My job ends here, it’s up to you ensuring that I didn’t mess things up.” Laughing

You might believe that the problem surfaces only with the On Error Resume Next statement, and that converting the On Error Goto <label> command can be easily translated to equivalent C# code. Alas, this isn't the case if you have multiple On Error Goto statements in one method, or if you use the Resume Next statement, or if you turn error handling on and off by alternating On Error Goto <labal> and On Error Goto 0 commands. Code of this sort can be a nightmare even for the bravest developer, and I doubt a VB6-to-C# convert can handle it correctly. Of course, you don't have any problem if you convert to VB.NET, because all these variants are fully supported by VB.NET.

By the way, if you are curious about how often you’ve used On Error statements it’s time to run our free VB6 Bulk Analyzer tool. You’d be surprised!

Let’s now tackle late binding. You can use late-binding in both VB6 (always) and in VB.NET (if Option Strict is Off), therefore translating the following VB6 code to VB.NET is a trivial task for both humans and automatic translators:

Sub IncrementValue(ByVal obj As Object, ByVal valueBeingAdded As Object)
    obj.Value = obj.Value + addedValue
    obj.Refresh
End Sub

C# doesn’t support late binding, thus there is no easy way to render this code into C# short of using reflection. This is how I’d translate it, after adding a variable and a few remarks to make it more readable:

void IncrementValue(object obj, object valueBeingAdded)
{
    // read Value property
    object value = obj.GetType().InvokeMember("Value",
       
System.Reflection.BindingFlags.GetProperty, null, obj, null);
    // increment it (assumes it's a double)
    object newValue = Convert.ToDouble(value) + Convert.ToDouble(valueBeingAdded);
    // assign back to Value property
    obj.GetType().InvokeMember("Value",
        System.Reflection.BindingFlags.SetProperty, null, obj,
        new object[] { newValue });
    // call the Refresh method
    obj.GetType().InvokeMember("Refresh",
        System.Reflection.BindingFlags.InvokeMethod, null, obj, null);
}

At least, this C# code is perfectly equivalent to the original VB6 code, right?

Wrong. In fact, the VB6 "+" operator also works with strings, therefore I can’t blindly assume that the Value property is numeric. As a matter of fact, when working with late binding you can’t assume anything about the objects you work with. Here’s the new version that works with strings and numeric properties:

void IncrementValue(object obj, object valueBeingAdded)
{
    // read Value property
    object value = obj.GetType().InvokeMember("Value",
        System.Reflection.BindingFlags.GetProperty, null, obj, null);
    // either increment it (assumes it's a double) or append the string
    object newValue;
    if (value is string)
    {
        newValue = Convert.ToString(value) +
            Convert.ToString(valueBeingAdded);
    }
    else
    {
        newValue = Convert.ToDouble(value) +
            Convert.ToDouble(valueBeingAdded);
    }
    // assign back to Value property
    obj.GetType().InvokeMember("Value",
        System.Reflection.BindingFlags.SetProperty, null, obj,
        new object[] { newValue });
    // call the Refresh method
    obj.GetType().InvokeMember("Refresh",
        System.Reflection.BindingFlags.InvokeMethod, null, obj, null);
}

Do we now have a piece of C# that is perfectly equivalent to the original VB6 code? The answer is again no, because there are many other details that I should account for, including:

a) I assumed that Value is a property – the C# code fails if Value is a class field, unlike VB6 and VB.NET
b) I assumed that Refresh has no parameters – the VB6 and VB.NET code works correctly even if Refresh has one or more optional arguments, but the C# code fails in this case
c) I didn’t care about error codes: if an overflow occurs or if the object doesn’t expose the Value or Refresh members, I should ensure that the caller code sees the same error code that it would see under VB6 or VB.NET.

In the (very unlikely) hypothesis that you never, ever used On Error Resume Next statements and late binding calls, let’s see an example that uses two nested loops and leverages the fact that the Exit statement allows you to specify whether you want to leave the Do loop or the For loop. (I could have made the example more complex by adding a While…Wend loop.)

Sub FindElement(arr() As Integer, ByVal value As Integer)
    Dim row As Integer, col As Integer
    ' outer loop
    For row = 0 To UBound(arr)
        ' inner loop
        col = 0
        Do While col <= UBound(arr, 2)
            If arr(row, col) >= value Then Exit For
            col = col + 1
        Loop
    Next
    Debug.Print "FOUND AT ROW=" & row & ", COL=" & col
    ' additional code here...
End Sub

This code converts to VB.NET without any problem, but requires some extra work to convert to C#:

void FindElement(int[,] arr, int value)
{
    int row = 0;
    int col = 0;
    // outer loop
    row = 0;
    while (row < arr.GetUpperBound(0))
    {
        // inner loop
        col = 0;
        while (col < arr.GetUpperBound(1))
        {
            if (arr[row, col] >= value)
            {
                goto ExitOuterLoop;
            }
            col++;
        }
        row++;
    }
ExitOuterLoop:
    System.Diagnostics.Debug.WriteLine("FOUND AT ROW=" + row.ToString()
        + ", COL=" + col.ToString()); 
    // additional code here...
}

Aarghh! The dreaded GoTo! I like concise and readable code, but I surely don’t want my code to be infested by Goto statements, therefore I rewrote the code the way most C# developers would do:

void FindElement(int[,] arr, int value)
{
    int row = 0;
    int col = 0;
    bool found = false;
    // outer loop
    row = 0;
    while (row < arr.GetUpperBound(0))
    {
        // inner loop
        col = 0;
        while (col < arr.GetUpperBound(1))
        {
            if (arr[row, col] >= value)
            {
                found = true;
                break;
            }
            col++;
        }
        If ( found )
        {
            break;
        }
        row++;
    }
    System.Diagnostics.Debug.WriteLine("FOUND AT ROW=" + row.ToString()
        + ", COL=" + col.ToString()); 
    // additional code here...
}

Not counting remarks, we started from a 11-line VB6 method and ended up with a C# method containing 26 lines, a 236% “improvement.” 

To recap: I picked three simple VB6 examples that use common features – error handling, late-binding, and nested loops- and in all cases I ended up with more verbose and less readable C# code, in spite of all my attempts to make the C# code look like “native” C#. Never forget that the situation can only get worse if the translation is performed by means of a software tool.

The bottom line: converting from VB6 to “equivalent” C# often delivers ugly, verbose, unmaintainable code or code that isn’t perfectly equivalent to the original VB6 code.

In some cases you can come up with C# code that approximates the original VB6 code’s behavior, but that’s a completely different story. When migrating an application with 1 million code lines, I don’t want to worry about the thousands of occurrences of On Error statements or late-bound calls. Not to mention other important differences such as arrays with any LBound, auto-instancing (As New) variables and arrays thereof, fixed-length strings, and so forth.


USING VB6-TO-C# CONVERTERS (aka ONE DOUBLE-JUMP)

Once again, it is important to bear in mind that in all previous examples I made a manual translation from VB6 to C# and came up with the most efficient and less verbose C# that is equivalent to the original VB6 code. Sure, you can drop a few curly braces and merge a couple of lines into a single statement, but I would be surprised if it were possible to significantly improve the C# code seen above.

Actually, I would be very surprised if a VB6 conversion tool could be able to generate the kind of C# code that a human developer would write. My guess is that in the best cases you’d see many calls to support methods – e.g. to work around late-binding deficiencies or simulate methods in the VB library – and a lot of code that just doesn’t “look like” C#.

In the worst (and more frequent) cases, you’d obtain C# code that isn’t perfectly equivalent to the original VB6 code and that forces you to carefully test each and every method that uses error handling, late binding, auto-instancing (As New) variables, arrays, file operations... in other words, virtual every single statement in your application.

If you are switching to C# with the purpose of having well-structured and readable code that performs at least as well as the original VB6 code, you are not going to achieve this goal by means of an automatic VB6-to-C# converter.

USING VB.NET-TO-C# CONVERTERS (aka TWO SINGLE-JUMPS)

At times you really need to convert from VB6 to C#. My recommendation is that you should try hard to convince your (internal or external) customer that translating to C# gives you more headaches than benefits. …but for the sake of discussion, let’s say that you have no choice and that C# is the target language.

Even in such case, a double-jump from VB6 to C# using an automatic converter isn’t the best option, both for technical and business reasons. It is much more rational jumping from VB6 to VB.NET and then using a VB.NET-to-C# converter to get the final result.

I built my opinion on many facts:

a) translating to VB.NET is simpler and faster – either using the free Upgrade Wizard that comes with Visual Studio or a commercial tool such as our VB Migration Partner. You can have a prototype sooner and you can bring a first version to the market in a fraction of the time it takes to just have a C# project that compiles without errors.

b) if you have a group of VB6 developers, odds are that they are already familiar with VB.NET. If they aren’t, they can learn VB.NET quickly because the two languages are similar. By comparison, becoming proficient with C# is a longer and more expensive process. By converting to VB.NET first you leverage the experience of your developers and reduce overall migration costs. (Don’t forget that C# developers usually ask for more money.)

c) There are several VB.NET-to-C# converters on the market, including the exceptional Instant C# (by Tangigle Software Solutions) and C-Sharpener for VB by Elegance Technologies. They deliver the best C# code that a software can automatically do. The companies behind these tools have just one mission: translating from VB.NET to C# in the best way, and in fact they are often updated and produce code that is more and more optimized and each new release. I use one of them and I am entirely satisfied.

d) from the business perspective, it is more convenient to purchase two separate tools, a VB6-to-VB.NET converter and a VB.NET-to-C# converter, because you can use the latter in other circumstances. If you have a tool that converts from VB6 to C# in one step, you can't use it to convert the VB.NET code that you happen to have already (for example ASP.NET pages) or that had to be translated manually from VB6 because of some deficiency in the migration tool.

e) speaking of convenience, the Standard Edition of both Instant-C# and C-Sharpener for VB cost less than 200$, which is a real bargain considering how useful they can be in many situations. This price is negligible if compared with the total cost of the migration of a real-world VB6 business application.

One might object that using a single integrated tool is better than using two distinct converters. But consider this: our VB Migration Partner converts at about 18,000 lines per minute; one of these converters splits out code at about 15,000 lines per minute; both of them come with a batch version, therefore you can easily automate your VB6-to-C# conversions. By combining them you can migrate a 100,000 line VB6 project to C# in about 12 minutes, 2x or 3x faster than what it takes to the Upgrade Wizard to perform just the first jump to VB.NET.