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.