Tangible Software Solutions

VB.NET and C# Comparison and Equivalents


Equivalents were produced with the Free Edition of Instant C# and the Free Edition of Instant VB.


VB.NET C#
Public MustInherit Class AbstractClass
    Protected MustOverride Sub AbstractMethod()
End Class
public abstract class AbstractClass
{
    protected abstract void AbstractMethod();
}
VB.NET C#
Public
Private
Friend
Protected
Protected Friend
public
private
internal
protected
protected internal

Unsized Array

VB.NET C#
Dim myArray() As Integer int[] myArray = null;

Sized Array

VB.NET C#
Dim myArray(1) As Integer int[] myArray = new int[2];

Access Array Element

VB.NET C#
x = myArray(0) x = myArray[0];

Jagged Array

VB.NET C#
Dim myArray(1)() As Integer int[][] myArray = new int[2][];

Rectangular Array

VB.NET C#
Dim myArray(1, 2) As Integer int[,] myArray = new int[2, 3];

Resizing Array

VB.NET C#
ReDim Preserve myArray(newSize)
ReDim myArray(newSize)
System.Array.Resize(ref myArray, newSize + 1);
myArray = new foo[newSize + 1];

Initializing Array

VB.NET C#
myArray = New Integer(1) {1,2} myArray = new int[2] {1, 2};

Empty Array

VB.NET C#
Dim myArray As String() = New String() {} string[] myArray = new string[0];
VB.NET C#
Public Class AsyncAndAwait
    Public Async Function GetSourceCode() As Task(Of String)
        Return Await (New Net.WebClient).DownloadStringTaskAsync(s)
    End Function

    Private Async Sub AsyncWithLambdas()
        Await RunAsync(CoreDispatcherPriority.Normal, Async Sub()
            Await messageDialogService.ShowAsync()
            dialogIsShowing = False
        End Sub)
    End Function
End Class
public class AsyncAndAwait
{
    public async Task<string> GetSourceCode()
    {
        return await(new System.Net.WebClient()).DownloadStringTaskAsync(s);
    }

    private async void AsyncWithLambdas()
    {
        await RunAsync(CoreDispatcherPriority.Normal, async () =>
        {
            await messageDialogService.ShowAsync();
            dialogIsShowing = false;
        });
    }
}

Class Attribute

VB.NET C#
<Serializable()>
Public Class Foo
[Serializable()]
public class Foo

Method Attribute

VB.NET C#
<DebuggerHidden()>
Public Sub Method()
[System.Diagnostics.DebuggerHidden()]
public void Method()

Method Return Type Attribute

VB.NET C#
Public Function Method() As <ReturnAttribute> Integer [return: ReturnAttribute]
public int TestReturnValueAttribute()

Method Parameter Attribute

VB.NET C#
Public Sub Method(<ParamAttribute> ByVal x As Integer) public void Method([ParamAttribute] int x)

Named Properties in Attributes

Attributes have a special syntax which allows setting properties of the attribute type after the attribute constructor arguments — called named properties. These have the same syntax as named arguments in VB, but not in C#. Note that in the following example, 'property1' and 'property2' are named properties, not named arguments — the attribute type constructor for this example would only have one parameter, not three. Named arguments are not available for attributes.

VB.NET C#
' note: property1 and property2 are named properties:
<FooAttribute(arg, property1:=1, property2:=2)>
Public Class Foo
End Class
// note: property1 and property2 are named properties:
[FooAttribute(arg, property1=1, property2=2)]
public class Foo
{
}

AutoEventWireup is an ASP.NET page attribute that will allow automatic wireup of page events when this attribute is set to true on the page. The AutoEventWireup page attribute leads to the following options for the associated VB and C# code-behind class:

AutoEventWireup = true

This allows specifying Page_Load, Page_Init, etc. event handler methods by name alone. In VB, no Handles clause is appended and in C# no event wireup code is required. Provided the method name matches, the methods will automatically handle the associated events. If you do erroneously code any event wireup code, this will cause the event handler to execute twice.

AutoEventWireup = false

This requires that you code the event handler wireup. In VB, you would specify a Handles clause and in C# you would include code to subscribe to the events. However, in C# the problem is that the first page event occurs before the event wireup code has a chance to execute. A common C# approach in this case is to override the base class OnLoad, OnInit, etc. methods, dispensing with the need to have event handler wireup code for the page events. For these cases (Handles used instead of setting AutoEventWireup to true), Instant C# automatically adds event wireups corresponding to all 'Page' methods where a Handles clause is used to the Page_Init or OnInit method if it exists. If no Page_Init or OnInit method exists, a new OnInit method is added. The event wireup for the Page_Init method is added to the class constructor — if no constructor exists, then a constructor is added by Instant C#.

VB.NET C#
Sub Method1(ByRef myParam As Integer)
End Sub

Sub Method2
    Method1(foo)
End Sub
void Method1(ref int myParam)
{
}

