Migrating VB.NET to C#: Step-by-Step Guide

How to migrate VB.NET to C# - syntax conversion, project structure changes, COM dependencies, testing strategy, and tooling. A practical step-by-step guide.

ASP.NET Core Modernization Migration Upgrade

Migrating VB.NET to C#: Step-by-Step Guide

  • Tuesday, August 4, 2026

How to migrate VB.NET to C# - syntax conversion, project structure changes, COM dependencies, testing strategy, and tooling. A practical step-by-step guide.

If you're running a VB.NET application and considering a migration to C#, the short answer is this: VB.NET and C# compile to the same .NET runtime, so migration is a code conversion and refactoring exercise rather than a platform change - but it's not trivial, and the complexity depends almost entirely on what your codebase does rather than how large it is. A 50,000-line VB.NET application with clean, straightforward business logic can migrate faster than a 15,000-line application with heavy use of late binding, optional parameters, and VB-specific runtime behaviors.

This guide walks through the assessment, the conversion process, the patterns that need manual attention, and the testing strategy that makes migration safe.

Why Migrate from VB.NET to C# at All?

VB.NET is not dead - Microsoft still maintains it as part of .NET, and existing VB.NET applications will continue to run. The practical reasons to migrate are narrower but real:

Ecosystem convergence. The .NET ecosystem - tutorials, Stack Overflow answers, NuGet packages, AI coding tools, Microsoft documentation - has converged almost entirely on C#. New features like Span<T>, record types, pattern matching, and source generators are documented and exemplified in C#. VB.NET supports most of these but the documentation, examples, and community support are increasingly C#-first.

Hiring difficulty. Finding VB.NET developers is meaningfully harder than finding C# developers and has been getting harder for several years. Migrating to C# expands the available developer pool and makes onboarding new developers faster.

Tooling support. Some modern .NET tooling - .NET Aspire, Blazor, some code analysis tools - is C#-first or C#-only. A C# codebase has access to the full tooling ecosystem without workarounds.

The migration is contained. Unlike migrating from VB.NET to Python or Java (which changes the runtime), VB.NET to C# stays on the same .NET platform. The same Entity Framework Core models work, the same NuGet packages work, the same Azure services work. You're changing syntax and idioms, not infrastructure.

Step 1: Assess the Codebase Before Writing Any C#

The assessment phase determines the migration's scope and complexity. Skipping it produces scope surprises that cost more time than the assessment would have.

What to measure:

Lines of code by file type. VB.NET projects typically contain .vb files (application code), .vbproj files (project configuration), and sometimes .aspx or .ascx files if the project includes ASP.NET Web Forms. Count separately - Web Forms UI files require different handling from pure VB.NET business logic.

VB.NET-specific feature usage. Some VB.NET features have no direct C# equivalent and require pattern changes rather than syntax substitution:

VB.NET FeatureMigration ComplexityC# Approach
On Error GoTo / On Error Resume NextMediumRewrite as try/catch blocks
Late binding (Dim x As Object)HighIntroduce proper types or dynamic (avoid where possible)
Optional parameters with Optional/IsMissingLowC# optional parameters or overloads
My namespace (My.Application, My.Settings)MediumReplace with .NET BCL equivalents
WithEvents / event handlersLowC# event syntax
IsNothing() / IsNumeric() / IsDate()Lowis null / int.TryParse() / DateTime.TryParse()
String comparison with = operatorLow.Equals() or == with explicit culture
Dim with implicit typingLowvar in C#
Module (static class equivalent)Lowstatic class
Integer / Long / String typesLowint / long / string
Default properties (indexers)MediumExplicit indexer implementation

COM dependency inventory. If the VB.NET application calls COM objects - early or late binding - these need individual assessment. COM dependencies are the most common cause of VB.NET migration complexity and timeline overrun.

Third-party package compatibility. Any VB.NET-specific libraries (Microsoft.VisualBasic namespace usage) need replacing. Most functionality in Microsoft.VisualBasic has a direct .NET BCL equivalent, but it needs mapping explicitly.

Output from the assessment:

  • Estimated lines of code to convert
  • Count and complexity of VB.NET-specific patterns requiring manual handling
  • COM dependency list with replacement plan per component
  • Risk areas (late binding, On Error Resume Next, COM) flagged for focused testing

Step 2: Choose a Conversion Approach

