Intro
Although the post's tiltle is self-descriptive, I made this intro to tell you about the moment I had to face such a challenge
It's nothing new that Microsoft is pushing hard towards the use of C# in all of the new developments, platforms, toolkits and so on. Neither it's new that they -although not saying it in the explicit way- are slowly but surely leaving VB.Net behind on purpose.
For instance, check https://devblogs.microsoft.com/vbteam/visual-basic-support-planned-for-net-5-0/: "One of the major benefits of using Visual Basic is that the language has been stable for a very long time. The significant number of programmers using Visual Basic demonstrates that its stability and descriptive style is valued. Going forward, we do not plan to evolve Visual Basic as a language".
I don't know how to interpret a move of adding VB.Net support in .net 5, but at the same time "not evolving the language".
Anyway, for the last couple of years, my feeling was that in the long run, using C# will pay back more (in support, online documentation, resources, finding people to hire and so on) than VB.Net. Neither to say, running a solution in the cloud.
That being said, the subject of this post is to tell the last part of a transformation journey of a product my company owns, which is a software development that was started back in 2002, and evolved in time from VB.Net, (.Net 1.0, WinForms app) to its current state in 2022: C# .Net 4.7.2, full web application.
Stages of the project
Before putting hands on work, a plan had to be traced. That plan was divided in these stages:
Planning
Preparation (pre-conversion)
Conversion
Fixes (post-conversion)
Testing
About the software target of the conversion
The software converted is a document management system (DMS) with workflow capabilities, which by now it's more like a Business Process Management (BPM) than a DMS. The product comprises more than 50 projects (from installers, webforms app, windows services, a WCF service, libraries, etc.) and near 500K lines of code, all of which had to go through the conversion process.
The solution as a whole is still in transformation, leaving behind webforms for the new razor pages, and replacing the old WCF service by WebApi, but that's for another post.
Here I'll tell you how I organized a step by step process to face this daunting task.
Planning
The planning phase is the most important part of the process. In this phase, I had to:
Find information about the language differences (C# vs Vb.Net)
Check the available tools that would help me get the job done
Evaluate pros and cons of those tools
Knowing the selected tool specific issues
Make a tree of project dependencies
Language differences
Despite knowing how to write code in VB.Net and C#, I had to find resources about their not so obvious differences.
I feel comfortable writing in any of those languages, but having a guide at hand would help me to verify the converted product. Besides, I learned a couple of things about what C# could and couldn't do compared to VB.Net (and vice versa), so it's worth having the C# language reference as a reminder.
Just to be clear, this post is not about language differences, but I think it's worth showing some of the most relevants:
VB.Net is less restrictive when assigning variables or values than C#
This is a working example of VB.Net's less restrictive assignments:
Dim a = 25
Function Test(s as String)
Return s & " is a string"
End Function
Dim b = Test(a) 'Here, variable 'a' is an int, but passed to a function that expects a string without problems.
A lot of this loosely typed expressions can be tighten by changing the option Set Strict ON in the VB.Net project. If you don't have it set already, and turn it ON later, be ready to find A LOT of issues in the Error window of Visual Studio.
Keep in mind that I'm not telling that VB.Net is a "loosely typed" language or that C# is a "strongly typed" language, because those affirmations have to do with project settings and the use of certain keywords like dynamic in C#, Set Strict On in VB.Net, Object assignments/use and so on.
VB.Net is less restrictive on code syntax. Both of these are correct:
Dim a = b.ToString()
Dim c = b.ToString 'hey! where are the parenthesis?
For more information, you may check these resources:
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/casting-and-type-conversions
https://sites.harding.edu/fmccown/vbnet_csharp_comparison.html
Besides checking those resources, always go to the Microsoft site and take a look at the latest language version specifications.
Available tools
First advice: don't use online tools. They only work for small code snippets, or they lack of conversion quality, mostly because they also lack context.
That being said, the first tool that came to my mind was some sort of open source package that I could run from inside the project, and I found one called Code Converter (GitHub - icsharpcode/CodeConverter: Convert code from C# to VB.NET and vice versa using Roslyn).
It did a very good job for snippets and small programs, but for bigger things I had to do a lot of manual corrections. The truth is, by now, I don't remember the exact corrections that I had to do, but in that time and with the latest available version (mid 2020's) there were so many errors that I felt overwhelmed, and thought that it wouldn't help me against the giant code base I had to deal with.
Searching online, I found two more tools, one from TangibleSoftwareSolutions and other from VBConversions. None of them are free, so I had to look for reviews before I decided for one. Finally I chose VBConversions.
Evaluate pros and cons of the available tools
CodeConverter
Pros: it's open source, easy to install, easy to check the results.
Cons: the first big run I made and on a piece of code showed a lot of things that had to be manually fixed. Alert! Big work ahead!
Advise: always check the latest version of any tool you try. Maybe CodeConverter it's a lot better now than when I tested it!
VBConversions (https://vbconversions.com/)
Pros: I found good reviews, and had a chance to try it in a small piece of code.
Cons: Any tested piece of code cannot be greater than 2000 lines per project, so not much to evaluate from it. Also, it's not free (U$ 295 lifetime license as of Oct 2022).
TangibleSoftwareSolutions (https://tangiblesoftwaresolutions.com/)
Pros: Relative low price, at least compared to VBConversions (U$ 149 as of Oct 2022)
Cons: I didn't find much online reviews about the product, which made me doubt, although they offer not only VB.Net <-> C# conversion, but a lot others.
Knowing the selected tool's issues
I started with a small project and run the converter on it. A few issues were shown and after fixing them, I ran it again. When no issues were reported by the tool, and a converted project was produced, I went to check it in the IDE. Surprise, surprise. There were a lot of errors when opened in Visual Studio. The root cause of these errors where mainly two:
Running the converter in a standalone project (i.e. dettached from the solution it belongs to)
Errors due to the tool itself.
Cause #1 was hard to pinpoint at first. When opening the VB.Net project in the converter, it was being reported as a Visual Studio 2013 project, although I was using Visual Studio 2019 by that time. I sent a note to the developer but he didn't know how to help me on this. After a few trials and errors, I decided to run the converter in the project, but without moving it from its original folder (i.e. I didn't isolate it in a temporary folder), so I had a reference to the parent solution, and what a discovery! The number of errors decreased a lot. Every tool has its own way of working things out, but having the project and the solution in the same conversion context caused the converter to detect it as a Visual Studio 2019 project, and the conversion went better. Cause #2 was due to the converter itself, its something you may find almost with any tool. I reported several of them to the developer of VBConversions, but I had to keep going... no time for waiting for a patch! These are examples of errors that I found:
There were some calls to functions that expected a ref parameter, and the ref keyword was not added in the converted code
Some parenthesis were missing in ToString, GetType, FirstOrDefault, etc., causing them to be interpreted as a delegate instead of a function call. That's why it's a good idea to write those statements in the right way, i.e. as a function call.
There were many more errors, but I won't describe each one because they were tool-specific.
The important thing is to take note of these glitches and know how to fix them in the source code first, and in the converted code after.
Make a tree of project dependencies
The point of making a tree of dependencies is to start from the project that has the lowest number of dependencies (bottom-to-top approach).
This allowed me to isolate the project and run tests on it without being concerned on dependencies.
In general, projects with less dependencies are smaller or less complex than projects with more dependencies.
A "solution" in Visual Studio is composed of several projects, that can be of many types and languages and the product this post is based on is composed of at least 4 core solutions, each of which reference a lot of projects in turn.
These are the 4 main modules:
DocumentServer (DS) solution: A windows service that contains the business logic and database access layer. It's made of 20 different projects.
ImageServer (IS) solution: A windows service that abstracts the file system (document repository) from the web service. It's about 11 different projects.
Web Service: The API of the project, which connects clients (web apps) and the low level layers (DS and IS). It's about 17 projects.
Web Application: The frontend of the product. 13 different projects.
Not all projects are unique to their solutions; there are several shared projects, but these numbers give you and idea of the complexity of the conversion process, and why it had to be carefully planned.
This is the map of dependencies of just the DS solution. Projects at the top have the least dependencies, meaning that many of other projects depend on them.

Note: I said the approach is "bottom-to-top" regarding the logical order of dependencies, although I realized that the diagram above is built top-to-bottom.
Anyway, following the arrows and colors would give you and idea of the dependency order: at the top, the projects with LESS dependencies; at the bottom, the project with the MOST dependencies.
Preparation (pre-conversion)
With the plan almost ready to be executed, I prepared the first project.
These are some of the things I had to check before running the converter:
Set Strict ON in the project
Add all the CType functions needed in object assignments/comparissons, i.e.:
before: Dim text as String = HttpContext.GetResourceObject("LABEL")
after: Dim text as String = CType(HttpContext.GetResourceObject("LABEL"), String)
Change string comparisons:
before: If a = "" 'variable 'a' could be null or empty and this will be true in either case in vb.net, but not in C#.
after: If String.IsNullOrEmpty(a)
Using Visual Studio's RegEx Find/Replace helped a lot:
Find: (If|OrElse|AndAlso|Not)\s([_A-Za-z0-9.]+)[\s]*=[\s]*String\.Empty
Replace by: $1 String.IsNullOrEmpty($2)
The same for inequalities:
Find: (If|OrElse|AndAlso|Not)\s([_A-Za-z0-9.]+)[\s]*<>[\s]*String\.Empty
Replace by: $1 Not String.IsNullOrEmpty($2)
Check all uses of the byref keyword and take note where they happen, so to check them in the converted code.
Change all IIF statements by IF function. The latter takes into account that both parts (true and false parts) are of the same type.
Remove all VB.Net constants and replace them by literals. I.e.:
vbCrLf by Environment.NewLine
vbCr by Convert.ToChar(13)
and so on.
Remove Chr, Asc, Left, Right, Space, CDec, CInt, CDbl, CSng and replace by Convert.Toxxx or build a helper function (for Left and Right)
Rewrite all Exit Try because they have no equivalent in C#.
Remove Enums inside Interfaces, this is not allowed in C#.
Check the correct implementation of events. VB.Net uses WithEvents to automatically bind events with handlers, which is something C# does not do.
Rework code that uses ReDim.
Rework code that define static variables inside methods.
Remove the .Item accessor in collections, since C# shows this as an error. Instead, C# uses the [ ] syntax to access items.
Be careful when assigning an explicit value (either directly of by calling a function) to the iterator variable in a loop. VB.Net allows this, C# doesn't.
Conversion
This part is not fun at all. I just ran the converter and waited for it to finish.
With small projects it was fast, but with larger projects, with lot of external references, it took several minutes to complete.
Fixes (post-conversion)
If the previous task was not my type of fun, this was neither.
Every tool out there will leave you with plenty of stuff to fix. The amount of these depends on:
The quality of the tool used
The quality and simplicity of the code to be converted
The type of constructs used. I.e. Linq, event handlers, lambda expression are more likely to be mistranslated as opposed to less complex expressions.
So, this is a shortened list of fixes I had to apply to the new C# project. The kind of issues to fix largely depend on the tool used to do the conversion, so I don't think it's worth it to list everything I had to do, just a few I consider relevant:
Remove references to Microsoft.VisualBasic.dll
Sort any errors in the Errors window, so you may apply the same fix to several error instances.
Find all "vb" texts and see if they are leftovers of the vb world or just coincidences (like variable names).
Search & replace unnecessary Convert calls added.
Check the calls that used byref originally in VB.Net and verify they are called with the ref keyword in the C# code. If the function that expects a ref parameter was converted with the ref keyword, and the caller of that function does not use it, a compiler error will be shown (easy fix), but I found some functions that were converted without the ref keyword, neither in the function definition nor in the caller (tool error). There is no error shown, since it's a logical error, not a compiler error, and could be catastrophic during runtime.
Check the correct binding of handlers to declared events. In backend projects, events are less likely to be used than in frontend projects (like winforms).
Fix any explicit casts that may have been missed by the converter.
Testing
There are some kind of projects that are easier to test than other.
For instance, class libraries that contain mostly utility classes are easy to test.
On the other hand, multithreaded services with a lot of dependencies are harder to test.
There is no ultimate guide to how a test should be carried on. In my case, since I knew very well each project, what I did was to put a lot of bookmarks and breakpoints in parts of the converted code that I wanted to check step by step in a debug session, and that I knew were critical and not test-automated.
After each project was completely converted and errors were fixed, compiling it was the last thing to do.
Finally, when the build and test were successful I replaced the original VB.Net project in the solution with the C# version, and did an integration test, usually running the app and checking those bookmarks/breakpoints mentioned earlier.
Final words
The best of converting the first project is that it leaves a lot of knowledge on how to tackle the next project in the list.
It's essential to keep a check list (the plan), and update it according to the issues that were found during every step.
It's also usefull to take notes, keep adjusting the procedures, and have a list of Regex ready to help.
If you ever face a challenge like this, and when you see the last bit of code is converted, look behind and think of that moment when you started reading this post seeking for help, advise, or just a miracle...
Comments