void Method2()
{
    Method1(ref foo);   // 'ref' is required in the method call also
}

The closest equivalent to the standard VB casting operators (CType and the corresponding CInt, CStr, etc.) are calls to the System.Convert methods if the appropriate method exists, otherwise they are converted to the standard C# casting operator.

The behavior of both the System.Convert methods and the standard C# casting operator are subtly different from the VB casting operators though, so you should always test the behavior of your converted code.

The C# equivalent to VB's 'DirectCast' is the standard C# casting operator.

The VB 'TryCast' operator always converts to the C# 'as' operator. The C# 'as' operator converts to the VB 'TryCast' operator, except for the case of nullable types, in which case you must use the 'CType' operator.

VB.NET C#


x = CBool(y)
x = CInt(y)
x = CDec(y)
x = CChar(y)
x = CStr(y)
x = CDate(y)
x = CObj(y)

x = CType(y, Foo)
x = DirectCast(y, Foo)
x = TryCast(y, Foo)
using System;

x = Convert.ToBoolean(y);
x = Convert.ToInt32(y);
x = Convert.ToDecimal(y);
x = Convert.ToChar(y);
x = Convert.ToString(y);
x = Convert.ToDateTime(y);
x = (object)y;

x = (Foo)y;
x = (Foo)y;
x = y as Foo;

A further complication is that C# integer casts always truncate, while the VB integer conversion operators always round. For this reason, in order to achieve the same results as the C# casts, a call to the .NET Math.Truncate function is required prior to the call to the CInt, CLng, and CShort operators when converting C# integer casts.

C# VB.NET
i = (int)someDouble; i = CInt(Math.Truncate(someDouble))

In VB, the keyword 'From' is used to initialize collections during construction.

VB.NET C#
' lists:
Dim myList As New List(Of Integer)() From {1, 2, 3}

' dictionaries:
Dim myD As New Dictionary(Of String, Integer) From {
    {string1, 80},
    {string2, 85}
}
// lists:
List<int> myList = new List<int>() {1, 2, 3};

// dictionaries:
Dictionary<string, int> myD = new Dictionary<string, int>() {
    {string1, 80},
    {string2, 85}
};

Unlike VB's #Const, C# #define's cannot set values — they always specify 'true'.

VB.NET C#
#Const ONE = True
#Const TWO = 2

#If ONE Then
    ' code for condition ONE
#ElseIf TWO Then
    ' code for condition TWO
#Else
    ' code for other cases
#End If
#define ONE
// #Const TWO = 2 — no C# equivalent

#if ONE
    // code for condition ONE
#elif TWO
    // code for condition TWO
#else
    // code for other cases
#endif

Local Constant

VB.NET C#
Const myConst As Integer = 2 const int myConst = 2;

Local Variable

VB.NET C#
Dim myVar As Integer = 2 int myVar = 2;

Inferred Types

VB.NET C#
Option Infer On
...
Dim myVar = 2
var myVar = 2;

Shared/Static Field

VB.NET C#
Public Shared S As Integer public static int S;

Read-Only Field

VB.NET C#
Public ReadOnly R As Integer = 2 public readonly int R = 2;

VB Static Local Variable

VB.NET C# (no direct equivalent)
Sub Method()
    Static s As Integer
    s += 1
End Sub
private int Method_s;
void Method()
{
    Method_s += 1;
}
VB.NET C#
Class Foo
    Public Sub New()
        Me.New(0)   ' call to other constructor
    End Sub

    Public Sub New(ByVal i As Integer)
    End Sub

    Protected Overrides Sub Finalize()
    End Sub
End Class
class Foo
{
    public Foo() : this(0)   // call to other constructor
    {
    }

    public Foo(int i)
    {
    }