There are two realistic approaches for most VB.NET-to-C# migrations. The right choice depends on codebase size and complexity.

Approach A: Automated Conversion + Manual Review

Automated VB.NET-to-C# converters translate syntax mechanically. The output is rarely production-quality but provides a starting point that's faster than writing C# from scratch.

Tools:

  • Telerik Code Converter (telerik.com/converter) - free, web-based, handles most syntax conversions well
  • SharpDevelop's VB.NET to C# converter - open source, integrates into the IDE
  • CSharpifier - community tool for quick conversions

The converter workflow:

  1. Run the converter on each .vb file
  2. Review every output file - converters handle syntax but miss semantic issues
  3. Fix VB.NET-specific patterns the converter couldn't translate (late binding, error handling)
  4. Run the compiler and fix all build errors before moving to the next file
  5. Run tests after each file or logical group of files

What automated converters handle well: Basic syntax (variable declarations, loops, conditionals, method signatures, class structure), simple string operations, arithmetic, access modifiers.

What they handle poorly: Late binding, On Error Resume Next patterns, Microsoft.VisualBasic runtime function usage, implicit type conversions VB.NET allows but C# doesn't, and context-dependent operator behaviour differences.

Approach B: Manual Rewrite by Module

For applications where the business logic is complex, the VB.NET code uses heavy late binding, or the conversion output from automated tools requires more review effort than a direct rewrite, a module-by-module manual rewrite is more reliable.

When to choose this approach:

  • The codebase uses On Error Resume Next pervasively (very common in older VB.NET applications) - converting this to proper C# exception handling requires understanding what errors are being silently ignored and why
  • Significant COM late binding through CreateObject() and untyped object references
  • The code has low test coverage, making it difficult to validate automated conversion output

Step 3: Project Structure Changes

VB.NET and C# project files have the same structure in modern SDK-style .csproj format, making the project conversion straightforward:

VB.NET project file (.vbproj):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>MyApp</RootNamespace>
  </PropertyGroup>
</Project>

Converted C# project file (.csproj):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>MyApp</RootNamespace>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>

The only meaningful changes: the file extension (.vbproj → .csproj), the language version (implicit in the SDK), and the addition of <Nullable>enable</Nullable> - C#'s nullable reference types, which have no VB.NET equivalent but are a significant safety improvement worth enabling from the start.

Step 4: Common VB.NET to C# Conversion Patterns

Variable Declarations and Types

' VB.NET
Dim name As String = "Facile"
Dim count As Integer = 0
Dim price As Decimal = 9.99D
Dim isActive As Boolean = True
Dim items As New List(Of String)()
Dim result As Object ' late binding - needs specific type in C#
// C#
string name = "Facile";     // or: var name = "Facile";
int count = 0;
decimal price = 9.99m;
bool isActive = true;
var items = new List<string>();
object result;              // or introduce the proper type

Control Flow

' VB.NET
If x > 0 Then
    DoSomething()
ElseIf x < 0 Then
    DoSomethingElse()
Else
    DoDefault()
End If

For i As Integer = 0 To 9
    Console.WriteLine(i)
Next

For Each item As String In items
    Console.WriteLine(item)
Next

Select Case status
    Case "Active"
        ActivateAccount()
    Case "Inactive"
        DeactivateAccount()
    Case Else
        HandleUnknown()
End Select
// C#
if (x > 0)
{
    DoSomething();
}
else if (x < 0)
{
    DoSomethingElse();
}
else
{
    DoDefault();
}

for (int i = 0; i <= 9; i++)
{
    Console.WriteLine(i);
}

foreach (string item in items)
{
    Console.WriteLine(item);
}

switch (status)
{
    case "Active":
        ActivateAccount();
        break;
    case "Inactive":
        DeactivateAccount();
        break;
    default:
        HandleUnknown();
        break;
}

// Or C# pattern matching (cleaner for modern code):
switch (status)
{
    case "Active" => ActivateAccount(),
    case "Inactive" => DeactivateAccount(),
    _ => HandleUnknown()
};

Error Handling

This is the most important conversion in most VB.NET codebases. On Error Resume Next - which silently ignores all errors - has no C# equivalent and should not be emulated. Each instance needs individual analysis:

' VB.NET - silently ignores errors
On Error Resume Next
Dim result = SomeOperation()
If Err.Number <> 0 Then
    LogError(Err.Description)
    Err.Clear()