    ~Foo()
    {
    }
}
VB.NET C#
Integer
Boolean
String
Char
Single
Double
Date
Object
Decimal
Short
Long
Byte
SByte
UShort
UInteger
ULong
int
bool
string
char
float
double
System.DateTime (no C# language built-in type)
object
decimal
short
long
byte
sbyte
ushort
uint
ulong
VB.NET C#
Public Delegate Sub FooDelegate() public delegate void FooDelegate();

Implicitly-Typed Enum

VB.NET C#
Public Enum FormMode
    EditMode
    NewMode
End Enum
public enum FormMode
{
    EditMode,
    NewMode
}

Explicitly-Typed Enum

VB.NET C#
Public Enum FormMode As Byte
    EditMode
    NewMode
End Enum
public enum FormMode : byte
{
    EditMode,
    NewMode
}

Event Declarations

VB.NET C#
Public Event EventOne(ByVal p As Foo) ' implicit delegate

Public Event EventTwo As FooDelegate ' explicit delegate
public delegate void EventOneEventHandler(Foo p);
public event EventOneEventHandler EventOne;

public event FooDelegate EventTwo;

Event Wireups

VB.NET C#
AddHandler ControlA.Click, New ControlA.SomeEventHandler(AddressOf ControlA_Click)

AddHandler ControlA.Click, AddressOf ControlA_Click
ControlA.Click += new ControlA.SomeEventHandler(ControlA_Click);

ControlA.Click += ControlA_Click;

Strict Conversion of WithEvents/Handles

VB.NET C#
Class TestClass
    Private WithEvents MyField As FooDelegate

    Sub Method() Handles MyField.FooEvent
    End Sub
End Class
using System.Runtime.CompilerServices;
using System.Diagnostics;

class TestClass
{
    [AccessedThroughProperty(nameof(MyField))]
    private FooDelegate _MyField;
    private FooDelegate MyField
    {
        [DebuggerNonUserCode]
        get
        {
            return _MyField;
        }
        [MethodImpl(MethodImplOptions.Synchronized), DebuggerNonUserCode]
        set
        {
            if (_MyField != null)
                _MyField.FooEvent -= Method;

            _MyField = value;

            if (value != null)
                _MyField.FooEvent += Method;
        }
    }

    void Method()
    {
    }
}

Casual Conversion of WithEvents/Handles

VB.NET C#
Class TestClass
    Private WithEvents MyField As FooDelegate

    Sub Method() Handles MyField.FooEvent
    End Sub
End Class
class TestClass
{
    private FooDelegate MyField;

    void Method()
    {
    }

    public TestClass()
    {
        SubscribeToEvents();
    }

    private bool EventsSubscribed = false;
    private void SubscribeToEvents()
    {
        if (EventsSubscribed)
            return;
        else
            EventsSubscribed = true;

        MyField.FooEvent += Method;
    }
}
VB.NET C#
Try
    ...
Catch x As FooException
    ...
Catch y As BarException When z = 1
    ...
Finally
    ...
End Try
try
{
    ...
}
catch (FooException x)
{
    ...
}
catch (BarException y) when (z == 1)
{
    ...
}
finally
{
    ...
}

VB extension methods are declared in modules and use the 'Extension' attribute, while C# extension methods are declared in static classes and use the 'this' keyword on the first parameter.

VB.NET C#
Public Module Foo
    <System.Runtime.CompilerServices.Extension> _
    Sub Extension(ByVal myParam As String)
        ' ...
    End Sub
End Module
public static class Foo
{
    void Extension(this string myParam)
    {
        // ...
    }
}

Creating a List

VB.NET C#
Dim myVar As New List(Of Integer) List<int> myVar = new List<int>();

Creating a Dictionary

VB.NET C#
Dim myVar As New Dictionary(Of String, Integer) Dictionary<string, int> myVar = new Dictionary<string, int>();

Defining a Generic Class

VB.NET C#
Public Class GenericClass (Of T)
End Class
public class GenericClass<T>
{
}

Defining a Generic Class with a Constraint

VB.NET C#
Public Class GenericClass (Of T As SomeBase)
End Class
public class GenericClass<T> where T: SomeBase
{
}

Defining a Generic Class with a 'new' Constraint

VB.NET C#
Public Class GenericClass (Of T As New)
End Class
public class GenericClass<T> where T: new()
{
}

Defining a Generic Method

VB.NET C#
Public Function Compare(Of T)(param1 As T, param2 As T) As Integer
End Function
public int Compare<T>(T param1, T param2)
{
}

Defining a Generic Method with a Constraint

VB.NET C#
Sub Swap(Of T As Class)(ByRef l As T, ByRef r As T)
End Sub
void Swap<T>(ref T l, ref T r) where T: class
{
}
VB.NET C#
Imports Foo
Imports Foo.Bar   ' 'Bar' is a type

' namespace alias:
Imports foo = SomeNamespace
' type alias:
Imports bar = SomeNamespace.SomeType
using Foo;
using static Foo.Bar;   // 'Bar' is a type

// namespace alias:
using foo = SomeNamespace;
// type alias:
using bar = SomeNamespace.SomeType;
VB.NET C#
Option Infer On
...
' the type of x is inferred as 'Integer', but without 'Option Infer On', x type is 'Object':
Dim x = 3
// the type of x is inferred as 'int':
var x = 3;

Basic Inheritance

VB.NET C#
Class Foo
    Inherits SomeBase

End Class
class Foo : SomeBase
{
}

Inheritance Keywords

VB.NET C#
MustInherit    (class)
MustOverride    (method)
Overrides    (method)
NotInheritable    (class)
NotOverridable    (method)
Shadows    (member)
abstract    (class)
abstract    (method)
override    (method)
sealed    (class)
sealed    (method)
new    (member)

Defining Interfaces

VB.NET C#
Public Interface IFoo
    Sub Method()
End Interface
public interface IFoo
{
    void Method();
}

Implementing Interfaces

VB.NET C#
Public Class Foo
    Implements IFoo

    Public Sub Method() Implements IFoo.Method
    End Sub
End Class

*note that you can use a method name which does not match the interface method name, provided that the 'Implements' name matches
// implicit implementation:
public class Foo : IFoo
{
    public void Method()
    {
    }
}

or:

// explicit implementation:
public class Foo : IFoo
{
    void IFoo.Method()
    {
    }
}
VB.NET C#
Public Iterator Function OneToHundred() As IEnumerable(Of Integer)
    For x = 1 To 100
        Yield x
    Next
End Function
public IEnumerable<int> OneToHundred()
{
    for (var x = 1; x <= 100; x++)
    {
        yield return x;
    }
}

Expression Lambda

VB.NET C#
myVar = Function(text As String) text.Length myVar = (string text) => text.Length;

Multi-statement Lambda

VB.NET C#
myVar = Function(text As String)
    Return text.Length
End Function
myVar = (string text) =>
{
    return text.Length;
}

Event Wireup with Lambda

VB.NET C#
AddHandler button.Click, Sub(src, e) Log(src, e) button.Click += (src, e) => Log(src, e);

VB uses the 'CreateObject' function to acquire a type instance at run-time where the type is unknown at compile time. VB then allows invoking members of this instance even though the type is unknown at compile time (this requires the VB Option Strict setting to be turned off). C# allows the same behavior via the 'dynamic' keyword and a call to System.Activator.CreateInstance:

VB.NET C#
Option Strict Off
...
Dim o As Object = CreateObject(progID)
o.Foo() ' late-bound call to 'Foo' method
dynamic o = System.Activator.CreateInstance(System.Type.GetTypeFromProgID(progID));
o.Foo(); // late-bound call to 'Foo' method
VB.NET C#
Dim payingCustomers = From c In db.Customers
        Where c.Orders.Count > 0
        Select c.Email, c.Name

Dim numbers() As Integer = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }
Dim numberGroups = From n In numbers
        Group n By GroupKey = n Mod 5 Into g = Group
        Select New With {Key .Remainder = GroupKey, Key .Numbers = g}