End If

' VB.NET - structured error handling
Try
    result = SomeOperation()
Catch ex As Exception
    LogError(ex.Message)
End Try
// C# - always use try/catch
try
{
    var result = SomeOperation();
}
catch (SpecificException ex)  // catch specific exceptions where possible
{
    _logger.LogError(ex, "Operation failed");
    // handle or rethrow
}
catch (Exception ex)
{
    _logger.LogError(ex, "Unexpected error in SomeOperation");
    throw;  // rethrow unexpected exceptions - don't swallow them
}

The On Error Resume Next problem: When converting a VB.NET codebase that uses On Error Resume Next pervasively, each instance requires understanding what would happen if the operation fails. Some are genuinely intentional (file existence checks where "file not found" is expected) - these should become explicit checks with File.Exists() or similar. Others are accidental error suppression - these should become proper try/catch blocks with logging.

String Operations

' VB.NET
Dim result As String = Left(name, 5)
Dim found As Integer = InStr(name, "abc")
Dim upper As String = UCase(name)
Dim trimmed As String = Trim(name)
Dim combined As String = name & " " & surname
// C#
string result = name[..5];                    // or name.Substring(0, 5)
int found = name.IndexOf("abc") + 1;         // InStr is 1-based; IndexOf is 0-based
string upper = name.ToUpper();
string trimmed = name.Trim();
string combined = $"{name} {surname}";       // string interpolation

The Microsoft.VisualBasic Namespace

VB.NET code often uses functions from the Microsoft.VisualBasic namespace - IsNumeric(), IsDate(), Format(), DateDiff(), Left(), Right(), Mid(). Replace these with .NET BCL equivalents:

VB.NETC# Equivalent
IsNumeric(x)int.TryParse(x, out _) or double.TryParse(x, out _)
IsDate(x)DateTime.TryParse(x, out _)
IsNothing(x)x is null
Format(d, "dd/MM/yyyy")d.ToString("dd/MM/yyyy")
DateDiff(DateInterval.Day, d1, d2)(d2 - d1).Days
Left(s, n)s[..n] or s.Substring(0, n)
Right(s, n)s[^n..] or s.Substring(s.Length - n)
Mid(s, start, len)s.Substring(start - 1, len) (InStr is 1-based)
StrConv(s, vbProperCase)CultureInfo.CurrentCulture.TextInfo.ToTitleCase(s)
NowDateTime.Now
TodayDateTime.Today

Step 5: Handling Late Binding

Late binding - calling methods on Object-typed variables without compile-time type checking - is common in older VB.NET code and has no safe direct equivalent in C#.

' VB.NET late binding
Dim obj As Object = CreateObject("Excel.Application")
obj.Visible = True
obj.Workbooks.Add()

Three conversion options:

Option 1: Introduce the proper type (best choice when possible)

// Add reference to Microsoft.Office.Interop.Excel
using Excel = Microsoft.Office.Interop.Excel;
var excel = new Excel.Application();
excel.Visible = true;
excel.Workbooks.Add();

Option 2: Use dynamic (temporary bridge - avoids compiler errors but loses type safety)

dynamic obj = Activator.CreateInstance(
    Type.GetTypeFromProgID("Excel.Application")!);
obj.Visible = true;
obj.Workbooks.Add();

Option 3: Replace the COM dependency entirely (best long-term choice for non-Office COM)

// Replace Excel COM automation with EPPlus or ClosedXML
using var package = new ExcelPackage();
var sheet = package.Workbook.Worksheets.Add("Sheet1");
// No COM required - pure .NET

For most production migrations, Option 3 is the correct long-term approach. COM interop in .NET works but creates deployment complexity (requires COM components installed on the server) and has no benefit over modern .NET libraries for most use cases.

Step 6: Testing Strategy

Testing is what makes a VB.NET to C# migration safe. Without tests, you have no reliable way to confirm that the C# code produces the same outputs as the VB.NET code for the same inputs.

Establish a baseline before converting:

The most effective approach is black-box testing at the API or function level before migration begins - capturing what the VB.NET application does for a representative set of inputs. These become the acceptance tests for the migrated C# code.

Three testing levels for migration:

Unit tests on converted business logic:

[Fact]
public void CalculateDiscount_ReturnsCorrectAmount_ForPremiumCustomer()
{
    // Same test, now running against C# implementation
    var service = new PricingService();
    var result = service.CalculateDiscount(100m, CustomerTier.Premium);
    Assert.Equal(15m, result); // matches known VB.NET output
}

Integration tests on data access:

After conversion, run the full data access layer against a test database with known data - confirm that EF Core (or Dapper) queries return the same results as the original ADO.NET queries in the VB.NET code.

Side-by-side comparison testing:

For applications where automated tests are impractical to write before the migration (common in legacy codebases), run the VB.NET and C# versions simultaneously against the same inputs and compare outputs. This requires both versions to be runnable at the same time - possible during a phased migration.

Step 7: Modernize as You Go (Selectively)

The migration to C# creates a natural opportunity to modernize code patterns - but it's important to separate migration (VB.NET → C#, same behaviour) from modernization (improving the design), because mixing them makes validation harder.

Patterns worth modernizing during migration:

  • Replace for loops over collections with LINQ where intent is clearer
  • Replace string concatenation in loops with StringBuilder
  • Add nullable reference type annotations
  • Replace null checking patterns with is null / is not null

Patterns to defer until after migration is validated:

  • Changing the architecture (introducing DI, repositories, CQRS)
  • Changing the data access layer from ADO.NET to EF Core
  • Changing the API surface (method signatures, return types)

The rule: if the change makes the C# code behave differently from the VB.NET code, it's modernization, not migration. Do migration first, validate it against the VB.NET baseline, then modernize in a separate phase.

Frequently Asked Questions

How long does a VB.NET to C# migration take?

For a clean VB.NET codebase (minimal late binding, structured error handling, no COM dependencies) of around 20,000 lines, an experienced C# developer can convert approximately 500–1,000 lines per day using automated conversion plus manual review. A 20,000-line project might take 4–6 weeks. COM-heavy or late-binding-heavy codebases take significantly longer - the assessment phase is what determines the real estimate, not the line count alone.

Should I migrate to C# and .NET 8 at the same time?

These are separable concerns and are best separated. VB.NET targeting .NET 6 can be migrated to C# still targeting .NET 6, then separately upgraded to .NET 8. Combining both changes makes validation harder - a failing test could be caused by the language change or the framework version change, and knowing which matters for fixing it. Migrate language first, validate, then upgrade the target framework.

Can I have VB.NET and C# in the same solution?

Yes - Visual Studio supports solutions containing both VB.NET and C# projects, and they can reference each other freely since both compile to the same .NET intermediate language. This enables a phased migration where some projects are converted while others remain VB.NET, useful for large solutions where converting everything at once isn't practical.

What are the biggest risks in a VB.NET to C# migration?

The three most common risk areas are: On Error Resume Next patterns that silently suppress errors in VB.NET code - when these are properly converted to C# exception handling, previously hidden errors may surface for the first time; late binding code that the VB.NET compiler allowed but that doesn't translate cleanly to strongly-typed C#; and VB.NET-specific string and date functions behaving slightly differently from their C# BCL equivalents (particularly around culture-sensitive string comparisons and date formatting). All three are manageable with thorough testing.

Is there a tool that fully automates VB.NET to C# conversion?

Partial automation only. Tools like Telerik Code Converter handle syntax translation well but cannot handle semantic differences - late binding, error handling patterns, implicit type conversions, and VB.NET runtime behavior. Every automated conversion output needs review before being considered production-ready. For complex codebases, manual review of converter output often takes as long as a direct rewrite.

After migrating to C#, should we also move from .NET Framework to .NET 8?

Usually yes, but separately and after the language migration is validated. .NET 8 provides better performance, current security patches, and access to the full modern .NET ecosystem. The migration path from .NET Framework to .NET 8 is well-documented and supported by Microsoft's .NET Upgrade Assistant tooling. Running both migrations sequentially - VB.NET to C# first, then .NET Framework to .NET 8 - is lower risk than attempting both simultaneously.

Closing

Migrating from VB.NET to C# is one of the more contained legacy modernization projects available to .NET teams - the platform stays the same, the tooling stays the same, and the migration is primarily a syntax and idiom conversion. The complexity is real but bounded, and the outcome is a codebase with significantly broader hiring availability, better tooling support, and access to the full modern .NET ecosystem.

If you're planning a VB.NET to C# migration and want an experienced team to assess scope and lead the work, our VB.NET development and migration team has delivered migrations across a range of codebase sizes and complexity levels