var payingCustomers = from c in db.Customers
        where c.Orders.Count > 0
        select new {c.Email, c.Name};

int[] numbers = {5, 4, 1, 3, 9, 8, 6, 7, 2, 0};
var numberGroups = from n in numbers
        group n by n % 5 into g
        let GroupKey = g.Key
        select new
        {
            Remainder = GroupKey,
            Numbers = g
        }

For Loop

VB.NET C#
For i As Integer = 1 To 9
Next i

For i As Integer = 9 to 1 Step -1
Next i
for (int i = 1; i <= 9; i++)
{
}

for (int i = 9; i >= 1; i--)
{
}

For Each Loop

VB.NET C#
For Each s As String In StringList
Next s
foreach (string s in StringList)
{
}

While Loop

VB.NET C#
Do While condition
Loop

or:

While condition
End While
while (condition)
{
}

Do While Loop

VB.NET C#
Do
Loop While condition
do
{
} while (condition);

Loop Until

VB.NET C# (no specific 'loop until' construct)
Do
Loop Until condition
do
{
} while ( ! condition);

Do Until

VB.NET C# (no specific 'do until' construct)
Do Until condition
Loop
while ( ! condition)
{
}
VB.NET C#
Public Module Utility
    Public Sub UtilityMethod()
        ...
    End Sub
End Module

or:

Public NotInheritable Class Utility
    Private Sub New() ' prevent instantiation
    End Sub

    Public Shared Sub UtilityMethod()
        ...
    End Sub
End Class
public static class Utility
{
    public static void UtilityMethod()
    {
        ...
    }
}

Inequality tests in VB using the '<>' operator involving nullable types (System.Nullable with a struct type parameter) behave very differently from the same tests in C# using the '!=' operator. In VB, if the nullable value is 'Nothing', then the conditional always evaluates to 'Nothing', which has the same effect as the conditional being 'False'. In C#, if the nullable value is 'null', then the conditional evaluates to 'true' when comparing to a non-null value.

The root of the inconsistency between VB and C# is that the '==' and '!=' operators are interpreted in C# exactly as if you were calling the System.Nullable 'Equals' method, while the VB '=' and '<>' operators are not, instead relying on the unusual VB language rules. To get the expected conditional results, the correct VB approach would be to use the System.Nullable 'Equals' method.

VB C#
Dim nullInt As Integer? = Nothing
If nullInt <> 0 Then
   ' statement doesn't execute!
   MessageBox.Show("not equal")
End If
int? nullInt = null;
if (nullInt != 0)
   // statement executes as expected
   MessageBox.Show("not equal");

The following C# reproduces the VB behavior:

C#
int? nullInt = null;
if (nullInt.HasValue && nullInt != 0)
   // statement doesn't execute
   MessageBox.Show("not equal");

The following VB is a better approach and is equivalent to our original C# code:

VB C#
Dim nullInt As Integer? = Nothing
If Not nullInt.Equals(0) Then
   ' statement executes as expected
   MessageBox.Show("not equal")
End If
int? nullInt = null;
if (nullInt != 0)
   // statement executes as expected
   MessageBox.Show("not equal");

Note that you can call a method on a 'null' System.Nullable instance without throwing an exception since nullable instances are never null like reference types are null. Instead, they are generic value types with no value set.

Named Type Object Initializer

VB.NET C#
Dim myVar = New Person() With
{
    .Name = s1,
    .Surname = s2
}
var myVar = new Person()
{
    Name = s1,
    Surname = s2
};

Anonymous Type Object Initializer

VB.NET C#
Dim myVar = New With
{
   .Name = x,
   .Surname = y
}
var myVar = new
{
   Name = x,
   Surname = y
};
VB.NET C#
Public Shared Operator *(ByVal X As SomeType, ByVal Y As SomeType) As Long
    ' ...
End Operator

Public Shared Narrowing Operator CType(ByVal X As SomeType) As SomeOtherType
    ' ...
End Operator

Public Shared Widening Operator CType(ByVal X As SomeOtherType) As SomeType
    ' ...
End Operator
public static long operator * (SomeType X, SomeType Y)
{
    // ...
}

public static explicit operator SomeOtherType(SomeType X)
{
    // ...
}

public static implicit operator SomeType(SomeOtherType X)
{
    // ...
}
VB.NET C#
Dim a As Integer = 1
Dim b As Integer = 2
Dim c As Integer = 3

' Mod has lower precedence than division:
Dim doubleResult1 = a Mod b / c

' int division has lower precedence than regular division:
Dim longResult = a \ b / c

' exponentiation has the highest operator precedence in VB:
Dim doubleResult2 = a / (b ^ 2 + 4)

' VB Not has fairly low precedence, unlike C# ! or ~
Dim intResult = Not a * b

' Xor has the lowest precedence, unlike C# '^':
Dim bool1 As Boolean = True
Dim bool2 As Boolean = False
Dim bool3 As Boolean = True
Dim bool4 As Boolean = False
Dim boolResult = bool1 AndAlso bool2 Xor bool3 AndAlso bool4
int a = 1;
int b = 2;
int c = 3;

// Mod has lower precedence than division:
var doubleResult1 = a % (b / (double)c);

// int division has lower precedence than regular division:
var longResult = a / Convert.ToInt32(b / (double)c);

// exponentiation has the highest operator precedence in VB:
var doubleResult2 = a / (Math.Pow(b, 2) + 4);

// VB Not has fairly low precedence, unlike C# ! or ~
var intResult = ~(a * b);

// Xor has the lowest precedence, unlike C# '^':
bool bool1 = true;
bool bool2 = false;
bool bool3 = true;
bool bool4 = false;
var boolResult = (bool1 && bool2) ^ (bool3 && bool4);
VB.NET C#
+, -, *, >, >=, <, <=, <<, >>
&    (string concatenation)
()    (indexing)
And
Or
AndAlso
OrElse
Not
Xor
Mod
x ^ y
=
Is
IsNot
<>
TypeOf x Is y

If(x, y, z)
If(x, y)

x / y    (where x and y are integers)
x / y    (where x and y are doubles)
x \ y    (where x and y are integers)
x \ y    (where x and y are doubles)
unchanged
+    (string concatenation)
[]
&
|
&&
||
! or ~    (depending on context)
^
%
Math.Pow(x, y)    (no exponentiation operator)
= or ==    (depending on context)
==
!=
!=
x is y

x ? y : z
x ?? y

x / (double)y
x / y
x / y
Convert.ToInt32(x) / Convert.ToInt32(y)

Optional Parameters

VB.NET C#
Sub Method(p1 As Integer, Optional p2 As Integer = 0) public void Method(int p1, int p2 = 0)

Named Arguments

VB.NET C#
Method(p1 := 2, p2 := 3) Method(p1 : 2, p2 : 3)
VB.NET C#
Sub Method(ParamArray myParam As Object())
End Sub
void Method(params object[] myParam)
{
}

Property

VB.NET C#
Property MyProperty() As Integer
    Get
        Return field
    End Get
    Set (ByVal var As Integer)
        field = var
    End Set
End Property
public int MyProperty
{
    get
    {
        return field;
    }
    set
    {
        field = value;
    }
}

Indexer

VB.NET C#
Private field() As Integer

Public Default Property Item(ByVal index As Integer) As Integer
    Get
        Return field(index)
    End Get
    Set(ByVal value As Integer)
        field(index) = value
    End Set
End Property
private int[] field

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

Parameterized Property

VB.NET C# (no direct equivalent)
Public Property ParameterizedProperty(ByVal i As Integer) As Integer
    Get
        Return GetFoo(i)
    End Get
    Set(value As Integer)
        SetFoo(i, value)
    End Set
End Property
public int ParameterizedProperty(int i)
{
    return GetFoo(i);
}
public void set_ParameterizedProperty(int i, int value)
{
    SetFoo(i, value);
}

For simple cases, VB's Select construct can be converted to the C# switch construct. However, if any of the Case statements include range or non-constant expressions, then the equivalent is an if/else block.

VB C#
' converts to switch:
Select Case x
    Case 0
        ...
    Case 1
        ...
End Select

' converts to if/else:
Select Case x
    Case 1 To 3
        ...
    Case Is < 10, Is > 20, Is = 15
        ...
End Select
// converts to switch:
switch (x)
{
    case 0:
        // ...
        break;
    case 1:
        // ...
        break;
}

// converts to if/else:
if (x >= 1 && x <= 3)
{
    ...
}
else if ((x < 10) || (x > 20) || (x == 15))
{
    ...
}
VB.NET C#
SyncLock x
    ...
End SyncLock
lock (x)
{
    ...
}
VB.NET C#
Public Function TupleReturningMethod1() As (first As String, middle As String, last As String)
    ' ... retrieve first, middle and last
    Return (first, middle, last)
End Function

' option to not assign names to tuple fields:
Public Function TupleReturningMethod2() As (String, String, String)
    ' ... retrieve first, middle and last
    Return (first, middle, last)
End Function

Public Sub CallTupleMethods()
    ' assign the tuple to an implicitly-type variable:
    Dim names = TupleReturningMethod1()
    ' can reference different parts of tuple via 'Item1', 'Item2', etc.
    Foo(names.Item1, names.Item3)
    ' can reference different parts via names provided in tuple header:
    Foo(names.first, names.last)

    ' assign the tuple to an explicitly-typed variable:
    Dim tuple As (first1 As String, middle1 As String, last1 As String) = TupleReturningMethod1()
End Sub
public (string first, string middle, string last) TupleReturningMethod1()
{
    // ... retrieve first, middle and last
    return (first, middle, last);
}

// option to not assign names to tuple fields:
public (string, string, string) TupleReturningMethod2()
{
    // ... retrieve first, middle and last
    return (first, middle, last);
}

public void CallTupleMethods()
{
    // assign the tuple to an implicitly-type variable:
    var names = TupleReturningMethod1();
    // can reference different parts of tuple via 'Item1', 'Item2', etc.
    Foo(names.Item1, names.Item3);
    // can reference different parts via names provided in tuple header:
    Foo(names.first, names.last);

    // assign the tuple to an explicitly-typed variable:
    (string first1, string middle1, string last1) tuple = TupleReturningMethod1();
}
VB.NET C#
x = TypeOf y Is z
v = GetType(w)
x = y is z;
v = typeof(w);

VB has two ways of declaring access to an unmanaged dll — the legacy 'Declare' syntax and the modern DllImport syntax (which is also used by C#).

VB.NET C#
' legacy syntax:
Public Declare Function APIFunction Lib "kernel32"(ByRef x As String) As Integer

' modern syntax:
Imports System.Runtime.InteropServices

<DllImport("kernel32")>
Public Shared Function APIFunction(ByRef x As String) As Integer
End Function
using System.Runtime.InteropServices;

[DllImport("kernel32")]
public static extern int APIFunction(ref string x);

VB 'Collection' is a strange beast in that it allows treating the collection alternately as a keyed list or positional list so a single .NET collection will not capture everything. Also, it's 1-based, unlike nearly all other collections.

The following helper class is used by Instant C# to replicate the behavior of the VB Collection class. Note that it's intended as a drop-in replacement so it uses 1-based parameters. The replacement collection is made generic as an improvement — replace 'Collection' with 'Collection<object>' for the exact VB equivalent, but you should specify the actual type even though you don't do this in VB.

// ----------------------------------------------------------------------------------------
// Copyright © 2003 - 2024 Tangible Software Solutions, Inc.
// This class can be used by anyone provided that the copyright notice remains intact.
//
// This class replicates the behavior of the VB Collection class.
// ----------------------------------------------------------------------------------------
using System.Collections.Generic;
using System;

public class Collection<T>
{
    private List<T> objects = new List<T>();
    private List<string> keys = new List<string>();

    public void Add(T newObject, string key = null, object before = null, object after = null)
    {
        if (after != null)
        {
            if (after as string != null)
                Insert(newObject, keys.IndexOf(after as string) + 1, key);
            else
                Insert(newObject, (int)after, key);
        }
        else if (before != null)
        {
            if (before as string != null)
                Insert(newObject, keys.IndexOf(before as string), key);
            else
                Insert(newObject, (int)before - 1, key);
        }
        else
            Insert(newObject, objects.Count, key);
    }

    private void Insert(T newObject, int index, string key)
    {
        objects.Insert(index, newObject);
        keys.Insert(index, key);
    }

    public void Clear()
    {
        objects.Clear();
        keys.Clear();
    }

    public bool Contains(string key)
    {
        return keys.Contains(key);
    }

    public int Count
    {
        get
        {
            return objects.Count;
        }
    }

    public void Remove(string key)
    {
        RemoveAt(keys.IndexOf(key));
    }

    public void Remove(int positionOneBased)
    {
        RemoveAt(positionOneBased - 1);
    }

    private void RemoveAt(int index)
    {
        objects.RemoveAt(index);
        keys.RemoveAt(index);
    }

    public T this[int positionOneBased]
    {
        get
        {
            return objects[positionOneBased - 1];
        }
    }

    public T this[string key]
    {
        get
        {
            return objects[keys.IndexOf(key)];
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return objects.GetEnumerator();
    }
}

Instant C# converts calls to the legacy VB DateDiff function and IsDate function via the following helper class inserted into the conversion output:

// ----------------------------------------------------------------------------------------
// Copyright © 2003 - 2024 Tangible Software Solutions, Inc.
// This class can be used by anyone provided that the copyright notice remains intact.
//
// The methods in this class replicate the behavior of IsDate and DateDiff.
// ----------------------------------------------------------------------------------------
using System;
using System.Globalization;

public static class DateHelper
{
    public static bool IsDate(object expression)
    {
        if (expression == null)
            return false;

        DateTime testDate;
        return DateTime.TryParse(expression.ToString(), out testDate);
    }

    public enum DateInterval
    {
        Day,
        DayOfYear,
        Hour,
        Minute,
        Month,
        Quarter,
        Second,
        Weekday,
        WeekOfYear,
        Year
    }

    public static long DateDiff(DateInterval intervalType, DateTime dateOne, DateTime dateTwo)
    {
        switch (intervalType)
        {
            case DateInterval.Day:
            case DateInterval.DayOfYear:
                TimeSpan spanForDays = dateTwo - dateOne;
                return (long)spanForDays.TotalDays;
            case DateInterval.Hour:
                TimeSpan spanForHours = dateTwo - dateOne;
                return (long)spanForHours.TotalHours;
            case DateInterval.Minute:
                TimeSpan spanForMinutes = dateTwo - dateOne;
                return (long)spanForMinutes.TotalMinutes;
            case DateInterval.Month:
                return ((dateTwo.Year - dateOne.Year) * 12) + (dateTwo.Month - dateOne.Month);
            case DateInterval.Quarter:
                long dateOneQuarter = (long)Math.Ceiling(dateOne.Month / 3.0);
                long dateTwoQuarter = (long)Math.Ceiling(dateTwo.Month / 3.0);
                return (4 * (dateTwo.Year - dateOne.Year)) + dateTwoQuarter - dateOneQuarter;
            case DateInterval.Second:
                TimeSpan spanForSeconds = dateTwo - dateOne;
                return (long)spanForSeconds.TotalSeconds;
            case DateInterval.Weekday:
                TimeSpan spanForWeekdays = dateTwo - dateOne;
                return (long)(spanForWeekdays.TotalDays / 7.0);
            case DateInterval.WeekOfYear:
                DateTime dateOneModified = dateOne;
                DateTime dateTwoModified = dateTwo;
                while (dateTwoModified.DayOfWeek != DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek)
                {
                    dateTwoModified = dateTwoModified.AddDays(-1);
                }
                while (dateOneModified.DayOfWeek != DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek)
                {
                    dateOneModified = dateOneModified.AddDays(-1);
                }
                TimeSpan spanForWeekOfYear = dateTwoModified - dateOneModified;
                return (long)(spanForWeekOfYear.TotalDays / 7.0);
            case DateInterval.Year:
                return dateTwo.Year - dateOne.Year;
            default:
                return 0;
        }
    }
}

Only simple cases of VB legacy error handling can be converted to C#.

VB.NET C#
Public Sub LegacyErrorHandling()

    On Error GoTo ErrorHandler
    ' ... main logic

ErrorHandler:
    ' ... error handling code
End Sub
public void LegacyErrorHandling()
{
    try
    {
        // ... main logic
    }
    catch
    {
        // ... error handling code
    }
}

Instead of 'Return', VB also allows assigning to the function name — this value will be returned by the function, but execution continues until an 'Exit Function', 'End Function', or 'Return' statement is reached.

VB.NET C#
Function Typical() As Short
    Typical = 2
    Foo()
End Function
public short Typical()
{
    short tempTypical = 2;
    Foo();
    return tempTypical;
}

Instant C# converts calls to the legacy VB IsNumeric and Val functions via the following helper class inserted into the conversion output:

// ----------------------------------------------------------------------------------------
// Copyright © 2003 - 2024 Tangible Software Solutions, Inc.
// This class can be used by anyone provided that the copyright notice remains intact.
//
// The methods in this class replicate the behavior of IsNumeric and Val.
// ----------------------------------------------------------------------------------------
using System;
using System.Globalization;

public static class NumericHelper
{
    public static bool IsNumeric(object expression)
    {
        if (expression == null)
            return false;

        double testDouble;
        if (expression is string)
        {
            CultureInfo provider;
            if (((string)expression).StartsWith("$"))
                provider = new CultureInfo("en-US");
            else
                provider = CultureInfo.InvariantCulture;

            if (double.TryParse((string)expression, NumberStyles.Any, provider, out testDouble))
                return true;
        }
        else
        {
            if (double.TryParse(expression.ToString(), out testDouble))
                return true;
        }

        // VB's 'IsNumeric' returns true for any boolean value:
        bool testBool;
        if (bool.TryParse(expression.ToString(), out testBool))
            return true;

        return false;
    }

    public static double Val(string expression)
    {
        if (expression == null)
            return 0;

        // try the entire string, then progressively smaller substrings to replicate the behavior of VB's 'Val', which ignores trailing characters after a recognizable value:
        for (int size = expression.Length; size > 0; size--)
        {
            double testDouble;
            if (double.TryParse(expression.Substring(0, size), out testDouble))
                return testDouble;
        }

        // no value is recognized, so return 0:
        return 0;
    }

    public static double Val(object expression)
    {
        if (expression == null)
            return 0;

        double testDouble;
        if (double.TryParse(expression.ToString(), out testDouble))
            return testDouble;

        // VB's 'Val' function returns -1 for 'true':
        bool testBool;
        if (bool.TryParse(expression.ToString(), out testBool))
            return testBool ? -1 : 0;

        // VB's 'Val' function returns the day of the month for dates:
        DateTime testDate;
        if (DateTime.TryParse(expression.ToString(), out testDate))
            return testDate.Day;

        // no value is recognized, so return 0:
        return 0;
    }

    public static int Val(char expression)
    {
        int testInt;
        if (int.TryParse(expression.ToString(), out testInt))
            return testInt;
        else
            return 0;
    }
}

Instant C# converts calls to the legacy VB Mid statement (unrelated to the Mid function) via the following helper class inserted into the conversion output:

// ----------------------------------------------------------------------------------------
// Copyright © 2003 - 2024 Tangible Software Solutions, Inc.
// This class can be used by anyone provided that the copyright notice remains intact.
//
// The methods in this class replicate the behavior of miscellaneous VB features.
// ----------------------------------------------------------------------------------------
using System;

public static class ConversionHelper
{
    public static void MidStatement(ref string target, int oneBasedStart, char insert)
    {
        // These 'MidStatement' method overloads replicate the behavior of the VB 'Mid' statement (which is unrelated to the VB 'Mid' function)

        if (target == null)
            return;

        target = target.Remove(oneBasedStart - 1, 1).Insert(oneBasedStart - 1, insert.ToString());
    }

    public static void MidStatement(ref string target, int oneBasedStart, string insert)
    {
        // These 'MidStatement' method overloads replicate the behavior of the VB 'Mid' statement (which is unrelated to the VB 'Mid' function)

        if (target == null || insert == null)
            return;

        target = target.PadRight(target.Length + insert.Length).Remove(oneBasedStart - 1, insert.Length).Insert(oneBasedStart - 1, insert).Substring(0, target.Length);
    }

    public static void MidStatement(ref string target, int oneBasedStart, string insert, int length)
    {
        // These 'MidStatement' method overloads replicate the behavior of the VB 'Mid' statement (which is unrelated to the VB 'Mid' function)

        if (target == null || insert == null)
            return;

        int minLength = Math.Min(insert.Length, length);
        target = target.PadRight(target.Length + insert.Length).Remove(oneBasedStart - 1, minLength).Insert(oneBasedStart - 1, insert.Substring(0, minLength)).Substring(0, target.Length);
    }

    // ... more helper methods
}

VB.NET C# (omitting null handling)
LCase(x)
UCase(x)
Left(x, 2)
Right(x, 2)
Trim(x)
LTrim(x)
RTrim(x)
Mid(x, 3)
InStr(x, y)
InStrRev(x, y)
x.ToLower()
x.ToUpper()
x.Substring(0, 2)
x.Substring(x.Length - 2)
x.Trim(' ')
x.TrimStart(' ')
x.TrimEnd(' ')
x.Substring(2)
x.IndexOf(y) + 1
x.LastIndexOf(y) + 1

These are rarely used, but still supported.

VB.NET C#
Dim myIntegerVar%
Dim myStringVar$
Dim myFloatVar!
Dim myDoubleVar#
int myIntegerVar = 0;
string myStringVar = null;
float myFloatVar = 0;
double myDoubleVar = 0;
VB.NET C#
' VB 2015 multiline string:
Dim myVar = "first line
    second line"
// verbatim string:
var myVar = @"first line
    second line";

// C#11 raw string literal:
var myVar = """
first line
    second line
""";

Copyright © 2004 – 2024 Tangible Software Solutions, Inc.